feat: add workspace/cases core capability
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import * as App from '../../../wailsjs/go/api/App';
|
||||
import WorkspaceTree from './WorkspaceTree.svelte';
|
||||
|
||||
let plugins = [];
|
||||
let vaultStatus = { status: 'unknown', path: '', vaultId: '' };
|
||||
@@ -75,6 +76,10 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if vaultOpen}
|
||||
<WorkspaceTree />
|
||||
{/if}
|
||||
|
||||
<div class="sidebar-footer">
|
||||
{#if vaultStatus.status !== 'unknown'}
|
||||
<span class="vault-indicator" class:vault-open={vaultStatus.status === 'open'} class:vault-closed={vaultStatus.status !== 'open'}>
|
||||
@@ -129,6 +134,13 @@
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
:global(workspace-tree) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
color: #666;
|
||||
font-size: 0.7rem;
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import * as App from '../../../wailsjs/go/api/App';
|
||||
|
||||
let nodes = [];
|
||||
let currentNodeId = '';
|
||||
let loading = true;
|
||||
let error = '';
|
||||
let expandedNodes = {};
|
||||
let showCreate = false;
|
||||
let newNodeTitle = '';
|
||||
let newNodeParentId = '';
|
||||
let newNodeType = 'case';
|
||||
let creating = false;
|
||||
|
||||
onMount(async () => {
|
||||
await loadTree();
|
||||
});
|
||||
|
||||
async function loadTree() {
|
||||
loading = true;
|
||||
error = '';
|
||||
try {
|
||||
const result = await App.GetWorkspaceTree();
|
||||
if (result.status === 'not initialized') {
|
||||
nodes = [];
|
||||
currentNodeId = '';
|
||||
} else {
|
||||
nodes = result.nodes || [];
|
||||
currentNodeId = result.currentNodeId || '';
|
||||
const root = nodes.find(n => !n.parentId);
|
||||
if (root) expandedNodes[root.id] = true;
|
||||
}
|
||||
} catch (e) {
|
||||
error = String(e);
|
||||
}
|
||||
loading = false;
|
||||
}
|
||||
|
||||
function childrenOf(parentId) {
|
||||
return nodes.filter(n => n.parentId === parentId).sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
function roots() {
|
||||
return nodes.filter(n => !n.parentId).sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
function toggle(id) {
|
||||
expandedNodes[id] = !expandedNodes[id];
|
||||
expandedNodes = expandedNodes;
|
||||
}
|
||||
|
||||
function hasKids(id) {
|
||||
return nodes.some(n => n.parentId === id);
|
||||
}
|
||||
|
||||
function icon(type) {
|
||||
if (type === 'space') return '\u{1F310}';
|
||||
if (type === 'case') return '\u{1F4CB}';
|
||||
if (type === 'folder') return '\u{1F4C1}';
|
||||
return '\u{1F4C4}';
|
||||
}
|
||||
|
||||
async function selectNode(id) {
|
||||
const err = await App.SetCurrentWorkspaceNode(id);
|
||||
if (err) { error = err; return; }
|
||||
currentNodeId = id;
|
||||
}
|
||||
|
||||
function openCreate(parentId, type) {
|
||||
newNodeParentId = parentId;
|
||||
newNodeType = type;
|
||||
newNodeTitle = '';
|
||||
showCreate = true;
|
||||
}
|
||||
|
||||
async function doCreate() {
|
||||
if (!newNodeTitle.trim()) return;
|
||||
creating = true;
|
||||
const res = await App.CreateWorkspaceNode(newNodeParentId, newNodeType, newNodeTitle.trim());
|
||||
if (res.error) { error = res.error; creating = false; return; }
|
||||
showCreate = false;
|
||||
creating = false;
|
||||
await loadTree();
|
||||
expandedNodes[newNodeParentId] = true;
|
||||
expandedNodes = expandedNodes;
|
||||
}
|
||||
|
||||
function cancelCreate() {
|
||||
showCreate = false;
|
||||
newNodeTitle = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wt">
|
||||
<div class="wt-header">
|
||||
<span class="wt-title">Workspace</span>
|
||||
<button class="wt-btn" on:click={() => openCreate('', 'space')} type="button">+</button>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<div class="wt-loading">Loading...</div>
|
||||
{:else if error}
|
||||
<div class="wt-error">{error}</div>
|
||||
{:else}
|
||||
{#each roots() as node (node.id)}
|
||||
<div class="wt-node">
|
||||
<div class="wt-row" class:selected={node.id === currentNodeId}>
|
||||
{#if hasKids(node.id)}
|
||||
<button class="wt-expand" on:click={() => toggle(node.id)} type="button">{expandedNodes[node.id] ? '\u25BE' : '\u25B8'}</button>
|
||||
{:else}
|
||||
<span class="wt-expand-spacer"></span>
|
||||
{/if}
|
||||
<span class="wt-icon">{icon(node.type)}</span>
|
||||
<button class="wt-label" on:click={() => selectNode(node.id)} type="button">{node.title}</button>
|
||||
<button class="wt-btn wt-btn-small" on:click={() => openCreate(node.id, 'case')} type="button">+</button>
|
||||
</div>
|
||||
{#if expandedNodes[node.id]}
|
||||
{#each childrenOf(node.id) as child (child.id)}
|
||||
<div class="wt-node wt-child">
|
||||
<div class="wt-row" class:selected={child.id === currentNodeId}>
|
||||
{#if hasKids(child.id)}
|
||||
<button class="wt-expand" on:click={() => toggle(child.id)} type="button">{expandedNodes[child.id] ? '\u25BE' : '\u25B8'}</button>
|
||||
{:else}
|
||||
<span class="wt-expand-spacer"></span>
|
||||
{/if}
|
||||
<span class="wt-icon">{icon(child.type)}</span>
|
||||
<button class="wt-label" on:click={() => selectNode(child.id)} type="button">{child.title}</button>
|
||||
<button class="wt-btn wt-btn-small" on:click={() => openCreate(child.id, 'folder')} type="button">+</button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if showCreate}
|
||||
<div class="wt-create">
|
||||
<div class="wt-create-header">
|
||||
<span>New {newNodeType}</span>
|
||||
<button class="wt-btn" on:click={cancelCreate} type="button">{'\u2715'}</button>
|
||||
</div>
|
||||
<input type="text" bind:value={newNodeTitle} placeholder="Name..." disabled={creating} />
|
||||
<div class="wt-create-actions">
|
||||
<button class="wt-btn-primary" on:click={doCreate} type="button" disabled={creating || !newNodeTitle.trim()}>{creating ? '...' : 'Create'}</button>
|
||||
<button class="wt-btn" on:click={cancelCreate} type="button" disabled={creating}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wt { display: flex; flex-direction: column; flex: 1; overflow: hidden; position: relative; }
|
||||
.wt-header { display: flex; align-items: center; justify-content: space-between; padding: 0.4rem 0.6rem; border-bottom: 1px solid #0f3460; }
|
||||
.wt-title { color: #a0a0b8; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; }
|
||||
.wt-btn { background: none; border: none; color: #666; cursor: pointer; font-size: 0.85rem; padding: 0.1rem 0.3rem; border-radius: 3px; }
|
||||
.wt-btn:hover { color: #4ecca3; background: rgba(78,204,163,0.1); }
|
||||
.wt-btn-small { font-size: 0.7rem; opacity: 0; }
|
||||
.wt-row:hover .wt-btn-small { opacity: 1; }
|
||||
.wt-loading, .wt-error { padding: 0.5rem; font-size: 0.75rem; color: #666; }
|
||||
.wt-error { color: #e94560; }
|
||||
.wt-node { }
|
||||
.wt-row { display: flex; align-items: center; gap: 0.2rem; padding: 0.2rem 0.4rem; }
|
||||
.wt-row:hover { background: rgba(15,52,96,0.4); }
|
||||
.wt-row.selected { background: rgba(78,204,163,0.1); }
|
||||
.wt-expand { width: 1rem; height: 1rem; display: flex; align-items: center; justify-content: center; font-size: 0.65rem; color: #666; background: none; border: none; cursor: pointer; padding: 0; }
|
||||
.wt-expand:hover { color: #e0e0f0; }
|
||||
.wt-expand-spacer { width: 1rem; flex-shrink: 0; }
|
||||
.wt-icon { font-size: 0.8rem; flex-shrink: 0; }
|
||||
.wt-label { flex: 1; background: none; border: none; color: #e0e0f0; font-size: 0.78rem; text-align: left; cursor: pointer; padding: 0.1rem 0.2rem; border-radius: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.wt-label:hover { color: #4ecca3; }
|
||||
.wt-child .wt-row { padding-left: 1.2rem; }
|
||||
.wt-create { position: absolute; bottom: 0; left: 0; right: 0; background: #16213e; border-top: 1px solid #0f3460; padding: 0.6rem; z-index: 10; }
|
||||
.wt-create-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.4rem; color: #a0a0b8; font-size: 0.7rem; text-transform: uppercase; }
|
||||
.wt-create input { width: 100%; background: #0f3460; border: 1px solid #1a3a5c; color: #e0e0f0; padding: 0.35rem 0.5rem; border-radius: 4px; font-size: 0.8rem; margin-bottom: 0.4rem; box-sizing: border-box; }
|
||||
.wt-create input:focus { outline: none; border-color: #4ecca3; }
|
||||
.wt-create-actions { display: flex; gap: 0.4rem; justify-content: flex-end; }
|
||||
.wt-btn-primary { background: #4ecca3; color: #1a1a2e; border: none; padding: 0.3rem 0.6rem; border-radius: 4px; cursor: pointer; font-size: 0.75rem; font-weight: 600; }
|
||||
.wt-btn-primary:hover:not(:disabled) { background: #3dbb92; }
|
||||
.wt-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
</style>
|
||||
Vendored
+14
@@ -5,10 +5,14 @@ import {api} from '../models';
|
||||
import {permissions} from '../models';
|
||||
import {plugin} from '../models';
|
||||
|
||||
export function ArchiveWorkspaceNode(arg1:string):Promise<string>;
|
||||
|
||||
export function CloseVault():Promise<void>;
|
||||
|
||||
export function CreateVault(arg1:string):Promise<void>;
|
||||
|
||||
export function CreateWorkspaceNode(arg1:string,arg2:string,arg3:string):Promise<Record<string, any>>;
|
||||
|
||||
export function DisablePlugin(arg1:string):Promise<string>;
|
||||
|
||||
export function EnablePlugin(arg1:string):Promise<string>;
|
||||
@@ -19,6 +23,8 @@ export function GetCapabilities():Promise<Array<capability.Entry>>;
|
||||
|
||||
export function GetContributions():Promise<api.ContributionSummary>;
|
||||
|
||||
export function GetCurrentWorkspaceNode():Promise<Record<string, any>>;
|
||||
|
||||
export function GetPermissions():Promise<Array<permissions.Entry>>;
|
||||
|
||||
export function GetPlugins():Promise<Array<plugin.Plugin>>;
|
||||
@@ -27,6 +33,10 @@ export function GetVaultPluginState():Promise<Record<string, any>>;
|
||||
|
||||
export function GetVaultStatus():Promise<Record<string, string>>;
|
||||
|
||||
export function GetWorkspaceTree():Promise<Record<string, any>>;
|
||||
|
||||
export function MoveWorkspaceNode(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function OpenVault(arg1:string):Promise<void>;
|
||||
|
||||
export function ReadPluginDataJSON(arg1:string,arg2:string):Promise<Record<string, any>>;
|
||||
@@ -39,12 +49,16 @@ export function RecordDesiredPlugin(arg1:string,arg2:string,arg3:string):Promise
|
||||
|
||||
export function ReloadPlugins():Promise<number|string>;
|
||||
|
||||
export function RenameWorkspaceNode(arg1:string,arg2:string):Promise<string>;
|
||||
|
||||
export function SelectDirectory():Promise<string>;
|
||||
|
||||
export function SelectVaultForOpen():Promise<string>;
|
||||
|
||||
export function SetCurrentVault(arg1:string):Promise<string>;
|
||||
|
||||
export function SetCurrentWorkspaceNode(arg1:string):Promise<string>;
|
||||
|
||||
export function UpdateAppSettings(arg1:Record<string, any>):Promise<string>;
|
||||
|
||||
export function WritePluginDataJSON(arg1:string,arg2:string,arg3:Record<string, any>):Promise<string>;
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function ArchiveWorkspaceNode(arg1) {
|
||||
return window['go']['api']['App']['ArchiveWorkspaceNode'](arg1);
|
||||
}
|
||||
|
||||
export function CloseVault() {
|
||||
return window['go']['api']['App']['CloseVault']();
|
||||
}
|
||||
@@ -10,6 +14,10 @@ export function CreateVault(arg1) {
|
||||
return window['go']['api']['App']['CreateVault'](arg1);
|
||||
}
|
||||
|
||||
export function CreateWorkspaceNode(arg1, arg2, arg3) {
|
||||
return window['go']['api']['App']['CreateWorkspaceNode'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function DisablePlugin(arg1) {
|
||||
return window['go']['api']['App']['DisablePlugin'](arg1);
|
||||
}
|
||||
@@ -30,6 +38,10 @@ export function GetContributions() {
|
||||
return window['go']['api']['App']['GetContributions']();
|
||||
}
|
||||
|
||||
export function GetCurrentWorkspaceNode() {
|
||||
return window['go']['api']['App']['GetCurrentWorkspaceNode']();
|
||||
}
|
||||
|
||||
export function GetPermissions() {
|
||||
return window['go']['api']['App']['GetPermissions']();
|
||||
}
|
||||
@@ -46,6 +58,14 @@ export function GetVaultStatus() {
|
||||
return window['go']['api']['App']['GetVaultStatus']();
|
||||
}
|
||||
|
||||
export function GetWorkspaceTree() {
|
||||
return window['go']['api']['App']['GetWorkspaceTree']();
|
||||
}
|
||||
|
||||
export function MoveWorkspaceNode(arg1, arg2) {
|
||||
return window['go']['api']['App']['MoveWorkspaceNode'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function OpenVault(arg1) {
|
||||
return window['go']['api']['App']['OpenVault'](arg1);
|
||||
}
|
||||
@@ -70,6 +90,10 @@ export function ReloadPlugins() {
|
||||
return window['go']['api']['App']['ReloadPlugins']();
|
||||
}
|
||||
|
||||
export function RenameWorkspaceNode(arg1, arg2) {
|
||||
return window['go']['api']['App']['RenameWorkspaceNode'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function SelectDirectory() {
|
||||
return window['go']['api']['App']['SelectDirectory']();
|
||||
}
|
||||
@@ -82,6 +106,10 @@ export function SetCurrentVault(arg1) {
|
||||
return window['go']['api']['App']['SetCurrentVault'](arg1);
|
||||
}
|
||||
|
||||
export function SetCurrentWorkspaceNode(arg1) {
|
||||
return window['go']['api']['App']['SetCurrentWorkspaceNode'](arg1);
|
||||
}
|
||||
|
||||
export function UpdateAppSettings(arg1) {
|
||||
return window['go']['api']['App']['UpdateAppSettings'](arg1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user