Skip to content

Commit 877cdea

Browse files
Merge pull request #255 from frappe/folders
2 parents 13d0e89 + 5e382de commit 877cdea

23 files changed

+874
-379
lines changed

builder/builder/doctype/builder_page/builder_page.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"set_meta_tags",
3636
"options_tab",
3737
"authenticated_access",
38-
"disable_indexing"
38+
"disable_indexing",
39+
"project_folder"
3940
],
4041
"fields": [
4142
{
@@ -194,11 +195,17 @@
194195
"fieldname": "disable_indexing",
195196
"fieldtype": "Check",
196197
"label": "Disable Indexing"
198+
},
199+
{
200+
"fieldname": "project_folder",
201+
"fieldtype": "Link",
202+
"label": "Project Folder",
203+
"options": "Builder Project Folder"
197204
}
198205
],
199206
"index_web_pages_for_search": 1,
200207
"links": [],
201-
"modified": "2024-09-02 14:20:24.457766",
208+
"modified": "2024-11-26 17:03:15.281018",
202209
"modified_by": "Administrator",
203210
"module": "Builder",
204211
"name": "Builder Page",

builder/builder/doctype/builder_project_folder/__init__.py

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright (c) 2024, Frappe Technologies Pvt Ltd and contributors
2+
// For license information, please see license.txt
3+
4+
// frappe.ui.form.on("Builder Project Folder", {
5+
// refresh(frm) {
6+
7+
// },
8+
// });
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"actions": [],
3+
"allow_rename": 1,
4+
"autoname": "field:folder_name",
5+
"creation": "2024-11-26 16:57:02.359763",
6+
"doctype": "DocType",
7+
"engine": "InnoDB",
8+
"field_order": [
9+
"folder_name"
10+
],
11+
"fields": [
12+
{
13+
"fieldname": "folder_name",
14+
"fieldtype": "Data",
15+
"label": "Folder Name",
16+
"unique": 1
17+
}
18+
],
19+
"index_web_pages_for_search": 1,
20+
"links": [],
21+
"modified": "2024-11-26 17:04:15.337801",
22+
"modified_by": "Administrator",
23+
"module": "Builder",
24+
"name": "Builder Project Folder",
25+
"naming_rule": "By fieldname",
26+
"owner": "Administrator",
27+
"permissions": [
28+
{
29+
"create": 1,
30+
"delete": 1,
31+
"email": 1,
32+
"export": 1,
33+
"print": 1,
34+
"read": 1,
35+
"report": 1,
36+
"role": "System Manager",
37+
"share": 1,
38+
"write": 1
39+
},
40+
{
41+
"create": 1,
42+
"delete": 1,
43+
"email": 1,
44+
"export": 1,
45+
"print": 1,
46+
"read": 1,
47+
"report": 1,
48+
"role": "Website Manager",
49+
"share": 1,
50+
"write": 1
51+
}
52+
],
53+
"sort_field": "creation",
54+
"sort_order": "DESC",
55+
"states": []
56+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) 2024, Frappe Technologies Pvt Ltd and contributors
2+
# For license information, please see license.txt
3+
4+
# import frappe
5+
from frappe.model.document import Document
6+
7+
8+
class BuilderProjectFolder(Document):
9+
pass

frontend/src/components/AppsMenu.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
</template>
1515
<template #body>
1616
<div
17-
class="flex w-fit min-w-32 max-w-48 flex-col rounded-lg border border-outline-gray-2 bg-surface-white p-1.5 text-sm text-text-icons-gray-8 shadow-xl auto-fill-[100px] dark:bg-surface-gray-1">
17+
class="flex w-fit min-w-32 max-w-48 flex-col rounded-lg border border-outline-gray-2 bg-surface-white p-1.5 text-sm text-ink-gray-8 shadow-xl auto-fill-[100px] dark:bg-surface-gray-1">
1818
<a
1919
:href="app.route"
2020
v-for="app in apps.data"

