Skip to content

Commit 0229ccc

Browse files
committed
refactor: reorganize page editor UI with tabbed left sidebar and improved property controls
- Replace PageEditorCollabStatusCard and PageToolbarActions with new PageEditorLeftSidebar component featuring tabs for path/recent/favorites/selected pages - Change arrow head properties from boolean switches to dropdown selects with 'none' and 'open' options - Add height control to note properties alongside existing width control - Add duplicate and select-all actions to canvas context menu - Show resize
1 parent a274cb7 commit 0229ccc

21 files changed

Lines changed: 901 additions & 317 deletions
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<script setup lang="ts">
2+
import { ref } from "vue";
3+
import { Route, History, Star, ListChecks } from "lucide-vue-next";
4+
import type { Button } from "@/components/ui/button";
5+
6+
export type LeftTab = "path" | "recent" | "favorites" | "selected";
7+
8+
const activeTab = ref<LeftTab>("path");
9+
10+
const tabs = [
11+
{ key: "path" as LeftTab, label: "Path", icon: Route },
12+
{ key: "recent" as LeftTab, label: "Recent", icon: History },
13+
{ key: "favorites" as LeftTab, label: "Favorites", icon: Star },
14+
{ key: "selected" as LeftTab, label: "Selected", icon: ListChecks },
15+
];
16+
</script>
17+
18+
<template>
19+
<div class="flex h-full w-full overflow-hidden">
20+
<!-- Vertical tab strip -->
21+
<div
22+
class="border-border/40 bg-muted/30 flex flex-col items-center gap-1 border-r py-2"
23+
style="width: 40px; min-width: 40px"
24+
>
25+
<button
26+
v-for="tab in tabs"
27+
:key="tab.key"
28+
:title="tab.label"
29+
class="hover:bg-muted flex h-8 w-8 items-center justify-center rounded-md transition-colors"
30+
:class="{
31+
'bg-primary/10 text-primary': activeTab === tab.key,
32+
'text-muted-foreground': activeTab !== tab.key,
33+
}"
34+
@click="activeTab = tab.key"
35+
>
36+
<component :is="tab.icon" class="h-4 w-4" />
37+
</button>
38+
</div>
39+
40+
<!-- Content pane -->
41+
<div class="flex-1 overflow-y-auto p-2">
42+
<slot :active-tab="activeTab" />
43+
</div>
44+
</div>
45+
</template>

new-deepnotes/apps/web/src/features/pages/PageEditorView.vue

