Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ declare module 'vue' {
CSS: typeof import('./src/components/Icons/CSS.vue')['default']
CustomSearchPanel: typeof import('./src/components/Controls/CodeMirror/CustomSearchPanel.vue')['default']
DashboardContent: typeof import('./src/components/DashboardContent.vue')['default']
DashboardHead: typeof import('./src/components/DashboardHead.vue')['default']
DashboardSidebar: typeof import('./src/components/DashboardSidebar.vue')['default']
DashboardToolbar: typeof import('./src/components/DashboardToolbar.vue')['default']
DataLoaderBlock: typeof import('./src/components/DataLoaderBlock.vue')['default']
Expand Down
141 changes: 25 additions & 116 deletions frontend/src/components/DashboardContent.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="no-scrollbar flex-1 overflow-auto">
<section class="m-auto mb-32 flex h-fit w-3/4 max-w-6xl flex-col pt-5">
<section class="m-auto mb-24 flex h-fit w-3/4 max-w-6xl flex-col pt-5">
<!-- pages -->
<div>
<div v-if="!webPages.data?.length && !searchFilter && !typeFilter" class="col-span-full">
Expand Down Expand Up @@ -48,105 +48,6 @@
size="sm">
Load More
</BuilderButton>
<!-- list head -->
<div class="sticky top-0 order-[-1] mb-4 flex items-center justify-between bg-surface-white px-3 py-5">
<h1 class="text-xl font-semibold text-ink-gray-9">
{{ builderStore.activeFolder || "All Pages" }}
</h1>
<div class="flex gap-2">
<div>
<Button
variant="solid"
v-if="selectionMode && selectedPages.size"
@click="showFolderSelectorDialog = true">
Move To Folder
</Button>
</div>
<div class="relative flex" v-show="!selectionMode">
<BuilderInput
class="w-48"
type="text"
placeholder="Filter by title or route"
v-model="searchFilter"
@input="
(value: string) => {
searchFilter = value;
}
">
<template #prefix>
<FeatherIcon name="search" class="size-4 text-ink-gray-5"></FeatherIcon>
</template>
</BuilderInput>
</div>
<div class="max-md:hidden" v-show="!selectionMode && displayType !== 'tree'">
<Select
v-model="typeFilter"
:options="[
{ label: 'Type', value: '', disabled: true },
{ label: 'All', value: 'all' },
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' },
{ label: 'Unpublished', value: 'unpublished' },
]" />
</div>
<div v-if="displayType === 'tree' && !selectionMode">
<Button
variant="subtle"
size="sm"
class="w-20"
@click="
treeExpanded
? (routeTreeRef?.collapseAll(), (treeExpanded = false))
: (routeTreeRef?.expandAll(), (treeExpanded = true))
">
{{ treeExpanded ? "Collapse" : "Expand" }}
</Button>
</div>
<div class="max-sm:hidden" v-show="displayType !== 'tree' && !selectionMode">
<Select
v-model="orderBy"
:options="[
{ label: 'Sort', value: '', disabled: true },
{ label: 'Last Created', value: 'creation' },
{ label: 'Last Modified', value: 'modified' },
{
label: 'Alphabetically (A-Z)',
value: 'alphabetically_a_z',
},
{
label: 'Alphabetically (Z-A)',
value: 'alphabetically_z_a',
},
]" />
</div>
<div class="max-md:hidden">
<OptionToggle
class="[&>div]:min-w-0"
:options="[
{
label: 'Grid',
value: 'grid',
icon: 'grid',
hideLabel: true,
},
{
label: 'List',
value: 'list',
icon: 'list',
hideLabel: true,
},
{
label: 'Route Tree',
value: 'tree',
icon: ListTreeIcon,
hideLabel: true,
showTooltip: true,
},
]"
v-model="displayType"></OptionToggle>
</div>
</div>
</div>
</section>
</div>
<SelectFolder
Expand All @@ -156,35 +57,46 @@
</template>

