|
55 | 55 | // Track mouse position for paste operations |
56 | 56 | let mousePosition = $state({ x: 0, y: 0 }); |
57 | 57 |
|
| 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 | +
|
58 | 78 | // Panel visibility state |
59 | 79 | let showProperties = $state(false); |
60 | 80 | let showNodeLibrary = $state(false); |
|
514 | 534 | case 's': |
515 | 535 | event.preventDefault(); |
516 | 536 | if (event.shiftKey) { |
517 | | - saveAsFile(); |
| 537 | + handleSaveAs(); |
518 | 538 | } else { |
519 | | - saveFile(); |
| 539 | + handleSave(); |
520 | 540 | } |
521 | 541 | return; |
522 | 542 | case 'o': |
|
991 | 1011 | <button class="toolbar-btn" onclick={handleOpen} use:tooltip={{ text: "Open/Import", shortcut: "Ctrl+O" }} aria-label="Open/Import"> |
992 | 1012 | <Icon name="download" size={16} /> |
993 | 1013 | </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> |
996 | 1049 | </button> |
997 | 1050 | <button class="toolbar-btn" onclick={() => exportDialogOpen = true} use:tooltip={{ text: "Python Code", shortcut: "Ctrl+E" }} aria-label="View Python Code"> |
998 | 1051 | <Icon name="braces" size={16} /> |
|
1375 | 1428 | background: color-mix(in srgb, var(--error) 15%, var(--surface-raised)); |
1376 | 1429 | } |
1377 | 1430 |
|
| 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 | +
|
1378 | 1445 | /* Logo overlay */ |
1379 | 1446 | .logo-overlay { |
1380 | 1447 | position: fixed; |
|
0 commit comments