Lines changed: 58 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ import {
1010
CardTitle,
1111
} from "@/components/ui/card";
1212
import { useSession } from "../auth/useSession";
13-
import PageToolbarActions from "../spatial/PageToolbarActions.vue";
1413
import SpatialPageView from "../spatial/SpatialPageView.vue";
1514
import { useUserTemplates } from "../spatial/useUserTemplates";
1615
import PageEditorBacklinksCard from "./PageEditorBacklinksCard.vue";
17-
import PageEditorCollabStatusCard from "./PageEditorCollabStatusCard.vue";
16+
import PageEditorLeftSidebar from "./PageEditorLeftSidebar.vue";
1817
import PageEditorManagementCard from "./PageEditorManagementCard.vue";
1918
import PageEditorSnapshotsCard from "./PageEditorSnapshotsCard.vue";
2019
import NotePropertiesCard from "../spatial/NotePropertiesCard.vue";
@@ -360,17 +359,6 @@ onMounted(() => {
360359
@select-arrow="selectedArrowId = $event?.[0] ?? null; selectedArrowModel = $event?.[1] ?? null"
361360
/>
362361

363-
<!-- === Toolbar actions === -->
364-
<template #toolbar-actions>
365-
<PageToolbarActions
366-
@insert-note="spatialViewRef?.insertNoteAtCenter()"
367-
@insert-arrow="spatialViewRef?.insertArrowBetweenSelected()"
368-
@zoom-in="spatialViewRef?.zoomIn()"
369-
@zoom-out="spatialViewRef?.zoomOut()"
370-
@fit-to-screen="spatialViewRef?.fitToScreen()"
371-
/>
372-
</template>
373-
374362
<!-- === Toolbar center: breadcrumb path === -->
375363
<template #toolbar-center>
376364
<nav
@@ -401,61 +389,64 @@ onMounted(() => {
401389

402390
<!-- === Left sidebar === -->
403391
<template #left-sidebar>
404-
<div class="space-y-3">
392+
<PageEditorLeftSidebar v-slot="{ activeTab }">
405393
<!-- Current path -->
406-
<Card>
407-
<CardHeader class="pb-2">
408-
<CardTitle class="text-sm">Path</CardTitle>
409-
</CardHeader>
410-
<CardContent class="space-y-2 text-xs">
411-
<p v-if="pathLoading" class="text-muted-foreground">Loading…</p>
412-
<p v-else-if="pathError" class="text-destructive">{{ pathError }}</p>
413-
<nav v-else class="text-muted-foreground flex flex-wrap items-center gap-1">
414-
<template v-for="(pid, i) in pathPageIds" :key="pid">
415-
<span v-if="i > 0">/</span>
416-
<RouterLink
417-
v-if="i < pathPageIds.length - 1"
418-
class="text-primary hover:underline"
419-
:to="`/pages/${pid}`"
394+
<div v-if="activeTab === 'path'" class="space-y-3">
395+
<Card>
396+
<CardHeader class="pb-2">
397+
<CardTitle class="text-sm">Path</CardTitle>
398+
</CardHeader>
399+
<CardContent class="space-y-2 text-xs">
400+
<p v-if="pathLoading" class="text-muted-foreground">Loading…</p>
401+
<p v-else-if="pathError" class="text-destructive">{{ pathError }}</p>
402+
<nav v-else class="text-muted-foreground flex flex-wrap items-center gap-1">
403+
<template v-for="(pid, i) in pathPageIds" :key="pid">
404+
<span v-if="i > 0">/</span>
405+
<RouterLink
406+
v-if="i < pathPageIds.length - 1"
407+
class="text-primary hover:underline"
408+
:to="`/pages/${pid}`"
409+
>
410+
{{ pagePathLabel(pid, pageLabels) }}
411+
</RouterLink>
412+
<span v-else class="text-foreground font-medium">
413+
{{ pagePathLabel(pid, pageLabels) }}
414+
</span>
415+
</template>
416+
</nav>
417+
<div class="flex flex-wrap gap-1">
418+
<Button
419+
size="xs"
420+
variant="secondary"
421+
class="h-6 text-[10px]"
422+
:disabled="pagePrefsLoading"
423+
@click="bumpAsStarting()"
420424
>
421-
{{ pagePathLabel(pid, pageLabels) }}
422-
</RouterLink>
423-
<span v-else class="text-foreground font-medium">
424-
{{ pagePathLabel(pid, pageLabels) }}
425-
</span>
426-
</template>
427-
</nav>
428-
<div class="flex flex-wrap gap-1">
429-
<Button
430-
size="xs"
431-
variant="secondary"
432-
class="h-6 text-[10px]"
433-
:disabled="pagePrefsLoading"
434-
@click="bumpAsStarting()"
435-
>
436-
Make starting
437-
</Button>
438-
<Button
439-
size="xs"
440-
variant="outline"
441-
class="h-6 text-[10px]"
442-
:disabled="pagePrefsLoading"
443-
@click="toggleFavorite()"
444-
>
445-
{{ isFavorite ? "Unfavorite" : "Favorite" }}
446-
</Button>
447-
</div>
448-
<p v-if="bumpMessage" class="text-muted-foreground text-[10px]">
449-
{{ bumpMessage }}
450-
</p>
451-
<p v-if="favoriteMessage" class="text-amber-700 dark:text-amber-300 text-[10px]">
452-
{{ favoriteMessage }}
453-
</p>
454-
</CardContent>
455-
</Card>
425+
Make starting
426+
</Button>
427+
<Button
428+
size="xs"
429+
variant="outline"
430+
class="h-6 text-[10px]"
431+
:disabled="pagePrefsLoading"
432+
@click="toggleFavorite()"
433+
>
434+
{{ isFavorite ? "Unfavorite" : "Favorite" }}
435+
</Button>
436+
</div>
437+
<p v-if="bumpMessage" class="text-muted-foreground text-[10px]">
438+
{{ bumpMessage }}
439+
</p>
440+
<p v-if="favoriteMessage" class="text-amber-700 dark:text-amber-300 text-[10px]">
441+
{{ favoriteMessage }}
442+
</p>
443+
</CardContent>
444+
</Card>
445+
</div>
456446

457447
<!-- Recent pages -->
458448
<RecentPagesCard
449+
v-if="activeTab === 'recent'"
459450
:recent-page-ids="recentPageIds"
460451
:current-page-id="pageId"
461452
:page-labels="pageLabels"
@@ -464,6 +455,7 @@ onMounted(() => {
464455

465456
<!-- Favorite pages -->
466457
<FavoritePagesCard
458+
v-if="activeTab === 'favorites'"
467459
:favorite-page-ids="favoritePageIds"
468460
:current-page-id="pageId"
469461
:page-labels="pageLabels"
@@ -472,25 +464,13 @@ onMounted(() => {
472464

473465
<!-- Selected pages -->
474466
<SelectedPagesCard
467+
v-if="activeTab === 'selected'"
475468
:selected-page-ids="selectedPageIds"
476469
:current-page-id="pageId"
477470
:page-labels="pageLabels"
478471
@clear="selectedPageIds = []"
479472
/>
480-
481-
<!-- Collab status mini -->
482-
<PageEditorCollabStatusCard
483-
:collab-loading="collabLoading"
484-
:load-error="loadError"
485-
:crypto-error="cryptoError"
486-
:collab-ws-live="collabWsLive"
487-
:collab-ws-error="collabWsError"
488-
:update-count="updateCount"
489-
:collab-last-index="collabLastIndex"
490-
:push-error="pushError"
491-
@unlock-with-password="onUnlockWithPassword($event)"
492-
/>
493-
</div>
473+
</PageEditorLeftSidebar>
494474
</template>
495475

496476
<!-- === Right sidebar === -->
@@ -523,6 +503,7 @@ onMounted(() => {
523503
@update:anchor-x="selectedNoteModel.anchor.value.x = $event"
524504
@update:anchor-y="selectedNoteModel.anchor.value.y = $event"
525505
@update:width="selectedNoteModel.width.value.expanded = $event"
506+
@update:height="selectedNoteModel.height.value.expanded = $event"
526507
@update:color="selectedNoteModel.color.value = $event"
527508
@update:color-inherit="selectedNoteModel.color.inherit.value = $event"
528509
@update:collapsible="selectedNoteModel.collapsing.enabled.value = $event"

new-deepnotes/apps/web/src/features/spatial/ArrowPropertiesCard.vue

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,17 @@ const props = defineProps<{
1515
const emit = defineEmits({
1616
'update:body-type': (value: 'curve' | 'line') => true,
1717
'update:body-style': (value: string) => true,
18-
'update:source-head': (value: boolean) => true,
19-
'update:target-head': (value: boolean) => true,
18+
'update:source-head': (value: string) => true,
19+
'update:target-head': (value: string) => true,
2020
'update:color': (value: number) => true,
2121
'update:color-inherit': (value: boolean) => true,
2222
'update:read-only': (value: boolean) => true,
2323
})
2424
2525
const bodyType = computed(() => props.arrowModel?.bodyType?.value ?? 'curve')
2626
const bodyStyle = computed(() => props.arrowModel?.bodyStyle?.value ?? 'solid')
27-
const sourceHead = computed(() => props.arrowModel?.sourceHead?.value !== 'none')
28-
const targetHead = computed(() => props.arrowModel?.targetHead?.value !== 'none')
27+
const sourceHead = computed(() => props.arrowModel?.sourceHead?.value ?? 'none')
28+
const targetHead = computed(() => props.arrowModel?.targetHead?.value ?? 'open')
2929
const color = computed(() => props.arrowModel?.color?.value ?? 0)
3030
const colorInherit = computed(() => props.arrowModel?.color?.inherit?.value ?? false)
3131
const readOnlyArrow = computed(() => props.arrowModel?.readOnly?.value ?? false)
@@ -74,22 +74,28 @@ function handleColorSelect(colorIndex: number) {
7474
<!-- Arrow Heads -->
7575
<div class="space-y-2">
7676
<Label>Arrow Heads</Label>
77-
<div class="flex items-center gap-4">
78-
<div class="flex items-center gap-2">
79-
<Switch
80-
:model-value="sourceHead"
77+
<div class="flex gap-2">
78+
<div class="flex-1">
79+
<select
80+
:value="sourceHead"
81+
class="h-8 w-full rounded-md border border-input bg-background px-2 text-xs ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
8182
:disabled="readOnly"
82-
@update:model-value="emit('update:source-head', Boolean($event))"
83-
/>
84-
<Label>Source</Label>
83+
@change="emit('update:source-head', ($event.target as HTMLSelectElement).value)"
84+
>
85+
<option value="none">None</option>
86+
<option value="open">Open</option>
87+
</select>
8588
</div>
86-
<div class="flex items-center gap-2">
87-
<Switch
88-
:model-value="targetHead"
89+
<div class="flex-1">
90+
<select
91+
:value="targetHead"
92+
class="h-8 w-full rounded-md border border-input bg-background px-2 text-xs ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
8993
:disabled="readOnly"
90-
@update:model-value="emit('update:target-head', Boolean($event))"
91-
/>
92-
<Label>Target</Label>
94+
@change="emit('update:target-head', ($event.target as HTMLSelectElement).value)"
95+
>
96+
<option value="none">None</option>
97+
<option value="open">Open</option>
98+
</select>
9399
</div>
94100
</div>
95101
</div>

new-deepnotes/apps/web/src/features/spatial/CanvasContextMenu.vue

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
DropdownMenuItem,
77
DropdownMenuSeparator,
88
} from '@/components/ui/dropdown-menu'
9-
import { Plus, Clipboard, Trash2, Copy, Scissors } from 'lucide-vue-next'
9+
import { Plus, Clipboard, Trash2, Copy, Scissors, Files, ListChecks } from 'lucide-vue-next'
1010
import type { ClipboardNote, ClipboardArrow } from './clipboard'
1111
import { readClipboardPayload } from './clipboard'
1212
@@ -19,6 +19,8 @@ const props = defineProps<{
1919
onDeleteSelected: () => void
2020
onCopySelected: () => void
2121
onCutSelected: () => void
22+
onDuplicateSelected: () => void
23+
onSelectAll: () => void
2224
hasSelection: boolean
2325
}>()
2426
@@ -60,6 +62,16 @@ function handleCutSelected() {
6062
props.onCutSelected()
6163
emit('close')
6264
}
65+
66+
function handleDuplicateSelected() {
67+
props.onDuplicateSelected()
68+
emit('close')
69+
}
70+
71+
function handleSelectAll() {
72+
props.onSelectAll()
73+
emit('close')
74+
}
6375
</script>
6476

6577
<template>
@@ -86,6 +98,11 @@ function handleCutSelected() {
8698

8799
<DropdownMenuSeparator v-if="hasSelection" />
88100

101+
<DropdownMenuItem v-if="hasSelection" @click="handleDuplicateSelected">
102+
<Files class="h-4 w-4" />
103+
<span>Duplicate</span>
104+
</DropdownMenuItem>
105+
89106
<DropdownMenuItem v-if="hasSelection" @click="handleCopySelected">
90107
<Copy class="h-4 w-4" />
91108
<span>Copy</span>

0 commit comments

Comments
 (0)