@fcannizzaro/streamdeck-react 0.1.12 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/dist/bundler-shared.d.ts +10 -0
- package/dist/bundler-shared.js +28 -1
- package/dist/font-inline.js +1 -1
- package/dist/google-font.d.ts +61 -0
- package/dist/google-font.js +124 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/plugin.js +44 -35
- package/dist/rollup.d.ts +1 -1
- package/dist/rollup.js +3 -1
- package/dist/roots/registry.js +20 -20
- package/dist/types.d.ts +15 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +3 -1
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -63,11 +63,13 @@ export const counterAction = defineAction({
|
|
|
63
63
|
Register it in your plugin entrypoint:
|
|
64
64
|
|
|
65
65
|
```ts
|
|
66
|
-
import { createPlugin } from "@fcannizzaro/streamdeck-react";
|
|
66
|
+
import { createPlugin, googleFont } from "@fcannizzaro/streamdeck-react";
|
|
67
67
|
import { counterAction } from "./actions/counter.tsx";
|
|
68
68
|
|
|
69
|
+
const inter = await googleFont("Inter");
|
|
70
|
+
|
|
69
71
|
const plugin = createPlugin({
|
|
70
|
-
fonts: [],
|
|
72
|
+
fonts: [inter],
|
|
71
73
|
actions: [counterAction],
|
|
72
74
|
});
|
|
73
75
|
|
|
@@ -83,12 +85,13 @@ await plugin.connect();
|
|
|
83
85
|
|
|
84
86
|
## Samples
|
|
85
87
|
|
|
86
|
-
- `samples/counter/`
|
|
87
|
-
- `samples/zustand/`
|
|
88
|
-
- `samples/jotai/`
|
|
89
|
-
- `samples/pokemon/`
|
|
90
|
-
- `samples/
|
|
91
|
-
- `samples/
|
|
88
|
+
- `samples/counter/` — local state, persisted settings, dial interaction
|
|
89
|
+
- `samples/zustand/` — shared state across keys via a module-scope Zustand store
|
|
90
|
+
- `samples/jotai/` — shared atom state with a plugin-level Jotai Provider wrapper
|
|
91
|
+
- `samples/pokemon/` — data fetching with TanStack Query and remote image rendering
|
|
92
|
+
- `samples/animation/` — spring bounce, fade-slide, and spring dial animations
|
|
93
|
+
- `samples/snake/` — snake game on the Stream Deck+ TouchStrip using dial controls and touch tap
|
|
94
|
+
- `samples/weather/` — weather forecast dials with animated detail panels and a shared Zustand store
|
|
92
95
|
|
|
93
96
|
## DevTools
|
|
94
97
|
|
package/dist/bundler-shared.d.ts
CHANGED
|
@@ -4,8 +4,16 @@ export interface StreamDeckTarget {
|
|
|
4
4
|
platform: StreamDeckPlatform;
|
|
5
5
|
arch: StreamDeckArch;
|
|
6
6
|
}
|
|
7
|
+
/** Takumi renderer backend selection. Mirrors the runtime `TakumiBackend` type for build-time configuration. */
|
|
8
|
+
export type TakumiBackend = "native-binding" | "wasm";
|
|
7
9
|
export interface StreamDeckTargetOptions {
|
|
8
10
|
targets?: StreamDeckTarget[];
|
|
11
|
+
/**
|
|
12
|
+
* Takumi renderer backend. When `"wasm"`, native `.node` binding
|
|
13
|
+
* copying is skipped entirely during the build.
|
|
14
|
+
* @default "native-binding"
|
|
15
|
+
*/
|
|
16
|
+
takumi?: TakumiBackend;
|
|
9
17
|
}
|
|
10
18
|
export interface ResolvedTarget extends StreamDeckTarget {
|
|
11
19
|
pkg: string;
|
|
@@ -17,6 +25,8 @@ export declare function isArch(value: string): value is StreamDeckArch;
|
|
|
17
25
|
export declare function isDevelopmentMode(watchMode: boolean | undefined): boolean;
|
|
18
26
|
export declare const NOOP_DEVTOOLS_ID = "\0streamdeck-react:noop-devtools";
|
|
19
27
|
export declare const NOOP_DEVTOOLS_CODE = "export function startDevtoolsServer() {}";
|
|
28
|
+
export declare const TAKUMI_NATIVE_LOADER_ID = "\0streamdeck-react:takumi-native";
|
|
29
|
+
export declare const TAKUMI_NATIVE_LOADER_CODE: string;
|
|
20
30
|
/**
|
|
21
31
|
* Returns true when devtools should be stripped from the bundle.
|
|
22
32
|
* Only strips in explicit production mode (NODE_ENV=production) to avoid
|
package/dist/bundler-shared.js
CHANGED
|
@@ -40,6 +40,32 @@ function isDevelopmentMode(watchMode) {
|
|
|
40
40
|
var NOOP_DEVTOOLS_ID = "\0streamdeck-react:noop-devtools";
|
|
41
41
|
var NOOP_DEVTOOLS_CODE = "export function startDevtoolsServer() {}";
|
|
42
42
|
var DEVTOOLS_IMPORT_SOURCE = "./devtools/index.js";
|
|
43
|
+
var TAKUMI_NATIVE_LOADER_ID = "\0streamdeck-react:takumi-native";
|
|
44
|
+
var TAKUMI_NATIVE_LOADER_CODE = `
|
|
45
|
+
import { createRequire } from "node:module";
|
|
46
|
+
const require = createRequire(import.meta.url);
|
|
47
|
+
let binding = null;
|
|
48
|
+
if (process.platform === "darwin") {
|
|
49
|
+
if (process.arch === "arm64") {
|
|
50
|
+
try { binding = require("./core.darwin-arm64.node"); } catch {}
|
|
51
|
+
} else if (process.arch === "x64") {
|
|
52
|
+
try { binding = require("./core.darwin-x64.node"); } catch {}
|
|
53
|
+
}
|
|
54
|
+
} else if (process.platform === "win32") {
|
|
55
|
+
if (process.arch === "arm64") {
|
|
56
|
+
try { binding = require("./core.win32-arm64-msvc.node"); } catch {}
|
|
57
|
+
} else if (process.arch === "x64") {
|
|
58
|
+
try { binding = require("./core.win32-x64-msvc.node"); } catch {}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!binding) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"[@fcannizzaro/streamdeck-react] Failed to load @takumi-rs/core native binding for " +
|
|
64
|
+
process.platform + "-" + process.arch
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
export const { Renderer, OutputFormat, DitheringAlgorithm, AnimationOutputFormat, extractResourceUrls } = binding;
|
|
68
|
+
`.trim();
|
|
43
69
|
/**
|
|
44
70
|
* Returns true when devtools should be stripped from the bundle.
|
|
45
71
|
* Only strips in explicit production mode (NODE_ENV=production) to avoid
|
|
@@ -92,6 +118,7 @@ function expandTargets(targets) {
|
|
|
92
118
|
* In production: missing bindings throw an error (the plugin won't work).
|
|
93
119
|
*/
|
|
94
120
|
function copyNativeBindings(outDir, isDevelopment, options, warn) {
|
|
121
|
+
if (options.takumi === "wasm") return;
|
|
95
122
|
try {
|
|
96
123
|
const requestedTargets = normalizeTargetRequests(options, isDevelopment);
|
|
97
124
|
if (requestedTargets.length === 0) {
|
|
@@ -129,4 +156,4 @@ function copyNativeBindings(outDir, isDevelopment, options, warn) {
|
|
|
129
156
|
}
|
|
130
157
|
}
|
|
131
158
|
//#endregion
|
|
132
|
-
export { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, copyNativeBindings, isDevelopmentMode, isLibraryDevtoolsImport, shouldStripDevtools };
|
|
159
|
+
export { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, TAKUMI_NATIVE_LOADER_CODE, TAKUMI_NATIVE_LOADER_ID, copyNativeBindings, isDevelopmentMode, isLibraryDevtoolsImport, shouldStripDevtools };
|
package/dist/font-inline.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { dirname, resolve } from "node:path";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import { realpathSync } from "node:fs";
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
5
5
|
//#region src/font-inline.ts
|
|
6
6
|
var FONT_RE = /\.(ttf|otf|woff2?)$/;
|
|
7
7
|
/**
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { FontConfig } from './types';
|
|
2
|
+
type FontWeight = FontConfig["weight"];
|
|
3
|
+
type FontStyle = FontConfig["style"];
|
|
4
|
+
export interface GoogleFontVariant {
|
|
5
|
+
weight?: FontWeight;
|
|
6
|
+
style?: FontStyle;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Fetch a Google Font as a ready-to-use `FontConfig`.
|
|
10
|
+
*
|
|
11
|
+
* Downloads TTF font data directly from Google Fonts — no npm package
|
|
12
|
+
* needed. The returned config can be passed straight into
|
|
13
|
+
* `createPlugin({ fonts: [...] })`.
|
|
14
|
+
*
|
|
15
|
+
* Font files are cached to `.google-fonts/` in the current working
|
|
16
|
+
* directory. Subsequent calls read from disk without network access.
|
|
17
|
+
*
|
|
18
|
+
* @param family - The Google Font family name (e.g. `"Inter"`, `"Roboto"`).
|
|
19
|
+
* @returns A `FontConfig` with weight 400 and normal style.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const inter = await googleFont("Inter");
|
|
24
|
+
* createPlugin({ fonts: [inter], actions: [...] });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function googleFont(family: string): Promise<FontConfig>;
|
|
28
|
+
/**
|
|
29
|
+
* Fetch a specific weight/style variant of a Google Font.
|
|
30
|
+
*
|
|
31
|
+
* @param family - The Google Font family name.
|
|
32
|
+
* @param variant - Weight and/or style to fetch.
|
|
33
|
+
* @returns A single `FontConfig`.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const bold = await googleFont("Inter", { weight: 700 });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function googleFont(family: string, variant: GoogleFontVariant): Promise<FontConfig>;
|
|
41
|
+
/**
|
|
42
|
+
* Fetch multiple weight/style variants of a Google Font in one call.
|
|
43
|
+
*
|
|
44
|
+
* All variants are fetched in parallel for maximum throughput.
|
|
45
|
+
*
|
|
46
|
+
* @param family - The Google Font family name.
|
|
47
|
+
* @param variants - Array of weight/style combinations to fetch.
|
|
48
|
+
* @returns An array of `FontConfig` objects, one per variant.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const fonts = await googleFont("Inter", [
|
|
53
|
+
* { weight: 400 },
|
|
54
|
+
* { weight: 700 },
|
|
55
|
+
* { weight: 700, style: "italic" },
|
|
56
|
+
* ]);
|
|
57
|
+
* createPlugin({ fonts, actions: [...] });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function googleFont(family: string, variants: GoogleFontVariant[]): Promise<FontConfig[]>;
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
//#region src/google-font.ts
|
|
4
|
+
var TTF_USER_AGENT = "Mozilla/4.0";
|
|
5
|
+
var GOOGLE_FONTS_CSS2_BASE = "https://fonts.googleapis.com/css2";
|
|
6
|
+
var CACHE_DIR = ".google-fonts";
|
|
7
|
+
function sanitizeName(family) {
|
|
8
|
+
return family.toLowerCase().replace(/\s+/g, "-");
|
|
9
|
+
}
|
|
10
|
+
function cacheFilePath(family, weight, style) {
|
|
11
|
+
return join(process.cwd(), CACHE_DIR, `${sanitizeName(family)}-${weight}-${style}.ttf`);
|
|
12
|
+
}
|
|
13
|
+
async function fileExists(path) {
|
|
14
|
+
try {
|
|
15
|
+
await access(path);
|
|
16
|
+
return true;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function ensureCacheDir() {
|
|
22
|
+
await mkdir(join(process.cwd(), CACHE_DIR), { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
async function readCached(family, weight, style) {
|
|
25
|
+
const path = cacheFilePath(family, weight, style);
|
|
26
|
+
if (!await fileExists(path)) return null;
|
|
27
|
+
return {
|
|
28
|
+
name: family,
|
|
29
|
+
data: (await readFile(path)).buffer,
|
|
30
|
+
weight,
|
|
31
|
+
style
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async function writeCache(family, weight, style, data) {
|
|
35
|
+
await ensureCacheDir();
|
|
36
|
+
await writeFile(cacheFilePath(family, weight, style), Buffer.from(data));
|
|
37
|
+
}
|
|
38
|
+
function buildCss2Url(family, variants) {
|
|
39
|
+
const hasItalic = variants.some((v) => v.style === "italic");
|
|
40
|
+
const encodedFamily = encodeURIComponent(family);
|
|
41
|
+
if (hasItalic) {
|
|
42
|
+
const specs = variants.map((v) => {
|
|
43
|
+
return `${v.style === "italic" ? 1 : 0},${v.weight ?? 400}`;
|
|
44
|
+
});
|
|
45
|
+
specs.sort();
|
|
46
|
+
return `${GOOGLE_FONTS_CSS2_BASE}?family=${encodedFamily}:ital,wght@${specs.join(";")}`;
|
|
47
|
+
}
|
|
48
|
+
const weights = variants.map((v) => v.weight ?? 400);
|
|
49
|
+
weights.sort((a, b) => a - b);
|
|
50
|
+
return `${GOOGLE_FONTS_CSS2_BASE}?family=${encodedFamily}:wght@${weights.join(";")}`;
|
|
51
|
+
}
|
|
52
|
+
function parseFontFaces(css) {
|
|
53
|
+
const results = [];
|
|
54
|
+
const seen = /* @__PURE__ */ new Set();
|
|
55
|
+
const blockRegex = /@font-face\s*\{([^}]+)\}/g;
|
|
56
|
+
let match;
|
|
57
|
+
while ((match = blockRegex.exec(css)) !== null) {
|
|
58
|
+
const block = match[1];
|
|
59
|
+
const urlMatch = block.match(/url\(([^)]+)\)/);
|
|
60
|
+
const weightMatch = block.match(/font-weight:\s*(\d+)/);
|
|
61
|
+
const styleMatch = block.match(/font-style:\s*(normal|italic)/);
|
|
62
|
+
if (!urlMatch?.[1]) continue;
|
|
63
|
+
const weight = Number(weightMatch?.[1] ?? 400);
|
|
64
|
+
const style = styleMatch?.[1] ?? "normal";
|
|
65
|
+
const key = `${weight}:${style}`;
|
|
66
|
+
if (seen.has(key)) continue;
|
|
67
|
+
seen.add(key);
|
|
68
|
+
results.push({
|
|
69
|
+
url: urlMatch[1],
|
|
70
|
+
weight,
|
|
71
|
+
style
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return results;
|
|
75
|
+
}
|
|
76
|
+
async function fetchGoogleFonts(family, variants) {
|
|
77
|
+
const results = [];
|
|
78
|
+
const uncached = [];
|
|
79
|
+
for (const v of variants) {
|
|
80
|
+
const weight = v.weight ?? 400;
|
|
81
|
+
const style = v.style ?? "normal";
|
|
82
|
+
const cached = await readCached(family, weight, style);
|
|
83
|
+
if (cached) results.push(cached);
|
|
84
|
+
else uncached.push({
|
|
85
|
+
weight,
|
|
86
|
+
style
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (uncached.length === 0) return results;
|
|
90
|
+
const url = buildCss2Url(family, uncached);
|
|
91
|
+
const cssResponse = await fetch(url, { headers: { "User-Agent": TTF_USER_AGENT } });
|
|
92
|
+
if (!cssResponse.ok) throw new Error(`Google Fonts: failed to fetch CSS for "${family}" (HTTP ${cssResponse.status})`);
|
|
93
|
+
const faces = parseFontFaces(await cssResponse.text());
|
|
94
|
+
if (faces.length === 0) throw new Error(`Google Fonts: no font faces found in CSS response for "${family}". Verify the family name is correct at https://fonts.google.com`);
|
|
95
|
+
const downloaded = await Promise.all(faces.map(async (face) => {
|
|
96
|
+
const fontResponse = await fetch(face.url);
|
|
97
|
+
if (!fontResponse.ok) throw new Error(`Google Fonts: failed to download font file for "${family}" weight ${face.weight} (HTTP ${fontResponse.status})`);
|
|
98
|
+
const data = await fontResponse.arrayBuffer();
|
|
99
|
+
writeCache(family, face.weight, face.style, data).catch(() => {});
|
|
100
|
+
return {
|
|
101
|
+
name: family,
|
|
102
|
+
data,
|
|
103
|
+
weight: face.weight,
|
|
104
|
+
style: face.style
|
|
105
|
+
};
|
|
106
|
+
}));
|
|
107
|
+
return [...results, ...downloaded];
|
|
108
|
+
}
|
|
109
|
+
async function googleFont(family, variantOrVariants) {
|
|
110
|
+
if (variantOrVariants === void 0) return (await fetchGoogleFonts(family, [{
|
|
111
|
+
weight: 400,
|
|
112
|
+
style: "normal"
|
|
113
|
+
}]))[0];
|
|
114
|
+
if (!Array.isArray(variantOrVariants)) return (await fetchGoogleFonts(family, [{
|
|
115
|
+
weight: variantOrVariants.weight ?? 400,
|
|
116
|
+
style: variantOrVariants.style ?? "normal"
|
|
117
|
+
}]))[0];
|
|
118
|
+
return fetchGoogleFonts(family, variantOrVariants.map((v) => ({
|
|
119
|
+
weight: v.weight ?? 400,
|
|
120
|
+
style: v.style ?? "normal"
|
|
121
|
+
})));
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { googleFont };
|
package/dist/index.d.ts
CHANGED
|
@@ -23,7 +23,9 @@ export { ProgressBar } from './components/ProgressBar';
|
|
|
23
23
|
export { CircularGauge } from './components/CircularGauge';
|
|
24
24
|
export { ErrorBoundary } from './components/ErrorBoundary';
|
|
25
25
|
export { tw } from './tw/index';
|
|
26
|
-
export
|
|
26
|
+
export { googleFont } from './google-font';
|
|
27
|
+
export type { GoogleFontVariant } from './google-font';
|
|
28
|
+
export type { PluginConfig, Plugin, FontConfig, ActionConfig, ActionConfigInput, ActionDefinition, ActionUUID, ManifestActions, EncoderLayout, WrapperComponent, TakumiBackend, Controller, Coordinates, Size, DeviceInfo, ActionInfo, CanvasInfo, TouchStripLayout, TouchStripLayoutItem, KeyDownPayload, KeyUpPayload, DialRotatePayload, DialPressPayload, TouchTapPayload, DialHints, StreamDeckAccess, TouchStripInfo, TouchStripTapPayload, TouchStripDialRotatePayload, TouchStripDialPressPayload, } from './types';
|
|
27
29
|
export type { TapOptions, LongPressOptions, DoubleTapOptions } from './hooks/gestures';
|
|
28
30
|
export type { BoxProps } from './components/Box';
|
|
29
31
|
export type { TextProps } from './components/Text';
|
package/dist/index.js
CHANGED
|
@@ -18,4 +18,5 @@ import { ProgressBar } from "./components/ProgressBar.js";
|
|
|
18
18
|
import { CircularGauge } from "./components/CircularGauge.js";
|
|
19
19
|
import { ErrorBoundary } from "./components/ErrorBoundary.js";
|
|
20
20
|
import { tw } from "./tw/index.js";
|
|
21
|
-
|
|
21
|
+
import { googleFont } from "./google-font.js";
|
|
22
|
+
export { Box, CircularGauge, Easings, ErrorBoundary, Icon, Image, ProgressBar, SpringPresets, Text, createPlugin, defineAction, googleFont, physicalDevice, tw, useAction, useCanvas, useDevice, useDialDown, useDialHint, useDialRotate, useDialUp, useDoubleTap, useGlobalSettings, useInterval, useKeyDown, useKeyUp, useLongPress, useOpenUrl, usePrevious, usePropertyInspector, useSendToPI, useSettings, useShowAlert, useShowOk, useSpring, useStreamDeck, useSwitchProfile, useTap, useTick, useTimeout, useTitle, useTouchStrip, useTouchStripDialDown, useTouchStripDialRotate, useTouchStripDialUp, useTouchStripTap, useTouchTap, useTween, useWillAppear, useWillDisappear };
|
package/dist/plugin.js
CHANGED
|
@@ -7,46 +7,55 @@ import { Renderer } from "@takumi-rs/core";
|
|
|
7
7
|
//#region src/plugin.ts
|
|
8
8
|
function createPlugin(config) {
|
|
9
9
|
const adapter = config.adapter ?? physicalDevice();
|
|
10
|
-
const renderer = new Renderer({ fonts: config.fonts.map((f) => ({
|
|
11
|
-
name: f.name,
|
|
12
|
-
data: f.data,
|
|
13
|
-
weight: f.weight,
|
|
14
|
-
style: f.style
|
|
15
|
-
})) });
|
|
16
|
-
const renderPool = config.useWorker !== false ? new RenderPool(config.fonts) : null;
|
|
17
|
-
const renderConfig = {
|
|
18
|
-
renderer,
|
|
19
|
-
imageFormat: config.imageFormat ?? "png",
|
|
20
|
-
caching: config.caching ?? true,
|
|
21
|
-
devicePixelRatio: config.devicePixelRatio ?? 1,
|
|
22
|
-
debug: config.debug ?? process.env.NODE_ENV !== "production",
|
|
23
|
-
imageCacheMaxBytes: config.imageCacheMaxBytes ?? 16 * 1024 * 1024,
|
|
24
|
-
touchStripCacheMaxBytes: config.touchStripCacheMaxBytes ?? 8 * 1024 * 1024,
|
|
25
|
-
renderPool
|
|
26
|
-
};
|
|
27
|
-
const registry = new RootRegistry(renderConfig, adapter, async (settings) => {
|
|
28
|
-
await adapter.setGlobalSettings(settings);
|
|
29
|
-
}, config.wrapper);
|
|
30
|
-
adapter.getGlobalSettings().then((gs) => {
|
|
31
|
-
registry.setGlobalSettings(gs);
|
|
32
|
-
}).catch((err) => {
|
|
33
|
-
console.error("[@fcannizzaro/streamdeck-react] Failed to load global settings:", err);
|
|
34
|
-
});
|
|
35
|
-
adapter.onGlobalSettingsChanged((settings) => {
|
|
36
|
-
registry.setGlobalSettings(settings);
|
|
37
|
-
});
|
|
38
|
-
for (const definition of config.actions) registerActionWithAdapter(adapter, definition, registry, config.onActionError);
|
|
39
|
-
if (renderConfig.debug) metrics.enable();
|
|
40
|
-
if (config.devtools) startDevtoolsServer({
|
|
41
|
-
devtoolsName: adapter.pluginUUID,
|
|
42
|
-
registry,
|
|
43
|
-
renderConfig
|
|
44
|
-
});
|
|
45
10
|
return { async connect() {
|
|
11
|
+
const takumiMode = config.takumi ?? "native-binding";
|
|
12
|
+
const renderPool = (takumiMode === "wasm" ? false : config.useWorker !== false) ? new RenderPool(config.fonts) : null;
|
|
13
|
+
const renderConfig = {
|
|
14
|
+
renderer: await initializeRenderer(takumiMode, config.fonts),
|
|
15
|
+
imageFormat: config.imageFormat ?? "png",
|
|
16
|
+
caching: config.caching ?? true,
|
|
17
|
+
devicePixelRatio: config.devicePixelRatio ?? 1,
|
|
18
|
+
debug: config.debug ?? process.env.NODE_ENV !== "production",
|
|
19
|
+
imageCacheMaxBytes: config.imageCacheMaxBytes ?? 16 * 1024 * 1024,
|
|
20
|
+
touchStripCacheMaxBytes: config.touchStripCacheMaxBytes ?? 8 * 1024 * 1024,
|
|
21
|
+
renderPool
|
|
22
|
+
};
|
|
23
|
+
const registry = new RootRegistry(renderConfig, adapter, async (settings) => {
|
|
24
|
+
await adapter.setGlobalSettings(settings);
|
|
25
|
+
}, config.wrapper);
|
|
26
|
+
adapter.getGlobalSettings().then((gs) => {
|
|
27
|
+
registry.setGlobalSettings(gs);
|
|
28
|
+
}).catch((err) => {
|
|
29
|
+
console.error("[@fcannizzaro/streamdeck-react] Failed to load global settings:", err);
|
|
30
|
+
});
|
|
31
|
+
adapter.onGlobalSettingsChanged((settings) => {
|
|
32
|
+
registry.setGlobalSettings(settings);
|
|
33
|
+
});
|
|
34
|
+
for (const definition of config.actions) registerActionWithAdapter(adapter, definition, registry, config.onActionError);
|
|
35
|
+
if (renderConfig.debug) metrics.enable();
|
|
36
|
+
if (config.devtools) startDevtoolsServer({
|
|
37
|
+
devtoolsName: adapter.pluginUUID,
|
|
38
|
+
registry,
|
|
39
|
+
renderConfig
|
|
40
|
+
});
|
|
46
41
|
if (renderPool != null) renderPool.initialize().catch(() => {});
|
|
47
42
|
await adapter.connect();
|
|
48
43
|
} };
|
|
49
44
|
}
|
|
45
|
+
async function initializeRenderer(mode, fonts) {
|
|
46
|
+
const fontData = fonts.map((f) => ({
|
|
47
|
+
name: f.name,
|
|
48
|
+
data: f.data,
|
|
49
|
+
weight: f.weight,
|
|
50
|
+
style: f.style
|
|
51
|
+
}));
|
|
52
|
+
if (mode === "wasm") {
|
|
53
|
+
const wasm = await import("@takumi-rs/wasm");
|
|
54
|
+
await wasm.default();
|
|
55
|
+
return new wasm.Renderer({ fonts: fontData });
|
|
56
|
+
}
|
|
57
|
+
return new Renderer({ fonts: fontData });
|
|
58
|
+
}
|
|
50
59
|
function registerActionWithAdapter(adapter, definition, registry, onError) {
|
|
51
60
|
const handleError = (actionId, err) => {
|
|
52
61
|
const error = err instanceof Error ? err : new Error(String(err));
|
package/dist/rollup.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Plugin } from 'rollup';
|
|
2
2
|
import { StreamDeckTargetOptions } from './bundler-shared';
|
|
3
|
-
export type { StreamDeckPlatform, StreamDeckArch, StreamDeckTarget, StreamDeckTargetOptions, } from './bundler-shared';
|
|
3
|
+
export type { StreamDeckPlatform, StreamDeckArch, StreamDeckTarget, StreamDeckTargetOptions, TakumiBackend, } from './bundler-shared';
|
|
4
4
|
export interface StreamDeckRollupOptions extends StreamDeckTargetOptions {
|
|
5
5
|
/**
|
|
6
6
|
* Path to the plugin `manifest.json`. When omitted, the plugin
|
package/dist/rollup.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, copyNativeBindings, isDevelopmentMode, isLibraryDevtoolsImport, shouldStripDevtools } from "./bundler-shared.js";
|
|
1
|
+
import { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, TAKUMI_NATIVE_LOADER_CODE, TAKUMI_NATIVE_LOADER_ID, copyNativeBindings, isDevelopmentMode, isLibraryDevtoolsImport, shouldStripDevtools } from "./bundler-shared.js";
|
|
2
2
|
import { loadFont, resolveFontId } from "./font-inline.js";
|
|
3
3
|
import { generateManifestTypes } from "./manifest-codegen.js";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
@@ -39,6 +39,7 @@ function streamDeckReact(options = {}) {
|
|
|
39
39
|
order: "pre",
|
|
40
40
|
handler(source, importer) {
|
|
41
41
|
if (shouldStripDevtools(this.meta.watchMode) && isLibraryDevtoolsImport(source, importer)) return NOOP_DEVTOOLS_ID;
|
|
42
|
+
if (options.takumi !== "wasm" && source === "@takumi-rs/core") return TAKUMI_NATIVE_LOADER_ID;
|
|
42
43
|
return resolveFontId(source, importer);
|
|
43
44
|
}
|
|
44
45
|
},
|
|
@@ -46,6 +47,7 @@ function streamDeckReact(options = {}) {
|
|
|
46
47
|
order: "pre",
|
|
47
48
|
handler(id) {
|
|
48
49
|
if (id === "\0streamdeck-react:noop-devtools") return NOOP_DEVTOOLS_CODE;
|
|
50
|
+
if (id === "\0streamdeck-react:takumi-native") return TAKUMI_NATIVE_LOADER_CODE;
|
|
49
51
|
return loadFont(id);
|
|
50
52
|
}
|
|
51
53
|
},
|
package/dist/roots/registry.js
CHANGED
|
@@ -5,44 +5,44 @@ import { TouchStripRoot } from "./touchstrip-root.js";
|
|
|
5
5
|
var SEGMENT_WIDTH = 200;
|
|
6
6
|
var KEY_SIZES = {
|
|
7
7
|
0: {
|
|
8
|
-
width:
|
|
9
|
-
height:
|
|
8
|
+
width: 144,
|
|
9
|
+
height: 144
|
|
10
10
|
},
|
|
11
11
|
1: {
|
|
12
|
-
width:
|
|
13
|
-
height:
|
|
12
|
+
width: 144,
|
|
13
|
+
height: 144
|
|
14
14
|
},
|
|
15
15
|
2: {
|
|
16
|
-
width:
|
|
17
|
-
height:
|
|
16
|
+
width: 144,
|
|
17
|
+
height: 144
|
|
18
18
|
},
|
|
19
19
|
3: {
|
|
20
|
-
width:
|
|
21
|
-
height:
|
|
20
|
+
width: 144,
|
|
21
|
+
height: 144
|
|
22
22
|
},
|
|
23
23
|
4: {
|
|
24
|
-
width:
|
|
25
|
-
height:
|
|
24
|
+
width: 144,
|
|
25
|
+
height: 144
|
|
26
26
|
},
|
|
27
27
|
5: {
|
|
28
|
-
width:
|
|
29
|
-
height:
|
|
28
|
+
width: 144,
|
|
29
|
+
height: 144
|
|
30
30
|
},
|
|
31
31
|
6: {
|
|
32
|
-
width:
|
|
33
|
-
height:
|
|
32
|
+
width: 144,
|
|
33
|
+
height: 144
|
|
34
34
|
},
|
|
35
35
|
7: {
|
|
36
36
|
width: 144,
|
|
37
37
|
height: 144
|
|
38
38
|
},
|
|
39
39
|
8: {
|
|
40
|
-
width:
|
|
41
|
-
height:
|
|
40
|
+
width: 144,
|
|
41
|
+
height: 144
|
|
42
42
|
},
|
|
43
43
|
9: {
|
|
44
|
-
width:
|
|
45
|
-
height:
|
|
44
|
+
width: 144,
|
|
45
|
+
height: 144
|
|
46
46
|
},
|
|
47
47
|
10: {
|
|
48
48
|
width: 144,
|
|
@@ -72,8 +72,8 @@ function getCanvasInfo(deviceType, surfaceType) {
|
|
|
72
72
|
};
|
|
73
73
|
return {
|
|
74
74
|
...KEY_SIZES[deviceType] ?? {
|
|
75
|
-
width:
|
|
76
|
-
height:
|
|
75
|
+
width: 144,
|
|
76
|
+
height: 144
|
|
77
77
|
},
|
|
78
78
|
type: "key"
|
|
79
79
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -73,12 +73,26 @@ export interface TouchStripLayout {
|
|
|
73
73
|
items: TouchStripLayoutItem[];
|
|
74
74
|
}
|
|
75
75
|
export type EncoderLayout = string | TouchStripLayout;
|
|
76
|
+
/** Takumi renderer backend selection. `"native-binding"` uses the native Rust NAPI addon (`@takumi-rs/core`). `"wasm"` uses the WebAssembly build (`@takumi-rs/wasm`), suitable for WebContainer and browser environments. */
|
|
77
|
+
export type TakumiBackend = "native-binding" | "wasm";
|
|
76
78
|
export interface PluginConfig {
|
|
77
79
|
/** Stream Deck adapter. Defaults to physicalDevice() (Elgato SDK). */
|
|
78
80
|
adapter?: StreamDeckAdapter;
|
|
79
81
|
fonts: FontConfig[];
|
|
80
82
|
actions: ActionDefinition[];
|
|
81
83
|
wrapper?: WrapperComponent;
|
|
84
|
+
/**
|
|
85
|
+
* Takumi renderer backend.
|
|
86
|
+
*
|
|
87
|
+
* - `"native-binding"` — uses `@takumi-rs/core` (native Rust NAPI addon).
|
|
88
|
+
* Requires a platform-specific binary (e.g. `@takumi-rs/core-darwin-arm64`).
|
|
89
|
+
* - `"wasm"` — uses `@takumi-rs/wasm` (WebAssembly build).
|
|
90
|
+
* Requires `@takumi-rs/wasm` to be installed. WOFF fonts are not supported
|
|
91
|
+
* in this mode — use TTF/OTF only. Worker threads are force-disabled.
|
|
92
|
+
*
|
|
93
|
+
* @default "native-binding"
|
|
94
|
+
*/
|
|
95
|
+
takumi?: TakumiBackend;
|
|
82
96
|
imageFormat?: "png" | "webp";
|
|
83
97
|
caching?: boolean;
|
|
84
98
|
devicePixelRatio?: number;
|
|
@@ -91,7 +105,7 @@ export interface PluginConfig {
|
|
|
91
105
|
imageCacheMaxBytes?: number;
|
|
92
106
|
/** Maximum TouchStrip raw buffer cache size in bytes. Set to 0 to disable. @default 8388608 (8 MB) */
|
|
93
107
|
touchStripCacheMaxBytes?: number;
|
|
94
|
-
/** Offload Takumi rendering to a worker thread. Set to false to disable. @default true */
|
|
108
|
+
/** Offload Takumi rendering to a worker thread. Set to false to disable. Automatically disabled when `takumi` is `"wasm"`. @default true */
|
|
95
109
|
useWorker?: boolean;
|
|
96
110
|
}
|
|
97
111
|
export interface Plugin {
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
import { StreamDeckTargetOptions } from './bundler-shared';
|
|
3
|
-
export type { StreamDeckPlatform, StreamDeckArch, StreamDeckTarget, StreamDeckTargetOptions, } from './bundler-shared';
|
|
3
|
+
export type { StreamDeckPlatform, StreamDeckArch, StreamDeckTarget, StreamDeckTargetOptions, TakumiBackend, } from './bundler-shared';
|
|
4
4
|
export interface StreamDeckReactOptions extends StreamDeckTargetOptions {
|
|
5
5
|
/**
|
|
6
6
|
* The plugin UUID used to restart the plugin after each build
|
package/dist/vite.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, copyNativeBindings, isLibraryDevtoolsImport, shouldStripDevtools } from "./bundler-shared.js";
|
|
1
|
+
import { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, TAKUMI_NATIVE_LOADER_CODE, TAKUMI_NATIVE_LOADER_ID, copyNativeBindings, isLibraryDevtoolsImport, shouldStripDevtools } from "./bundler-shared.js";
|
|
2
2
|
import { loadFont, resolveFontId } from "./font-inline.js";
|
|
3
3
|
import { generateManifestTypes } from "./manifest-codegen.js";
|
|
4
4
|
import { resolve } from "node:path";
|
|
@@ -47,10 +47,12 @@ function streamDeckReact(options = {}) {
|
|
|
47
47
|
},
|
|
48
48
|
resolveId(source, importer) {
|
|
49
49
|
if (stripDevtools && isLibraryDevtoolsImport(source, importer)) return NOOP_DEVTOOLS_ID;
|
|
50
|
+
if (options.takumi !== "wasm" && source === "@takumi-rs/core") return TAKUMI_NATIVE_LOADER_ID;
|
|
50
51
|
return resolveFontId(source, importer);
|
|
51
52
|
},
|
|
52
53
|
load(id) {
|
|
53
54
|
if (id === "\0streamdeck-react:noop-devtools") return NOOP_DEVTOOLS_CODE;
|
|
55
|
+
if (id === "\0streamdeck-react:takumi-native") return TAKUMI_NATIVE_LOADER_CODE;
|
|
54
56
|
return loadFont(id);
|
|
55
57
|
},
|
|
56
58
|
writeBundle() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fcannizzaro/streamdeck-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Build Stream Deck plugins with React — render components directly to keys, dials, and touch screens",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"elgato",
|
|
@@ -60,22 +60,23 @@
|
|
|
60
60
|
"test": "bun test ./src"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@takumi-rs/core": "^0.
|
|
64
|
-
"@takumi-rs/helpers": "^0.
|
|
63
|
+
"@takumi-rs/core": "^0.73.1",
|
|
64
|
+
"@takumi-rs/helpers": "^0.73.1",
|
|
65
65
|
"react-reconciler": "^0.33.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@happy-dom/global-registrator": "^20.8.
|
|
68
|
+
"@happy-dom/global-registrator": "^20.8.4",
|
|
69
69
|
"@types/react": "^19.2.14",
|
|
70
70
|
"@types/react-dom": "^19.2.3",
|
|
71
71
|
"@types/react-reconciler": "^0.33.0",
|
|
72
72
|
"react-dom": "^19.2.4",
|
|
73
73
|
"rollup": "^4.59.0",
|
|
74
|
-
"vite": "8.0.0
|
|
74
|
+
"vite": "8.0.0",
|
|
75
75
|
"vite-plugin-dts": "^4.5.4"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
78
|
"@elgato/streamdeck": "^2.0.2",
|
|
79
|
+
"@takumi-rs/wasm": "^0.71.7",
|
|
79
80
|
"react": "^18.0.0 || ^19.0.0",
|
|
80
81
|
"rollup": "^4.0.0",
|
|
81
82
|
"typescript": "^5",
|
|
@@ -85,6 +86,9 @@
|
|
|
85
86
|
"@elgato/streamdeck": {
|
|
86
87
|
"optional": true
|
|
87
88
|
},
|
|
89
|
+
"@takumi-rs/wasm": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
88
92
|
"rollup": {
|
|
89
93
|
"optional": true
|
|
90
94
|
},
|