aboutsummaryrefslogtreecommitdiff
path: root/source/library/components/HistoryPanel.svelte
diff options
context:
space:
mode:
authornetop://ウィビ <paul@webb.page>2026-04-24 16:37:33 -0700
committernetop://ウィビ <paul@webb.page>2026-04-24 16:37:33 -0700
commit510fd8cbe53abb39cba2c7cbaaefcf2783dc0066 (patch)
tree8f753a33c475b285f2a297785d34cda3b0a8faed /source/library/components/HistoryPanel.svelte
parent261f3bdb77799009344aab4a60686b7186ebd3b0 (diff)
downloadgraphiql-510fd8cbe53abb39cba2c7cbaaefcf2783dc0066.tar.gz
graphiql-510fd8cbe53abb39cba2c7cbaaefcf2783dc0066.zip
Implement v0.6-1.0: shortcuts, format, export/import, splitter, timing, APQ
- v0.6: matchShortcut + format(); Cmd+Shift+Enter/W/F + Cmd+Alt+arrows - v0.7: SessionStore.exportAll/importTabs with version-1 validator - v0.8: Splitter component + four resize handles persisted under layout.* - v0.10: createApqFetcher (HTTP-only) wrapping shared http-body helpers - Drop .svelte re-exports from index.ts for multi-entry JSR/npm publishing
Diffstat (limited to 'source/library/components/HistoryPanel.svelte')
-rw-r--r--source/library/components/HistoryPanel.svelte106
1 files changed, 100 insertions, 6 deletions
diff --git a/source/library/components/HistoryPanel.svelte b/source/library/components/HistoryPanel.svelte
index b7f5c4c..01f397a 100644
--- a/source/library/components/HistoryPanel.svelte
+++ b/source/library/components/HistoryPanel.svelte
@@ -3,13 +3,29 @@
type Props = {
entries: HistoryEntry[];
+ notice?: string | null;
onClear: () => void;
+ onDismissNotice?: () => void;
+ onExport?: () => void;
onFavorite: (id: string) => void;
+ onImport?: (file: File) => void;
onLoad: (id: string, inNewTab: boolean) => void;
onRemove: (id: string) => void;
};
- let { entries, onClear, onFavorite, onLoad, onRemove }: Props = $props();
+ let {
+ entries,
+ notice = null,
+ onClear,
+ onDismissNotice,
+ onExport,
+ onFavorite,
+ onImport,
+ onLoad,
+ onRemove
+ }: Props = $props();
+
+ let fileInput = $state<HTMLInputElement | null>(null);
const sorted = $derived([...entries].sort((a, b) => {
if (a.favorite !== b.favorite)
@@ -33,6 +49,20 @@
onLoad(id, event.shiftKey);
}
}
+
+ function onImportClick() {
+ fileInput?.click();
+ }
+
+ function onFileChange(event: Event) {
+ const input = event.currentTarget as HTMLInputElement;
+ const file = input.files?.[0];
+
+ if (file)
+ onImport?.(file);
+
+ input.value = "";
+ }
</script>
<style lang="scss">
@@ -50,6 +80,7 @@
align-items: center;
border-bottom: 1px solid var(--graphiql-border, #333);
display: flex;
+ gap: 0.5rem;
justify-content: space-between;
padding: 0.5rem 0.75rem;
}
@@ -59,7 +90,12 @@
font-weight: 600;
}
- .clear {
+ .actions {
+ display: flex;
+ gap: 0.5rem;
+ }
+
+ .action {
background: none;
border: none;
color: var(--graphiql-muted, #858585);
@@ -73,6 +109,36 @@
}
}
+ .hidden-input {
+ display: none;
+ }
+
+ .notice {
+ align-items: center;
+ background: var(--graphiql-bg, #1e1e1e);
+ border-bottom: 1px solid var(--graphiql-border, #333);
+ color: var(--graphiql-fg, #d4d4d4);
+ display: flex;
+ font-size: 0.75rem;
+ gap: 0.5rem;
+ justify-content: space-between;
+ padding: 0.375rem 0.75rem;
+ }
+
+ .notice-dismiss {
+ background: none;
+ border: none;
+ color: var(--graphiql-muted, #858585);
+ cursor: pointer;
+ font-size: 0.875rem;
+ line-height: 1;
+ padding: 0 0.25rem;
+
+ &:hover {
+ color: var(--graphiql-fg, #d4d4d4);
+ }
+ }
+
.list {
display: grid;
gap: 0.125rem;
@@ -147,10 +213,38 @@
</style>
<div class="panel">
- <div class="header">
- <span class="title">History</span>
- {#if entries.length > 0}
- <button class="clear" onclick={onClear} type="button">Clear</button>
+ <div>
+ <div class="header">
+ <span class="title">History</span>
+ <div class="actions">
+ {#if onExport}
+ <button class="action" onclick={onExport} type="button">Export</button>
+ {/if}
+ {#if onImport}
+ <button class="action" onclick={onImportClick} type="button">Import</button>
+ <input
+ accept="application/json"
+ bind:this={fileInput}
+ class="hidden-input"
+ onchange={onFileChange}
+ type="file"/>
+ {/if}
+ {#if entries.length > 0}
+ <button class="action" onclick={onClear} type="button">Clear</button>
+ {/if}
+ </div>
+ </div>
+ {#if notice}
+ <div class="notice">
+ <span>{notice}</span>
+ {#if onDismissNotice}
+ <button
+ aria-label="Dismiss notice"
+ class="notice-dismiss"
+ onclick={onDismissNotice}
+ type="button">×</button>
+ {/if}
+ </div>
{/if}
</div>
<div class="list">