Skip to content

Commit 4b7a0c1

Browse files
authored
Merge pull request #243 from pathsim/feature/save-visual-feedback
save visual feedback
2 parents cc8d34f + eeb5878 commit 4b7a0c1

2 files changed

Lines changed: 78 additions & 4 deletions

File tree

src/lib/components/icons/Icon.svelte

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,13 @@
132132
<polyline points="17 8 12 3 7 8"/>
133133
<line x1="12" y1="3" x2="12" y2="15"/>
134134
</svg>
135+
{:else if name === 'upload-plus'}
136+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
137+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
138+
<polyline points="17 8 12 3 7 8"/>
139+
<line x1="12" y1="3" x2="12" y2="15"/>
140+
<circle cx="22" cy="3" r="1.5" fill="currentColor" stroke="none"/>
141+
</svg>
135142
{:else if name === 'play'}
136143
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
137144
<polygon points="5 3 19 12 5 21 5 3"/>

src/routes/+page.svelte

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,26 @@
5555
// Track mouse position for paste operations
5656
let mousePosition = $state({ x: 0, y: 0 });
5757
58+
// Save feedback animation state
59+
let saveFlash = $state<'save' | 'save-as' | null>(null);
60+
let saveFlashTimeout: ReturnType<typeof setTimeout> | undefined;
61+
62+
function flashSaveButton(which: 'save' | 'save-as') {
63+
clearTimeout(saveFlashTimeout);
64+
saveFlash = which;
65+
saveFlashTimeout = setTimeout(() => { saveFlash = null; }, 1500);
66+
}
67+
68+
async function handleSave() {
69+
const success = await saveFile();
70+
if (success) flashSaveButton('save');
71+
}
72+
73+
async function handleSaveAs() {
74+
const success = await saveAsFile();
75+
if (success) flashSaveButton('save-as');
76+
}
77+
5878
// Panel visibility state
5979
let showProperties = $state(false);
6080
let showNodeLibrary = $state(false);
@@ -514,9 +534,9 @@
514534
case 's':
515535
event.preventDefault();
516536
if (event.shiftKey) {
517-
saveAsFile();
537+
handleSaveAs();
518538
} else {
519-
saveFile();
539+
handleSave();
520540
}
521541
return;
522542
case 'o':
@@ -991,8 +1011,41 @@
9911011
<button class="toolbar-btn" onclick={handleOpen} use:tooltip={{ text: "Open/Import", shortcut: "Ctrl+O" }} aria-label="Open/Import">
9921012
<Icon name="download" size={16} />
9931013
</button>
994-
<button class="toolbar-btn" onclick={() => saveFile()} use:tooltip={{ text: $currentFileName ? `Save '${$currentFileName}'` : "Save", shortcut: "Ctrl+S" }} aria-label="Save">
995-
<Icon name="upload" size={16} />
1014+
<button
1015+
class="toolbar-btn"
1016+
onclick={() => handleSave()}
1017+
use:tooltip={{ text: $currentFileName ? `Save '${$currentFileName}'` : "Save", shortcut: "Ctrl+S" }}
1018+
aria-label="Save"
1019+
>
1020+
<span class="icon-crossfade">
1021+
{#if saveFlash === 'save'}
1022+
<span class="icon-crossfade-item" in:fade={{ duration: 200 }} out:fade={{ duration: 200 }}>
1023+
<Icon name="check" size={16} />
1024+
</span>
1025+
{:else}
1026+
<span class="icon-crossfade-item" in:fade={{ duration: 200 }} out:fade={{ duration: 200 }}>
1027+
<Icon name="upload" size={16} />
1028+
</span>
1029+
{/if}
1030+
</span>
1031+
</button>
1032+
<button
1033+
class="toolbar-btn"
1034+
onclick={() => handleSaveAs()}
1035+
use:tooltip={{ text: "Save As", shortcut: "Ctrl+Shift+S" }}
1036+
aria-label="Save As"
1037+
>
1038+
<span class="icon-crossfade">
1039+
{#if saveFlash === 'save-as'}
1040+
<span class="icon-crossfade-item" in:fade={{ duration: 200 }} out:fade={{ duration: 200 }}>
1041+
<Icon name="check" size={16} />
1042+
</span>
1043+
{:else}
1044+
<span class="icon-crossfade-item" in:fade={{ duration: 200 }} out:fade={{ duration: 200 }}>
1045+
<Icon name="upload-plus" size={16} />
1046+
</span>
1047+
{/if}
1048+
</span>
9961049
</button>
9971050
<button class="toolbar-btn" onclick={() => exportDialogOpen = true} use:tooltip={{ text: "Python Code", shortcut: "Ctrl+E" }} aria-label="View Python Code">
9981051
<Icon name="braces" size={16} />
@@ -1375,6 +1428,20 @@
13751428
background: color-mix(in srgb, var(--error) 15%, var(--surface-raised));
13761429
}
13771430
1431+
.icon-crossfade {
1432+
position: relative;
1433+
display: flex;
1434+
align-items: center;
1435+
justify-content: center;
1436+
width: 16px;
1437+
height: 16px;
1438+
}
1439+
1440+
.icon-crossfade-item {
1441+
position: absolute;
1442+
display: flex;
1443+
}
1444+
13781445
/* Logo overlay */
13791446
.logo-overlay {
13801447
position: fixed;

0 commit comments

Comments
 (0)