This commit is contained in:
Mike Sawka 2024-10-31 12:34:30 -07:00 committed by GitHub
parent 502f9515d8
commit 473225d94b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 149 additions and 38 deletions

View File

@ -53,6 +53,10 @@ const config = {
minimumSystemVersion: "10.15.0",
mergeASARs: true,
singleArchFiles: "dist/bin/wavesrv.*",
plist: {
NSMicrophoneUsageDescription:
"This app requires microphone access to enable audio input for terminal applications.",
},
},
linux: {
artifactName: "${name}-${platform}-${arch}-${version}.${ext}",

View File

@ -95,7 +95,6 @@ console.log = log;
console.log(
sprintf(
"waveterm-app starting, data_dir=%s, config_dir=%s electronpath=%s gopath=%s arch=%s/%s",
waveDataDir,
waveConfigDir,
getElectronAppBasePath(),
getElectronAppUnpackedBasePath(),
@ -402,6 +401,13 @@ electron.ipcMain.on("quicklook", (event, filePath: string) => {
}
});
electron.ipcMain.on("open-native-path", (event, filePath: string) => {
console.log("open-native-path", filePath);
electron.shell.openPath(filePath).catch((err) => {
console.error(`Failed to open path ${filePath}:`, err);
});
});
async function createNewWaveWindow(): Promise<void> {
const clientData = await services.ClientService.GetClientData();
const fullConfig = await services.FileService.GetFullConfig();
@ -547,8 +553,7 @@ function convertMenuDefArrToMenu(menuDefArr: ElectronContextMenuItem[]): electro
type: menuDef.type,
click: (_, window) => {
const ww = window as WaveBrowserWindow;
const tabView = ww.activeTabView;
tabView?.webContents?.send("contextmenu-click", menuDef.id);
ww?.activeTabView?.webContents?.send("contextmenu-click", menuDef.id);
},
checked: menuDef.checked,
};

View File

@ -78,6 +78,17 @@ function getAppMenu(callbacks: AppMenuCallbacks): Electron.Menu {
}
);
}
appMenu.push({
label: "Permissions",
submenu: [
{
label: "Request Audio Access",
click: (_, window) => {
getWindowWebContents(window)?.send("request-audio-access");
},
},
],
});
appMenu.push({
role: "quit",
});

View File

@ -47,6 +47,7 @@ contextBridge.exposeInMainWorld("api", {
onWaveInit: (callback) => ipcRenderer.on("wave-init", (_event, initOpts) => callback(initOpts)),
sendLog: (log) => ipcRenderer.send("fe-log", log),
onQuicklook: (filePath: string) => ipcRenderer.send("quicklook", filePath),
openNativePath: (filePath: string) => ipcRenderer.send("open-native-path", filePath),
});
// Custom event for "new-window"
@ -60,3 +61,15 @@ ipcRenderer.on("webcontentsid-from-blockid", (e, blockId, responseCh) => {
const wcId = webviewElem?.dataset?.webcontentsid;
ipcRenderer.send(responseCh, wcId);
});
ipcRenderer.on("request-audio-access", (e) => {
ipcRenderer.send("fe-log", "Requesting audio access");
navigator.mediaDevices
.getUserMedia({ audio: true })
.then(() => {
ipcRenderer.send("fe-log", "Audio access granted");
})
.catch((err) => {
ipcRenderer.send("fe-log", "Audio access denied: " + err);
});
});

View File

