Add Open in New Window and reexecution on import change (#6379)
* Quick prototype: open in new window in file tree * WIP: refresh on imported file change * Fix up reexecution * Clean up * Add test 'Assembly gets reexecuted when imported models are updated externally' * Clean up
This commit is contained in:
@ -6,6 +6,7 @@ import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
|||||||
import {
|
import {
|
||||||
executorInputPath,
|
executorInputPath,
|
||||||
getUtils,
|
getUtils,
|
||||||
|
kclSamplesPath,
|
||||||
testsInputPath,
|
testsInputPath,
|
||||||
} from '@e2e/playwright/test-utils'
|
} from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
@ -472,4 +473,94 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'Assembly gets reexecuted when imported models are updated externally',
|
||||||
|
{ tag: ['@electron'] },
|
||||||
|
async ({ context, page, homePage, scene, toolbar, cmdBar, tronApp }) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
const midPoint = { x: 500, y: 250 }
|
||||||
|
const washerPoint = { x: 645, y: 250 }
|
||||||
|
const partColor: [number, number, number] = [120, 120, 120]
|
||||||
|
const redPartColor: [number, number, number] = [200, 0, 0]
|
||||||
|
const bgColor: [number, number, number] = [30, 30, 30]
|
||||||
|
const tolerance = 50
|
||||||
|
const projectName = 'assembly'
|
||||||
|
|
||||||
|
await test.step('Setup parts and expect imported model', async () => {
|
||||||
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
const projectDir = path.join(dir, projectName)
|
||||||
|
await fsp.mkdir(projectDir, { recursive: true })
|
||||||
|
await Promise.all([
|
||||||
|
fsp.copyFile(
|
||||||
|
executorInputPath('cube.kcl'),
|
||||||
|
path.join(projectDir, 'cube.kcl')
|
||||||
|
),
|
||||||
|
fsp.copyFile(
|
||||||
|
kclSamplesPath(
|
||||||
|
path.join(
|
||||||
|
'pipe-flange-assembly',
|
||||||
|
'mcmaster-parts',
|
||||||
|
'98017a257-washer.step'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
path.join(projectDir, 'foreign.step')
|
||||||
|
),
|
||||||
|
fsp.writeFile(
|
||||||
|
path.join(projectDir, 'main.kcl'),
|
||||||
|
`
|
||||||
|
import "cube.kcl" as cube
|
||||||
|
import "foreign.step" as foreign
|
||||||
|
cube
|
||||||
|
foreign
|
||||||
|
|> translate(x = 40, z = 10)`
|
||||||
|
),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.openProject(projectName)
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
await scene.expectPixelColor(partColor, midPoint, tolerance)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Change imported kcl file and expect change', async () => {
|
||||||
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
// Append appearance to the cube.kcl file
|
||||||
|
await fsp.appendFile(
|
||||||
|
path.join(dir, projectName, 'cube.kcl'),
|
||||||
|
`\n |> appearance(color = "#ff0000")`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
await scene.expectPixelColor(redPartColor, midPoint, tolerance)
|
||||||
|
await scene.expectPixelColor(partColor, washerPoint, tolerance)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Change imported step file and expect change', async () => {
|
||||||
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
// Replace the washer with a pipe
|
||||||
|
await fsp.copyFile(
|
||||||
|
kclSamplesPath(
|
||||||
|
path.join(
|
||||||
|
'pipe-flange-assembly',
|
||||||
|
'mcmaster-parts',
|
||||||
|
'1120t74-pipe.step'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
path.join(dir, projectName, 'foreign.step')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
// Expect pipe to take over the red cube but leave some space where the washer was
|
||||||
|
await scene.expectPixelColor(partColor, midPoint, tolerance)
|
||||||
|
await scene.expectPixelColor(bgColor, washerPoint, tolerance)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -1024,6 +1024,10 @@ export function testsInputPath(fileName: string): string {
|
|||||||
return path.join('rust', 'kcl-lib', 'tests', 'inputs', fileName)
|
return path.join('rust', 'kcl-lib', 'tests', 'inputs', fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function kclSamplesPath(fileName: string): string {
|
||||||
|
return path.join('public', 'kcl-samples', fileName)
|
||||||
|
}
|
||||||
|
|
||||||
export async function doAndWaitForImageDiff(
|
export async function doAndWaitForImageDiff(
|
||||||
page: Page,
|
page: Page,
|
||||||
fn: () => Promise<unknown>,
|
fn: () => Promise<unknown>,
|
||||||
|
1
interface.d.ts
vendored
1
interface.d.ts
vendored
@ -20,6 +20,7 @@ export interface IElectronAPI {
|
|||||||
open: typeof dialog.showOpenDialog
|
open: typeof dialog.showOpenDialog
|
||||||
save: typeof dialog.showSaveDialog
|
save: typeof dialog.showSaveDialog
|
||||||
openExternal: typeof shell.openExternal
|
openExternal: typeof shell.openExternal
|
||||||
|
openInNewWindow: (name: string) => void
|
||||||
takeElectronWindowScreenshot: ({
|
takeElectronWindowScreenshot: ({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
@ -194,6 +194,14 @@ export const FileMachineProvider = ({
|
|||||||
navigate(`..${PATHS.FILE}/${encodeURIComponent(event.output.path)}`)
|
navigate(`..${PATHS.FILE}/${encodeURIComponent(event.output.path)}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
openFileInNewWindow: ({ event }) => {
|
||||||
|
if (event.type !== 'Open file in new window') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
commandBarActor.send({ type: 'Close' })
|
||||||
|
window.electron.openInNewWindow(event.data.name)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actors: {
|
actors: {
|
||||||
readFiles: fromPromise(async ({ input }) => {
|
readFiles: fromPromise(async ({ input }) => {
|
||||||
|
@ -24,7 +24,7 @@ import { sortFilesAndDirectories } from '@src/lib/desktopFS'
|
|||||||
import useHotkeyWrapper from '@src/lib/hotkeyWrapper'
|
import useHotkeyWrapper from '@src/lib/hotkeyWrapper'
|
||||||
import { PATHS } from '@src/lib/paths'
|
import { PATHS } from '@src/lib/paths'
|
||||||
import type { FileEntry } from '@src/lib/project'
|
import type { FileEntry } from '@src/lib/project'
|
||||||
import { codeManager, kclManager } from '@src/lib/singletons'
|
import { codeManager, kclManager, rustContext } from '@src/lib/singletons'
|
||||||
import { reportRejection } from '@src/lib/trap'
|
import { reportRejection } from '@src/lib/trap'
|
||||||
import type { IndexLoaderData } from '@src/lib/types'
|
import type { IndexLoaderData } from '@src/lib/types'
|
||||||
|
|
||||||
@ -32,6 +32,7 @@ import { ToastInsert } from '@src/components/ToastInsert'
|
|||||||
import { commandBarActor } from '@src/lib/singletons'
|
import { commandBarActor } from '@src/lib/singletons'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import styles from './FileTree.module.css'
|
import styles from './FileTree.module.css'
|
||||||
|
import { jsAppSettings } from '@src/lib/settings/settingsUtils'
|
||||||
|
|
||||||
function getIndentationCSS(level: number) {
|
function getIndentationCSS(level: number) {
|
||||||
return `calc(1rem * ${level + 1})`
|
return `calc(1rem * ${level + 1})`
|
||||||
@ -163,6 +164,7 @@ const FileTreeItem = ({
|
|||||||
onCreateFile,
|
onCreateFile,
|
||||||
onCreateFolder,
|
onCreateFolder,
|
||||||
onCloneFileOrFolder,
|
onCloneFileOrFolder,
|
||||||
|
onOpenInNewWindow,
|
||||||
newTreeEntry,
|
newTreeEntry,
|
||||||
level = 0,
|
level = 0,
|
||||||
treeSelection,
|
treeSelection,
|
||||||
@ -183,6 +185,7 @@ const FileTreeItem = ({
|
|||||||
onCreateFile: (name: string) => void
|
onCreateFile: (name: string) => void
|
||||||
onCreateFolder: (name: string) => void
|
onCreateFolder: (name: string) => void
|
||||||
onCloneFileOrFolder: (path: string) => void
|
onCloneFileOrFolder: (path: string) => void
|
||||||
|
onOpenInNewWindow: (path: string) => void
|
||||||
newTreeEntry: TreeEntry
|
newTreeEntry: TreeEntry
|
||||||
level?: number
|
level?: number
|
||||||
treeSelection: FileEntry | undefined
|
treeSelection: FileEntry | undefined
|
||||||
@ -212,10 +215,25 @@ const FileTreeItem = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: make this not just name based but sub path instead
|
||||||
|
const isImportedInCurrentFile = kclManager.ast.body.some(
|
||||||
|
(n) =>
|
||||||
|
n.type === 'ImportStatement' &&
|
||||||
|
((n.path.type === 'Kcl' &&
|
||||||
|
n.path.filename.includes(fileOrDir.name)) ||
|
||||||
|
(n.path.type === 'Foreign' && n.path.path.includes(fileOrDir.name)))
|
||||||
|
)
|
||||||
|
|
||||||
if (isCurrentFile && eventType === 'change') {
|
if (isCurrentFile && eventType === 'change') {
|
||||||
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
|
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
|
||||||
code = normalizeLineEndings(code)
|
code = normalizeLineEndings(code)
|
||||||
codeManager.updateCodeStateEditor(code)
|
codeManager.updateCodeStateEditor(code)
|
||||||
|
} else if (isImportedInCurrentFile && eventType === 'change') {
|
||||||
|
await rustContext.clearSceneAndBustCache(
|
||||||
|
{ settings: await jsAppSettings() },
|
||||||
|
codeManager?.currentFilePath || undefined
|
||||||
|
)
|
||||||
|
await kclManager.executeAst()
|
||||||
}
|
}
|
||||||
fileSend({ type: 'Refresh' })
|
fileSend({ type: 'Refresh' })
|
||||||
},
|
},
|
||||||
@ -439,6 +457,7 @@ const FileTreeItem = ({
|
|||||||
onCreateFile={onCreateFile}
|
onCreateFile={onCreateFile}
|
||||||
onCreateFolder={onCreateFolder}
|
onCreateFolder={onCreateFolder}
|
||||||
onCloneFileOrFolder={onCloneFileOrFolder}
|
onCloneFileOrFolder={onCloneFileOrFolder}
|
||||||
|
onOpenInNewWindow={onOpenInNewWindow}
|
||||||
newTreeEntry={newTreeEntry}
|
newTreeEntry={newTreeEntry}
|
||||||
lastDirectoryClicked={lastDirectoryClicked}
|
lastDirectoryClicked={lastDirectoryClicked}
|
||||||
onClickDirectory={onClickDirectory}
|
onClickDirectory={onClickDirectory}
|
||||||
@ -479,6 +498,7 @@ const FileTreeItem = ({
|
|||||||
onRename={addCurrentItemToRenaming}
|
onRename={addCurrentItemToRenaming}
|
||||||
onDelete={() => setIsConfirmingDelete(true)}
|
onDelete={() => setIsConfirmingDelete(true)}
|
||||||
onClone={() => onCloneFileOrFolder(fileOrDir.path)}
|
onClone={() => onCloneFileOrFolder(fileOrDir.path)}
|
||||||
|
onOpenInNewWindow={() => onOpenInNewWindow(fileOrDir.path)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -489,6 +509,7 @@ interface FileTreeContextMenuProps {
|
|||||||
onRename: () => void
|
onRename: () => void
|
||||||
onDelete: () => void
|
onDelete: () => void
|
||||||
onClone: () => void
|
onClone: () => void
|
||||||
|
onOpenInNewWindow: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function FileTreeContextMenu({
|
function FileTreeContextMenu({
|
||||||
@ -496,6 +517,7 @@ function FileTreeContextMenu({
|
|||||||
onRename,
|
onRename,
|
||||||
onDelete,
|
onDelete,
|
||||||
onClone,
|
onClone,
|
||||||
|
onOpenInNewWindow,
|
||||||
}: FileTreeContextMenuProps) {
|
}: FileTreeContextMenuProps) {
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
const metaKey = platform === 'macos' ? '⌘' : 'Ctrl'
|
const metaKey = platform === 'macos' ? '⌘' : 'Ctrl'
|
||||||
@ -525,6 +547,12 @@ function FileTreeContextMenu({
|
|||||||
>
|
>
|
||||||
Clone
|
Clone
|
||||||
</ContextMenuItem>,
|
</ContextMenuItem>,
|
||||||
|
<ContextMenuItem
|
||||||
|
data-testid="context-menu-open-in-new-window"
|
||||||
|
onClick={onOpenInNewWindow}
|
||||||
|
>
|
||||||
|
Open in new window
|
||||||
|
</ContextMenuItem>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -636,11 +664,21 @@ export const useFileTreeOperations = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openInNewWindow(args: { path: string }) {
|
||||||
|
send({
|
||||||
|
type: 'Open file in new window',
|
||||||
|
data: {
|
||||||
|
name: args.path,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createFile,
|
createFile,
|
||||||
createFolder,
|
createFolder,
|
||||||
cloneFileOrDir,
|
cloneFileOrDir,
|
||||||
newTreeEntry,
|
newTreeEntry,
|
||||||
|
openInNewWindow,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -648,8 +686,13 @@ export const FileTree = ({
|
|||||||
className = '',
|
className = '',
|
||||||
onNavigateToFile: closePanel,
|
onNavigateToFile: closePanel,
|
||||||
}: FileTreeProps) => {
|
}: FileTreeProps) => {
|
||||||
const { createFile, createFolder, cloneFileOrDir, newTreeEntry } =
|
const {
|
||||||
useFileTreeOperations()
|
createFile,
|
||||||
|
createFolder,
|
||||||
|
cloneFileOrDir,
|
||||||
|
openInNewWindow,
|
||||||
|
newTreeEntry,
|
||||||
|
} = useFileTreeOperations()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
@ -666,6 +709,7 @@ export const FileTree = ({
|
|||||||
onCreateFile={(name: string) => createFile({ dryRun: false, name })}
|
onCreateFile={(name: string) => createFile({ dryRun: false, name })}
|
||||||
onCreateFolder={(name: string) => createFolder({ dryRun: false, name })}
|
onCreateFolder={(name: string) => createFolder({ dryRun: false, name })}
|
||||||
onCloneFileOrFolder={(path: string) => cloneFileOrDir({ path })}
|
onCloneFileOrFolder={(path: string) => cloneFileOrDir({ path })}
|
||||||
|
onOpenInNewWindow={(path: string) => openInNewWindow({ path })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -676,11 +720,13 @@ export const FileTreeInner = ({
|
|||||||
onCreateFile,
|
onCreateFile,
|
||||||
onCreateFolder,
|
onCreateFolder,
|
||||||
onCloneFileOrFolder,
|
onCloneFileOrFolder,
|
||||||
|
onOpenInNewWindow,
|
||||||
newTreeEntry,
|
newTreeEntry,
|
||||||
}: {
|
}: {
|
||||||
onCreateFile: (name: string) => void
|
onCreateFile: (name: string) => void
|
||||||
onCreateFolder: (name: string) => void
|
onCreateFolder: (name: string) => void
|
||||||
onCloneFileOrFolder: (path: string) => void
|
onCloneFileOrFolder: (path: string) => void
|
||||||
|
onOpenInNewWindow: (path: string) => void
|
||||||
newTreeEntry: TreeEntry
|
newTreeEntry: TreeEntry
|
||||||
onNavigateToFile?: () => void
|
onNavigateToFile?: () => void
|
||||||
}) => {
|
}) => {
|
||||||
@ -792,6 +838,7 @@ export const FileTreeInner = ({
|
|||||||
onCreateFile={onCreateFile}
|
onCreateFile={onCreateFile}
|
||||||
onCreateFolder={onCreateFolder}
|
onCreateFolder={onCreateFolder}
|
||||||
onCloneFileOrFolder={onCloneFileOrFolder}
|
onCloneFileOrFolder={onCloneFileOrFolder}
|
||||||
|
onOpenInNewWindow={onOpenInNewWindow}
|
||||||
newTreeEntry={newTreeEntry}
|
newTreeEntry={newTreeEntry}
|
||||||
onClickDirectory={onClickDirectory}
|
onClickDirectory={onClickDirectory}
|
||||||
onNavigateToFile={onNavigateToFile_}
|
onNavigateToFile={onNavigateToFile_}
|
||||||
|
@ -132,8 +132,13 @@ export const sidebarPanes: SidebarPane[] = [
|
|||||||
icon: 'folder',
|
icon: 'folder',
|
||||||
sidebarName: 'Project Files',
|
sidebarName: 'Project Files',
|
||||||
Content: (props: { id: SidebarType; onClose: () => void }) => {
|
Content: (props: { id: SidebarType; onClose: () => void }) => {
|
||||||
const { createFile, createFolder, cloneFileOrDir, newTreeEntry } =
|
const {
|
||||||
useFileTreeOperations()
|
createFile,
|
||||||
|
createFolder,
|
||||||
|
cloneFileOrDir,
|
||||||
|
openInNewWindow,
|
||||||
|
newTreeEntry,
|
||||||
|
} = useFileTreeOperations()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -155,6 +160,7 @@ export const sidebarPanes: SidebarPane[] = [
|
|||||||
createFolder({ dryRun: false, name })
|
createFolder({ dryRun: false, name })
|
||||||
}
|
}
|
||||||
onCloneFileOrFolder={(path: string) => cloneFileOrDir({ path })}
|
onCloneFileOrFolder={(path: string) => cloneFileOrDir({ path })}
|
||||||
|
onOpenInNewWindow={(path: string) => openInNewWindow({ path })}
|
||||||
newTreeEntry={newTreeEntry}
|
newTreeEntry={newTreeEntry}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -10,6 +10,7 @@ type FileMachineContext = {
|
|||||||
|
|
||||||
type FileMachineEvents =
|
type FileMachineEvents =
|
||||||
| { type: 'Open file'; data: { name: string } }
|
| { type: 'Open file'; data: { name: string } }
|
||||||
|
| { type: 'Open file in new window'; data: { name: string } }
|
||||||
| {
|
| {
|
||||||
type: 'Rename file'
|
type: 'Rename file'
|
||||||
data: { oldName: string; newName: string; isDir: boolean }
|
data: { oldName: string; newName: string; isDir: boolean }
|
||||||
@ -95,6 +96,7 @@ export const fileMachine = setup({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
navigateToFile: () => {},
|
navigateToFile: () => {},
|
||||||
|
openFileInNewWindow: () => {},
|
||||||
renameToastSuccess: () => {},
|
renameToastSuccess: () => {},
|
||||||
createToastSuccess: () => {},
|
createToastSuccess: () => {},
|
||||||
toastSuccess: () => {},
|
toastSuccess: () => {},
|
||||||
@ -213,6 +215,10 @@ export const fileMachine = setup({
|
|||||||
target: 'Opening file',
|
target: 'Opening file',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Open file in new window': {
|
||||||
|
target: 'Opening file in new window',
|
||||||
|
},
|
||||||
|
|
||||||
'Set selected directory': {
|
'Set selected directory': {
|
||||||
target: 'Has files',
|
target: 'Has files',
|
||||||
actions: ['setSelectedDirectory'],
|
actions: ['setSelectedDirectory'],
|
||||||
@ -400,6 +406,10 @@ export const fileMachine = setup({
|
|||||||
entry: ['navigateToFile'],
|
entry: ['navigateToFile'],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Opening file in new window': {
|
||||||
|
entry: ['openFileInNewWindow'],
|
||||||
|
},
|
||||||
|
|
||||||
'Creating file': {
|
'Creating file': {
|
||||||
invoke: {
|
invoke: {
|
||||||
src: 'createFile',
|
src: 'createFile',
|
||||||
|
@ -283,6 +283,10 @@ ipcMain.handle('shell.openExternal', (event, data) => {
|
|||||||
return shell.openExternal(data)
|
return shell.openExternal(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('openInNewWindow', (event, data) => {
|
||||||
|
return createWindow(data)
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
'take.screenshot',
|
'take.screenshot',
|
||||||
async (event, data: { width: number; height: number }) => {
|
async (event, data: { width: number; height: number }) => {
|
||||||
|
@ -21,6 +21,7 @@ const resizeWindow = (width: number, height: number) =>
|
|||||||
const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args)
|
const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args)
|
||||||
const save = (args: any) => ipcRenderer.invoke('dialog.showSaveDialog', args)
|
const save = (args: any) => ipcRenderer.invoke('dialog.showSaveDialog', args)
|
||||||
const openExternal = (url: any) => ipcRenderer.invoke('shell.openExternal', url)
|
const openExternal = (url: any) => ipcRenderer.invoke('shell.openExternal', url)
|
||||||
|
const openInNewWindow = (url: any) => ipcRenderer.invoke('openInNewWindow', url)
|
||||||
const takeElectronWindowScreenshot = ({
|
const takeElectronWindowScreenshot = ({
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@ -248,6 +249,7 @@ contextBridge.exposeInMainWorld('electron', {
|
|||||||
save,
|
save,
|
||||||
// opens the URL
|
// opens the URL
|
||||||
openExternal,
|
openExternal,
|
||||||
|
openInNewWindow,
|
||||||
showInFolder,
|
showInFolder,
|
||||||
getPath,
|
getPath,
|
||||||
packageJson,
|
packageJson,
|
||||||
|
Reference in New Issue
Block a user