<script setup lang="ts">
import OptionToggle from "@/components/Controls/OptionToggle.vue";
import SelectFolder from "@/components/Modals/SelectFolder.vue";
import PageCard from "@/components/PageCard.vue";
import PageListItem from "@/components/PageListItem.vue";
import RouteTreeView from "@/components/RouteTreeView.vue";
import { useDashboardState } from "@/composables/useDashboardState";
import { webPages } from "@/data/webPage";
import vOnClickAndHold from "@/directives/vOnClickAndHold";
import useBuilderStore from "@/stores/builderStore";
import { BuilderPage } from "@/types/Builder/BuilderPage";
import { useShortcut } from "@/utils/useShortcut";
import { useStorage, watchDebounced } from "@vueuse/core";
import { Button, createResource, Select } from "frappe-ui";
import { watchDebounced } from "@vueuse/core";
import { createResource } from "frappe-ui";
import { useTelemetry } from "frappe-ui/frappe";
import { onActivated, Ref, ref, watch } from "vue";
import ListTreeIcon from "~icons/lucide/list-tree";
import { onActivated, onMounted, onUnmounted, ref, watch } from "vue";

const routeTreeRef = ref<InstanceType<typeof RouteTreeView>>();
const treeExpanded = ref(true);

const { capture } = useTelemetry();
const builderStore = useBuilderStore();
const displayType = useStorage("displayType", "grid") as Ref<"grid" | "list" | "tree">;
const showFolderSelectorDialog = ref(false);
const {
searchFilter,
typeFilter,
orderBy,
displayType,
selectionMode,
selectedPages,
showFolderSelectorDialog,
expandTreeFn,
collapseTreeFn,
} = useDashboardState();

onMounted(() => {
expandTreeFn.value = () => routeTreeRef.value?.expandAll();
collapseTreeFn.value = () => routeTreeRef.value?.collapseAll();
});

const searchFilter = ref("");
const typeFilter = useStorage("typeFilter", "") as Ref<"" | "draft" | "published" | "unpublished" | "all">;
const orderBy = useStorage("orderBy", "creation") as Ref<
"creation" | "modified" | "alphabetically_a_z" | "alphabetically_z_a"
>;
onUnmounted(() => {
expandTreeFn.value = null;
collapseTreeFn.value = null;
});

const orderMap = {
creation: "creation desc",
Expand All @@ -193,9 +105,6 @@ const orderMap = {
alphabetically_z_a: "page_title desc",
};

const selectedPages = ref(new Set<string>());
const selectionMode = ref(false);

onActivated(() => {
capture("builder_dashboard_page_visited");
});
Expand Down
122 changes: 122 additions & 0 deletions frontend/src/components/DashboardHead.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<template>
<div class="m-auto flex w-3/4 max-w-6xl items-center justify-between bg-surface-white px-3.5 py-5 pt-8">
<h1 class="text-xl font-semibold text-ink-gray-9">
{{ builderStore.activeFolder || "All Pages" }}
</h1>
<div class="flex gap-2">
<div>
<Button
variant="solid"
v-if="selectionMode && selectedPages.size"
@click="showFolderSelectorDialog = true">
Move To Folder
</Button>
</div>
<div class="relative flex" v-show="!selectionMode">
<BuilderInput
class="w-48"
type="text"
placeholder="Filter by title or route"
v-model="searchFilter"
@input="
(value: string) => {
searchFilter = value;
}
">
<template #prefix>
<FeatherIcon name="search" class="size-4 text-ink-gray-5"></FeatherIcon>
</template>
</BuilderInput>
</div>
<div class="max-md:hidden" v-show="!selectionMode && displayType !== 'tree'">
<Select
v-model="typeFilter"
:options="[
{ label: 'Type', value: '', disabled: true },
{ label: 'All', value: 'all' },
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' },
{ label: 'Unpublished', value: 'unpublished' },
]" />
</div>
<div v-if="displayType === 'tree' && !selectionMode">
<Button
variant="subtle"
size="sm"
class="w-20"
@click="
treeExpanded
? (collapseTreeFn?.(), (treeExpanded = false))
: (expandTreeFn?.(), (treeExpanded = true))
">
{{ treeExpanded ? "Collapse" : "Expand" }}
</Button>
</div>
<div class="max-sm:hidden" v-show="displayType !== 'tree' && !selectionMode">
<Select
v-model="orderBy"
:options="[
{ label: 'Sort', value: '', disabled: true },
{ label: 'Last Created', value: 'creation' },
{ label: 'Last Modified', value: 'modified' },
{
label: 'Alphabetically (A-Z)',
value: 'alphabetically_a_z',
},
{
label: 'Alphabetically (Z-A)',
value: 'alphabetically_z_a',
},
]" />
</div>
<div class="max-md:hidden">
<OptionToggle
class="[&>div]:min-w-0"
:options="[
{
label: 'Grid',
value: 'grid',
icon: 'grid',
hideLabel: true,
},
{
label: 'List',
value: 'list',
icon: 'list',
hideLabel: true,
},
{
label: 'Route Tree',
value: 'tree',
icon: ListTreeIcon,
hideLabel: true,
showTooltip: true,
},
]"
v-model="displayType"></OptionToggle>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import OptionToggle from "@/components/Controls/OptionToggle.vue";
import { useDashboardState } from "@/composables/useDashboardState";
import useBuilderStore from "@/stores/builderStore";
import { Button, Select } from "frappe-ui";
import ListTreeIcon from "~icons/lucide/list-tree";

