@fcannizzaro/streamdeck-react 0.1.12 → 0.1.14
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/action.d.ts +2 -2
- package/dist/action.js +2 -1
- package/dist/bundler-shared.d.ts +31 -0
- package/dist/bundler-shared.js +64 -2
- 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 +4 -1
- package/dist/index.js +2 -1
- package/dist/manifest-extract.d.ts +32 -0
- package/dist/manifest-extract.js +141 -0
- package/dist/manifest-gen.d.ts +52 -0
- package/dist/manifest-gen.js +229 -0
- package/dist/manifest-types.d.ts +238 -0
- package/dist/plugin.js +44 -35
- package/dist/render/render-pool.js +1 -1
- package/dist/rollup.d.ts +37 -10
- package/dist/rollup.js +43 -14
- package/dist/roots/registry.js +20 -20
- package/dist/types.d.ts +35 -35
- package/dist/vite.d.ts +24 -8
- package/dist/vite.js +48 -13
- package/package.json +10 -5
- package/dist/manifest-codegen.d.ts +0 -38
- package/dist/manifest-codegen.js +0 -110
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/action.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ActionConfig, ActionDefinition } from './types';
|
|
2
2
|
import { JsonObject } from '@elgato/utils';
|
|
3
|
-
export declare function defineAction<S extends JsonObject = JsonObject>(config:
|
|
3
|
+
export declare function defineAction<S extends JsonObject = JsonObject>(config: ActionConfig<S>): ActionDefinition<S>;
|
package/dist/action.js
CHANGED
|
@@ -7,7 +7,8 @@ function defineAction(config) {
|
|
|
7
7
|
touchStrip: config.touchStrip,
|
|
8
8
|
dialLayout: config.dialLayout,
|
|
9
9
|
wrapper: config.wrapper,
|
|
10
|
-
defaultSettings: config.defaultSettings ?? {}
|
|
10
|
+
defaultSettings: config.defaultSettings ?? {},
|
|
11
|
+
info: config.info
|
|
11
12
|
};
|
|
12
13
|
}
|
|
13
14
|
//#endregion
|
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
|
|
@@ -43,3 +53,24 @@ export declare function expandTargets(targets: StreamDeckTarget[]): ResolvedTarg
|
|
|
43
53
|
* In production: missing bindings throw an error (the plugin won't work).
|
|
44
54
|
*/
|
|
45
55
|
export declare function copyNativeBindings(outDir: string, isDevelopment: boolean, options: StreamDeckTargetOptions, warn: (message: string) => void): void;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the .sdPlugin directory root from the bundler output directory.
|
|
58
|
+
*
|
|
59
|
+
* Strategy:
|
|
60
|
+
* 1. Walk up from outDir looking for a parent named `*.sdPlugin`
|
|
61
|
+
* 2. If not found, assume outDir's parent is the .sdPlugin root
|
|
62
|
+
* (handles `<uuid>.sdPlugin/bin/` → `<uuid>.sdPlugin/`)
|
|
63
|
+
*
|
|
64
|
+
* @returns The .sdPlugin directory path, or `null` if unresolvable.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolvePluginDir(outDir: string): string | null;
|
|
67
|
+
/**
|
|
68
|
+
* Derive the CodePath (manifest entry point) from the bundler output
|
|
69
|
+
* file path, relative to the .sdPlugin directory.
|
|
70
|
+
*
|
|
71
|
+
* Example:
|
|
72
|
+
* outFile: "/project/com.example.sdPlugin/bin/plugin.mjs"
|
|
73
|
+
* pluginDir: "/project/com.example.sdPlugin"
|
|
74
|
+
* → "bin/plugin.mjs"
|
|
75
|
+
*/
|
|
76
|
+
export declare function deriveCodePath(outFile: string, pluginDir: string): string;
|
package/dist/bundler-shared.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dirname, join } from "node:path";
|
|
1
|
+
import { dirname, join, relative } from "node:path";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { copyFileSync, existsSync } from "node:fs";
|
|
4
4
|
//#region src/bundler-shared.ts
|
|
@@ -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) {
|
|
@@ -128,5 +155,40 @@ function copyNativeBindings(outDir, isDevelopment, options, warn) {
|
|
|
128
155
|
throw new Error(`[@fcannizzaro/streamdeck-react] Failed to copy native binding: ${String(err)}`);
|
|
129
156
|
}
|
|
130
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Resolve the .sdPlugin directory root from the bundler output directory.
|
|
160
|
+
*
|
|
161
|
+
* Strategy:
|
|
162
|
+
* 1. Walk up from outDir looking for a parent named `*.sdPlugin`
|
|
163
|
+
* 2. If not found, assume outDir's parent is the .sdPlugin root
|
|
164
|
+
* (handles `<uuid>.sdPlugin/bin/` → `<uuid>.sdPlugin/`)
|
|
165
|
+
*
|
|
166
|
+
* @returns The .sdPlugin directory path, or `null` if unresolvable.
|
|
167
|
+
*/
|
|
168
|
+
function resolvePluginDir(outDir) {
|
|
169
|
+
let dir = outDir;
|
|
170
|
+
const root = dirname(dir);
|
|
171
|
+
for (let i = 0; i < 4; i++) {
|
|
172
|
+
if (dir.endsWith(".sdPlugin")) return dir;
|
|
173
|
+
const parent = dirname(dir);
|
|
174
|
+
if (parent === dir) break;
|
|
175
|
+
dir = parent;
|
|
176
|
+
}
|
|
177
|
+
const parent = dirname(outDir);
|
|
178
|
+
if (parent !== root && parent.endsWith(".sdPlugin")) return parent;
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Derive the CodePath (manifest entry point) from the bundler output
|
|
183
|
+
* file path, relative to the .sdPlugin directory.
|
|
184
|
+
*
|
|
185
|
+
* Example:
|
|
186
|
+
* outFile: "/project/com.example.sdPlugin/bin/plugin.mjs"
|
|
187
|
+
* pluginDir: "/project/com.example.sdPlugin"
|
|
188
|
+
* → "bin/plugin.mjs"
|
|
189
|
+
*/
|
|
190
|
+
function deriveCodePath(outFile, pluginDir) {
|
|
191
|
+
return relative(pluginDir, outFile).replace(/\\/g, "/");
|
|
192
|
+
}
|
|
131
193
|
//#endregion
|
|
132
|
-
export { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, copyNativeBindings, isDevelopmentMode, isLibraryDevtoolsImport, shouldStripDevtools };
|
|
194
|
+
export { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, TAKUMI_NATIVE_LOADER_CODE, TAKUMI_NATIVE_LOADER_ID, copyNativeBindings, deriveCodePath, isDevelopmentMode, isLibraryDevtoolsImport, resolvePluginDir, 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,10 @@ 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, ActionDefinition, EncoderLayout, WrapperComponent, TakumiBackend, Controller, Coordinates, Size, DeviceInfo, ActionInfo, CanvasInfo, TouchStripLayout, TouchStripLayoutItem, KeyDownPayload, KeyUpPayload, DialRotatePayload, DialPressPayload, TouchTapPayload, DialHints, StreamDeckAccess, TouchStripInfo, TouchStripTapPayload, TouchStripDialRotatePayload, TouchStripDialPressPayload, } from './types';
|
|
29
|
+
export type { PluginManifestInfo, ActionManifestInfo, ManifestController, ManifestEncoderInfo, ManifestTriggerDescription, ManifestStateInfo, ManifestOSInfo, ManifestNodejsInfo, ManifestProfileInfo, } from './manifest-types';
|
|
27
30
|
export type { TapOptions, LongPressOptions, DoubleTapOptions } from './hooks/gestures';
|
|
28
31
|
export type { BoxProps } from './components/Box';
|
|
29
32
|
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 };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ActionManifestInfo, ManifestActionSource } from './manifest-types';
|
|
2
|
+
type ASTNode = Record<string, any>;
|
|
3
|
+
export interface ExtractedAction {
|
|
4
|
+
uuid: string;
|
|
5
|
+
hasKey: boolean;
|
|
6
|
+
hasDial: boolean;
|
|
7
|
+
hasTouchStrip: boolean;
|
|
8
|
+
info: ActionManifestInfo | null;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Extract all `defineAction()` calls from an ESTree-compatible AST.
|
|
12
|
+
*
|
|
13
|
+
* Walks the entire AST looking for `defineAction({ uuid, key?, dial?,
|
|
14
|
+
* touchStrip?, info? })` patterns and returns the extracted metadata.
|
|
15
|
+
*
|
|
16
|
+
* Actions with `info.disabled: true` are included in the results but
|
|
17
|
+
* can be filtered by the caller.
|
|
18
|
+
*
|
|
19
|
+
* @param ast - ESTree-compatible AST (from Rollup's `this.parse()` or `moduleParsed`)
|
|
20
|
+
* @returns Array of extracted action metadata
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractActionsFromAST(ast: ASTNode): ExtractedAction[];
|
|
23
|
+
/**
|
|
24
|
+
* Convert an ExtractedAction to a ManifestActionSource for the
|
|
25
|
+
* manifest generation engine.
|
|
26
|
+
*
|
|
27
|
+
* The `key`/`dial`/`touchStrip` fields are set to `true` (truthy)
|
|
28
|
+
* when the corresponding property was present in the defineAction()
|
|
29
|
+
* call, enabling the controller derivation logic in manifest-gen.ts.
|
|
30
|
+
*/
|
|
31
|
+
export declare function extractedToActionSource(extracted: ExtractedAction): ManifestActionSource;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
//#region src/manifest-extract.ts
|
|
2
|
+
var SKIP_KEYS = new Set([
|
|
3
|
+
"type",
|
|
4
|
+
"start",
|
|
5
|
+
"end",
|
|
6
|
+
"loc",
|
|
7
|
+
"range"
|
|
8
|
+
]);
|
|
9
|
+
function walkAST(node, visitor) {
|
|
10
|
+
if (!node || typeof node !== "object" || !node.type) return;
|
|
11
|
+
visitor(node);
|
|
12
|
+
for (const key of Object.keys(node)) {
|
|
13
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
14
|
+
const child = node[key];
|
|
15
|
+
if (Array.isArray(child)) {
|
|
16
|
+
for (const item of child) if (item && typeof item === "object" && item.type) walkAST(item, visitor);
|
|
17
|
+
} else if (child && typeof child === "object" && child.type) walkAST(child, visitor);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
var UNEVALUABLE = Symbol("unevaluable");
|
|
21
|
+
function evaluateStatic(node) {
|
|
22
|
+
if (!node) return UNEVALUABLE;
|
|
23
|
+
switch (node.type) {
|
|
24
|
+
case "Literal": return node.value;
|
|
25
|
+
case "ObjectExpression": {
|
|
26
|
+
const obj = {};
|
|
27
|
+
for (const prop of node.properties ?? []) {
|
|
28
|
+
if (prop.type === "SpreadElement") return UNEVALUABLE;
|
|
29
|
+
if (prop.type !== "Property") continue;
|
|
30
|
+
if (prop.computed) return UNEVALUABLE;
|
|
31
|
+
const key = prop.key.type === "Identifier" ? prop.key.name : prop.key.value;
|
|
32
|
+
if (typeof key !== "string") return UNEVALUABLE;
|
|
33
|
+
const value = evaluateStatic(prop.value);
|
|
34
|
+
if (value === UNEVALUABLE) return UNEVALUABLE;
|
|
35
|
+
obj[key] = value;
|
|
36
|
+
}
|
|
37
|
+
return obj;
|
|
38
|
+
}
|
|
39
|
+
case "ArrayExpression": {
|
|
40
|
+
const arr = [];
|
|
41
|
+
for (const el of node.elements ?? []) {
|
|
42
|
+
if (!el) {
|
|
43
|
+
arr.push(null);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (el.type === "SpreadElement") return UNEVALUABLE;
|
|
47
|
+
const value = evaluateStatic(el);
|
|
48
|
+
if (value === UNEVALUABLE) return UNEVALUABLE;
|
|
49
|
+
arr.push(value);
|
|
50
|
+
}
|
|
51
|
+
return arr;
|
|
52
|
+
}
|
|
53
|
+
case "UnaryExpression":
|
|
54
|
+
if (node.operator === "-" && node.argument?.type === "Literal" && typeof node.argument.value === "number") return -node.argument.value;
|
|
55
|
+
if (node.operator === "!") {
|
|
56
|
+
const arg = evaluateStatic(node.argument);
|
|
57
|
+
if (arg === UNEVALUABLE) return UNEVALUABLE;
|
|
58
|
+
return !arg;
|
|
59
|
+
}
|
|
60
|
+
return UNEVALUABLE;
|
|
61
|
+
case "TemplateLiteral":
|
|
62
|
+
if (node.expressions?.length === 0 && node.quasis?.length === 1) return node.quasis[0]?.value?.cooked;
|
|
63
|
+
return UNEVALUABLE;
|
|
64
|
+
default: return UNEVALUABLE;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function isDefineActionCall(node) {
|
|
68
|
+
if (node.type !== "CallExpression") return false;
|
|
69
|
+
const callee = node.callee;
|
|
70
|
+
return callee?.type === "Identifier" && callee.name === "defineAction";
|
|
71
|
+
}
|
|
72
|
+
function extractFromDefineAction(node) {
|
|
73
|
+
const args = node.arguments;
|
|
74
|
+
if (!args || args.length === 0) return null;
|
|
75
|
+
const arg = args[0];
|
|
76
|
+
if (arg.type !== "ObjectExpression") return null;
|
|
77
|
+
const propMap = /* @__PURE__ */ new Map();
|
|
78
|
+
for (const prop of arg.properties ?? []) {
|
|
79
|
+
if (prop.type !== "Property" || prop.computed) continue;
|
|
80
|
+
const key = prop.key?.type === "Identifier" ? prop.key.name : prop.key?.value;
|
|
81
|
+
if (typeof key === "string") propMap.set(key, prop.value);
|
|
82
|
+
}
|
|
83
|
+
const uuidNode = propMap.get("uuid");
|
|
84
|
+
if (!uuidNode || uuidNode.type !== "Literal" || typeof uuidNode.value !== "string") return null;
|
|
85
|
+
const infoNode = propMap.get("info");
|
|
86
|
+
let info = null;
|
|
87
|
+
if (infoNode) {
|
|
88
|
+
const evaluated = evaluateStatic(infoNode);
|
|
89
|
+
if (evaluated !== UNEVALUABLE && typeof evaluated === "object" && evaluated !== null) {
|
|
90
|
+
const candidate = evaluated;
|
|
91
|
+
if (typeof candidate.name === "string" && typeof candidate.icon === "string") info = candidate;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
uuid: uuidNode.value,
|
|
96
|
+
hasKey: propMap.has("key"),
|
|
97
|
+
hasDial: propMap.has("dial"),
|
|
98
|
+
hasTouchStrip: propMap.has("touchStrip"),
|
|
99
|
+
info
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Extract all `defineAction()` calls from an ESTree-compatible AST.
|
|
104
|
+
*
|
|
105
|
+
* Walks the entire AST looking for `defineAction({ uuid, key?, dial?,
|
|
106
|
+
* touchStrip?, info? })` patterns and returns the extracted metadata.
|
|
107
|
+
*
|
|
108
|
+
* Actions with `info.disabled: true` are included in the results but
|
|
109
|
+
* can be filtered by the caller.
|
|
110
|
+
*
|
|
111
|
+
* @param ast - ESTree-compatible AST (from Rollup's `this.parse()` or `moduleParsed`)
|
|
112
|
+
* @returns Array of extracted action metadata
|
|
113
|
+
*/
|
|
114
|
+
function extractActionsFromAST(ast) {
|
|
115
|
+
const results = [];
|
|
116
|
+
walkAST(ast, (node) => {
|
|
117
|
+
if (!isDefineActionCall(node)) return;
|
|
118
|
+
const extracted = extractFromDefineAction(node);
|
|
119
|
+
if (extracted) results.push(extracted);
|
|
120
|
+
});
|
|
121
|
+
return results;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Convert an ExtractedAction to a ManifestActionSource for the
|
|
125
|
+
* manifest generation engine.
|
|
126
|
+
*
|
|
127
|
+
* The `key`/`dial`/`touchStrip` fields are set to `true` (truthy)
|
|
128
|
+
* when the corresponding property was present in the defineAction()
|
|
129
|
+
* call, enabling the controller derivation logic in manifest-gen.ts.
|
|
130
|
+
*/
|
|
131
|
+
function extractedToActionSource(extracted) {
|
|
132
|
+
return {
|
|
133
|
+
uuid: extracted.uuid,
|
|
134
|
+
key: extracted.hasKey ? true : void 0,
|
|
135
|
+
dial: extracted.hasDial ? true : void 0,
|
|
136
|
+
touchStrip: extracted.hasTouchStrip ? true : void 0,
|
|
137
|
+
info: extracted.info ?? void 0
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { extractActionsFromAST, extractedToActionSource };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ManifestConfig } from './manifest-types';
|
|
2
|
+
export interface ManifestValidationError {
|
|
3
|
+
field: string;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Validate just the plugin UUID format.
|
|
8
|
+
*
|
|
9
|
+
* Used in `buildStart` for early error reporting before action
|
|
10
|
+
* extraction is complete.
|
|
11
|
+
*
|
|
12
|
+
* @returns A validation error if the UUID is invalid, or `null` if valid.
|
|
13
|
+
*/
|
|
14
|
+
export declare function validatePluginUUID(uuid: string): ManifestValidationError | null;
|
|
15
|
+
/**
|
|
16
|
+
* Validate a full ManifestConfig for correctness.
|
|
17
|
+
*
|
|
18
|
+
* Checks:
|
|
19
|
+
* - All action UUIDs are prefixed with the plugin UUID
|
|
20
|
+
* - No duplicate action UUIDs
|
|
21
|
+
* - Plugin UUID matches reverse-DNS pattern
|
|
22
|
+
*
|
|
23
|
+
* Called in `writeBundle` after action extraction is complete.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateManifestConfig(config: ManifestConfig, warn?: (msg: string) => void): ManifestValidationError[];
|
|
26
|
+
type JsonRecord = Record<string, any>;
|
|
27
|
+
/**
|
|
28
|
+
* Build the full manifest JSON object from a ManifestConfig.
|
|
29
|
+
*
|
|
30
|
+
* Applies all auto-derivation defaults and transforms camelCase
|
|
31
|
+
* to the PascalCase format expected by the Elgato schema.
|
|
32
|
+
*
|
|
33
|
+
* @param config - The manifest configuration
|
|
34
|
+
* @param codePath - Override CodePath (typically derived from bundler output)
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildManifestJson(config: ManifestConfig, codePath?: string): JsonRecord;
|
|
37
|
+
/**
|
|
38
|
+
* Generate the full manifest JSON string from a ManifestConfig.
|
|
39
|
+
*
|
|
40
|
+
* @param config - The manifest configuration
|
|
41
|
+
* @param codePath - Override CodePath (typically derived from bundler output)
|
|
42
|
+
* @returns Formatted JSON string
|
|
43
|
+
*/
|
|
44
|
+
export declare function generateManifestJsonString(config: ManifestConfig, codePath?: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Write manifest.json only when the content has changed.
|
|
47
|
+
* Creates the parent directory if it does not exist.
|
|
48
|
+
*
|
|
49
|
+
* @returns `true` if the file was written, `false` if content was unchanged.
|
|
50
|
+
*/
|
|
51
|
+
export declare function writeManifestIfChanged(outPath: string, content: string): boolean;
|
|
52
|
+
export {};
|