From 68b91ccd39e4c7d6094285c549d251f605c36708 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sat, 17 Feb 2024 15:13:17 +0800 Subject: [PATCH 1/3] feat: show error dialog on init error --- packages/client-api/package.json | 1 + packages/client-api/src/init-element.ts | 135 ++++++----- packages/client-api/src/init-window.ts | 148 +++++++----- .../src/user-config/get-user-config.ts | 11 +- .../client-api/src/utils/create-logger.ts | 62 ++--- packages/desktop/Cargo.lock | 222 ++++++++++++++---- packages/desktop/Cargo.toml | 1 + packages/desktop/src/main.rs | 6 + pnpm-lock.yaml | 9 + 9 files changed, 391 insertions(+), 204 deletions(-) diff --git a/packages/client-api/package.json b/packages/client-api/package.json index 1f53279d..292c9181 100644 --- a/packages/client-api/package.json +++ b/packages/client-api/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@tauri-apps/api": "2.0.0-alpha.13", + "@tauri-apps/plugin-dialog": "2.0.0-alpha.5", "@tauri-apps/plugin-shell": "2.0.0-alpha.3", "@tauri-apps/plugin-window": "2.0.0-alpha.1", "glazewm": "1.1.2", diff --git a/packages/client-api/src/init-element.ts b/packages/client-api/src/init-element.ts index d63b65bd..b8209837 100644 --- a/packages/client-api/src/init-element.ts +++ b/packages/client-api/src/init-element.ts @@ -5,17 +5,21 @@ import { runWithOwner, } from 'solid-js'; import { createStore } from 'solid-js/store'; +import { message } from '@tauri-apps/plugin-dialog'; import { getStyleBuilder, getParsedElementConfig, getChildConfigs, type GlobalConfig, + TemplatePropertyError, } from './user-config'; import { getElementProviders } from './providers'; import type { ElementContext } from './element-context.model'; import { ElementType } from './element-type.model'; -import type { PickPartial } from './utils'; +import { createLogger, type PickPartial } from './utils'; + +const logger = createLogger('init-element'); export interface InitElementArgs { id: string; @@ -31,74 +35,89 @@ export interface InitElementArgs { export async function initElement( args: InitElementArgs, ): Promise { - const styleBuilder = getStyleBuilder(args.owner); - const childConfigs = getChildConfigs(args.rawConfig); + try { + const styleBuilder = getStyleBuilder(args.owner); + const childConfigs = getChildConfigs(args.rawConfig); - // Create partial element context; `providers` and `parsedConfig` are set later. - const [elementContext, setElementContext] = createStore< - PickPartial - >({ - id: args.id, - type: args.type, - rawConfig: args.rawConfig, - globalConfig: args.globalConfig, - args: args.args, - env: args.env, - initChildElement, - providers: undefined, - parsedConfig: undefined, - }); + // Create partial element context; `providers` and `parsedConfig` are set later. + const [elementContext, setElementContext] = createStore< + PickPartial + >({ + id: args.id, + type: args.type, + rawConfig: args.rawConfig, + globalConfig: args.globalConfig, + args: args.args, + env: args.env, + initChildElement, + providers: undefined, + parsedConfig: undefined, + }); - const { element, merged } = await getElementProviders( - elementContext, - args.ancestorProviders, - args.owner, - ); + const { element, merged } = await getElementProviders( + elementContext, + args.ancestorProviders, + args.owner, + ); - setElementContext('providers', merged); + setElementContext('providers', merged); - const parsedConfig = getParsedElementConfig( - elementContext as PickPartial, - args.owner, - ); + const parsedConfig = getParsedElementConfig( + elementContext as PickPartial, + args.owner, + ); - // Since `parsedConfig` and `providers` are set after initializing providers - // and parsing the element config, they are initially unavailable on 'self' - // provider. - setElementContext('parsedConfig', parsedConfig); + // Since `parsedConfig` and `providers` are set after initializing providers + // and parsing the element config, they are initially unavailable on 'self' + // provider. + setElementContext('parsedConfig', parsedConfig); - runWithOwner(args.owner, () => { - createEffect(() => { - if (parsedConfig.styles) { - styleBuilder.setElementStyles( - parsedConfig.id, - parsedConfig.styles, - ); - } + runWithOwner(args.owner, () => { + createEffect(() => { + if (parsedConfig.styles) { + styleBuilder.setElementStyles( + parsedConfig.id, + parsedConfig.styles, + ); + } + }); }); - }); - async function initChildElement(id: string) { - const childConfig = childConfigs.find( - childConfig => childConfig.id === id, - ); + async function initChildElement(id: string) { + const childConfig = childConfigs.find( + childConfig => childConfig.id === id, + ); + + // Check whether an element with the given ID exists in the config. + if (!childConfig) { + return null; + } - // Check whether an element with the given ID exists in the config. - if (!childConfig) { - return null; + return initElement({ + id, + type: childConfig.type, + rawConfig: childConfig.config, + globalConfig: args.globalConfig, + args: args.args, + env: args.env, + ancestorProviders: [...(args.ancestorProviders ?? []), element], + owner: args.owner, + }); } - return initElement({ - id, - type: childConfig.type, - rawConfig: childConfig.config, - globalConfig: args.globalConfig, - args: args.args, - env: args.env, - ancestorProviders: [...(args.ancestorProviders ?? []), element], - owner: args.owner, + return elementContext as ElementContext; + } catch (err) { + logger.error( + 'Error initializing window:', + //@ts-ignore + Object.keys(err), + err instanceof TemplatePropertyError, + err, + ); + message((err as Error).message, { + title: 'Encountered an error!', + type: 'error', }); + throw err; } - - return elementContext as ElementContext; } diff --git a/packages/client-api/src/init-window.ts b/packages/client-api/src/init-window.ts index dc013329..b1e3eb37 100644 --- a/packages/client-api/src/init-window.ts +++ b/packages/client-api/src/init-window.ts @@ -1,5 +1,6 @@ import { createEffect, getOwner, runWithOwner } from 'solid-js'; import { getCurrent as getCurrentWindow } from '@tauri-apps/api/window'; +import { message as messageDialog } from '@tauri-apps/plugin-dialog'; import { GlobalConfigSchema, @@ -17,6 +18,9 @@ import { import { initElement } from './init-element'; import type { WindowContext } from './element-context.model'; import { ElementType } from './element-type.model'; +import { createLogger } from '~/utils'; + +const logger = createLogger('init-window'); export function initWindow(callback: (context: WindowContext) => void) { initWindowAsync().then(callback); @@ -32,74 +36,88 @@ export function initWindow(callback: (context: WindowContext) => void) { * * Building CSS and appending it to `` */ export async function initWindowAsync(): Promise { - // TODO: Create new root if owner is null. - const owner = getOwner()!; - const config = await getUserConfig(); - const styleBuilder = getStyleBuilder(owner); - - const openArgs = - window.__ZEBAR_OPEN_ARGS ?? - (await getOpenWindowArgs(await getCurrentWindow().label)); - - const windowConfig = (config as UserConfig)[ - `window/${openArgs.windowId}` as const - ]; - - if (!windowConfig) { - throw new Error( - `Window '${openArgs.windowId}' doesn't exist in config. Is there a` + - `property for 'window/${openArgs.windowId}'?`, + try { + // TODO: Create new root if owner is null. + const owner = getOwner()!; + const config = await getUserConfig(); + const styleBuilder = getStyleBuilder(owner); + + const openArgs = + window.__ZEBAR_OPEN_ARGS ?? + (await getOpenWindowArgs(getCurrentWindow().label)); + + const windowConfig = (config as UserConfig)[ + `window/${openArgs.windowId}` as const + ]; + + if (!windowConfig) { + throw new Error( + `Window \`${openArgs.windowId}\` isn\'t defined in the config. ` + + `Is there a property for \`window/${openArgs.windowId}\`?`, + ); + } + + const globalConfig = GlobalConfigSchema.strip().parse( + (config as UserConfig)?.global ?? {}, ); - } - const globalConfig = GlobalConfigSchema.strip().parse( - (config as UserConfig)?.global ?? {}, - ); - - const windowContext = (await initElement({ - id: openArgs.windowId, - type: ElementType.WINDOW, - rawConfig: windowConfig, - globalConfig, - args: openArgs.args, - env: openArgs.env, - ancestorProviders: [], - owner, - })) as WindowContext; - - // Set global SCSS/CSS styles. - runWithOwner(owner, () => { - createEffect(() => { - if (windowContext.parsedConfig.global_styles) { - styleBuilder.setGlobalStyles( - windowContext.parsedConfig.global_styles, - ); - } + const windowContext = (await initElement({ + id: openArgs.windowId, + type: ElementType.WINDOW, + rawConfig: windowConfig, + globalConfig, + args: openArgs.args, + env: openArgs.env, + ancestorProviders: [], + owner, + })) as WindowContext; + + // Set global SCSS/CSS styles. + runWithOwner(owner, () => { + createEffect(() => { + if (windowContext.parsedConfig.global_styles) { + styleBuilder.setGlobalStyles( + windowContext.parsedConfig.global_styles, + ); + } + }); }); - }); - - // Set window position and apply window styles/effects. - runWithOwner(owner, () => { - createEffect(async () => { - // Create `styles` and `position` variables prior to awaiting, such that - // dependencies are tracked successfully within the effect. - const styles: Partial = { - zOrder: windowContext.parsedConfig.z_order, - showInTaskbar: windowContext.parsedConfig.show_in_taskbar, - resizable: windowContext.parsedConfig.resizable, - }; - - const position: Partial = { - x: windowContext.parsedConfig.position_x, - y: windowContext.parsedConfig.position_y, - width: windowContext.parsedConfig.width, - height: windowContext.parsedConfig.height, - }; - - await setWindowStyles(styles); - await setWindowPosition(position); + + // Set window position and apply window styles/effects. + runWithOwner(owner, () => { + createEffect(async () => { + // Create `styles` and `position` variables prior to awaiting, such that + // dependencies are tracked successfully within the effect. + const styles: Partial = { + zOrder: windowContext.parsedConfig.z_order, + showInTaskbar: windowContext.parsedConfig.show_in_taskbar, + resizable: windowContext.parsedConfig.resizable, + }; + + const position: Partial = { + x: windowContext.parsedConfig.position_x, + y: windowContext.parsedConfig.position_y, + width: windowContext.parsedConfig.width, + height: windowContext.parsedConfig.height, + }; + + await setWindowStyles(styles); + await setWindowPosition(position); + }); }); - }); - return windowContext; + return windowContext; + } catch (err) { + logger.error('Failed to initialize window:', err); + + let message = (err as Error)?.message + ? `Failed to initialize window.\n\n${(err as Error).message}` + : `Failed to initialize window.`; + + // Show error dialog and exit. + messageDialog(message, { title: 'Fatal error :(', type: 'error' }); + await getCurrentWindow().close(); + + throw err; + } } diff --git a/packages/client-api/src/user-config/get-user-config.ts b/packages/client-api/src/user-config/get-user-config.ts index f37486de..c4fc9c1c 100644 --- a/packages/client-api/src/user-config/get-user-config.ts +++ b/packages/client-api/src/user-config/get-user-config.ts @@ -1,5 +1,5 @@ import { createSignal } from 'solid-js'; -import { parse } from 'yaml'; +import { YAMLParseError, parse } from 'yaml'; import { formatConfigError } from './shared'; import { createLogger } from '~/utils'; @@ -29,6 +29,13 @@ export async function getUserConfig() { return configObj; } catch (err) { - throw formatConfigError(err); + console.log('err', err.constructor.name); + if (err instanceof YAMLParseError) { + err. + } + + throw new Error( + `Problem reading config file: ${(err as Error).message}`, + ); } } diff --git a/packages/client-api/src/utils/create-logger.ts b/packages/client-api/src/utils/create-logger.ts index 6bf0ccd8..4ccba99b 100644 --- a/packages/client-api/src/utils/create-logger.ts +++ b/packages/client-api/src/utils/create-logger.ts @@ -1,55 +1,44 @@ -enum LogLevel { - DEBUG, - INFO, - WARNING, - ERROR, - OFF, -} - type LogMethod = 'log' | 'warn' | 'error'; -// TODO: Get a minimum log level from environment. export function createLogger(section: string) { function log( consoleLogMethod: LogMethod, message: string, - ...data: any[] + ...data: unknown[] ) { const date = new Date(); - const timestamp = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}:${date.getMilliseconds()}`; + const timestamp = + `${date.getHours().toString().padStart(2, '0')}:` + + `${date.getMinutes().toString().padStart(2, '0')}:` + + `${date.getSeconds().toString().padStart(2, '0')}:` + + `${date.getMilliseconds().toString().padStart(3, '0')}`; // Clone data to avoid reference changes in Chrome console. - // TODO: This needs to be improved. - const clonedData = data.map(data => { - return data === null || data === undefined - ? data - : structuredClone(data); - }); + const clonedData = data.map(tryClone); console[consoleLogMethod]( - `${timestamp} [${section}] ${message}`, + `%c${timestamp}%c [${section}] %c${message}`, + 'color: #f5f9b4', + 'color: #d0b4f9', + 'color: inherit', ...clonedData, ); } - function debug(message: string, ...data: any) { - if (shouldLog(LogLevel.DEBUG)) log('log', message, ...data); + function debug(message: string, ...data: unknown[]) { + log('log', message, ...data); } - function info(message: string, ...data: any) { - if (shouldLog(LogLevel.INFO)) log('log', message, ...data); + function info(message: string, ...data: unknown[]) { + log('log', message, ...data); } - function warn(message: string, ...data: any) { - if (shouldLog(LogLevel.WARNING)) log('warn', message, ...data); + function warn(message: string, ...data: unknown[]) { + log('warn', message, ...data); } - function error(message: string, ...data: any) { - if (shouldLog(LogLevel.ERROR)) log('error', message, ...data); - } - - function shouldLog(logLevel: LogLevel): boolean { - return true; + function error(message: string, ...data: unknown[]) { + log('error', message, ...data); } return { @@ -59,3 +48,16 @@ export function createLogger(section: string) { error, }; } + +function tryClone(data: unknown) { + if (data === null || data === undefined || data instanceof Error) { + return data; + } + + try { + return structuredClone(data); + } catch (err) { + console.warn('Unable to clone data'); + return data; + } +} diff --git a/packages/desktop/Cargo.lock b/packages/desktop/Cargo.lock index 99c8b54b..5dae0284 100644 --- a/packages/desktop/Cargo.lock +++ b/packages/desktop/Cargo.lock @@ -239,7 +239,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4af014b17dd80e8af9fa689b2d4a211ddba6eb583c1622f35d0cb543f6b17e4" dependencies = [ "atk-sys", - "glib", + "glib 0.18.3", "libc", ] @@ -249,8 +249,8 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009" dependencies = [ - "glib-sys", - "gobject-sys", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "libc", "system-deps", ] @@ -389,7 +389,7 @@ checksum = "f33613627f0dea6a731b0605101fad59ba4f193a52c96c4687728d822605a8a1" dependencies = [ "bitflags 2.4.1", "cairo-sys-rs", - "glib", + "glib 0.18.3", "libc", "once_cell", "thiserror", @@ -401,7 +401,7 @@ version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" dependencies = [ - "glib-sys", + "glib-sys 0.18.1", "libc", "system-deps", ] @@ -833,6 +833,7 @@ dependencies = [ "sysinfo", "tauri", "tauri-build", + "tauri-plugin-dialog", "tauri-plugin-http", "tauri-plugin-shell", "tauri-plugin-single-instance", @@ -1198,7 +1199,7 @@ dependencies = [ "gdk-pixbuf", "gdk-sys", "gio", - "glib", + "glib 0.18.3", "libc", "pango", ] @@ -1211,7 +1212,7 @@ checksum = "446f32b74d22c33b7b258d4af4ffde53c2bf96ca2e29abdf1a785fe59bd6c82c" dependencies = [ "gdk-pixbuf-sys", "gio", - "glib", + "glib 0.18.3", "libc", "once_cell", ] @@ -1222,9 +1223,9 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.18.1", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "libc", "system-deps", ] @@ -1237,9 +1238,9 @@ checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.18.1", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "libc", "pango-sys", "pkg-config", @@ -1253,8 +1254,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a90fbf5c033c65d93792192a49a8efb5bb1e640c419682a58bb96f5ae77f3d4a" dependencies = [ "gdk-sys", - "glib-sys", - "gobject-sys", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "libc", "pkg-config", "system-deps", @@ -1267,7 +1268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fee8f00f4ee46cad2939b8990f5c70c94ff882c3028f3cc5abf950fa4ab53043" dependencies = [ "gdk-sys", - "glib-sys", + "glib-sys 0.18.1", "libc", "system-deps", "x11", @@ -1334,8 +1335,8 @@ dependencies = [ "futures-core", "futures-io", "futures-util", - "gio-sys", - "glib", + "gio-sys 0.18.1", + "glib 0.18.3", "libc", "once_cell", "pin-project-lite", @@ -1343,19 +1344,54 @@ dependencies = [ "thiserror", ] +[[package]] +name = "gio-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229" +dependencies = [ + "glib-sys 0.16.3", + "gobject-sys 0.16.3", + "libc", + "system-deps", + "winapi", +] + [[package]] name = "gio-sys" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" dependencies = [ - "glib-sys", - "gobject-sys", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "libc", "system-deps", "winapi", ] +[[package]] +name = "glib" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16aa2475c9debed5a32832cb5ff2af5a3f9e1ab9e69df58eaadc1ab2004d6eba" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys 0.16.3", + "glib-macros 0.16.8", + "glib-sys 0.16.3", + "gobject-sys 0.16.3", + "libc", + "once_cell", + "smallvec", + "thiserror", +] + [[package]] name = "glib" version = "0.18.3" @@ -1368,10 +1404,10 @@ dependencies = [ "futures-executor", "futures-task", "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", + "gio-sys 0.18.1", + "glib-macros 0.18.3", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "libc", "memchr", "once_cell", @@ -1379,6 +1415,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "glib-macros" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1a9325847aa46f1e96ffea37611b9d51fc4827e67f79e7de502a297560a67b" +dependencies = [ + "anyhow", + "heck", + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "glib-macros" version = "0.18.3" @@ -1393,6 +1444,16 @@ dependencies = [ "syn 2.0.32", ] +[[package]] +name = "glib-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65" +dependencies = [ + "libc", + "system-deps", +] + [[package]] name = "glib-sys" version = "0.18.1" @@ -1409,13 +1470,24 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gobject-sys" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1" +dependencies = [ + "glib-sys 0.16.3", + "libc", + "system-deps", +] + [[package]] name = "gobject-sys" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" dependencies = [ - "glib-sys", + "glib-sys 0.18.1", "libc", "system-deps", ] @@ -1433,7 +1505,7 @@ dependencies = [ "gdk", "gdk-pixbuf", "gio", - "glib", + "glib 0.18.3", "gtk-sys", "gtk3-macros", "libc", @@ -1451,9 +1523,9 @@ dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.18.1", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "libc", "pango-sys", "system-deps", @@ -1762,7 +1834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" dependencies = [ "bitflags 1.3.2", - "glib", + "glib 0.18.3", "javascriptcore-rs-sys", ] @@ -1772,8 +1844,8 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" dependencies = [ - "glib-sys", - "gobject-sys", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "libc", "system-deps", ] @@ -1863,7 +1935,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" dependencies = [ - "glib", + "glib 0.18.3", "gtk", "gtk-sys", "libappindicator-sys", @@ -2226,6 +2298,17 @@ dependencies = [ "objc_exception", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc_exception" version = "0.1.2" @@ -2347,7 +2430,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" dependencies = [ "gio", - "glib", + "glib 0.18.3", "libc", "once_cell", "pango-sys", @@ -2359,8 +2442,8 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" dependencies = [ - "glib-sys", - "gobject-sys", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "libc", "system-deps", ] @@ -2897,6 +2980,29 @@ dependencies = [ "winreg 0.50.0", ] +[[package]] +name = "rfd" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9e7b57df6e8472152674607f6cc68aa14a748a3157a857a94f516e11aeacc2" +dependencies = [ + "block", + "dispatch", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", + "gtk-sys", + "js-sys", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.48.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -3261,7 +3367,7 @@ checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" dependencies = [ "futures-channel", "gio", - "glib", + "glib 0.18.3", "libc", "soup3-sys", ] @@ -3272,9 +3378,9 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.18.1", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "libc", "system-deps", ] @@ -3451,8 +3557,8 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gio", - "glib", - "glib-sys", + "glib 0.18.3", + "glib-sys 0.18.1", "gtk", "image", "instant", @@ -3606,6 +3712,24 @@ dependencies = [ "tauri-utils", ] +[[package]] +name = "tauri-plugin-dialog" +version = "2.0.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba998ef0f268e5a5fbc36629f71dc2f4e069812a89eaa1e7a826084ffd6abc2a" +dependencies = [ + "glib 0.16.9", + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-fs", + "thiserror", +] + [[package]] name = "tauri-plugin-fs" version = "2.0.0-alpha.4" @@ -4344,10 +4468,10 @@ dependencies = [ "gdk", "gdk-sys", "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", + "gio-sys 0.18.1", + "glib 0.18.3", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "gtk", "gtk-sys", "javascriptcore-rs", @@ -4366,9 +4490,9 @@ dependencies = [ "bitflags 1.3.2", "cairo-sys-rs", "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", + "gio-sys 0.18.1", + "glib-sys 0.18.1", + "gobject-sys 0.18.0", "gtk-sys", "javascriptcore-rs-sys", "libc", diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 39a62472..c2108b8e 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -18,6 +18,7 @@ async-trait = "0.1.74" clap = { version = "4.4.8", features = ["derive"] } reqwest = { version = "0.11.22", features = ["json"] } tauri = { version = "2.0.0-alpha.17", features = ["macos-private-api"] } +tauri-plugin-dialog = "2.0.0-alpha.0" tauri-plugin-http = "2.0.0-alpha.5" tauri-plugin-shell = "2.0.0-alpha.4" tauri-plugin-single-instance = "2.0.0-alpha.4" diff --git a/packages/desktop/src/main.rs b/packages/desktop/src/main.rs index e0a991b8..ea1a8375 100644 --- a/packages/desktop/src/main.rs +++ b/packages/desktop/src/main.rs @@ -140,6 +140,7 @@ async fn main() { app.handle().plugin(tauri_plugin_shell::init())?; app.handle().plugin(tauri_plugin_http::init())?; + app.handle().plugin(tauri_plugin_dialog::init())?; providers::manager::init(app)?; @@ -149,6 +150,11 @@ async fn main() { let app_handle = app.handle().clone(); + // Prevent the app icon from showing up in the dock on MacOS. + // TODO: Enable once https://github.com/tauri-apps/tauri/pull/8713 is released. + // #[cfg(target_os = "macos")] + // app.set_activation_policy(ActivationPolicy::Accessory); + // Handle creation of new windows (both from the initial and // subsequent instances of the application) _ = task::spawn(async move { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ddac753..490a3328 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,9 @@ importers: '@tauri-apps/api': specifier: 2.0.0-alpha.13 version: 2.0.0-alpha.13 + '@tauri-apps/plugin-dialog': + specifier: 2.0.0-alpha.5 + version: 2.0.0-alpha.5 '@tauri-apps/plugin-shell': specifier: 2.0.0-alpha.3 version: 2.0.0-alpha.3 @@ -1198,6 +1201,12 @@ packages: '@tauri-apps/cli-win32-x64-msvc': 2.0.0-alpha.17 dev: true + /@tauri-apps/plugin-dialog@2.0.0-alpha.5: + resolution: {integrity: sha512-OHodi10R5an4nVq/kavU9HOATkc20RYNVdFYWMvPSVyI7rL1n55llPmbGXdWiZO94Tn10XZyZDVs7eJ1uKOkeA==} + dependencies: + '@tauri-apps/api': 2.0.0-alpha.13 + dev: false + /@tauri-apps/plugin-shell@2.0.0-alpha.3: resolution: {integrity: sha512-v4qLTE9DfzpYdcjZq3ASOC7gFtMtzIDWN5Bo2FheB+pHQvDRq8mrP8gTdkL4/9pQaOFlB0B+MKCwANYhD1jLEQ==} dependencies: From 7c2edd17b1438a2933a514156d232c6f2a6fc5b2 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sat, 17 Feb 2024 15:33:25 +0800 Subject: [PATCH 2/3] feat: format zod parsing errors --- packages/client-api/src/init-element.ts | 17 +++++------- packages/client-api/src/init-window.ts | 15 ++++++----- .../src/providers/get-element-providers.ts | 5 ++-- .../user-config/get-parsed-element-config.ts | 3 ++- .../src/user-config/get-user-config.ts | 5 ---- packages/client-api/src/user-config/index.ts | 1 + .../src/user-config/parse-with-schema.ts | 26 +++++++++++++++++++ 7 files changed, 47 insertions(+), 25 deletions(-) create mode 100644 packages/client-api/src/user-config/parse-with-schema.ts diff --git a/packages/client-api/src/init-element.ts b/packages/client-api/src/init-element.ts index b8209837..4fdeac6b 100644 --- a/packages/client-api/src/init-element.ts +++ b/packages/client-api/src/init-element.ts @@ -5,14 +5,13 @@ import { runWithOwner, } from 'solid-js'; import { createStore } from 'solid-js/store'; -import { message } from '@tauri-apps/plugin-dialog'; +import { message as messageDialog } from '@tauri-apps/plugin-dialog'; import { getStyleBuilder, getParsedElementConfig, getChildConfigs, type GlobalConfig, - TemplatePropertyError, } from './user-config'; import { getElementProviders } from './providers'; import type { ElementContext } from './element-context.model'; @@ -107,17 +106,13 @@ export async function initElement( return elementContext as ElementContext; } catch (err) { - logger.error( - 'Error initializing window:', - //@ts-ignore - Object.keys(err), - err instanceof TemplatePropertyError, - err, - ); - message((err as Error).message, { - title: 'Encountered an error!', + logger.error('Failed to initialize element:', err); + + messageDialog((err as Error)?.message ?? 'Unknown reason.', { + title: 'Failed to initialize element!', type: 'error', }); + throw err; } } diff --git a/packages/client-api/src/init-window.ts b/packages/client-api/src/init-window.ts index b1e3eb37..f472566c 100644 --- a/packages/client-api/src/init-window.ts +++ b/packages/client-api/src/init-window.ts @@ -7,6 +7,7 @@ import { type UserConfig, getUserConfig, getStyleBuilder, + parseWithSchema, } from './user-config'; import { getOpenWindowArgs, @@ -57,7 +58,8 @@ export async function initWindowAsync(): Promise { ); } - const globalConfig = GlobalConfigSchema.strip().parse( + const globalConfig = parseWithSchema( + GlobalConfigSchema.strip(), (config as UserConfig)?.global ?? {}, ); @@ -110,12 +112,13 @@ export async function initWindowAsync(): Promise { } catch (err) { logger.error('Failed to initialize window:', err); - let message = (err as Error)?.message - ? `Failed to initialize window.\n\n${(err as Error).message}` - : `Failed to initialize window.`; + messageDialog((err as Error)?.message ?? 'Unknown reason.', { + title: 'Failed to initialize window!', + type: 'error', + }); - // Show error dialog and exit. - messageDialog(message, { title: 'Fatal error :(', type: 'error' }); + // Error during window initialization is unrecoverable, so we close + // the window. await getCurrentWindow().close(); throw err; diff --git a/packages/client-api/src/providers/get-element-providers.ts b/packages/client-api/src/providers/get-element-providers.ts index 81888b86..d06bedfe 100644 --- a/packages/client-api/src/providers/get-element-providers.ts +++ b/packages/client-api/src/providers/get-element-providers.ts @@ -7,7 +7,7 @@ import { } from 'solid-js'; import { createStore } from 'solid-js/store'; -import { ProvidersConfigSchema } from '~/user-config'; +import { ProvidersConfigSchema, parseWithSchema } from '~/user-config'; import type { ElementContext } from '~/element-context.model'; import { createProvider } from './create-provider'; import type { PickPartial } from '~/utils'; @@ -35,7 +35,8 @@ export async function getElementProviders( * Get map of element providers. */ async function getElementProviders() { - const providerConfigs = ProvidersConfigSchema.parse( + const providerConfigs = parseWithSchema( + ProvidersConfigSchema, (elementContext.rawConfig as Record)?.providers ?? [], ); diff --git a/packages/client-api/src/user-config/get-parsed-element-config.ts b/packages/client-api/src/user-config/get-parsed-element-config.ts index 39d038f7..602b1de8 100644 --- a/packages/client-api/src/user-config/get-parsed-element-config.ts +++ b/packages/client-api/src/user-config/get-parsed-element-config.ts @@ -9,6 +9,7 @@ import { TemplateConfigSchema, WindowConfigSchemaP1, TemplatePropertyError, + parseWithSchema, } from '~/user-config'; import type { PickPartial } from '~/utils'; @@ -66,7 +67,7 @@ export function getParsedElementConfig( // TODO: Add logging for updated config here. const newConfig = Object.fromEntries(newConfigEntries); - return schema.parse(newConfig); + return parseWithSchema(schema, newConfig); } return parsedConfig; diff --git a/packages/client-api/src/user-config/get-user-config.ts b/packages/client-api/src/user-config/get-user-config.ts index c4fc9c1c..214dd53a 100644 --- a/packages/client-api/src/user-config/get-user-config.ts +++ b/packages/client-api/src/user-config/get-user-config.ts @@ -29,11 +29,6 @@ export async function getUserConfig() { return configObj; } catch (err) { - console.log('err', err.constructor.name); - if (err instanceof YAMLParseError) { - err. - } - throw new Error( `Problem reading config file: ${(err as Error).message}`, ); diff --git a/packages/client-api/src/user-config/index.ts b/packages/client-api/src/user-config/index.ts index c0ce25dd..b7eb4e9e 100644 --- a/packages/client-api/src/user-config/index.ts +++ b/packages/client-api/src/user-config/index.ts @@ -2,6 +2,7 @@ export * from './get-parsed-element-config'; export * from './get-style-builder'; export * from './get-user-config'; export * from './global-config.model'; +export * from './parse-with-schema'; export * from './shared'; export * from './user-config.model'; export * from './window'; diff --git a/packages/client-api/src/user-config/parse-with-schema.ts b/packages/client-api/src/user-config/parse-with-schema.ts new file mode 100644 index 00000000..368f1252 --- /dev/null +++ b/packages/client-api/src/user-config/parse-with-schema.ts @@ -0,0 +1,26 @@ +import { ZodError, type z } from 'zod'; + +/** + * Parse a value with error formatting. + */ +export function parseWithSchema( + schema: T, + value: unknown, +): z.infer { + try { + return schema.parse(value); + } catch (err) { + if (err instanceof ZodError && err.errors.length) { + const [firstError] = err.errors; + const { message, path } = firstError!; + const fullPath = path.join('.'); + + throw new Error( + `Property '${fullPath}' in config isn't valid.\n` + + `⚠️ ${message}`, + ); + } + + throw new Error('Failed to parse config.'); + } +} From 31442a144ba3aa4c3f87ef77846747555e6162b3 Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Sat, 17 Feb 2024 15:49:07 +0800 Subject: [PATCH 3/3] feat: format errors during template parsing --- packages/client-api/src/init-element.ts | 13 ++++--- packages/client-api/src/init-window.ts | 2 +- .../user-config/get-parsed-element-config.ts | 22 ++++++------ .../src/user-config/get-user-config.ts | 5 ++- .../src/user-config/parse-with-schema.ts | 2 +- .../user-config/shared/format-config-error.ts | 34 ------------------- .../src/user-config/shared/index.ts | 2 -- .../shared/template-property-error.ts | 17 ---------- 8 files changed, 24 insertions(+), 73 deletions(-) delete mode 100644 packages/client-api/src/user-config/shared/format-config-error.ts delete mode 100644 packages/client-api/src/user-config/shared/template-property-error.ts diff --git a/packages/client-api/src/init-element.ts b/packages/client-api/src/init-element.ts index 4fdeac6b..14e5b575 100644 --- a/packages/client-api/src/init-element.ts +++ b/packages/client-api/src/init-element.ts @@ -106,12 +106,15 @@ export async function initElement( return elementContext as ElementContext; } catch (err) { - logger.error('Failed to initialize element:', err); + // Let error immediately bubble up if element is a window. + if (args.type !== ElementType.WINDOW) { + logger.error('Failed to initialize element:', err); - messageDialog((err as Error)?.message ?? 'Unknown reason.', { - title: 'Failed to initialize element!', - type: 'error', - }); + await messageDialog((err as Error)?.message ?? 'Unknown reason.', { + title: 'Failed to initialize element!', + type: 'error', + }); + } throw err; } diff --git a/packages/client-api/src/init-window.ts b/packages/client-api/src/init-window.ts index f472566c..56435505 100644 --- a/packages/client-api/src/init-window.ts +++ b/packages/client-api/src/init-window.ts @@ -112,7 +112,7 @@ export async function initWindowAsync(): Promise { } catch (err) { logger.error('Failed to initialize window:', err); - messageDialog((err as Error)?.message ?? 'Unknown reason.', { + await messageDialog((err as Error)?.message ?? 'Unknown reason.', { title: 'Failed to initialize window!', type: 'error', }); diff --git a/packages/client-api/src/user-config/get-parsed-element-config.ts b/packages/client-api/src/user-config/get-parsed-element-config.ts index 602b1de8..3da156bc 100644 --- a/packages/client-api/src/user-config/get-parsed-element-config.ts +++ b/packages/client-api/src/user-config/get-parsed-element-config.ts @@ -8,7 +8,6 @@ import { GroupConfigSchemaP1, TemplateConfigSchema, WindowConfigSchemaP1, - TemplatePropertyError, parseWithSchema, } from '~/user-config'; import type { PickPartial } from '~/utils'; @@ -52,15 +51,18 @@ export function getParsedElementConfig( return [key, rendered]; } catch (err) { - // Re-throw error as `TemplatePropertyError`. - throw err instanceof TemplateError - ? new TemplatePropertyError( - err.message, - key, - value, - err.templateIndex, - ) - : err; + if (!(err instanceof TemplateError)) { + throw err; + } + + const { message, templateIndex } = err; + + throw new Error( + `Property '${key}' in config isn't valid.\n\n` + + 'Syntax error at:\n' + + `...${value.slice(templateIndex - 30, templateIndex)} << \n\n` + + `⚠️ ${message}`, + ); } }); diff --git a/packages/client-api/src/user-config/get-user-config.ts b/packages/client-api/src/user-config/get-user-config.ts index 214dd53a..49cec245 100644 --- a/packages/client-api/src/user-config/get-user-config.ts +++ b/packages/client-api/src/user-config/get-user-config.ts @@ -1,7 +1,6 @@ import { createSignal } from 'solid-js'; -import { YAMLParseError, parse } from 'yaml'; +import { parse } from 'yaml'; -import { formatConfigError } from './shared'; import { createLogger } from '~/utils'; import { readConfigFile } from '~/desktop'; @@ -30,7 +29,7 @@ export async function getUserConfig() { return configObj; } catch (err) { throw new Error( - `Problem reading config file: ${(err as Error).message}`, + `Problem reading config file. ${(err as Error).message}`, ); } } diff --git a/packages/client-api/src/user-config/parse-with-schema.ts b/packages/client-api/src/user-config/parse-with-schema.ts index 368f1252..b9f5e99c 100644 --- a/packages/client-api/src/user-config/parse-with-schema.ts +++ b/packages/client-api/src/user-config/parse-with-schema.ts @@ -21,6 +21,6 @@ export function parseWithSchema( ); } - throw new Error('Failed to parse config.'); + throw new Error(`Failed to parse config. ${(err as Error).message}`); } } diff --git a/packages/client-api/src/user-config/shared/format-config-error.ts b/packages/client-api/src/user-config/shared/format-config-error.ts deleted file mode 100644 index c78f75c1..00000000 --- a/packages/client-api/src/user-config/shared/format-config-error.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ZodError } from 'zod'; - -import { TemplatePropertyError } from './template-property-error'; - -export function formatConfigError(err: unknown) { - if (!(err instanceof Error)) { - return new Error('Problem reading config file.'); - } - - if (err instanceof ZodError && err.errors.length) { - const [firstError] = err.errors; - const { message, path } = firstError!; - const fullPath = path.join('.'); - - return new Error( - `Property '${fullPath}' in config isn't valid.\n` + `⚠️ ${message}`, - ); - } - - if (err instanceof TemplatePropertyError) { - const { message, path, template, templateIndex } = err; - - return new Error( - `Property '${path}' in config isn't valid.\n\n` + - 'Syntax error at:\n' + - `...${template.slice(templateIndex - 30, templateIndex)} << \n\n` + - `⚠️ ${message}`, - ); - } - - return new Error( - `Problem reading config file: ${(err as Error).message}.`, - ); -} diff --git a/packages/client-api/src/user-config/shared/index.ts b/packages/client-api/src/user-config/shared/index.ts index 6658db36..7bb6ea76 100644 --- a/packages/client-api/src/user-config/shared/index.ts +++ b/packages/client-api/src/user-config/shared/index.ts @@ -1,5 +1,3 @@ export * from './boolean-like.model'; -export * from './format-config-error'; export * from './get-child-configs'; -export * from './template-property-error'; export * from './with-dynamic-key'; diff --git a/packages/client-api/src/user-config/shared/template-property-error.ts b/packages/client-api/src/user-config/shared/template-property-error.ts deleted file mode 100644 index bc7e2a87..00000000 --- a/packages/client-api/src/user-config/shared/template-property-error.ts +++ /dev/null @@ -1,17 +0,0 @@ -export class TemplatePropertyError extends Error { - public path: string; - public template: string; - public templateIndex: number; - - constructor( - message: string, - path: string, - template: string, - templateIndex: number, - ) { - super(message); - this.path = path; - this.template = template; - this.templateIndex = templateIndex; - } -}