@1agh/maude 0.41.0 → 0.43.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.
- package/apps/studio/.ai/cache/_stats.json +7 -0
- package/apps/studio/acp/bootstrap-brief.ts +12 -2
- package/apps/studio/annotations-layer.tsx +1055 -278
- package/apps/studio/annotations-model.ts +58 -0
- package/apps/studio/api.ts +173 -63
- package/apps/studio/bin/_html-playwright.mjs +26 -4
- package/apps/studio/bin/_ingest-footage.mjs +386 -0
- package/apps/studio/bin/_pdf-playwright.mjs +13 -2
- package/apps/studio/bin/_png-playwright.mjs +15 -2
- package/apps/studio/bin/_pptx-playwright.mjs +17 -4
- package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
- package/apps/studio/bin/_screenshot-playwright.mjs +17 -2
- package/apps/studio/bin/_svg-playwright.mjs +26 -4
- package/apps/studio/bin/_video-playwright.mjs +141 -71
- package/apps/studio/bin/ingest-footage.sh +27 -0
- package/apps/studio/bin/photo-adjust.sh +163 -0
- package/apps/studio/bin/photo-bg-remove.sh +160 -0
- package/apps/studio/bin/probe-footage.sh +28 -0
- package/apps/studio/bin/read-annotations.mjs +125 -2
- package/apps/studio/bin/screenshot.sh +53 -4
- package/apps/studio/canvas-edit.ts +459 -10
- package/apps/studio/canvas-lib.tsx +523 -5
- package/apps/studio/canvas-pipeline.ts +65 -7
- package/apps/studio/canvas-shell.tsx +156 -16
- package/apps/studio/client/app.jsx +476 -88
- package/apps/studio/client/export-center.jsx +460 -0
- package/apps/studio/client/github.js +4 -0
- package/apps/studio/client/panels/GitPanel.jsx +8 -0
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
- package/apps/studio/client/panels/StickerPicker.jsx +160 -0
- package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
- package/apps/studio/client/photo-knobs.jsx +437 -0
- package/apps/studio/client/styles/3-shell-maude.css +59 -0
- package/apps/studio/client/styles/4-components.css +150 -0
- package/apps/studio/commands/edit-source-command.ts +31 -4
- package/apps/studio/config.schema.json +2 -2
- package/apps/studio/context-menu.tsx +31 -6
- package/apps/studio/dist/client.bundle.js +5561 -19
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
- package/apps/studio/dist/runtime/pixi-js.js +519 -519
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +25 -0
- package/apps/studio/export-dialog.tsx +18 -25
- package/apps/studio/exporters/_runtime.ts +104 -0
- package/apps/studio/exporters/html.ts +12 -20
- package/apps/studio/exporters/index.ts +14 -2
- package/apps/studio/exporters/jobs.ts +376 -0
- package/apps/studio/exporters/pdf.ts +16 -20
- package/apps/studio/exporters/png.ts +12 -20
- package/apps/studio/exporters/pptx.ts +22 -23
- package/apps/studio/exporters/scope.ts +1 -0
- package/apps/studio/exporters/svg.ts +14 -22
- package/apps/studio/exporters/video.ts +37 -19
- package/apps/studio/footage/schema.test.ts +195 -0
- package/apps/studio/footage/schema.ts +484 -0
- package/apps/studio/footage-store.ts +235 -0
- package/apps/studio/fs-watch.ts +10 -0
- package/apps/studio/git/endpoints.ts +68 -7
- package/apps/studio/git/service.ts +198 -20
- package/apps/studio/github/service.ts +67 -0
- package/apps/studio/handoff.ts +3 -2
- package/apps/studio/http.ts +318 -64
- package/apps/studio/input-router.tsx +10 -0
- package/apps/studio/inspect.ts +53 -1
- package/apps/studio/paths.ts +10 -0
- package/apps/studio/photo/filters.ts +285 -0
- package/apps/studio/photo/pipeline.ts +607 -0
- package/apps/studio/photo/schema.ts +367 -0
- package/apps/studio/photo-store.ts +154 -0
- package/apps/studio/runtime-bundle.ts +29 -6
- package/apps/studio/server.ts +8 -2
- package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
- package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
- package/apps/studio/stickers/life-style/best.png +0 -0
- package/apps/studio/stickers/life-style/come-on.png +0 -0
- package/apps/studio/stickers/life-style/cut.png +0 -0
- package/apps/studio/stickers/life-style/detox.png +0 -0
- package/apps/studio/stickers/life-style/just.png +0 -0
- package/apps/studio/stickers/life-style/manifest.json +68 -0
- package/apps/studio/stickers/life-style/mirror.png +0 -0
- package/apps/studio/stickers/life-style/nice.png +0 -0
- package/apps/studio/stickers/life-style/objects.png +0 -0
- package/apps/studio/stickers/life-style/ok.png +0 -0
- package/apps/studio/stickers/life-style/queen.png +0 -0
- package/apps/studio/stickers/life-style/star.png +0 -0
- package/apps/studio/stickers/life-style/sun.png +0 -0
- package/apps/studio/stickers/life-style/water.png +0 -0
- package/apps/studio/stickers/life-style/yeah.png +0 -0
- package/apps/studio/stickers/life-style/you-can.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
- package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
- package/apps/studio/stickers/project-status/group-100.png +0 -0
- package/apps/studio/stickers/project-status/group-101.png +0 -0
- package/apps/studio/stickers/project-status/group-102.png +0 -0
- package/apps/studio/stickers/project-status/group-103.png +0 -0
- package/apps/studio/stickers/project-status/group-104.png +0 -0
- package/apps/studio/stickers/project-status/group-105.png +0 -0
- package/apps/studio/stickers/project-status/group-106.png +0 -0
- package/apps/studio/stickers/project-status/group-107.png +0 -0
- package/apps/studio/stickers/project-status/group-108.png +0 -0
- package/apps/studio/stickers/project-status/group-109.png +0 -0
- package/apps/studio/stickers/project-status/group-110.png +0 -0
- package/apps/studio/stickers/project-status/group-111.png +0 -0
- package/apps/studio/stickers/project-status/group-112.png +0 -0
- package/apps/studio/stickers/project-status/group-113.png +0 -0
- package/apps/studio/stickers/project-status/group-114.png +0 -0
- package/apps/studio/stickers/project-status/group-115.png +0 -0
- package/apps/studio/stickers/project-status/group-117.png +0 -0
- package/apps/studio/stickers/project-status/group-118.png +0 -0
- package/apps/studio/stickers/project-status/group-119.png +0 -0
- package/apps/studio/stickers/project-status/group-120.png +0 -0
- package/apps/studio/stickers/project-status/group-121.png +0 -0
- package/apps/studio/stickers/project-status/group-122.png +0 -0
- package/apps/studio/stickers/project-status/group-41.png +0 -0
- package/apps/studio/stickers/project-status/group-42.png +0 -0
- package/apps/studio/stickers/project-status/group-43.png +0 -0
- package/apps/studio/stickers/project-status/group-44.png +0 -0
- package/apps/studio/stickers/project-status/group-45.png +0 -0
- package/apps/studio/stickers/project-status/group-46.png +0 -0
- package/apps/studio/stickers/project-status/group-47.png +0 -0
- package/apps/studio/stickers/project-status/group-48.png +0 -0
- package/apps/studio/stickers/project-status/group-49.png +0 -0
- package/apps/studio/stickers/project-status/group-50.png +0 -0
- package/apps/studio/stickers/project-status/group-51.png +0 -0
- package/apps/studio/stickers/project-status/group-52.png +0 -0
- package/apps/studio/stickers/project-status/group-53.png +0 -0
- package/apps/studio/stickers/project-status/group-54.png +0 -0
- package/apps/studio/stickers/project-status/group-55.png +0 -0
- package/apps/studio/stickers/project-status/group-56.png +0 -0
- package/apps/studio/stickers/project-status/group-57.png +0 -0
- package/apps/studio/stickers/project-status/group-58.png +0 -0
- package/apps/studio/stickers/project-status/group-59.png +0 -0
- package/apps/studio/stickers/project-status/group-60.png +0 -0
- package/apps/studio/stickers/project-status/group-61.png +0 -0
- package/apps/studio/stickers/project-status/group-62.png +0 -0
- package/apps/studio/stickers/project-status/group-63.png +0 -0
- package/apps/studio/stickers/project-status/group-64.png +0 -0
- package/apps/studio/stickers/project-status/group-65.png +0 -0
- package/apps/studio/stickers/project-status/group-66.png +0 -0
- package/apps/studio/stickers/project-status/group-67.png +0 -0
- package/apps/studio/stickers/project-status/group-68.png +0 -0
- package/apps/studio/stickers/project-status/group-69.png +0 -0
- package/apps/studio/stickers/project-status/group-70.png +0 -0
- package/apps/studio/stickers/project-status/group-71.png +0 -0
- package/apps/studio/stickers/project-status/group-72.png +0 -0
- package/apps/studio/stickers/project-status/group-73.png +0 -0
- package/apps/studio/stickers/project-status/group-74.png +0 -0
- package/apps/studio/stickers/project-status/group-75.png +0 -0
- package/apps/studio/stickers/project-status/group-76.png +0 -0
- package/apps/studio/stickers/project-status/group-77.png +0 -0
- package/apps/studio/stickers/project-status/group-78.png +0 -0
- package/apps/studio/stickers/project-status/group-79.png +0 -0
- package/apps/studio/stickers/project-status/group-80.png +0 -0
- package/apps/studio/stickers/project-status/group-81.png +0 -0
- package/apps/studio/stickers/project-status/group-82.png +0 -0
- package/apps/studio/stickers/project-status/group-83.png +0 -0
- package/apps/studio/stickers/project-status/group-84.png +0 -0
- package/apps/studio/stickers/project-status/group-85.png +0 -0
- package/apps/studio/stickers/project-status/group-86.png +0 -0
- package/apps/studio/stickers/project-status/group-87.png +0 -0
- package/apps/studio/stickers/project-status/group-88.png +0 -0
- package/apps/studio/stickers/project-status/group-89.png +0 -0
- package/apps/studio/stickers/project-status/group-90.png +0 -0
- package/apps/studio/stickers/project-status/group-91.png +0 -0
- package/apps/studio/stickers/project-status/group-92.png +0 -0
- package/apps/studio/stickers/project-status/group-93.png +0 -0
- package/apps/studio/stickers/project-status/group-94.png +0 -0
- package/apps/studio/stickers/project-status/group-96.png +0 -0
- package/apps/studio/stickers/project-status/group-97.png +0 -0
- package/apps/studio/stickers/project-status/group-98.png +0 -0
- package/apps/studio/stickers/project-status/group-99.png +0 -0
- package/apps/studio/stickers/project-status/manifest.json +328 -0
- package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
- package/apps/studio/test/acp-commands.test.ts +2 -1
- package/apps/studio/test/asset-api.test.ts +3 -3
- package/apps/studio/test/canvas-edit.test.ts +17 -6
- package/apps/studio/test/canvas-origin-gate.test.ts +19 -0
- package/apps/studio/test/dns-rebinding-guard.test.ts +27 -0
- package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
- package/apps/studio/test/edit-source-command.test.ts +23 -0
- package/apps/studio/test/element-structural-edit.test.ts +46 -0
- package/apps/studio/test/export-center.test.tsx +287 -0
- package/apps/studio/test/export-shim-multi-capture.test.ts +83 -0
- package/apps/studio/test/exporters/history.test.ts +32 -3
- package/apps/studio/test/exporters/jobs.test.ts +263 -0
- package/apps/studio/test/footage-store.test.ts +100 -0
- package/apps/studio/test/git-branches.test.ts +97 -0
- package/apps/studio/test/github-api.test.ts +56 -1
- package/apps/studio/test/input-router.test.ts +33 -0
- package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
- package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
- package/apps/studio/test/photo-edit-api.test.ts +201 -0
- package/apps/studio/test/photo-filters.test.ts +149 -0
- package/apps/studio/test/photo-pipeline.test.ts +106 -0
- package/apps/studio/test/photo-taxonomy.test.ts +96 -0
- package/apps/studio/test/read-annotations.test.ts +164 -0
- package/apps/studio/test/shell-importmap.test.ts +49 -0
- package/apps/studio/test/text-editability-stamp.test.ts +131 -0
- package/apps/studio/test/timeline-parse.test.ts +57 -0
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
- package/apps/studio/test/undo-stack.test.ts +33 -0
- package/apps/studio/test/video-asset.test.ts +5 -5
- package/apps/studio/test/video-render-bridge.test.ts +23 -1
- package/apps/studio/text-caret.ts +239 -0
- package/apps/studio/tool-palette.tsx +43 -20
- package/apps/studio/undo-stack.ts +94 -2
- package/apps/studio/use-annotation-resize.tsx +4 -4
- package/apps/studio/use-canvas-media-drop.tsx +38 -1
- package/apps/studio/use-selection-set.tsx +21 -0
- package/apps/studio/video-comp.tsx +48 -7
- package/apps/studio/whats-new.json +70 -0
- package/apps/studio/ws.ts +6 -0
- package/cli/commands/design.mjs +34 -3
- package/cli/lib/gitignore-block.mjs +2 -0
- package/package.json +10 -10
- package/plugins/design/templates/_shell.html +1 -0
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import { useEffect, useRef, useState } from 'react';
|
|
21
21
|
|
|
22
|
-
import { appRecentProjects, isNativeApp, openLocalProject, pickDirectory } from '../github.js';
|
|
22
|
+
import { appRecentProjects, isNativeApp, openGitHubUrl, openLocalProject, pickDirectory } from '../github.js';
|
|
23
23
|
|
|
24
24
|
const SHARED = new Set(['main', 'master']);
|
|
25
25
|
|
|
@@ -43,6 +43,10 @@ function Icon({ name, size = 16, className }) {
|
|
|
43
43
|
// refresh drafts — a circular arrow.
|
|
44
44
|
refresh: (<><path d="M12.5 8a4.5 4.5 0 1 1-1.3-3.2" /><polyline points="12.8 2.5 12.8 5 10.3 5" /></>),
|
|
45
45
|
spinner: <path d="M8 2.2a5.8 5.8 0 1 0 5.8 5.8" />,
|
|
46
|
+
// "get latest" — pull the shared version's new commits down.
|
|
47
|
+
download: (<><line x1="8" y1="2.5" x2="8" y2="10.5" /><polyline points="5 7.5 8 10.5 11 7.5" /><line x1="3.5" y1="13.2" x2="12.5" y2="13.2" /></>),
|
|
48
|
+
// dismiss (×) — used by the "continue on <draft>" resume nudge.
|
|
49
|
+
close: (<><line x1="4" y1="4" x2="12" y2="12" /><line x1="12" y1="4" x2="4" y2="12" /></>),
|
|
46
50
|
}[name];
|
|
47
51
|
return (
|
|
48
52
|
<svg className={className} width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
@@ -93,7 +97,7 @@ async function postJson(url, body, opts = {}) {
|
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
export default function RepoBranchSwitcher({ project, liveBranch }) {
|
|
100
|
+
export default function RepoBranchSwitcher({ project, liveBranch, remoteSync, onGetLatest }) {
|
|
97
101
|
const native = isNativeApp();
|
|
98
102
|
const [status, setStatus] = useState(null); // { repo, branch }
|
|
99
103
|
const [branches, setBranches] = useState([]);
|
|
@@ -103,6 +107,34 @@ export default function RepoBranchSwitcher({ project, liveBranch }) {
|
|
|
103
107
|
const [draftName, setDraftName] = useState('');
|
|
104
108
|
const [foldConfirm, setFoldConfirm] = useState(false);
|
|
105
109
|
const [folding, setFolding] = useState('');
|
|
110
|
+
// After a GitHub-remote fold: { url, number } (PR opened) or { message } (draft
|
|
111
|
+
// pushed but no PR). Keeps the confirm sheet open to show the result (DDR-162).
|
|
112
|
+
const [prResult, setPrResult] = useState(null);
|
|
113
|
+
const [prCopied, setPrCopied] = useState(false);
|
|
114
|
+
// Get-latest (pull) busy state for the dock nudge.
|
|
115
|
+
const [pulling, setPulling] = useState(false);
|
|
116
|
+
// "Continue on <draft>?" nudge — dismissed for this session once the user opts out.
|
|
117
|
+
const [resumeDismissed, setResumeDismissed] = useState(false);
|
|
118
|
+
|
|
119
|
+
// Open the PR in the OS browser. A WKWebView anchor can't reach the default browser,
|
|
120
|
+
// so native goes through the Tauri opener (github.com host-locked in Rust); an older
|
|
121
|
+
// desktop build without that command falls back to copy-to-clipboard. Web just opens.
|
|
122
|
+
async function reviewPr(url) {
|
|
123
|
+
if (!isNativeApp()) {
|
|
124
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
await openGitHubUrl(url);
|
|
129
|
+
} catch {
|
|
130
|
+
try {
|
|
131
|
+
await navigator.clipboard.writeText(url);
|
|
132
|
+
setPrCopied(true);
|
|
133
|
+
} catch {
|
|
134
|
+
/* clipboard blocked — the URL is shown selectable in the sheet */
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
106
138
|
const [busy, setBusy] = useState(false);
|
|
107
139
|
const [switching, setSwitching] = useState('');
|
|
108
140
|
const [err, setErr] = useState('');
|
|
@@ -248,17 +280,43 @@ export default function RepoBranchSwitcher({ project, liveBranch }) {
|
|
|
248
280
|
else { setErr(r.json?.error || 'Could not create the draft.'); setBusy(false); }
|
|
249
281
|
}
|
|
250
282
|
|
|
251
|
-
// "
|
|
283
|
+
// "Get latest" from the dock nudge — pull the shared version's new commits. Reuses
|
|
284
|
+
// the app's pull handler (onGetLatest = gitGetLatest): on success it re-probes so the
|
|
285
|
+
// nudge clears; a content conflict opens the visual resolver (handled in app.jsx).
|
|
286
|
+
async function getLatest() {
|
|
287
|
+
if (pulling || !onGetLatest) return;
|
|
288
|
+
setPulling(true);
|
|
289
|
+
setErr('');
|
|
290
|
+
const r = await onGetLatest();
|
|
291
|
+
setPulling(false);
|
|
292
|
+
if (r && !r.ok && !r.conflict)
|
|
293
|
+
setErr(r.status === 401 || r.authRequired ? 'Sign in with GitHub to get the latest.' : r.error || 'Could not get the latest.');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// "Add this draft to the Shared version" (Task 7 / DDR-162). On a GitHub remote this
|
|
297
|
+
// opens a pull request (the merge happens on GitHub, post-review) — surface the PR
|
|
298
|
+
// link and keep the sheet open. A local project merges directly (reload). A draft that
|
|
299
|
+
// pushed but couldn't open a PR (no sign-in / non-github) shows a heads-up.
|
|
252
300
|
async function foldDraft() {
|
|
253
|
-
setFoldConfirm(false);
|
|
254
301
|
setFolding(branch);
|
|
255
302
|
setErr('');
|
|
256
|
-
const r = await postJson('/_api/git/fold', { name: branch });
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
303
|
+
const r = await postJson('/_api/git/fold', { name: branch }, { timeoutMs: 45000 });
|
|
304
|
+
const j = r.json;
|
|
305
|
+
if (r.ok && j?.ok && j.prUrl) {
|
|
306
|
+
setPrResult({ url: j.prUrl, number: j.prNumber, repo: j.repo });
|
|
307
|
+
setFolding('');
|
|
308
|
+
reloadBranches(); // the draft still exists (the PR references it) — keep the list
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (r.ok && j?.ok && j.prUnavailable) {
|
|
312
|
+
setPrResult({ message: j.error || 'Draft published, but the pull request could not be opened.' });
|
|
260
313
|
setFolding('');
|
|
314
|
+
reloadBranches();
|
|
315
|
+
return;
|
|
261
316
|
}
|
|
317
|
+
if (r.ok && j?.ok) { window.location.reload(); return; } // local merge (no remote)
|
|
318
|
+
setErr(r.status === 401 ? `Sign in with GitHub to add “${branch}” to ${sharedName}.` : j?.error || 'Could not add the branch.');
|
|
319
|
+
setFolding('');
|
|
262
320
|
}
|
|
263
321
|
|
|
264
322
|
async function switchRepo(path) {
|
|
@@ -282,6 +340,16 @@ export default function RepoBranchSwitcher({ project, liveBranch }) {
|
|
|
282
340
|
|
|
283
341
|
const slug = slugify(draftName);
|
|
284
342
|
const currentDraft = onShared ? null : branches.find((b) => b.current);
|
|
343
|
+
// On the Shared version with a draft around? Offer to jump back to the most
|
|
344
|
+
// recently-worked local draft. Maude shows whatever branch HEAD is, so after a
|
|
345
|
+
// merge / Get-latest you land on the Shared version — this saves the manual switch
|
|
346
|
+
// back to the draft you were iterating on. Dismissible for the session.
|
|
347
|
+
const resumeDraft =
|
|
348
|
+
onShared && !resumeDismissed && !switching && !folding
|
|
349
|
+
? [...branches]
|
|
350
|
+
.filter((b) => !SHARED.has(b.name) && b.where !== 'remote')
|
|
351
|
+
.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0))[0]
|
|
352
|
+
: null;
|
|
285
353
|
|
|
286
354
|
// Search-miss (DDR-133, Task 4): a typed name that matches no LOCAL or already-
|
|
287
355
|
// fetched remote branch offers an explicit remote search (the bounded fetch),
|
|
@@ -316,6 +384,23 @@ export default function RepoBranchSwitcher({ project, liveBranch }) {
|
|
|
316
384
|
|
|
317
385
|
return (
|
|
318
386
|
<div className="rb-dock-wrap">
|
|
387
|
+
{remoteSync?.remoteAhead && !switching && !folding && (
|
|
388
|
+
<button type="button" data-testid="switcher-get-latest" className="btn btn--primary rb-getlatest" onClick={getLatest} disabled={pulling} title={`Get the latest ${sharedName}`}>
|
|
389
|
+
<Icon name={pulling ? 'spinner' : 'download'} size={14} className={pulling ? 'rb-spin' : ''} />
|
|
390
|
+
<span className="rb-getlatest-tx">{pulling ? 'Getting the latest…' : 'Get latest'}</span>
|
|
391
|
+
{!pulling && remoteSync.behind > 0 && <span className="rb-getlatest-sub">{remoteSync.behind} new on {sharedName}</span>}
|
|
392
|
+
</button>
|
|
393
|
+
)}
|
|
394
|
+
{resumeDraft && (
|
|
395
|
+
<div className="rb-resume" data-testid="switcher-resume">
|
|
396
|
+
<button type="button" className="rb-resume-go" onClick={() => switchDraft(resumeDraft.name)} title={`Switch to ${resumeDraft.name}`}>
|
|
397
|
+
<span className="rb-resume-icon"><Icon name="draft" size={13} /></span>
|
|
398
|
+
<span className="rb-resume-tx">Continue on <b>{resumeDraft.name}</b></span>
|
|
399
|
+
<Icon name="chevron-right" size={12} className="rb-resume-arrow" />
|
|
400
|
+
</button>
|
|
401
|
+
<button type="button" className="rb-resume-x" data-testid="switcher-resume-dismiss" aria-label="Dismiss" onClick={() => setResumeDismissed(true)}><Icon name="close" size={12} /></button>
|
|
402
|
+
</div>
|
|
403
|
+
)}
|
|
319
404
|
<div className="rb-dock" ref={rootRef}>
|
|
320
405
|
{open && (
|
|
321
406
|
<div className="rb-pop rb-pop--up" id="rb-switch-pop" role="menu" aria-label="Switch project or version" data-testid="repo-switcher-popup">
|
|
@@ -376,7 +461,7 @@ export default function RepoBranchSwitcher({ project, liveBranch }) {
|
|
|
376
461
|
</span>
|
|
377
462
|
<Icon name="check" size={14} className="rb-pop-check" />
|
|
378
463
|
</button>
|
|
379
|
-
<button type="button" data-testid="switcher-merge" className="rb-fold" role="menuitem" onClick={() => { setOpen(false); setFoldConfirm(true); }}>
|
|
464
|
+
<button type="button" data-testid="switcher-merge" className="rb-fold" role="menuitem" onClick={() => { setOpen(false); setErr(''); setPrResult(null); setPrCopied(false); setFoldConfirm(true); }}>
|
|
380
465
|
<span className="rb-fold-icon"><Icon name="arrow-up-to-line" size={15} /></span>
|
|
381
466
|
<span className="rb-fold-tx">
|
|
382
467
|
<span className="rb-fold-title">Merge this branch → {sharedName}</span>
|
|
@@ -459,19 +544,46 @@ export default function RepoBranchSwitcher({ project, liveBranch }) {
|
|
|
459
544
|
{err && !open && !newDraft && !switching && <div className="rb-switcher-err" role="alert">{err}</div>}
|
|
460
545
|
</div>
|
|
461
546
|
|
|
462
|
-
{/*
|
|
547
|
+
{/* Add-to-default confirm — the one modal in this surface. No 3-way merge UI.
|
|
548
|
+
After a GitHub-remote fold it flips to a pull-request result view (DDR-162). */}
|
|
463
549
|
{foldConfirm && (
|
|
464
|
-
<div className="rb-scrim" role="presentation" onClick={() => setFoldConfirm(false)}>
|
|
465
|
-
<div className="rb-sheet" role="dialog" aria-modal="true" aria-labelledby="rb-sheet-title" aria-describedby="rb-sheet-body" onClick={(e) => e.stopPropagation()} onKeyDown={(e) => { if (e.key === 'Escape') setFoldConfirm(false); }}>
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
550
|
+
<div className="rb-scrim" role="presentation" onClick={() => { setFoldConfirm(false); setPrResult(null); setPrCopied(false); }}>
|
|
551
|
+
<div className="rb-sheet" role="dialog" aria-modal="true" aria-labelledby="rb-sheet-title" aria-describedby="rb-sheet-body" onClick={(e) => e.stopPropagation()} onKeyDown={(e) => { if (e.key === 'Escape') { setFoldConfirm(false); setPrResult(null); setPrCopied(false); } }}>
|
|
552
|
+
{prResult ? (
|
|
553
|
+
prResult.url ? (
|
|
554
|
+
<>
|
|
555
|
+
<span className="rb-sheet-icon"><Icon name="share" size={20} /></span>
|
|
556
|
+
<h2 className="rb-sheet-title" id="rb-sheet-title">Pull request opened{prResult.number ? ` #${prResult.number}` : ''}</h2>
|
|
557
|
+
<p className="rb-sheet-body" id="rb-sheet-body">Your draft <b>“{branch}”</b> is up as a pull request into <b>{sharedName}</b>{prResult.repo ? <> of <b>{prResult.repo}</b></> : null}. Review and merge it on GitHub — that keeps your team's review rules.</p>
|
|
558
|
+
<p className="rb-sheet-meta rb-pr-url">{prResult.url}</p>
|
|
559
|
+
<div className="rb-sheet-actions">
|
|
560
|
+
<button type="button" className="btn btn--ghost" onClick={() => { setFoldConfirm(false); setPrResult(null); setPrCopied(false); }}>Close</button>
|
|
561
|
+
<button type="button" className="btn btn--primary" data-testid="switcher-pr-link" onClick={() => reviewPr(prResult.url)}><Icon name="share" size={15} /> {prCopied ? 'Link copied — paste in your browser' : 'Review on GitHub'}</button>
|
|
562
|
+
</div>
|
|
563
|
+
</>
|
|
564
|
+
) : (
|
|
565
|
+
<>
|
|
566
|
+
<span className="rb-sheet-icon"><Icon name="share" size={20} /></span>
|
|
567
|
+
<h2 className="rb-sheet-title" id="rb-sheet-title">Draft published</h2>
|
|
568
|
+
<p className="rb-sheet-body" id="rb-sheet-body">{prResult.message}</p>
|
|
569
|
+
<div className="rb-sheet-actions">
|
|
570
|
+
<button type="button" data-testid="switcher-pr-dismiss" className="btn btn--primary" onClick={() => { setFoldConfirm(false); setPrResult(null); setPrCopied(false); }}>Got it</button>
|
|
571
|
+
</div>
|
|
572
|
+
</>
|
|
573
|
+
)
|
|
574
|
+
) : (
|
|
575
|
+
<>
|
|
576
|
+
<span className="rb-sheet-icon"><Icon name="arrow-up-to-line" size={20} /></span>
|
|
577
|
+
<h2 className="rb-sheet-title" id="rb-sheet-title">Add this branch to {sharedName}</h2>
|
|
578
|
+
<p className="rb-sheet-body" id="rb-sheet-body">Propose everything in <b>“{currentDraft?.name || branch}”</b> to <b>{sharedName}</b> — the default branch everyone shares.</p>
|
|
579
|
+
<p className="rb-sheet-meta">On a shared GitHub project this opens a pull request for review; a local project merges directly. Nothing is lost — your work is kept in History.</p>
|
|
580
|
+
{err && <p className="rb-newdraft-err">{err}</p>}
|
|
581
|
+
<div className="rb-sheet-actions">
|
|
582
|
+
<button type="button" className="btn btn--ghost" onClick={() => { setFoldConfirm(false); setErr(''); }}>Cancel</button>
|
|
583
|
+
<button type="button" data-testid="switcher-merge-confirm" className="btn btn--primary" disabled={!!folding} onClick={foldDraft}><Icon name="arrow-up-to-line" size={15} /> {folding ? 'Adding…' : `Add to ${sharedName}`}</button>
|
|
584
|
+
</div>
|
|
585
|
+
</>
|
|
586
|
+
)}
|
|
475
587
|
</div>
|
|
476
588
|
</div>
|
|
477
589
|
)}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// StickerPicker.jsx — feature-whiteboard-annotation-improvements, Phase 4.
|
|
2
|
+
//
|
|
3
|
+
// Searchable, pack-grouped picker for MAUDE's bundled whiteboard sticker packs
|
|
4
|
+
// (GET /_api/stickers — main-origin only, DDR-054/DDR-088; mirrors AssetPicker's
|
|
5
|
+
// shell/scrim treatment in app.jsx). Triggered from the canvas-side tool
|
|
6
|
+
// palette's Stickers button via `postMessage({dgn:'open-sticker-picker'})`;
|
|
7
|
+
// `onPick(sticker)` hands the caller `{ file, keywords, url }` — app.jsx
|
|
8
|
+
// re-uploads the picked PNG's bytes through the existing /_api/asset lane
|
|
9
|
+
// (content-addressed, canvas-origin-allowlisted) and relays the resulting
|
|
10
|
+
// project path down to the canvas, exactly like the replace-annotation-media
|
|
11
|
+
// flow already does for AssetPicker.
|
|
12
|
+
|
|
13
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
14
|
+
|
|
15
|
+
function StIcon({ name, size = 15 }) {
|
|
16
|
+
if (name === 'x') {
|
|
17
|
+
return (
|
|
18
|
+
<svg width={size} height={size} viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
19
|
+
<path
|
|
20
|
+
d="M4 4l8 8M12 4l-8 8"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
strokeWidth="1.6"
|
|
23
|
+
strokeLinecap="round"
|
|
24
|
+
/>
|
|
25
|
+
</svg>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function StickerPicker({ onPick, onClose }) {
|
|
32
|
+
const [packs, setPacks] = useState(null); // null = loading
|
|
33
|
+
const [query, setQuery] = useState('');
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
let alive = true;
|
|
37
|
+
fetch('/_api/stickers')
|
|
38
|
+
.then((r) => r.json())
|
|
39
|
+
.then((j) => alive && setPacks(j.ok ? j.packs : []))
|
|
40
|
+
.catch(() => alive && setPacks([]));
|
|
41
|
+
return () => {
|
|
42
|
+
alive = false;
|
|
43
|
+
};
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const onKey = (e) => {
|
|
48
|
+
if (e.key === 'Escape') {
|
|
49
|
+
e.stopPropagation();
|
|
50
|
+
onClose();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
window.addEventListener('keydown', onKey, true);
|
|
54
|
+
return () => window.removeEventListener('keydown', onKey, true);
|
|
55
|
+
}, [onClose]);
|
|
56
|
+
|
|
57
|
+
const filteredPacks = useMemo(() => {
|
|
58
|
+
if (!packs) return [];
|
|
59
|
+
const q = query.trim().toLowerCase();
|
|
60
|
+
if (!q) return packs;
|
|
61
|
+
return packs
|
|
62
|
+
.map((pack) => ({
|
|
63
|
+
...pack,
|
|
64
|
+
stickers: pack.stickers.filter(
|
|
65
|
+
(s) =>
|
|
66
|
+
s.file.toLowerCase().includes(q) ||
|
|
67
|
+
(s.keywords || []).some((k) => k.toLowerCase().includes(q))
|
|
68
|
+
),
|
|
69
|
+
}))
|
|
70
|
+
.filter((pack) => pack.stickers.length > 0);
|
|
71
|
+
}, [packs, query]);
|
|
72
|
+
|
|
73
|
+
const totalShown = filteredPacks.reduce((n, p) => n + p.stickers.length, 0);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
className="st-scrim"
|
|
78
|
+
role="presentation"
|
|
79
|
+
onMouseDown={(e) => {
|
|
80
|
+
if (e.target === e.currentTarget) onClose();
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
<div
|
|
84
|
+
className="st-dialog st-sticker-picker"
|
|
85
|
+
role="dialog"
|
|
86
|
+
aria-modal="true"
|
|
87
|
+
aria-label="Stickers"
|
|
88
|
+
>
|
|
89
|
+
<div className="st-dialog-hd">
|
|
90
|
+
<span className="st-dialog-title">Stickers</span>
|
|
91
|
+
<button type="button" className="st-iconbtn" aria-label="Close" onClick={onClose}>
|
|
92
|
+
<StIcon name="x" size={15} />
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
<div className="st-dialog-bd">
|
|
96
|
+
<input
|
|
97
|
+
className="input"
|
|
98
|
+
type="text"
|
|
99
|
+
value={query}
|
|
100
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
101
|
+
placeholder="Search stickers…"
|
|
102
|
+
aria-label="Search stickers"
|
|
103
|
+
autoFocus
|
|
104
|
+
/>
|
|
105
|
+
<div className="st-sp-body">
|
|
106
|
+
{packs == null ? (
|
|
107
|
+
<div className="st-sp-empty">Loading…</div>
|
|
108
|
+
) : totalShown === 0 ? (
|
|
109
|
+
<div className="st-sp-empty">
|
|
110
|
+
{query.trim() ? `No stickers match "${query.trim()}"` : 'No stickers bundled'}
|
|
111
|
+
</div>
|
|
112
|
+
) : (
|
|
113
|
+
filteredPacks.map((pack) => (
|
|
114
|
+
<div key={pack.slug}>
|
|
115
|
+
<div className="st-sp-pack-hd">
|
|
116
|
+
<span className="st-sp-pack-name">{pack.name}</span>
|
|
117
|
+
{pack.author && <span className="st-sp-pack-author">by {pack.author}</span>}
|
|
118
|
+
</div>
|
|
119
|
+
<div className="st-sp-grid">
|
|
120
|
+
{pack.stickers.map((s) => (
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
key={s.file}
|
|
124
|
+
className="st-sp-cell"
|
|
125
|
+
title={s.keywords?.[0] || s.file}
|
|
126
|
+
onClick={() => onPick(s)}
|
|
127
|
+
>
|
|
128
|
+
<img className="st-sp-thumb" src={s.url} alt={s.keywords?.[0] || s.file} loading="lazy" />
|
|
129
|
+
</button>
|
|
130
|
+
))}
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
))
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
{packs && packs.length > 0 && (
|
|
138
|
+
<div className="st-sp-ft">
|
|
139
|
+
{packs.map((pack) => (
|
|
140
|
+
<span key={pack.slug}>
|
|
141
|
+
{pack.name}
|
|
142
|
+
{pack.author ? ` — ${pack.author}` : ''}
|
|
143
|
+
{pack.attributionUrl ? (
|
|
144
|
+
<>
|
|
145
|
+
{' '}
|
|
146
|
+
(
|
|
147
|
+
<a href={pack.attributionUrl} target="_blank" rel="noreferrer">
|
|
148
|
+
source
|
|
149
|
+
</a>
|
|
150
|
+
)
|
|
151
|
+
</>
|
|
152
|
+
) : null}
|
|
153
|
+
</span>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -112,7 +112,7 @@ export default function TimelinePanel({
|
|
|
112
112
|
total = 0,
|
|
113
113
|
frame = 0,
|
|
114
114
|
playing = false,
|
|
115
|
-
loop =
|
|
115
|
+
loop = false,
|
|
116
116
|
onSeek,
|
|
117
117
|
onPlay,
|
|
118
118
|
onPause,
|
|
@@ -145,6 +145,17 @@ export default function TimelinePanel({
|
|
|
145
145
|
const clamped = clamp(Math.round(frame), 0, totalFrames - 1);
|
|
146
146
|
const trackRef = useRef(null);
|
|
147
147
|
const draggingRef = useRef(false);
|
|
148
|
+
const rootRef = useRef(null);
|
|
149
|
+
// Focus the dock the moment it opens. The transport shortcuts (Space play,
|
|
150
|
+
// arrow frame-step) live on the SHELL window, so they only fire when focus is
|
|
151
|
+
// in the shell — but opening the Timeline via ⌘⇧T while focus sat in the
|
|
152
|
+
// canvas iframe (the common case after any text/element interaction) used to
|
|
153
|
+
// leave focus stuck in the iframe, so Space/arrows silently did nothing.
|
|
154
|
+
// Pulling focus here on mount (mount === open, the panel is conditionally
|
|
155
|
+
// rendered) hands the transport keys a shell focus target immediately.
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
rootRef.current?.focus();
|
|
158
|
+
}, []);
|
|
148
159
|
// Drag-to-retime a sequence's duration: { index, startX, startDur, rowW, curDur }.
|
|
149
160
|
const [retimeDrag, setRetimeDrag] = useState(null);
|
|
150
161
|
// Drag the block body to move a clip's `from`: { index, startX, startFrom, rowW, curFrom }.
|
|
@@ -337,8 +348,10 @@ export default function TimelinePanel({
|
|
|
337
348
|
|
|
338
349
|
return (
|
|
339
350
|
<aside
|
|
351
|
+
ref={rootRef}
|
|
352
|
+
tabIndex={-1}
|
|
340
353
|
className={`tl-panel${resizing ? ' is-resizing' : ''}${dropActive ? ' is-drop' : ''}`}
|
|
341
|
-
style={height ? { height } :
|
|
354
|
+
style={{ ...(height ? { height } : {}), outline: 'none' }}
|
|
342
355
|
onDragOver={(e) => {
|
|
343
356
|
if (!onDropMedia || !hasFiles(e.dataTransfer)) return;
|
|
344
357
|
e.preventDefault();
|