feat(workspaces): add workspace sessions and management
Introduce workspace support to group repositories and store per-workspace open tabs and the last active repository. Integrate a workspace picker into the repository tab bar and add workspace controls to the dashboard to create, edit, switch and delete workspaces. Persist and migrate workspace preferences robustly and include tests for migration, corruption and edge cases. - Persist per-workspace sessions and active repo to localStorage - Add workspace picker in tabs and dashboard management hooks - Include migration/validation logic and unit tests for edge cases
This commit is contained in:
+14
-1
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Columns3, Database, Folder, House, GitPullRequest, Plus, X } from "@lucide/svelte";
|
||||
|
||||
import SelectMenu from "./components/SelectMenu.svelte";
|
||||
|
||||
interface RepositoryTabItem {
|
||||
path: string;
|
||||
name: string;
|
||||
@@ -22,6 +24,10 @@
|
||||
export let onAdd: () => void | Promise<void> = () => {};
|
||||
export let onReorder: (path: string, targetPath: string, after: boolean) => void = () => {};
|
||||
|
||||
export let workspaceId = "";
|
||||
export let workspaceOptions: { value: string; label: string }[] = [];
|
||||
export let onWorkspaceChange: (id: string) => unknown = () => {};
|
||||
|
||||
let navigation: HTMLElement;
|
||||
let drag: { path: string; pointerId: number; startX: number; startScroll: number; source: number; target: number; width: number; centers: number[]; element: HTMLElement } | null = null;
|
||||
let dragging = false;
|
||||
@@ -89,6 +95,7 @@
|
||||
<button type="button" class:active={activeView === "issues"} aria-current={activeView === "issues" ? "page" : undefined} disabled={isBusy} onclick={onOpenIssues}><Columns3 size={17}/><span>Issues & Boards</span></button>
|
||||
</nav>
|
||||
{#if activeView === "repository"}
|
||||
<div class="repository-row">
|
||||
<nav bind:this={navigation} class="repository-navigation" class:reordering={dragging} aria-label={language === "de" ? "Geöffnete Repositories" : "Open repositories"}>
|
||||
{#each repoTabs as repo, index (repo.path)}
|
||||
<div class="repository-tab" class:active={isActive(repo.path)}
|
||||
@@ -103,10 +110,16 @@
|
||||
{/each}
|
||||
<button class="repository-add" type="button" onclick={onAdd} disabled={isBusy} title={language === "de" ? "Repository-Ordner öffnen" : "Open repository folder"} aria-label={language === "de" ? "Repository-Ordner öffnen" : "Open repository folder"}><Plus size={18}/></button>
|
||||
</nav>
|
||||
<div class="workspace-picker"><SelectMenu options={workspaceOptions} value={workspaceId} onChange={onWorkspaceChange} disabled={isBusy} ariaLabel={language === "de" ? "Workspace wechseln" : "Switch workspace"} /></div>
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<style>
|
||||
.repository-row{display:flex;min-width:0;align-items:stretch;background:color-mix(in srgb,var(--color-surface) 45%,var(--app-bg))}
|
||||
.workspace-picker{flex:0 0 180px;min-width:0;align-self:center;padding:3px 5px 3px 8px;border-left:1px solid var(--color-border-subtle)}
|
||||
.workspace-picker :global(.select-menu-trigger){min-height:28px;height:28px;font-size:12px}
|
||||
@media(max-width:600px){.workspace-picker{flex-basis:140px}}
|
||||
.workspace-navigation{flex:0 0 auto;min-width:0;background:var(--app-bg);color:var(--color-ink-muted);font-family:var(--font-sans);border-bottom:1px solid var(--color-border-subtle)}
|
||||
.global-navigation{display:flex;align-items:stretch;gap:2px;min-height:38px;padding:0 5px;overflow-x:auto;scrollbar-width:thin;border-bottom:1px solid var(--color-border-subtle)}
|
||||
button{font:inherit;cursor:pointer;color:inherit;background:transparent;border:0;box-shadow:none}
|
||||
@@ -117,7 +130,7 @@
|
||||
.global-navigation button.active{color:var(--color-ink);font-weight:600}
|
||||
.global-navigation button.active::after{position:absolute;content:"";height:2px;bottom:0;left:12px;right:12px;background:var(--color-accent)}
|
||||
.global-navigation button.active>:global(svg){color:var(--color-accent)}
|
||||
.repository-navigation{display:flex;align-items:stretch;gap:4px;min-height:34px;padding:4px 5px 0;overflow-x:auto;scrollbar-width:thin;background:color-mix(in srgb,var(--color-surface) 45%,var(--app-bg))}
|
||||
.repository-navigation{flex:1;min-width:0;display:flex;align-items:stretch;gap:4px;min-height:34px;padding:4px 5px 0;overflow-x:auto;scrollbar-width:thin;background:color-mix(in srgb,var(--color-surface) 45%,var(--app-bg))}
|
||||
.repository-tab{display:flex;flex:0 0 auto;align-items:center;min-width:100px;max-width:250px;border:1px solid var(--color-border);border-bottom:0;border-radius:5px 5px 0 0;background:var(--app-bg)}
|
||||
.repository-tab.active{background:var(--color-surface-raised);border-color:var(--color-border-input);color:var(--color-ink)}
|
||||
.repository-tab:hover{background:var(--color-surface-hover)}
|
||||
|
||||
Reference in New Issue
Block a user