From 1f52a243162c5b465c1ce4337c8aa42600d369b6 Mon Sep 17 00:00:00 2001 From: thearturca Date: Wed, 14 Aug 2024 09:35:47 +0300 Subject: [PATCH 1/2] feat(providers): added custom provider support --- .../src/providers/create-provider.ts | 3 + .../custom/create-custom-provider.ts | 78 +++++++++++++++++++ packages/client-api/src/providers/index.ts | 1 + .../src/user-config/get-script-manager.ts | 4 +- .../src/user-config/shared/fn-path.ts | 8 ++ .../src/user-config/shared/index.ts | 1 + .../window/element-events-config.model.ts | 8 +- .../window/provider-config.model.ts | 2 + .../user-config/window/provider-type.model.ts | 1 + .../providers/custom-provider.config.model.ts | 16 ++++ .../src/user-config/window/providers/index.ts | 1 + 11 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 packages/client-api/src/providers/custom/create-custom-provider.ts create mode 100644 packages/client-api/src/user-config/shared/fn-path.ts create mode 100644 packages/client-api/src/user-config/window/providers/custom-provider.config.model.ts diff --git a/packages/client-api/src/providers/create-provider.ts b/packages/client-api/src/providers/create-provider.ts index 5b6ec097..4ba1ce1c 100644 --- a/packages/client-api/src/providers/create-provider.ts +++ b/packages/client-api/src/providers/create-provider.ts @@ -2,6 +2,7 @@ import type { Owner } from 'solid-js'; import { createBatteryProvider } from './battery/create-battery-provider'; import { createCpuProvider } from './cpu/create-cpu-provider'; +import { createCustomProvider } from './custom/create-custom-provider'; import { createDateProvider } from './date/create-date-provider'; import { createGlazeWmProvider } from './glazewm/create-glazewm-provider'; import { createHostProvider } from './host/create-host-provider'; @@ -30,6 +31,8 @@ export async function createProvider( return createBatteryProvider(config, owner); case ProviderType.CPU: return createCpuProvider(config, owner); + case ProviderType.CUSTOM: + return createCustomProvider(config, elementContext, owner); case ProviderType.DATE: return createDateProvider(config, owner); case ProviderType.GLAZEWM: diff --git a/packages/client-api/src/providers/custom/create-custom-provider.ts b/packages/client-api/src/providers/custom/create-custom-provider.ts new file mode 100644 index 00000000..13544ca0 --- /dev/null +++ b/packages/client-api/src/providers/custom/create-custom-provider.ts @@ -0,0 +1,78 @@ +import { + createSignal, + onCleanup, + runWithOwner, + type Owner, +} from 'solid-js'; + +import type { ElementContext } from '~/index'; +import { + getScriptManager, + type CustomProviderConfig, +} from '~/user-config'; +import type { PickPartial } from '~/utils'; + +export interface CustomState { + state: unknown; +} + +export async function createCustomProvider( + config: CustomProviderConfig, + elementContext: PickPartial< + ElementContext, + 'parsedConfig' | 'providers' + >, + owner: Owner, +): Promise { + const scriptManager = getScriptManager(); + + if (config.start_fn_path) + await scriptManager.callFn( + config.start_fn_path, + new Event('custom'), + elementContext, + ); + + const [state, setState] = createSignal(); + + // run refresh fn first time to set initial state + setState( + await scriptManager.callFn( + config.refresh_fn_path, + new Event('custom'), + elementContext, + ), + ); + + // and then every refresh interval + const interval = setInterval( + async () => + setState( + await scriptManager.callFn( + config.refresh_fn_path, + new Event('custom'), + elementContext, + ), + ), + config.refresh_interval, + ); + + runWithOwner(owner, () => { + onCleanup(async () => { + clearInterval(interval); + + if (config.stop_fn_path) + await scriptManager.callFn( + config.stop_fn_path, + new Event('custom'), + elementContext, + ); + }); + }); + + return { + get state() { + return state(); + }, + }; +} diff --git a/packages/client-api/src/providers/index.ts b/packages/client-api/src/providers/index.ts index d0106b1c..1cda0cf1 100644 --- a/packages/client-api/src/providers/index.ts +++ b/packages/client-api/src/providers/index.ts @@ -1,5 +1,6 @@ export * from './battery/create-battery-provider'; export * from './cpu/create-cpu-provider'; +export * from './custom/create-custom-provider'; export * from './date/create-date-provider'; export * from './glazewm/create-glazewm-provider'; export * from './ip/create-ip-provider'; diff --git a/packages/client-api/src/user-config/get-script-manager.ts b/packages/client-api/src/user-config/get-script-manager.ts index 33019e87..92df5364 100644 --- a/packages/client-api/src/user-config/get-script-manager.ts +++ b/packages/client-api/src/user-config/get-script-manager.ts @@ -1,7 +1,7 @@ import { convertFileSrc } from '@tauri-apps/api/core'; import { join, homeDir } from '@tauri-apps/api/path'; -import { createLogger } from '~/utils'; +import { createLogger, type PickPartial } from '~/utils'; import type { ElementContext } from '../element-context.model'; const logger = createLogger('script-manager'); @@ -34,7 +34,7 @@ async function loadScriptForFn(fnPath: string): Promise { async function callFn( fnPath: string, event: Event, - context: ElementContext, + context: PickPartial, ): Promise { const { modulePath, functionName } = parseFnPath(fnPath); const foundModule = await resolveModule(modulePath); diff --git a/packages/client-api/src/user-config/shared/fn-path.ts b/packages/client-api/src/user-config/shared/fn-path.ts new file mode 100644 index 00000000..71959c31 --- /dev/null +++ b/packages/client-api/src/user-config/shared/fn-path.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; + +export const FnPathSchema = z + .string() + .regex( + /^(.+)#([a-zA-Z_$][a-zA-Z0-9_$]*)$/, + "Invalid function path. Needs to be in format 'path/to/my-script.js#functionName'.", + ); diff --git a/packages/client-api/src/user-config/shared/index.ts b/packages/client-api/src/user-config/shared/index.ts index 7bb6ea76..e2c86901 100644 --- a/packages/client-api/src/user-config/shared/index.ts +++ b/packages/client-api/src/user-config/shared/index.ts @@ -1,3 +1,4 @@ export * from './boolean-like.model'; +export * from './fn-path'; export * from './get-child-configs'; export * from './with-dynamic-key'; diff --git a/packages/client-api/src/user-config/window/element-events-config.model.ts b/packages/client-api/src/user-config/window/element-events-config.model.ts index bcd6bef2..ea3d6772 100644 --- a/packages/client-api/src/user-config/window/element-events-config.model.ts +++ b/packages/client-api/src/user-config/window/element-events-config.model.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { FnPathSchema } from '../shared'; import type { Prettify } from '~/utils'; /** @@ -108,12 +109,7 @@ export const ElementEventsConfigSchema = z .array( z.object({ type: z.enum(HTML_EVENTS), - fn_path: z - .string() - .regex( - /^(.+)#([a-zA-Z_$][a-zA-Z0-9_$]*)$/, - "Invalid function path. Needs to be in format 'path/to/my-script.js#functionName'.", - ), + fn_path: FnPathSchema, selector: z.string().optional(), }), ) diff --git a/packages/client-api/src/user-config/window/provider-config.model.ts b/packages/client-api/src/user-config/window/provider-config.model.ts index 7adfbaa0..7f8158e9 100644 --- a/packages/client-api/src/user-config/window/provider-config.model.ts +++ b/packages/client-api/src/user-config/window/provider-config.model.ts @@ -4,6 +4,7 @@ import type { Prettify } from '~/utils'; import { BatteryProviderConfigSchema, CpuProviderConfigSchema, + CustomProviderConfigSchema, DateProviderConfigSchema, GlazeWmProviderConfigSchema, HostProviderConfigSchema, @@ -20,6 +21,7 @@ import { export const ProviderConfigSchema = z.union([ BatteryProviderConfigSchema, CpuProviderConfigSchema, + CustomProviderConfigSchema, DateProviderConfigSchema, GlazeWmProviderConfigSchema, HostProviderConfigSchema, diff --git a/packages/client-api/src/user-config/window/provider-type.model.ts b/packages/client-api/src/user-config/window/provider-type.model.ts index 6ea9c221..62e63a34 100644 --- a/packages/client-api/src/user-config/window/provider-type.model.ts +++ b/packages/client-api/src/user-config/window/provider-type.model.ts @@ -3,6 +3,7 @@ import { z } from 'zod'; export enum ProviderType { BATTERY = 'battery', CPU = 'cpu', + CUSTOM = 'custom', DATE = 'date', GLAZEWM = 'glazewm', HOST = 'host', diff --git a/packages/client-api/src/user-config/window/providers/custom-provider.config.model.ts b/packages/client-api/src/user-config/window/providers/custom-provider.config.model.ts new file mode 100644 index 00000000..006697d3 --- /dev/null +++ b/packages/client-api/src/user-config/window/providers/custom-provider.config.model.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; + +import { ProviderType } from '../provider-type.model'; +import { FnPathSchema } from '~/user-config/shared'; + +export const CustomProviderConfigSchema = z.object({ + type: z.literal(ProviderType.CUSTOM), + refresh_interval: z.coerce.number().default(5 * 1000), + start_fn_path: FnPathSchema.optional(), + refresh_fn_path: FnPathSchema, + stop_fn_path: FnPathSchema.optional(), +}); + +export type CustomProviderConfig = z.infer< + typeof CustomProviderConfigSchema +>; diff --git a/packages/client-api/src/user-config/window/providers/index.ts b/packages/client-api/src/user-config/window/providers/index.ts index caca0198..1fbddf49 100644 --- a/packages/client-api/src/user-config/window/providers/index.ts +++ b/packages/client-api/src/user-config/window/providers/index.ts @@ -1,5 +1,6 @@ export * from './battery-provider-config.model'; export * from './cpu-provider-config.model'; +export * from './custom-provider.config.model'; export * from './date-provider-config.model'; export * from './glazewm-provider-config.model'; export * from './host-provider-config.model'; From 9e2d4e092c3350e90d1e0a83223b2889f08222c1 Mon Sep 17 00:00:00 2001 From: thearturca Date: Wed, 14 Aug 2024 10:30:04 +0300 Subject: [PATCH 2/2] feat(providers): added events based custom provider for realtime data --- .../custom/create-custom-provider.ts | 93 +++++++++++++------ .../providers/custom-provider.config.model.ts | 22 +++-- packages/client-api/src/utils/index.ts | 1 + .../client-api/src/utils/is-event-target.ts | 3 + 4 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 packages/client-api/src/utils/is-event-target.ts diff --git a/packages/client-api/src/providers/custom/create-custom-provider.ts b/packages/client-api/src/providers/custom/create-custom-provider.ts index 13544ca0..d23cf854 100644 --- a/packages/client-api/src/providers/custom/create-custom-provider.ts +++ b/packages/client-api/src/providers/custom/create-custom-provider.ts @@ -1,6 +1,7 @@ import { createSignal, onCleanup, + onMount, runWithOwner, type Owner, } from 'solid-js'; @@ -10,7 +11,7 @@ import { getScriptManager, type CustomProviderConfig, } from '~/user-config'; -import type { PickPartial } from '~/utils'; +import { isEventTarget, type PickPartial } from '~/utils'; export interface CustomState { state: unknown; @@ -24,6 +25,7 @@ export async function createCustomProvider( >, owner: Owner, ): Promise { + const [state, setState] = createSignal(); const scriptManager = getScriptManager(); if (config.start_fn_path) @@ -33,42 +35,73 @@ export async function createCustomProvider( elementContext, ); - const [state, setState] = createSignal(); - - // run refresh fn first time to set initial state - setState( - await scriptManager.callFn( - config.refresh_fn_path, + if ('emitter_fn_path' in config) { + const eventTarget = await scriptManager.callFn( + config.emitter_fn_path, new Event('custom'), elementContext, - ), - ); + ); - // and then every refresh interval - const interval = setInterval( - async () => - setState( - await scriptManager.callFn( - config.refresh_fn_path, - new Event('custom'), - elementContext, - ), + if (!isEventTarget(eventTarget)) + throw new TypeError(`Emitter function must return an event target.`); + + const listener = (event: Event) => { + if (event.type !== 'value') return; + + if (!('value' in event)) return; + + setState(event.value); + }; + + runWithOwner(owner, () => { + onMount(() => eventTarget.addEventListener('value', listener)); + onCleanup(async () => { + eventTarget.removeEventListener('value', listener); + + if (config.stop_fn_path) + await scriptManager.callFn( + config.stop_fn_path, + new Event('custom'), + elementContext, + ); + }); + }); + } else { + // run refresh fn first time to set initial state + setState( + await scriptManager.callFn( + config.refresh_fn_path, + new Event('custom'), + elementContext, ), - config.refresh_interval, - ); + ); + + // and then every refresh interval + const interval = setInterval( + async () => + setState( + await scriptManager.callFn( + config.refresh_fn_path, + new Event('custom'), + elementContext, + ), + ), + config.refresh_interval, + ); - runWithOwner(owner, () => { - onCleanup(async () => { - clearInterval(interval); + runWithOwner(owner, () => { + onCleanup(async () => { + clearInterval(interval); - if (config.stop_fn_path) - await scriptManager.callFn( - config.stop_fn_path, - new Event('custom'), - elementContext, - ); + if (config.stop_fn_path) + await scriptManager.callFn( + config.stop_fn_path, + new Event('custom'), + elementContext, + ); + }); }); - }); + } return { get state() { diff --git a/packages/client-api/src/user-config/window/providers/custom-provider.config.model.ts b/packages/client-api/src/user-config/window/providers/custom-provider.config.model.ts index 006697d3..9549837c 100644 --- a/packages/client-api/src/user-config/window/providers/custom-provider.config.model.ts +++ b/packages/client-api/src/user-config/window/providers/custom-provider.config.model.ts @@ -3,13 +3,21 @@ import { z } from 'zod'; import { ProviderType } from '../provider-type.model'; import { FnPathSchema } from '~/user-config/shared'; -export const CustomProviderConfigSchema = z.object({ - type: z.literal(ProviderType.CUSTOM), - refresh_interval: z.coerce.number().default(5 * 1000), - start_fn_path: FnPathSchema.optional(), - refresh_fn_path: FnPathSchema, - stop_fn_path: FnPathSchema.optional(), -}); +export const CustomProviderConfigSchema = z.union([ + z.object({ + type: z.literal(ProviderType.CUSTOM), + refresh_interval: z.coerce.number().default(5 * 1000), + start_fn_path: FnPathSchema.optional(), + refresh_fn_path: FnPathSchema, + stop_fn_path: FnPathSchema.optional(), + }), + z.object({ + type: z.literal(ProviderType.CUSTOM), + start_fn_path: FnPathSchema.optional(), + emitter_fn_path: FnPathSchema, + stop_fn_path: FnPathSchema.optional(), + }), +]); export type CustomProviderConfig = z.infer< typeof CustomProviderConfigSchema diff --git a/packages/client-api/src/utils/index.ts b/packages/client-api/src/utils/index.ts index 85438cd5..ea5885da 100644 --- a/packages/client-api/src/utils/index.ts +++ b/packages/client-api/src/utils/index.ts @@ -5,5 +5,6 @@ export * from './create-getter-proxy'; export * from './create-logger'; export * from './create-string-scanner'; export * from './get-coordinate-distance'; +export * from './is-event-target'; export * from './simple-hash'; export * from './to-css-selector'; diff --git a/packages/client-api/src/utils/is-event-target.ts b/packages/client-api/src/utils/is-event-target.ts new file mode 100644 index 00000000..3d19e241 --- /dev/null +++ b/packages/client-api/src/utils/is-event-target.ts @@ -0,0 +1,3 @@ +export function isEventTarget(obj: any): obj is EventTarget { + return obj && typeof obj.addEventListener === 'function'; +}