v7.20.1 — Directory Get save-on-demand: explicit Save button (left of Open in editor) files fetched scripts into root/user created/OVE-Directory; no more auto-save on Get
This commit is contained in:
parent
65d4f91be6
commit
1afece5a9b
4 changed files with 155 additions and 29 deletions
|
|
@ -5,13 +5,14 @@
|
||||||
// type, its date, and its star rating. Visitors rate entries 1–5 (one vote
|
// type, its date, and its star rating. Visitors rate entries 1–5 (one vote
|
||||||
// per script per visitor, enforced upstream), see the live average to the
|
// per script per visitor, enforced upstream), see the live average to the
|
||||||
// right of the stars, sort the whole list by Name / Type / Date / Rating,
|
// right of the stars, sort the whole list by Name / Type / Date / Rating,
|
||||||
// and "Get" any entry: the hash string is fetched, decoded back into the
|
// and "Get" any entry: the hash string is fetched and decoded back into the
|
||||||
// exact script, and filed in this browser's preset tree under
|
// exact script. v7.20: each entry card is compact -- the Get button sits at
|
||||||
// "root/user created/OVE-Directory" -- from where it opens in the editor.
|
// the card's top right with the date immediately to its left, so the rating
|
||||||
// v7.20: each entry card is compact -- the Get button sits at the card's
|
// row never wraps the button onto its own line (the card dropped from
|
||||||
// top right with the date immediately to its left, so the rating row never
|
// ~126 px to ~94 px at the sidebar width). v7.20.1: Get no longer files the
|
||||||
// wraps the button onto its own line (the card dropped from ~126 px to
|
// script into the preset tree automatically -- the panel carries an
|
||||||
// ~94 px at the sidebar width).
|
// explicit Save button (left of Open in editor) that places the preset
|
||||||
|
// under "root/user created/OVE-Directory" on demand, deduped by content.
|
||||||
|
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
|
@ -24,6 +25,7 @@ import {
|
||||||
FileCode2,
|
FileCode2,
|
||||||
Loader2,
|
Loader2,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
|
Save,
|
||||||
Star,
|
Star,
|
||||||
TriangleAlert,
|
TriangleAlert,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
@ -78,8 +80,10 @@ interface GotPanel {
|
||||||
hash: string
|
hash: string
|
||||||
script: string
|
script: string
|
||||||
compiles: boolean
|
compiles: boolean
|
||||||
savedAs: string
|
/** Name the preset was saved under; null until the Save button is hit. */
|
||||||
alreadyHad: boolean
|
savedAs: string | null
|
||||||
|
/** Whether a content-identical preset was already in the folder; null unsaved. */
|
||||||
|
alreadyHad: boolean | null
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── one directory row ─────────────────────────────────────────────────────
|
// ── one directory row ─────────────────────────────────────────────────────
|
||||||
|
|
@ -148,6 +152,7 @@ function DirRow({
|
||||||
got,
|
got,
|
||||||
onVote,
|
onVote,
|
||||||
onGet,
|
onGet,
|
||||||
|
onSave,
|
||||||
}: {
|
}: {
|
||||||
entry: DirMeta
|
entry: DirMeta
|
||||||
myVote: number
|
myVote: number
|
||||||
|
|
@ -156,6 +161,7 @@ function DirRow({
|
||||||
got: GotPanel | null
|
got: GotPanel | null
|
||||||
onVote: (stars: number) => void
|
onVote: (stars: number) => void
|
||||||
onGet: () => void
|
onGet: () => void
|
||||||
|
onSave: () => void
|
||||||
}) {
|
}) {
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
|
|
||||||
|
|
@ -205,7 +211,7 @@ function DirRow({
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={onGet}
|
onClick={onGet}
|
||||||
className="h-7 gap-1 border-zinc-700 px-2.5 text-[11px]"
|
className="h-7 gap-1 border-zinc-700 px-2.5 text-[11px]"
|
||||||
title="Fetch this script: it's saved to this browser's presets and can open in the editor"
|
title="Fetch this script -- then Save it to your presets or open it in the editor"
|
||||||
>
|
>
|
||||||
{got ? <Check className="size-3.5 text-emerald-400" aria-hidden /> : <Download className="size-3.5" aria-hidden />}
|
{got ? <Check className="size-3.5 text-emerald-400" aria-hidden /> : <Download className="size-3.5" aria-hidden />}
|
||||||
{got ? 'Get again' : 'Get'}
|
{got ? 'Get again' : 'Get'}
|
||||||
|
|
@ -213,6 +219,8 @@ function DirRow({
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{/* v7.20.1 -- the whole card body below stays identical; only the
|
||||||
|
got-panel footer grows the Save button. */}
|
||||||
|
|
||||||
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5">
|
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5">
|
||||||
<StarRow entry={entry} myVote={myVote} busy={voting} onVote={onVote} />
|
<StarRow entry={entry} myVote={myVote} busy={voting} onVote={onVote} />
|
||||||
|
|
@ -225,12 +233,19 @@ function DirRow({
|
||||||
{got && (
|
{got && (
|
||||||
<div className="mt-3 space-y-2 border-t border-zinc-800 pt-3">
|
<div className="mt-3 space-y-2 border-t border-zinc-800 pt-3">
|
||||||
<p className="text-[11px] leading-relaxed text-zinc-400">
|
<p className="text-[11px] leading-relaxed text-zinc-400">
|
||||||
{got.alreadyHad ? (
|
{got.savedAs ? (
|
||||||
<>You already had this exact script -- no duplicate was made.</>
|
got.alreadyHad ? (
|
||||||
|
<>You already had this exact script -- no duplicate was made.</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Saved to your presets as <span className="text-emerald-300">{got.savedAs}</span> in{' '}
|
||||||
|
<span className="font-mono">{DOWNLOAD_FOLDER}</span>.
|
||||||
|
</>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
Saved to your presets as <span className="text-emerald-300">{got.savedAs}</span> in{' '}
|
Fetched -- not saved yet. Hit <span className="text-emerald-300">Save</span> to keep it in{' '}
|
||||||
<span className="font-mono">{DOWNLOAD_FOLDER}</span>.
|
<span className="font-mono">{DOWNLOAD_FOLDER}</span>, or open it in the editor.
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -262,14 +277,40 @@ function DirRow({
|
||||||
<FileCode2 className="size-3.5 shrink-0" aria-hidden />
|
<FileCode2 className="size-3.5 shrink-0" aria-hidden />
|
||||||
{got.compiles ? 'Decodes and compiles OK' : "Decodes, but doesn't compile (yet)"}
|
{got.compiles ? 'Decodes and compiles OK' : "Decodes, but doesn't compile (yet)"}
|
||||||
</p>
|
</p>
|
||||||
<Button
|
{/* v7.20.1 -- Save (left) places the preset into the user's
|
||||||
size="sm"
|
"user created/OVE-Directory" folder; Open in editor (right)
|
||||||
onClick={openInEditor}
|
just loads the script text into the editor. After saving, the
|
||||||
className="h-8 gap-1.5 bg-emerald-400 px-3 text-xs font-semibold text-emerald-950 hover:bg-emerald-300"
|
button turns into a static Saved badge (the content-twin check
|
||||||
title="Load this script into the editor"
|
makes repeat saves a no-op anyway). */}
|
||||||
>
|
<span className="flex shrink-0 items-center gap-2">
|
||||||
<FileCode2 className="size-3.5" aria-hidden /> Open in editor
|
{got.savedAs ? (
|
||||||
</Button>
|
<span
|
||||||
|
role="status"
|
||||||
|
title={`This preset is in ${DOWNLOAD_FOLDER}`}
|
||||||
|
className="flex h-8 items-center gap-1.5 rounded-md border border-emerald-400/30 bg-emerald-400/10 px-3 text-xs font-semibold text-emerald-300"
|
||||||
|
>
|
||||||
|
<Check className="size-3.5" aria-hidden /> Saved
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={onSave}
|
||||||
|
className="h-8 gap-1.5 border-emerald-400/40 bg-emerald-400/10 px-3 text-xs font-semibold text-emerald-300 hover:bg-emerald-400/20 hover:text-emerald-200"
|
||||||
|
title={`Save this preset into ${DOWNLOAD_FOLDER}`}
|
||||||
|
>
|
||||||
|
<Save className="size-3.5" aria-hidden /> Save
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={openInEditor}
|
||||||
|
className="h-8 gap-1.5 bg-emerald-400 px-3 text-xs font-semibold text-emerald-950 hover:bg-emerald-300"
|
||||||
|
title="Load this script into the editor"
|
||||||
|
>
|
||||||
|
<FileCode2 className="size-3.5" aria-hidden /> Open in editor
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -348,6 +389,8 @@ export function DirectoryTab() {
|
||||||
[voting, toast],
|
[voting, toast],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// v7.20.1 -- Get only fetches + decodes + previews. The Save button (per
|
||||||
|
// row, in the got panel) does the filing into the preset tree on demand.
|
||||||
const get = useCallback(
|
const get = useCallback(
|
||||||
async (entry: DirMeta) => {
|
async (entry: DirMeta) => {
|
||||||
if (getting !== null) return
|
if (getting !== null) return
|
||||||
|
|
@ -357,21 +400,19 @@ export function DirectoryTab() {
|
||||||
const decoded = decodeScriptHash(full.hash)
|
const decoded = decodeScriptHash(full.hash)
|
||||||
if (!decoded.ok) throw new Error('The stored share string did not decode -- it may be corrupted.')
|
if (!decoded.ok) throw new Error('The stored share string did not decode -- it may be corrupted.')
|
||||||
const v = validateScript(decoded.script)
|
const v = validateScript(decoded.script)
|
||||||
const save = saveDownloadedPreset(decoded.script, entry.name)
|
|
||||||
if (!save.ok) throw new Error(save.error ?? 'Could not save to this browser.')
|
|
||||||
setGot((prev) => ({
|
setGot((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[entry.id]: {
|
[entry.id]: {
|
||||||
hash: full.hash,
|
hash: full.hash,
|
||||||
script: decoded.script,
|
script: decoded.script,
|
||||||
compiles: v.ok,
|
compiles: v.ok,
|
||||||
savedAs: save.preset?.name ?? entry.name,
|
savedAs: null,
|
||||||
alreadyHad: Boolean(save.alreadyHad),
|
alreadyHad: null,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
toast({
|
toast({
|
||||||
title: save.alreadyHad ? 'Already in your presets' : 'Saved to your presets',
|
title: 'Fetched from the directory',
|
||||||
description: `"${save.preset?.name ?? entry.name}" -- open it in the editor from the panel below, or hit Apply after switching to the Script tab.`,
|
description: `"${entry.name}" -- Save it to your presets or open it in the editor.`,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast({
|
toast({
|
||||||
|
|
@ -386,6 +427,36 @@ export function DirectoryTab() {
|
||||||
[getting, toast],
|
[getting, toast],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// v7.20.1 -- the explicit Save: files the fetched script into this
|
||||||
|
// browser's preset tree under root/user created/OVE-Directory, deduped
|
||||||
|
// by exact content (a twin already in the folder is reported, not copied).
|
||||||
|
// Synchronous (localStorage), so no busy state to track.
|
||||||
|
const save = useCallback(
|
||||||
|
(entry: DirMeta) => {
|
||||||
|
const panel = got[entry.id]
|
||||||
|
if (!panel || panel.savedAs) return
|
||||||
|
const res = saveDownloadedPreset(panel.script, entry.name)
|
||||||
|
if (!res.ok) {
|
||||||
|
toast({
|
||||||
|
title: 'Save failed',
|
||||||
|
description: res.error ?? 'Could not save to this browser.',
|
||||||
|
variant: 'destructive',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const savedAs = res.preset?.name ?? entry.name
|
||||||
|
setGot((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[entry.id]: { ...panel, savedAs, alreadyHad: Boolean(res.alreadyHad) },
|
||||||
|
}))
|
||||||
|
toast({
|
||||||
|
title: res.alreadyHad ? 'Already in your presets' : 'Saved to your presets',
|
||||||
|
description: `"${savedAs}" in ${DOWNLOAD_FOLDER}.`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[got, toast],
|
||||||
|
)
|
||||||
|
|
||||||
const online = status?.status === 'ok'
|
const online = status?.status === 'ok'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -492,6 +563,7 @@ export function DirectoryTab() {
|
||||||
got={got[entry.id] ?? null}
|
got={got[entry.id] ?? null}
|
||||||
onVote={(stars) => void vote(entry, stars)}
|
onVote={(stars) => void vote(entry, stars)}
|
||||||
onGet={() => void get(entry)}
|
onGet={() => void get(entry)}
|
||||||
|
onSave={() => save(entry)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<p className="flex items-center justify-center gap-1.5 pt-1 text-center font-mono text-[11px] text-zinc-600">
|
<p className="flex items-center justify-center gap-1.5 pt-1 text-center font-mono text-[11px] text-zinc-600">
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,16 @@ export interface ReleaseNote {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RELEASE_NOTES: ReleaseNote[] = [
|
export const RELEASE_NOTES: ReleaseNote[] = [
|
||||||
|
{
|
||||||
|
version: '7.20.1',
|
||||||
|
name: 'Directory Gets save on demand',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
kind: 'improved',
|
||||||
|
text: 'Getting a script from the Directory no longer drops it into the preset tree on its own. The Get panel now has a Save button right beside Open in editor -- I fetch first, look over the share string and the compile status, and only when I hit Save does the preset land in root/user created/OVE-Directory. Saving twice never makes a duplicate, and the button settles into a quiet Saved badge once it\'s filed.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: '7.20.0',
|
version: '7.20.0',
|
||||||
name: 'Paste-to-decode + a compact Directory',
|
name: 'Paste-to-decode + a compact Directory',
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,22 @@
|
||||||
// warns in the console when the two drift apart.
|
// warns in the console when the two drift apart.
|
||||||
//
|
//
|
||||||
// History:
|
// History:
|
||||||
|
// 7.20.1 — Directory Get save-on-demand (user request): clicking Get on a
|
||||||
|
// directory entry no longer files the script into the preset tree
|
||||||
|
// automatically -- the got panel now carries an explicit Save
|
||||||
|
// button, placed left of "Open in editor" in the panel footer,
|
||||||
|
// that places the preset under root/user created/OVE-Directory on
|
||||||
|
// demand (saveDownloadedPreset, deduped by exact content so repeat
|
||||||
|
// saves never make a duplicate; an identical twin already in the
|
||||||
|
// folder is reported as "Already in your presets", no copy).
|
||||||
|
// After saving, the button becomes a static "Saved" badge. Get
|
||||||
|
// itself now only fetches + decodes + previews (toast "Fetched
|
||||||
|
// from the directory"); Open in editor stays exactly as it was
|
||||||
|
// (loads the script text into the editor, saves nothing). GotPanel's
|
||||||
|
// savedAs/alreadyHad became string|null / boolean|null (null until
|
||||||
|
// the Save button is hit). Version bump with matching release note;
|
||||||
|
// EMBEDDED_SEED_VERSION untouched (7.19.0 -- the library did not
|
||||||
|
// change).
|
||||||
// 7.20 — Paste-to-decode + compact Directory cards (user request, three
|
// 7.20 — Paste-to-decode + compact Directory cards (user request, three
|
||||||
// items). (1) Script editor: pasting a share string straight into
|
// items). (1) Script editor: pasting a share string straight into
|
||||||
// the editor textarea now decodes it in place -- onPaste runs
|
// the editor textarea now decodes it in place -- onPaste runs
|
||||||
|
|
@ -674,4 +690,4 @@
|
||||||
|
|
||||||
export const APP_NAME = 'Open Visualization Engine'
|
export const APP_NAME = 'Open Visualization Engine'
|
||||||
export const APP_SHORT = 'OVE'
|
export const APP_SHORT = 'OVE'
|
||||||
export const APP_VERSION = '7.20.0'
|
export const APP_VERSION = '7.20.1'
|
||||||
|
|
|
||||||
28
worklog.md
28
worklog.md
|
|
@ -2849,3 +2849,31 @@ The persistence chain is now end-to-end: every future sandbox reprovision restor
|
||||||
|
|
||||||
Stage Summary:
|
Stage Summary:
|
||||||
- PUSH DELIVERED: origin/zai-dev now at 631642d+ (v7.18.0 + v7.19.0 x2 + v7.20.0 + worklogs), authenticated with the operator-supplied scoped token, which was used once as a throwaway HTTP header and never stored anywhere. origin/main remains protected at bbbee3c -- merging zai-dev into main (PR or local merge + push from an authorized lane) is the one remaining operator-side step if they want main current.
|
- PUSH DELIVERED: origin/zai-dev now at 631642d+ (v7.18.0 + v7.19.0 x2 + v7.20.0 + worklogs), authenticated with the operator-supplied scoped token, which was used once as a throwaway HTTP header and never stored anywhere. origin/main remains protected at bbbee3c -- merging zai-dev into main (PR or local merge + push from an authorized lane) is the one remaining operator-side step if they want main current.
|
||||||
|
|
||||||
|
---
|
||||||
|
Task ID: 58
|
||||||
|
Agent: main (Z.ai Code)
|
||||||
|
Task: v7.20.1 (user request): "On the Directory tab, when the user clicks the 'Get' button, there needs to be a 'Save' button to the left of the 'Open in Editor' button that will place the preset into the users 'user created' 'OVE-Directory' path. With this, let's bump the version to 7.20.1."
|
||||||
|
|
||||||
|
Behavior change (directory-tab.tsx):
|
||||||
|
- Get is now fetch + decode + preview ONLY: the got panel opens with "Fetched -- not saved yet. Hit Save to keep it in root/user created/OVE-Directory, or open it in the editor." and the toast "Fetched from the directory". saveDownloadedPreset moved out of the get callback entirely.
|
||||||
|
- New explicit Save button in the panel footer, LEFT of "Open in editor" (outline + emerald accent + lucide Save icon, title "Save this preset into root/user created/OVE-Directory"). It calls a new save callback: saveDownloadedPreset(script, entry.name) -- the existing v7.11 helper that files the preset under root/user created/OVE-Directory with unique-name numbering, deduped by exact content. Toast "Saved to your presets" / "Already in your presets"; the panel intro flips to the saved wording; the button becomes a static "Saved ✓" badge (role=status) so it cannot double-fire. "Open in editor" is unchanged: loads the script text into the editor, saves nothing.
|
||||||
|
- GotPanel.savedAs/alreadyHad became string|null / boolean|null (null until Save is hit). Get-again resets them to null (Save button returns; the content-twin dedup keeps re-saves no-ops).
|
||||||
|
- Mid-task bug caught by E2E: first edit round added onSave to the props TYPE but not the destructuring -> ReferenceError "onSave is not defined" crashed the DirectoryTab render the moment a got panel appeared (articles vanished; a stale-Fast-Refresh red herring initially pointed elsewhere). One-line fix; lint clean after.
|
||||||
|
|
||||||
|
Version: version.ts APP_VERSION 7.20.0 -> 7.20.1 + full history entry; release-notes.ts 7.20.1 "Directory Gets save on demand" (improved, first person, plain double minus). EMBEDDED_SEED_VERSION untouched at 7.19.0 (library unchanged, no re-seed).
|
||||||
|
|
||||||
|
BROWSER E2E (agent-browser, ALL PASS after the fix):
|
||||||
|
- Baseline: OVE-Directory folder empty in localStorage; title v7.20.1.
|
||||||
|
- Get on Aqua-Burst: panel intro "Fetched -- not saved yet...", buttons in order [Copy, Save, Open in editor] (Save LEFT of Open in editor verified by index), localStorage still empty -- no auto-save.
|
||||||
|
- Save: localStorage gains "Aqua-Burst" under root/user created/OVE-Directory; panel intro "Saved to your presets as Aqua-Burst in root/user created/OVE-Directory."; Save button replaced by the "Saved" badge; Open in editor still present.
|
||||||
|
- Get again: panel resets to unsaved with the Save button back; Save again -> "You already had this exact script -- no duplicate was made.", folder still exactly 1 preset (content-twin dedup held).
|
||||||
|
- Toasts confirmed live: "Fetched from the directory | 'Aqua-Burst' -- Save it to your presets or open it in the editor." and "Already in your presets | 'Aqua-Burst' in root/user created/OVE-Directory."
|
||||||
|
- Open in editor: switches to the Script tab with the decoded script loaded (2,663 chars, name + [frame] sections).
|
||||||
|
- Presets tab: root -> user created -> OVE-Directory -> Aqua-Burst ("Your preset, stored in this browser") -- the exact path requested.
|
||||||
|
- What's New: 7.20.1 "Directory Gets save on demand" CURRENT, 7.20.0 below it. No console errors; footer flush (docH 577 = innerH 577, footer bottom = docH), no horizontal scroll; bun run lint clean; dev.log all 200s (list + status + two script fetches).
|
||||||
|
|
||||||
|
Delivery: committed and pushed to origin/zai-dev with the operator's token from Task 57 (same one-off http.extraheader pattern, never stored).
|
||||||
|
|
||||||
|
Stage Summary:
|
||||||
|
- v7.20.1 shipped: Directory Gets are save-on-demand. The Get panel's new Save button (left of Open in editor) places the fetched script into root/user created/OVE-Directory on click, deduped by content, with a Saved badge confirming it; Get itself no longer auto-files anything. Verified end-to-end in the browser and pushed to the remote.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue