@liqvid/studio 1.0.0-alpha.0

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.
Files changed (129) hide show
  1. package/LICENSE +9 -0
  2. package/dist/esm/LiqvidDevToolsProvider.js +49 -0
  3. package/dist/esm/api/contract.mjs +48 -0
  4. package/dist/esm/api/project-meta.mjs +33 -0
  5. package/dist/esm/api/recording.mjs +156 -0
  6. package/dist/esm/api/root.mjs +4 -0
  7. package/dist/esm/api/static-file.mjs +82 -0
  8. package/dist/esm/api/types.mjs +1 -0
  9. package/dist/esm/client.mjs +98 -0
  10. package/dist/esm/conventions.mjs +3 -0
  11. package/dist/esm/index.mjs +20 -0
  12. package/dist/esm/initialize.mjs +50 -0
  13. package/dist/esm/jobs/watch-assets.mjs +99 -0
  14. package/dist/esm/jobs/watch-project-files.mjs +216 -0
  15. package/dist/esm/next/api.mjs +48 -0
  16. package/dist/esm/next/page.js +15 -0
  17. package/dist/esm/pages/NewProjectButton.js +62 -0
  18. package/dist/esm/pages/RebuildButton.js +24 -0
  19. package/dist/esm/pages/root-actions.js +151 -0
  20. package/dist/esm/pages/root.js +25 -0
  21. package/dist/esm/pages/root.module.css +326 -0
  22. package/dist/esm/palette.css +279 -0
  23. package/dist/esm/providers/hosting/github-pages.mjs +10 -0
  24. package/dist/esm/providers/hosting/liqvid-studio.mjs +8 -0
  25. package/dist/esm/providers/hosting/s3.mjs +9 -0
  26. package/dist/esm/providers/hosting/sftp.mjs +22 -0
  27. package/dist/esm/providers/index.mjs +10 -0
  28. package/dist/esm/providers/social/bluesky.mjs +8 -0
  29. package/dist/esm/providers/social/facebook.mjs +8 -0
  30. package/dist/esm/providers/social/instagram.mjs +8 -0
  31. package/dist/esm/providers/social/twitter.mjs +7 -0
  32. package/dist/esm/providers/social/youtube.mjs +7 -0
  33. package/dist/esm/providers/types.mjs +1 -0
  34. package/dist/esm/publish.mjs +37 -0
  35. package/dist/esm/recording/RecordingControl.js +110 -0
  36. package/dist/esm/recording/RecordingControl.module.css +0 -0
  37. package/dist/esm/recording/RecordingDialog.js +114 -0
  38. package/dist/esm/recording/RecordingDialog.module.css +194 -0
  39. package/dist/esm/schemas/liqvid-config.mjs +32 -0
  40. package/dist/esm/schemas/project.mjs +27 -0
  41. package/dist/esm/schemas/recording-meta.mjs +11 -0
  42. package/dist/esm/types/assets.mjs +1 -0
  43. package/dist/esm/types.mjs +12 -0
  44. package/dist/esm/ui/Dialog.js +71 -0
  45. package/dist/esm/ui/DockableDialog.js +131 -0
  46. package/dist/esm/ui/DockableDialog.module.css +63 -0
  47. package/dist/esm/ui/RadioTabs.js +13 -0
  48. package/dist/esm/ui/RadioTabs.module.css +54 -0
  49. package/dist/esm/ui/Tabs.js +29 -0
  50. package/dist/esm/ui/Tabs.module.css +31 -0
  51. package/dist/esm/ui/Toast.js +64 -0
  52. package/dist/esm/ui/Toast.module.css +50 -0
  53. package/dist/esm/ui/Toaster.js +13 -0
  54. package/dist/esm/ui/Toaster.module.css +9 -0
  55. package/dist/esm/ui/test.js +14 -0
  56. package/dist/esm/utils/dom.mjs +6 -0
  57. package/dist/esm/utils/fs.mjs +94 -0
  58. package/dist/esm/utils/misc.mjs +15 -0
  59. package/dist/esm/utils/react.mjs +8 -0
  60. package/dist/esm/utils/rsync.mjs +57 -0
  61. package/dist/templates/project.json.hbs +5 -0
  62. package/dist/templates/projects/code/page.tsx.hbs +23 -0
  63. package/dist/templates/projects/code/src/assets.ts.hbs +9 -0
  64. package/dist/templates/projects/code/src/client.tsx.hbs +22 -0
  65. package/dist/templates/projects/code/src/helpers.ts.hbs +21 -0
  66. package/dist/templates/projects/code/src/highlights.ts.hbs +3 -0
  67. package/dist/templates/projects/code/src/markers.ts.hbs +13 -0
  68. package/dist/templates/projects/code/src/project.ts.hbs +23 -0
  69. package/dist/templates/projects/code/template.json +3 -0
  70. package/dist/templates/projects/default/page.tsx.hbs +23 -0
  71. package/dist/templates/projects/default/src/assets.ts.hbs +9 -0
  72. package/dist/templates/projects/default/src/client.tsx.hbs +22 -0
  73. package/dist/templates/projects/default/src/helpers.ts.hbs +21 -0
  74. package/dist/templates/projects/default/src/highlights.ts.hbs +3 -0
  75. package/dist/templates/projects/default/src/markers.ts.hbs +13 -0
  76. package/dist/templates/projects/default/src/project.ts.hbs +23 -0
  77. package/dist/templates/projects/default/template.json +4 -0
  78. package/dist/templates/types.ts.hbs +20 -0
  79. package/dist/types/LiqvidDevToolsProvider.d.ts +12 -0
  80. package/dist/types/api/contract.d.mts +66 -0
  81. package/dist/types/api/project-meta.d.mts +1 -0
  82. package/dist/types/api/recording.d.mts +5 -0
  83. package/dist/types/api/root.d.mts +1 -0
  84. package/dist/types/api/static-file.d.mts +6 -0
  85. package/dist/types/api/types.d.mts +1 -0
  86. package/dist/types/client.d.mts +43 -0
  87. package/dist/types/conventions.d.mts +3 -0
  88. package/dist/types/index.d.mts +19 -0
  89. package/dist/types/initialize.d.mts +14 -0
  90. package/dist/types/jobs/watch-assets.d.mts +18 -0
  91. package/dist/types/jobs/watch-project-files.d.mts +4 -0
  92. package/dist/types/next/api.d.mts +14 -0
  93. package/dist/types/next/page.d.ts +4 -0
  94. package/dist/types/pages/NewProjectButton.d.ts +1 -0
  95. package/dist/types/pages/RebuildButton.d.ts +1 -0
  96. package/dist/types/pages/root-actions.d.ts +29 -0
  97. package/dist/types/pages/root.d.ts +1 -0
  98. package/dist/types/providers/hosting/github-pages.d.mts +11 -0
  99. package/dist/types/providers/hosting/liqvid-studio.d.mts +10 -0
  100. package/dist/types/providers/hosting/s3.d.mts +11 -0
  101. package/dist/types/providers/hosting/sftp.d.mts +13 -0
  102. package/dist/types/providers/index.d.mts +10 -0
  103. package/dist/types/providers/social/bluesky.d.mts +10 -0
  104. package/dist/types/providers/social/facebook.d.mts +10 -0
  105. package/dist/types/providers/social/instagram.d.mts +10 -0
  106. package/dist/types/providers/social/twitter.d.mts +9 -0
  107. package/dist/types/providers/social/youtube.d.mts +9 -0
  108. package/dist/types/providers/types.d.mts +9 -0
  109. package/dist/types/publish.d.mts +1 -0
  110. package/dist/types/recording/RecordingControl.d.ts +16 -0
  111. package/dist/types/recording/RecordingDialog.d.ts +10 -0
  112. package/dist/types/schemas/liqvid-config.d.mts +51 -0
  113. package/dist/types/schemas/project.d.mts +51 -0
  114. package/dist/types/schemas/recording-meta.d.mts +17 -0
  115. package/dist/types/types/assets.d.mts +3 -0
  116. package/dist/types/types.d.mts +20 -0
  117. package/dist/types/ui/Dialog.d.ts +31 -0
  118. package/dist/types/ui/DockableDialog.d.ts +25 -0
  119. package/dist/types/ui/RadioTabs.d.ts +18 -0
  120. package/dist/types/ui/Tabs.d.ts +9 -0
  121. package/dist/types/ui/Toast.d.ts +23 -0
  122. package/dist/types/ui/Toaster.d.ts +6 -0
  123. package/dist/types/ui/test.d.ts +9 -0
  124. package/dist/types/utils/dom.d.mts +3 -0
  125. package/dist/types/utils/fs.d.mts +32 -0
  126. package/dist/types/utils/misc.d.mts +4 -0
  127. package/dist/types/utils/react.d.mts +5 -0
  128. package/dist/types/utils/rsync.d.mts +10 -0
  129. package/package.json +94 -0
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ export const ProviderConfigYouTube = z.object({
3
+ username: z.string(),
4
+ });
5
+ export class YouTubeProvider {
6
+ async publish() { }
7
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import { providersMap } from "./providers/index.mjs";
2
+ import { LiqvidConfig } from "./schemas/liqvid-config.mjs";
3
+ import { loadJson } from "./utils/fs.mjs";
4
+ // Example Usage (Demonstration - NOT production ready)
5
+ async function main() {
6
+ // load config file
7
+ const configFileName = "./liqvid.config.json";
8
+ const $config = await loadJson(LiqvidConfig, configFileName);
9
+ if (!$config.isOk) {
10
+ console.error("failed to parse config file", $config.unwrapErr());
11
+ process.exit(1);
12
+ }
13
+ const config = $config.unwrap();
14
+ // initialize providers
15
+ const contentProviderConfig = config.providers[config.backend.content];
16
+ if (!contentProviderConfig) {
17
+ throw new Error(`missing configuration for content provider "${config.backend.content}"`);
18
+ }
19
+ const contentProvider = new providersMap[config.backend.content](contentProviderConfig);
20
+ // const mediaProviderConfig = config.providers[config.backend.media];
21
+ // if (!mediaProviderConfig) {
22
+ // throw new Error(
23
+ // `missing configuration for media provider "${config.backend.media}"`,
24
+ // );
25
+ // }
26
+ // const _mediaProvider = new providersMap[config.backend.media](
27
+ // mediaProviderConfig as any,
28
+ // );
29
+ try {
30
+ await contentProvider.publishContent("./build/client");
31
+ console.log("Directory synchronization completed successfully!");
32
+ }
33
+ catch (error) {
34
+ console.error("Directory synchronization failed:", error);
35
+ }
36
+ }
37
+ main();
@@ -0,0 +1,110 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEventListener } from "@liqvid/event-emitter/react";
4
+ import { useKeyboardShortcut } from "@liqvid/keymap/react";
5
+ import { useRecordingApi } from "@liqvid/recording";
6
+ import { useForceUpdate } from "@liqvid/utils";
7
+ import { useCallback, useRef, useState } from "react";
8
+ import { saveRecording } from "../client.mjs";
9
+ import { useProjectContext } from "../LiqvidDevToolsProvider";
10
+ import { DockableDialog } from "../ui/DockableDialog";
11
+ import { RecordingDialog } from "./RecordingDialog";
12
+ /**
13
+ * Liqvid recording control.
14
+ */
15
+ export function RecordingControl({ shortcuts }) {
16
+ const { manager, discard, pauseResume, startStop } = useRecordingApi();
17
+ const { projectPath } = useProjectContext();
18
+ const forceUpdate = useForceUpdate();
19
+ // Collect finalized data from all plugins
20
+ const finalizedDataRef = useRef([]);
21
+ // recording manager
22
+ useEventListener(manager, "finalize", useCallback((args) => {
23
+ if (args === undefined) {
24
+ // End signal - save all collected data to server
25
+ const recordingData = finalizedDataRef.current;
26
+ finalizedDataRef.current = [];
27
+ if (recordingData.length > 0) {
28
+ saveRecording({
29
+ body: {
30
+ durationMs: manager.duration.inMilliseconds(),
31
+ plugins: recordingData,
32
+ },
33
+ search: { url: projectPath },
34
+ });
35
+ }
36
+ }
37
+ else {
38
+ // Individual plugin data - collect it
39
+ finalizedDataRef.current.push(args);
40
+ }
41
+ forceUpdate();
42
+ }, [forceUpdate, manager, projectPath]));
43
+ useEventListener(manager, "start", forceUpdate);
44
+ useEventListener(manager, "pause", forceUpdate);
45
+ useEventListener(manager, "resume", forceUpdate);
46
+ // warn before closing if recordings exist
47
+ useWarnBeforeClosingIfRecordingsExist();
48
+ // active plugins
49
+ const activePlugins = useRef(null);
50
+ if (activePlugins.current === null) {
51
+ activePlugins.current = {};
52
+ // for (const plugin of plugins) {
53
+ // activePlugins.current[plugin.package] = false;
54
+ // }
55
+ }
56
+ // plugins dictionary
57
+ const [pluginsByKey] = useState(() => {
58
+ const dict = {};
59
+ // for (const plugin of plugins) {
60
+ // dict[plugin.package] = plugin;
61
+ // }
62
+ return dict;
63
+ });
64
+ /* keyboard controls */
65
+ useKeyboardShortcut(shortcuts?.discard, discard);
66
+ useKeyboardShortcut(shortcuts?.pause, pauseResume);
67
+ useKeyboardShortcut(shortcuts?.startStop, startStop);
68
+ /* render */
69
+ return (_jsxs(DockableDialog.Root, { name: "recording", shortcut: shortcuts?.toggle, children: [_jsx(DockableDialog.Trigger, { asChild: true, children: _jsx(RecordingIndicatorIcon, {}) }), _jsx(RecordingDialog, { shortcuts: shortcuts })] }));
70
+ }
71
+ function useWarnBeforeClosingIfRecordingsExist() {
72
+ const { recordings } = useRecordingApi();
73
+ const warn = useRef(false);
74
+ warn.current = recordings.length > 0;
75
+ useEventListener(globalThis?.window, "beforeunload", useCallback((e) => {
76
+ if (warn.current)
77
+ e.returnValue = "You have recording data";
78
+ }, []));
79
+ }
80
+ const colors = {
81
+ active: "red",
82
+ inactive: "#666",
83
+ paused: "yellow",
84
+ };
85
+ const labels = {
86
+ active: "Recording in progress",
87
+ inactive: "No recording in progress",
88
+ paused: "Recording is paused",
89
+ };
90
+ /**
91
+ * Red when playback is active
92
+ */
93
+ function RecordingIndicatorIcon(props) {
94
+ const state = useRecordingState();
95
+ return (_jsxs("svg", { height: "36", viewBox: "-50 -50 100 100", width: "36", ...props, children: [_jsx("title", { children: labels[state] }), _jsx("circle", { cx: "0", cy: "0", fill: colors[state], r: "35", stroke: "white", strokeWidth: "5" })] }));
96
+ }
97
+ function useRecordingState() {
98
+ const { manager } = useRecordingApi();
99
+ const forceUpdate = useForceUpdate();
100
+ // recording manager
101
+ useEventListener(manager, "cancel", forceUpdate);
102
+ useEventListener(manager, "finalize", forceUpdate);
103
+ useEventListener(manager, "pause", forceUpdate);
104
+ useEventListener(manager, "resume", forceUpdate);
105
+ useEventListener(manager, "start", forceUpdate);
106
+ if (manager?.active) {
107
+ return manager.paused ? "paused" : "active";
108
+ }
109
+ return "inactive";
110
+ }
File without changes
@@ -0,0 +1,114 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Collapsible } from "@base-ui/react/collapsible";
3
+ import { Keymap } from "@liqvid/keymap";
4
+ import { useRecordingApi } from "@liqvid/recording";
5
+ import { usePluginApi } from "@liqvid/studio-plugin-api";
6
+ import { formatTime, formatTimeDuration } from "@liqvid/utils";
7
+ import { Fragment, useCallback, useEffect, useState } from "react";
8
+ import { listRecordings } from "../client.mjs";
9
+ import { useProjectContext } from "../LiqvidDevToolsProvider";
10
+ import { DockableDialog } from "../ui/DockableDialog";
11
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/Tabs";
12
+ import { useToggle } from "../utils/react.mjs";
13
+ import styles from "./RecordingDialog.module.css";
14
+ export function RecordingDialog({ shortcuts, onShortcutChange, }) {
15
+ const { projectPath } = useProjectContext();
16
+ const { enabledPlugins, togglePlugin } = useRecordingApi();
17
+ const { plugins } = usePluginApi();
18
+ const [recordings, setRecordings] = useState([]);
19
+ useEffect(() => {
20
+ listRecordings({ search: { url: projectPath } }).then(($res) => {
21
+ if ($res.isErr) {
22
+ console.error($res.unwrapErr());
23
+ return;
24
+ }
25
+ setRecordings($res.unwrap());
26
+ });
27
+ }, [projectPath]);
28
+ return (_jsxs(DockableDialog.Dialog, { children: [_jsx(DockableDialog.Header, { children: "Recording" }), _jsx(DockableDialog.Content, { children: _jsx("div", { id: "lv-recording-dialog", children: _jsxs(Tabs, { defaultValue: "configuration", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { className: "lv-recording-tabs", value: "configuration", children: "Configuration" }), _jsx(TabsTrigger, { className: "lv-recording-tabs", value: "saved", children: "Recordings" }), _jsx(TabsTrigger, { className: "lv-recording-tabs", value: "shortcuts", children: "Shortcuts" })] }), _jsx(TabsContent, { asChild: true, value: "configuration", children: _jsxs("section", { children: [_jsx("h3", { children: "Plugins" }), _jsx("div", { className: styles.togglePlugins, children: Object.values(plugins).map((plugin) => {
29
+ if (!("recorder" in plugin))
30
+ return null;
31
+ const studioPlugin = plugins[plugin.package];
32
+ if (!studioPlugin)
33
+ return null;
34
+ return (_jsx("button", { "aria-checked": enabledPlugins[plugin.package], className: styles.recordingToggle, onClick: () => togglePlugin(plugin.package), role: "switch", type: "button", children: plugin.icon({ height: 24, width: 24 }) }, plugin.package));
35
+ }) }), _jsx("table", { className: styles.configurationTable, children: _jsx("tbody", { children: Object.values(plugins).map((plugin) => {
36
+ if (!("recorder" in plugin))
37
+ return null;
38
+ if (!enabledPlugins[plugin.package])
39
+ return null;
40
+ const ConfigurationComponent = plugin.configurationComponent;
41
+ if (!ConfigurationComponent)
42
+ return null;
43
+ return (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: plugin.icon({ height: 36, width: 36 }) }), _jsx("td", { children: _jsx(ConfigurationComponent, {}) })] }, plugin.package));
44
+ }) }) })] }) }), _jsx(TabsContent, { asChild: true, value: "saved", children: _jsxs("section", { children: [_jsx("h3", { children: "Saved" }), _jsx("div", { className: styles.Recordings, children: recordings.map((r) => (_jsx(RecordingRow, { recording: r }, r.name))) })] }) }), _jsx(TabsContent, { asChild: true, value: "shortcuts", children: _jsxs("section", { children: [_jsx("h3", { children: "Shortcuts" }), _jsx(ShortcutsTable, { onShortcutChange: onShortcutChange, shortcuts: shortcuts })] }) })] }) }) })] }));
45
+ }
46
+ export function RecordingRow({ recording: r }) {
47
+ const { value: expanded, set: setExpanded } = useToggle();
48
+ const { plugins } = usePluginApi();
49
+ // console.log({ plugins });
50
+ return (_jsxs(Collapsible.Root, { className: styles.RecordingRow, onOpenChange: setExpanded, open: expanded, children: [_jsxs(Collapsible.Trigger, { className: styles.RecordingRowTrigger, children: [_jsx("span", { className: styles.recordingName, children: r.name }), _jsx("span", { className: styles.pluginIcons, children: r.plugins.map((p) => p in plugins ? (_jsx(Fragment, { children: plugins[p].icon() }, p)) : null) }), _jsx("time", { className: styles.recordingDuration, dateTime: formatTimeDuration(r.duration), children: formatTime(r.duration) })] }), _jsx(Collapsible.Panel, { className: styles.RecordingRowExpand, children: r.plugins.map((p) => {
51
+ const plugin = plugins[p];
52
+ if (!plugin)
53
+ return null;
54
+ const Component = plugin.recordingComponent;
55
+ if (!Component)
56
+ return null;
57
+ return _jsx(Component, { name: r.name }, plugin.package);
58
+ }) })] }));
59
+ }
60
+ const shortcutCommands = [
61
+ ["Toggle panel", "toggle"],
62
+ ["Start/Stop recording", "startStop"],
63
+ ["Pause recording", "pause"],
64
+ ["Discard recording", "discard"],
65
+ ];
66
+ function ShortcutsTable({ shortcuts, onShortcutChange, }) {
67
+ return (_jsxs("table", { className: styles.shortcutsTable, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Command" }), _jsx("th", { children: "Shortcut" })] }) }), _jsx("tbody", { children: shortcutCommands.map(([label, key]) => (_jsx(ShortcutRow, { label: label, onChange: onShortcutChange
68
+ ? (value) => onShortcutChange(key, value)
69
+ : undefined, shortcut: shortcuts?.[key] }, key))) })] }));
70
+ }
71
+ function ShortcutRow({ label, shortcut, onChange, }) {
72
+ const [isRecording, setIsRecording] = useState(false);
73
+ const [localValue, setLocalValue] = useState(undefined);
74
+ const displayValue = localValue ?? shortcut;
75
+ const identifyKey = useCallback((e) => {
76
+ e.preventDefault();
77
+ const seq = Keymap.identify(e);
78
+ setLocalValue(seq);
79
+ onChange?.(seq);
80
+ setIsRecording(false);
81
+ }, [onChange]);
82
+ const handleFocus = useCallback(() => {
83
+ setIsRecording(true);
84
+ }, []);
85
+ const handleBlur = useCallback(() => {
86
+ setIsRecording(false);
87
+ }, []);
88
+ return (_jsxs("tr", { children: [_jsx("td", { children: label }), _jsx("td", { children: _jsx("input", { className: styles.shortcutInput, "data-recording": isRecording || undefined, onBlur: handleBlur, onFocus: handleFocus, onKeyDown: identifyKey, placeholder: isRecording ? "Press a key..." : "Click to record", readOnly: true, type: "text", value: displayValue ? fmtSeq(displayValue) : "" }) })] }));
89
+ }
90
+ /** Format key sequences with special characters on Mac */
91
+ function fmtSeq(str) {
92
+ if (!isMac())
93
+ return str;
94
+ if (str === undefined)
95
+ return str;
96
+ return str
97
+ .split("+")
98
+ .map((k) => {
99
+ if (k === "Ctrl")
100
+ return "^";
101
+ else if (k === "Alt")
102
+ return "\u2325";
103
+ if (k === "Shift")
104
+ return "\u21E7";
105
+ if (k === "Meta")
106
+ return "\u2318";
107
+ return k;
108
+ })
109
+ .join("");
110
+ }
111
+ function isMac() {
112
+ return (typeof globalThis.navigator !== "undefined" &&
113
+ navigator.platform === "MacIntel");
114
+ }
@@ -0,0 +1,194 @@
1
+ .Recordings {
2
+ --radius: 4px;
3
+
4
+ background-color: var(--gray-ui);
5
+ border: 1px solid var(--gray-sep);
6
+ border-radius: var(--radius);
7
+
8
+ margin: 4px 0;
9
+
10
+ * {
11
+ transition-property: background-color, border-color, color;
12
+ transition-duration: 150ms;
13
+ }
14
+ }
15
+
16
+ .RecordingRow {
17
+ &:first-child {
18
+ &,
19
+ & > .RecordingRowTrigger {
20
+ border-radius: var(--radius) var(--radius) 0 0;
21
+ }
22
+ }
23
+
24
+ &:last-child {
25
+ &,
26
+ & > .RecordingRowTrigger {
27
+ border-radius: 0 0 var(--radius) var(--radius);
28
+ }
29
+ }
30
+ }
31
+
32
+ .RecordingRowTrigger {
33
+ align-items: center;
34
+ cursor: pointer;
35
+ display: flex;
36
+ padding: 4px 8px;
37
+ width: 100%;
38
+
39
+ &:hover {
40
+ background-color: var(--gray-hover);
41
+ }
42
+
43
+ &[data-panel-open] {
44
+ background-color: var(--gray-active);
45
+ }
46
+ }
47
+
48
+ .recordingName {
49
+ font-family: monospace;
50
+ }
51
+
52
+ .pluginIcons {
53
+ margin-left: auto;
54
+
55
+ svg {
56
+ height: 24px;
57
+ width: 24px;
58
+ }
59
+ }
60
+
61
+ .recordingDuration {
62
+ font-family: monospace;
63
+ font-size: 16px;
64
+ text-align: right;
65
+ width: 4em;
66
+ }
67
+
68
+ .RecordingRowExpand {
69
+ background-color: var(--gray-subtle);
70
+ overflow: hidden;
71
+
72
+ &[data-open] {
73
+ animation: slideDown 150ms ease-out;
74
+ }
75
+
76
+ &[data-closed] {
77
+ animation: slideUp 150ms ease-out;
78
+ }
79
+ }
80
+
81
+ @keyframes slideDown {
82
+ from {
83
+ height: 0;
84
+ }
85
+ to {
86
+ height: var(--collapsible-panel-height);
87
+ }
88
+ }
89
+
90
+ @keyframes slideUp {
91
+ from {
92
+ height: var(--collapsible-panel-height);
93
+ }
94
+ to {
95
+ height: 0;
96
+ }
97
+ }
98
+
99
+ .details {
100
+ padding: 4px 4px 4px 16px;
101
+ }
102
+
103
+ .togglePlugins {
104
+ display: flex;
105
+ gap: 0.5em;
106
+ }
107
+
108
+ .recordingToggle {
109
+ background-color: var(--gray-ui);
110
+ border-radius: 4px;
111
+ cursor: pointer;
112
+ height: 36px;
113
+ width: 36px;
114
+
115
+ transition: unset;
116
+
117
+ &[aria-checked="true"] {
118
+ background-color: red;
119
+ }
120
+
121
+ > svg {
122
+ height: 36px;
123
+ width: 36px;
124
+ }
125
+ }
126
+
127
+ .configurationTable {
128
+ border: 1px solid #000;
129
+ margin: 1em auto;
130
+ width: 100%;
131
+
132
+ > tbody > tr > th {
133
+ text-align: right;
134
+ padding: 4px;
135
+ width: calc(36px + 8px);
136
+ }
137
+
138
+ > tbody > tr > td {
139
+ padding: 4px;
140
+ text-align: left;
141
+ }
142
+ }
143
+
144
+ .shortcutsTable {
145
+ border: 1px solid var(--gray-sep);
146
+ border-collapse: collapse;
147
+ margin: 0.5em 0;
148
+ width: 100%;
149
+
150
+ th,
151
+ td {
152
+ border: 1px solid var(--gray-sep);
153
+ padding: 6px 8px;
154
+ text-align: left;
155
+ }
156
+
157
+ thead th {
158
+ background-color: var(--gray-ui);
159
+ font-size: 12px;
160
+ font-weight: 600;
161
+ text-transform: uppercase;
162
+ }
163
+
164
+ tbody tr:hover {
165
+ background-color: var(--gray-hover);
166
+ }
167
+ }
168
+
169
+ .shortcutInput {
170
+ background-color: var(--gray-subtle);
171
+ border: 1px solid var(--gray-sep);
172
+ border-radius: 4px;
173
+ cursor: pointer;
174
+ font-family: monospace;
175
+ font-size: 13px;
176
+ padding: 4px 8px;
177
+ text-align: center;
178
+ width: 100%;
179
+
180
+ &:focus {
181
+ border-color: var(--blue-focus, #0066cc);
182
+ outline: none;
183
+ }
184
+
185
+ &[data-recording] {
186
+ background-color: var(--yellow-subtle, #fffbe6);
187
+ border-color: var(--yellow-border, #d9a600);
188
+ }
189
+
190
+ &::placeholder {
191
+ color: var(--gray-text-muted, #888);
192
+ font-style: italic;
193
+ }
194
+ }
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+ import { ProviderConfigGitHubPages } from "../providers/hosting/github-pages.mjs";
3
+ import { ProviderConfigLiqvidStudio } from "../providers/hosting/liqvid-studio.mjs";
4
+ import { ProviderConfigS3 } from "../providers/hosting/s3.mjs";
5
+ import { ProviderConfigSFTP } from "../providers/hosting/sftp.mjs";
6
+ import { ProviderConfigBlueSky } from "../providers/social/bluesky.mjs";
7
+ import { ProviderConfigFacebook } from "../providers/social/facebook.mjs";
8
+ import { ProviderConfigInstagram } from "../providers/social/instagram.mjs";
9
+ import { ProviderConfigTwitter } from "../providers/social/twitter.mjs";
10
+ import { ProviderConfigYouTube } from "../providers/social/youtube.mjs";
11
+ export const LiqvidConfig = z.object({
12
+ $schema: z.string(),
13
+ backend: z.object({
14
+ content: z.enum(["githubPages", "liqvidStudio", "s3", "sftp"]),
15
+ media: z.enum(["liqvidStudio", "s3", "sftp"]),
16
+ }),
17
+ providers: z.object({
18
+ // hosting
19
+ githubPages: ProviderConfigGitHubPages.optional(),
20
+ liqvidStudio: ProviderConfigLiqvidStudio.optional(),
21
+ s3: ProviderConfigS3.optional(),
22
+ sftp: ProviderConfigSFTP.optional(),
23
+ // social
24
+ ...{
25
+ bluesky: ProviderConfigBlueSky.optional(),
26
+ facebook: ProviderConfigFacebook.optional(),
27
+ instagram: ProviderConfigInstagram.optional(),
28
+ twitter: ProviderConfigTwitter.optional(),
29
+ youtube: ProviderConfigYouTube.optional(),
30
+ },
31
+ }),
32
+ });
@@ -0,0 +1,27 @@
1
+ import { DurationOptions } from "@liqvid/duration/zod";
2
+ import { z } from "zod";
3
+ export const AspectRatio = z.object({
4
+ height: z.number(),
5
+ width: z.number(),
6
+ });
7
+ export const AspectRatioSpecifier = z.union([
8
+ z.templateLiteral([z.number(), ":", z.number()]),
9
+ z.tuple([z.number(), z.number()]),
10
+ AspectRatio,
11
+ ]);
12
+ /**
13
+ * project.json files
14
+ */
15
+ export const ProjectJson = z.object({
16
+ aspectRatio: AspectRatioSpecifier.optional().default({
17
+ height: 9,
18
+ width: 16,
19
+ }),
20
+ name: z.string(),
21
+ });
22
+ /**
23
+ * auto-generated project-meta.json files
24
+ */
25
+ export const AutoGenProjectMeta = z.object({
26
+ duration: DurationOptions,
27
+ });
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ export const RecordingMetaFile = z.object({
3
+ created: z.iso.datetime(),
4
+ duration: z.object({
5
+ milliseconds: z.number(),
6
+ }),
7
+ });
8
+ export const RecordingMeta = RecordingMetaFile.extend({
9
+ name: z.string(),
10
+ plugins: z.array(z.string()),
11
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ export class DirectoryHelper {
2
+ dirname;
3
+ constructor(dirname = "") {
4
+ this.dirname = dirname;
5
+ }
6
+ dir(dirname) {
7
+ return new DirectoryHelper(`${this.dirname}/${dirname}`);
8
+ }
9
+ file(filename, version) {
10
+ return `${this.dirname}/${filename}`;
11
+ }
12
+ }
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Dialog } from "@base-ui/react/dialog";
3
+ import classNames from "classnames";
4
+ import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useMemo, useState, } from "react";
5
+ import styles from "./Dialog.module.css";
6
+ const DialogApiContext = createContext({
7
+ close() { },
8
+ isDialog: false,
9
+ isOpen: false,
10
+ });
11
+ DialogApiContext.displayName = "DialogApi";
12
+ export function useDialogApi() {
13
+ return useContext(DialogApiContext);
14
+ }
15
+ export function DialogRoot({ children, defaultOpen = false, onOpenChange, open: controlledOpen, }) {
16
+ // open state
17
+ const [open, setOpen] = useState(defaultOpen);
18
+ /** setOpen() wrapper which updates ancestor dropdown */
19
+ const wrappedOnOpenChange = useCallback((newOpen) => {
20
+ (onOpenChange ?? setOpen)(newOpen);
21
+ }, [onOpenChange]);
22
+ // api
23
+ const dialogApi = useMemo(() => ({
24
+ close() {
25
+ setOpen(false);
26
+ },
27
+ isDialog: true,
28
+ get isOpen() {
29
+ return controlledOpen ?? open;
30
+ },
31
+ }), [controlledOpen, open]);
32
+ return (_jsx(Dialog.Root, { modal: true, onOpenChange: wrappedOnOpenChange, open: controlledOpen ?? open, children: _jsx(DialogApiContext.Provider, { value: dialogApi, children: children }) }));
33
+ }
34
+ export function DialogClose({ asChild, children, className, ...props }) {
35
+ const combinedClassName = classNames(styles.Close, className);
36
+ if (asChild && isValidElement(children)) {
37
+ return (_jsx(Dialog.Close, { ...props, render: (renderProps) => {
38
+ const child = Children.only(children);
39
+ return cloneElement(child, {
40
+ ...renderProps,
41
+ className: classNames(combinedClassName, child.props.className),
42
+ });
43
+ } }));
44
+ }
45
+ return (_jsx(Dialog.Close, { className: combinedClassName, ...props, children: children }));
46
+ }
47
+ export function DialogContent({ className, ...props }) {
48
+ return (_jsx(Dialog.Popup, { className: classNames(styles.Content, className), ...props }));
49
+ }
50
+ export function DialogPortal(props) {
51
+ return _jsx(Dialog.Portal, { ...props });
52
+ }
53
+ export function DialogOverlay({ className, ...props }) {
54
+ return (_jsx(Dialog.Backdrop, { className: classNames(styles.Overlay, className), ...props }));
55
+ }
56
+ export function DialogTitle({ className, ...props }) {
57
+ return (_jsx(Dialog.Title, { className: classNames(styles.Title, className), ...props }));
58
+ }
59
+ export function DialogTrigger({ asChild, children, className, ...props }) {
60
+ const combinedClassName = classNames(styles.Trigger, className);
61
+ if (asChild && isValidElement(children)) {
62
+ return (_jsx(Dialog.Trigger, { ...props, render: (renderProps) => {
63
+ const child = Children.only(children);
64
+ return cloneElement(child, {
65
+ ...renderProps,
66
+ className: classNames(combinedClassName, child.props.className),
67
+ });
68
+ } }));
69
+ }
70
+ return (_jsx(Dialog.Trigger, { className: combinedClassName, ...props, children: children }));
71
+ }