style: formatting

This commit is contained in:
ByteAtATime 2025-06-11 14:58:11 -07:00
parent 85c9595d15
commit fc7c835c66
38 changed files with 4202 additions and 2343 deletions

View file

@ -1 +1,8 @@
sidecar/plugin-host.js sidecar/plugin-host.js
sidecar/dist
src-tauri
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb

View file

@ -1,7 +1,3 @@
{ {
"recommendations": [ "recommendations": ["svelte.svelte-vscode", "tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
"svelte.svelte-vscode",
"tauri-apps.tauri-vscode",
"rust-lang.rust-analyzer"
]
} }

3116
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,29 +1,29 @@
{ {
"name": "sidecar", "name": "sidecar",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.ts", "main": "index.ts",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"build": "esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node && pkg dist/index.js --output app --public && pnpm rename", "build": "esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node && pkg dist/index.js --output app --public && pnpm rename",
"rename": "mkdir -p ../src-tauri/binaries && mv \"app$([[ \"$OSTYPE\" =~ msys|win32 ]] && echo .exe)\" \"../src-tauri/binaries/app-$(rustc -vV | awk '/host:/ {print $2}')$([[ \"$OSTYPE\" =~ msys|win32 ]] && echo .exe)\"", "rename": "mkdir -p ../src-tauri/binaries && mv \"app$([[ \"$OSTYPE\" =~ msys|win32 ]] && echo .exe)\" \"../src-tauri/binaries/app-$(rustc -vV | awk '/host:/ {print $2}')$([[ \"$OSTYPE\" =~ msys|win32 ]] && echo .exe)\"",
"check": "tsc --noEmit" "check": "tsc --noEmit"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"packageManager": "pnpm@10.11.1", "packageManager": "pnpm@10.11.1",
"devDependencies": { "devDependencies": {
"@types/node": "^24.0.0", "@types/node": "^24.0.0",
"@types/react": "^19.1.7", "@types/react": "^19.1.7",
"@types/react-reconciler": "^0.32.0", "@types/react-reconciler": "^0.32.0",
"@yao-pkg/pkg": "^6.5.1", "@yao-pkg/pkg": "^6.5.1",
"esbuild": "^0.25.5", "esbuild": "^0.25.5",
"typescript": "^5.8.3" "typescript": "^5.8.3"
}, },
"dependencies": { "dependencies": {
"msgpackr": "^1.11.4", "msgpackr": "^1.11.4",
"react": "^19.1.0", "react": "^19.1.0",
"react-reconciler": "^0.32.0" "react-reconciler": "^0.32.0"
} }
} }

938
sidecar/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,11 @@
declare module "*.txt" { declare module '*.txt' {
const content: string; const content: string;
export default content; export default content;
} }
declare global { declare global {
namespace JSX { namespace JSX {
interface IntrinsicElements { interface IntrinsicElements {
[elemName: string]: any; [elemName: string]: any;
} }
} }
} }

View file

@ -1,234 +1,230 @@
import type { HostConfig } from "react-reconciler"; import type { HostConfig } from 'react-reconciler';
import type { import type {
ComponentType, ComponentType,
ComponentProps, ComponentProps,
Container, Container,
RaycastInstance, RaycastInstance,
TextInstance, TextInstance,
UpdatePayload, UpdatePayload,
ParentInstance, ParentInstance,
AnyInstance, AnyInstance
} from "./types"; } from './types';
import { import {
instances, instances,
getNextInstanceId, getNextInstanceId,
commitBuffer, commitBuffer,
addToCommitBuffer, addToCommitBuffer,
clearCommitBuffer, clearCommitBuffer
} from "./state"; } from './state';
import { writeOutput } from "./io"; import { writeOutput } from './io';
import { serializeProps, optimizeCommitBuffer } from "./utils"; import { serializeProps, optimizeCommitBuffer } from './utils';
import React from "react"; import React from 'react';
const appendChildToParent = (parent: ParentInstance, child: AnyInstance) => { const appendChildToParent = (parent: ParentInstance, child: AnyInstance) => {
const existingIndex = parent.children.findIndex(({ id }) => id === child.id); const existingIndex = parent.children.findIndex(({ id }) => id === child.id);
if (existingIndex > -1) { if (existingIndex > -1) {
parent.children.splice(existingIndex, 1); parent.children.splice(existingIndex, 1);
} }
parent.children.push(child); parent.children.push(child);
addToCommitBuffer({ addToCommitBuffer({
type: "APPEND_CHILD", type: 'APPEND_CHILD',
payload: { parentId: parent.id, childId: child.id }, payload: { parentId: parent.id, childId: child.id }
}); });
}; };
const insertChildBefore = ( const insertChildBefore = (
parent: ParentInstance, parent: ParentInstance,
child: AnyInstance, child: AnyInstance,
beforeChild: AnyInstance beforeChild: AnyInstance
) => { ) => {
const existingIndex = parent.children.findIndex(({ id }) => id === child.id); const existingIndex = parent.children.findIndex(({ id }) => id === child.id);
if (existingIndex > -1) { if (existingIndex > -1) {
parent.children.splice(existingIndex, 1); parent.children.splice(existingIndex, 1);
} }
const beforeIndex = parent.children.findIndex( const beforeIndex = parent.children.findIndex(({ id }) => id === beforeChild.id);
({ id }) => id === beforeChild.id if (beforeIndex !== -1) {
); parent.children.splice(beforeIndex, 0, child);
if (beforeIndex !== -1) { addToCommitBuffer({
parent.children.splice(beforeIndex, 0, child); type: 'INSERT_BEFORE',
addToCommitBuffer({ payload: {
type: "INSERT_BEFORE", parentId: parent.id,
payload: { childId: child.id,
parentId: parent.id, beforeId: beforeChild.id
childId: child.id, }
beforeId: beforeChild.id, });
}, } else {
}); appendChildToParent(parent, child);
} else { }
appendChildToParent(parent, child);
}
}; };
const removeChildFromParent = (parent: ParentInstance, child: AnyInstance) => { const removeChildFromParent = (parent: ParentInstance, child: AnyInstance) => {
parent.children = parent.children.filter(({ id }) => id !== child.id); parent.children = parent.children.filter(({ id }) => id !== child.id);
addToCommitBuffer({ addToCommitBuffer({
type: "REMOVE_CHILD", type: 'REMOVE_CHILD',
payload: { parentId: parent.id, childId: child.id }, payload: { parentId: parent.id, childId: child.id }
}); });
}; };
export const hostConfig: HostConfig< export const hostConfig: HostConfig<
ComponentType, ComponentType,
ComponentProps, ComponentProps,
Container, Container,
RaycastInstance, RaycastInstance,
TextInstance, TextInstance,
never, never,
never, never,
RaycastInstance, RaycastInstance,
object, object,
UpdatePayload, UpdatePayload,
unknown, unknown,
Record<string, unknown>, Record<string, unknown>,
NodeJS.Timeout, NodeJS.Timeout,
number number
> = { > = {
getPublicInstance(instance) { getPublicInstance(instance) {
return instance; return instance;
}, },
getRootHostContext() { getRootHostContext() {
return {}; return {};
}, },
getChildHostContext() { getChildHostContext() {
return {}; return {};
}, },
prepareForCommit: () => null, prepareForCommit: () => null,
resetAfterCommit: () => { resetAfterCommit: () => {
if (commitBuffer.length > 0) { if (commitBuffer.length > 0) {
const optimizedPayload = optimizeCommitBuffer(commitBuffer); const optimizedPayload = optimizeCommitBuffer(commitBuffer);
writeOutput({ writeOutput({
type: "BATCH_UPDATE", type: 'BATCH_UPDATE',
payload: optimizedPayload, payload: optimizedPayload
}); });
clearCommitBuffer(); clearCommitBuffer();
} }
}, },
createInstance(type, props, root, hostContext, internalInstanceHandle) { createInstance(type, props, root, hostContext, internalInstanceHandle) {
const componentType = const componentType =
typeof type === "string" typeof type === 'string' ? type : type.displayName || type.name || 'Anonymous';
? type const id = getNextInstanceId();
: type.displayName || type.name || "Anonymous"; const instance: RaycastInstance = {
const id = getNextInstanceId(); id,
const instance: RaycastInstance = { type: componentType,
id, children: [],
type: componentType, props: serializeProps(props),
children: [], _internalFiber: internalInstanceHandle
props: serializeProps(props), };
_internalFiber: internalInstanceHandle, (internalInstanceHandle as any).stateNode = instance;
}; instances.set(id, instance);
(internalInstanceHandle as any).stateNode = instance;
instances.set(id, instance);
addToCommitBuffer({ addToCommitBuffer({
type: "CREATE_INSTANCE", type: 'CREATE_INSTANCE',
payload: { id, type: componentType, props: instance.props }, payload: { id, type: componentType, props: instance.props }
}); });
return instance; return instance;
}, },
createTextInstance(text) { createTextInstance(text) {
const id = getNextInstanceId(); const id = getNextInstanceId();
const instance: TextInstance = { id, type: "TEXT", text }; const instance: TextInstance = { id, type: 'TEXT', text };
instances.set(id, instance); instances.set(id, instance);
addToCommitBuffer({ type: "CREATE_TEXT_INSTANCE", payload: instance }); addToCommitBuffer({ type: 'CREATE_TEXT_INSTANCE', payload: instance });
return instance; return instance;
}, },
appendInitialChild: appendChildToParent, appendInitialChild: appendChildToParent,
appendChild: appendChildToParent, appendChild: appendChildToParent,
appendChildToContainer: appendChildToParent, appendChildToContainer: appendChildToParent,
insertBefore: insertChildBefore, insertBefore: insertChildBefore,
insertInContainerBefore: insertChildBefore, insertInContainerBefore: insertChildBefore,
removeChild: removeChildFromParent, removeChild: removeChildFromParent,
removeChildFromContainer: removeChildFromParent, removeChildFromContainer: removeChildFromParent,
commitUpdate(instance, type, oldProps, newProps, internalHandle) { commitUpdate(instance, type, oldProps, newProps, internalHandle) {
instance.props = serializeProps(newProps); instance.props = serializeProps(newProps);
addToCommitBuffer({ addToCommitBuffer({
type: "UPDATE_PROPS", type: 'UPDATE_PROPS',
payload: { id: instance.id, props: instance.props }, payload: { id: instance.id, props: instance.props }
}); });
}, },
commitTextUpdate(textInstance, oldText, newText) { commitTextUpdate(textInstance, oldText, newText) {
textInstance.text = newText; textInstance.text = newText;
addToCommitBuffer({ addToCommitBuffer({
type: "UPDATE_TEXT", type: 'UPDATE_TEXT',
payload: { id: textInstance.id, text: newText }, payload: { id: textInstance.id, text: newText }
}); });
}, },
finalizeInitialChildren: () => false, finalizeInitialChildren: () => false,
shouldSetTextContent: () => false, shouldSetTextContent: () => false,
clearContainer: (container) => { clearContainer: (container) => {
container.children = []; container.children = [];
addToCommitBuffer({ addToCommitBuffer({
type: "CLEAR_CONTAINER", type: 'CLEAR_CONTAINER',
payload: { containerId: container.id }, payload: { containerId: container.id }
}); });
}, },
scheduleTimeout: setTimeout, scheduleTimeout: setTimeout,
cancelTimeout: (id) => clearTimeout(id as NodeJS.Timeout), cancelTimeout: (id) => clearTimeout(id as NodeJS.Timeout),
noTimeout: -1 as unknown as NodeJS.Timeout, noTimeout: -1 as unknown as NodeJS.Timeout,
isPrimaryRenderer: true, isPrimaryRenderer: true,
supportsMutation: true, supportsMutation: true,
supportsPersistence: false, supportsPersistence: false,
supportsHydration: false, supportsHydration: false,
detachDeletedInstance() {}, detachDeletedInstance() {},
commitMount() {}, commitMount() {},
hideInstance() {}, hideInstance() {},
hideTextInstance() {}, hideTextInstance() {},
unhideInstance() {}, unhideInstance() {},
unhideTextInstance() {}, unhideTextInstance() {},
resetTextContent() {}, resetTextContent() {},
preparePortalMount() {}, preparePortalMount() {},
getCurrentUpdatePriority: () => 1, getCurrentUpdatePriority: () => 1,
getInstanceFromNode: () => null, getInstanceFromNode: () => null,
beforeActiveInstanceBlur: () => {}, beforeActiveInstanceBlur: () => {},
afterActiveInstanceBlur: () => {}, afterActiveInstanceBlur: () => {},
prepareScopeUpdate() {}, prepareScopeUpdate() {},
getInstanceFromScope: () => null, getInstanceFromScope: () => null,
setCurrentUpdatePriority() {}, setCurrentUpdatePriority() {},
resolveUpdatePriority: () => 1, resolveUpdatePriority: () => 1,
maySuspendCommit: () => false, maySuspendCommit: () => false,
NotPendingTransition: null, NotPendingTransition: null,
HostTransitionContext: React.createContext(0), HostTransitionContext: React.createContext(0),
resetFormInstance: function (): void { resetFormInstance: function (): void {
throw new Error("Function not implemented."); throw new Error('Function not implemented.');
}, },
requestPostPaintCallback: function (): void { requestPostPaintCallback: function (): void {
throw new Error("Function not implemented."); throw new Error('Function not implemented.');
}, },
shouldAttemptEagerTransition: function (): boolean { shouldAttemptEagerTransition: function (): boolean {
throw new Error("Function not implemented."); throw new Error('Function not implemented.');
}, },
trackSchedulerEvent: function (): void { trackSchedulerEvent: function (): void {
throw new Error("Function not implemented."); throw new Error('Function not implemented.');
}, },
resolveEventType: function (): null | string { resolveEventType: function (): null | string {
throw new Error("Function not implemented."); throw new Error('Function not implemented.');
}, },
resolveEventTimeStamp: function (): number { resolveEventTimeStamp: function (): number {
throw new Error("Function not implemented."); throw new Error('Function not implemented.');
}, },
preloadInstance: function (): boolean { preloadInstance: function (): boolean {
throw new Error("Function not implemented."); throw new Error('Function not implemented.');
}, },
startSuspendingCommit: function (): void { startSuspendingCommit: function (): void {
throw new Error("Function not implemented."); throw new Error('Function not implemented.');
}, },
suspendInstance: function (): void { suspendInstance: function (): void {
throw new Error("Function not implemented."); throw new Error('Function not implemented.');
}, },
waitForCommitToBeReady: function () { waitForCommitToBeReady: function () {
throw new Error("Function not implemented."); throw new Error('Function not implemented.');
}, }
}; };

View file

@ -1,65 +1,60 @@
import { createInterface } from "readline"; import { createInterface } from 'readline';
import { writeLog, writeOutput } from "./io"; import { writeLog, writeOutput } from './io';
import { runPlugin } from "./plugin"; import { runPlugin } from './plugin';
import { instances } from "./state"; import { instances } from './state';
import { batchedUpdates } from "./reconciler"; import { batchedUpdates } from './reconciler';
process.on("unhandledRejection", (reason: unknown) => { process.on('unhandledRejection', (reason: unknown) => {
writeLog(`--- UNHANDLED PROMISE REJECTION ---`); writeLog(`--- UNHANDLED PROMISE REJECTION ---`);
const stack = const stack = reason && typeof reason === 'object' && 'stack' in reason ? reason.stack : reason;
reason && typeof reason === "object" && "stack" in reason writeLog(stack);
? reason.stack
: reason;
writeLog(stack);
}); });
const rl = createInterface({ input: process.stdin }); const rl = createInterface({ input: process.stdin });
rl.on("line", (line) => { rl.on('line', (line) => {
batchedUpdates(() => { batchedUpdates(() => {
try { try {
const command: { action: string; payload: unknown } = JSON.parse(line); const command: { action: string; payload: unknown } = JSON.parse(line);
switch (command.action) { switch (command.action) {
case "run-plugin": case 'run-plugin':
runPlugin(); runPlugin();
break; break;
case "dispatch-event": { case 'dispatch-event': {
const { instanceId, handlerName, args } = command.payload as { const { instanceId, handlerName, args } = command.payload as {
instanceId: number; instanceId: number;
handlerName: string; handlerName: string;
args: unknown[]; args: unknown[];
}; };
const instance = instances.get(instanceId); const instance = instances.get(instanceId);
if (!instance) { if (!instance) {
writeLog(`Instance ${instanceId} not found.`); writeLog(`Instance ${instanceId} not found.`);
return; return;
} }
const handler = instance._internalFiber?.memoizedProps?.[handlerName]; const handler = instance._internalFiber?.memoizedProps?.[handlerName];
if (typeof handler === "function") { if (typeof handler === 'function') {
handler(...args); handler(...args);
} else { } else {
writeLog( writeLog(`Handler ${handlerName} not found on instance ${instanceId}`);
`Handler ${handlerName} not found on instance ${instanceId}` }
); break;
} }
break; default:
} writeLog(`Unknown command action: ${command.action}`);
default: }
writeLog(`Unknown command action: ${command.action}`); } catch (err: unknown) {
} const error =
} catch (err: unknown) { err instanceof Error
const error = ? { message: err.message, stack: err.stack }
err instanceof Error : { message: String(err) };
? { message: err.message, stack: err.stack } writeLog(`ERROR: ${error.message} \n ${error.stack ?? ''}`);
: { message: String(err) }; writeOutput({ type: 'error', payload: error.message });
writeLog(`ERROR: ${error.message} \n ${error.stack ?? ""}`); }
writeOutput({ type: "error", payload: error.message }); });
}
});
}); });
writeLog("Node.js Sidecar started successfully with React Reconciler."); writeLog('Node.js Sidecar started successfully with React Reconciler.');

View file

@ -1,25 +1,25 @@
import { Packr } from "msgpackr"; import { Packr } from 'msgpackr';
const packr = new Packr(); const packr = new Packr();
export const writeOutput = (data: object): void => { export const writeOutput = (data: object): void => {
try { try {
const payload = packr.pack(data); const payload = packr.pack(data);
const header = Buffer.alloc(4); const header = Buffer.alloc(4);
header.writeUInt32BE(payload.length); header.writeUInt32BE(payload.length);
process.stdout.write(header); process.stdout.write(header);
process.stdout.write(payload); process.stdout.write(payload);
} catch (e: unknown) { } catch (e: unknown) {
const errorString = e instanceof Error ? e.toString() : String(e); const errorString = e instanceof Error ? e.toString() : String(e);
const errorPayload = packr.pack({ type: "log", payload: errorString }); const errorPayload = packr.pack({ type: 'log', payload: errorString });
const errorHeader = Buffer.alloc(4); const errorHeader = Buffer.alloc(4);
errorHeader.writeUInt32BE(errorPayload.length); errorHeader.writeUInt32BE(errorPayload.length);
process.stdout.write(errorHeader); process.stdout.write(errorHeader);
process.stdout.write(errorPayload); process.stdout.write(errorPayload);
} }
}; };
export const writeLog = (message: unknown): void => { export const writeLog = (message: unknown): void => {
writeOutput({ type: "log", payload: message }); writeOutput({ type: 'log', payload: message });
}; };

View file

@ -1,107 +1,94 @@
import React from "react"; import React from 'react';
import { jsx } from "react/jsx-runtime"; import { jsx } from 'react/jsx-runtime';
import plugin from "../dist/plugin/emoji.txt"; import plugin from '../dist/plugin/emoji.txt';
import { updateContainer } from "./reconciler"; import { updateContainer } from './reconciler';
import { writeLog } from "./io"; import { writeLog } from './io';
const createPluginRequire = const createPluginRequire =
() => () =>
(moduleName: string): unknown => { (moduleName: string): unknown => {
if (moduleName === "react") { if (moduleName === 'react') {
return React; return React;
} }
if (moduleName.startsWith("@raycast/api")) { if (moduleName.startsWith('@raycast/api')) {
const storage = new Map<string, string>(); const storage = new Map<string, string>();
const LocalStorage = { const LocalStorage = {
getItem: async (key: string) => storage.get(key), getItem: async (key: string) => storage.get(key),
setItem: async (key: string, value: string) => storage.set(key, value), setItem: async (key: string, value: string) => storage.set(key, value),
removeItem: async (key: string) => storage.delete(key), removeItem: async (key: string) => storage.delete(key),
clear: async () => storage.clear(), clear: async () => storage.clear()
}; };
const createWrapperComponent = const createWrapperComponent =
(name: string) => (name: string) =>
({ children, ...rest }: { children?: React.ReactNode }) => ({ children, ...rest }: { children?: React.ReactNode }) =>
jsx(name, { ...rest, children }); jsx(name, { ...rest, children });
const ListComponent = createWrapperComponent("List"); const ListComponent = createWrapperComponent('List');
const ListSectionComponent = createWrapperComponent("ListSection"); const ListSectionComponent = createWrapperComponent('ListSection');
const ListDropdownComponent = createWrapperComponent("ListDropdown"); const ListDropdownComponent = createWrapperComponent('ListDropdown');
const ActionPanelComponent = createWrapperComponent("ActionPanel"); const ActionPanelComponent = createWrapperComponent('ActionPanel');
const ActionPanelSectionComponent = const ActionPanelSectionComponent = createWrapperComponent('ActionPanelSection');
createWrapperComponent("ActionPanelSection");
Object.assign(ListComponent, { Object.assign(ListComponent, {
Item: "ListItem", Item: 'ListItem',
Section: ListSectionComponent, Section: ListSectionComponent,
Dropdown: ListDropdownComponent, Dropdown: ListDropdownComponent
}); });
Object.assign(ListDropdownComponent, { Item: "ListDropdownItem" }); Object.assign(ListDropdownComponent, { Item: 'ListDropdownItem' });
Object.assign(ActionPanelComponent, { Object.assign(ActionPanelComponent, {
Section: ActionPanelSectionComponent, Section: ActionPanelSectionComponent
}); });
return { return {
LocalStorage, LocalStorage,
environment: { environment: {
assetsPath: assetsPath: '/home/byte/code/raycast-linux/sidecar/dist/plugin/assets/'
"/home/byte/code/raycast-linux/sidecar/dist/plugin/assets/", },
}, getPreferenceValues: () => ({
getPreferenceValues: () => ({ primaryAction: 'paste',
primaryAction: "paste", unicodeVersion: '14.0',
unicodeVersion: "14.0", shortCodes: true
shortCodes: true, }),
}), usePersistentState: <T>(
usePersistentState: <T>( key: string,
key: string, initialValue: T
initialValue: T ): [T, React.Dispatch<React.SetStateAction<T>>, boolean] => {
): [T, React.Dispatch<React.SetStateAction<T>>, boolean] => { const [state, setState] = React.useState(initialValue);
const [state, setState] = React.useState(initialValue); return [state, setState, false];
return [state, setState, false]; },
}, List: ListComponent,
List: ListComponent, ActionPanel: ActionPanelComponent,
ActionPanel: ActionPanelComponent, Action: {
Action: { Paste: 'Action.Paste',
Paste: "Action.Paste", CopyToClipboard: 'Action.CopyToClipboard',
CopyToClipboard: "Action.CopyToClipboard", OpenInBrowser: 'Action.OpenInBrowser'
OpenInBrowser: "Action.OpenInBrowser", }
}, };
}; }
}
return require(moduleName); return require(moduleName);
}; };
export const runPlugin = (): void => { export const runPlugin = (): void => {
const scriptText = plugin; const scriptText = plugin;
const pluginModule = { const pluginModule = {
exports: {} as { default: React.ComponentType | null }, exports: {} as { default: React.ComponentType | null }
}; };
const scriptFunction = new Function( const scriptFunction = new Function('require', 'module', 'exports', 'React', scriptText);
"require",
"module",
"exports",
"React",
scriptText
);
scriptFunction( scriptFunction(createPluginRequire(), pluginModule, pluginModule.exports, React);
createPluginRequire(),
pluginModule,
pluginModule.exports,
React
);
const PluginRootComponent = pluginModule.exports.default; const PluginRootComponent = pluginModule.exports.default;
if (!PluginRootComponent) { if (!PluginRootComponent) {
throw new Error("Plugin did not export a default component."); throw new Error('Plugin did not export a default component.');
} }
writeLog("Plugin loaded. Initializing React render..."); writeLog('Plugin loaded. Initializing React render...');
const AppElement = React.createElement(PluginRootComponent); const AppElement = React.createElement(PluginRootComponent);
updateContainer(AppElement, () => { updateContainer(AppElement, () => {
writeLog("Initial render complete"); writeLog('Initial render complete');
}); });
}; };

View file

@ -1,34 +1,31 @@
import Reconciler, { type RootTag } from "react-reconciler"; import Reconciler, { type RootTag } from 'react-reconciler';
import type React from "react"; import type React from 'react';
import { root } from "./state"; import { root } from './state';
import { hostConfig } from "./hostConfig"; import { hostConfig } from './hostConfig';
import { writeLog } from "./io"; import { writeLog } from './io';
const reconciler = Reconciler(hostConfig); const reconciler = Reconciler(hostConfig);
const onRecoverableError = (error: Error) => { const onRecoverableError = (error: Error) => {
writeLog(`--- REACT RECOVERABLE ERROR ---`); writeLog(`--- REACT RECOVERABLE ERROR ---`);
writeLog(`Error: ${error.message}`); writeLog(`Error: ${error.message}`);
}; };
export const container = reconciler.createContainer( export const container = reconciler.createContainer(
root as unknown as RootTag, root as unknown as RootTag,
0, 0,
null, null,
false, false,
null, null,
"", '',
onRecoverableError, onRecoverableError,
null null
); );
export const updateContainer = ( export const updateContainer = (element: React.ReactElement, callback?: () => void) => {
element: React.ReactElement, reconciler.updateContainer(element, container, null, callback);
callback?: () => void
) => {
reconciler.updateContainer(element, container, null, callback);
}; };
export const batchedUpdates = (callback: () => void) => { export const batchedUpdates = (callback: () => void) => {
reconciler.batchedUpdates(callback, null); reconciler.batchedUpdates(callback, null);
}; };

View file

@ -1,7 +1,7 @@
import type { AnyInstance, Commit, Container } from "./types"; import type { AnyInstance, Commit, Container } from './types';
export const instances = new Map<number, AnyInstance>(); export const instances = new Map<number, AnyInstance>();
export const root: Container = { id: "root", children: [] }; export const root: Container = { id: 'root', children: [] };
let instanceCounter = 0; let instanceCounter = 0;
export const getNextInstanceId = (): number => ++instanceCounter; export const getNextInstanceId = (): number => ++instanceCounter;
@ -9,9 +9,9 @@ export const getNextInstanceId = (): number => ++instanceCounter;
export let commitBuffer: Commit[] = []; export let commitBuffer: Commit[] = [];
export const clearCommitBuffer = (): void => { export const clearCommitBuffer = (): void => {
commitBuffer = []; commitBuffer = [];
}; };
export const addToCommitBuffer = (commit: Commit): void => { export const addToCommitBuffer = (commit: Commit): void => {
commitBuffer.push(commit); commitBuffer.push(commit);
}; };

View file

@ -1,28 +1,28 @@
import type React from "react"; import type React from 'react';
import type Reconciler from "react-reconciler"; import type Reconciler from 'react-reconciler';
export type ComponentType = string | React.ComponentType<any>; export type ComponentType = string | React.ComponentType<any>;
export type ComponentProps = Record<string, unknown>; export type ComponentProps = Record<string, unknown>;
export interface BaseInstance { export interface BaseInstance {
id: number; id: number;
_internalFiber?: Reconciler.Fiber; _internalFiber?: Reconciler.Fiber;
} }
export interface RaycastInstance extends BaseInstance { export interface RaycastInstance extends BaseInstance {
type: ComponentType; type: ComponentType;
props: ComponentProps; props: ComponentProps;
children: (RaycastInstance | TextInstance)[]; children: (RaycastInstance | TextInstance)[];
} }
export interface TextInstance extends BaseInstance { export interface TextInstance extends BaseInstance {
type: "TEXT"; type: 'TEXT';
text: string; text: string;
} }
export interface Container { export interface Container {
id: "root"; id: 'root';
children: (RaycastInstance | TextInstance)[]; children: (RaycastInstance | TextInstance)[];
} }
export type AnyInstance = RaycastInstance | TextInstance; export type AnyInstance = RaycastInstance | TextInstance;
@ -30,12 +30,12 @@ export type ParentInstance = RaycastInstance | Container;
export type UpdatePayload = Record<string, unknown>; export type UpdatePayload = Record<string, unknown>;
export interface Commit { export interface Commit {
type: string; type: string;
payload: unknown; payload: unknown;
} }
export interface SerializedReactElement { export interface SerializedReactElement {
$$typeof: "react.element.serialized"; $$typeof: 'react.element.serialized';
type: string; type: string;
props: Record<string, unknown>; props: Record<string, unknown>;
} }

View file

@ -1,109 +1,89 @@
import React from "react"; import React from 'react';
import type { import type { ComponentType, Commit, SerializedReactElement, ParentInstance } from './types';
ComponentType, import { root, instances } from './state';
Commit,
SerializedReactElement,
ParentInstance,
} from "./types";
import { root, instances } from "./state";
export const getComponentDisplayName = (type: ComponentType): string => { export const getComponentDisplayName = (type: ComponentType): string => {
if (typeof type === "string") { if (typeof type === 'string') {
return type; return type;
} }
return type.displayName ?? type.name ?? "Anonymous"; return type.displayName ?? type.name ?? 'Anonymous';
}; };
const isSerializableReactElement = ( const isSerializableReactElement = (value: unknown): value is React.ReactElement =>
value: unknown React.isValidElement(value);
): value is React.ReactElement => React.isValidElement(value);
function serializeReactElement( function serializeReactElement(element: React.ReactElement): SerializedReactElement {
element: React.ReactElement return {
): SerializedReactElement { $$typeof: 'react.element.serialized',
return { type: getComponentDisplayName(element.type as ComponentType),
$$typeof: "react.element.serialized", props: serializeProps(element.props as Record<string, unknown>)
type: getComponentDisplayName(element.type as ComponentType), };
props: serializeProps(element.props as Record<string, unknown>),
};
} }
export function serializeProps( export function serializeProps(props: Record<string, unknown>): Record<string, unknown> {
props: Record<string, unknown> return Object.fromEntries(
): Record<string, unknown> { Object.entries(props)
return Object.fromEntries( .filter(([key, value]) => key !== 'children' && typeof value !== 'function')
Object.entries(props) .map(([key, value]) => {
.filter( if (isSerializableReactElement(value)) {
([key, value]) => key !== "children" && typeof value !== "function" return [key, serializeReactElement(value)];
) }
.map(([key, value]) => { if (Array.isArray(value)) {
if (isSerializableReactElement(value)) { return [
return [key, serializeReactElement(value)]; key,
} value.map((item) =>
if (Array.isArray(value)) { isSerializableReactElement(item) ? serializeReactElement(item) : item
return [ )
key, ];
value.map((item) => }
isSerializableReactElement(item) return [key, value];
? serializeReactElement(item) })
: item );
),
];
}
return [key, value];
})
);
} }
export function optimizeCommitBuffer(buffer: Commit[]): Commit[] { export function optimizeCommitBuffer(buffer: Commit[]): Commit[] {
const OPTIMIZATION_THRESHOLD = 10; const OPTIMIZATION_THRESHOLD = 10;
const childOpsByParent = new Map<ParentInstance["id"], Commit[]>(); const childOpsByParent = new Map<ParentInstance['id'], Commit[]>();
const otherOps: Commit[] = []; const otherOps: Commit[] = [];
for (const op of buffer) { for (const op of buffer) {
const { type, payload } = op; const { type, payload } = op;
const parentId = (payload as { parentId?: ParentInstance["id"] })?.parentId; const parentId = (payload as { parentId?: ParentInstance['id'] })?.parentId;
const isChildOp = const isChildOp =
type === "APPEND_CHILD" || type === 'APPEND_CHILD' || type === 'REMOVE_CHILD' || type === 'INSERT_BEFORE';
type === "REMOVE_CHILD" ||
type === "INSERT_BEFORE";
if (isChildOp && parentId) { if (isChildOp && parentId) {
childOpsByParent.set( childOpsByParent.set(parentId, (childOpsByParent.get(parentId) ?? []).concat(op));
parentId, } else {
(childOpsByParent.get(parentId) ?? []).concat(op) otherOps.push(op);
); }
} else { }
otherOps.push(op);
}
}
if (childOpsByParent.size === 0) { if (childOpsByParent.size === 0) {
return buffer; return buffer;
} }
const finalOps = [...otherOps]; const finalOps = [...otherOps];
for (const [parentId, ops] of childOpsByParent.entries()) { for (const [parentId, ops] of childOpsByParent.entries()) {
if (ops.length <= OPTIMIZATION_THRESHOLD) { if (ops.length <= OPTIMIZATION_THRESHOLD) {
finalOps.push(...ops); finalOps.push(...ops);
continue; continue;
} }
const parentInstance = const parentInstance = parentId === 'root' ? root : instances.get(parentId as number);
parentId === "root" ? root : instances.get(parentId as number);
if (parentInstance && "children" in parentInstance) { if (parentInstance && 'children' in parentInstance) {
const childrenIds = parentInstance.children.map(({ id }) => id); const childrenIds = parentInstance.children.map(({ id }) => id);
finalOps.push({ finalOps.push({
type: "REPLACE_CHILDREN", type: 'REPLACE_CHILDREN',
payload: { parentId, childrenIds }, payload: { parentId, childrenIds }
}); });
} else { } else {
finalOps.push(...ops); finalOps.push(...ops);
} }
} }
return finalOps; return finalOps;
} }

View file

@ -1,29 +1,29 @@
{ {
"compilerOptions": { "compilerOptions": {
// Environment setup & latest features // Environment setup & latest features
"lib": ["ESNext"], "lib": ["ESNext"],
"target": "ESNext", "target": "ESNext",
"module": "Preserve", "module": "Preserve",
"moduleDetection": "force", "moduleDetection": "force",
"jsx": "react-jsx", "jsx": "react-jsx",
"allowJs": true, "allowJs": true,
// Bundler mode // Bundler mode
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"noEmit": true, "noEmit": true,
// Best practices // Best practices
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"noImplicitOverride": true, "noImplicitOverride": true,
// Some stricter flags (disabled by default) // Some stricter flags (disabled by default)
"noUnusedLocals": false, "noUnusedLocals": false,
"noUnusedParameters": false, "noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false "noPropertyAccessFromIndexSignature": false
} }
} }

View file

@ -1,24 +1,24 @@
{ {
"$schema": "../gen/schemas/desktop-schema.json", "$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default", "identifier": "default",
"description": "Capability for the main window", "description": "Capability for the main window",
"windows": ["main"], "windows": ["main"],
"permissions": [ "permissions": [
"core:default", "core:default",
"opener:default", "opener:default",
"clipboard-manager:default", "clipboard-manager:default",
"clipboard-manager:allow-write-text", "clipboard-manager:allow-write-text",
"clipboard-manager:allow-write-html", "clipboard-manager:allow-write-html",
"clipboard-manager:allow-write-image", "clipboard-manager:allow-write-image",
"shell:allow-stdin-write", "shell:allow-stdin-write",
{ {
"identifier": "shell:allow-spawn", "identifier": "shell:allow-spawn",
"allow": [ "allow": [
{ {
"name": "binaries/app", "name": "binaries/app",
"sidecar": true "sidecar": true
} }
] ]
} }
] ]
} }

View file

@ -1,13 +1,13 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" /> <link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tauri + SvelteKit + Typescript App</title> <title>Tauri + SvelteKit + Typescript App</title>
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

View file

@ -1,40 +1,36 @@
import { setResults } from "../results.svelte"; import { setResults } from '../results.svelte';
import type * as api from "@raycast/api"; import type * as api from '@raycast/api';
import { Toast } from "./toast"; import { Toast } from './toast';
import { import { writeText, writeHtml, writeImage } from '@tauri-apps/plugin-clipboard-manager';
writeText,
writeHtml,
writeImage,
} from "@tauri-apps/plugin-clipboard-manager";
export const mockRaycastApi = { export const mockRaycastApi = {
updateCommandMetadata: async (metadata: { subtitle?: string | null }) => { updateCommandMetadata: async (metadata: { subtitle?: string | null }) => {
setResults([{ subtitle: metadata.subtitle }]); setResults([{ subtitle: metadata.subtitle }]);
}, },
environment: { environment: {
launchType: "userInitiated", launchType: 'userInitiated'
}, },
LaunchType: { LaunchType: {
UserInitiated: "userInitiated", UserInitiated: 'userInitiated',
Background: "background", Background: 'background'
}, },
Toast: Toast as typeof api.Toast, Toast: Toast as typeof api.Toast,
Clipboard: { Clipboard: {
copy: async ( copy: async (
content: string | number | api.Clipboard.Content, content: string | number | api.Clipboard.Content,
options?: api.Clipboard.CopyOptions options?: api.Clipboard.CopyOptions
) => { ) => {
if (typeof content === "string" || typeof content === "number") { if (typeof content === 'string' || typeof content === 'number') {
await writeText(content.toString()); await writeText(content.toString());
} else { } else {
if ("html" in content) { if ('html' in content) {
await writeHtml(content.html); await writeHtml(content.html);
} else if ("file" in content) { } else if ('file' in content) {
await writeImage(content.file); await writeImage(content.file);
} else { } else {
await writeText(content.text); await writeText(content.text);
} }
} }
}, }
}, }
} satisfies typeof api; } satisfies typeof api;

View file

@ -1,32 +1,32 @@
import { setToast } from "$lib/results.svelte"; import { setToast } from '$lib/results.svelte';
import type * as api from "@raycast/api"; import type * as api from '@raycast/api';
export class Toast { export class Toast {
public static readonly Style = { public static readonly Style = {
Success: "SUCCESS", Success: 'SUCCESS',
Failure: "FAILURE", Failure: 'FAILURE',
Animated: "ANIMATED", Animated: 'ANIMATED'
}; };
public style: "SUCCESS" | "FAILURE" | "ANIMATED"; public style: 'SUCCESS' | 'FAILURE' | 'ANIMATED';
public title: string; public title: string;
public message: string | undefined; public message: string | undefined;
public primaryAction: api.Toast.ActionOptions | undefined; public primaryAction: api.Toast.ActionOptions | undefined;
public secondaryAction: api.Toast.ActionOptions | undefined; public secondaryAction: api.Toast.ActionOptions | undefined;
constructor(props: api.Toast.Options) { constructor(props: api.Toast.Options) {
this.style = props.style ?? "SUCCESS"; // TODO: is this default value correct? this.style = props.style ?? 'SUCCESS'; // TODO: is this default value correct?
this.title = props.title; this.title = props.title;
this.message = props.message; this.message = props.message;
this.primaryAction = props.primaryAction; this.primaryAction = props.primaryAction;
this.secondaryAction = props.secondaryAction; this.secondaryAction = props.secondaryAction;
} }
async show(): Promise<void> { async show(): Promise<void> {
setToast(this); setToast(this);
} }
async hide(): Promise<void> { async hide(): Promise<void> {
setToast(null); setToast(null);
} }
} }

View file

@ -1,20 +1,18 @@
<!-- src/ResultsList.svelte --> <!-- src/ResultsList.svelte -->
<script lang="ts"> <script lang="ts">
import { getResults } from "$lib/results.svelte"; import { getResults } from '$lib/results.svelte';
const pluginResults = $derived(getResults()); const pluginResults = $derived(getResults());
</script> </script>
<div class="flex-grow overflow-y-auto"> <div class="flex-grow overflow-y-auto">
<ul> <ul>
{#each pluginResults as result} {#each pluginResults as result}
<li <li class="flex cursor-pointer items-center rounded-lg p-2 hover:bg-blue-500/20">
class="flex items-center p-2 rounded-lg hover:bg-blue-500/20 cursor-pointer" <div class="ml-2 flex flex-col">
> <span class="text-xs text-gray-400">{result.subtitle}</span>
<div class="flex flex-col ml-2"> </div>
<span class="text-xs text-gray-400">{result.subtitle}</span> </li>
</div> {/each}
</li> </ul>
{/each}
</ul>
</div> </div>

View file

@ -1,58 +1,57 @@
<script lang="ts"> <script lang="ts">
import { Separator } from "./ui/separator"; import { Separator } from './ui/separator';
import { getToast } from "../results.svelte"; import { getToast } from '../results.svelte';
import { Toast } from "$lib/api/toast"; import { Toast } from '$lib/api/toast';
import { Button } from "./ui/button"; import { Button } from './ui/button';
import type * as api from "@raycast/api"; import type * as api from '@raycast/api';
import { Kbd } from "./ui/kbd"; import { Kbd } from './ui/kbd';
import { shortcutToText } from "$lib/renderKey"; import { shortcutToText } from '$lib/renderKey';
const toast = getToast(); const toast = getToast();
const styles = $derived.by(() => { const styles = $derived.by(() => {
switch (toast?.style) { switch (toast?.style) {
case Toast.Style.Success: case Toast.Style.Success:
return "bg-green-500/20"; return 'bg-green-500/20';
case Toast.Style.Failure: case Toast.Style.Failure:
return "bg-red-500/20"; return 'bg-red-500/20';
default: default:
return "bg-gray-500/20"; return 'bg-gray-500/20';
} }
}); });
</script> </script>
<Separator /> <Separator />
{#if toast} {#if toast}
{@const actualToast = toast as Toast} {@const actualToast = toast as Toast}
<div class="py-2 px-4 flex items-center gap-4 {styles}"> <div class="flex items-center gap-4 px-4 py-2 {styles}">
{#if actualToast.style === "SUCCESS"} {#if actualToast.style === 'SUCCESS'}
<div class="w-2.5 h-2.5 bg-green-500 rounded-full"></div> <div class="h-2.5 w-2.5 rounded-full bg-green-500"></div>
{:else if actualToast.style === "FAILURE"} {:else if actualToast.style === 'FAILURE'}
<div class="w-2.5 h-2.5 bg-red-500 rounded-full"></div> <div class="h-2.5 w-2.5 rounded-full bg-red-500"></div>
{:else if actualToast.style === "ANIMATED"} {:else if actualToast.style === 'ANIMATED'}
<div class="w-2.5 h-2.5 bg-gray-500 rounded-full"></div> <div class="h-2.5 w-2.5 rounded-full bg-gray-500"></div>
{/if} {/if}
{actualToast.title} {actualToast.title}
<div class="grow"></div> <div class="grow"></div>
{#if actualToast.primaryAction} {#if actualToast.primaryAction}
<Button <Button
variant="ghost" variant="ghost"
onclick={() => onclick={() => actualToast.primaryAction!.onAction(actualToast as api.Toast)}
actualToast.primaryAction!.onAction(actualToast as api.Toast)} >
> {actualToast.primaryAction.title}
{actualToast.primaryAction.title}
{#if actualToast.primaryAction.shortcut} {#if actualToast.primaryAction.shortcut}
<Kbd> <Kbd>
{shortcutToText(actualToast.primaryAction.shortcut)} {shortcutToText(actualToast.primaryAction.shortcut)}
</Kbd> </Kbd>
{/if} {/if}
</Button> </Button>
{/if} {/if}
</div> </div>
{/if} {/if}

View file

@ -1,36 +1,36 @@
<script lang="ts" module> <script lang="ts" module>
import { cn, type WithElementRef } from "$lib/utils.js"; import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements"; import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, tv } from "tailwind-variants"; import { type VariantProps, tv } from 'tailwind-variants';
export const buttonVariants = tv({ export const buttonVariants = tv({
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0", base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-all focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
destructive: destructive:
"bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white", 'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
outline: outline:
"bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border", 'bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border',
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: "text-primary underline-offset-4 hover:underline", link: 'text-primary underline-offset-4 hover:underline'
}, },
size: { size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3", default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
lg: "h-10 rounded-md px-6 has-[>svg]:px-4", lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: "size-9", icon: 'size-9'
}, }
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: 'default',
size: "default", size: 'default'
}, }
}); });
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"]; export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>["size"]; export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> & export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & { WithElementRef<HTMLAnchorAttributes> & {
@ -42,11 +42,11 @@
<script lang="ts"> <script lang="ts">
let { let {
class: className, class: className,
variant = "default", variant = 'default',
size = "default", size = 'default',
ref = $bindable(null), ref = $bindable(null),
href = undefined, href = undefined,
type = "button", type = 'button',
disabled, disabled,
children, children,
...restProps ...restProps
@ -60,7 +60,7 @@
class={cn(buttonVariants({ variant, size }), className)} class={cn(buttonVariants({ variant, size }), className)}
href={disabled ? undefined : href} href={disabled ? undefined : href}
aria-disabled={disabled} aria-disabled={disabled}
role={disabled ? "link" : undefined} role={disabled ? 'link' : undefined}
tabindex={disabled ? -1 : undefined} tabindex={disabled ? -1 : undefined}
{...restProps} {...restProps}
> >

View file

@ -2,8 +2,8 @@ import Root, {
type ButtonProps, type ButtonProps,
type ButtonSize, type ButtonSize,
type ButtonVariant, type ButtonVariant,
buttonVariants, buttonVariants
} from "./button.svelte"; } from './button.svelte';
export { export {
Root, Root,
@ -13,5 +13,5 @@ export {
buttonVariants, buttonVariants,
type ButtonProps, type ButtonProps,
type ButtonSize, type ButtonSize,
type ButtonVariant, type ButtonVariant
}; };

View file

@ -1,7 +1,7 @@
import Root from "./input.svelte"; import Root from './input.svelte';
export { export {
Root, Root,
// //
Root as Input, Root as Input
}; };

View file

@ -1,56 +1,50 @@
<script lang="ts"> <script lang="ts">
import type { import type { HTMLInputAttributes, HTMLInputTypeAttribute } from 'svelte/elements';
HTMLInputAttributes, import { cn, type WithElementRef } from '$lib/utils.js';
HTMLInputTypeAttribute,
} from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js";
type InputType = Exclude<HTMLInputTypeAttribute, "file">; type InputType = Exclude<HTMLInputTypeAttribute, 'file'>;
type Props = WithElementRef< type Props = WithElementRef<
Omit<HTMLInputAttributes, "type"> & Omit<HTMLInputAttributes, 'type'> &
( ({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
| { type: "file"; files?: FileList } >;
| { type?: InputType; files?: undefined }
)
>;
let { let {
ref = $bindable(null), ref = $bindable(null),
value = $bindable(), value = $bindable(),
type, type,
files = $bindable(), files = $bindable(),
class: className, class: className,
...restProps ...restProps
}: Props = $props(); }: Props = $props();
</script> </script>
{#if type === "file"} {#if type === 'file'}
<input <input
bind:this={ref} bind:this={ref}
data-slot="input" data-slot="input"
class={cn( class={cn(
"selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 pt-1.5 text-sm font-medium outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", 'selection:bg-primary dark:bg-input/30 selection:text-primary-foreground border-input ring-offset-background placeholder:text-muted-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 pt-1.5 text-sm font-medium shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className className
)} )}
type="file" type="file"
bind:files bind:files
bind:value bind:value
{...restProps} {...restProps}
/> />
{:else} {:else}
<input <input
bind:this={ref} bind:this={ref}
data-slot="input" data-slot="input"
class={cn( class={cn(
"border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground shadow-xs flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", 'border-input bg-background selection:bg-primary dark:bg-input/30 selection:text-primary-foreground ring-offset-background placeholder:text-muted-foreground flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className className
)} )}
{type} {type}
bind:value bind:value
{...restProps} {...restProps}
/> />
{/if} {/if}

View file

@ -1,3 +1,3 @@
import Kbd from "./kbd.svelte"; import Kbd from './kbd.svelte';
export { Kbd }; export { Kbd };

View file

@ -1,48 +1,48 @@
<script lang="ts" module> <script lang="ts" module>
import { tv, type VariantProps } from "tailwind-variants"; import { tv, type VariantProps } from 'tailwind-variants';
import type { WithChildren } from "bits-ui"; import type { WithChildren } from 'bits-ui';
const style = tv({ const style = tv({
base: "inline-flex place-items-center justify-center gap-1 rounded-md p-0.5", base: 'inline-flex place-items-center justify-center gap-1 rounded-md p-0.5',
variants: { variants: {
variant: { variant: {
outline: "border-border bg-background text-muted-foreground border", outline: 'border-border bg-background text-muted-foreground border',
secondary: "bg-secondary text-muted-foreground", secondary: 'bg-secondary text-muted-foreground',
primary: "bg-primary text-primary-foreground", primary: 'bg-primary text-primary-foreground'
}, },
size: { size: {
sm: "min-w-6 gap-1.5 p-0.5 px-1 text-sm", sm: 'min-w-6 gap-1.5 p-0.5 px-1 text-sm',
default: "min-w-8 gap-1.5 p-1 px-2", default: 'min-w-8 gap-1.5 p-1 px-2',
lg: "min-w-9 gap-2 p-1 px-3 text-lg", lg: 'min-w-9 gap-2 p-1 px-3 text-lg'
}, }
}, }
}); });
type Size = VariantProps<typeof style>["size"]; type Size = VariantProps<typeof style>['size'];
type Variant = VariantProps<typeof style>["variant"]; type Variant = VariantProps<typeof style>['variant'];
export type KbdPropsWithoutHTML = WithChildren<{ export type KbdPropsWithoutHTML = WithChildren<{
ref?: HTMLElement | null; ref?: HTMLElement | null;
class?: string; class?: string;
size?: Size; size?: Size;
variant?: Variant; variant?: Variant;
}>; }>;
export type KbdProps = KbdPropsWithoutHTML; export type KbdProps = KbdPropsWithoutHTML;
</script> </script>
<script lang="ts"> <script lang="ts">
import { cn } from "$lib/utils"; import { cn } from '$lib/utils';
let { let {
ref = $bindable(null), ref = $bindable(null),
class: className, class: className,
size = "default", size = 'default',
variant = "outline", variant = 'outline',
children, children
}: KbdProps = $props(); }: KbdProps = $props();
</script> </script>
<kbd bind:this={ref} class={cn(style({ size, variant }), className)}> <kbd bind:this={ref} class={cn(style({ size, variant }), className)}>
{@render children?.()} {@render children?.()}
</kbd> </kbd>

View file

@ -1,7 +1,7 @@
import Root from "./separator.svelte"; import Root from './separator.svelte';
export { export {
Root, Root,
// //
Root as Separator, Root as Separator
}; };

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Separator as SeparatorPrimitive } from "bits-ui"; import { Separator as SeparatorPrimitive } from 'bits-ui';
import { cn } from "$lib/utils.js"; import { cn } from '$lib/utils.js';
let { let {
ref = $bindable(null), ref = $bindable(null),
@ -13,7 +13,7 @@
bind:ref bind:ref
data-slot="separator" data-slot="separator"
class={cn( class={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=vertical]:h-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px", 'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
className className
)} )}
{...restProps} {...restProps}

View file

@ -1,51 +1,45 @@
import { mockRaycastApi } from "./api"; import { mockRaycastApi } from './api';
import plugin from "./plugin.js?raw"; import plugin from './plugin.js?raw';
declare global { declare global {
interface Window { interface Window {
require: (moduleName: string) => unknown; require: (moduleName: string) => unknown;
module: { exports: { default?: () => unknown } }; module: { exports: { default?: () => unknown } };
} }
} }
window.require = function (moduleName) { window.require = function (moduleName) {
console.log(`Plugin is requesting module: ${moduleName}`); console.log(`Plugin is requesting module: ${moduleName}`);
if (moduleName === "@raycast/api") { if (moduleName === '@raycast/api') {
return mockRaycastApi; return mockRaycastApi;
} }
throw new Error( throw new Error(`Module not found: ${moduleName}. Our fake 'require' is very limited!`);
`Module not found: ${moduleName}. Our fake 'require' is very limited!`
);
}; };
window.module = { window.module = {
exports: {}, exports: {}
}; };
export async function runPlugin() { export async function runPlugin() {
console.log("Requesting plugin script from Rust backend..."); console.log('Requesting plugin script from Rust backend...');
const scriptText = plugin; const scriptText = plugin;
console.log("Executing plugin script in a try/catch block..."); console.log('Executing plugin script in a try/catch block...');
try { try {
// TOOD: don't use eval // TOOD: don't use eval
eval(scriptText); eval(scriptText);
} catch (e) { } catch (e) {
console.error("Error evaluating plugin script:", e); console.error('Error evaluating plugin script:', e);
return; return;
} }
const pluginMainFunction = window.module.exports.default; const pluginMainFunction = window.module.exports.default;
if (pluginMainFunction && typeof pluginMainFunction === "function") { if (pluginMainFunction && typeof pluginMainFunction === 'function') {
console.log( console.log('Plugin script loaded successfully. Running its main command...');
"Plugin script loaded successfully. Running its main command..." await pluginMainFunction();
); console.log('Plugin main command finished.');
await pluginMainFunction(); } else {
console.log("Plugin main command finished."); console.error('Could not find a default export function in the plugin script.');
} else { }
console.error(
"Could not find a default export function in the plugin script."
);
}
} }

View file

@ -1,68 +1,64 @@
"use strict"; 'use strict';
var __defProp = Object.defineProperty; var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty; var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => { var __export = (target, all) => {
for (var name in all) for (var name in all) __defProp(target, name, { get: all[name], enumerable: true });
__defProp(target, name, { get: all[name], enumerable: true });
}; };
var __copyProps = (to, from, except, desc) => { var __copyProps = (to, from, except, desc) => {
if ((from && typeof from === "object") || typeof from === "function") { if ((from && typeof from === 'object') || typeof from === 'function') {
for (let key of __getOwnPropNames(from)) for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except) if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { __defProp(to, key, {
get: () => from[key], get: () => from[key],
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable, enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
}); });
} }
return to; return to;
}; };
var __toCommonJS = (mod) => var __toCommonJS = (mod) => __copyProps(__defProp({}, '__esModule', { value: true }), mod);
__copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts // src/index.ts
var src_exports = {}; var src_exports = {};
__export(src_exports, { __export(src_exports, {
default: () => src_default, default: () => src_default
}); });
module.exports = __toCommonJS(src_exports); module.exports = __toCommonJS(src_exports);
var import_api = require("@raycast/api"); var import_api = require('@raycast/api');
var command = async () => { var command = async () => {
const now = new Date(); const now = new Date();
const london = now.toLocaleString(void 0, { const london = now.toLocaleString(void 0, {
timeZone: "Europe/London", timeZone: 'Europe/London',
timeStyle: "short", timeStyle: 'short'
}); });
const berlin = now.toLocaleString(void 0, { const berlin = now.toLocaleString(void 0, {
timeZone: "Europe/Berlin", timeZone: 'Europe/Berlin',
timeStyle: "short", timeStyle: 'short'
}); });
const moscow = now.toLocaleString(void 0, { const moscow = now.toLocaleString(void 0, {
timeZone: "Europe/Moscow", timeZone: 'Europe/Moscow',
timeStyle: "short", timeStyle: 'short'
}); });
const india = now.toLocaleString(void 0, { const india = now.toLocaleString(void 0, {
timeZone: "Asia/Kolkata", timeZone: 'Asia/Kolkata',
timeStyle: "short", timeStyle: 'short'
}); });
const subtitle = `\u{1F1EC}\u{1F1E7} ${london} \u{1F1F3}\u{1F1F1}\u{1F1E9}\u{1F1EA}\u{1F1F3}\u{1F1F4}\u{1F1E9}\u{1F1F0}\u{1F1F5}\u{1F1F1} ${berlin} \u{1F1F7}\u{1F1FA} ${moscow} \u{1F1EE}\u{1F1F3} ${india}`; const subtitle = `\u{1F1EC}\u{1F1E7} ${london} \u{1F1F3}\u{1F1F1}\u{1F1E9}\u{1F1EA}\u{1F1F3}\u{1F1F4}\u{1F1E9}\u{1F1F0}\u{1F1F5}\u{1F1F1} ${berlin} \u{1F1F7}\u{1F1FA} ${moscow} \u{1F1EE}\u{1F1F3} ${india}`;
await (0, import_api.updateCommandMetadata)({ subtitle }); await (0, import_api.updateCommandMetadata)({ subtitle });
if ( if (import_api.environment.launchType === import_api.LaunchType.UserInitiated) {
import_api.environment.launchType === import_api.LaunchType.UserInitiated const toast = new import_api.Toast({
) { style: import_api.Toast.Style.Success,
const toast = new import_api.Toast({ title: 'Refreshed!',
style: import_api.Toast.Style.Success, message: subtitle
title: "Refreshed!", });
message: subtitle, toast.primaryAction = {
}); title: 'Copy to Clipboard',
toast.primaryAction = { shortcut: { modifiers: ['cmd', 'shift'], key: 'c' },
title: "Copy to Clipboard", onAction: () => import_api.Clipboard.copy(subtitle)
shortcut: { modifiers: ["cmd", "shift"], key: "c" }, };
onAction: () => import_api.Clipboard.copy(subtitle), await toast.show();
}; }
await toast.show();
}
}; };
var src_default = command; var src_default = command;
// Annotate the CommonJS export names for ESM import in node: // Annotate the CommonJS export names for ESM import in node:

View file

@ -1,67 +1,64 @@
import type { Keyboard } from "@raycast/api"; import type { Keyboard } from '@raycast/api';
type ShortcutParts = { type ShortcutParts = {
modifiers: Keyboard.KeyModifier[]; modifiers: Keyboard.KeyModifier[];
key: Keyboard.KeyEquivalent; key: Keyboard.KeyEquivalent;
}; };
function formatShortcutParts(parts: ShortcutParts, isMac: boolean): string { function formatShortcutParts(parts: ShortcutParts, isMac: boolean): string {
const modifierMap = { const modifierMap = {
mac: { mac: {
cmd: "⌘", cmd: '⌘',
ctrl: "⌃", ctrl: '⌃',
opt: "⌥", opt: '⌥',
shift: "⇧", shift: '⇧'
}, },
other: { other: {
cmd: "Win", cmd: 'Win',
ctrl: "Ctrl", ctrl: 'Ctrl',
opt: "Alt", opt: 'Alt',
shift: "Shift", shift: 'Shift'
}, }
}; };
const keyMap: Partial<Record<Keyboard.KeyEquivalent, string>> = { const keyMap: Partial<Record<Keyboard.KeyEquivalent, string>> = {
return: "⏎", return: '⏎',
enter: "⏎", enter: '⏎',
delete: "⌫", delete: '⌫',
backspace: "⌫", backspace: '⌫',
deleteForward: "⌦", deleteForward: '⌦',
arrowUp: "↑", arrowUp: '↑',
arrowDown: "↓", arrowDown: '↓',
arrowLeft: "←", arrowLeft: '←',
arrowRight: "→", arrowRight: '→',
tab: "⇥", tab: '⇥',
escape: "⎋", escape: '⎋',
space: "␣", space: '␣'
}; };
const currentModifiers = isMac ? modifierMap.mac : modifierMap.other; const currentModifiers = isMac ? modifierMap.mac : modifierMap.other;
const modifierStrings = parts.modifiers.map((mod) => currentModifiers[mod]); const modifierStrings = parts.modifiers.map((mod) => currentModifiers[mod]);
const keyString = keyMap[parts.key] ?? parts.key.toUpperCase(); const keyString = keyMap[parts.key] ?? parts.key.toUpperCase();
const allParts = [...modifierStrings, keyString]; const allParts = [...modifierStrings, keyString];
return allParts.join(" + "); return allParts.join(' + ');
} }
export function shortcutToText( export function shortcutToText(shortcut: Keyboard.Shortcut, forceOS?: 'macOS' | 'windows'): string {
shortcut: Keyboard.Shortcut, const isMac = forceOS
forceOS?: "macOS" | "windows" ? forceOS === 'macOS'
): string { : typeof navigator !== 'undefined' && /Mac/i.test(navigator.platform);
const isMac = forceOS
? forceOS === "macOS"
: typeof navigator !== "undefined" && /Mac/i.test(navigator.platform);
if ("modifiers" in shortcut) { if ('modifiers' in shortcut) {
return formatShortcutParts(shortcut, isMac); return formatShortcutParts(shortcut, isMac);
} else { } else {
if (isMac) { if (isMac) {
return formatShortcutParts(shortcut.macOS, true); return formatShortcutParts(shortcut.macOS, true);
} else { } else {
return formatShortcutParts(shortcut.windows, false); return formatShortcutParts(shortcut.windows, false);
} }
} }
} }

View file

@ -1,19 +1,19 @@
import type { Toast } from "./api/toast"; import type { Toast } from './api/toast';
type Result = { type Result = {
subtitle?: string | null; subtitle?: string | null;
}; };
let results = $state<Result[]>([]); let results = $state<Result[]>([]);
export const getResults = () => results; export const getResults = () => results;
export const setResults = (newResults: Result[]) => { export const setResults = (newResults: Result[]) => {
results = newResults; results = newResults;
}; };
let toast = $state<Toast | null>(null); let toast = $state<Toast | null>(null);
export const getToast = (): Toast | null => toast; export const getToast = (): Toast | null => toast;
export const setToast = (newToast: Toast | null) => { export const setToast = (newToast: Toast | null) => {
toast = newToast; toast = newToast;
}; };

View file

@ -1,13 +1,13 @@
import { clsx, type ClassValue } from "clsx"; import { clsx, type ClassValue } from 'clsx';
import { twMerge } from "tailwind-merge"; import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T; export type WithoutChild<T> = T extends { child?: any } ? Omit<T, 'child'> : T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T; export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, 'children'> : T;
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>; export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null }; export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };

View file

@ -1,285 +1,274 @@
<script lang="ts"> <script lang="ts">
import { Command, type Child } from "@tauri-apps/plugin-shell"; import { Command, type Child } from '@tauri-apps/plugin-shell';
import { SvelteMap } from "svelte/reactivity"; import { SvelteMap } from 'svelte/reactivity';
import { Unpackr } from "msgpackr"; import { Unpackr } from 'msgpackr';
import { tick } from "svelte"; import { tick } from 'svelte';
import { VList } from "virtua/svelte"; import { VList } from 'virtua/svelte';
interface UINode { interface UINode {
id: number; id: number;
type: string; type: string;
props: Record<string, any>; props: Record<string, any>;
children: number[]; children: number[];
} }
let uiTree: SvelteMap<number, UINode> = $state(new SvelteMap()); let uiTree: SvelteMap<number, UINode> = $state(new SvelteMap());
let rootNodeId: number | null = $state(null); let rootNodeId: number | null = $state(null);
let sidecarLogs: string[] = $state([]); let sidecarLogs: string[] = $state([]);
let sidecarChild: Child | null = null; let sidecarChild: Child | null = null;
let updateCounter = $state(0); let updateCounter = $state(0);
const unpackr = new Unpackr(); const unpackr = new Unpackr();
type ListItem = { type ListItem = {
id: number; id: number;
type: "header" | "item"; type: 'header' | 'item';
props: Record<string, any>; props: Record<string, any>;
height: number; height: number;
}; };
let flatList: ListItem[] = $state([]); let flatList: ListItem[] = $state([]);
$effect(() => { $effect(() => {
if (!rootNodeId) { if (!rootNodeId) {
flatList = []; flatList = [];
return; return;
} }
const newFlatList: ListItem[] = []; const newFlatList: ListItem[] = [];
const root = uiTree.get(rootNodeId); const root = uiTree.get(rootNodeId);
if (!root) return; if (!root) return;
const HEADER_HEIGHT = 34; const HEADER_HEIGHT = 34;
const ITEM_HEIGHT = 40; const ITEM_HEIGHT = 40;
for (const childId of root.children) { for (const childId of root.children) {
const sectionNode = uiTree.get(childId); const sectionNode = uiTree.get(childId);
if (sectionNode && sectionNode.type === "ListSection") { if (sectionNode && sectionNode.type === 'ListSection') {
newFlatList.push({ newFlatList.push({
id: sectionNode.id, id: sectionNode.id,
type: "header", type: 'header',
props: sectionNode.props, props: sectionNode.props,
height: HEADER_HEIGHT, height: HEADER_HEIGHT
}); });
for (const itemId of sectionNode.children) { for (const itemId of sectionNode.children) {
const itemNode = uiTree.get(itemId); const itemNode = uiTree.get(itemId);
if (itemNode) { if (itemNode) {
newFlatList.push({ newFlatList.push({
id: itemNode.id, id: itemNode.id,
type: "item", type: 'item',
props: itemNode.props, props: itemNode.props,
height: ITEM_HEIGHT, height: ITEM_HEIGHT
}); });
} }
} }
} }
} }
flatList = newFlatList; flatList = newFlatList;
}); });
$effect(() => { $effect(() => {
let receiveBuffer = Buffer.alloc(0); let receiveBuffer = Buffer.alloc(0);
function processReceiveBuffer() { function processReceiveBuffer() {
while (receiveBuffer.length >= 4) { while (receiveBuffer.length >= 4) {
const messageLength = receiveBuffer.readUInt32BE(0); const messageLength = receiveBuffer.readUInt32BE(0);
const totalLength = 4 + messageLength; const totalLength = 4 + messageLength;
if (receiveBuffer.length >= totalLength) { if (receiveBuffer.length >= totalLength) {
const messagePayload = receiveBuffer.subarray(4, totalLength); const messagePayload = receiveBuffer.subarray(4, totalLength);
receiveBuffer = receiveBuffer.subarray(totalLength); receiveBuffer = receiveBuffer.subarray(totalLength);
try { try {
const message = unpackr.unpack(messagePayload); const message = unpackr.unpack(messagePayload);
handleSidecarMessage(message); handleSidecarMessage(message);
} catch (e) { } catch (e) {
console.error("Failed to unpack sidecar message:", e); console.error('Failed to unpack sidecar message:', e);
} }
} else { } else {
break; break;
} }
} }
} }
async function connectAndRun() { async function connectAndRun() {
const command = Command.sidecar("binaries/app", undefined, { const command = Command.sidecar('binaries/app', undefined, {
encoding: "raw", encoding: 'raw'
}); });
command.stdout.on("data", (chunk) => { command.stdout.on('data', (chunk) => {
try { try {
receiveBuffer = Buffer.concat([receiveBuffer, Buffer.from(chunk)]); receiveBuffer = Buffer.concat([receiveBuffer, Buffer.from(chunk)]);
processReceiveBuffer(); processReceiveBuffer();
} catch (e) { } catch (e) {
console.error("Failed to parse sidecar message:", chunk, e); console.error('Failed to parse sidecar message:', chunk, e);
} }
}); });
command.stderr.on("data", (line) => { command.stderr.on('data', (line) => {
sidecarLogs = [...sidecarLogs, `STDERR: ${line}`]; sidecarLogs = [...sidecarLogs, `STDERR: ${line}`];
}); });
sidecarChild = await command.spawn(); sidecarChild = await command.spawn();
sidecarLogs = [ sidecarLogs = [...sidecarLogs, `Sidecar spawned with PID: ${sidecarChild.pid}`];
...sidecarLogs, if (sidecarChild) {
`Sidecar spawned with PID: ${sidecarChild.pid}`, sidecarChild.write(JSON.stringify({ action: 'run-plugin' }) + '\n');
]; }
if (sidecarChild) { }
sidecarChild.write(JSON.stringify({ action: "run-plugin" }) + "\n"); connectAndRun();
} return () => {
} console.log('Component unmounting, killing sidecar...');
connectAndRun(); sidecarChild?.kill();
return () => { };
console.log("Component unmounting, killing sidecar..."); });
sidecarChild?.kill();
};
});
function sendToSidecar(message: object) { function sendToSidecar(message: object) {
if (sidecarChild) { if (sidecarChild) {
sidecarChild.write(JSON.stringify(message) + "\n"); sidecarChild.write(JSON.stringify(message) + '\n');
} }
} }
function processSingleCommand( function processSingleCommand(
command: any, command: any,
tempTree: Map<number, UINode>, tempTree: Map<number, UINode>,
tempState: { rootNodeId: number | null }, tempState: { rootNodeId: number | null },
getMutableNode: (id: number) => UINode | undefined getMutableNode: (id: number) => UINode | undefined
) { ) {
switch (command.type) { switch (command.type) {
case "REPLACE_CHILDREN": { case 'REPLACE_CHILDREN': {
const { parentId, childrenIds } = command.payload; const { parentId, childrenIds } = command.payload;
const parentNode = getMutableNode(parentId); const parentNode = getMutableNode(parentId);
if (parentNode) { if (parentNode) {
parentNode.children = childrenIds; parentNode.children = childrenIds;
} }
break; break;
} }
case "log": case 'log':
console.log("SIDECAR:", command.payload); console.log('SIDECAR:', command.payload);
sidecarLogs = [...sidecarLogs, command.payload]; sidecarLogs = [...sidecarLogs, command.payload];
break; break;
case "CREATE_TEXT_INSTANCE": case 'CREATE_TEXT_INSTANCE':
case "CREATE_INSTANCE": { case 'CREATE_INSTANCE': {
const { id, type, props } = command.payload; const { id, type, props } = command.payload;
tempTree.set(id, { id, type, props, children: [] }); tempTree.set(id, { id, type, props, children: [] });
break; break;
} }
case "UPDATE_PROPS": { case 'UPDATE_PROPS': {
const { id, props } = command.payload; const { id, props } = command.payload;
const node = getMutableNode(id); const node = getMutableNode(id);
if (node) { if (node) {
Object.assign(node.props, props); Object.assign(node.props, props);
} }
break; break;
} }
case "APPEND_CHILD": { case 'APPEND_CHILD': {
const { parentId, childId } = command.payload; const { parentId, childId } = command.payload;
if (parentId === "root") { if (parentId === 'root') {
tempState.rootNodeId = childId; tempState.rootNodeId = childId;
} else { } else {
const parentNode = getMutableNode(parentId); const parentNode = getMutableNode(parentId);
if (parentNode) { if (parentNode) {
const existingIdx = parentNode.children.indexOf(childId); const existingIdx = parentNode.children.indexOf(childId);
if (existingIdx > -1) parentNode.children.splice(existingIdx, 1); if (existingIdx > -1) parentNode.children.splice(existingIdx, 1);
parentNode.children.push(childId); parentNode.children.push(childId);
} }
} }
break; break;
} }
case "REMOVE_CHILD": { case 'REMOVE_CHILD': {
const { parentId, childId } = command.payload; const { parentId, childId } = command.payload;
const parentNode = getMutableNode(parentId); const parentNode = getMutableNode(parentId);
if (parentNode) { if (parentNode) {
const index = parentNode.children.indexOf(childId); const index = parentNode.children.indexOf(childId);
if (index > -1) parentNode.children.splice(index, 1); if (index > -1) parentNode.children.splice(index, 1);
} }
break; break;
} }
case "INSERT_BEFORE": { case 'INSERT_BEFORE': {
const { parentId, childId, beforeId } = command.payload; const { parentId, childId, beforeId } = command.payload;
const parentNode = getMutableNode(parentId); const parentNode = getMutableNode(parentId);
if (parentNode) { if (parentNode) {
const oldIndex = parentNode.children.indexOf(childId); const oldIndex = parentNode.children.indexOf(childId);
if (oldIndex > -1) parentNode.children.splice(oldIndex, 1); if (oldIndex > -1) parentNode.children.splice(oldIndex, 1);
const insertIndex = parentNode.children.indexOf(beforeId); const insertIndex = parentNode.children.indexOf(beforeId);
if (insertIndex > -1) { if (insertIndex > -1) {
parentNode.children.splice(insertIndex, 0, childId); parentNode.children.splice(insertIndex, 0, childId);
} else { } else {
parentNode.children.push(childId); parentNode.children.push(childId);
} }
} }
break; break;
} }
} }
} }
function handleSidecarMessage(message: any) { function handleSidecarMessage(message: any) {
const commands = const commands = message.type === 'BATCH_UPDATE' ? message.payload : [message];
message.type === "BATCH_UPDATE" ? message.payload : [message]; if (commands.length === 0) {
if (commands.length === 0) { updateCounter++;
updateCounter++; return;
return; }
} const tempTree = new Map(uiTree);
const tempTree = new Map(uiTree); const tempState = { rootNodeId: rootNodeId };
const tempState = { rootNodeId: rootNodeId }; const mutatedIds = new Set<number>();
const mutatedIds = new Set<number>(); const getMutableNode = (id: number): UINode | undefined => {
const getMutableNode = (id: number): UINode | undefined => { if (!mutatedIds.has(id)) {
if (!mutatedIds.has(id)) { const originalNode = tempTree.get(id);
const originalNode = tempTree.get(id); if (!originalNode) return undefined;
if (!originalNode) return undefined; const clonedNode = {
const clonedNode = { ...originalNode,
...originalNode, props: { ...originalNode.props },
props: { ...originalNode.props }, children: [...originalNode.children]
children: [...originalNode.children], };
}; tempTree.set(id, clonedNode);
tempTree.set(id, clonedNode); mutatedIds.add(id);
mutatedIds.add(id); return clonedNode;
return clonedNode; }
} return tempTree.get(id);
return tempTree.get(id); };
};
for (const command of commands) { for (const command of commands) {
processSingleCommand(command, tempTree, tempState, getMutableNode); processSingleCommand(command, tempTree, tempState, getMutableNode);
} }
uiTree = new SvelteMap(tempTree); uiTree = new SvelteMap(tempTree);
rootNodeId = tempState.rootNodeId; rootNodeId = tempState.rootNodeId;
updateCounter++; updateCounter++;
} }
function dispatchEvent(instanceId: number, handlerName: string, args: any[]) { function dispatchEvent(instanceId: number, handlerName: string, args: any[]) {
console.log( console.log(`[EVENT] Dispatching '${handlerName}' to instance ${instanceId}`);
`[EVENT] Dispatching '${handlerName}' to instance ${instanceId}` sendToSidecar({
); action: 'dispatch-event',
sendToSidecar({ payload: { instanceId, handlerName, args }
action: "dispatch-event", });
payload: { instanceId, handlerName, args }, }
});
}
</script> </script>
<main class="flex grow flex-col h-screen"> <main class="flex h-screen grow flex-col">
{#if rootNodeId} {#if rootNodeId}
{@const rootNode = uiTree.get(rootNodeId)} {@const rootNode = uiTree.get(rootNodeId)}
{#if rootNode?.type === "List"} {#if rootNode?.type === 'List'}
<div class="flex h-full flex-col"> <div class="flex h-full flex-col">
<input <input
type="text" type="text"
class="w-full border-b border-gray-300 px-4 py-3 text-lg focus:border-blue-500 focus:outline-none" class="w-full border-b border-gray-300 px-4 py-3 text-lg focus:border-blue-500 focus:outline-none"
placeholder="Search Emojis..." placeholder="Search Emojis..."
oninput={(e) => oninput={(e) => dispatchEvent(rootNode.id, 'onSearchTextChange', [e.currentTarget.value])}
dispatchEvent(rootNode.id, "onSearchTextChange", [ />
e.currentTarget.value,
])}
/>
<div class="flex-grow"> <div class="flex-grow">
<VList data={flatList} getKey={(item) => item.id} class="h-full"> <VList data={flatList} getKey={(item) => item.id} class="h-full">
{#snippet children(item)} {#snippet children(item)}
{#if item.type === "header"} {#if item.type === 'header'}
<h3 <h3 class="px-4 pt-2.5 pb-1 text-xs font-semibold text-gray-500 uppercase">
class="px-4 pb-1 pt-2.5 text-xs font-semibold uppercase text-gray-500" {item.props.title}
> </h3>
{item.props.title} {:else if item.type === 'item'}
</h3> <div class="flex items-center gap-3 px-4 py-2">
{:else if item.type === "item"} <span class="text-lg">{item.props.icon}</span>
<div class="flex items-center gap-3 px-4 py-2"> <span>{item.props.title}</span>
<span class="text-lg">{item.props.icon}</span> </div>
<span>{item.props.title}</span> {/if}
</div> {/snippet}
{/if} </VList>
{/snippet} </div>
</VList> </div>
</div> {/if}
</div> {/if}
{/if}
{/if}
</main> </main>

View file

@ -1,15 +1,15 @@
// Tauri doesn't have a Node.js server to do proper SSR // Tauri doesn't have a Node.js server to do proper SSR
// so we will use adapter-static to prerender the app (SSG) // so we will use adapter-static to prerender the app (SSG)
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info // See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
import adapter from "@sveltejs/adapter-static"; import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
preprocess: vitePreprocess(), preprocess: vitePreprocess(),
kit: { kit: {
adapter: adapter(), adapter: adapter()
}, }
}; };
export default config; export default config;

View file

@ -1,19 +1,19 @@
{ {
"extends": "./.svelte-kit/tsconfig.json", "extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"moduleResolution": "bundler" "moduleResolution": "bundler"
} }
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
// //
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in // from the referenced tsconfig.json - TypeScript does not merge them in
} }

View file

@ -1,33 +1,33 @@
import { defineConfig } from "vite"; import { defineConfig } from 'vite';
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from "@sveltejs/kit/vite"; import { sveltekit } from '@sveltejs/kit/vite';
import { nodePolyfills } from "vite-plugin-node-polyfills"; import { nodePolyfills } from 'vite-plugin-node-polyfills';
const host = process.env.TAURI_DEV_HOST; const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(async () => ({ export default defineConfig(async () => ({
plugins: [tailwindcss(), sveltekit(), nodePolyfills()], plugins: [tailwindcss(), sveltekit(), nodePolyfills()],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// //
// 1. prevent vite from obscuring rust errors // 1. prevent vite from obscuring rust errors
clearScreen: false, clearScreen: false,
// 2. tauri expects a fixed port, fail if that port is not available // 2. tauri expects a fixed port, fail if that port is not available
server: { server: {
port: 1420, port: 1420,
strictPort: true, strictPort: true,
host: host || false, host: host || false,
hmr: host hmr: host
? { ? {
protocol: "ws", protocol: 'ws',
host, host,
port: 1421, port: 1421
} }
: undefined, : undefined,
watch: { watch: {
// 3. tell vite to ignore watching `src-tauri` // 3. tell vite to ignore watching `src-tauri`
ignored: ["**/src-tauri/**"], ignored: ['**/src-tauri/**']
}, }
}, }
})); }));