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/dist
src-tauri
package-lock.json
pnpm-lock.yaml
yarn.lock
bun.lock
bun.lockb

View file

@ -1,7 +1,3 @@
{
"recommendations": [
"svelte.svelte-vscode",
"tauri-apps.tauri-vscode",
"rust-lang.rust-analyzer"
]
"recommendations": ["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",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"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",
"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"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.11.1",
"devDependencies": {
"@types/node": "^24.0.0",
"@types/react": "^19.1.7",
"@types/react-reconciler": "^0.32.0",
"@yao-pkg/pkg": "^6.5.1",
"esbuild": "^0.25.5",
"typescript": "^5.8.3"
},
"dependencies": {
"msgpackr": "^1.11.4",
"react": "^19.1.0",
"react-reconciler": "^0.32.0"
}
"name": "sidecar",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"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",
"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"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.11.1",
"devDependencies": {
"@types/node": "^24.0.0",
"@types/react": "^19.1.7",
"@types/react-reconciler": "^0.32.0",
"@yao-pkg/pkg": "^6.5.1",
"esbuild": "^0.25.5",
"typescript": "^5.8.3"
},
"dependencies": {
"msgpackr": "^1.11.4",
"react": "^19.1.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" {
const content: string;
export default content;
declare module '*.txt' {
const content: string;
export default content;
}
declare global {
namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
}
namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
}
}

View file

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

View file

@ -1,65 +1,60 @@
import { createInterface } from "readline";
import { writeLog, writeOutput } from "./io";
import { runPlugin } from "./plugin";
import { instances } from "./state";
import { batchedUpdates } from "./reconciler";
import { createInterface } from 'readline';
import { writeLog, writeOutput } from './io';
import { runPlugin } from './plugin';
import { instances } from './state';
import { batchedUpdates } from './reconciler';
process.on("unhandledRejection", (reason: unknown) => {
writeLog(`--- UNHANDLED PROMISE REJECTION ---`);
const stack =
reason && typeof reason === "object" && "stack" in reason
? reason.stack
: reason;
writeLog(stack);
process.on('unhandledRejection', (reason: unknown) => {
writeLog(`--- UNHANDLED PROMISE REJECTION ---`);
const stack = reason && typeof reason === 'object' && 'stack' in reason ? reason.stack : reason;
writeLog(stack);
});
const rl = createInterface({ input: process.stdin });
rl.on("line", (line) => {
batchedUpdates(() => {
try {
const command: { action: string; payload: unknown } = JSON.parse(line);
rl.on('line', (line) => {
batchedUpdates(() => {
try {
const command: { action: string; payload: unknown } = JSON.parse(line);
switch (command.action) {
case "run-plugin":
runPlugin();
break;
case "dispatch-event": {
const { instanceId, handlerName, args } = command.payload as {
instanceId: number;
handlerName: string;
args: unknown[];
};
switch (command.action) {
case 'run-plugin':
runPlugin();
break;
case 'dispatch-event': {
const { instanceId, handlerName, args } = command.payload as {
instanceId: number;
handlerName: string;
args: unknown[];
};
const instance = instances.get(instanceId);
if (!instance) {
writeLog(`Instance ${instanceId} not found.`);
return;
}
const instance = instances.get(instanceId);
if (!instance) {
writeLog(`Instance ${instanceId} not found.`);
return;
}
const handler = instance._internalFiber?.memoizedProps?.[handlerName];
const handler = instance._internalFiber?.memoizedProps?.[handlerName];
if (typeof handler === "function") {
handler(...args);
} else {
writeLog(
`Handler ${handlerName} not found on instance ${instanceId}`
);
}
break;
}
default:
writeLog(`Unknown command action: ${command.action}`);
}
} catch (err: unknown) {
const error =
err instanceof Error
? { message: err.message, stack: err.stack }
: { message: String(err) };
writeLog(`ERROR: ${error.message} \n ${error.stack ?? ""}`);
writeOutput({ type: "error", payload: error.message });
}
});
if (typeof handler === 'function') {
handler(...args);
} else {
writeLog(`Handler ${handlerName} not found on instance ${instanceId}`);
}
break;
}
default:
writeLog(`Unknown command action: ${command.action}`);
}
} catch (err: unknown) {
const error =
err instanceof Error
? { message: err.message, stack: err.stack }
: { message: String(err) };
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();
export const writeOutput = (data: object): void => {
try {
const payload = packr.pack(data);
const header = Buffer.alloc(4);
header.writeUInt32BE(payload.length);
try {
const payload = packr.pack(data);
const header = Buffer.alloc(4);
header.writeUInt32BE(payload.length);
process.stdout.write(header);
process.stdout.write(payload);
} catch (e: unknown) {
const errorString = e instanceof Error ? e.toString() : String(e);
const errorPayload = packr.pack({ type: "log", payload: errorString });
const errorHeader = Buffer.alloc(4);
errorHeader.writeUInt32BE(errorPayload.length);
process.stdout.write(errorHeader);
process.stdout.write(errorPayload);
}
process.stdout.write(header);
process.stdout.write(payload);
} catch (e: unknown) {
const errorString = e instanceof Error ? e.toString() : String(e);
const errorPayload = packr.pack({ type: 'log', payload: errorString });
const errorHeader = Buffer.alloc(4);
errorHeader.writeUInt32BE(errorPayload.length);
process.stdout.write(errorHeader);
process.stdout.write(errorPayload);
}
};
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 { jsx } from "react/jsx-runtime";
import plugin from "../dist/plugin/emoji.txt";
import { updateContainer } from "./reconciler";
import { writeLog } from "./io";
import React from 'react';
import { jsx } from 'react/jsx-runtime';
import plugin from '../dist/plugin/emoji.txt';
import { updateContainer } from './reconciler';
import { writeLog } from './io';
const createPluginRequire =
() =>
(moduleName: string): unknown => {
if (moduleName === "react") {
return React;
}
() =>
(moduleName: string): unknown => {
if (moduleName === 'react') {
return React;
}
if (moduleName.startsWith("@raycast/api")) {
const storage = new Map<string, string>();
const LocalStorage = {
getItem: async (key: string) => storage.get(key),
setItem: async (key: string, value: string) => storage.set(key, value),
removeItem: async (key: string) => storage.delete(key),
clear: async () => storage.clear(),
};
if (moduleName.startsWith('@raycast/api')) {
const storage = new Map<string, string>();
const LocalStorage = {
getItem: async (key: string) => storage.get(key),
setItem: async (key: string, value: string) => storage.set(key, value),
removeItem: async (key: string) => storage.delete(key),
clear: async () => storage.clear()
};
const createWrapperComponent =
(name: string) =>
({ children, ...rest }: { children?: React.ReactNode }) =>
jsx(name, { ...rest, children });
const createWrapperComponent =
(name: string) =>
({ children, ...rest }: { children?: React.ReactNode }) =>
jsx(name, { ...rest, children });
const ListComponent = createWrapperComponent("List");
const ListSectionComponent = createWrapperComponent("ListSection");
const ListDropdownComponent = createWrapperComponent("ListDropdown");
const ActionPanelComponent = createWrapperComponent("ActionPanel");
const ActionPanelSectionComponent =
createWrapperComponent("ActionPanelSection");
const ListComponent = createWrapperComponent('List');
const ListSectionComponent = createWrapperComponent('ListSection');
const ListDropdownComponent = createWrapperComponent('ListDropdown');
const ActionPanelComponent = createWrapperComponent('ActionPanel');
const ActionPanelSectionComponent = createWrapperComponent('ActionPanelSection');
Object.assign(ListComponent, {
Item: "ListItem",
Section: ListSectionComponent,
Dropdown: ListDropdownComponent,
});
Object.assign(ListDropdownComponent, { Item: "ListDropdownItem" });
Object.assign(ActionPanelComponent, {
Section: ActionPanelSectionComponent,
});
Object.assign(ListComponent, {
Item: 'ListItem',
Section: ListSectionComponent,
Dropdown: ListDropdownComponent
});
Object.assign(ListDropdownComponent, { Item: 'ListDropdownItem' });
Object.assign(ActionPanelComponent, {
Section: ActionPanelSectionComponent
});
return {
LocalStorage,
environment: {
assetsPath:
"/home/byte/code/raycast-linux/sidecar/dist/plugin/assets/",
},
getPreferenceValues: () => ({
primaryAction: "paste",
unicodeVersion: "14.0",
shortCodes: true,
}),
usePersistentState: <T>(
key: string,
initialValue: T
): [T, React.Dispatch<React.SetStateAction<T>>, boolean] => {
const [state, setState] = React.useState(initialValue);
return [state, setState, false];
},
List: ListComponent,
ActionPanel: ActionPanelComponent,
Action: {
Paste: "Action.Paste",
CopyToClipboard: "Action.CopyToClipboard",
OpenInBrowser: "Action.OpenInBrowser",
},
};
}
return {
LocalStorage,
environment: {
assetsPath: '/home/byte/code/raycast-linux/sidecar/dist/plugin/assets/'
},
getPreferenceValues: () => ({
primaryAction: 'paste',
unicodeVersion: '14.0',
shortCodes: true
}),
usePersistentState: <T>(
key: string,
initialValue: T
): [T, React.Dispatch<React.SetStateAction<T>>, boolean] => {
const [state, setState] = React.useState(initialValue);
return [state, setState, false];
},
List: ListComponent,
ActionPanel: ActionPanelComponent,
Action: {
Paste: 'Action.Paste',
CopyToClipboard: 'Action.CopyToClipboard',
OpenInBrowser: 'Action.OpenInBrowser'
}
};
}
return require(moduleName);
};
return require(moduleName);
};
export const runPlugin = (): void => {
const scriptText = plugin;
const pluginModule = {
exports: {} as { default: React.ComponentType | null },
};
const scriptFunction = new Function(
"require",
"module",
"exports",
"React",
scriptText
);
const scriptText = plugin;
const pluginModule = {
exports: {} as { default: React.ComponentType | null }
};
const scriptFunction = new Function('require', 'module', 'exports', 'React', scriptText);
scriptFunction(
createPluginRequire(),
pluginModule,
pluginModule.exports,
React
);
scriptFunction(createPluginRequire(), pluginModule, pluginModule.exports, React);
const PluginRootComponent = pluginModule.exports.default;
const PluginRootComponent = pluginModule.exports.default;
if (!PluginRootComponent) {
throw new Error("Plugin did not export a default component.");
}
if (!PluginRootComponent) {
throw new Error('Plugin did not export a default component.');
}
writeLog("Plugin loaded. Initializing React render...");
const AppElement = React.createElement(PluginRootComponent);
updateContainer(AppElement, () => {
writeLog("Initial render complete");
});
writeLog('Plugin loaded. Initializing React render...');
const AppElement = React.createElement(PluginRootComponent);
updateContainer(AppElement, () => {
writeLog('Initial render complete');
});
};

View file

@ -1,34 +1,31 @@
import Reconciler, { type RootTag } from "react-reconciler";
import type React from "react";
import { root } from "./state";
import { hostConfig } from "./hostConfig";
import { writeLog } from "./io";
import Reconciler, { type RootTag } from 'react-reconciler';
import type React from 'react';
import { root } from './state';
import { hostConfig } from './hostConfig';
import { writeLog } from './io';
const reconciler = Reconciler(hostConfig);
const onRecoverableError = (error: Error) => {
writeLog(`--- REACT RECOVERABLE ERROR ---`);
writeLog(`Error: ${error.message}`);
writeLog(`--- REACT RECOVERABLE ERROR ---`);
writeLog(`Error: ${error.message}`);
};
export const container = reconciler.createContainer(
root as unknown as RootTag,
0,
null,
false,
null,
"",
onRecoverableError,
null
root as unknown as RootTag,
0,
null,
false,
null,
'',
onRecoverableError,
null
);
export const updateContainer = (
element: React.ReactElement,
callback?: () => void
) => {
reconciler.updateContainer(element, container, null, callback);
export const updateContainer = (element: React.ReactElement, callback?: () => void) => {
reconciler.updateContainer(element, container, null, callback);
};
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 root: Container = { id: "root", children: [] };
export const root: Container = { id: 'root', children: [] };
let instanceCounter = 0;
export const getNextInstanceId = (): number => ++instanceCounter;
@ -9,9 +9,9 @@ export const getNextInstanceId = (): number => ++instanceCounter;
export let commitBuffer: Commit[] = [];
export const clearCommitBuffer = (): void => {
commitBuffer = [];
commitBuffer = [];
};
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 Reconciler from "react-reconciler";
import type React from 'react';
import type Reconciler from 'react-reconciler';
export type ComponentType = string | React.ComponentType<any>;
export type ComponentProps = Record<string, unknown>;
export interface BaseInstance {
id: number;
_internalFiber?: Reconciler.Fiber;
id: number;
_internalFiber?: Reconciler.Fiber;
}
export interface RaycastInstance extends BaseInstance {
type: ComponentType;
props: ComponentProps;
children: (RaycastInstance | TextInstance)[];
type: ComponentType;
props: ComponentProps;
children: (RaycastInstance | TextInstance)[];
}
export interface TextInstance extends BaseInstance {
type: "TEXT";
text: string;
type: 'TEXT';
text: string;
}
export interface Container {
id: "root";
children: (RaycastInstance | TextInstance)[];
id: 'root';
children: (RaycastInstance | TextInstance)[];
}
export type AnyInstance = RaycastInstance | TextInstance;
@ -30,12 +30,12 @@ export type ParentInstance = RaycastInstance | Container;
export type UpdatePayload = Record<string, unknown>;
export interface Commit {
type: string;
payload: unknown;
type: string;
payload: unknown;
}
export interface SerializedReactElement {
$$typeof: "react.element.serialized";
type: string;
props: Record<string, unknown>;
$$typeof: 'react.element.serialized';
type: string;
props: Record<string, unknown>;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,36 +1,36 @@
<script lang="ts" module>
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
import { type VariantProps, tv } from "tailwind-variants";
import { cn, type WithElementRef } from '$lib/utils.js';
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
import { type VariantProps, tv } from 'tailwind-variants';
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",
variants: {
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:
"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:
"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",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
'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',
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline'
},
size: {
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",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
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',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9'
}
},
defaultVariants: {
variant: "default",
size: "default",
},
variant: 'default',
size: 'default'
}
});
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
WithElementRef<HTMLAnchorAttributes> & {
@ -42,11 +42,11 @@
<script lang="ts">
let {
class: className,
variant = "default",
size = "default",
variant = 'default',
size = 'default',
ref = $bindable(null),
href = undefined,
type = "button",
type = 'button',
disabled,
children,
...restProps
@ -60,7 +60,7 @@
class={cn(buttonVariants({ variant, size }), className)}
href={disabled ? undefined : href}
aria-disabled={disabled}
role={disabled ? "link" : undefined}
role={disabled ? 'link' : undefined}
tabindex={disabled ? -1 : undefined}
{...restProps}
>

View file

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

View file

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

View file

@ -1,56 +1,50 @@
<script lang="ts">
import type {
HTMLInputAttributes,
HTMLInputTypeAttribute,
} from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLInputAttributes, 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<
Omit<HTMLInputAttributes, "type"> &
(
| { type: "file"; files?: FileList }
| { type?: InputType; files?: undefined }
)
>;
type Props = WithElementRef<
Omit<HTMLInputAttributes, 'type'> &
({ type: 'file'; files?: FileList } | { type?: InputType; files?: undefined })
>;
let {
ref = $bindable(null),
value = $bindable(),
type,
files = $bindable(),
class: className,
...restProps
}: Props = $props();
let {
ref = $bindable(null),
value = $bindable(),
type,
files = $bindable(),
class: className,
...restProps
}: Props = $props();
</script>
{#if type === "file"}
<input
bind:this={ref}
data-slot="input"
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",
"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",
className
)}
type="file"
bind:files
bind:value
{...restProps}
/>
{#if type === 'file'}
<input
bind:this={ref}
data-slot="input"
class={cn(
'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]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className
)}
type="file"
bind:files
bind:value
{...restProps}
/>
{:else}
<input
bind:this={ref}
data-slot="input"
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",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
)}
{type}
bind:value
{...restProps}
/>
<input
bind:this={ref}
data-slot="input"
class={cn(
'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',
className
)}
{type}
bind:value
{...restProps}
/>
{/if}

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { Separator as SeparatorPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
import { Separator as SeparatorPrimitive } from 'bits-ui';
import { cn } from '$lib/utils.js';
let {
ref = $bindable(null),
@ -13,7 +13,7 @@
bind:ref
data-slot="separator"
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
)}
{...restProps}

View file

@ -1,51 +1,45 @@
import { mockRaycastApi } from "./api";
import plugin from "./plugin.js?raw";
import { mockRaycastApi } from './api';
import plugin from './plugin.js?raw';
declare global {
interface Window {
require: (moduleName: string) => unknown;
module: { exports: { default?: () => unknown } };
}
interface Window {
require: (moduleName: string) => unknown;
module: { exports: { default?: () => unknown } };
}
}
window.require = function (moduleName) {
console.log(`Plugin is requesting module: ${moduleName}`);
if (moduleName === "@raycast/api") {
return mockRaycastApi;
}
throw new Error(
`Module not found: ${moduleName}. Our fake 'require' is very limited!`
);
console.log(`Plugin is requesting module: ${moduleName}`);
if (moduleName === '@raycast/api') {
return mockRaycastApi;
}
throw new Error(`Module not found: ${moduleName}. Our fake 'require' is very limited!`);
};
window.module = {
exports: {},
exports: {}
};
export async function runPlugin() {
console.log("Requesting plugin script from Rust backend...");
const scriptText = plugin;
console.log('Requesting plugin script from Rust backend...');
const scriptText = plugin;
console.log("Executing plugin script in a try/catch block...");
try {
// TOOD: don't use eval
eval(scriptText);
} catch (e) {
console.error("Error evaluating plugin script:", e);
return;
}
console.log('Executing plugin script in a try/catch block...');
try {
// TOOD: don't use eval
eval(scriptText);
} catch (e) {
console.error('Error evaluating plugin script:', e);
return;
}
const pluginMainFunction = window.module.exports.default;
const pluginMainFunction = window.module.exports.default;
if (pluginMainFunction && typeof pluginMainFunction === "function") {
console.log(
"Plugin script loaded successfully. Running its main command..."
);
await pluginMainFunction();
console.log("Plugin main command finished.");
} else {
console.error(
"Could not find a default export function in the plugin script."
);
}
if (pluginMainFunction && typeof pluginMainFunction === 'function') {
console.log('Plugin script loaded successfully. Running its main command...');
await pluginMainFunction();
console.log('Plugin main command finished.');
} 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 __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
for (var name in all) __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if ((from && typeof from === "object") || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, {
get: () => from[key],
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable,
});
}
return to;
if ((from && typeof from === 'object') || typeof from === 'function') {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, {
get: () => from[key],
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toCommonJS = (mod) =>
__copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __toCommonJS = (mod) => __copyProps(__defProp({}, '__esModule', { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
default: () => src_default,
default: () => src_default
});
module.exports = __toCommonJS(src_exports);
var import_api = require("@raycast/api");
var import_api = require('@raycast/api');
var command = async () => {
const now = new Date();
const london = now.toLocaleString(void 0, {
timeZone: "Europe/London",
timeStyle: "short",
});
const berlin = now.toLocaleString(void 0, {
timeZone: "Europe/Berlin",
timeStyle: "short",
});
const moscow = now.toLocaleString(void 0, {
timeZone: "Europe/Moscow",
timeStyle: "short",
});
const india = now.toLocaleString(void 0, {
timeZone: "Asia/Kolkata",
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}`;
await (0, import_api.updateCommandMetadata)({ subtitle });
if (
import_api.environment.launchType === import_api.LaunchType.UserInitiated
) {
const toast = new import_api.Toast({
style: import_api.Toast.Style.Success,
title: "Refreshed!",
message: subtitle,
});
toast.primaryAction = {
title: "Copy to Clipboard",
shortcut: { modifiers: ["cmd", "shift"], key: "c" },
onAction: () => import_api.Clipboard.copy(subtitle),
};
await toast.show();
}
const now = new Date();
const london = now.toLocaleString(void 0, {
timeZone: 'Europe/London',
timeStyle: 'short'
});
const berlin = now.toLocaleString(void 0, {
timeZone: 'Europe/Berlin',
timeStyle: 'short'
});
const moscow = now.toLocaleString(void 0, {
timeZone: 'Europe/Moscow',
timeStyle: 'short'
});
const india = now.toLocaleString(void 0, {
timeZone: 'Asia/Kolkata',
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}`;
await (0, import_api.updateCommandMetadata)({ subtitle });
if (import_api.environment.launchType === import_api.LaunchType.UserInitiated) {
const toast = new import_api.Toast({
style: import_api.Toast.Style.Success,
title: 'Refreshed!',
message: subtitle
});
toast.primaryAction = {
title: 'Copy to Clipboard',
shortcut: { modifiers: ['cmd', 'shift'], key: 'c' },
onAction: () => import_api.Clipboard.copy(subtitle)
};
await toast.show();
}
};
var src_default = command;
// 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 = {
modifiers: Keyboard.KeyModifier[];
key: Keyboard.KeyEquivalent;
modifiers: Keyboard.KeyModifier[];
key: Keyboard.KeyEquivalent;
};
function formatShortcutParts(parts: ShortcutParts, isMac: boolean): string {
const modifierMap = {
mac: {
cmd: "⌘",
ctrl: "⌃",
opt: "⌥",
shift: "⇧",
},
other: {
cmd: "Win",
ctrl: "Ctrl",
opt: "Alt",
shift: "Shift",
},
};
const modifierMap = {
mac: {
cmd: '⌘',
ctrl: '⌃',
opt: '⌥',
shift: '⇧'
},
other: {
cmd: 'Win',
ctrl: 'Ctrl',
opt: 'Alt',
shift: 'Shift'
}
};
const keyMap: Partial<Record<Keyboard.KeyEquivalent, string>> = {
return: "⏎",
enter: "⏎",
delete: "⌫",
backspace: "⌫",
deleteForward: "⌦",
arrowUp: "↑",
arrowDown: "↓",
arrowLeft: "←",
arrowRight: "→",
tab: "⇥",
escape: "⎋",
space: "␣",
};
const keyMap: Partial<Record<Keyboard.KeyEquivalent, string>> = {
return: '⏎',
enter: '⏎',
delete: '⌫',
backspace: '⌫',
deleteForward: '⌦',
arrowUp: '↑',
arrowDown: '↓',
arrowLeft: '←',
arrowRight: '→',
tab: '⇥',
escape: '⎋',
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(
shortcut: Keyboard.Shortcut,
forceOS?: "macOS" | "windows"
): string {
const isMac = forceOS
? forceOS === "macOS"
: typeof navigator !== "undefined" && /Mac/i.test(navigator.platform);
export function shortcutToText(shortcut: Keyboard.Shortcut, forceOS?: 'macOS' | 'windows'): string {
const isMac = forceOS
? forceOS === 'macOS'
: typeof navigator !== 'undefined' && /Mac/i.test(navigator.platform);
if ("modifiers" in shortcut) {
return formatShortcutParts(shortcut, isMac);
} else {
if (isMac) {
return formatShortcutParts(shortcut.macOS, true);
} else {
return formatShortcutParts(shortcut.windows, false);
}
}
if ('modifiers' in shortcut) {
return formatShortcutParts(shortcut, isMac);
} else {
if (isMac) {
return formatShortcutParts(shortcut.macOS, true);
} else {
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 = {
subtitle?: string | null;
subtitle?: string | null;
};
let results = $state<Result[]>([]);
export const getResults = () => results;
export const setResults = (newResults: Result[]) => {
results = newResults;
results = newResults;
};
let toast = $state<Toast | null>(null);
export const getToast = (): Toast | null => toast;
export const setToast = (newToast: Toast | null) => {
toast = newToast;
toast = newToast;
};

View file

@ -1,13 +1,13 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// 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
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 WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };

View file

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

View file

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

View file

@ -1,19 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// 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
//
// 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
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// 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
//
// 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
}

View file

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