frontend/src/components/Controls/BuilderButton.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const classes = computed(() => {
1616
if (props.variant === "solid") {
1717
_classes.push([
1818
"bg-surface-gray-7",
19-
"text-ink-white",
19+
"!text-ink-white",
2020
"hover:bg-surface-gray-6",
2121
"active:bg-surface-gray-5",
2222
]);
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<template>
2+
<section
3+
class="sticky bottom-0 left-0 top-0 flex w-60 flex-col gap-3 bg-surface-gray-1 p-2 shadow-lg max-lg:hidden">
4+
<div class="flex flex-col">
5+
<span
6+
class="flex cursor-pointer gap-2 rounded p-2 text-base text-ink-gray-6"
7+
@click="() => setFolderActive('')"
8+
:class="{ 'bg-surface-modal text-ink-gray-8 shadow-sm dark:bg-surface-gray-2': !store.activeFolder }">
9+
<FilesIcon class="size-4"></FilesIcon>
10+
All Pages
11+
</span>
12+
<span class="flex cursor-pointer gap-2 p-2 text-base text-ink-gray-6" @click="emit('openSettings')">
13+
<SettingsIcon class="size-4"></SettingsIcon>
14+
Settings
15+
</span>
16+
</div>
17+
<div class="flex flex-col">
18+
<div class="flex items-center justify-between p-2 text-base text-ink-gray-6">
19+
<span>Folders</span>
20+
<BuilderButton
21+
variant="subtle"
22+
icon="plus"
23+
class="size-4 cursor-pointer hover:text-ink-gray-8"
24+
@click="showNewFolderDialog = true"></BuilderButton>
25+
</div>
26+
<span
27+
class="flex cursor-pointer gap-2 rounded p-2 text-base text-ink-gray-6"
28+
v-for="project in builderProjectFolder.data"
29+
:class="{
30+
'bg-surface-modal text-ink-gray-8 shadow-sm dark:bg-surface-gray-2': isFolderActive(
31+
project.folder_name,
32+
),
33+
}"
34+
@click="setFolderActive(project.folder_name)">
35+
<FolderIcon class="size-4"></FolderIcon>
36+
{{ project.folder_name }}
37+
</span>
38+
</div>
39+
<NewFolder v-model="showNewFolderDialog"></NewFolder>
40+
</section>
41+
</template>
42+
<script lang="ts" setup>
43+
import FilesIcon from "@/components/Icons/Files.vue";
44+
import FolderIcon from "@/components/Icons/Folder.vue";
45+
import SettingsIcon from "@/components/Icons/SettingsGear.vue";
46+
import NewFolder from "@/components/Modals/NewFolder.vue";
47+
import builderProjectFolder from "@/data/builderProjectFolder";
48+
import useStore from "@/store";
49+
import { ref } from "vue";
50+
51+
const store = useStore();
52+
53+
const emit = defineEmits(["openSettings", "setActiveFolder"]);
54+
55+
const showNewFolderDialog = ref(false);
56+
57+
const isFolderActive = (folderName: string) => {
58+
return store.activeFolder === folderName;
59+
};
60+
const setFolderActive = (folderName: string) => {
61+
store.activeFolder = folderName;
62+
};
63+
</script>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<template>
2+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
3+
<path
4+
fill-rule="evenodd"
5+
clip-rule="evenodd"
6+
d="M8 6.375C9.00102 6.375 9.8125 5.56352 9.8125 4.5625C9.8125 3.56148 9.00102 2.75 8 2.75C6.99898 2.75 6.1875 3.56148 6.1875 4.5625C6.1875 5.56352 6.99898 6.375 8 6.375ZM8 7.375C9.5533 7.375 10.8125 6.1158 10.8125 4.5625C10.8125 3.0092 9.5533 1.75 8 1.75C6.4467 1.75 5.1875 3.0092 5.1875 4.5625C5.1875 6.1158 6.4467 7.375 8 7.375ZM11.9375 13.125C12.9385 13.125 13.75 12.3135 13.75 11.3125C13.75 10.3115 12.9385 9.5 11.9375 9.5C10.9365 9.5 10.125 10.3115 10.125 11.3125C10.125 12.3135 10.9365 13.125 11.9375 13.125ZM11.9375 14.125C13.4908 14.125 14.75 12.8658 14.75 11.3125C14.75 9.7592 13.4908 8.5 11.9375 8.5C10.3842 8.5 9.125 9.7592 9.125 11.3125C9.125 12.8658 10.3842 14.125 11.9375 14.125ZM5.875 11.3125C5.875 12.3135 5.06352 13.125 4.0625 13.125C3.06148 13.125 2.25 12.3135 2.25 11.3125C2.25 10.3115 3.06148 9.5 4.0625 9.5C5.06352 9.5 5.875 10.3115 5.875 11.3125ZM6.875 11.3125C6.875 12.8658 5.6158 14.125 4.0625 14.125C2.5092 14.125 1.25 12.8658 1.25 11.3125C1.25 9.7592 2.5092 8.5 4.0625 8.5C5.6158 8.5 6.875 9.7592 6.875 11.3125Z"
7+
fill="currentColor"
8+
style="fill: currentColor; fill-opacity: 1" />
9+
</svg>
10+
</template>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<template>
2+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
3+
<path
4+
fill-rule="evenodd"
5+
clip-rule="evenodd"
6+
d="M10.023 2.5L8.43184 3.68632C8.25916 3.81507 8.04952 3.88462 7.83412 3.88462H2V12.5C2 13.0523 2.44772 13.5 3 13.5H13C13.5523 13.5 14 13.0523 14 12.5V2.5H10.023ZM9.49179 1.64872C9.6213 1.55216 9.77853 1.5 9.94008 1.5H14.35C14.709 1.5 15 1.79101 15 2.15V12.5C15 13.6046 14.1046 14.5 13 14.5H3C1.89543 14.5 1 13.6046 1 12.5V3.53462C1 3.17563 1.29102 2.88462 1.65 2.88462H7.83412L9.49179 1.64872Z"
7+
fill="currentColor" />
8+
</svg>
9+
</template>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<template>
2+
<Dialog
3+
class="overscroll-none"
4+
v-model="showModel"
5+
:options="{
6+
title: 'Create New Folder',
7+
size: 'sm',
8+
actions: [
9+
{
10+
label: 'Create Folder',
11+
variant: 'solid',
12+
loading: builderProjectFolder.loading,
13+
onClick: createFolder,
14+
},
15+
],
16+
}">
17+
<template #body-content>
18+
<BuilderInput
19+
@input="folderName = $event"
20+
type="Input"
21+
label="Folder Name"
22+
:required="true"></BuilderInput>
23+
</template>
24+
</Dialog>
25+
</template>
26+
<script setup lang="ts">
27+
import builderProjectFolder from "@/data/builderProjectFolder";
28+
import { useVModel } from "@vueuse/core";
29+
import { ref } from "vue";
30+
31+
const folderName = ref("");
32+
const props = defineProps<{
33+
modelValue: boolean;
34+
}>();
35+
const emit = defineEmits(["update:modelValue"]);
36+
37+
const showModel = useVModel(props, "modelValue", emit);
38+
39+
const createFolder = () => {
40+
if (!folderName.value) {
41+
return;
42+
}
43+
builderProjectFolder.insert
44+
.submit({
45+
folder_name: folderName.value,
46+
})
47+
.then(() => {
48+
folderName.value = "";
49+
showModel.value = false;
50+
});
51+
};
52+
</script>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<template>
2+
<Dialog
3+
class="overscroll-none"
4+
v-model="showModel"
5+
:options="{
6+
title: 'Select Folder',
7+
size: 'sm',
8+
}">
9+
<template #body-content>
10+
<span
11+
class="flex gap-2 rounded p-2 text-base text-ink-gray-3"
12+
:class="{
13+
'cursor-pointer text-ink-gray-6 hover:text-ink-gray-9': currentFolder,
14+
}"
15+
@click="folderSelected('')">
16+
<FeatherIcon name="home" class="size-4"></FeatherIcon>
17+
Home
18+
</span>
19+
<span
20+
class="flex cursor-default gap-2 rounded p-2 text-base text-ink-gray-2"
21+
v-for="project in builderProjectFolder.data"
22+
:class="{
23+
'cursor-pointer !text-ink-gray-6 hover:!text-ink-gray-9': currentFolder !== project.folder_name,
24+
}"
25+
@click="folderSelected(project.folder_name)">
26+
<FolderIcon class="size-4"></FolderIcon>
27+
{{ project.folder_name }}
28+
</span>
29+
</template>
30+
</Dialog>
31+
</template>
32+
<script setup lang="ts">
33+
import FolderIcon from "@/components/Icons/Folder.vue";
34+
import builderProjectFolder from "@/data/builderProjectFolder";
35+
import { useVModel } from "@vueuse/core";
36+
37+
const props = defineProps<{
38+
currentFolder: string;
39+
modelValue: boolean;
40+
}>();
41+
42+
const emit = defineEmits(["update:modelValue", "folderSelected"]);
43+
const showModel = useVModel(props, "modelValue", emit);
44+
45+
const folderSelected = (folder: string | null) => {
46+
if (folder === props.currentFolder) {
47+
return;
48+
}
49+
emit("folderSelected", folder);
50+
showModel.value = false;
51+
};
52+
</script>