const builderStore = useBuilderStore();
const {
searchFilter,
selectionMode,
selectedPages,
showFolderSelectorDialog,
treeExpanded,
displayType,
typeFilter,
orderBy,
expandTreeFn,
collapseTreeFn,
} = useDashboardState();
</script>
18 changes: 11 additions & 7 deletions frontend/src/components/RouteTreeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,23 @@
<Button
v-if="node.hasChildren"
variant="ghost"
class="!text-ink-gray-4"
class="!text-ink-gray-5"
@click.stop="toggleNode(node)"
:icon="node.expanded ? 'chevron-down' : 'chevron-right'"></Button>
<span v-else class="size-6 w-7 shrink-0"></span>
<!-- <FeatherIcon v-if="node.hasChildren" name="hash" class="size-3.5 shrink-0 text-ink-gray-5" /> -->
<!-- <FeatherIcon v-else name="file-minus" class="-mr-1 size-3.5 shrink-0 text-ink-gray-5" /> -->

<!-- Page node label row -->
<div v-if="node.page" class="flex min-w-0 flex-1 items-center gap-1.5 py-0.5">
<code class="shrink-0 px-1.5 py-0.5 font-mono text-xs font-medium text-ink-gray-8">
<code
class="shrink-0 py-0.5 font-mono text-sm text-ink-gray-6 group-hover:text-ink-gray-9"
:class="{
'font-semibold': node.hasChildren,
}">
/{{ node.label }}
</code>
<span
v-if="node.page.page_title"
class="truncate text-sm text-ink-gray-5"
class="truncate text-sm text-ink-gray-4"
:title="node.page.page_title">
{{ node.page.page_title }}
</span>
Expand All @@ -84,8 +86,10 @@
<!-- Folder node label -->
<div v-else class="flex min-w-0 flex-1 items-center gap-1 py-0.5">
<span
class="font-mono text-sm font-semibold"
:class="node.depth === 0 ? 'text-ink-gray-8' : 'text-ink-gray-6'">
class="font-mono text-sm text-ink-gray-6 group-hover:text-ink-gray-9"
:class="{
'font-semibold': node.hasChildren,
}">
/{{ node.label }}
</span>
</div>
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/composables/useDashboardState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useStorage } from "@vueuse/core";
import { ref, Ref } from "vue";

const searchFilter = ref("");
const selectionMode = ref(false);
const selectedPages = ref(new Set<string>());
const showFolderSelectorDialog = ref(false);
const treeExpanded = ref(true);

const displayType = useStorage("displayType", "grid") as Ref<"grid" | "list" | "tree">;
const typeFilter = useStorage("typeFilter", "") as Ref<"" | "draft" | "published" | "unpublished" | "all">;
const orderBy = useStorage("orderBy", "creation") as Ref<
"creation" | "modified" | "alphabetically_a_z" | "alphabetically_z_a"
>;

const expandTreeFn = ref<(() => void) | null>(null);
const collapseTreeFn = ref<(() => void) | null>(null);

export function useDashboardState() {
return {
searchFilter,
selectionMode,
selectedPages,
showFolderSelectorDialog,
treeExpanded,
displayType,
typeFilter,
orderBy,
expandTreeFn,
collapseTreeFn,
};
}
Loading
Loading