Refactored electron code and added spotlight feature.
This commit is contained in:
parent
d86a0a3c09
commit
5e7e9510fb
@ -400,3 +400,10 @@ body {
|
|||||||
.ant-btn-variant-outlined.ant-btn.ant-btn-icon-only {
|
.ant-btn-variant-outlined.ant-btn.ant-btn-icon-only {
|
||||||
min-width: 32px;
|
min-width: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.electron-spotlight-content .ant-input-outlined {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: 1px solid transparent !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|||||||
@ -46,6 +46,7 @@
|
|||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"gcode-preview": "^2.18.0",
|
"gcode-preview": "^2.18.0",
|
||||||
"keycloak-js": "^26.2.0",
|
"keycloak-js": "^26.2.0",
|
||||||
|
"keytar": "^7.9.0",
|
||||||
"loglevel": "^1.9.2",
|
"loglevel": "^1.9.2",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"online-3d-viewer": "^0.16.0",
|
"online-3d-viewer": "^0.16.0",
|
||||||
@ -73,10 +74,10 @@
|
|||||||
"description": "3D Printer ERP and Control Software.",
|
"description": "3D Printer ERP and Control Software.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env NODE_ENV=development vite",
|
"dev": "cross-env NODE_ENV=development vite",
|
||||||
"electron": "cross-env ELECTRON_START_URL=http://0.0.0.0:5173 && cross-env NODE_ENV=development && electron .",
|
"electron": "cross-env ELECTRON_START_URL=http://0.0.0.0:5780 && cross-env NODE_ENV=development && electron .",
|
||||||
"start": "serve -s build",
|
"start": "serve -s build",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"dev:electron": "concurrently \"cross-env NODE_ENV=development vite --no-open\" \"cross-env ELECTRON_START_URL=http://localhost:5173 cross-env NODE_ENV=development electron public/electron.js\"",
|
"dev:electron": "concurrently \"cross-env NODE_ENV=development vite --no-open\" \"cross-env ELECTRON_START_URL=http://localhost:5780 cross-env NODE_ENV=development electron public/electron.js\"",
|
||||||
"build:electron": "vite build && electron-builder",
|
"build:electron": "vite build && electron-builder",
|
||||||
"deploy": "vite build && wrangler pages deploy"
|
"deploy": "vite build && wrangler pages deploy"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,62 +1,44 @@
|
|||||||
import { app, BrowserWindow, ipcMain, shell, Menu } from 'electron'
|
import { app, ipcMain, shell, globalShortcut } from 'electron'
|
||||||
import path, { dirname } from 'path'
|
import { createRequire } from 'module'
|
||||||
import { fileURLToPath } from 'url'
|
import {
|
||||||
|
registerGlobalShortcuts,
|
||||||
|
setupSpotlightIPC
|
||||||
|
} from './spotlightWindow.js'
|
||||||
|
import {
|
||||||
|
createWindow,
|
||||||
|
setupMainWindowIPC,
|
||||||
|
setupMainWindowAppEvents,
|
||||||
|
setupDevAuthServer
|
||||||
|
} from './mainWindow.js'
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
// --- Keytar-backed auth session storage (main process) ---
|
||||||
const __dirname = dirname(__filename)
|
const require = createRequire(import.meta.url)
|
||||||
|
let keytar = null
|
||||||
let win
|
try {
|
||||||
|
// keytar is a native module; in some dev environments it may not be built yet.
|
||||||
function createWindow() {
|
keytar = require('keytar')
|
||||||
win = new BrowserWindow({
|
} catch (e) {
|
||||||
width: 1200,
|
console.warn(
|
||||||
height: 800,
|
'[keytar] Not available; auth session persistence will be disabled.',
|
||||||
frame: false,
|
e?.message || e
|
||||||
titleBarStyle: 'hiddenInset',
|
)
|
||||||
trafficLightPosition: { x: 14, y: 12 },
|
|
||||||
backgroundColor: '#141414',
|
|
||||||
icon: path.join(__dirname, './logo512.png'),
|
|
||||||
webPreferences: {
|
|
||||||
nodeIntegration: true,
|
|
||||||
contextIsolation: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const KEYTAR_SERVICE = app.name || 'Farm Control'
|
||||||
|
const KEYTAR_ACCOUNT = 'authSession'
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
createWindow()
|
||||||
|
registerGlobalShortcuts()
|
||||||
|
setupSpotlightIPC()
|
||||||
|
setupMainWindowIPC()
|
||||||
|
setupMainWindowAppEvents(app)
|
||||||
|
setupDevAuthServer()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Set up custom menu bar
|
app.on('will-quit', () => {
|
||||||
const env = (process.env.NODE_ENV || 'development').trim()
|
globalShortcut.unregisterAll()
|
||||||
if (env === 'development') {
|
})
|
||||||
const devMenu = [
|
|
||||||
{
|
|
||||||
label: 'Developer',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Toggle Developer Tools',
|
|
||||||
accelerator:
|
|
||||||
process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
|
||||||
click: () => {
|
|
||||||
win.webContents.toggleDevTools()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
const menu = Menu.buildFromTemplate(devMenu)
|
|
||||||
Menu.setApplicationMenu(menu)
|
|
||||||
} else {
|
|
||||||
Menu.setApplicationMenu(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
// For development, load from localhost; for production, load the built index.html
|
|
||||||
if (process.env.ELECTRON_START_URL) {
|
|
||||||
win.loadURL(process.env.ELECTRON_START_URL)
|
|
||||||
} else {
|
|
||||||
win.loadFile(path.join(__dirname, '../build/index.html'))
|
|
||||||
}
|
|
||||||
|
|
||||||
setupWindowEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
app.whenReady().then(createWindow)
|
|
||||||
|
|
||||||
// IPC handler to get OS
|
// IPC handler to get OS
|
||||||
ipcMain.handle('os-info', () => {
|
ipcMain.handle('os-info', () => {
|
||||||
@ -65,112 +47,46 @@ ipcMain.handle('os-info', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// IPC handler to get window state
|
ipcMain.handle('auth-session-get', async () => {
|
||||||
ipcMain.handle('window-state', () => {
|
try {
|
||||||
return {
|
if (!keytar) return null
|
||||||
isFullScreen: win ? win.isFullScreen() : false,
|
const raw = await keytar.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT)
|
||||||
isMaximized: win ? win.isMaximized() : false
|
if (!raw) return null
|
||||||
|
return JSON.parse(raw)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[keytar] Failed to read auth session.', e?.message || e)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Emit events to renderer when window is maximized/unmaximized
|
ipcMain.handle('auth-session-set', async (event, session) => {
|
||||||
function setupWindowEvents() {
|
try {
|
||||||
if (!win) return
|
if (!keytar) return false
|
||||||
win.on('maximize', () => {
|
if (!session || typeof session !== 'object') return false
|
||||||
win.webContents.send('window-state', {
|
await keytar.setPassword(
|
||||||
isMaximized: true
|
KEYTAR_SERVICE,
|
||||||
})
|
KEYTAR_ACCOUNT,
|
||||||
})
|
JSON.stringify(session)
|
||||||
win.on('enter-full-screen', () => {
|
)
|
||||||
console.log('Entered fullscreen')
|
return true
|
||||||
win.webContents.send('window-state', {
|
} catch (e) {
|
||||||
isFullScreen: true
|
console.warn('[keytar] Failed to write auth session.', e?.message || e)
|
||||||
})
|
return false
|
||||||
})
|
|
||||||
win.on('leave-full-screen', () => {
|
|
||||||
win.webContents.send('window-state', {
|
|
||||||
isFullScreen: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
win.on('unmaximize', () => {
|
|
||||||
win.webContents.send('window-state', {
|
|
||||||
isMaximized: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPC handlers for window controls
|
|
||||||
ipcMain.on('window-control', (event, action) => {
|
|
||||||
if (!win) return
|
|
||||||
switch (action) {
|
|
||||||
case 'minimize':
|
|
||||||
win.minimize()
|
|
||||||
break
|
|
||||||
case 'maximize':
|
|
||||||
if (win.isMaximized()) {
|
|
||||||
win.unmaximize()
|
|
||||||
} else {
|
|
||||||
win.maximize()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'close':
|
|
||||||
win.close()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add this after other ipcMain handlers
|
ipcMain.handle('auth-session-clear', async () => {
|
||||||
|
try {
|
||||||
|
if (!keytar) return false
|
||||||
|
return await keytar.deletePassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[keytar] Failed to clear auth session.', e?.message || e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// IPC handler for opening external URLs
|
||||||
ipcMain.handle('open-external-url', (event, url) => {
|
ipcMain.handle('open-external-url', (event, url) => {
|
||||||
console.log('Opening external url...')
|
console.log('Opening external url...')
|
||||||
shell.openExternal(url)
|
shell.openExternal(url)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('open-url', (event, url) => {
|
|
||||||
event.preventDefault()
|
|
||||||
console.log('App opened with URL:', url)
|
|
||||||
if (url.startsWith('farmcontrol://app')) {
|
|
||||||
// Extract the path/query after 'farmcontrol://app'
|
|
||||||
const redirectPath = url.replace('farmcontrol://app', '') || '/'
|
|
||||||
if (win && win.webContents) {
|
|
||||||
win.webContents.send('navigate', redirectPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
|
||||||
if (process.platform !== 'darwin') app.quit()
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('activate', () => {
|
|
||||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
|
||||||
})
|
|
||||||
|
|
||||||
const env = (process.env.NODE_ENV || 'development').trim()
|
|
||||||
console.log(env)
|
|
||||||
if (env == 'development') {
|
|
||||||
console.log('Starting development auth web server...')
|
|
||||||
import('express').then(({ default: express }) => {
|
|
||||||
const app = express()
|
|
||||||
const port = 3500
|
|
||||||
|
|
||||||
app.use((req, res) => {
|
|
||||||
const redirectPath = req.originalUrl
|
|
||||||
res.send(
|
|
||||||
`Open Farmcontrol to continue... (Redirect path: ${redirectPath})`
|
|
||||||
)
|
|
||||||
if (win && win.webContents) {
|
|
||||||
win.webContents.send('navigate', redirectPath)
|
|
||||||
win.show()
|
|
||||||
win.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.listen(port, () => {
|
|
||||||
console.log(`Dev auth server running on http://localhost:${port}`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
console.log('Will use url scheme instead of auth server.')
|
|
||||||
}
|
|
||||||
|
|||||||
196
public/mainWindow.js
Normal file
196
public/mainWindow.js
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import { BrowserWindow, ipcMain, Menu } from 'electron'
|
||||||
|
import path, { dirname } from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
|
let win
|
||||||
|
|
||||||
|
function attachKeyboardShortcuts(browserWindow) {
|
||||||
|
if (!browserWindow) return
|
||||||
|
// Keyboard shortcuts for the main window can be added here if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupWindowEvents() {
|
||||||
|
if (!win) return
|
||||||
|
win.on('maximize', () => {
|
||||||
|
win.webContents.send('window-state', {
|
||||||
|
isMaximized: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
win.on('enter-full-screen', () => {
|
||||||
|
console.log('Entered fullscreen')
|
||||||
|
win.webContents.send('window-state', {
|
||||||
|
isFullScreen: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
win.on('leave-full-screen', () => {
|
||||||
|
win.webContents.send('window-state', {
|
||||||
|
isFullScreen: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
win.on('unmaximize', () => {
|
||||||
|
win.webContents.send('window-state', {
|
||||||
|
isMaximized: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWindow() {
|
||||||
|
win = new BrowserWindow({
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
frame: false,
|
||||||
|
titleBarStyle: 'hiddenInset',
|
||||||
|
trafficLightPosition: { x: 14, y: 12 },
|
||||||
|
backgroundColor: '#141414',
|
||||||
|
icon: path.join(__dirname, './logo512.png'),
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set up custom menu bar
|
||||||
|
const env = (process.env.NODE_ENV || 'development').trim()
|
||||||
|
if (env === 'development') {
|
||||||
|
const devMenu = [
|
||||||
|
{
|
||||||
|
label: 'Developer',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Toggle Developer Tools',
|
||||||
|
accelerator:
|
||||||
|
process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||||
|
click: () => {
|
||||||
|
win.webContents.toggleDevTools()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const menu = Menu.buildFromTemplate(devMenu)
|
||||||
|
Menu.setApplicationMenu(menu)
|
||||||
|
} else {
|
||||||
|
Menu.setApplicationMenu(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For development, load from localhost; for production, load the built index.html
|
||||||
|
if (process.env.ELECTRON_START_URL) {
|
||||||
|
win.loadURL(process.env.ELECTRON_START_URL)
|
||||||
|
} else {
|
||||||
|
win.loadFile(path.join(__dirname, '../build/index.html'))
|
||||||
|
}
|
||||||
|
|
||||||
|
setupWindowEvents()
|
||||||
|
attachKeyboardShortcuts(win)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWindow() {
|
||||||
|
return win
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupMainWindowIPC() {
|
||||||
|
// IPC handler to get window state
|
||||||
|
ipcMain.handle('window-state', () => {
|
||||||
|
if (!win || win.isDestroyed())
|
||||||
|
return { isFullScreen: false, isMaximized: false }
|
||||||
|
return {
|
||||||
|
isFullScreen: win ? win.isFullScreen() : false,
|
||||||
|
isMaximized: win ? win.isMaximized() : false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// IPC handlers for window controls
|
||||||
|
ipcMain.on('window-control', (event, action) => {
|
||||||
|
if (!win) return
|
||||||
|
switch (action) {
|
||||||
|
case 'minimize':
|
||||||
|
win.minimize()
|
||||||
|
break
|
||||||
|
case 'maximize':
|
||||||
|
if (win.isMaximized()) {
|
||||||
|
win.unmaximize()
|
||||||
|
} else {
|
||||||
|
win.maximize()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'close':
|
||||||
|
win.close()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('open-internal-url', (event, url) => {
|
||||||
|
if (!win || win.isDestroyed()) {
|
||||||
|
createWindow()
|
||||||
|
// Wait for window to finish loading before sending navigate event
|
||||||
|
win.webContents.once('did-finish-load', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
win.webContents.send('navigate', url)
|
||||||
|
win.show()
|
||||||
|
win.focus()
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
win.webContents.send('navigate', url)
|
||||||
|
win.show()
|
||||||
|
win.focus()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupMainWindowAppEvents(app) {
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') app.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('open-url', (event, url) => {
|
||||||
|
event.preventDefault()
|
||||||
|
console.log('App opened with URL:', url)
|
||||||
|
if (url.startsWith('farmcontrol://app')) {
|
||||||
|
// Extract the path/query after 'farmcontrol://app'
|
||||||
|
const redirectPath = url.replace('farmcontrol://app', '') || '/'
|
||||||
|
if (win && win.webContents) {
|
||||||
|
win.webContents.send('navigate', redirectPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupDevAuthServer() {
|
||||||
|
const env = (process.env.NODE_ENV || 'development').trim()
|
||||||
|
if (env == 'development') {
|
||||||
|
console.log('Starting development auth web server...')
|
||||||
|
import('express').then(({ default: express }) => {
|
||||||
|
const app = express()
|
||||||
|
const port = 3500
|
||||||
|
|
||||||
|
app.use((req, res) => {
|
||||||
|
const redirectPath = req.originalUrl
|
||||||
|
res.send(
|
||||||
|
`Open Farmcontrol to continue... (Redirect path: ${redirectPath})`
|
||||||
|
)
|
||||||
|
if (win && win.webContents) {
|
||||||
|
win.webContents.send('navigate', redirectPath)
|
||||||
|
win.show()
|
||||||
|
win.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Dev auth server running on http://localhost:${port}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log('Will use url scheme instead of auth server.')
|
||||||
|
}
|
||||||
|
}
|
||||||
136
public/spotlightWindow.js
Normal file
136
public/spotlightWindow.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { BrowserWindow, ipcMain, globalShortcut } from 'electron'
|
||||||
|
import path, { dirname } from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
|
let spotlightWin
|
||||||
|
|
||||||
|
function getSpotlightRouteUrl() {
|
||||||
|
const routePath = '/dashboard/electron/spotlightcontent'
|
||||||
|
|
||||||
|
// Dev: BrowserRouter, so use a real path.
|
||||||
|
if (process.env.ELECTRON_START_URL) {
|
||||||
|
const base = String(process.env.ELECTRON_START_URL).replace(/\/$/, '')
|
||||||
|
return `${base}${routePath}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prod (file://): App.jsx chooses HashRouter when `index.html` is in the URL.
|
||||||
|
// So we must load `index.html#/electron/spotlightcontent`.
|
||||||
|
return {
|
||||||
|
filePath: path.join(__dirname, '../build/index.html'),
|
||||||
|
hash: routePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openSpotlightContentWindow() {
|
||||||
|
// If already open, just focus it (avoids accidental window spam).
|
||||||
|
if (spotlightWin && !spotlightWin.isDestroyed()) {
|
||||||
|
spotlightWin.show()
|
||||||
|
spotlightWin.focus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
spotlightWin = new BrowserWindow({
|
||||||
|
width: 700,
|
||||||
|
height: 40,
|
||||||
|
frame: false,
|
||||||
|
resizable: false,
|
||||||
|
center: true,
|
||||||
|
vibrancy: 'menu',
|
||||||
|
transparent: true,
|
||||||
|
icon: path.join(__dirname, './logo512.png'),
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const target = getSpotlightRouteUrl()
|
||||||
|
if (typeof target === 'string') {
|
||||||
|
spotlightWin.loadURL(target)
|
||||||
|
} else {
|
||||||
|
spotlightWin.loadFile(target.filePath, { hash: target.hash })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide spotlight window instead of destroying it when closed
|
||||||
|
spotlightWin.on('close', (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (spotlightWin && !spotlightWin.isDestroyed()) {
|
||||||
|
spotlightWin.hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Hide spotlight window when it loses focus (clicking outside)
|
||||||
|
spotlightWin.on('blur', () => {
|
||||||
|
if (spotlightWin && !spotlightWin.isDestroyed()) {
|
||||||
|
spotlightWin.hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
attachSpotlightKeyboardShortcuts(spotlightWin)
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachSpotlightKeyboardShortcuts(browserWindow) {
|
||||||
|
if (!browserWindow) return
|
||||||
|
|
||||||
|
browserWindow.webContents.on('before-input-event', (event, input) => {
|
||||||
|
// ESC -> hide SpotlightContent window (if it's the spotlight window)
|
||||||
|
if (
|
||||||
|
input?.type === 'keyDown' &&
|
||||||
|
String(input?.key || '').toLowerCase() === 'escape' &&
|
||||||
|
browserWindow === spotlightWin
|
||||||
|
) {
|
||||||
|
event.preventDefault()
|
||||||
|
if (spotlightWin && !spotlightWin.isDestroyed()) {
|
||||||
|
spotlightWin.hide()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alt+Shift+Q -> open SpotlightContent window
|
||||||
|
if (
|
||||||
|
input?.type === 'keyDown' &&
|
||||||
|
input?.alt === true &&
|
||||||
|
input?.shift === true &&
|
||||||
|
String(input?.key || '').toLowerCase() === 'q'
|
||||||
|
) {
|
||||||
|
event.preventDefault()
|
||||||
|
openSpotlightContentWindow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerGlobalShortcuts() {
|
||||||
|
try {
|
||||||
|
const registered = globalShortcut.register('Alt+Shift+Q', () => {
|
||||||
|
openSpotlightContentWindow()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!registered) {
|
||||||
|
console.warn('[globalShortcut] Failed to register Alt+Shift+Q')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(
|
||||||
|
'[globalShortcut] Error registering Alt+Shift+Q',
|
||||||
|
e?.message || e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setupSpotlightIPC() {
|
||||||
|
// IPC handler to resize spotlight window
|
||||||
|
ipcMain.handle('spotlight-window-resize', (event, height) => {
|
||||||
|
if (!spotlightWin || spotlightWin.isDestroyed()) return false
|
||||||
|
try {
|
||||||
|
const currentBounds = spotlightWin.getBounds()
|
||||||
|
spotlightWin.setSize(currentBounds.width, height)
|
||||||
|
spotlightWin.center()
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[spotlight] Failed to resize window.', e?.message || e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
15
src/App.jsx
15
src/App.jsx
@ -12,7 +12,10 @@ import PrivateRoute from './components/PrivateRoute'
|
|||||||
import '../assets/stylesheets/App.css'
|
import '../assets/stylesheets/App.css'
|
||||||
import { PrintServerProvider } from './components/Dashboard/context/PrintServerContext.jsx'
|
import { PrintServerProvider } from './components/Dashboard/context/PrintServerContext.jsx'
|
||||||
import { AuthProvider } from './components/Dashboard/context/AuthContext.jsx'
|
import { AuthProvider } from './components/Dashboard/context/AuthContext.jsx'
|
||||||
import { SpotlightProvider } from './components/Dashboard/context/SpotlightContext.jsx'
|
import {
|
||||||
|
SpotlightProvider,
|
||||||
|
ElectronSpotlightContentPage
|
||||||
|
} from './components/Dashboard/context/SpotlightContext.jsx'
|
||||||
import { ActionsModalProvider } from './components/Dashboard/context/ActionsModalContext.jsx'
|
import { ActionsModalProvider } from './components/Dashboard/context/ActionsModalContext.jsx'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -58,6 +61,16 @@ const AppContent = () => {
|
|||||||
<ActionsModalProvider>
|
<ActionsModalProvider>
|
||||||
<MessageProvider>
|
<MessageProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path='/dashboard/electron/spotlightcontent'
|
||||||
|
element={
|
||||||
|
<PrivateRoute
|
||||||
|
component={() => (
|
||||||
|
<ElectronSpotlightContentPage />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path='/'
|
path='/'
|
||||||
element={
|
element={
|
||||||
|
|||||||
@ -57,7 +57,13 @@ const AuthProvider = ({ children }) => {
|
|||||||
const [showUnauthorizedModal, setShowUnauthorizedModal] = useState(false)
|
const [showUnauthorizedModal, setShowUnauthorizedModal] = useState(false)
|
||||||
const [showAuthErrorModal, setShowAuthErrorModal] = useState(false)
|
const [showAuthErrorModal, setShowAuthErrorModal] = useState(false)
|
||||||
const [authError, setAuthError] = useState(null)
|
const [authError, setAuthError] = useState(null)
|
||||||
const { openExternalUrl, isElectron } = useContext(ElectronContext)
|
const {
|
||||||
|
openExternalUrl,
|
||||||
|
isElectron,
|
||||||
|
getAuthSession,
|
||||||
|
setAuthSession,
|
||||||
|
clearAuthSession
|
||||||
|
} = useContext(ElectronContext)
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
@ -71,23 +77,86 @@ const AuthProvider = ({ children }) => {
|
|||||||
redirectType = 'app-scheme'
|
redirectType = 'app-scheme'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if cookies are enabled and show warning if not
|
const extractUserFromAuthData = (authData) => {
|
||||||
|
if (!authData || typeof authData !== 'object') return null
|
||||||
|
if (authData.user && typeof authData.user === 'object') return authData.user
|
||||||
|
|
||||||
|
// Some endpoints may return the "user" fields at the top-level. Only treat it
|
||||||
|
// as a user object if it looks like one (avoid confusing token-only payloads).
|
||||||
|
const looksLikeUser =
|
||||||
|
authData._id || authData.username || authData.email || authData.name
|
||||||
|
if (looksLikeUser) return authData
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSessionExpired = (session) => {
|
||||||
|
if (!session?.expiresAt) return true
|
||||||
|
const now = new Date()
|
||||||
|
const expirationDate = new Date(session.expiresAt)
|
||||||
|
return expirationDate <= now
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistSession = useCallback(
|
||||||
|
async ({ token: nextToken, expiresAt: nextExpiresAt, user: nextUser }) => {
|
||||||
|
if (isElectron) {
|
||||||
|
return await setAuthSession({
|
||||||
|
token: nextToken,
|
||||||
|
expiresAt: nextExpiresAt,
|
||||||
|
user: nextUser
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return setAuthCookies({
|
||||||
|
access_token: nextToken,
|
||||||
|
expires_at: nextExpiresAt,
|
||||||
|
user: nextUser
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[isElectron, setAuthSession]
|
||||||
|
)
|
||||||
|
|
||||||
|
const clearPersistedSession = useCallback(async () => {
|
||||||
|
if (isElectron) {
|
||||||
|
return await clearAuthSession()
|
||||||
|
}
|
||||||
|
clearAuthCookies()
|
||||||
|
return true
|
||||||
|
}, [isElectron, clearAuthSession])
|
||||||
|
|
||||||
|
// Check if cookies are enabled and show warning if not (web only)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isElectron) return
|
||||||
if (!areCookiesEnabled()) {
|
if (!areCookiesEnabled()) {
|
||||||
messageApi.warning(
|
messageApi.warning(
|
||||||
'Cookies are disabled. Login state may not persist between tabs.'
|
'Cookies are disabled. Login state may not persist between tabs.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [messageApi])
|
}, [messageApi, isElectron])
|
||||||
|
|
||||||
// Read token from cookies if present
|
// Read token from cookies (web) or keytar (electron) if present
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
try {
|
try {
|
||||||
console.log(
|
if (isElectron) {
|
||||||
'Retreiving token from cookies...',
|
const session = await getAuthSession()
|
||||||
getAuthCookies(),
|
if (
|
||||||
validateAuthCookies()
|
!cancelled &&
|
||||||
)
|
session &&
|
||||||
|
session.token &&
|
||||||
|
!isSessionExpired(session)
|
||||||
|
) {
|
||||||
|
setToken(session.token)
|
||||||
|
setUserProfile(session.user)
|
||||||
|
setExpiresAt(session.expiresAt)
|
||||||
|
setAuthenticated(true)
|
||||||
|
} else if (!cancelled) {
|
||||||
|
setAuthenticated(false)
|
||||||
|
setUserProfile(null)
|
||||||
|
setShowUnauthorizedModal(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// First validate existing cookies to clean up expired ones
|
// First validate existing cookies to clean up expired ones
|
||||||
if (validateAuthCookies()) {
|
if (validateAuthCookies()) {
|
||||||
const {
|
const {
|
||||||
@ -95,33 +164,41 @@ const AuthProvider = ({ children }) => {
|
|||||||
expiresAt: storedExpiresAt,
|
expiresAt: storedExpiresAt,
|
||||||
user: storedUser
|
user: storedUser
|
||||||
} = getAuthCookies()
|
} = getAuthCookies()
|
||||||
console.log('Retrieved from cookies:', {
|
|
||||||
storedUser,
|
|
||||||
storedToken,
|
|
||||||
storedExpiresAt
|
|
||||||
})
|
|
||||||
|
|
||||||
|
if (!cancelled) {
|
||||||
setToken(storedToken)
|
setToken(storedToken)
|
||||||
setUserProfile(storedUser)
|
setUserProfile(storedUser)
|
||||||
setExpiresAt(storedExpiresAt)
|
setExpiresAt(storedExpiresAt)
|
||||||
setAuthenticated(true)
|
setAuthenticated(true)
|
||||||
} else {
|
}
|
||||||
|
} else if (!cancelled) {
|
||||||
setAuthenticated(false)
|
setAuthenticated(false)
|
||||||
setUserProfile(null)
|
setUserProfile(null)
|
||||||
setShowUnauthorizedModal(true)
|
setShowUnauthorizedModal(true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error reading auth cookies:', error)
|
console.error('Error loading persisted auth session:', error)
|
||||||
clearAuthCookies()
|
await clearPersistedSession()
|
||||||
|
if (!cancelled) {
|
||||||
setAuthenticated(false)
|
setAuthenticated(false)
|
||||||
setUserProfile(null)
|
setUserProfile(null)
|
||||||
setShowUnauthorizedModal(true)
|
setShowUnauthorizedModal(true)
|
||||||
}
|
}
|
||||||
setRetreivedTokenFromCookies(true)
|
} finally {
|
||||||
}, [])
|
if (!cancelled) setRetreivedTokenFromCookies(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load()
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [isElectron, getAuthSession, clearPersistedSession])
|
||||||
|
|
||||||
// Set up cookie synchronization between tabs
|
// Set up cookie synchronization between tabs
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (isElectron) return
|
||||||
const cleanupCookieSync = setupCookieSync(() => {
|
const cleanupCookieSync = setupCookieSync(() => {
|
||||||
// When cookies change in another tab, re-validate and update state
|
// When cookies change in another tab, re-validate and update state
|
||||||
try {
|
try {
|
||||||
@ -159,16 +236,19 @@ const AuthProvider = ({ children }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return cleanupCookieSync
|
return cleanupCookieSync
|
||||||
}, [token, expiresAt, userProfile])
|
}, [token, expiresAt, userProfile, isElectron])
|
||||||
|
|
||||||
const logout = useCallback((redirectUri = '/login') => {
|
const logout = useCallback(
|
||||||
|
(redirectUri = '/login') => {
|
||||||
setAuthenticated(false)
|
setAuthenticated(false)
|
||||||
setToken(null)
|
setToken(null)
|
||||||
setExpiresAt(null)
|
setExpiresAt(null)
|
||||||
setUserProfile(null)
|
setUserProfile(null)
|
||||||
clearAuthCookies()
|
clearPersistedSession()
|
||||||
window.location.href = `${config.backendUrl}/auth/logout?redirect_uri=${encodeURIComponent(redirectUri)}`
|
window.location.href = `${config.backendUrl}/auth/logout?redirect_uri=${encodeURIComponent(redirectUri)}`
|
||||||
}, [])
|
},
|
||||||
|
[clearPersistedSession]
|
||||||
|
)
|
||||||
|
|
||||||
// Login using query parameters
|
// Login using query parameters
|
||||||
const loginWithSSO = useCallback(
|
const loginWithSSO = useCallback(
|
||||||
@ -210,19 +290,23 @@ const AuthProvider = ({ children }) => {
|
|||||||
logger.debug('Got auth token!')
|
logger.debug('Got auth token!')
|
||||||
const authData = response.data
|
const authData = response.data
|
||||||
|
|
||||||
setToken(authData.access_token)
|
const nextToken = authData.access_token
|
||||||
setExpiresAt(authData.expires_at)
|
const nextExpiresAt = authData.expires_at
|
||||||
setUserProfile(authData)
|
const nextUser = extractUserFromAuthData(authData)
|
||||||
|
|
||||||
// Store in cookies for persistence between tabs
|
setToken(nextToken)
|
||||||
const cookieSuccess = setAuthCookies({
|
setExpiresAt(nextExpiresAt)
|
||||||
user: authData,
|
setUserProfile(nextUser)
|
||||||
access_token: authData.access_token,
|
|
||||||
expires_at: authData.expires_at
|
// Persist session (cookies on web, keytar on electron)
|
||||||
|
const persisted = await persistSession({
|
||||||
|
token: nextToken,
|
||||||
|
expiresAt: nextExpiresAt,
|
||||||
|
user: nextUser
|
||||||
})
|
})
|
||||||
if (!cookieSuccess) {
|
if (!persisted) {
|
||||||
messageApi.warning(
|
messageApi.warning(
|
||||||
'Authentication successful but failed to save login state. You may need to log in again if you close this tab.'
|
'Authentication successful but failed to save login state. You may need to log in again when you restart the app.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +337,14 @@ const AuthProvider = ({ children }) => {
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isElectron, navigate, location.search, location.pathname, messageApi]
|
[
|
||||||
|
isElectron,
|
||||||
|
navigate,
|
||||||
|
location.search,
|
||||||
|
location.pathname,
|
||||||
|
messageApi,
|
||||||
|
persistSession
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
// Function to check if the user is logged in
|
// Function to check if the user is logged in
|
||||||
@ -272,13 +363,20 @@ const AuthProvider = ({ children }) => {
|
|||||||
logger.debug('Got auth token!')
|
logger.debug('Got auth token!')
|
||||||
const authData = response.data
|
const authData = response.data
|
||||||
|
|
||||||
setToken(authData.access_token)
|
const nextToken = authData.access_token
|
||||||
setExpiresAt(authData.expires_at)
|
const nextExpiresAt = authData.expires_at
|
||||||
setUserProfile(authData)
|
const nextUser = extractUserFromAuthData(authData)
|
||||||
|
|
||||||
// Update cookies with fresh data
|
setToken(nextToken)
|
||||||
const cookieSuccess = setAuthCookies(authData)
|
setExpiresAt(nextExpiresAt)
|
||||||
if (!cookieSuccess) {
|
setUserProfile(nextUser)
|
||||||
|
|
||||||
|
const persisted = await persistSession({
|
||||||
|
token: nextToken,
|
||||||
|
expiresAt: nextExpiresAt,
|
||||||
|
user: nextUser
|
||||||
|
})
|
||||||
|
if (!persisted) {
|
||||||
messageApi.warning(
|
messageApi.warning(
|
||||||
'Failed to update login state. You may need to log in again if you close this tab.'
|
'Failed to update login state. You may need to log in again if you close this tab.'
|
||||||
)
|
)
|
||||||
@ -300,13 +398,13 @@ const AuthProvider = ({ children }) => {
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [token, messageApi])
|
}, [token, messageApi, persistSession])
|
||||||
|
|
||||||
const setUnauthenticated = () => {
|
const setUnauthenticated = () => {
|
||||||
setToken(null)
|
setToken(null)
|
||||||
setExpiresAt(null)
|
setExpiresAt(null)
|
||||||
setUserProfile(null)
|
setUserProfile(null)
|
||||||
clearAuthCookies()
|
clearPersistedSession()
|
||||||
setAuthenticated(false)
|
setAuthenticated(false)
|
||||||
if (showSessionExpiredModal == false) {
|
if (showSessionExpiredModal == false) {
|
||||||
setShowUnauthorizedModal(true)
|
setShowUnauthorizedModal(true)
|
||||||
@ -324,12 +422,19 @@ const AuthProvider = ({ children }) => {
|
|||||||
if (response.status === 200 && response.data) {
|
if (response.status === 200 && response.data) {
|
||||||
const authData = response.data
|
const authData = response.data
|
||||||
|
|
||||||
setToken(authData.access_token)
|
const nextToken = authData.access_token
|
||||||
setExpiresAt(authData.expires_at)
|
const nextExpiresAt = authData.expires_at
|
||||||
|
const nextUser = extractUserFromAuthData(authData) || userProfile
|
||||||
|
|
||||||
// Update cookies with fresh token data
|
setToken(nextToken)
|
||||||
const cookieSuccess = setAuthCookies(authData)
|
setExpiresAt(nextExpiresAt)
|
||||||
if (!cookieSuccess) {
|
|
||||||
|
const persisted = await persistSession({
|
||||||
|
token: nextToken,
|
||||||
|
expiresAt: nextExpiresAt,
|
||||||
|
user: nextUser
|
||||||
|
})
|
||||||
|
if (!persisted) {
|
||||||
messageApi.warning(
|
messageApi.warning(
|
||||||
'Failed to update login state. You may need to log in again if you close this tab.'
|
'Failed to update login state. You may need to log in again if you close this tab.'
|
||||||
)
|
)
|
||||||
@ -338,7 +443,7 @@ const AuthProvider = ({ children }) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Token refresh failed', error)
|
console.error('Token refresh failed', error)
|
||||||
}
|
}
|
||||||
}, [token, messageApi])
|
}, [token, messageApi, persistSession, userProfile])
|
||||||
|
|
||||||
const handleSessionExpiredModalOk = () => {
|
const handleSessionExpiredModalOk = () => {
|
||||||
setShowSessionExpiredModal(false)
|
setShowSessionExpiredModal(false)
|
||||||
@ -419,7 +524,8 @@ const AuthProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Check cookies directly if expiresAt is not set in state
|
// Check cookies directly if expiresAt is not set in state (web only)
|
||||||
|
if (isElectron) return
|
||||||
const expiryInfo = checkAuthCookiesExpiry(5) // Check if expiring within 5 minutes
|
const expiryInfo = checkAuthCookiesExpiry(5) // Check if expiring within 5 minutes
|
||||||
if (expiryInfo.isExpiringSoon && expiryInfo.minutesRemaining <= 1) {
|
if (expiryInfo.isExpiringSoon && expiryInfo.minutesRemaining <= 1) {
|
||||||
// Show notification for cookies expiring soon
|
// Show notification for cookies expiring soon
|
||||||
@ -478,7 +584,7 @@ const AuthProvider = ({ children }) => {
|
|||||||
clearInterval(intervalId)
|
clearInterval(intervalId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [expiresAt, authenticated, notificationApi, refreshToken])
|
}, [expiresAt, authenticated, notificationApi, refreshToken, isElectron])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const authCode =
|
const authCode =
|
||||||
|
|||||||
@ -47,6 +47,15 @@ const ElectronProvider = ({ children }) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to open internal URL via Electron
|
||||||
|
const openInternalUrl = (url) => {
|
||||||
|
if (electronAvailable && ipcRenderer) {
|
||||||
|
ipcRenderer.invoke('open-internal-url', url)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ipcRenderer) return
|
if (!ipcRenderer) return
|
||||||
|
|
||||||
@ -96,6 +105,45 @@ const ElectronProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getAuthSession = async () => {
|
||||||
|
if (!electronAvailable || !ipcRenderer) return null
|
||||||
|
return await ipcRenderer.invoke('auth-session-get')
|
||||||
|
}
|
||||||
|
|
||||||
|
const setAuthSession = async (session) => {
|
||||||
|
if (!electronAvailable || !ipcRenderer) return false
|
||||||
|
return await ipcRenderer.invoke('auth-session-set', session)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearAuthSession = async () => {
|
||||||
|
if (!electronAvailable || !ipcRenderer) return false
|
||||||
|
return await ipcRenderer.invoke('auth-session-clear')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backwards-compatible helpers
|
||||||
|
const getToken = async () => {
|
||||||
|
const session = await getAuthSession()
|
||||||
|
return session?.token || null
|
||||||
|
}
|
||||||
|
|
||||||
|
const setToken = async (token) => {
|
||||||
|
const session = (await getAuthSession()) || {}
|
||||||
|
return await setAuthSession({ ...session, token })
|
||||||
|
}
|
||||||
|
|
||||||
|
const resizeSpotlightWindow = async (height) => {
|
||||||
|
if (!electronAvailable || !ipcRenderer) return false
|
||||||
|
try {
|
||||||
|
return await ipcRenderer.invoke('spotlight-window-resize', height)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
'[ElectronContext] Failed to resize spotlight window:',
|
||||||
|
error
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ElectronContext.Provider
|
<ElectronContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -104,7 +152,14 @@ const ElectronProvider = ({ children }) => {
|
|||||||
isFullScreen,
|
isFullScreen,
|
||||||
isElectron: electronAvailable,
|
isElectron: electronAvailable,
|
||||||
handleWindowControl,
|
handleWindowControl,
|
||||||
openExternalUrl
|
openExternalUrl,
|
||||||
|
openInternalUrl,
|
||||||
|
getAuthSession,
|
||||||
|
setAuthSession,
|
||||||
|
clearAuthSession,
|
||||||
|
getToken,
|
||||||
|
setToken,
|
||||||
|
resizeSpotlightWindow
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@ -7,7 +7,9 @@ import {
|
|||||||
Spin,
|
Spin,
|
||||||
message,
|
message,
|
||||||
Form,
|
Form,
|
||||||
Button
|
Button,
|
||||||
|
Card,
|
||||||
|
Divider
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
@ -32,20 +34,27 @@ import {
|
|||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
|
||||||
import { ApiServerContext } from './ApiServerContext'
|
import { ApiServerContext } from './ApiServerContext'
|
||||||
import { AuthContext } from './AuthContext'
|
import { AuthContext } from './AuthContext'
|
||||||
|
import { ElectronContext } from './ElectronContext'
|
||||||
|
|
||||||
const SpotlightContext = createContext()
|
const SpotlightContext = createContext()
|
||||||
|
|
||||||
const SpotlightProvider = ({ children }) => {
|
const SpotlightContent = ({
|
||||||
|
isActive,
|
||||||
|
openKey,
|
||||||
|
defaultQuery,
|
||||||
|
onRequestClose,
|
||||||
|
isElectron
|
||||||
|
}) => {
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [showModal, setShowModal] = useState(false)
|
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [query, setQuery] = useState('')
|
const [query, setQuery] = useState('')
|
||||||
const [listData, setListData] = useState([])
|
const [listData, setListData] = useState([])
|
||||||
const [messageApi, contextHolder] = message.useMessage()
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
const [inputPrefix, setInputPrefix] = useState({ prefix: '', mode: null })
|
const [inputPrefix, setInputPrefix] = useState(null)
|
||||||
const { fetchSpotlightData } = useContext(ApiServerContext)
|
const { fetchSpotlightData } = useContext(ApiServerContext)
|
||||||
const { token } = useContext(AuthContext)
|
const { token } = useContext(AuthContext)
|
||||||
|
const { openInternalUrl } = useContext(ElectronContext)
|
||||||
|
|
||||||
// Refs for throttling/debouncing
|
// Refs for throttling/debouncing
|
||||||
const lastFetchTime = useRef(0)
|
const lastFetchTime = useRef(0)
|
||||||
@ -54,39 +63,6 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
const inputRef = useRef(null)
|
const inputRef = useRef(null)
|
||||||
const formRef = useRef(null)
|
const formRef = useRef(null)
|
||||||
|
|
||||||
const showSpotlight = (defaultQuery = '') => {
|
|
||||||
setQuery(defaultQuery)
|
|
||||||
setShowModal(true)
|
|
||||||
|
|
||||||
// Set prefix based on default query if provided
|
|
||||||
if (defaultQuery) {
|
|
||||||
// Check if the default query contains a prefix
|
|
||||||
const upperQuery = defaultQuery.toUpperCase()
|
|
||||||
const prefixInfo = parsePrefix(upperQuery)
|
|
||||||
|
|
||||||
if (prefixInfo) {
|
|
||||||
setInputPrefix(prefixInfo)
|
|
||||||
// Set the query to only the part after the prefix and mode character
|
|
||||||
const remainingValue = defaultQuery.substring(
|
|
||||||
prefixInfo.prefix.length + 1
|
|
||||||
)
|
|
||||||
setQuery(remainingValue)
|
|
||||||
checkAndFetchData(defaultQuery)
|
|
||||||
} else {
|
|
||||||
setInputPrefix(null)
|
|
||||||
checkAndFetchData(defaultQuery)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setInputPrefix(null)
|
|
||||||
// Only clear data if we're opening with an empty query and no existing data
|
|
||||||
if (listData.length === 0) {
|
|
||||||
setListData([])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus will be handled in useEffect for proper timing after modal renders
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to parse prefix and mode from query
|
// Helper function to parse prefix and mode from query
|
||||||
const parsePrefix = (query) => {
|
const parsePrefix = (query) => {
|
||||||
// Check for prefix format: XXX: or XXX? or XXX^
|
// Check for prefix format: XXX: or XXX? or XXX^
|
||||||
@ -98,7 +74,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
if ([':', '?', '^'].includes(modeChar)) {
|
if ([':', '?', '^'].includes(modeChar)) {
|
||||||
const prefixModel = getModelByPrefix(potentialPrefix)
|
const prefixModel = getModelByPrefix(potentialPrefix)
|
||||||
|
|
||||||
if (prefixModel.prefix === potentialPrefix) {
|
if (prefixModel && prefixModel.prefix === potentialPrefix) {
|
||||||
return {
|
return {
|
||||||
...prefixModel,
|
...prefixModel,
|
||||||
mode: modeChar
|
mode: modeChar
|
||||||
@ -283,7 +259,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Helper to get row actions and default action for a model
|
// Helper to get row actions and default action for a model
|
||||||
const getRowActions = (model) =>
|
const getRowActions = (model) =>
|
||||||
(model.actions || []).filter((action) => action.row === true)
|
(model?.actions || []).filter((action) => action.row === true)
|
||||||
|
|
||||||
const getDefaultRowAction = (model) =>
|
const getDefaultRowAction = (model) =>
|
||||||
getRowActions(model).find((action) => action.default)
|
getRowActions(model).find((action) => action.default)
|
||||||
@ -294,8 +270,12 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
const itemId = item._id || item.id
|
const itemId = item._id || item.id
|
||||||
const url = action.url(itemId)
|
const url = action.url(itemId)
|
||||||
if (url && url !== '#') {
|
if (url && url !== '#') {
|
||||||
|
if (isElectron) {
|
||||||
|
openInternalUrl(url)
|
||||||
|
} else {
|
||||||
navigate(url)
|
navigate(url)
|
||||||
setShowModal(false)
|
}
|
||||||
|
if (onRequestClose) onRequestClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,7 +289,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
const item = listData[0]
|
const item = listData[0]
|
||||||
let type = item.type || item.objectType || inputPrefix?.type
|
let type = item.type || item.objectType || inputPrefix?.type
|
||||||
const model = getModelByName(type)
|
const model = getModelByName(type)
|
||||||
const defaultAction = getDefaultRowAction(model)
|
const defaultAction = model ? getDefaultRowAction(model) : null
|
||||||
if (defaultAction) {
|
if (defaultAction) {
|
||||||
triggerRowAction(defaultAction, item)
|
triggerRowAction(defaultAction, item)
|
||||||
}
|
}
|
||||||
@ -323,7 +303,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
const item = listData[index]
|
const item = listData[index]
|
||||||
let type = item.type || item.objectType || inputPrefix?.type
|
let type = item.type || item.objectType || inputPrefix?.type
|
||||||
const model = getModelByName(type)
|
const model = getModelByName(type)
|
||||||
const defaultAction = getDefaultRowAction(model)
|
const defaultAction = model ? getDefaultRowAction(model) : null
|
||||||
if (defaultAction) {
|
if (defaultAction) {
|
||||||
triggerRowAction(defaultAction, item)
|
triggerRowAction(defaultAction, item)
|
||||||
}
|
}
|
||||||
@ -341,7 +321,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
|
|
||||||
// Focus and select text in input when modal becomes visible
|
// Focus and select text in input when modal becomes visible
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showModal && inputRef.current) {
|
if (isActive && inputRef.current) {
|
||||||
// Use a small timeout to ensure the modal is fully rendered and visible
|
// Use a small timeout to ensure the modal is fully rendered and visible
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const input = inputRef.current.input
|
const input = inputRef.current.input
|
||||||
@ -351,25 +331,52 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, 50)
|
}, 50)
|
||||||
}
|
}
|
||||||
}, [showModal])
|
}, [isActive])
|
||||||
|
|
||||||
// Focus input when inputPrefix changes
|
// Focus input when inputPrefix changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showModal) {
|
if (isActive) {
|
||||||
// Only clear data if there's no existing data and no current query
|
|
||||||
if (listData.length === 0 && !query) {
|
|
||||||
setListData([])
|
|
||||||
}
|
|
||||||
focusInput()
|
focusInput()
|
||||||
}
|
}
|
||||||
}, [inputPrefix, showModal])
|
}, [inputPrefix, isActive])
|
||||||
|
|
||||||
// Update form value when query changes
|
// Update form value when query changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showModal && formRef.current) {
|
if (isActive && formRef.current) {
|
||||||
formRef.current.setFieldsValue({ query: query })
|
formRef.current.setFieldsValue({ query: query })
|
||||||
}
|
}
|
||||||
}, [query, showModal])
|
}, [query, isActive])
|
||||||
|
|
||||||
|
// Apply/refresh defaultQuery when the component is opened (modal) or mounted (standalone)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isActive) return
|
||||||
|
|
||||||
|
const nextDefaultQuery = defaultQuery || ''
|
||||||
|
|
||||||
|
// Set prefix based on default query if provided
|
||||||
|
if (nextDefaultQuery) {
|
||||||
|
const upperQuery = nextDefaultQuery.toUpperCase()
|
||||||
|
const prefixInfo = parsePrefix(upperQuery)
|
||||||
|
|
||||||
|
if (prefixInfo) {
|
||||||
|
setInputPrefix(prefixInfo)
|
||||||
|
const remainingValue = nextDefaultQuery.substring(
|
||||||
|
prefixInfo.prefix.length + 1
|
||||||
|
)
|
||||||
|
setQuery(remainingValue)
|
||||||
|
checkAndFetchData(nextDefaultQuery)
|
||||||
|
} else {
|
||||||
|
setInputPrefix(null)
|
||||||
|
setQuery(nextDefaultQuery)
|
||||||
|
checkAndFetchData(nextDefaultQuery)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setQuery('')
|
||||||
|
setInputPrefix(null)
|
||||||
|
// Keep previous listData behavior (don’t force-clear on open)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [openKey, isActive])
|
||||||
|
|
||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -394,18 +401,7 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const spotlightContent = (
|
||||||
<SpotlightContext.Provider value={{ showSpotlight }}>
|
|
||||||
{contextHolder}
|
|
||||||
<Modal
|
|
||||||
open={showModal}
|
|
||||||
onCancel={() => setShowModal(false)}
|
|
||||||
closeIcon={null}
|
|
||||||
footer={null}
|
|
||||||
width={700}
|
|
||||||
styles={{ content: { padding: 0 } }}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
>
|
|
||||||
<Flex vertical>
|
<Flex vertical>
|
||||||
<Form ref={formRef} onValuesChange={handleSpotlightChange}>
|
<Form ref={formRef} onValuesChange={handleSpotlightChange}>
|
||||||
<Form.Item name='query' initialValue={query} style={{ margin: 0 }}>
|
<Form.Item name='query' initialValue={query} style={{ margin: 0 }}>
|
||||||
@ -444,13 +440,15 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
{listData.length > 0 && (
|
{listData.length > 0 && (
|
||||||
|
<>
|
||||||
|
{isElectron && <Divider style={{ margin: 0 }} />}
|
||||||
<div style={{ marginLeft: '18px', marginRight: '14px' }}>
|
<div style={{ marginLeft: '18px', marginRight: '14px' }}>
|
||||||
<List
|
<List
|
||||||
dataSource={listData}
|
dataSource={listData}
|
||||||
renderItem={(item, index) => {
|
renderItem={(item, index) => {
|
||||||
let type = item.objectType || inputPrefix?.type
|
let type = item.objectType || inputPrefix?.type
|
||||||
const model = getModelByName(type)
|
const model = getModelByName(type)
|
||||||
const Icon = model.icon
|
const Icon = model?.icon
|
||||||
const rowActions = getRowActions(model)
|
const rowActions = getRowActions(model)
|
||||||
let shortcutText = ''
|
let shortcutText = ''
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
@ -538,8 +536,62 @@ const SpotlightProvider = ({ children }) => {
|
|||||||
}}
|
}}
|
||||||
></List>
|
></List>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{contextHolder}
|
||||||
|
{spotlightContent}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SpotlightContent.propTypes = {
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
openKey: PropTypes.number,
|
||||||
|
defaultQuery: PropTypes.string,
|
||||||
|
onRequestClose: PropTypes.func,
|
||||||
|
isElectron: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
SpotlightContent.defaultProps = {
|
||||||
|
isActive: true,
|
||||||
|
openKey: 0,
|
||||||
|
defaultQuery: '',
|
||||||
|
onRequestClose: null
|
||||||
|
}
|
||||||
|
|
||||||
|
const SpotlightProvider = ({ children }) => {
|
||||||
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
const [openKey, setOpenKey] = useState(0)
|
||||||
|
const [defaultQuery, setDefaultQuery] = useState('')
|
||||||
|
|
||||||
|
const showSpotlight = (nextDefaultQuery = '') => {
|
||||||
|
setDefaultQuery(nextDefaultQuery)
|
||||||
|
setOpenKey((k) => k + 1)
|
||||||
|
setShowModal(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SpotlightContext.Provider value={{ showSpotlight }}>
|
||||||
|
<Modal
|
||||||
|
open={showModal}
|
||||||
|
onCancel={() => setShowModal(false)}
|
||||||
|
closeIcon={null}
|
||||||
|
footer={null}
|
||||||
|
width={700}
|
||||||
|
styles={{ content: { padding: 0 } }}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
>
|
||||||
|
<SpotlightContent
|
||||||
|
isActive={showModal}
|
||||||
|
openKey={openKey}
|
||||||
|
defaultQuery={defaultQuery}
|
||||||
|
onRequestClose={() => setShowModal(false)}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
{children}
|
{children}
|
||||||
</SpotlightContext.Provider>
|
</SpotlightContext.Provider>
|
||||||
@ -550,4 +602,100 @@ SpotlightProvider.propTypes = {
|
|||||||
children: PropTypes.node.isRequired
|
children: PropTypes.node.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export { SpotlightProvider, SpotlightContext }
|
const ElectronSpotlightContentPage = () => {
|
||||||
|
const cardRef = useRef(null)
|
||||||
|
const resizeTimeoutRef = useRef(null)
|
||||||
|
const { resizeSpotlightWindow, isElectron } = useContext(ElectronContext)
|
||||||
|
|
||||||
|
// Function to measure and resize window
|
||||||
|
const updateWindowHeight = () => {
|
||||||
|
if (!cardRef.current || !isElectron || !resizeSpotlightWindow) return
|
||||||
|
|
||||||
|
// Clear any pending resize
|
||||||
|
if (resizeTimeoutRef.current) {
|
||||||
|
clearTimeout(resizeTimeoutRef.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce the resize to avoid too many calls
|
||||||
|
resizeTimeoutRef.current = setTimeout(() => {
|
||||||
|
try {
|
||||||
|
const cardElement = cardRef.current
|
||||||
|
if (!cardElement) return
|
||||||
|
|
||||||
|
// Get the scroll height of the card
|
||||||
|
const scrollHeight = cardElement.scrollHeight
|
||||||
|
|
||||||
|
// Add some padding for better appearance (e.g., 10px top and bottom)
|
||||||
|
const padding = 0
|
||||||
|
const newHeight = scrollHeight + padding * 2
|
||||||
|
|
||||||
|
// Set minimum height to prevent window from being too small
|
||||||
|
const minHeight = 30
|
||||||
|
const finalHeight = Math.max(newHeight, minHeight)
|
||||||
|
|
||||||
|
// Call IPC to resize the window
|
||||||
|
resizeSpotlightWindow(finalHeight)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to update spotlight window height:', error)
|
||||||
|
}
|
||||||
|
}, 100) // 100ms debounce
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use ResizeObserver to watch for content changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (!cardRef.current || !isElectron || !resizeSpotlightWindow) return
|
||||||
|
|
||||||
|
const cardElement = cardRef.current
|
||||||
|
|
||||||
|
// Create ResizeObserver to watch for size changes
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
updateWindowHeight()
|
||||||
|
})
|
||||||
|
|
||||||
|
resizeObserver.observe(cardElement)
|
||||||
|
|
||||||
|
// Initial measurement after a short delay to ensure content is rendered
|
||||||
|
const initialTimeout = setTimeout(() => {
|
||||||
|
updateWindowHeight()
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect()
|
||||||
|
if (resizeTimeoutRef.current) {
|
||||||
|
clearTimeout(resizeTimeoutRef.current)
|
||||||
|
}
|
||||||
|
clearTimeout(initialTimeout)
|
||||||
|
}
|
||||||
|
}, [isElectron, resizeSpotlightWindow])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={cardRef}
|
||||||
|
style={{
|
||||||
|
maxWidth: 700,
|
||||||
|
padding: 0
|
||||||
|
}}
|
||||||
|
className='electron-spotlight-content'
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
styles={{ body: { padding: 0 } }}
|
||||||
|
variant={'borderless'}
|
||||||
|
style={{ border: 'none', backgroundColor: 'transparent' }}
|
||||||
|
>
|
||||||
|
<SpotlightContent
|
||||||
|
isActive={true}
|
||||||
|
openKey={1}
|
||||||
|
defaultQuery=''
|
||||||
|
isElectron={true}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
SpotlightProvider,
|
||||||
|
SpotlightContext,
|
||||||
|
SpotlightContent,
|
||||||
|
ElectronSpotlightContentPage
|
||||||
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
allowedHosts: ['dev.tombutcher.work'],
|
allowedHosts: ['dev.tombutcher.work'],
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: 5173,
|
port: 5780,
|
||||||
open: false
|
open: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
117
yarn.lock
117
yarn.lock
@ -3355,7 +3355,7 @@ baseline-browser-mapping@^2.8.25:
|
|||||||
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz#5de72358cf363ac41e7d642af239f6ac5ed1270a"
|
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz#5de72358cf363ac41e7d642af239f6ac5ed1270a"
|
||||||
integrity sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==
|
integrity sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==
|
||||||
|
|
||||||
bl@^4.1.0:
|
bl@^4.0.3, bl@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
|
resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a"
|
||||||
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
|
integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==
|
||||||
@ -3671,6 +3671,11 @@ character-reference-invalid@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
|
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
|
||||||
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
|
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
|
||||||
|
|
||||||
|
chownr@^1.1.1:
|
||||||
|
version "1.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||||
|
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
|
||||||
|
|
||||||
chownr@^2.0.0:
|
chownr@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
|
resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
|
||||||
@ -4376,7 +4381,7 @@ dequal@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
|
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
|
||||||
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
||||||
|
|
||||||
detect-libc@^2.0.1:
|
detect-libc@^2.0.0, detect-libc@^2.0.1:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad"
|
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad"
|
||||||
integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
|
integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==
|
||||||
@ -4635,7 +4640,7 @@ encoding@^0.1.13:
|
|||||||
dependencies:
|
dependencies:
|
||||||
iconv-lite "^0.6.2"
|
iconv-lite "^0.6.2"
|
||||||
|
|
||||||
end-of-stream@^1.1.0:
|
end-of-stream@^1.1.0, end-of-stream@^1.4.1:
|
||||||
version "1.4.5"
|
version "1.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c"
|
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c"
|
||||||
integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==
|
integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==
|
||||||
@ -5203,6 +5208,11 @@ execa@^5.1.1:
|
|||||||
signal-exit "^3.0.3"
|
signal-exit "^3.0.3"
|
||||||
strip-final-newline "^2.0.0"
|
strip-final-newline "^2.0.0"
|
||||||
|
|
||||||
|
expand-template@^2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
||||||
|
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
|
||||||
|
|
||||||
exponential-backoff@^3.1.1:
|
exponential-backoff@^3.1.1:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz#51cf92c1c0493c766053f9d3abee4434c244d2f6"
|
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz#51cf92c1c0493c766053f9d3abee4434c244d2f6"
|
||||||
@ -5480,6 +5490,11 @@ fresh@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4"
|
resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4"
|
||||||
integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==
|
integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==
|
||||||
|
|
||||||
|
fs-constants@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||||
|
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||||
|
|
||||||
fs-extra@^10.0.0, fs-extra@^10.1.0:
|
fs-extra@^10.0.0, fs-extra@^10.1.0:
|
||||||
version "10.1.0"
|
version "10.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
||||||
@ -5648,6 +5663,11 @@ get-symbol-description@^1.1.0:
|
|||||||
es-errors "^1.3.0"
|
es-errors "^1.3.0"
|
||||||
get-intrinsic "^1.2.6"
|
get-intrinsic "^1.2.6"
|
||||||
|
|
||||||
|
github-from-package@0.0.0:
|
||||||
|
version "0.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
|
||||||
|
integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
|
||||||
|
|
||||||
gl-matrix@^3.3.0, gl-matrix@^3.4.3:
|
gl-matrix@^3.3.0, gl-matrix@^3.4.3:
|
||||||
version "3.4.4"
|
version "3.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.4.4.tgz#7789ee4982f62c7a7af447ee488f3bd6b0c77003"
|
resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.4.4.tgz#7789ee4982f62c7a7af447ee488f3bd6b0c77003"
|
||||||
@ -6505,6 +6525,14 @@ keycloak-js@^26.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-26.2.1.tgz#644e8b8403268f7a39c3980183e22a6fb64f17db"
|
resolved "https://registry.yarnpkg.com/keycloak-js/-/keycloak-js-26.2.1.tgz#644e8b8403268f7a39c3980183e22a6fb64f17db"
|
||||||
integrity sha512-bZt6fQj/TLBAmivXSxSlqAJxBx/knNZDQGJIW4ensGYGN4N6tUKV8Zj3Y7/LOV8eIpvWsvqV70fbACihK8Ze0Q==
|
integrity sha512-bZt6fQj/TLBAmivXSxSlqAJxBx/knNZDQGJIW4ensGYGN4N6tUKV8Zj3Y7/LOV8eIpvWsvqV70fbACihK8Ze0Q==
|
||||||
|
|
||||||
|
keytar@^7.9.0:
|
||||||
|
version "7.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/keytar/-/keytar-7.9.0.tgz#4c6225708f51b50cbf77c5aae81721964c2918cb"
|
||||||
|
integrity sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==
|
||||||
|
dependencies:
|
||||||
|
node-addon-api "^4.3.0"
|
||||||
|
prebuild-install "^7.0.1"
|
||||||
|
|
||||||
keyv@^4.0.0, keyv@^4.5.3, keyv@^4.5.4:
|
keyv@^4.0.0, keyv@^4.5.3, keyv@^4.5.4:
|
||||||
version "4.5.4"
|
version "4.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
|
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
|
||||||
@ -7301,7 +7329,7 @@ minimatch@^9.0.3, minimatch@^9.0.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^2.0.1"
|
brace-expansion "^2.0.1"
|
||||||
|
|
||||||
minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
|
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||||
@ -7370,6 +7398,11 @@ minizlib@^2.1.1, minizlib@^2.1.2:
|
|||||||
minipass "^3.0.0"
|
minipass "^3.0.0"
|
||||||
yallist "^4.0.0"
|
yallist "^4.0.0"
|
||||||
|
|
||||||
|
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
|
||||||
|
version "0.5.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||||
|
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||||
|
|
||||||
mkdirp@^1.0.3, mkdirp@^1.0.4:
|
mkdirp@^1.0.3, mkdirp@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||||
@ -7431,6 +7464,11 @@ nanopop@2.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/nanopop/-/nanopop-2.3.0.tgz#a5f672fba27d45d6ecbd0b59789c040072915123"
|
resolved "https://registry.yarnpkg.com/nanopop/-/nanopop-2.3.0.tgz#a5f672fba27d45d6ecbd0b59789c040072915123"
|
||||||
integrity sha512-fzN+T2K7/Ah25XU02MJkPZ5q4Tj5FpjmIYq4rvoHX4yb16HzFdCO6JxFFn5Y/oBhQ8no8fUZavnyIv9/+xkBBw==
|
integrity sha512-fzN+T2K7/Ah25XU02MJkPZ5q4Tj5FpjmIYq4rvoHX4yb16HzFdCO6JxFFn5Y/oBhQ8no8fUZavnyIv9/+xkBBw==
|
||||||
|
|
||||||
|
napi-build-utils@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
|
||||||
|
integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
|
||||||
|
|
||||||
natural-compare@^1.4.0:
|
natural-compare@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||||
@ -7454,7 +7492,7 @@ no-case@^3.0.4:
|
|||||||
lower-case "^2.0.2"
|
lower-case "^2.0.2"
|
||||||
tslib "^2.0.3"
|
tslib "^2.0.3"
|
||||||
|
|
||||||
node-abi@^3.45.0:
|
node-abi@^3.3.0, node-abi@^3.45.0:
|
||||||
version "3.85.0"
|
version "3.85.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.85.0.tgz#b115d575e52b2495ef08372b058e13d202875a7d"
|
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.85.0.tgz#b115d575e52b2495ef08372b058e13d202875a7d"
|
||||||
integrity sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==
|
integrity sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==
|
||||||
@ -7466,6 +7504,11 @@ node-addon-api@^1.6.3:
|
|||||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d"
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d"
|
||||||
integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==
|
integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==
|
||||||
|
|
||||||
|
node-addon-api@^4.3.0:
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
|
||||||
|
integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
|
||||||
|
|
||||||
node-api-version@^0.2.0:
|
node-api-version@^0.2.0:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/node-api-version/-/node-api-version-0.2.1.tgz#19bad54f6d65628cbee4e607a325e4488ace2de9"
|
resolved "https://registry.yarnpkg.com/node-api-version/-/node-api-version-0.2.1.tgz#19bad54f6d65628cbee4e607a325e4488ace2de9"
|
||||||
@ -7922,6 +7965,24 @@ postcss@^8.5.6:
|
|||||||
picocolors "^1.1.1"
|
picocolors "^1.1.1"
|
||||||
source-map-js "^1.2.1"
|
source-map-js "^1.2.1"
|
||||||
|
|
||||||
|
prebuild-install@^7.0.1:
|
||||||
|
version "7.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
|
||||||
|
integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==
|
||||||
|
dependencies:
|
||||||
|
detect-libc "^2.0.0"
|
||||||
|
expand-template "^2.0.3"
|
||||||
|
github-from-package "0.0.0"
|
||||||
|
minimist "^1.2.3"
|
||||||
|
mkdirp-classic "^0.5.3"
|
||||||
|
napi-build-utils "^2.0.0"
|
||||||
|
node-abi "^3.3.0"
|
||||||
|
pump "^3.0.0"
|
||||||
|
rc "^1.2.7"
|
||||||
|
simple-get "^4.0.0"
|
||||||
|
tar-fs "^2.0.0"
|
||||||
|
tunnel-agent "^0.6.0"
|
||||||
|
|
||||||
prelude-ls@^1.2.1:
|
prelude-ls@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||||
@ -8429,7 +8490,7 @@ rc-virtual-list@^3.14.2, rc-virtual-list@^3.5.1, rc-virtual-list@^3.5.2:
|
|||||||
rc-resize-observer "^1.0.0"
|
rc-resize-observer "^1.0.0"
|
||||||
rc-util "^5.36.0"
|
rc-util "^5.36.0"
|
||||||
|
|
||||||
rc@^1.0.1, rc@^1.1.6:
|
rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||||
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
|
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
|
||||||
@ -8544,7 +8605,7 @@ read-pkg@^2.0.0:
|
|||||||
normalize-package-data "^2.3.2"
|
normalize-package-data "^2.3.2"
|
||||||
path-type "^2.0.0"
|
path-type "^2.0.0"
|
||||||
|
|
||||||
readable-stream@^3.4.0:
|
readable-stream@^3.1.1, readable-stream@^3.4.0:
|
||||||
version "3.6.2"
|
version "3.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
|
||||||
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
|
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
|
||||||
@ -9072,6 +9133,20 @@ signal-exit@^4.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
|
||||||
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
|
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
|
||||||
|
|
||||||
|
simple-concat@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
|
||||||
|
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
|
||||||
|
|
||||||
|
simple-get@^4.0.0:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
|
||||||
|
integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
|
||||||
|
dependencies:
|
||||||
|
decompress-response "^6.0.0"
|
||||||
|
once "^1.3.1"
|
||||||
|
simple-concat "^1.0.0"
|
||||||
|
|
||||||
simple-swizzle@^0.2.2:
|
simple-swizzle@^0.2.2:
|
||||||
version "0.2.4"
|
version "0.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz#a8d11a45a11600d6a1ecdff6363329e3648c3667"
|
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.4.tgz#a8d11a45a11600d6a1ecdff6363329e3648c3667"
|
||||||
@ -9563,6 +9638,27 @@ synckit@^0.11.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@pkgr/core" "^0.2.9"
|
"@pkgr/core" "^0.2.9"
|
||||||
|
|
||||||
|
tar-fs@^2.0.0:
|
||||||
|
version "2.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.4.tgz#800824dbf4ef06ded9afea4acafe71c67c76b930"
|
||||||
|
integrity sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==
|
||||||
|
dependencies:
|
||||||
|
chownr "^1.1.1"
|
||||||
|
mkdirp-classic "^0.5.2"
|
||||||
|
pump "^3.0.0"
|
||||||
|
tar-stream "^2.1.4"
|
||||||
|
|
||||||
|
tar-stream@^2.1.4:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
|
||||||
|
integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==
|
||||||
|
dependencies:
|
||||||
|
bl "^4.0.3"
|
||||||
|
end-of-stream "^1.4.1"
|
||||||
|
fs-constants "^1.0.0"
|
||||||
|
inherits "^2.0.3"
|
||||||
|
readable-stream "^3.1.1"
|
||||||
|
|
||||||
tar@^6.0.5, tar@^6.1.11, tar@^6.1.12, tar@^6.2.1:
|
tar@^6.0.5, tar@^6.1.11, tar@^6.1.12, tar@^6.2.1:
|
||||||
version "6.2.1"
|
version "6.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
|
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
|
||||||
@ -9725,6 +9821,13 @@ tsparticles@^3.9.1:
|
|||||||
"@tsparticles/updater-twinkle" "3.9.1"
|
"@tsparticles/updater-twinkle" "3.9.1"
|
||||||
"@tsparticles/updater-wobble" "3.9.1"
|
"@tsparticles/updater-wobble" "3.9.1"
|
||||||
|
|
||||||
|
tunnel-agent@^0.6.0:
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||||
|
integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
type-check@^0.4.0, type-check@~0.4.0:
|
type-check@^0.4.0, type-check@~0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user