diff --git a/.github/workflows/testdriver.yml b/.github/workflows/testdriver.yml new file mode 100644 index 000000000..2b42fbf3d --- /dev/null +++ b/.github/workflows/testdriver.yml @@ -0,0 +1,156 @@ +name: TestDriver.ai + +on: + push: + branches: + - main + tags: + - "v[0-9]+.[0-9]+.[0-9]+*" + pull_request: + branches: + - main + schedule: + - cron: 0 21 * * * + workflow_dispatch: null + +env: + GO_VERSION: "1.22" + NODE_VERSION: "20" + +permissions: + contents: read # To allow the action to read repository contents + pull-requests: write # To allow the action to create/update pull request comments + +jobs: + build_and_upload: + name: Test Onboarding + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + # General build dependencies + - uses: actions/setup-go@v5 + with: + go-version: ${{env.GO_VERSION}} + cache-dependency-path: | + go.sum + - uses: actions/setup-node@v4 + with: + node-version: ${{env.NODE_VERSION}} + - name: Install Yarn + run: | + corepack enable + yarn install + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Build + run: task package + env: + USE_SYSTEM_FPM: true # Ensure that the installed version of FPM is used rather than the bundled one. + CSC_IDENTITY_AUTO_DISCOVERY: false # disable codesign + shell: powershell # electron-builder's Windows code signing package has some compatibility issues with pwsh, so we need to use Windows Powershell + + # Upload .exe as an artifact + - name: Upload .exe artifact + id: upload + uses: actions/upload-artifact@v4 + with: + name: windows-exe + path: make/*.exe + + - uses: testdriverai/action@main + id: testdriver + env: + FORCE_COLOR: "3" + with: + key: ${{ secrets.DASHCAM_API }} + prerun: | + $headers = @{ + Authorization = "token ${{ secrets.GITHUB_TOKEN }}" + } + + $downloadFolder = "./download" + $artifactFileName = "waveterm.exe" + $artifactFilePath = "$downloadFolder/$artifactFileName" + + Write-Host "Starting the artifact download process..." + + # Create the download directory if it doesn't exist + if (-not (Test-Path -Path $downloadFolder)) { + Write-Host "Creating download folder..." + mkdir $downloadFolder + } else { + Write-Host "Download folder already exists." + } + + # Fetch the artifact upload URL + Write-Host "Fetching the artifact upload URL..." + $artifactUrl = (Invoke-RestMethod -Uri "https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" -Headers $headers).artifacts[0].archive_download_url + + if ($artifactUrl) { + Write-Host "Artifact URL successfully fetched: $artifactUrl" + } else { + Write-Error "Failed to fetch the artifact URL." + exit 1 + } + + # Download the artifact (zipped file) + Write-Host "Starting artifact download..." + $artifactZipPath = "$env:TEMP\artifact.zip" + try { + Invoke-WebRequest -Uri $artifactUrl ` + -Headers $headers ` + -OutFile $artifactZipPath ` + -MaximumRedirection 5 + + Write-Host "Artifact downloaded successfully to $artifactZipPath" + } catch { + Write-Error "Error downloading artifact: $_" + exit 1 + } + + # Unzip the artifact + $artifactUnzipPath = "$env:TEMP\artifact" + Write-Host "Unzipping the artifact to $artifactUnzipPath..." + try { + Expand-Archive -Path $artifactZipPath -DestinationPath $artifactUnzipPath -Force + Write-Host "Artifact unzipped successfully to $artifactUnzipPath" + } catch { + Write-Error "Failed to unzip the artifact: $_" + exit 1 + } + + # Find the installer or app executable + $artifactInstallerPath = Get-ChildItem -Path $artifactUnzipPath -Filter *.exe -Recurse | Select-Object -First 1 + + if ($artifactInstallerPath) { + Write-Host "Executable file found: $($artifactInstallerPath.FullName)" + } else { + Write-Error "Executable file not found. Exiting." + exit 1 + } + + # Run the installer and log the result + Write-Host "Running the installer: $($artifactInstallerPath.FullName)..." + try { + Start-Process -FilePath $artifactInstallerPath.FullName -Wait + Write-Host "Installer ran successfully." + } catch { + Write-Error "Failed to run the installer: $_" + exit 1 + } + + # Optional: If the app executable is different from the installer, find and launch it + $wavePath = Join-Path $env:USERPROFILE "AppData\Local\Programs\waveterm\Wave.exe" + + Write-Host "Launching the application: $($wavePath)" + Start-Process -FilePath $wavePath + Write-Host "Application launched." + + prompt: | + 1. /run testdriver/onboarding.yml + 2. /generate desktop 20 diff --git a/assets/appicon-windows.png b/assets/appicon-windows.png new file mode 100644 index 000000000..e2a8ed412 Binary files /dev/null and b/assets/appicon-windows.png differ diff --git a/assets/appicon-windows.svg b/assets/appicon-windows.svg new file mode 100644 index 000000000..6fbf2443c --- /dev/null +++ b/assets/appicon-windows.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/appicon.png b/build/appicon.png deleted file mode 100644 index a10809fb0..000000000 Binary files a/build/appicon.png and /dev/null differ diff --git a/build/icons.icns b/build/icon.icns similarity index 100% rename from build/icons.icns rename to build/icon.icns diff --git a/build/icon.ico b/build/icon.ico index 523704eaa..a6d2d8787 100644 Binary files a/build/icon.ico and b/build/icon.ico differ diff --git a/electron-builder.config.cjs b/electron-builder.config.cjs index 89841c218..8768fcd3d 100644 --- a/electron-builder.config.cjs +++ b/electron-builder.config.cjs @@ -3,6 +3,8 @@ const pkg = require("./package.json"); const fs = require("fs"); const path = require("path"); +const windowsShouldSign = !!process.env.SM_CODE_SIGNING_CERT_SHA1_HASH; + /** * @type {import('electron-builder').Configuration} * @see https://www.electron.build/configuration/configuration @@ -47,7 +49,6 @@ const config = { arch: ["universal", "arm64", "x64"], }, ], - icon: "build/icons.icns", category: "public.app-category.developer-tools", minimumSystemVersion: "10.15.0", mergeASARs: true, @@ -57,7 +58,6 @@ const config = { artifactName: "${name}-${platform}-${arch}-${version}.${ext}", category: "TerminalEmulator", executableName: pkg.name, - icon: "build/icons.icns", target: ["zip", "deb", "rpm", "AppImage", "pacman"], synopsis: pkg.description, description: null, @@ -73,12 +73,13 @@ const config = { afterInstall: "build/deb-postinstall.tpl", }, win: { - icon: "build/icons.icns", - publisherName: "Command Line Inc", target: ["nsis", "msi", "zip"], - certificateSubjectName: "Command Line Inc", - certificateSha1: process.env.SM_CODE_SIGNING_CERT_SHA1_HASH, - signingHashAlgorithms: ["sha256"], + signtoolOptions: windowsShouldSign && { + signingHashAlgorithms: ["sha256"], + publisherName: "Command Line Inc", + certificateSubjectName: "Command Line Inc", + certificateSha1: process.env.SM_CODE_SIGNING_CERT_SHA1_HASH, + }, }, appImage: { license: "LICENSE", diff --git a/emain/docsite.ts b/emain/docsite.ts index ccdcc602b..ddf9d21b8 100644 --- a/emain/docsite.ts +++ b/emain/docsite.ts @@ -17,7 +17,10 @@ export async function initDocsite() { console.log("Embedded docsite is running, using embedded version for help view"); docsiteUrl = docsiteEmbeddedUrl; } else { - console.log("Embedded docsite is not running, using web version for help view", response); + console.log( + "Embedded docsite is not running, using web version for help view", + "status: " + response?.status + ); docsiteUrl = docsiteWebUrl; } } catch (error) { diff --git a/emain/emain.ts b/emain/emain.ts index e23c7f05c..4713f45e6 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -471,10 +471,12 @@ function createBrowserWindow(clientId: string, waveWindow: WaveWindow, fullConfi } }); win.on( + // @ts-expect-error "resize", debounce(400, (e) => mainResizeHandler(e, waveWindow.oid, win)) ); win.on( + // @ts-expect-error "move", debounce(400, (e) => mainResizeHandler(e, waveWindow.oid, win)) ); @@ -1073,7 +1075,7 @@ async function relaunchBrowserWindows(): Promise { } for (const win of wins) { await win.readyPromise; - console.log("show", win.waveWindowId); + console.log("show window", win.waveWindowId); win.show(); } } diff --git a/frontend/app/app-bg.tsx b/frontend/app/app-bg.tsx new file mode 100644 index 000000000..5cc4a929f --- /dev/null +++ b/frontend/app/app-bg.tsx @@ -0,0 +1,123 @@ +import { getWebServerEndpoint } from "@/util/endpoints"; +import * as util from "@/util/util"; +import useResizeObserver from "@react-hook/resize-observer"; +import { generate as generateCSS, parse as parseCSS, walk as walkCSS } from "css-tree"; +import { useAtomValue } from "jotai"; +import { CSSProperties, useCallback, useLayoutEffect, useRef } from "react"; +import { debounce } from "throttle-debounce"; +import { atoms, getApi, PLATFORM, WOS } from "./store/global"; +import { useWaveObjectValue } from "./store/wos"; + +function encodeFileURL(file: string) { + const webEndpoint = getWebServerEndpoint(); + return webEndpoint + `/wave/stream-file?path=${encodeURIComponent(file)}&no404=1`; +} + +function processBackgroundUrls(cssText: string): string { + if (util.isBlank(cssText)) { + return null; + } + cssText = cssText.trim(); + if (cssText.endsWith(";")) { + cssText = cssText.slice(0, -1); + } + const attrRe = /^background(-image)?\s*:\s*/i; + cssText = cssText.replace(attrRe, ""); + const ast = parseCSS("background: " + cssText, { + context: "declaration", + }); + let hasUnsafeUrl = false; + walkCSS(ast, { + visit: "Url", + enter(node) { + const originalUrl = node.value.trim(); + if ( + originalUrl.startsWith("http:") || + originalUrl.startsWith("https:") || + originalUrl.startsWith("data:") + ) { + return; + } + // allow file:/// urls (if they are absolute) + if (originalUrl.startsWith("file://")) { + const path = originalUrl.slice(7); + if (!path.startsWith("/")) { + console.log(`Invalid background, contains a non-absolute file URL: ${originalUrl}`); + hasUnsafeUrl = true; + return; + } + const newUrl = encodeFileURL(path); + node.value = newUrl; + return; + } + // allow absolute paths + if (originalUrl.startsWith("/") || originalUrl.startsWith("~/")) { + const newUrl = encodeFileURL(originalUrl); + node.value = newUrl; + return; + } + hasUnsafeUrl = true; + console.log(`Invalid background, contains an unsafe URL scheme: ${originalUrl}`); + }, + }); + if (hasUnsafeUrl) { + return null; + } + const rtnStyle = generateCSS(ast); + if (rtnStyle == null) { + return null; + } + return rtnStyle.replace(/^background:\s*/, ""); +} + +export function AppBackground() { + const bgRef = useRef(null); + const tabId = useAtomValue(atoms.activeTabId); + const [tabData] = useWaveObjectValue(WOS.makeORef("tab", tabId)); + const bgAttr = tabData?.meta?.bg; + const style: CSSProperties = {}; + if (!util.isBlank(bgAttr)) { + try { + const processedBg = processBackgroundUrls(bgAttr); + if (!util.isBlank(processedBg)) { + const opacity = util.boundNumber(tabData?.meta?.["bg:opacity"], 0, 1) ?? 0.5; + style.opacity = opacity; + style.background = processedBg; + const blendMode = tabData?.meta?.["bg:blendmode"]; + if (!util.isBlank(blendMode)) { + style.backgroundBlendMode = blendMode; + } + } + } catch (e) { + console.error("error processing background", e); + } + } + const getAvgColor = useCallback( + debounce(30, () => { + if ( + bgRef.current && + PLATFORM !== "darwin" && + bgRef.current && + "windowControlsOverlay" in window.navigator + ) { + const titlebarRect: Dimensions = (window.navigator.windowControlsOverlay as any).getTitlebarAreaRect(); + const bgRect = bgRef.current.getBoundingClientRect(); + if (titlebarRect && bgRect) { + const windowControlsLeft = titlebarRect.width - titlebarRect.height; + const windowControlsRect: Dimensions = { + top: titlebarRect.top, + left: windowControlsLeft, + height: titlebarRect.height, + width: bgRect.width - bgRect.left - windowControlsLeft, + }; + getApi().updateWindowControlsOverlay(windowControlsRect); + } + } + }), + [bgRef, style] + ); + useLayoutEffect(getAvgColor, [getAvgColor]); + useResizeObserver(bgRef, getAvgColor); + + return
; +} diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index 627e4a20b..02ce0da0d 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -1,26 +1,22 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { useWaveObjectValue } from "@/app/store/wos"; import { Workspace } from "@/app/workspace/workspace"; import { ContextMenuModel } from "@/store/contextmenu"; -import { PLATFORM, WOS, atoms, getApi, globalStore, removeFlashError, useSettingsPrefixAtom } from "@/store/global"; +import { PLATFORM, atoms, createBlock, globalStore, removeFlashError, useSettingsPrefixAtom } from "@/store/global"; import { appHandleKeyDown } from "@/store/keymodel"; -import { getWebServerEndpoint } from "@/util/endpoints"; import { getElemAsStr } from "@/util/focusutil"; import * as keyutil from "@/util/keyutil"; import * as util from "@/util/util"; -import useResizeObserver from "@react-hook/resize-observer"; import clsx from "clsx"; import Color from "color"; -import * as csstree from "css-tree"; import debug from "debug"; -import * as jotai from "jotai"; +import { Provider, useAtomValue } from "jotai"; import "overlayscrollbars/overlayscrollbars.css"; -import * as React from "react"; +import { Fragment, useEffect, useState } from "react"; import { DndProvider } from "react-dnd"; import { HTML5Backend } from "react-dnd-html5-backend"; -import { debounce } from "throttle-debounce"; +import { AppBackground } from "./app-bg"; import "./app.less"; import { CenteredDiv } from "./element/quickelems"; @@ -28,7 +24,6 @@ const dlog = debug("wave:app"); const focusLog = debug("wave:focus"); const App = () => { - let Provider = jotai.Provider; return ( @@ -36,7 +31,7 @@ const App = () => { ); }; -function isContentEditableBeingEdited() { +function isContentEditableBeingEdited(): boolean { const activeElement = document.activeElement; return ( activeElement && @@ -45,17 +40,17 @@ function isContentEditableBeingEdited() { ); } -function canEnablePaste() { +function canEnablePaste(): boolean { const activeElement = document.activeElement; return activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA" || isContentEditableBeingEdited(); } -function canEnableCopy() { +function canEnableCopy(): boolean { const sel = window.getSelection(); return !util.isBlank(sel?.toString()); } -function canEnableCut() { +function canEnableCut(): boolean { const sel = window.getSelection(); if (document.activeElement?.classList.contains("xterm-helper-textarea")) { return false; @@ -63,12 +58,26 @@ function canEnableCut() { return !util.isBlank(sel?.toString()) && canEnablePaste(); } -function handleContextMenu(e: React.MouseEvent) { +async function getClipboardURL(): Promise { + try { + const clipboardText = await navigator.clipboard.readText(); + if (clipboardText == null) { + return null; + } + const url = new URL(clipboardText); + return url; + } catch (e) { + return null; + } +} + +async function handleContextMenu(e: React.MouseEvent) { e.preventDefault(); const canPaste = canEnablePaste(); const canCopy = canEnableCopy(); const canCut = canEnableCut(); - if (!canPaste && !canCopy && !canCut) { + const clipboardURL = await getClipboardURL(); + if (!canPaste && !canCopy && !canCut && !clipboardURL) { return; } let menu: ContextMenuItem[] = []; @@ -81,13 +90,27 @@ function handleContextMenu(e: React.MouseEvent) { if (canPaste) { menu.push({ label: "Paste", role: "paste" }); } + if (clipboardURL) { + menu.push({ type: "separator" }); + menu.push({ + label: "Open Clipboard URL (" + clipboardURL.hostname + ")", + click: () => { + createBlock({ + meta: { + view: "web", + url: clipboardURL.toString(), + }, + }); + }, + }); + } ContextMenuModel.showContextMenu(menu, e); } function AppSettingsUpdater() { const windowSettingsAtom = useSettingsPrefixAtom("window"); - const windowSettings = jotai.useAtomValue(windowSettingsAtom); - React.useEffect(() => { + const windowSettings = useAtomValue(windowSettingsAtom); + useEffect(() => { const isTransparentOrBlur = (windowSettings?.["window:transparent"] || windowSettings?.["window:blur"]) ?? false; const opacity = util.boundNumber(windowSettings?.["window:opacity"] ?? 0.8, 0, 1); @@ -126,7 +149,7 @@ function AppFocusHandler() { return null; // for debugging - React.useEffect(() => { + useEffect(() => { document.addEventListener("focusin", appFocusIn); document.addEventListener("focusout", appFocusOut); document.addEventListener("selectionchange", appSelectionChange); @@ -146,105 +169,8 @@ function AppFocusHandler() { return null; } -function encodeFileURL(file: string) { - const webEndpoint = getWebServerEndpoint(); - return webEndpoint + `/wave/stream-file?path=${encodeURIComponent(file)}&no404=1`; -} - -function processBackgroundUrls(cssText: string): string { - if (util.isBlank(cssText)) { - return null; - } - cssText = cssText.trim(); - if (cssText.endsWith(";")) { - cssText = cssText.slice(0, -1); - } - const attrRe = /^background(-image):\s*/; - cssText = cssText.replace(attrRe, ""); - const ast = csstree.parse("background: " + cssText, { - context: "declaration", - }); - let hasJSUrl = false; - csstree.walk(ast, { - visit: "Url", - enter(node) { - const originalUrl = node.value.trim(); - if (originalUrl.startsWith("javascript:")) { - hasJSUrl = true; - return; - } - if (originalUrl.startsWith("data:")) { - return; - } - const newUrl = encodeFileURL(originalUrl); - node.value = newUrl; - }, - }); - if (hasJSUrl) { - console.log("invalid background, contains a 'javascript' protocol url which is not allowed"); - return null; - } - const rtnStyle = csstree.generate(ast); - if (rtnStyle == null) { - return null; - } - return rtnStyle.replace(/^background:\s*/, ""); -} - -function AppBackground() { - const bgRef = React.useRef(null); - const tabId = jotai.useAtomValue(atoms.activeTabId); - const [tabData] = useWaveObjectValue(WOS.makeORef("tab", tabId)); - const bgAttr = tabData?.meta?.bg; - const style: React.CSSProperties = {}; - if (!util.isBlank(bgAttr)) { - try { - const processedBg = processBackgroundUrls(bgAttr); - if (!util.isBlank(processedBg)) { - const opacity = util.boundNumber(tabData?.meta?.["bg:opacity"], 0, 1) ?? 0.5; - style.opacity = opacity; - style.background = processedBg; - const blendMode = tabData?.meta?.["bg:blendmode"]; - if (!util.isBlank(blendMode)) { - style.backgroundBlendMode = blendMode; - } - } - } catch (e) { - console.error("error processing background", e); - } - } - const getAvgColor = React.useCallback( - debounce(30, () => { - if ( - bgRef.current && - PLATFORM !== "darwin" && - bgRef.current && - "windowControlsOverlay" in window.navigator - ) { - const titlebarRect: Dimensions = (window.navigator.windowControlsOverlay as any).getTitlebarAreaRect(); - const bgRect = bgRef.current.getBoundingClientRect(); - if (titlebarRect && bgRect) { - const windowControlsLeft = titlebarRect.width - titlebarRect.height; - const windowControlsRect: Dimensions = { - top: titlebarRect.top, - left: windowControlsLeft, - height: titlebarRect.height, - width: bgRect.width - bgRect.left - windowControlsLeft, - }; - getApi().updateWindowControlsOverlay(windowControlsRect); - } - } - }), - [bgRef, style] - ); - React.useLayoutEffect(getAvgColor, [getAvgColor]); - useResizeObserver(bgRef, getAvgColor); - - return
; -} - const AppKeyHandlers = () => { - React.useEffect(() => { + useEffect(() => { const staticKeyDownHandler = keyutil.keydownWrapper(appHandleKeyDown); document.addEventListener("keydown", staticKeyDownHandler); @@ -256,11 +182,11 @@ const AppKeyHandlers = () => { }; const FlashError = () => { - const flashErrors = jotai.useAtomValue(atoms.flashErrors); - const [hoveredId, setHoveredId] = React.useState(null); - const [ticker, setTicker] = React.useState(0); + const flashErrors = useAtomValue(atoms.flashErrors); + const [hoveredId, setHoveredId] = useState(null); + const [ticker, setTicker] = useState(0); - React.useEffect(() => { + useEffect(() => { if (flashErrors.length == 0 || hoveredId != null) { return; } @@ -297,10 +223,10 @@ const FlashError = () => { function convertNewlinesToBreaks(text) { return text.split("\n").map((part, index) => ( - + {part}
-
+ )); } @@ -328,10 +254,10 @@ const FlashError = () => { }; const AppInner = () => { - const prefersReducedMotion = jotai.useAtomValue(atoms.prefersReducedMotionAtom); - const client = jotai.useAtomValue(atoms.client); - const windowData = jotai.useAtomValue(atoms.waveWindow); - const isFullScreen = jotai.useAtomValue(atoms.isFullScreen); + const prefersReducedMotion = useAtomValue(atoms.prefersReducedMotionAtom); + const client = useAtomValue(atoms.client); + const windowData = useAtomValue(atoms.waveWindow); + const isFullScreen = useAtomValue(atoms.isFullScreen); if (client == null || windowData == null) { return ( diff --git a/frontend/app/block/block.less b/frontend/app/block/block.less index 967098c69..75c44a1fd 100644 --- a/frontend/app/block/block.less +++ b/frontend/app/block/block.less @@ -221,15 +221,6 @@ font-size: 11px; } } - - .block-frame-div-url, - .block-frame-div-search { - background: rgba(255, 255, 255, 0.1); - - input { - opacity: 1; - } - } } .block-frame-end-icons { diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index 56a407d29..81c04c3df 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -4,6 +4,7 @@ import { BlockComponentModel2, BlockProps } from "@/app/block/blocktypes"; import { PlotView } from "@/app/view/plotview/plotview"; import { PreviewModel, PreviewView, makePreviewModel } from "@/app/view/preview/preview"; +import { SysinfoView, SysinfoViewModel, makeSysinfoViewModel } from "@/app/view/sysinfo/sysinfo"; import { ErrorBoundary } from "@/element/errorboundary"; import { CenteredDiv } from "@/element/quickelems"; import { NodeModel, useDebouncedNodeInnerRect } from "@/layout/index"; @@ -17,7 +18,6 @@ import { getWaveObjectAtom, makeORef, useWaveObjectValue } from "@/store/wos"; import { focusedBlockId, getElemAsStr } from "@/util/focusutil"; import { isBlank } from "@/util/util"; import { Chat, ChatModel, makeChatModel } from "@/view/chat/chat"; -import { CpuPlotView, CpuPlotViewModel, makeCpuPlotViewModel } from "@/view/cpuplot/cpuplot"; import { HelpView, HelpViewModel, makeHelpViewModel } from "@/view/helpview/helpview"; import { QuickTipsView, QuickTipsViewModel } from "@/view/quicktipsview/quicktipsview"; import { TermViewModel, TerminalView, makeTerminalModel } from "@/view/term/term"; @@ -48,8 +48,9 @@ function makeViewModel(blockId: string, blockView: string, nodeModel: NodeModel) if (blockView === "waveai") { return makeWaveAiViewModel(blockId); } - if (blockView === "cpuplot") { - return makeCpuPlotViewModel(blockId); + if (blockView === "cpuplot" || blockView == "sysinfo") { + // "cpuplot" is for backwards compatibility with already-opened widgets + return makeSysinfoViewModel(blockId, blockView); } if (blockView === "help") { return makeHelpViewModel(blockId, nodeModel); @@ -96,8 +97,9 @@ function getViewElem( if (blockView === "waveai") { return ; } - if (blockView === "cpuplot") { - return ; + if (blockView === "cpuplot" || blockView === "sysinfo") { + // "cpuplot" is for backwards compatibility with already opened widgets + return ; } if (blockView == "help") { return ; diff --git a/frontend/app/store/ws.ts b/frontend/app/store/ws.ts index 1b11ab326..bbf5477b1 100644 --- a/frontend/app/store/ws.ts +++ b/frontend/app/store/ws.ts @@ -12,6 +12,7 @@ const dlog = debug("wave:ws"); const WarnWebSocketSendSize = 1024 * 1024; // 1MB const MaxWebSocketSendSize = 5 * 1024 * 1024; // 5MB const reconnectHandlers: (() => void)[] = []; +const StableConnTime = 2000; function addWSReconnectHandler(handler: () => void) { reconnectHandlers.push(handler); @@ -45,6 +46,7 @@ class WSControl { lastReconnectTime: number = 0; eoOpts: ElectronOverrideOpts; noReconnect: boolean = false; + onOpenTimeoutId: NodeJS.Timeout = null; constructor( baseHostPort: string, @@ -80,9 +82,15 @@ class WSControl { } : null ); - this.wsConn.onopen = this.onopen.bind(this); - this.wsConn.onmessage = this.onmessage.bind(this); - this.wsConn.onclose = this.onclose.bind(this); + this.wsConn.onopen = (e: Event) => { + this.onopen(e); + }; + this.wsConn.onmessage = (e: MessageEvent) => { + this.onmessage(e); + }; + this.wsConn.onclose = (e: CloseEvent) => { + this.onclose(e); + }; // turns out onerror is not necessary (onclose always follows onerror) // this.wsConn.onerror = this.onerror; } @@ -118,8 +126,11 @@ class WSControl { }, timeout * 1000); } - onclose(event: any) { + onclose(event: CloseEvent) { // console.log("close", event); + if (this.onOpenTimeoutId) { + clearTimeout(this.onOpenTimeoutId); + } if (event.wasClean) { dlog("connection closed"); } else { @@ -132,15 +143,18 @@ class WSControl { } } - onopen() { + onopen(e: Event) { dlog("connection open"); this.open = true; this.opening = false; + this.onOpenTimeoutId = setTimeout(() => { + this.reconnectTimes = 0; + dlog("clear reconnect times"); + }, StableConnTime); for (let handler of reconnectHandlers) { handler(); } this.runMsgQueue(); - // reconnectTimes is reset in onmessage:hello } runMsgQueue() { @@ -157,7 +171,7 @@ class WSControl { }, 100); } - onmessage(event: any) { + onmessage(event: MessageEvent) { let eventData = null; if (event.data != null) { eventData = JSON.parse(event.data); @@ -173,10 +187,6 @@ class WSControl { // nothing return; } - if (eventData.type == "hello") { - this.reconnectTimes = 0; - return; - } if (this.messageCallback) { try { this.messageCallback(eventData); diff --git a/frontend/app/theme.less b/frontend/app/theme.less index 4c33e6c3a..1904523a7 100644 --- a/frontend/app/theme.less +++ b/frontend/app/theme.less @@ -106,6 +106,9 @@ --conn-icon-color-7: #dbde52; --conn-icon-color-8: #58c142; + --sysinfo-cpu-color: #58c142; + --sysinfo-mem-color: #53b4ea; + --bulb-color: rgb(255, 221, 51); // term colors (16 + 6) form the base terminal theme diff --git a/frontend/app/view/cpuplot/cpuplot.less b/frontend/app/view/cpuplot/cpuplot.less deleted file mode 100644 index 4f950dc63..000000000 --- a/frontend/app/view/cpuplot/cpuplot.less +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2024, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -.plot-view { - display: flex; - justify-content: center; - align-items: stretch; - width: 100%; -} diff --git a/frontend/app/view/cpuplot/cpuplot.tsx b/frontend/app/view/cpuplot/cpuplot.tsx deleted file mode 100644 index e773f9a3a..000000000 --- a/frontend/app/view/cpuplot/cpuplot.tsx +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright 2024, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { getConnStatusAtom, globalStore, WOS } from "@/store/global"; -import * as util from "@/util/util"; -import * as Plot from "@observablehq/plot"; -import dayjs from "dayjs"; -import * as htl from "htl"; -import * as jotai from "jotai"; -import * as React from "react"; - -import { useDimensionsWithExistingRef } from "@/app/hook/useDimensions"; -import { waveEventSubscribe } from "@/app/store/wps"; -import { RpcApi } from "@/app/store/wshclientapi"; -import { WindowRpcClient } from "@/app/store/wshrpcutil"; -import "./cpuplot.less"; - -const DefaultNumPoints = 120; - -type DataItem = { - ts: number; - [k: string]: number; -}; - -const SysInfoMetricNames = { - cpu: "CPU %", - "mem:total": "Memory Total", - "mem:used": "Memory Used", - "mem:free": "Memory Free", - "mem:available": "Memory Available", -}; -for (let i = 0; i < 32; i++) { - SysInfoMetricNames[`cpu:${i}`] = `CPU[${i}] %`; -} - -function convertWaveEventToDataItem(event: WaveEvent): DataItem { - const eventData: TimeSeriesData = event.data; - if (eventData == null || eventData.ts == null || eventData.values == null) { - return null; - } - const dataItem = { ts: eventData.ts }; - for (const key in eventData.values) { - dataItem[key] = eventData.values[key]; - } - return dataItem; -} - -class CpuPlotViewModel { - viewType: string; - blockAtom: jotai.Atom; - termMode: jotai.Atom; - htmlElemFocusRef: React.RefObject; - blockId: string; - viewIcon: jotai.Atom; - viewText: jotai.Atom; - viewName: jotai.Atom; - dataAtom: jotai.PrimitiveAtom>; - addDataAtom: jotai.WritableAtom; - incrementCount: jotai.WritableAtom>; - loadingAtom: jotai.PrimitiveAtom; - numPoints: jotai.Atom; - metrics: jotai.Atom; - connection: jotai.Atom; - manageConnection: jotai.Atom; - connStatus: jotai.Atom; - - constructor(blockId: string) { - this.viewType = "cpuplot"; - this.blockId = blockId; - this.blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`); - this.addDataAtom = jotai.atom(null, (get, set, points) => { - const targetLen = get(this.numPoints) + 1; - let data = get(this.dataAtom); - try { - if (data.length > targetLen) { - data = data.slice(data.length - targetLen); - } - if (data.length < targetLen) { - const defaultData = this.getDefaultData(); - data = [...defaultData.slice(defaultData.length - targetLen + data.length), ...data]; - } - const newData = [...data.slice(points.length), ...points]; - set(this.dataAtom, newData); - } catch (e) { - console.log("Error adding data to cpuplot", e); - } - }); - this.manageConnection = jotai.atom(true); - this.loadingAtom = jotai.atom(true); - this.numPoints = jotai.atom((get) => { - const blockData = get(this.blockAtom); - const metaNumPoints = blockData?.meta?.["graph:numpoints"]; - if (metaNumPoints == null || metaNumPoints <= 0) { - return DefaultNumPoints; - } - return metaNumPoints; - }); - this.metrics = jotai.atom((get) => { - const blockData = get(this.blockAtom); - const metrics = blockData?.meta?.["graph:metrics"]; - if (metrics == null || !Array.isArray(metrics)) { - return ["cpu"]; - } - return metrics; - }); - this.viewIcon = jotai.atom((get) => { - return "chart-line"; // should not be hardcoded - }); - this.viewName = jotai.atom((get) => { - return "CPU %"; // should not be hardcoded - }); - this.incrementCount = jotai.atom(null, async (get, set) => { - const meta = get(this.blockAtom).meta; - const count = meta.count ?? 0; - await RpcApi.SetMetaCommand(WindowRpcClient, { - oref: WOS.makeORef("block", this.blockId), - meta: { count: count + 1 }, - }); - }); - this.connection = jotai.atom((get) => { - const blockData = get(this.blockAtom); - const connValue = blockData?.meta?.connection; - if (util.isBlank(connValue)) { - return "local"; - } - return connValue; - }); - this.dataAtom = jotai.atom(this.getDefaultData()); - this.loadInitialData(); - this.connStatus = jotai.atom((get) => { - const blockData = get(this.blockAtom); - const connName = blockData?.meta?.connection; - const connAtom = getConnStatusAtom(connName); - return get(connAtom); - }); - } - - async loadInitialData() { - globalStore.set(this.loadingAtom, true); - try { - const numPoints = globalStore.get(this.numPoints); - const connName = globalStore.get(this.connection); - const initialData = await RpcApi.EventReadHistoryCommand(WindowRpcClient, { - event: "sysinfo", - scope: connName, - maxitems: numPoints, - }); - if (initialData == null) { - return; - } - const newData = this.getDefaultData(); - const initialDataItems: DataItem[] = initialData.map(convertWaveEventToDataItem); - // splice the initial data into the default data (replacing the newest points) - newData.splice(newData.length - initialDataItems.length, initialDataItems.length, ...initialDataItems); - globalStore.set(this.addDataAtom, newData); - } catch (e) { - console.log("Error loading initial data for cpuplot", e); - } finally { - globalStore.set(this.loadingAtom, false); - } - } - - getDefaultData(): DataItem[] { - // set it back one to avoid backwards line being possible - const numPoints = globalStore.get(this.numPoints); - const currentTime = Date.now() - 1000; - const points: DataItem[] = []; - for (let i = numPoints; i > -1; i--) { - points.push({ ts: currentTime - i * 1000 }); - } - return points; - } -} - -function makeCpuPlotViewModel(blockId: string): CpuPlotViewModel { - const cpuPlotViewModel = new CpuPlotViewModel(blockId); - return cpuPlotViewModel; -} - -const plotColors = ["#58C142", "#FFC107", "#FF5722", "#2196F3", "#9C27B0", "#00BCD4", "#FFEB3B", "#795548"]; - -type CpuPlotViewProps = { - blockId: string; - model: CpuPlotViewModel; -}; - -function CpuPlotView({ model, blockId }: CpuPlotViewProps) { - const connName = jotai.useAtomValue(model.connection); - const lastConnName = React.useRef(connName); - const connStatus = jotai.useAtomValue(model.connStatus); - const addPlotData = jotai.useSetAtom(model.addDataAtom); - const loading = jotai.useAtomValue(model.loadingAtom); - - React.useEffect(() => { - if (connStatus?.status != "connected") { - return; - } - if (lastConnName.current !== connName) { - lastConnName.current = connName; - model.loadInitialData(); - } - }, [connStatus.status, connName]); - React.useEffect(() => { - const unsubFn = waveEventSubscribe({ - eventType: "sysinfo", - scope: connName, - handler: (event) => { - const loading = globalStore.get(model.loadingAtom); - if (loading) { - return; - } - const dataItem = convertWaveEventToDataItem(event); - addPlotData([dataItem]); - }, - }); - console.log("subscribe to sysinfo", connName); - return () => { - unsubFn(); - }; - }, [connName]); - if (connStatus?.status != "connected") { - return null; - } - if (loading) { - return null; - } - return ; -} - -const CpuPlotViewInner = React.memo(({ model }: CpuPlotViewProps) => { - const containerRef = React.useRef(); - const plotData = jotai.useAtomValue(model.dataAtom); - const domRect = useDimensionsWithExistingRef(containerRef, 30); - const parentHeight = domRect?.height ?? 0; - const parentWidth = domRect?.width ?? 0; - const yvals = jotai.useAtomValue(model.metrics); - - React.useEffect(() => { - const marks: Plot.Markish[] = []; - marks.push( - () => htl.svg` - - - - - ` - ); - if (yvals.length == 0) { - // nothing - } else if (yvals.length == 1) { - marks.push( - Plot.lineY(plotData, { - stroke: plotColors[0], - strokeWidth: 2, - x: "ts", - y: yvals[0], - }) - ); - marks.push( - Plot.areaY(plotData, { - fill: "url(#gradient)", - x: "ts", - y: yvals[0], - }) - ); - } else { - let idx = 0; - for (const yval of yvals) { - marks.push( - Plot.lineY(plotData, { - stroke: plotColors[idx % plotColors.length], - strokeWidth: 1, - x: "ts", - y: yval, - }) - ); - idx++; - } - } - const plot = Plot.plot({ - x: { grid: true, label: "time", tickFormat: (d) => `${dayjs.unix(d / 1000).format("HH:mm:ss")}` }, - y: { label: "%", domain: [0, 100] }, - width: parentWidth, - height: parentHeight, - marks: marks, - }); - - if (plot !== undefined) { - containerRef.current.append(plot); - } - - return () => { - if (plot !== undefined) { - plot.remove(); - } - }; - }, [plotData, parentHeight, parentWidth]); - - return
; -}); - -export { CpuPlotView, CpuPlotViewModel, makeCpuPlotViewModel }; diff --git a/frontend/app/view/helpview/helpview.tsx b/frontend/app/view/helpview/helpview.tsx index ae4176101..5f45c45b7 100644 --- a/frontend/app/view/helpview/helpview.tsx +++ b/frontend/app/view/helpview/helpview.tsx @@ -6,7 +6,7 @@ import { WebView, WebViewModel } from "@/app/view/webview/webview"; import { NodeModel } from "@/layout/index"; import { fireAndForget } from "@/util/util"; import { atom, useAtomValue } from "jotai"; -import { useEffect } from "react"; +import { useCallback } from "react"; import "./helpview.less"; class HelpViewModel extends WebViewModel { @@ -48,21 +48,35 @@ function makeHelpViewModel(blockId: string, nodeModel: NodeModel) { return new HelpViewModel(blockId, nodeModel); } +const baseUrlRegex = /http[s]?:\/\/([^:\/])+(:\d+)?/; + function HelpView({ model }: { model: HelpViewModel }) { const homepageUrl = useAtomValue(model.homepageUrl); - useEffect( - () => + + // Effect to update the docsite base url when the app restarts, since the webserver port is dynamic + const onFailLoad = useCallback( + (url: string) => fireAndForget(async () => { - const curDocsiteUrl = getApi().getDocsiteUrl(); - if (curDocsiteUrl !== homepageUrl) { - await model.setHomepageUrl(curDocsiteUrl, "block"); + const newDocsiteUrl = getApi().getDocsiteUrl(); + + // Correct the homepage URL, if necessary + if (newDocsiteUrl !== homepageUrl) { + await model.setHomepageUrl(newDocsiteUrl, "block"); + } + + // Correct the base URL of the current page, if necessary + const newBaseUrl = baseUrlRegex.exec(newDocsiteUrl)?.[0]; + const curBaseUrl = baseUrlRegex.exec(url)?.[0]; + console.log("fix-docsite-url", url, newDocsiteUrl, homepageUrl, curBaseUrl, newBaseUrl); + if (curBaseUrl && newBaseUrl && curBaseUrl !== newBaseUrl) { + model.loadUrl(url.replace(curBaseUrl, newBaseUrl), "fix-fail-load"); } }), - [] + [homepageUrl] ); return (
- +
); } diff --git a/frontend/app/view/preview/directorypreview.tsx b/frontend/app/view/preview/directorypreview.tsx index 59586c7a2..011cbfc52 100644 --- a/frontend/app/view/preview/directorypreview.tsx +++ b/frontend/app/view/preview/directorypreview.tsx @@ -534,6 +534,7 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { const [selectedPath, setSelectedPath] = useState(""); const [refreshVersion, setRefreshVersion] = useAtom(model.refreshVersion); const conn = useAtomValue(model.connection); + const blockData = useAtomValue(model.blockAtom); useEffect(() => { model.refreshCallback = () => { @@ -593,7 +594,12 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) { setSearchText((current) => current.slice(0, -1)); return true; } - if (checkKeyPressed(waveEvent, "Space") && searchText == "" && PLATFORM == "darwin") { + if ( + checkKeyPressed(waveEvent, "Space") && + searchText == "" && + PLATFORM == "darwin" && + !blockData?.meta?.connection + ) { getApi().onQuicklook(selectedPath); console.log(selectedPath); return true; diff --git a/frontend/app/view/sysinfo/sysinfo.less b/frontend/app/view/sysinfo/sysinfo.less new file mode 100644 index 000000000..2f36f915f --- /dev/null +++ b/frontend/app/view/sysinfo/sysinfo.less @@ -0,0 +1,33 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +.scrollable { + flex-flow: column nowrap; + flex-grow: 1; + margin-bottom: 0; + overflow-y: auto; + .sysinfo-view { + width: 100%; + height: 100%; + display: grid; + grid-template-rows: repeat(auto-fit, minmax(100px, 1fr)); + gap: 10px; + + &.two-columns { + grid-template-columns: 1fr 1fr; + } + + .sysinfo-plot-content { + min-height: 100px; + svg { + [aria-label="tip"] { + g { + path { + color: var(--border-color); + } + } + } + } + } + } +} diff --git a/frontend/app/view/sysinfo/sysinfo.tsx b/frontend/app/view/sysinfo/sysinfo.tsx new file mode 100644 index 000000000..6c15cff47 --- /dev/null +++ b/frontend/app/view/sysinfo/sysinfo.tsx @@ -0,0 +1,502 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { getConnStatusAtom, globalStore, WOS } from "@/store/global"; +import * as util from "@/util/util"; +import * as Plot from "@observablehq/plot"; +import clsx from "clsx"; +import dayjs from "dayjs"; +import * as htl from "htl"; +import * as jotai from "jotai"; +import * as React from "react"; + +import { useDimensionsWithExistingRef } from "@/app/hook/useDimensions"; +import { waveEventSubscribe } from "@/app/store/wps"; +import { RpcApi } from "@/app/store/wshclientapi"; +import { WindowRpcClient } from "@/app/store/wshrpcutil"; +import { atoms } from "@/store/global"; +import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react"; +import "./sysinfo.less"; + +const DefaultNumPoints = 120; + +type DataItem = { + ts: number; + [k: string]: number; +}; + +function defaultCpuMeta(name: string): TimeSeriesMeta { + return { + name: name, + label: "%", + miny: 0, + maxy: 100, + color: "var(--sysinfo-cpu-color)", + decimalPlaces: 0, + }; +} + +function defaultMemMeta(name: string, maxY: string): TimeSeriesMeta { + return { + name: name, + label: "GB", + miny: 0, + maxy: maxY, + color: "var(--sysinfo-mem-color)", + decimalPlaces: 1, + }; +} + +const PlotTypes: Object = { + CPU: function (dataItem: DataItem): Array { + return ["cpu"]; + }, + Mem: function (dataItem: DataItem): Array { + return ["mem:used"]; + }, + "CPU + Mem": function (dataItem: DataItem): Array { + return ["cpu", "mem:used"]; + }, + "All CPU": function (dataItem: DataItem): Array { + return Object.keys(dataItem) + .filter((item) => item.startsWith("cpu") && item != "cpu") + .sort((a, b) => { + const valA = parseInt(a.replace("cpu:", "")); + const valB = parseInt(b.replace("cpu:", "")); + return valA - valB; + }); + }, +}; + +const DefaultPlotMeta = { + cpu: defaultCpuMeta("CPU %"), + "mem:total": defaultMemMeta("Memory Total", "mem:total"), + "mem:used": defaultMemMeta("Memory Used", "mem:total"), + "mem:free": defaultMemMeta("Memory Free", "mem:total"), + "mem:available": defaultMemMeta("Memory Available", "mem:total"), +}; +for (let i = 0; i < 32; i++) { + DefaultPlotMeta[`cpu:${i}`] = defaultCpuMeta(`Core ${i}`); +} + +function convertWaveEventToDataItem(event: WaveEvent): DataItem { + const eventData: TimeSeriesData = event.data; + if (eventData == null || eventData.ts == null || eventData.values == null) { + return null; + } + const dataItem = { ts: eventData.ts }; + for (const key in eventData.values) { + dataItem[key] = eventData.values[key]; + } + return dataItem; +} + +class SysinfoViewModel { + viewType: string; + blockAtom: jotai.Atom; + termMode: jotai.Atom; + htmlElemFocusRef: React.RefObject; + blockId: string; + viewIcon: jotai.Atom; + viewText: jotai.Atom; + viewName: jotai.Atom; + dataAtom: jotai.PrimitiveAtom>; + addDataAtom: jotai.WritableAtom; + incrementCount: jotai.WritableAtom>; + loadingAtom: jotai.PrimitiveAtom; + numPoints: jotai.Atom; + metrics: jotai.Atom; + connection: jotai.Atom; + manageConnection: jotai.Atom; + connStatus: jotai.Atom; + plotMetaAtom: jotai.PrimitiveAtom>; + endIconButtons: jotai.Atom; + plotTypeSelectedAtom: jotai.Atom; + + constructor(blockId: string, viewType: string) { + this.viewType = viewType; + this.blockId = blockId; + this.blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`); + this.addDataAtom = jotai.atom(null, (get, set, points) => { + const targetLen = get(this.numPoints) + 1; + let data = get(this.dataAtom); + try { + if (data.length > targetLen) { + data = data.slice(data.length - targetLen); + } + if (data.length < targetLen) { + const defaultData = this.getDefaultData(); + data = [...defaultData.slice(defaultData.length - targetLen + data.length), ...data]; + } + const newData = [...data.slice(points.length), ...points]; + set(this.dataAtom, newData); + } catch (e) { + console.log("Error adding data to sysinfo", e); + } + }); + this.plotMetaAtom = jotai.atom(new Map(Object.entries(DefaultPlotMeta))); + this.manageConnection = jotai.atom(true); + this.loadingAtom = jotai.atom(true); + this.numPoints = jotai.atom((get) => { + const blockData = get(this.blockAtom); + const metaNumPoints = blockData?.meta?.["graph:numpoints"]; + if (metaNumPoints == null || metaNumPoints <= 0) { + return DefaultNumPoints; + } + return metaNumPoints; + }); + this.metrics = jotai.atom((get) => { + let plotType = get(this.plotTypeSelectedAtom); + const plotData = get(this.dataAtom); + try { + const metrics = PlotTypes[plotType](plotData[plotData.length - 1]); + if (metrics == null || !Array.isArray(metrics)) { + return ["cpu"]; + } + return metrics; + } catch (e) { + return ["cpu"]; + } + }); + this.plotTypeSelectedAtom = jotai.atom((get) => { + const blockData = get(this.blockAtom); + const plotType = blockData?.meta?.["sysinfo:type"]; + if (plotType == null || typeof plotType != "string") { + return "CPU"; + } + return plotType; + }); + this.viewIcon = jotai.atom((get) => { + return "chart-line"; // should not be hardcoded + }); + this.viewName = jotai.atom((get) => { + return get(this.plotTypeSelectedAtom); + }); + this.incrementCount = jotai.atom(null, async (get, set) => { + const meta = get(this.blockAtom).meta; + const count = meta.count ?? 0; + await RpcApi.SetMetaCommand(WindowRpcClient, { + oref: WOS.makeORef("block", this.blockId), + meta: { count: count + 1 }, + }); + }); + this.connection = jotai.atom((get) => { + const blockData = get(this.blockAtom); + const connValue = blockData?.meta?.connection; + if (util.isBlank(connValue)) { + return "local"; + } + return connValue; + }); + this.dataAtom = jotai.atom(this.getDefaultData()); + this.loadInitialData(); + this.connStatus = jotai.atom((get) => { + const blockData = get(this.blockAtom); + const connName = blockData?.meta?.connection; + const connAtom = getConnStatusAtom(connName); + return get(connAtom); + }); + } + + async loadInitialData() { + globalStore.set(this.loadingAtom, true); + try { + const numPoints = globalStore.get(this.numPoints); + const connName = globalStore.get(this.connection); + const initialData = await RpcApi.EventReadHistoryCommand(WindowRpcClient, { + event: "sysinfo", + scope: connName, + maxitems: numPoints, + }); + if (initialData == null) { + return; + } + const newData = this.getDefaultData(); + const initialDataItems: DataItem[] = initialData.map(convertWaveEventToDataItem); + // splice the initial data into the default data (replacing the newest points) + newData.splice(newData.length - initialDataItems.length, initialDataItems.length, ...initialDataItems); + globalStore.set(this.addDataAtom, newData); + } catch (e) { + console.log("Error loading initial data for sysinfo", e); + } finally { + globalStore.set(this.loadingAtom, false); + } + } + + getSettingsMenuItems(): ContextMenuItem[] { + const fullConfig = globalStore.get(atoms.fullConfigAtom); + const termThemes = fullConfig?.termthemes ?? {}; + const termThemeKeys = Object.keys(termThemes); + const plotData = globalStore.get(this.dataAtom); + + termThemeKeys.sort((a, b) => { + return termThemes[a]["display:order"] - termThemes[b]["display:order"]; + }); + const fullMenu: ContextMenuItem[] = []; + let submenu: ContextMenuItem[]; + if (plotData.length == 0) { + submenu = []; + } else { + submenu = Object.keys(PlotTypes).map((plotType) => { + const dataTypes = PlotTypes[plotType](plotData[plotData.length - 1]); + const currentlySelected = globalStore.get(this.plotTypeSelectedAtom); + const menuItem: ContextMenuItem = { + label: plotType, + type: "radio", + checked: currentlySelected == plotType, + click: async () => { + await RpcApi.SetMetaCommand(WindowRpcClient, { + oref: WOS.makeORef("block", this.blockId), + meta: { "graph:metrics": dataTypes, "sysinfo:type": plotType }, + }); + }, + }; + return menuItem; + }); + } + + fullMenu.push({ + label: "Plot Type", + submenu: submenu, + }); + fullMenu.push({ type: "separator" }); + return fullMenu; + } + + getDefaultData(): DataItem[] { + // set it back one to avoid backwards line being possible + const numPoints = globalStore.get(this.numPoints); + const currentTime = Date.now() - 1000; + const points: DataItem[] = []; + for (let i = numPoints; i > -1; i--) { + points.push({ ts: currentTime - i * 1000 }); + } + return points; + } +} + +function makeSysinfoViewModel(blockId: string, viewType: string): SysinfoViewModel { + const sysinfoViewModel = new SysinfoViewModel(blockId, viewType); + return sysinfoViewModel; +} + +const plotColors = ["#58C142", "#FFC107", "#FF5722", "#2196F3", "#9C27B0", "#00BCD4", "#FFEB3B", "#795548"]; + +type SysinfoViewProps = { + blockId: string; + model: SysinfoViewModel; +}; + +function resolveDomainBound(value: number | string, dataItem: DataItem): number | undefined { + if (typeof value == "number") { + return value; + } else if (typeof value == "string") { + return dataItem?.[value]; + } else { + return undefined; + } +} + +function SysinfoView({ model, blockId }: SysinfoViewProps) { + const connName = jotai.useAtomValue(model.connection); + const lastConnName = React.useRef(connName); + const connStatus = jotai.useAtomValue(model.connStatus); + const addPlotData = jotai.useSetAtom(model.addDataAtom); + const loading = jotai.useAtomValue(model.loadingAtom); + + React.useEffect(() => { + if (connStatus?.status != "connected") { + return; + } + if (lastConnName.current !== connName) { + lastConnName.current = connName; + model.loadInitialData(); + } + }, [connStatus.status, connName]); + React.useEffect(() => { + const unsubFn = waveEventSubscribe({ + eventType: "sysinfo", + scope: connName, + handler: (event) => { + const loading = globalStore.get(model.loadingAtom); + if (loading) { + return; + } + const dataItem = convertWaveEventToDataItem(event); + addPlotData([dataItem]); + }, + }); + console.log("subscribe to sysinfo", connName); + return () => { + unsubFn(); + }; + }, [connName]); + if (connStatus?.status != "connected") { + return null; + } + if (loading) { + return null; + } + return ; +} + +type SingleLinePlotProps = { + plotData: Array; + yval: string; + yvalMeta: TimeSeriesMeta; + blockId: string; + defaultColor: string; + title?: boolean; + sparkline?: boolean; +}; + +function SingleLinePlot({ + plotData, + yval, + yvalMeta, + blockId, + defaultColor, + title = false, + sparkline = false, +}: SingleLinePlotProps) { + const containerRef = React.useRef(); + const domRect = useDimensionsWithExistingRef(containerRef, 300); + const plotHeight = domRect?.height ?? 0; + const plotWidth = domRect?.width ?? 0; + const marks: Plot.Markish[] = []; + let decimalPlaces = yvalMeta?.decimalPlaces ?? 0; + let color = yvalMeta?.color; + if (!color) { + color = defaultColor; + } + marks.push( + () => htl.svg` + + + + + ` + ); + + marks.push( + Plot.lineY(plotData, { + stroke: color, + strokeWidth: 2, + x: "ts", + y: yval, + }) + ); + + // only add the gradient for single items + marks.push( + Plot.areaY(plotData, { + fill: `url(#gradient-${blockId}-${yval})`, + x: "ts", + y: yval, + }) + ); + if (title) { + marks.push( + Plot.text([yvalMeta.name], { + frameAnchor: "top-left", + dx: 4, + fill: "var(--grey-text-color)", + }) + ); + } + const labelY = yvalMeta?.label ?? "?"; + marks.push( + Plot.ruleX( + plotData, + Plot.pointerX({ x: "ts", py: yval, stroke: "var(--grey-text-color)", strokeWidth: 1, strokeDasharray: 2 }) + ) + ); + marks.push( + Plot.ruleY( + plotData, + Plot.pointerX({ px: "ts", y: yval, stroke: "var(--grey-text-color)", strokeWidth: 1, strokeDasharray: 2 }) + ) + ); + marks.push( + Plot.tip( + plotData, + Plot.pointerX({ + x: "ts", + y: yval, + fill: "var(--main-bg-color)", + anchor: "middle", + dy: -30, + title: (d) => + `${dayjs.unix(d.ts / 1000).format("HH:mm:ss")} ${Number(d[yval]).toFixed(decimalPlaces)}${labelY}`, + textPadding: 3, + }) + ) + ); + marks.push( + Plot.dot( + plotData, + Plot.pointerX({ x: "ts", y: yval, fill: color, r: 3, stroke: "var(--main-text-color)", strokeWidth: 1 }) + ) + ); + let maxY = resolveDomainBound(yvalMeta?.maxy, plotData[plotData.length - 1]) ?? 100; + let minY = resolveDomainBound(yvalMeta?.miny, plotData[plotData.length - 1]) ?? 0; + const plot = Plot.plot({ + axis: !sparkline, + x: { + grid: true, + label: "time", + tickFormat: (d) => `${dayjs.unix(d / 1000).format("HH:mm:ss")}`, + }, + y: { label: labelY, domain: [minY, maxY] }, + width: plotWidth, + height: plotHeight, + marks: marks, + }); + + React.useEffect(() => { + containerRef.current.append(plot); + + return () => { + plot.remove(); + }; + }, [plot, plotWidth, plotHeight]); + + return
; +} + +const SysinfoViewInner = React.memo(({ model }: SysinfoViewProps) => { + const plotData = jotai.useAtomValue(model.dataAtom); + const yvals = jotai.useAtomValue(model.metrics); + const plotMeta = jotai.useAtomValue(model.plotMetaAtom); + const osRef = React.useRef(); + let title = false; + let cols2 = false; + if (yvals.length > 1) { + title = true; + } + if (yvals.length > 2) { + cols2 = true; + } + + return ( + +
+ {yvals.map((yval, idx) => { + return ( + + ); + })} +
+
+ ); +}); + +export { makeSysinfoViewModel, SysinfoView, SysinfoViewModel }; diff --git a/frontend/app/view/waveai/waveai.tsx b/frontend/app/view/waveai/waveai.tsx index 0c170d645..db6bb67ef 100644 --- a/frontend/app/view/waveai/waveai.tsx +++ b/frontend/app/view/waveai/waveai.tsx @@ -81,12 +81,12 @@ export class WaveAiModel implements ViewModel { .filter(([k]) => k.startsWith("ai@")) .map(([k, v]) => { const aiPresetKeys = Object.keys(v).filter((k) => k.startsWith("ai:")); - console.log(aiPresetKeys); - v["display:name"] = + const newV = { ...v }; + newV["display:name"] = aiPresetKeys.length == 1 && aiPresetKeys.includes("ai:*") - ? `${v["display:name"] ?? "Default"} (${settings["ai:model"]})` - : v["display:name"]; - return [k, v]; + ? `${newV["display:name"] ?? "Default"} (${settings["ai:model"]})` + : newV["display:name"]; + return [k, newV]; }) ); }); @@ -109,7 +109,7 @@ export class WaveAiModel implements ViewModel { messages.pop(); set(this.messagesAtom, [...messages]); }); - this.simulateAssistantResponseAtom = atom(null, async (get, set, userMessage: ChatMessageType) => { + this.simulateAssistantResponseAtom = atom(null, async (_, set, userMessage: ChatMessageType) => { // unused at the moment. can replace the temp() function in the future const typingMessage: ChatMessageType = { id: crypto.randomUUID(), @@ -213,7 +213,7 @@ export class WaveAiModel implements ViewModel { } async fetchAiData(): Promise> { - const { data, fileInfo } = await fetchWaveFile(this.blockId, "aidata"); + const { data } = await fetchWaveFile(this.blockId, "aidata"); if (!data) { return []; } @@ -318,7 +318,6 @@ export class WaveAiModel implements ViewModel { content: errMsg, }; updatedHist.push(errorPrompt); - console.log(updatedHist); await BlockService.SaveWaveAiData(blockId, updatedHist); } setLocked(false); diff --git a/frontend/app/view/webview/webview.less b/frontend/app/view/webview/webview.less index 2d42f320e..ed104a478 100644 --- a/frontend/app/view/webview/webview.less +++ b/frontend/app/view/webview/webview.less @@ -15,3 +15,16 @@ transform: translate3d(0, 0, 0); will-change: transform; } + +.block-frame-div-url { + background: rgba(255, 255, 255, 0.1); + + input { + opacity: 1; + } + + .iconbutton { + width: fit-content !important; + margin-right: 5px; + } +} diff --git a/frontend/app/view/webview/webview.tsx b/frontend/app/view/webview/webview.tsx index ceb2f5455..376e8e9c5 100644 --- a/frontend/app/view/webview/webview.tsx +++ b/frontend/app/view/webview/webview.tsx @@ -46,6 +46,8 @@ export class WebViewModel implements ViewModel { urlInputRef: React.RefObject; nodeModel: NodeModel; endIconButtons?: Atom; + mediaPlaying: PrimitiveAtom; + mediaMuted: PrimitiveAtom; constructor(blockId: string, nodeModel: NodeModel) { this.nodeModel = nodeModel; @@ -58,7 +60,6 @@ export class WebViewModel implements ViewModel { this.homepageUrl = atom((get) => { const defaultUrl = get(defaultUrlAtom); const pinnedUrl = get(this.blockAtom).meta.pinnedurl; - console.log("homepageUrl", pinnedUrl, defaultUrl); return pinnedUrl ?? defaultUrl; }); this.urlWrapperClassName = atom(""); @@ -70,12 +71,18 @@ export class WebViewModel implements ViewModel { this.urlInputRef = React.createRef(); this.webviewRef = React.createRef(); + this.mediaPlaying = atom(false); + this.mediaMuted = atom(false); + this.viewText = atom((get) => { - let url = get(this.blockAtom)?.meta?.url || get(this.homepageUrl); + const homepageUrl = get(this.homepageUrl); + const metaUrl = get(this.blockAtom)?.meta?.url; const currUrl = get(this.url); - if (currUrl !== undefined) { - url = currUrl; - } + const urlWrapperClassName = get(this.urlWrapperClassName); + const refreshIcon = get(this.refreshIcon); + const mediaPlaying = get(this.mediaPlaying); + const mediaMuted = get(this.mediaMuted); + const url = currUrl ?? metaUrl ?? homepageUrl; return [ { elemtype: "iconbutton", @@ -97,7 +104,7 @@ export class WebViewModel implements ViewModel { }, { elemtype: "div", - className: clsx("block-frame-div-url", get(this.urlWrapperClassName)), + className: clsx("block-frame-div-url", urlWrapperClassName), onMouseOver: this.handleUrlWrapperMouseOver.bind(this), onMouseOut: this.handleUrlWrapperMouseOut.bind(this), children: [ @@ -111,26 +118,31 @@ export class WebViewModel implements ViewModel { onFocus: this.handleFocus.bind(this), onBlur: this.handleBlur.bind(this), }, + mediaPlaying && { + elemtype: "iconbutton", + icon: mediaMuted ? "volume-slash" : "volume", + click: this.handleMuteChange.bind(this), + }, { elemtype: "iconbutton", - icon: get(this.refreshIcon), + icon: refreshIcon, click: this.handleRefresh.bind(this), }, - ], + ].filter((v) => v), }, ] as HeaderElem[]; }); this.endIconButtons = atom((get) => { + const url = get(this.url); return [ { elemtype: "iconbutton", icon: "arrow-up-right-from-square", title: "Open in External Browser", click: () => { - const url = this.getUrl(); if (url != null && url != "") { - return getApi().openExternal(this.getUrl()); + return getApi().openExternal(url); } }, }, @@ -180,6 +192,25 @@ export class WebViewModel implements ViewModel { this.loadUrl(globalStore.get(this.homepageUrl), "home"); } + setMediaPlaying(isPlaying: boolean) { + console.log("setMediaPlaying", isPlaying); + globalStore.set(this.mediaPlaying, isPlaying); + } + + handleMuteChange(e: React.ChangeEvent) { + if (e) { + e.preventDefault(); + e.stopPropagation(); + } + try { + const newMutedVal = !this.webviewRef.current?.isAudioMuted(); + globalStore.set(this.mediaMuted, newMutedVal); + this.webviewRef.current?.setAudioMuted(newMutedVal); + } catch (e) { + console.error("Failed to change mute value", e); + } + } + handleUrlWrapperMouseOver(e: React.MouseEvent) { const urlInputFocused = globalStore.get(this.urlInputFocused); if (e.type === "mouseover" && !urlInputFocused) { @@ -436,9 +467,10 @@ function makeWebViewModel(blockId: string, nodeModel: NodeModel): WebViewModel { interface WebViewProps { blockId: string; model: WebViewModel; + onFailLoad?: (url: string) => void; } -const WebView = memo(({ model }: WebViewProps) => { +const WebView = memo(({ model, onFailLoad }: WebViewProps) => { const blockData = useAtomValue(model.blockAtom); const defaultUrl = useAtomValue(model.homepageUrl); const defaultSearchAtom = getSettingsKeyAtom("web:defaultsearch"); @@ -523,6 +555,10 @@ const WebView = memo(({ model }: WebViewProps) => { console.warn("Suppressed ERR_ABORTED error", e); } else { console.error(`Failed to load ${e.validatedURL}: ${e.errorDescription}`); + if (onFailLoad) { + const curUrl = model.webviewRef?.current.getURL(); + onFailLoad(curUrl); + } } }; const webviewFocus = () => { @@ -536,6 +572,12 @@ const WebView = memo(({ model }: WebViewProps) => { setDomReady(true); setBgColor(); }; + const handleMediaPlaying = () => { + model.setMediaPlaying(true); + }; + const handleMediaPaused = () => { + model.setMediaPlaying(false); + }; webview.addEventListener("did-navigate-in-page", navigateListener); webview.addEventListener("did-navigate", navigateListener); @@ -546,6 +588,8 @@ const WebView = memo(({ model }: WebViewProps) => { webview.addEventListener("focus", webviewFocus); webview.addEventListener("blur", webviewBlur); webview.addEventListener("dom-ready", handleDomReady); + webview.addEventListener("media-started-playing", handleMediaPlaying); + webview.addEventListener("media-paused", handleMediaPaused); // Clean up event listeners on component unmount return () => { @@ -558,6 +602,8 @@ const WebView = memo(({ model }: WebViewProps) => { webview.removeEventListener("focus", webviewFocus); webview.removeEventListener("blur", webviewBlur); webview.removeEventListener("dom-ready", handleDomReady); + webview.removeEventListener("media-started-playing", handleMediaPlaying); + webview.removeEventListener("media-paused", handleMediaPaused); }; }, []); diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 1dde47f8a..105ab4edf 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -333,6 +333,15 @@ declare global { command: string; msgFn: (msg: RpcMessage) => void; }; + + type TimeSeriesMeta = { + name?: string; + color?: string; + label?: string; + maxy?: string | number; + miny?: string | number; + decimalPlaces?: number; + }; } export {}; diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index a3e6eba1b..fcbe84680 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -306,6 +306,7 @@ declare global { "graph:*"?: boolean; "graph:numpoints"?: number; "graph:metrics"?: string[]; + "sysinfo:type"?: string; "bg:*"?: boolean; bg?: string; "bg:opacity"?: number; diff --git a/go.mod b/go.mod index 9f4774c85..202dccedf 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/kevinburke/ssh_config v1.2.0 github.com/mattn/go-sqlite3 v1.14.24 github.com/mitchellh/mapstructure v1.5.0 - github.com/sashabaranov/go-openai v1.31.0 + github.com/sashabaranov/go-openai v1.32.2 github.com/sawka/txwrap v0.2.0 github.com/shirou/gopsutil/v4 v4.24.9 github.com/skeema/knownhosts v1.3.0 diff --git a/go.sum b/go.sum index 732c02c94..590fed68d 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sashabaranov/go-openai v1.31.0 h1:rGe77x7zUeCjtS2IS7NCY6Tp4bQviXNMhkQM6hz/UC4= -github.com/sashabaranov/go-openai v1.31.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/sashabaranov/go-openai v1.32.2 h1:8z9PfYaLPbRzmJIYpwcWu6z3XU8F+RwVMF1QRSeSF2M= +github.com/sashabaranov/go-openai v1.32.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/sawka/txwrap v0.2.0 h1:V3LfvKVLULxcYSxdMguLwFyQFMEU9nFDJopg0ZkL+94= github.com/sawka/txwrap v0.2.0/go.mod h1:wwQ2SQiN4U+6DU/iVPhbvr7OzXAtgZlQCIGuvOswEfA= github.com/shirou/gopsutil/v4 v4.24.9 h1:KIV+/HaHD5ka5f570RZq+2SaeFsb/pq+fp2DGNWYoOI= diff --git a/package.json b/package.json index 0d50a9345..f77e05cea 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "productName": "Wave", "description": "Open-Source AI-Native Terminal Built for Seamless Workflows", "license": "Apache-2.0", - "version": "0.8.12-beta.0", + "version": "0.8.12", "homepage": "https://waveterm.dev", "build": { "appId": "dev.commandline.waveterm" @@ -30,18 +30,18 @@ "@chromatic-com/storybook": "^2.0.2", "@eslint/js": "^9.12.0", "@rollup/plugin-node-resolve": "^15.3.0", - "@storybook/addon-essentials": "^8.3.5", - "@storybook/addon-interactions": "^8.3.5", - "@storybook/addon-links": "^8.3.5", - "@storybook/blocks": "^8.3.5", - "@storybook/react": "^8.3.5", - "@storybook/react-vite": "^8.3.5", - "@storybook/test": "^8.3.5", - "@storybook/theming": "^8.3.5", + "@storybook/addon-essentials": "^8.3.6", + "@storybook/addon-interactions": "^8.3.6", + "@storybook/addon-links": "^8.3.6", + "@storybook/blocks": "^8.3.6", + "@storybook/react": "^8.3.6", + "@storybook/react-vite": "^8.3.6", + "@storybook/test": "^8.3.6", + "@storybook/theming": "^8.3.6", "@types/css-tree": "^2", "@types/debug": "^4", "@types/electron": "^1.6.10", - "@types/node": "^22.7.5", + "@types/node": "^22.7.6", "@types/papaparse": "^5", "@types/pngjs": "^6.0.5", "@types/react": "^18.3.11", @@ -54,8 +54,8 @@ "@types/uuid": "^10.0.0", "@types/ws": "^8", "@vitejs/plugin-react-swc": "^3.7.1", - "@vitest/coverage-istanbul": "^2.1.2", - "electron": "^32.1.2", + "@vitest/coverage-istanbul": "^2.1.3", + "electron": "^32.2.0", "electron-builder": "^25.1.7", "electron-vite": "^2.3.0", "eslint": "^9.12.0", @@ -66,22 +66,22 @@ "prettier-plugin-organize-imports": "^4.1.0", "rollup-plugin-flow": "^1.1.1", "semver": "^7.6.3", - "storybook": "^8.3.5", + "storybook": "^8.3.6", "storybook-dark-mode": "^4.0.2", "ts-node": "^10.9.2", - "tslib": "^2.6.3", + "tslib": "^2.8.0", "tsx": "^4.19.1", "typescript": "^5.6.3", - "typescript-eslint": "^8.8.1", - "vite": "^5.4.8", + "typescript-eslint": "^8.10.0", + "vite": "^5.4.9", "vite-plugin-image-optimizer": "^1.1.8", "vite-plugin-static-copy": "^2.0.0", "vite-plugin-svgr": "^4.2.0", "vite-tsconfig-paths": "^5.0.1", - "vitest": "^2.1.2" + "vitest": "^2.1.3" }, "dependencies": { - "@floating-ui/react": "^0.26.24", + "@floating-ui/react": "^0.26.25", "@monaco-editor/loader": "^1.4.0", "@monaco-editor/react": "^4.6.0", "@observablehq/plot": "^0.6.16", @@ -136,7 +136,7 @@ "use-device-pixel-ratio": "^1.1.2", "winston": "^3.15.0", "ws": "^8.18.0", - "yaml": "^2.5.1" + "yaml": "^2.6.0" }, "resolutions": { "send@npm:0.18.0": "0.19.0", diff --git a/pkg/blockcontroller/blockcontroller.go b/pkg/blockcontroller/blockcontroller.go index bd55082a3..4e9bc4886 100644 --- a/pkg/blockcontroller/blockcontroller.go +++ b/pkg/blockcontroller/blockcontroller.go @@ -369,7 +369,6 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj bc.ShellProc.Cmd.Write(ic.InputData) } if ic.TermSize != nil { - log.Printf("SETTERMSIZE: %dx%d\n", ic.TermSize.Rows, ic.TermSize.Cols) err = setTermSize(ctx, bc.BlockId, *ic.TermSize) if err != nil { log.Printf("error setting pty size: %v\n", err) diff --git a/pkg/waveobj/metaconsts.go b/pkg/waveobj/metaconsts.go index 41fdce4dd..e904c3def 100644 --- a/pkg/waveobj/metaconsts.go +++ b/pkg/waveobj/metaconsts.go @@ -64,6 +64,8 @@ const ( MetaKey_GraphNumPoints = "graph:numpoints" MetaKey_GraphMetrics = "graph:metrics" + MetaKey_SysinfoType = "sysinfo:type" + MetaKey_BgClear = "bg:*" MetaKey_Bg = "bg" MetaKey_BgOpacity = "bg:opacity" diff --git a/pkg/waveobj/wtypemeta.go b/pkg/waveobj/wtypemeta.go index 2b52e897a..93c1919f0 100644 --- a/pkg/waveobj/wtypemeta.go +++ b/pkg/waveobj/wtypemeta.go @@ -64,6 +64,8 @@ type MetaTSType struct { GraphNumPoints int `json:"graph:numpoints,omitempty"` GraphMetrics []string `json:"graph:metrics,omitempty"` + SysinfoType string `json:"sysinfo:type,omitempty"` + // for tabs BgClear bool `json:"bg:*,omitempty"` Bg string `json:"bg,omitempty"` diff --git a/pkg/wconfig/defaultconfig/defaultwidgets.json b/pkg/wconfig/defaultconfig/defaultwidgets.json index 726e8e3a7..2c6a8825d 100644 --- a/pkg/wconfig/defaultconfig/defaultwidgets.json +++ b/pkg/wconfig/defaultconfig/defaultwidgets.json @@ -41,13 +41,13 @@ } } }, - "defwidget@cpuplot": { + "defwidget@sysinfo": { "display:order": 5, "icon": "chart-line", - "label": "cpu", + "label": "sysinfo", "blockdef": { "meta": { - "view": "cpuplot" + "view": "sysinfo" } } }, diff --git a/pkg/wconfig/defaultconfig/presets.json b/pkg/wconfig/defaultconfig/presets.json index 78da237fe..30dc05942 100644 --- a/pkg/wconfig/defaultconfig/presets.json +++ b/pkg/wconfig/defaultconfig/presets.json @@ -104,15 +104,13 @@ "display:name": "Wave Proxy - gpt-4o-mini", "display:order": 0, "ai:*": true, + "ai:apitype": "", + "ai:baseurl": "", + "ai:apitoken": "", + "ai:name": "", + "ai:orgid": "", "ai:model": "gpt-4o-mini", "ai:maxtokens": 2048, "ai:timeoutms": 60000 - }, - "ai@ollama-llama3.1": { - "display:name": "ollama - llama3.1", - "display:order": 0, - "ai:*": true, - "ai:baseurl": "http://localhost:11434/v1", - "ai:model": "llama3.1:latest" } } diff --git a/pkg/web/ws.go b/pkg/web/ws.go index 4c605fa27..5ef0c0344 100644 --- a/pkg/web/ws.go +++ b/pkg/web/ws.go @@ -30,6 +30,9 @@ const wsInitialPingTime = 1 * time.Second const DefaultCommandTimeout = 2 * time.Second +var GlobalLock = &sync.Mutex{} +var RouteToConnMap = map[string]string{} // routeid => connid + func RunWebSocketServer(listener net.Listener) { gr := mux.NewRouter() gr.HandleFunc("/ws", HandleWs) @@ -40,10 +43,10 @@ func RunWebSocketServer(listener net.Listener) { Handler: gr, } server.SetKeepAlivesEnabled(false) - log.Printf("Running websocket server on %s\n", listener.Addr()) + log.Printf("[websocket] running websocket server on %s\n", listener.Addr()) err := server.Serve(listener) if err != nil { - log.Printf("[error] trying to run websocket server: %v\n", err) + log.Printf("[websocket] error trying to run websocket server: %v\n", err) } } @@ -81,7 +84,7 @@ func processWSCommand(jmsg map[string]any, outputCh chan any, rpcInputCh chan [] r := recover() if r != nil { rtnErr = fmt.Errorf("panic: %v", r) - log.Printf("panic in processMessage: %v\n", r) + log.Printf("[websocket] panic in processMessage: %v\n", r) debug.PrintStack() } if rtnErr == nil { @@ -108,7 +111,7 @@ func processWSCommand(jmsg map[string]any, outputCh chan any, rpcInputCh chan [] msgBytes, err := json.Marshal(rpcMsg) if err != nil { // this really should never fail since we just unmarshalled this value - log.Printf("error marshalling rpc message: %v\n", err) + log.Printf("[websocket] error marshalling rpc message: %v\n", err) return } rpcInputCh <- msgBytes @@ -125,7 +128,7 @@ func processWSCommand(jmsg map[string]any, outputCh chan any, rpcInputCh chan [] msgBytes, err := json.Marshal(rpcMsg) if err != nil { // this really should never fail since we just unmarshalled this value - log.Printf("error marshalling rpc message: %v\n", err) + log.Printf("[websocket] error marshalling rpc message: %v\n", err) return } rpcInputCh <- msgBytes @@ -152,7 +155,7 @@ func processMessage(jmsg map[string]any, outputCh chan any, rpcInputCh chan []by processWSCommand(jmsg, outputCh, rpcInputCh) } -func ReadLoop(conn *websocket.Conn, outputCh chan any, closeCh chan any, rpcInputCh chan []byte) { +func ReadLoop(conn *websocket.Conn, outputCh chan any, closeCh chan any, rpcInputCh chan []byte, routeId string) { readWait := wsReadWaitTimeout conn.SetReadLimit(64 * 1024) conn.SetReadDeadline(time.Now().Add(readWait)) @@ -160,13 +163,13 @@ func ReadLoop(conn *websocket.Conn, outputCh chan any, closeCh chan any, rpcInpu for { _, message, err := conn.ReadMessage() if err != nil { - log.Printf("ReadPump error: %v\n", err) + log.Printf("[websocket] ReadPump error (%s): %v\n", routeId, err) break } jmsg := map[string]any{} err = json.Unmarshal(message, &jmsg) if err != nil { - log.Printf("Error unmarshalling json: %v\n", err) + log.Printf("[websocket] error unmarshalling json: %v\n", err) break } conn.SetReadDeadline(time.Now().Add(readWait)) @@ -197,7 +200,7 @@ func WritePing(conn *websocket.Conn) error { return nil } -func WriteLoop(conn *websocket.Conn, outputCh chan any, closeCh chan any) { +func WriteLoop(conn *websocket.Conn, outputCh chan any, closeCh chan any, routeId string) { ticker := time.NewTicker(wsInitialPingTime) defer ticker.Stop() initialPing := true @@ -211,7 +214,7 @@ func WriteLoop(conn *websocket.Conn, outputCh chan any, closeCh chan any) { } else { barr, err = json.Marshal(msg) if err != nil { - log.Printf("cannot marshal websocket message: %v\n", err) + log.Printf("[websocket] cannot marshal websocket message: %v\n", err) // just loop again break } @@ -219,14 +222,14 @@ func WriteLoop(conn *websocket.Conn, outputCh chan any, closeCh chan any) { err = conn.WriteMessage(websocket.TextMessage, barr) if err != nil { conn.Close() - log.Printf("WritePump error: %v\n", err) + log.Printf("[websocket] WritePump error (%s): %v\n", routeId, err) return } case <-ticker.C: err := WritePing(conn) if err != nil { - log.Printf("WritePump error: %v\n", err) + log.Printf("[websocket] WritePump error (%s): %v\n", routeId, err) return } if initialPing { @@ -240,6 +243,31 @@ func WriteLoop(conn *websocket.Conn, outputCh chan any, closeCh chan any) { } } +func registerConn(wsConnId string, routeId string, wproxy *wshutil.WshRpcProxy) { + GlobalLock.Lock() + defer GlobalLock.Unlock() + curConnId := RouteToConnMap[routeId] + if curConnId != "" { + log.Printf("[websocket] warning: replacing existing connection for route %q\n", routeId) + wshutil.DefaultRouter.UnregisterRoute(routeId) + } + RouteToConnMap[routeId] = wsConnId + wshutil.DefaultRouter.RegisterRoute(routeId, wproxy) +} + +func unregisterConn(wsConnId string, routeId string) { + GlobalLock.Lock() + defer GlobalLock.Unlock() + curConnId := RouteToConnMap[routeId] + if curConnId != wsConnId { + // only unregister if we are the current connection (otherwise we were already removed) + log.Printf("[websocket] warning: trying to unregister connection %q for route %q but it is not the current connection (ignoring)\n", wsConnId, routeId) + return + } + delete(RouteToConnMap, routeId) + wshutil.DefaultRouter.UnregisterRoute(routeId) +} + func HandleWsInternal(w http.ResponseWriter, r *http.Request) error { windowId := r.URL.Query().Get("windowid") if windowId == "" { @@ -250,6 +278,7 @@ func HandleWsInternal(w http.ResponseWriter, r *http.Request) error { if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(fmt.Sprintf("error validating authkey: %v", err))) + log.Printf("[websocket] error validating authkey: %v\n", err) return err } conn, err := WebSocketUpgrader.Upgrade(w, r, nil) @@ -258,25 +287,21 @@ func HandleWsInternal(w http.ResponseWriter, r *http.Request) error { } defer conn.Close() wsConnId := uuid.New().String() - log.Printf("New websocket connection: windowid:%s connid:%s\n", windowId, wsConnId) outputCh := make(chan any, 100) closeCh := make(chan any) - eventbus.RegisterWSChannel(wsConnId, windowId, outputCh) var routeId string if windowId == wshutil.ElectronRoute { routeId = wshutil.ElectronRoute } else { routeId = wshutil.MakeWindowRouteId(windowId) } + log.Printf("[websocket] new connection: windowid:%s connid:%s routeid:%s\n", windowId, wsConnId, routeId) + eventbus.RegisterWSChannel(wsConnId, windowId, outputCh) defer eventbus.UnregisterWSChannel(wsConnId) - // we create a wshproxy to handle rpc messages to/from the window - wproxy := wshutil.MakeRpcProxy() - wshutil.DefaultRouter.RegisterRoute(routeId, wproxy) - defer func() { - wshutil.DefaultRouter.UnregisterRoute(routeId) - close(wproxy.ToRemoteCh) - }() - // WshServerFactoryFn(rpcInputCh, rpcOutputCh, wshrpc.RpcContext{}) + wproxy := wshutil.MakeRpcProxy() // we create a wshproxy to handle rpc messages to/from the window + defer close(wproxy.ToRemoteCh) + registerConn(wsConnId, routeId, wproxy) + defer unregisterConn(wsConnId, routeId) wg := &sync.WaitGroup{} wg.Add(2) go func() { @@ -293,12 +318,12 @@ func HandleWsInternal(w http.ResponseWriter, r *http.Request) error { go func() { // read loop defer wg.Done() - ReadLoop(conn, outputCh, closeCh, wproxy.FromRemoteCh) + ReadLoop(conn, outputCh, closeCh, wproxy.FromRemoteCh, routeId) }() go func() { // write loop defer wg.Done() - WriteLoop(conn, outputCh, closeCh) + WriteLoop(conn, outputCh, closeCh, routeId) }() wg.Wait() close(wproxy.FromRemoteCh) diff --git a/pkg/wlayout/wlayout.go b/pkg/wlayout/wlayout.go index 3f8bfdbd1..96885d17b 100644 --- a/pkg/wlayout/wlayout.go +++ b/pkg/wlayout/wlayout.go @@ -38,7 +38,7 @@ func GetStarterLayout() PortableLayout { }, Focused: true}, {IndexArr: []int{1}, BlockDef: &waveobj.BlockDef{ Meta: waveobj.MetaMapType{ - waveobj.MetaKey_View: "cpuplot", + waveobj.MetaKey_View: "sysinfo", }, }}, {IndexArr: []int{1, 1}, BlockDef: &waveobj.BlockDef{ diff --git a/pkg/wshrpc/wshremote/sysinfo.go b/pkg/wshrpc/wshremote/sysinfo.go index eba8290fb..045e5b5d1 100644 --- a/pkg/wshrpc/wshremote/sysinfo.go +++ b/pkg/wshrpc/wshremote/sysinfo.go @@ -16,6 +16,8 @@ import ( "github.com/wavetermdev/waveterm/pkg/wshutil" ) +const BYTES_PER_GB = 1073741824 + func getCpuData(values map[string]float64) { percentArr, err := cpu.Percent(0, false) if err != nil { @@ -38,10 +40,10 @@ func getMemData(values map[string]float64) { if err != nil { return } - values["mem:total"] = float64(memData.Total) - values["mem:available"] = float64(memData.Available) - values["mem:used"] = float64(memData.Used) - values["mem:free"] = float64(memData.Free) + values["mem:total"] = float64(memData.Total) / BYTES_PER_GB + values["mem:available"] = float64(memData.Available) / BYTES_PER_GB + values["mem:used"] = float64(memData.Used) / BYTES_PER_GB + values["mem:free"] = float64(memData.Free) / BYTES_PER_GB } func generateSingleServerData(client *wshutil.WshRpc, connName string) { diff --git a/pkg/wshrpc/wshserver/wshserver.go b/pkg/wshrpc/wshserver/wshserver.go index baed1bd3e..91facb368 100644 --- a/pkg/wshrpc/wshserver/wshserver.go +++ b/pkg/wshrpc/wshserver/wshserver.go @@ -83,7 +83,7 @@ func MakePlotData(ctx context.Context, blockId string) error { return err } viewName := block.Meta.GetString(waveobj.MetaKey_View, "") - if viewName != "cpuplot" { + if viewName != "cpuplot" && viewName != "sysinfo" { return fmt.Errorf("invalid view type: %s", viewName) } return filestore.WFS.MakeFile(ctx, blockId, "cpuplotdata", nil, filestore.FileOptsType{}) @@ -95,7 +95,7 @@ func SavePlotData(ctx context.Context, blockId string, history string) error { return err } viewName := block.Meta.GetString(waveobj.MetaKey_View, "") - if viewName != "cpuplot" { + if viewName != "cpuplot" && viewName != "sysinfo" { return fmt.Errorf("invalid view type: %s", viewName) } // todo: interpret the data being passed @@ -422,7 +422,6 @@ func (ws *WshServer) EventPublishCommand(ctx context.Context, data wps.WaveEvent } func (ws *WshServer) EventSubCommand(ctx context.Context, data wps.SubscriptionRequest) error { - log.Printf("EventSubCommand: %v\n", data) rpcSource := wshutil.GetRpcSourceFromContext(ctx) if rpcSource == "" { return fmt.Errorf("no rpc source set") diff --git a/pkg/wshutil/wshrouter.go b/pkg/wshutil/wshrouter.go index 26fdb8a39..6389e3d99 100644 --- a/pkg/wshutil/wshrouter.go +++ b/pkg/wshutil/wshrouter.go @@ -274,10 +274,14 @@ func (router *WshRouter) RegisterRoute(routeId string, rpc AbstractRpcClient) { log.Printf("[router] registering wsh route %q\n", routeId) router.Lock.Lock() defer router.Lock.Unlock() + alreadyExists := router.RouteMap[routeId] != nil + if alreadyExists { + log.Printf("[router] warning: route %q already exists (replacing)\n", routeId) + } router.RouteMap[routeId] = rpc go func() { // announce - if router.GetUpstreamClient() != nil { + if !alreadyExists && router.GetUpstreamClient() != nil { announceMsg := RpcMessage{Command: wshrpc.Command_RouteAnnounce, Source: routeId} announceBytes, _ := json.Marshal(announceMsg) router.GetUpstreamClient().SendRpcMessage(announceBytes) diff --git a/testdriver/onboarding.yml b/testdriver/onboarding.yml new file mode 100644 index 000000000..841d46226 --- /dev/null +++ b/testdriver/onboarding.yml @@ -0,0 +1,12 @@ +version: 4.0.65 +steps: + - prompt: complete the onboarding of wave terminal + commands: + - command: hover-text + text: Continue + description: button to complete onboarding + action: click + - command: hover-text + text: Get Started + description: button to complete onboarding + action: click diff --git a/yarn.lock b/yarn.lock index 1eb60f941..f63fa4aa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -847,9 +847,9 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react@npm:^0.26.24": - version: 0.26.24 - resolution: "@floating-ui/react@npm:0.26.24" +"@floating-ui/react@npm:^0.26.25": + version: 0.26.25 + resolution: "@floating-ui/react@npm:0.26.25" dependencies: "@floating-ui/react-dom": "npm:^2.1.2" "@floating-ui/utils": "npm:^0.2.8" @@ -857,7 +857,7 @@ __metadata: peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 10c0/c5c3ac265802087673a69b0e08b3bea1ee02de9da4cdbc40bb1c9e06823be72628a82f1655b40d56a4383715b4ab3b6deddff4e69146f513970ee592e1dd8f92 + checksum: 10c0/5206b06a5963e795af2f0b0a6ac39230012263ffa38dd60158e0f0b82d43a24e6a1c005fc8556ee5fe26e6353546ffb72e54716f6bd584fdab516dc128a78995 languageName: node linkType: hard @@ -1527,9 +1527,9 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-actions@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-actions@npm:8.3.5" +"@storybook/addon-actions@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-actions@npm:8.3.6" dependencies: "@storybook/global": "npm:^5.0.0" "@types/uuid": "npm:^9.0.1" @@ -1537,47 +1537,47 @@ __metadata: polished: "npm:^4.2.2" uuid: "npm:^9.0.0" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/c9872d9d41b33ae26be36dfb25ccb10e7c832d4677cffe4f3e8a42f2748d8c54e681810662b88e10d1e72223096ad1861e389ee7134974e9f3e2869958300e08 + storybook: ^8.3.6 + checksum: 10c0/77959b8f0b3e4748051411bb0cd8041cff68360aa3dc98745153bbfd48aa330f2228ac9b9a42c1f3fd40c003bab5b425ed2b1ff540a985d3a00869989e547b8e languageName: node linkType: hard -"@storybook/addon-backgrounds@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-backgrounds@npm:8.3.5" +"@storybook/addon-backgrounds@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-backgrounds@npm:8.3.6" dependencies: "@storybook/global": "npm:^5.0.0" memoizerific: "npm:^1.11.3" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/6a2ce804bd96827a0601b53ea1f15bec382e1099cf957a0ac3c9452da1a58702fb5cea4b76c564a78a3a4f66431eaf3d826ad400d3bd579fec94ec266f06e211 + storybook: ^8.3.6 + checksum: 10c0/7d05c94ce351d162a5f55378a7779ce26e84dadbe540c5d68abd470501e097e7b006434fbb057b9d86c19e595254187d2f9f5e9ee847190ae29bcc2e46fa55a1 languageName: node linkType: hard -"@storybook/addon-controls@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-controls@npm:8.3.5" +"@storybook/addon-controls@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-controls@npm:8.3.6" dependencies: "@storybook/global": "npm:^5.0.0" dequal: "npm:^2.0.2" lodash: "npm:^4.17.21" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/f0bf85a8f401649734ae63ece9b64fe77b10a290cd1e83f54d83da4ae58f5a3a9e3876497f8cb3e9ac12dd1814fe7d16cee88bd6985877422823965b20607385 + storybook: ^8.3.6 + checksum: 10c0/d7222232caaee1f3e914c08987891b511b79127635cc352a20dd529c4518573844847f7b5da91f62ce27c6809e9303c95878db6f1dea4b8d88769cbff64fbce4 languageName: node linkType: hard -"@storybook/addon-docs@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-docs@npm:8.3.5" +"@storybook/addon-docs@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-docs@npm:8.3.6" dependencies: "@mdx-js/react": "npm:^3.0.0" - "@storybook/blocks": "npm:8.3.5" - "@storybook/csf-plugin": "npm:8.3.5" + "@storybook/blocks": "npm:8.3.6" + "@storybook/csf-plugin": "npm:8.3.6" "@storybook/global": "npm:^5.0.0" - "@storybook/react-dom-shim": "npm:8.3.5" + "@storybook/react-dom-shim": "npm:8.3.6" "@types/react": "npm:^16.8.0 || ^17.0.0 || ^18.0.0" fs-extra: "npm:^11.1.0" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0" @@ -1586,121 +1586,121 @@ __metadata: rehype-slug: "npm:^6.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/51f277079397ecc0e5fef721307a618e3aa4727069dba2b331cb4de2e6e318f4de338132e1ea886c0e9ee0824a10cbc9a0887e0f09f341221a31a09111ce4836 + storybook: ^8.3.6 + checksum: 10c0/c2d03904b6e465add7b3a56ed3ee965c098ea7252d0fe431cc581b7774fe99803a4891062b064033578f78c7ab77465e2179cfb66f4333a4d5edcd01af62c421 languageName: node linkType: hard -"@storybook/addon-essentials@npm:^8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-essentials@npm:8.3.5" +"@storybook/addon-essentials@npm:^8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-essentials@npm:8.3.6" dependencies: - "@storybook/addon-actions": "npm:8.3.5" - "@storybook/addon-backgrounds": "npm:8.3.5" - "@storybook/addon-controls": "npm:8.3.5" - "@storybook/addon-docs": "npm:8.3.5" - "@storybook/addon-highlight": "npm:8.3.5" - "@storybook/addon-measure": "npm:8.3.5" - "@storybook/addon-outline": "npm:8.3.5" - "@storybook/addon-toolbars": "npm:8.3.5" - "@storybook/addon-viewport": "npm:8.3.5" + "@storybook/addon-actions": "npm:8.3.6" + "@storybook/addon-backgrounds": "npm:8.3.6" + "@storybook/addon-controls": "npm:8.3.6" + "@storybook/addon-docs": "npm:8.3.6" + "@storybook/addon-highlight": "npm:8.3.6" + "@storybook/addon-measure": "npm:8.3.6" + "@storybook/addon-outline": "npm:8.3.6" + "@storybook/addon-toolbars": "npm:8.3.6" + "@storybook/addon-viewport": "npm:8.3.6" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/dad5ec31342abafe93eefa10178278f2f3cb8f849824050cf9248ff976188fda9bdc167750a57c57f0f821a56dd46daef92e70816a62b3cba435484f263fb703 + storybook: ^8.3.6 + checksum: 10c0/5f0a53ee42792fb76afdc54485fa907981363138786306dbcf2dea990b5809843a60da1148cd60141844951f462ec021c06befa0b98fea796abead95ca54716b languageName: node linkType: hard -"@storybook/addon-highlight@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-highlight@npm:8.3.5" +"@storybook/addon-highlight@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-highlight@npm:8.3.6" dependencies: "@storybook/global": "npm:^5.0.0" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/9bd4d29d10d612aa6baf2a3c470fd571f32edd4d456e3c4eb0dfe416be7891b075f06a7f46da35274c58261db8be5e97d974edec3c11b477f1cc397b357a42ca + storybook: ^8.3.6 + checksum: 10c0/6e0fe4b28e956af5bd534af2bfd4299be075e22b2fa4cbbe661da2901929e5a44f50cb65ed9869c1afee603f711e023e000556823ba534e1f61390b0c1989136 languageName: node linkType: hard -"@storybook/addon-interactions@npm:^8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-interactions@npm:8.3.5" +"@storybook/addon-interactions@npm:^8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-interactions@npm:8.3.6" dependencies: "@storybook/global": "npm:^5.0.0" - "@storybook/instrumenter": "npm:8.3.5" - "@storybook/test": "npm:8.3.5" + "@storybook/instrumenter": "npm:8.3.6" + "@storybook/test": "npm:8.3.6" polished: "npm:^4.2.2" ts-dedent: "npm:^2.2.0" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/66aef03bdaee184911db0ddd212ef5c87f30d985d363b65085a88d19aa0304feb2753f342338f7b675463405ddfd0f6c7938ec62acdad5bbcd43af3173738932 + storybook: ^8.3.6 + checksum: 10c0/cccde56641ce455a319d68fb9d2aadf1df9404566199e4225bfacf6e8381d118b1fea41dce5ff36946dd7aac3a9ff6a4a6f17763064ce2e14dfdab6df743a3de languageName: node linkType: hard -"@storybook/addon-links@npm:^8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-links@npm:8.3.5" +"@storybook/addon-links@npm:^8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-links@npm:8.3.6" dependencies: "@storybook/csf": "npm:^0.1.11" "@storybook/global": "npm:^5.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.3.5 + storybook: ^8.3.6 peerDependenciesMeta: react: optional: true - checksum: 10c0/ac431b420196fea7f9aa2a62356d2221c75065515ba82e4a0bbcddaa66a9a61d93f186e6efde87ca43ddfd043f3503f4459449d0f3f76935707276b87698f0d3 + checksum: 10c0/28d837680ef9c51aa1a4f43fb8c461dce90281264b2a259bf4ca7929d734bbda9a8ca54bf04d328ca4436c6d64b9cdb5b594717f8840d6d9693d0e637bdd8909 languageName: node linkType: hard -"@storybook/addon-measure@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-measure@npm:8.3.5" +"@storybook/addon-measure@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-measure@npm:8.3.6" dependencies: "@storybook/global": "npm:^5.0.0" tiny-invariant: "npm:^1.3.1" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/49a8f2fac76a65fc2b4fdf6c90a4996e58cccf4a3db56a7f32a17eff2a34ebeccf7ad8042a3bd3b1e8deba1d9ba95ce95f8ee57f11a98de51ce6fc29b2a127b4 + storybook: ^8.3.6 + checksum: 10c0/0ca83d45cf0268a9b2ac24a5fabb76ac23fb1036c75f6ac16ea847266ccd3c1676469289c1c41f73b89cdf38a8ebddafdedf82669c3fe9f81464fd0ce5ee9fe8 languageName: node linkType: hard -"@storybook/addon-outline@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-outline@npm:8.3.5" +"@storybook/addon-outline@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-outline@npm:8.3.6" dependencies: "@storybook/global": "npm:^5.0.0" ts-dedent: "npm:^2.0.0" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/d1d4d734e1770aa4af8fba2d789ad5c570ae0eb66301d8c5444a92a07428f66cde4a32e48fc1ea8627801c7d5c99ae40563554eef7f1a5ad14e4144ee74f9726 + storybook: ^8.3.6 + checksum: 10c0/52fa8cecd40f0b9ccd96053b7b1207c7cafe7f2ed6808e04e5f0d6503ab5266aeb0710bbace4b787ac3af38ea9cd6e671fc8aa8eee993f4cb64811057c5e3b55 languageName: node linkType: hard -"@storybook/addon-toolbars@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-toolbars@npm:8.3.5" +"@storybook/addon-toolbars@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-toolbars@npm:8.3.6" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/1e198ab1c87204f3ed69493484e94e6ad6bd4ed3c5a164ceeb0719cf2ddf96159d0fc481584c413d0bf3ff3943196ca123888ac67b0dbf967195d024387de84e + storybook: ^8.3.6 + checksum: 10c0/58ee542f1530dc71956fa15578320b8aa31b47406d011e40447dba80c62269c867af548fe1e72ddd95606118fbd51a444c86c0ea6f580b497390122a17ab65a7 languageName: node linkType: hard -"@storybook/addon-viewport@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/addon-viewport@npm:8.3.5" +"@storybook/addon-viewport@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/addon-viewport@npm:8.3.6" dependencies: memoizerific: "npm:^1.11.3" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/26e02754694d3422c19383ca330eb541394e54151e6c3d86865cbedcf49a323820b649e837e47ef94ea310c80ca47b9baa56fadee3a02db31bce5eaae6f43674 + storybook: ^8.3.6 + checksum: 10c0/95b91e415cb803898334e28890394884c612f1bcffdef5256e2b6ec373815fcefb43da8698bfa739eaf211315c49de056d107c1de61a9ab20c594e16da3f741e languageName: node linkType: hard -"@storybook/blocks@npm:8.3.5, @storybook/blocks@npm:^8.3.5": - version: 8.3.5 - resolution: "@storybook/blocks@npm:8.3.5" +"@storybook/blocks@npm:8.3.6, @storybook/blocks@npm:^8.3.6": + version: 8.3.6 + resolution: "@storybook/blocks@npm:8.3.6" dependencies: "@storybook/csf": "npm:^0.1.11" "@storybook/global": "npm:^5.0.0" @@ -1719,21 +1719,21 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.3.5 + storybook: ^8.3.6 peerDependenciesMeta: react: optional: true react-dom: optional: true - checksum: 10c0/8b4d4b7761ff32de02c231231ef49457f96438916c9f5e2af6eebd90842a7de1f26781a768f5f2b080abd26c8da66c1fca60d304a87f530216eb01d6434e7ff9 + checksum: 10c0/28522998923a62a5a365f60200b8fe3bd6df42e81a61b58d75440fde04916f402281622ffb68c321dab8ca148490f942abe753c98102e278ca9b620ce1ecc1d1 languageName: node linkType: hard -"@storybook/builder-vite@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/builder-vite@npm:8.3.5" +"@storybook/builder-vite@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/builder-vite@npm:8.3.6" dependencies: - "@storybook/csf-plugin": "npm:8.3.5" + "@storybook/csf-plugin": "npm:8.3.6" "@types/find-cache-dir": "npm:^3.2.1" browser-assert: "npm:^1.2.1" es-module-lexer: "npm:^1.5.0" @@ -1744,7 +1744,7 @@ __metadata: ts-dedent: "npm:^2.0.0" peerDependencies: "@preact/preset-vite": "*" - storybook: ^8.3.5 + storybook: ^8.3.6 typescript: ">= 4.3.x" vite: ^4.0.0 || ^5.0.0 vite-plugin-glimmerx: "*" @@ -1755,7 +1755,7 @@ __metadata: optional: true vite-plugin-glimmerx: optional: true - checksum: 10c0/7f597c1f0ae252e408863b34011f9210dc4247f96c4790836efb004081d426131ff9bfa23d4b69da7479a4ec8cb81f430b9538ce62b87621747ffd389741c296 + checksum: 10c0/d8fe2f548bc18ecb9844bc57736de32f1954e14f4697905b99f8fe7f89727a04f0acf6f6b94dacd6f41750605ac89f4ca2bdd5f36d180d831d8bbc7ec6a8d154 languageName: node linkType: hard @@ -1768,12 +1768,12 @@ __metadata: languageName: node linkType: hard -"@storybook/components@npm:^8.3.5": - version: 8.3.5 - resolution: "@storybook/components@npm:8.3.5" +"@storybook/components@npm:^8.3.6": + version: 8.3.6 + resolution: "@storybook/components@npm:8.3.6" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/20b3217f1a0a48ab5a40c08960d966be1fc6842dd40fc9951328d9bdc76411e3d3820865fd93895eeccec718ef8c8512f19d1fcad9bdd2cb6a792f92ca4d73ce + storybook: ^8.3.6 + checksum: 10c0/65badbad5143c6585c94b08d7f3496dad42de256cb077c60dade0ca3b0fa03eb4dc5917fe4c3df03df35b7cf39001b8898794747951ac4b7594ed278ea01e4b0 languageName: node linkType: hard @@ -1786,9 +1786,9 @@ __metadata: languageName: node linkType: hard -"@storybook/core@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/core@npm:8.3.5" +"@storybook/core@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/core@npm:8.3.6" dependencies: "@storybook/csf": "npm:^0.1.11" "@types/express": "npm:^4.17.21" @@ -1803,18 +1803,18 @@ __metadata: semver: "npm:^7.6.2" util: "npm:^0.12.5" ws: "npm:^8.2.3" - checksum: 10c0/f01d13c2309af518f1d029d27a3dd1ce80ea7423c9d4927d1096634a84887051c3581404971499ab81e5b28acca517bf1b31a3281fe7fdb0b418f0c45767f16f + checksum: 10c0/75635195335f9f296e830b51e4d71d8f2db833ad1d6b279f9da349dd312307d07c036351cfb421aea2b69c39f11a95343d9d5518432c7d49923237a82c694388 languageName: node linkType: hard -"@storybook/csf-plugin@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/csf-plugin@npm:8.3.5" +"@storybook/csf-plugin@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/csf-plugin@npm:8.3.6" dependencies: unplugin: "npm:^1.3.1" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/528af7509500afe1baf446fb8d52978c71d18df0abafd27a81ab821977cfb211bb02190f5a7d09005c4c10dac5e91ef978e35c74ee477ed7d6e45583ebc8412d + storybook: ^8.3.6 + checksum: 10c0/977998f7a908b2832dd293ea14171f6fdf9c3a25d4035b5c868a8be54630d73983eb3ff4867e488408283d1bcb64fc51c001460610b0143b050b11db953dcb26 languageName: node linkType: hard @@ -1854,16 +1854,16 @@ __metadata: languageName: node linkType: hard -"@storybook/instrumenter@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/instrumenter@npm:8.3.5" +"@storybook/instrumenter@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/instrumenter@npm:8.3.6" dependencies: "@storybook/global": "npm:^5.0.0" "@vitest/utils": "npm:^2.0.5" util: "npm:^0.12.4" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/9dd4e9080591200aa410ae9f7c132b466173a4fc882ad78e06fdbe8d795b04cee3afc5b1385ecb3ff5b9fb314abc3da2ac1494a8bf815e1113c3e8a241c5b7b7 + storybook: ^8.3.6 + checksum: 10c0/e071fcdb9fdb87c215412351bba765beed43f3bb487e51e837a85e6eb12575edf89d77ca1ab9edb7a70ad01fd498bb3aa3b35bbdb318ef9246e43d1310734f87 languageName: node linkType: hard @@ -1876,43 +1876,43 @@ __metadata: languageName: node linkType: hard -"@storybook/manager-api@npm:^8.3.5": - version: 8.3.5 - resolution: "@storybook/manager-api@npm:8.3.5" +"@storybook/manager-api@npm:^8.3.6": + version: 8.3.6 + resolution: "@storybook/manager-api@npm:8.3.6" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/4983af0e3a05abec0114acfa8bfe9d5df816b91f76614eb3c37dd654a0d4fdf5379d4d0cffc1676c6681d6142acd44e81abcb7c4f6de6108c8d26158cca9820d + storybook: ^8.3.6 + checksum: 10c0/9193d0f2d7bffdc461083f3ec5acf31badeb935d4a2d76a7ab39d2bb261105e3b60695811802113d03ac2fed7562e5c1377e1a090243c2d56b5cede0c8bc5fdd languageName: node linkType: hard -"@storybook/preview-api@npm:^8.3.5": - version: 8.3.5 - resolution: "@storybook/preview-api@npm:8.3.5" +"@storybook/preview-api@npm:^8.3.6": + version: 8.3.6 + resolution: "@storybook/preview-api@npm:8.3.6" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/12d81d57c0931983428b8621311de79a3846ec0f1c51ab94c5a9e7795a1e4bade74aa62de3693c488001630403c98a5372caca4be613f6f806e7265afb22e3d7 + storybook: ^8.3.6 + checksum: 10c0/f0413c36765c3518503d8dc67752356106a8d90fbc8f1db14ab0b454458a41ff1e8ce012911e59fa4432dde4260495c93c6265273024dcb1e8bb4d4d6e13cedb languageName: node linkType: hard -"@storybook/react-dom-shim@npm:8.3.5": - version: 8.3.5 - resolution: "@storybook/react-dom-shim@npm:8.3.5" +"@storybook/react-dom-shim@npm:8.3.6": + version: 8.3.6 + resolution: "@storybook/react-dom-shim@npm:8.3.6" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.3.5 - checksum: 10c0/a765dc358ae2b1775197aa540024ff8f999c4d1673e76f632b74edd62bb609c8405f4ddfd392923ee6190bf0a1506a473cf41202323d8bda77b20681dc5a1594 + storybook: ^8.3.6 + checksum: 10c0/efc7917f8bc0d522ab075fd604c1aef9519f5ac534db56a37b03f7958eb1e0e954a7efde2c29ad6c553fb482cdaead7c9a78102e6dd172c3515bbf3d0062c774 languageName: node linkType: hard -"@storybook/react-vite@npm:^8.3.5": - version: 8.3.5 - resolution: "@storybook/react-vite@npm:8.3.5" +"@storybook/react-vite@npm:^8.3.6": + version: 8.3.6 + resolution: "@storybook/react-vite@npm:8.3.6" dependencies: "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.3.0" "@rollup/pluginutils": "npm:^5.0.2" - "@storybook/builder-vite": "npm:8.3.5" - "@storybook/react": "npm:8.3.5" + "@storybook/builder-vite": "npm:8.3.6" + "@storybook/react": "npm:8.3.6" find-up: "npm:^5.0.0" magic-string: "npm:^0.30.0" react-docgen: "npm:^7.0.0" @@ -1921,22 +1921,22 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.3.5 + storybook: ^8.3.6 vite: ^4.0.0 || ^5.0.0 - checksum: 10c0/a1e8969013cc74ffd353914880a533d5d64c2b912fa7b8e9528e06e2870625f7e9523647a8dfb03013e449a3eabcdefa5aa2155916dc6bb3063b66c3aa579be3 + checksum: 10c0/a1fa15aa49863cc1e1c6dc83186094ff43091c40daa720355eefb9a74934fa47d4c126bb6252ae0380a19219dd7e542c1782026acab3cd9a68f9513ba2f210e1 languageName: node linkType: hard -"@storybook/react@npm:8.3.5, @storybook/react@npm:^8.3.5": - version: 8.3.5 - resolution: "@storybook/react@npm:8.3.5" +"@storybook/react@npm:8.3.6, @storybook/react@npm:^8.3.6": + version: 8.3.6 + resolution: "@storybook/react@npm:8.3.6" dependencies: - "@storybook/components": "npm:^8.3.5" + "@storybook/components": "npm:^8.3.6" "@storybook/global": "npm:^5.0.0" - "@storybook/manager-api": "npm:^8.3.5" - "@storybook/preview-api": "npm:^8.3.5" - "@storybook/react-dom-shim": "npm:8.3.5" - "@storybook/theming": "npm:^8.3.5" + "@storybook/manager-api": "npm:^8.3.6" + "@storybook/preview-api": "npm:^8.3.6" + "@storybook/react-dom-shim": "npm:8.3.6" + "@storybook/theming": "npm:^8.3.6" "@types/escodegen": "npm:^0.0.6" "@types/estree": "npm:^0.0.51" "@types/node": "npm:^22.0.0" @@ -1952,27 +1952,27 @@ __metadata: type-fest: "npm:~2.19" util-deprecate: "npm:^1.0.2" peerDependencies: - "@storybook/test": 8.3.5 + "@storybook/test": 8.3.6 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.3.5 + storybook: ^8.3.6 typescript: ">= 4.2.x" peerDependenciesMeta: "@storybook/test": optional: true typescript: optional: true - checksum: 10c0/2db9aa673be975018c9a44cc4c0c662439a85298a4d931afe63bf4e1ca6f7ab6889eddf6d64a555483af84ccc65b3d81b712c2231096124063cf5afa77fcbb6d + checksum: 10c0/f536ec3c002f678b501ed0eb12b402770e2e185f9d751a6714609bacee7ae5f3ee05d25db3c7d44f1408819e313b78ce3aaa3eaa56a817b4c3b8083e5bd1383d languageName: node linkType: hard -"@storybook/test@npm:8.3.5, @storybook/test@npm:^8.3.5": - version: 8.3.5 - resolution: "@storybook/test@npm:8.3.5" +"@storybook/test@npm:8.3.6, @storybook/test@npm:^8.3.6": + version: 8.3.6 + resolution: "@storybook/test@npm:8.3.6" dependencies: "@storybook/csf": "npm:^0.1.11" "@storybook/global": "npm:^5.0.0" - "@storybook/instrumenter": "npm:8.3.5" + "@storybook/instrumenter": "npm:8.3.6" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.5.0" "@testing-library/user-event": "npm:14.5.2" @@ -1980,8 +1980,8 @@ __metadata: "@vitest/spy": "npm:2.0.5" util: "npm:^0.12.4" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/1890aeab0b10d08f6b6bbaf68defaa2a0794e17e8f43ac3d92c3873d34771e198d088a2f4c0b135a299fe586407c4315cd443e270afa65d84914fcc5d63ea099 + storybook: ^8.3.6 + checksum: 10c0/708932d35b62fd84fb0fe7813d229fdb8fda77a8403496c79652e5418f015a68d15a2c422325790bf71d869f9cca3a377e92bde7fc8881285ffb290087b08daf languageName: node linkType: hard @@ -1994,12 +1994,12 @@ __metadata: languageName: node linkType: hard -"@storybook/theming@npm:^8.3.5": - version: 8.3.5 - resolution: "@storybook/theming@npm:8.3.5" +"@storybook/theming@npm:^8.3.6": + version: 8.3.6 + resolution: "@storybook/theming@npm:8.3.6" peerDependencies: - storybook: ^8.3.5 - checksum: 10c0/25455033b2b3bdb86083f759df77df42bfff71405c6abd301963dd7596003b462a0960a86c5cec3503dc9e1eba565eeb82a897002c6823f9ee361155b9ddfd23 + storybook: ^8.3.6 + checksum: 10c0/9e6dc4cb4bb444cbf3ac0f5b42696d462cd4886a5dcd42b4a8aee332ab141a1b745041bbb3fb36ab220820a3ab36ac4edd5f1ef81b66bba4059b4bdaa890006e languageName: node linkType: hard @@ -2714,12 +2714,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^22.7.5": - version: 22.7.5 - resolution: "@types/node@npm:22.7.5" +"@types/node@npm:^22.7.6": + version: 22.7.6 + resolution: "@types/node@npm:22.7.6" dependencies: undici-types: "npm:~6.19.2" - checksum: 10c0/cf11f74f1a26053ec58066616e3a8685b6bcd7259bc569738b8f752009f9f0f7f85a1b2d24908e5b0f752482d1e8b6babdf1fbb25758711ec7bb9500bfcd6e60 + checksum: 10c0/d4406a63afce981c363fb1d1954aaf1759ad2d487c0833ebf667565ea4e45ff217d6fab4b5343badbdeccdf9d2e4a0841d633e0c929ceabcb33c288663dd0c73 languageName: node linkType: hard @@ -2950,15 +2950,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.8.1": - version: 8.8.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.8.1" +"@typescript-eslint/eslint-plugin@npm:8.10.0": + version: 8.10.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.10.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.8.1" - "@typescript-eslint/type-utils": "npm:8.8.1" - "@typescript-eslint/utils": "npm:8.8.1" - "@typescript-eslint/visitor-keys": "npm:8.8.1" + "@typescript-eslint/scope-manager": "npm:8.10.0" + "@typescript-eslint/type-utils": "npm:8.10.0" + "@typescript-eslint/utils": "npm:8.10.0" + "@typescript-eslint/visitor-keys": "npm:8.10.0" graphemer: "npm:^1.4.0" ignore: "npm:^5.3.1" natural-compare: "npm:^1.4.0" @@ -2969,66 +2969,66 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/020a0a482202b34c6665a56ec5902e38ae1870b2600ec1b2092de352b23099dde553781ee8323974f63962ebe164a6304f0019e937afb5cf7854b0e0163ad1ca + checksum: 10c0/4b77ba9c865a2a14e238cd330b5901f0274b8ce1c13324fccd0339b8eea82a50a4709394c903fd8cd5bd0d3aebace0761ff9a4a19fa20b00bb61349b7671c035 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.8.1": - version: 8.8.1 - resolution: "@typescript-eslint/parser@npm:8.8.1" +"@typescript-eslint/parser@npm:8.10.0": + version: 8.10.0 + resolution: "@typescript-eslint/parser@npm:8.10.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.8.1" - "@typescript-eslint/types": "npm:8.8.1" - "@typescript-eslint/typescript-estree": "npm:8.8.1" - "@typescript-eslint/visitor-keys": "npm:8.8.1" + "@typescript-eslint/scope-manager": "npm:8.10.0" + "@typescript-eslint/types": "npm:8.10.0" + "@typescript-eslint/typescript-estree": "npm:8.10.0" + "@typescript-eslint/visitor-keys": "npm:8.10.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/2afd147ccec6754316d6837d6108a5d822eb6071e1a7355073288c232530bc3e49901d3f08755ce02d497110c531f3b3658eb46d0ff875a69d4f360b5f938cb4 + checksum: 10c0/7becb2457c085c239838d301796074b790f46dd38c9fbc14ec1dec8e993c7115cd8a66cdc07983c3a68a2dd92e24e8acc49d69a4ebcc29e9869957eb52d1cb74 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.8.1": - version: 8.8.1 - resolution: "@typescript-eslint/scope-manager@npm:8.8.1" +"@typescript-eslint/scope-manager@npm:8.10.0": + version: 8.10.0 + resolution: "@typescript-eslint/scope-manager@npm:8.10.0" dependencies: - "@typescript-eslint/types": "npm:8.8.1" - "@typescript-eslint/visitor-keys": "npm:8.8.1" - checksum: 10c0/6f697baf087aedc3f0f228ff964fd108a9dd33fe4e5cc6c914be6367c324cee55629e099832668042bedfec8cdc72c6ef2ca960ee26966dbcc75753059a1352f + "@typescript-eslint/types": "npm:8.10.0" + "@typescript-eslint/visitor-keys": "npm:8.10.0" + checksum: 10c0/b8bb8635c4d6c00a3578d6265e3ee0f5d96d0c9dee534ed588aa411c3f4497fd71cce730c3ae7571e52453d955b191bc9edcc47c9af21a20c90e9a20f2371108 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.8.1": - version: 8.8.1 - resolution: "@typescript-eslint/type-utils@npm:8.8.1" +"@typescript-eslint/type-utils@npm:8.10.0": + version: 8.10.0 + resolution: "@typescript-eslint/type-utils@npm:8.10.0" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.8.1" - "@typescript-eslint/utils": "npm:8.8.1" + "@typescript-eslint/typescript-estree": "npm:8.10.0" + "@typescript-eslint/utils": "npm:8.10.0" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.3.0" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/6edfc2b9fca5233dd922141f080377b677db1093ec3e702a3ab52d58f77b91c0fb69479d4d42f125536b8fc0ffa85c07c7de2f17cc4c6fa1df1226ec01e5608c + checksum: 10c0/1af8fce8394279e6ac7bcef449a132072ee36e374c8d557564246ffe7150230844901ca0305e29525bf37c87010e03bf8bedec76fccbfe1e41931cb4f274e208 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.8.1": - version: 8.8.1 - resolution: "@typescript-eslint/types@npm:8.8.1" - checksum: 10c0/4b44857332a0b1bfafbeccb8be157f8266d9e226ac723f6af1272b9b670b49444423ddac733655163eb3b90e8c88393a68ab2d7f326f5775371eaf4b9ca31d7b +"@typescript-eslint/types@npm:8.10.0": + version: 8.10.0 + resolution: "@typescript-eslint/types@npm:8.10.0" + checksum: 10c0/f27dd43c8383e02e914a254257627e393dfc0f08b0f74a253c106813ae361f090271b2f3f2ef588fa3ca1329897d873da595bb5641fe8e3091b25eddca24b5d2 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.8.1": - version: 8.8.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.8.1" +"@typescript-eslint/typescript-estree@npm:8.10.0": + version: 8.10.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.10.0" dependencies: - "@typescript-eslint/types": "npm:8.8.1" - "@typescript-eslint/visitor-keys": "npm:8.8.1" + "@typescript-eslint/types": "npm:8.10.0" + "@typescript-eslint/visitor-keys": "npm:8.10.0" debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" is-glob: "npm:^4.0.3" @@ -3038,31 +3038,31 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/e3b9bc1e925c07833237044271cdc9bd8bdba3e2143dcfc5bf3bf481c89731b666a6fad25333a4b1980ac2f4c6f5e6e42c71206f73f3704e319f6b3b67463a6a + checksum: 10c0/535a740fe25be0e28fe68c41e3264273d1e5169c9f938e08cc0e3415c357726f43efa44621960108c318fc3305c425d29f3223b6e731d44d67f84058a8947304 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.8.1": - version: 8.8.1 - resolution: "@typescript-eslint/utils@npm:8.8.1" +"@typescript-eslint/utils@npm:8.10.0": + version: 8.10.0 + resolution: "@typescript-eslint/utils@npm:8.10.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.8.1" - "@typescript-eslint/types": "npm:8.8.1" - "@typescript-eslint/typescript-estree": "npm:8.8.1" + "@typescript-eslint/scope-manager": "npm:8.10.0" + "@typescript-eslint/types": "npm:8.10.0" + "@typescript-eslint/typescript-estree": "npm:8.10.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - checksum: 10c0/954a2e85ae56a3ebefb6e41fb33c59ffa886963860536e9729a35ecea55eefdc58858c7aa126048c4a61f4fd9997b4f7601e7884ed2b3e4e7a46c9e4617a9f29 + checksum: 10c0/a21a2933517176abd00fcd5d8d80023e35dc3d89d5746bbac43790b4e984ab1f371117db08048bce7f42d54c64f4e0e35161149f8f34fd25a27bff9d1110fd16 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.8.1": - version: 8.8.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.8.1" +"@typescript-eslint/visitor-keys@npm:8.10.0": + version: 8.10.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.10.0" dependencies: - "@typescript-eslint/types": "npm:8.8.1" + "@typescript-eslint/types": "npm:8.10.0" eslint-visitor-keys: "npm:^3.4.3" - checksum: 10c0/6f917090b61277bd443aa851c532c4a9cc91ad57aedf185c5dff0c530f158cce84ef815833bd8deffa87f0bbf7a9f1abd1e02e30af2463c4e7f27c0c08f59080 + checksum: 10c0/14721c4ac939640d5fd1ee1b6eeb07604b11a6017e319e21dcc71e7aac2992341fc7ae1992d977bad4433b6a1d0d1c0c279e6927316b26245f6e333f922fa458 languageName: node linkType: hard @@ -3084,9 +3084,9 @@ __metadata: languageName: node linkType: hard -"@vitest/coverage-istanbul@npm:^2.1.2": - version: 2.1.2 - resolution: "@vitest/coverage-istanbul@npm:2.1.2" +"@vitest/coverage-istanbul@npm:^2.1.3": + version: 2.1.3 + resolution: "@vitest/coverage-istanbul@npm:2.1.3" dependencies: "@istanbuljs/schema": "npm:^0.1.3" debug: "npm:^4.3.6" @@ -3099,8 +3099,8 @@ __metadata: test-exclude: "npm:^7.0.1" tinyrainbow: "npm:^1.2.0" peerDependencies: - vitest: 2.1.2 - checksum: 10c0/38da5a0ec4f67e83a41b49c6c1861b9bfc4ca8458fd06d96735f2998a105ae42190dbfb41c75549e6b9268dc932dcde8c1323b4df20139fc49d1a42ffab65823 + vitest: 2.1.3 + checksum: 10c0/6b21eb219f45dc0f3bfb35049280658687b6b2f4ba5e17dc2c7e2c221f5d37e60c6962c5cfd77bd5f2848bb56debd26f82e5684b293f5775a8a416a0173f1803 languageName: node linkType: hard @@ -3116,27 +3116,27 @@ __metadata: languageName: node linkType: hard -"@vitest/expect@npm:2.1.2": - version: 2.1.2 - resolution: "@vitest/expect@npm:2.1.2" +"@vitest/expect@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/expect@npm:2.1.3" dependencies: - "@vitest/spy": "npm:2.1.2" - "@vitest/utils": "npm:2.1.2" + "@vitest/spy": "npm:2.1.3" + "@vitest/utils": "npm:2.1.3" chai: "npm:^5.1.1" tinyrainbow: "npm:^1.2.0" - checksum: 10c0/57233a60685f81ff5cb615156ac164608488c584cb62d7cc63d7ac28674e4c954133d4bb0948e88241c0f07d31803c0d1efd88562c4cac8e1bc5a2b24367ec0f + checksum: 10c0/0837adcbb938feebcc083664afc5c4d12e42f1f2442b6f1bedc6b5650a8ff2448b1f10713b45afb099c839fb5cf766c971736267fa9b0fe2ac87f3e2d7f782c2 languageName: node linkType: hard -"@vitest/mocker@npm:2.1.2": - version: 2.1.2 - resolution: "@vitest/mocker@npm:2.1.2" +"@vitest/mocker@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/mocker@npm:2.1.3" dependencies: - "@vitest/spy": "npm:^2.1.0-beta.1" + "@vitest/spy": "npm:2.1.3" estree-walker: "npm:^3.0.3" magic-string: "npm:^0.30.11" peerDependencies: - "@vitest/spy": 2.1.2 + "@vitest/spy": 2.1.3 msw: ^2.3.5 vite: ^5.0.0 peerDependenciesMeta: @@ -3144,7 +3144,7 @@ __metadata: optional: true vite: optional: true - checksum: 10c0/24824666d3045bdbbff77481b033d58fd07db6247846c6090cae44b75080e691f743f850300f27f9b0a790c9e3c918848a400cf7c024c9633084c1ad6311d201 + checksum: 10c0/03c80628d092244f21a0ba9041665fc75f987d0d11fab1ae0b7027ec21e503f65057e8c24b936602c5f852d83fbb183da13d05dba117c99785b41b3dafd105ce languageName: node linkType: hard @@ -3166,33 +3166,33 @@ __metadata: languageName: node linkType: hard -"@vitest/pretty-format@npm:2.1.2, @vitest/pretty-format@npm:^2.1.2": - version: 2.1.2 - resolution: "@vitest/pretty-format@npm:2.1.2" +"@vitest/pretty-format@npm:2.1.3, @vitest/pretty-format@npm:^2.1.3": + version: 2.1.3 + resolution: "@vitest/pretty-format@npm:2.1.3" dependencies: tinyrainbow: "npm:^1.2.0" - checksum: 10c0/e2c35dc424450f46794ff420b050e2ce77b3f3d2bdf2509c1adf51d327eeb5cc4ea42fc44919d63b3afdbfcc6da7d7e82962193d0a543c81e0f35ccdfc808835 + checksum: 10c0/5a6ee872a8adf5e2764f2b5b2276d8a2199be4ef14777ab693428caf359481851400af10b59721d4972289c955ffe7277954a662b04cfb10233824574c7074ba languageName: node linkType: hard -"@vitest/runner@npm:2.1.2": - version: 2.1.2 - resolution: "@vitest/runner@npm:2.1.2" +"@vitest/runner@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/runner@npm:2.1.3" dependencies: - "@vitest/utils": "npm:2.1.2" + "@vitest/utils": "npm:2.1.3" pathe: "npm:^1.1.2" - checksum: 10c0/c6008703ef7b9033b219690a84003c9c078e9de7ace63cefe7c9cd455667d5081c328645e3a538e23fcc221170901d1d1bb0430c4402391d74f8ffab8db62f81 + checksum: 10c0/d5b077643265d10025e22fa64a0e54c3d4fddc23e05f9fcd143dbcc4080851b0df31985986e57890a974577a18d3af624758b6062801d7dd96f9b4f2eaf591f1 languageName: node linkType: hard -"@vitest/snapshot@npm:2.1.2": - version: 2.1.2 - resolution: "@vitest/snapshot@npm:2.1.2" +"@vitest/snapshot@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/snapshot@npm:2.1.3" dependencies: - "@vitest/pretty-format": "npm:2.1.2" + "@vitest/pretty-format": "npm:2.1.3" magic-string: "npm:^0.30.11" pathe: "npm:^1.1.2" - checksum: 10c0/a05805e9eb9d460830d9f30fbdd488fee4e8bb87dc55e71f5c3541fcd4ef4d333f5c020fd26e8554771157e4e8037d164a63ab5ac0046f7640aca0b8b3fbc837 + checksum: 10c0/a3dcea6a5f7581b6a34dc3bf5f7bd42a05e2ccf6e1171d9f1b759688aebe650e6412564d066aeaa45e83ac549d453b6a3edcf774a8ac728c0c639f8dc919039f languageName: node linkType: hard @@ -3205,21 +3205,12 @@ __metadata: languageName: node linkType: hard -"@vitest/spy@npm:2.1.2": - version: 2.1.2 - resolution: "@vitest/spy@npm:2.1.2" +"@vitest/spy@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/spy@npm:2.1.3" dependencies: tinyspy: "npm:^3.0.0" - checksum: 10c0/28781abb8c33274bfcf7ab85d4ce47f1583b0a11575fecbdce7b88dac5df5de62c5e11b0b55043e610c0712258b66aee2a8ff2f55068352c61b4a5d2aa9d50ca - languageName: node - linkType: hard - -"@vitest/spy@npm:^2.1.0-beta.1": - version: 2.1.1 - resolution: "@vitest/spy@npm:2.1.1" - dependencies: - tinyspy: "npm:^3.0.0" - checksum: 10c0/b251be1390c105b68aa95270159c4583c3e1a0f7a2e1f82db8b7fadedc3cb459c5ef9286033a1ae764810e00715552fc80afe4507cd8b0065934fb1a64926e06 + checksum: 10c0/8d85a5c2848c5bd81892af989aebad65d0c7ae74094aa98ad4f35ecf80755259c7a748a8e7bf683b2906fac29a51fc0ffa82f8fc073b36dbd8a0418261fccdba languageName: node linkType: hard @@ -3235,14 +3226,14 @@ __metadata: languageName: node linkType: hard -"@vitest/utils@npm:2.1.2": - version: 2.1.2 - resolution: "@vitest/utils@npm:2.1.2" +"@vitest/utils@npm:2.1.3": + version: 2.1.3 + resolution: "@vitest/utils@npm:2.1.3" dependencies: - "@vitest/pretty-format": "npm:2.1.2" + "@vitest/pretty-format": "npm:2.1.3" loupe: "npm:^3.1.1" tinyrainbow: "npm:^1.2.0" - checksum: 10c0/ab1fac69f34c32eb229c4e5f14bec37f16211a77ba16b0e178678d5a67fd74a209c365df0cf7d27bfd6fd2572d563a6b28269d13f958dc083175a6ae2c269085 + checksum: 10c0/55a044e43b84c0f8f573d8578107f26440678b6f506c8d9fee88b7ef120d19efd27c9be77985c107113b0f3f3db298dcee57074e1c1c214bee7a097fd08a209b languageName: node linkType: hard @@ -5226,16 +5217,16 @@ __metadata: languageName: node linkType: hard -"electron@npm:^32.1.2": - version: 32.1.2 - resolution: "electron@npm:32.1.2" +"electron@npm:^32.2.0": + version: 32.2.0 + resolution: "electron@npm:32.2.0" dependencies: "@electron/get": "npm:^2.0.0" "@types/node": "npm:^20.9.0" extract-zip: "npm:^2.0.1" bin: electron: cli.js - checksum: 10c0/9e453e046e024b1454fb212e977d622feba3b6d24ff922a360c06177ec8bcbe58e729e105260285e79c0a203dfcb42d3d795844030bba8681016ab50b7d4bb87 + checksum: 10c0/28d988a9d05c89e93d70cc790bd53ecc97135cc3fa9efe3617f10b87cdf85ada468d383afd7858bcf8f064aa189ea7f8987e32c7ebaac70bda64e8f9f85621e8 languageName: node linkType: hard @@ -10545,16 +10536,16 @@ __metadata: languageName: node linkType: hard -"storybook@npm:^8.3.5": - version: 8.3.5 - resolution: "storybook@npm:8.3.5" +"storybook@npm:^8.3.6": + version: 8.3.6 + resolution: "storybook@npm:8.3.6" dependencies: - "@storybook/core": "npm:8.3.5" + "@storybook/core": "npm:8.3.6" bin: getstorybook: ./bin/index.cjs sb: ./bin/index.cjs storybook: ./bin/index.cjs - checksum: 10c0/8f8ffe54c5dad8bad9d8701f63f4670cf3f388acd0e21e5bedeb86ebd2af5adf905024b6b9a2c46cf1199a0c1c86a97fddfa1fc70546149e18263b3b47c80585 + checksum: 10c0/f366ab5feeb354200c6df58f6d7bafc146b56af2d72ad93e19438e1750de2185c199fd804da7728ecaba73ef73404a53f0501829c2af6dffff830f848faf198c languageName: node linkType: hard @@ -10975,13 +10966,20 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.6.3": +"tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.4.0": version: 2.7.0 resolution: "tslib@npm:2.7.0" checksum: 10c0/469e1d5bf1af585742128827000711efa61010b699cb040ab1800bcd3ccdd37f63ec30642c9e07c4439c1db6e46345582614275daca3e0f4abae29b0083f04a6 languageName: node linkType: hard +"tslib@npm:^2.8.0": + version: 2.8.0 + resolution: "tslib@npm:2.8.0" + checksum: 10c0/31e4d14dc1355e9b89e4d3c893a18abb7f90b6886b089c2da91224d0a7752c79f3ddc41bc1aa0a588ac895bd97bb99c5bc2bfdb2f86de849f31caeb3ba79bbe5 + languageName: node + linkType: hard + "tsx@npm:^4.19.1": version: 4.19.1 resolution: "tsx@npm:4.19.1" @@ -11038,17 +11036,17 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:^8.8.1": - version: 8.8.1 - resolution: "typescript-eslint@npm:8.8.1" +"typescript-eslint@npm:^8.10.0": + version: 8.10.0 + resolution: "typescript-eslint@npm:8.10.0" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.8.1" - "@typescript-eslint/parser": "npm:8.8.1" - "@typescript-eslint/utils": "npm:8.8.1" + "@typescript-eslint/eslint-plugin": "npm:8.10.0" + "@typescript-eslint/parser": "npm:8.10.0" + "@typescript-eslint/utils": "npm:8.10.0" peerDependenciesMeta: typescript: optional: true - checksum: 10c0/d6793697fce239ef8838ced6e1e59940c30579c8f62c49bc605fdeda9f3f7a5c24bfddd997b142f8c411859dc0b9985ecdae569814dd4f8e6775e1899d55e9cc + checksum: 10c0/9eca1bfdea4e108bea06f0053b57f8a9eaeb43bf3316a39c6d29631bd3217ec67611e46d361417c48a5f6eeb99c4a7fc81c020734b1b47d7b8e42fa67d74ea0f languageName: node linkType: hard @@ -11382,9 +11380,9 @@ __metadata: languageName: node linkType: hard -"vite-node@npm:2.1.2": - version: 2.1.2 - resolution: "vite-node@npm:2.1.2" +"vite-node@npm:2.1.3": + version: 2.1.3 + resolution: "vite-node@npm:2.1.3" dependencies: cac: "npm:^6.7.14" debug: "npm:^4.3.6" @@ -11392,7 +11390,7 @@ __metadata: vite: "npm:^5.0.0" bin: vite-node: vite-node.mjs - checksum: 10c0/7bef84ee757373cc3d171aba51299389e31cb39265df7beef3bb4b70edf1f99425577cd70b9048d357653a0247e8c20f2aa62579d57b2cfc8d74cd6945828b7f + checksum: 10c0/1b06139880a8170651e025e8c35aa92a917f8ec8f24507cda5bf4be09843f6447e1f494932a8d7eb98124f1c8c9fee02283ef318ddd57e2b861d2d85a409a206 languageName: node linkType: hard @@ -11494,9 +11492,9 @@ __metadata: languageName: node linkType: hard -"vite@npm:^5.4.8": - version: 5.4.8 - resolution: "vite@npm:5.4.8" +"vite@npm:^5.4.9": + version: 5.4.9 + resolution: "vite@npm:5.4.9" dependencies: esbuild: "npm:^0.21.3" fsevents: "npm:~2.3.3" @@ -11533,21 +11531,21 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/af70af6d6316a3af71f44ebe3ab343bd66450d4157af73af3b32239e1b6ec43ff6f651d7cc4193b21ed3bff2e9356a3de9e96aee53857f39922e4a2d9fad75a1 + checksum: 10c0/e9c59f2c639047e37c79bbbb151c7a55a3dc27932957cf4cf0447ee0bdcc1ddfd9b1fb3ba0465371c01ba3616d62561327855794c2d652213c3a10a32e6d369d languageName: node linkType: hard -"vitest@npm:^2.1.2": - version: 2.1.2 - resolution: "vitest@npm:2.1.2" +"vitest@npm:^2.1.3": + version: 2.1.3 + resolution: "vitest@npm:2.1.3" dependencies: - "@vitest/expect": "npm:2.1.2" - "@vitest/mocker": "npm:2.1.2" - "@vitest/pretty-format": "npm:^2.1.2" - "@vitest/runner": "npm:2.1.2" - "@vitest/snapshot": "npm:2.1.2" - "@vitest/spy": "npm:2.1.2" - "@vitest/utils": "npm:2.1.2" + "@vitest/expect": "npm:2.1.3" + "@vitest/mocker": "npm:2.1.3" + "@vitest/pretty-format": "npm:^2.1.3" + "@vitest/runner": "npm:2.1.3" + "@vitest/snapshot": "npm:2.1.3" + "@vitest/spy": "npm:2.1.3" + "@vitest/utils": "npm:2.1.3" chai: "npm:^5.1.1" debug: "npm:^4.3.6" magic-string: "npm:^0.30.11" @@ -11558,13 +11556,13 @@ __metadata: tinypool: "npm:^1.0.0" tinyrainbow: "npm:^1.2.0" vite: "npm:^5.0.0" - vite-node: "npm:2.1.2" + vite-node: "npm:2.1.3" why-is-node-running: "npm:^2.3.0" peerDependencies: "@edge-runtime/vm": "*" "@types/node": ^18.0.0 || >=20.0.0 - "@vitest/browser": 2.1.2 - "@vitest/ui": 2.1.2 + "@vitest/browser": 2.1.3 + "@vitest/ui": 2.1.3 happy-dom: "*" jsdom: "*" peerDependenciesMeta: @@ -11582,7 +11580,7 @@ __metadata: optional: true bin: vitest: vitest.mjs - checksum: 10c0/79301678bb0207f4bfb16e155e1744ed0b9866c4e1913bb43a3821c01fcda033b7263dac8da87946f90dff6f8f1578a4f94f860409edf0332c3dbfa3a8202803 + checksum: 10c0/7688fdce37205e7f3b448039df216e103e3a52994af0201993e22decbb558d129a734001b991f3c3d80bf4a4ef91ca6a5665a7395d5b051249da60a0016eda36 languageName: node linkType: hard @@ -11637,20 +11635,20 @@ __metadata: dependencies: "@chromatic-com/storybook": "npm:^2.0.2" "@eslint/js": "npm:^9.12.0" - "@floating-ui/react": "npm:^0.26.24" + "@floating-ui/react": "npm:^0.26.25" "@monaco-editor/loader": "npm:^1.4.0" "@monaco-editor/react": "npm:^4.6.0" "@observablehq/plot": "npm:^0.6.16" "@react-hook/resize-observer": "npm:^2.0.2" "@rollup/plugin-node-resolve": "npm:^15.3.0" - "@storybook/addon-essentials": "npm:^8.3.5" - "@storybook/addon-interactions": "npm:^8.3.5" - "@storybook/addon-links": "npm:^8.3.5" - "@storybook/blocks": "npm:^8.3.5" - "@storybook/react": "npm:^8.3.5" - "@storybook/react-vite": "npm:^8.3.5" - "@storybook/test": "npm:^8.3.5" - "@storybook/theming": "npm:^8.3.5" + "@storybook/addon-essentials": "npm:^8.3.6" + "@storybook/addon-interactions": "npm:^8.3.6" + "@storybook/addon-links": "npm:^8.3.6" + "@storybook/blocks": "npm:^8.3.6" + "@storybook/react": "npm:^8.3.6" + "@storybook/react-vite": "npm:^8.3.6" + "@storybook/test": "npm:^8.3.6" + "@storybook/theming": "npm:^8.3.6" "@table-nav/core": "npm:^0.0.7" "@table-nav/react": "npm:^0.0.7" "@tanstack/react-table": "npm:^8.20.5" @@ -11658,7 +11656,7 @@ __metadata: "@types/css-tree": "npm:^2" "@types/debug": "npm:^4" "@types/electron": "npm:^1.6.10" - "@types/node": "npm:^22.7.5" + "@types/node": "npm:^22.7.6" "@types/papaparse": "npm:^5" "@types/pngjs": "npm:^6.0.5" "@types/react": "npm:^18.3.11" @@ -11671,7 +11669,7 @@ __metadata: "@types/uuid": "npm:^10.0.0" "@types/ws": "npm:^8" "@vitejs/plugin-react-swc": "npm:^3.7.1" - "@vitest/coverage-istanbul": "npm:^2.1.2" + "@vitest/coverage-istanbul": "npm:^2.1.3" "@xterm/addon-fit": "npm:^0.10.0" "@xterm/addon-serialize": "npm:^0.13.0" "@xterm/addon-web-links": "npm:^0.11.0" @@ -11683,7 +11681,7 @@ __metadata: css-tree: "npm:^3.0.0" dayjs: "npm:^1.11.13" debug: "npm:^4.3.7" - electron: "npm:^32.1.2" + electron: "npm:^32.2.0" electron-builder: "npm:^25.1.7" electron-updater: "npm:6.3.9" electron-vite: "npm:^2.3.0" @@ -11724,25 +11722,25 @@ __metadata: sharp: "npm:^0.33.5" shell-quote: "npm:^1.8.1" sprintf-js: "npm:^1.1.3" - storybook: "npm:^8.3.5" + storybook: "npm:^8.3.6" storybook-dark-mode: "npm:^4.0.2" throttle-debounce: "npm:^5.0.2" tinycolor2: "npm:^1.6.0" ts-node: "npm:^10.9.2" - tslib: "npm:^2.6.3" + tslib: "npm:^2.8.0" tsx: "npm:^4.19.1" typescript: "npm:^5.6.3" - typescript-eslint: "npm:^8.8.1" + typescript-eslint: "npm:^8.10.0" use-device-pixel-ratio: "npm:^1.1.2" - vite: "npm:^5.4.8" + vite: "npm:^5.4.9" vite-plugin-image-optimizer: "npm:^1.1.8" vite-plugin-static-copy: "npm:^2.0.0" vite-plugin-svgr: "npm:^4.2.0" vite-tsconfig-paths: "npm:^5.0.1" - vitest: "npm:^2.1.2" + vitest: "npm:^2.1.3" winston: "npm:^3.15.0" ws: "npm:^8.18.0" - yaml: "npm:^2.5.1" + yaml: "npm:^2.6.0" languageName: unknown linkType: soft @@ -11941,7 +11939,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.0.0": +"yaml@npm:^2.0.0, yaml@npm:^2.6.0": version: 2.6.0 resolution: "yaml@npm:2.6.0" bin: @@ -11950,15 +11948,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.5.1": - version: 2.5.1 - resolution: "yaml@npm:2.5.1" - bin: - yaml: bin.mjs - checksum: 10c0/40fba5682898dbeeb3319e358a968fe886509fab6f58725732a15f8dda3abac509f91e76817c708c9959a15f786f38ff863c1b88062d7c1162c5334a7d09cb4a - languageName: node - linkType: hard - "yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1"