frontend/src/components/PageCard.vue

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<template>
2+
<router-link :to="{ name: 'builder', params: { pageId: page.page_name } }">
3+
<div
4+
class="group relative flex w-full cursor-pointer flex-col gap-2 rounded-2xl bg-surface-white p-3"
5+
:class="{
6+
'!bg-surface-gray-2': selected,
7+
}">
8+
<img
9+
width="250"
10+
height="140"
11+
:src="page.preview"
12+
onerror="this.src='/assets/builder/images/fallback.png'"
13+
class="w-full overflow-hidden rounded-md object-cover shadow dark:border dark:border-outline-gray-1" />
14+
<div class="flex items-center justify-between border-outline-gray-2">
15+
<span class="inline-block max-w-[160px]">
16+
<div class="flex items-center gap-1">
17+
<p class="truncate text-base font-medium text-ink-gray-7 group-hover:text-ink-gray-9">
18+
{{ page.page_title || page.page_name }}
19+
</p>
20+
</div>
21+
<UseTimeAgo v-slot="{ timeAgo }" :time="page.modified">
22+
<p class="mt-1 block text-sm text-ink-gray-5 group-hover:text-ink-gray-6">Edited {{ timeAgo }}</p>
23+
</UseTimeAgo>
24+
</span>
25+
<Dropdown
26+
:options="[
27+
{
28+
group: 'Actions',
29+
hideLabel: true,
30+
items: [
31+
{ label: 'Duplicate', onClick: () => store.duplicatePage(page), icon: 'copy' },
32+
{
33+
label: 'View in Desk',
34+
onClick: () => store.openInDesk(page),
35+
icon: 'arrow-up-right',
36+
},
37+
],
38+
},
39+
{
40+
group: 'Delete',
41+
hideLabel: true,
42+
items: [{ label: 'Delete', onClick: () => store.deletePage(page), icon: 'trash' }],
43+
},
44+
]"
45+
size="xs"
46+
placement="right">
47+
<template v-slot="{ open }">
48+
<BuilderButton
49+
icon="more-horizontal"
50+
size="sm"
51+
variant="subtle"
52+
class="bg-surface-white !text-ink-gray-5 hover:!text-ink-gray-9"
53+
@click="open"></BuilderButton>
54+
</template>
55+
</Dropdown>
56+
</div>
57+
</div>
58+
</router-link>
59+
</template>
60+
<script setup lang="ts">
61+
import useStore from "@/store";
62+
import { BuilderPage } from "@/types/Builder/BuilderPage";
63+
import { UseTimeAgo } from "@vueuse/components";
64+
import { Dropdown } from "frappe-ui";
65+
66+
const store = useStore();
67+
68+
defineProps<{
69+
page: BuilderPage;
70+
selected: boolean;
71+
}>();
72+
</script>

0 commit comments

Comments
 (0)