@ -375,12 +375,24 @@ const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => {
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", nodeModel.blockId));
const style: React.CSSProperties = {};
let showBlockMask = false;
if (!isFocused && blockData?.meta?.["frame:bordercolor"]) {
style.borderColor = blockData.meta["frame:bordercolor"];
}
if (isFocused && blockData?.meta?.["frame:bordercolor:focused"]) {
style.borderColor = blockData.meta["frame:bordercolor:focused"];
if (isFocused) {
const tabData = jotai.useAtomValue(atoms.tabAtom);
const tabActiveBorderColor = tabData?.meta?.["bg:activebordercolor"];
if (tabActiveBorderColor) {
style.borderColor = tabActiveBorderColor;
}
if (blockData?.meta?.["frame:activebordercolor"]) {
style.borderColor = blockData.meta["frame:activebordercolor"];
}
} else {
const tabData = jotai.useAtomValue(atoms.tabAtom);
const tabBorderColor = tabData?.meta?.["bg:bordercolor"];
if (tabBorderColor) {
style.borderColor = tabBorderColor;
}
if (blockData?.meta?.["frame:bordercolor"]) {
style.borderColor = blockData.meta["frame:bordercolor"];
}
}
let innerElem = null;
if (isLayoutMode) {

View File

@ -111,6 +111,7 @@ interface CodeEditorProps {
text: string;
filename: string;
language?: string;
meta?: MetaType;
onChange?: (text: string) => void;
onMount?: (monacoPtr: MonacoTypes.editor.IStandaloneCodeEditor, monaco: Monaco) => () => void;
}
@ -125,7 +126,7 @@ const stickyScrollEnabledAtom = atom((get) => {
return settings["editor:stickyscrollenabled"] ?? false;
});
export function CodeEditor({ text, language, filename, onChange, onMount }: CodeEditorProps) {
export function CodeEditor({ text, language, filename, meta, onChange, onMount }: CodeEditorProps) {
const divRef = useRef<HTMLDivElement>(null);
const unmountRef = useRef<() => void>(null);
const minimapEnabled = useAtomValue(minimapEnabledAtom);
@ -157,8 +158,9 @@ export function CodeEditor({ text, language, filename, onChange, onMount }: Code
const opts = defaultEditorOptions();
opts.minimap.enabled = minimapEnabled;
opts.stickyScroll.enabled = stickyScrollEnabled;
opts.wordWrap = meta?.["editor:wordwrap"] ? "on" : "off";
return opts;
}, [minimapEnabled, stickyScrollEnabled]);
}, [minimapEnabled, stickyScrollEnabled, meta?.["editor:wordwrap"]]);
return (
<div className="code-editor-wrapper">

View File

@ -337,6 +337,13 @@ function DirectoryTable({
);
}
function getNormFilePath(finfo: FileInfo): string {
if (finfo.isdir) {
return finfo.dir;
}
return finfo.dir + "/" + finfo.name;
}
interface TableBodyProps {
bodyRef: React.RefObject<HTMLDivElement>;
model: PreviewModel;
@ -391,10 +398,25 @@ function TableBody({
}, [focusIndex]);
const handleFileContextMenu = useCallback(
(e: any, path: string, mimetype: string) => {
(e: any, finfo: FileInfo) => {
e.preventDefault();
e.stopPropagation();
const fileName = path.split("/").pop();
if (finfo == null) {
return;
}
const normPath = getNormFilePath(finfo);
const fileName = finfo.path.split("/").pop();
let openNativeLabel = "Open File";
if (finfo.isdir) {
openNativeLabel = "Open Directory in File Manager";
if (PLATFORM == "darwin") {
openNativeLabel = "Open Directory in Finder";
} else if (PLATFORM == "win32") {
openNativeLabel = "Open Directory in Explorer";
}
} else {
openNativeLabel = "Open File in Default Application";
}
const menu: ContextMenuItem[] = [
{
label: "Copy File Name",
@ -402,7 +424,7 @@ function TableBody({
},
{
label: "Copy Full File Name",
click: () => navigator.clipboard.writeText(path),
click: () => navigator.clipboard.writeText(finfo.path),
},
{
label: "Copy File Name (Shell Quoted)",
@ -410,7 +432,7 @@ function TableBody({
},
{
label: "Copy Full File Name (Shell Quoted)",
click: () => navigator.clipboard.writeText(shellQuote([path])),
click: () => navigator.clipboard.writeText(shellQuote([finfo.path])),
},
{
type: "separator",
@ -418,7 +440,16 @@ function TableBody({
{
label: "Download File",
click: async () => {
getApi().downloadFile(path);
getApi().downloadFile(normPath);
},
},
{
type: "separator",
},
{
label: openNativeLabel,
click: async () => {
getApi().openNativePath(normPath);
},
},
{
@ -430,14 +461,14 @@ function TableBody({
const blockDef: BlockDef = {
meta: {
view: "preview",
file: path,
file: finfo.path,
},
};
await createBlock(blockDef);
},
},
];
if (mimetype == "directory") {
if (finfo.mimetype == "directory") {
menu.push({
label: "Open Terminal in New Block",
click: async () => {
@ -445,7 +476,7 @@ function TableBody({
meta: {
controller: "shell",
view: "term",
"cmd:cwd": path,
"cmd:cwd": finfo.path,
},
};
await createBlock(termBlockDef);
@ -456,7 +487,7 @@ function TableBody({
menu.push({
label: "Delete File",
click: async () => {
await FileService.DeleteFile(conn, path).catch((e) => console.log(e));
await FileService.DeleteFile(conn, finfo.path).catch((e) => console.log(e));
setRefreshVersion((current) => current + 1);
},
});
@ -477,7 +508,7 @@ function TableBody({
setSearch("");
}}
onClick={() => setFocusIndex(idx)}
onContextMenu={(e) => handleFileContextMenu(e, row.getValue("path"), row.getValue("mimetype"))}
onContextMenu={(e) => handleFileContextMenu(e, row.original)}
>
{row.getVisibleCells().map((cell) => (
<div

View File

@ -62,6 +62,7 @@ const textApplicationMimetypes = [
"application/x-latex",
"application/x-sh",
"application/x-python",
"application/x-awk",
];
function isTextFile(mimeType: string): boolean {
@ -619,6 +620,7 @@ export class PreviewModel implements ViewModel {
getSettingsMenuItems(): ContextMenuItem[] {
const menuItems: ContextMenuItem[] = [];
const blockData = globalStore.get(this.blockAtom);
menuItems.push({
label: "Copy Full Path",
click: async () => {
@ -670,6 +672,18 @@ export class PreviewModel implements ViewModel {
click: this.handleFileRevert.bind(this),
});
}
menuItems.push({ type: "separator" });
menuItems.push({
label: "Word Wrap",
type: "checkbox",
checked: blockData?.meta?.["editor:wordwrap"] ?? false,
click: () => {
const blockOref = WOS.makeORef("block", this.blockId);
services.ObjectService.UpdateObjectMeta(blockOref, {
"editor:wordwrap": !blockData?.meta?.["editor:wordwrap"],
});
},
});
}
}
return menuItems;
@ -803,13 +817,14 @@ function CodeEditPreview({ model }: SpecializedViewProps) {
const fileContent = useAtomValue(model.fileContent);
const setNewFileContent = useSetAtom(model.newFileContent);
const fileName = useAtomValue(model.statFilePath);
const blockMeta = useAtomValue(model.blockAtom)?.meta;
function codeEditKeyDownHandler(e: WaveKeyboardEvent): boolean {
if (checkKeyPressed(e, "Cmd:e")) {
model.setEditMode(false);
return true;
}
if (checkKeyPressed(e, "Cmd:s")) {
if (checkKeyPressed(e, "Cmd:s") || checkKeyPressed(e, "Ctrl:s")) {
model.handleFileSave();
return true;
}
@ -852,6 +867,7 @@ function CodeEditPreview({ model }: SpecializedViewProps) {
<CodeEditor
text={fileContent}
filename={fileName}
meta={blockMeta}
onChange={(text) => setNewFileContent(text)}
onMount={onMount}
/>

View File

@ -132,7 +132,7 @@ class TermViewModel {
this.blockBg = jotai.atom((get) => {
const blockData = get(this.blockAtom);
const fullConfig = get(atoms.fullConfigAtom);
let themeName: string = globalStore.get(getSettingsKeyAtom("term:theme"));
let themeName: string = get(getSettingsKeyAtom("term:theme"));
if (blockData?.meta?.["term:theme"]) {
themeName = blockData.meta["term:theme"];
}

View File

@ -94,6 +94,7 @@ declare global {
onWaveInit: (callback: (initOpts: WaveInitOpts) => void) => void;
sendLog: (log: string) => void;
onQuicklook: (filePath: string) => void;
openNativePath(filePath: string): void;
};
type ElectronContextMenuItem = {

View File

@ -315,7 +315,7 @@ declare global {
"frame:*"?: boolean;
frame?: boolean;
"frame:bordercolor"?: string;
"frame:bordercolor:focused"?: string;
"frame:activebordercolor"?: string;
"frame:title"?: string;
"frame:icon"?: string;
"frame:text"?: string;
@ -340,6 +340,8 @@ declare global {
"ai:apiversion"?: string;
"ai:maxtokens"?: number;
"ai:timeoutms"?: number;
"editor:*"?: boolean;
"editor:wordwrap"?: boolean;
"graph:*"?: boolean;
"graph:numpoints"?: number;
"graph:metrics"?: string[];
@ -348,6 +350,8 @@ declare global {
bg?: string;
"bg:opacity"?: number;
"bg:blendmode"?: string;
"bg:bordercolor"?: string;
"bg:activebordercolor"?: string;
"term:*"?: boolean;
"term:fontsize"?: number;
"term:fontfamily"?: string;

View File

@ -32,7 +32,7 @@ const (
MetaKey_FrameClear = "frame:*"
MetaKey_Frame = "frame"
MetaKey_FrameBorderColor = "frame:bordercolor"
MetaKey_FrameBorderColor_Focused = "frame:bordercolor:focused"
MetaKey_FrameActiveBorderColor = "frame:activebordercolor"
MetaKey_FrameTitle = "frame:title"
MetaKey_FrameIcon = "frame:icon"
MetaKey_FrameText = "frame:text"
@ -60,6 +60,9 @@ const (
MetaKey_AiMaxTokens = "ai:maxtokens"
MetaKey_AiTimeoutMs = "ai:timeoutms"
MetaKey_EditorClear = "editor:*"
MetaKey_EditorWordWrap = "editor:wordwrap"
MetaKey_GraphClear = "graph:*"
MetaKey_GraphNumPoints = "graph:numpoints"
MetaKey_GraphMetrics = "graph:metrics"
@ -70,6 +73,8 @@ const (
MetaKey_Bg = "bg"
MetaKey_BgOpacity = "bg:opacity"
MetaKey_BgBlendMode = "bg:blendmode"
MetaKey_BgBorderColor = "bg:bordercolor"
MetaKey_BgActiveBorderColor = "bg:activebordercolor"
MetaKey_TermClear = "term:*"
MetaKey_TermFontSize = "term:fontsize"

View File

@ -28,13 +28,13 @@ type MetaTSType struct {
Icon string `json:"icon,omitempty"`
IconColor string `json:"icon:color,omitempty"`
FrameClear bool `json:"frame:*,omitempty"`
Frame bool `json:"frame,omitempty"`
FrameBorderColor string `json:"frame:bordercolor,omitempty"`
FrameBorderColor_Focused string `json:"frame:bordercolor:focused,omitempty"`
FrameTitle string `json:"frame:title,omitempty"`
FrameIcon string `json:"frame:icon,omitempty"`
FrameText string `json:"frame:text,omitempty"`
FrameClear bool `json:"frame:*,omitempty"`
Frame bool `json:"frame,omitempty"`
FrameBorderColor string `json:"frame:bordercolor,omitempty"`
FrameActiveBorderColor string `json:"frame:activebordercolor,omitempty"`
FrameTitle string `json:"frame:title,omitempty"`
FrameIcon string `json:"frame:icon,omitempty"`
FrameText string `json:"frame:text,omitempty"`
CmdClear bool `json:"cmd:*,omitempty"`
Cmd string `json:"cmd,omitempty"`
@ -60,6 +60,9 @@ type MetaTSType struct {
AiMaxTokens float64 `json:"ai:maxtokens,omitempty"`
AiTimeoutMs float64 `json:"ai:timeoutms,omitempty"`
EditorClear bool `json:"editor:*,omitempty"`
EditorWordWrap bool `json:"editor:wordwrap,omitempty"`
GraphClear bool `json:"graph:*,omitempty"`
GraphNumPoints int `json:"graph:numpoints,omitempty"`
GraphMetrics []string `json:"graph:metrics,omitempty"`
@ -67,10 +70,12 @@ type MetaTSType struct {
SysinfoType string `json:"sysinfo:type,omitempty"`
// for tabs
BgClear bool `json:"bg:*,omitempty"`
Bg string `json:"bg,omitempty"`
BgOpacity float64 `json:"bg:opacity,omitempty"`
BgBlendMode string `json:"bg:blendmode,omitempty"`
BgClear bool `json:"bg:*,omitempty"`
Bg string `json:"bg,omitempty"`
BgOpacity float64 `json:"bg:opacity,omitempty"`
BgBlendMode string `json:"bg:blendmode,omitempty"`
BgBorderColor string `json:"bg:bordercolor,omitempty"` // frame:bordercolor
BgActiveBorderColor string `json:"bg:activebordercolor,omitempty"` // frame:activebordercolor
TermClear bool `json:"term:*,omitempty"`
TermFontSize int `json:"term:fontsize,omitempty"`

View File

@ -23,14 +23,16 @@
"display:order": 1.1,
"bg:*": true,
"bg": "blue",
"bg:opacity": 0.3
"bg:opacity": 0.3,
"bg:activebordercolor": "rgba(0, 0, 255, 1.0)"
},
"bg@red": {
"display:name": "Red",
"display:order": 1.3,
"bg:*": true,
"bg": "red",
"bg:opacity": 0.3
"bg:opacity": 0.3,
"bg:activebordercolor": "rgba(255, 0, 0, 1.0)"
},
"bg@ocean-depths": {
"display:name": "Ocean Depths",