adminforth 2.4.0-next.33 → 2.4.0-next.331
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/callTsProxy.js +14 -4
- package/commands/createApp/templates/api.ts.hbs +10 -0
- package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
- package/commands/createApp/templates/index.ts.hbs +12 -1
- package/commands/createApp/templates/package.json.hbs +1 -1
- package/commands/createApp/templates/prisma.config.ts.hbs +8 -0
- package/commands/createApp/templates/schema.prisma.hbs +0 -1
- package/commands/createApp/utils.js +10 -0
- package/commands/createCustomComponent/configLoader.js +17 -4
- package/commands/createCustomComponent/main.js +13 -7
- package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
- package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +28 -0
- package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
- package/commands/createPlugin/templates/package.json.hbs +1 -1
- package/commands/generateModels.js +30 -22
- package/dist/auth.d.ts +9 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +21 -2
- package/dist/auth.js.map +1 -1
- package/dist/dataConnectors/baseConnector.d.ts +1 -1
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
- package/dist/dataConnectors/baseConnector.js +70 -18
- package/dist/dataConnectors/baseConnector.js.map +1 -1
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +15 -0
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +50 -15
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/dataConnectors/mysql.d.ts.map +1 -1
- package/dist/dataConnectors/mysql.js +11 -0
- package/dist/dataConnectors/mysql.js.map +1 -1
- package/dist/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +43 -14
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/dataConnectors/sqlite.d.ts.map +1 -1
- package/dist/dataConnectors/sqlite.js +11 -0
- package/dist/dataConnectors/sqlite.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -21
- package/dist/index.js.map +1 -1
- package/dist/modules/codeInjector.d.ts +2 -0
- package/dist/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +62 -6
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/configValidator.d.ts +6 -0
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +209 -25
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +199 -31
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/styles.d.ts +499 -13
- package/dist/modules/styles.d.ts.map +1 -1
- package/dist/modules/styles.js +555 -31
- package/dist/modules/styles.js.map +1 -1
- package/dist/modules/utils.d.ts +7 -15
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +45 -68
- package/dist/modules/utils.js.map +1 -1
- package/dist/servers/express.d.ts +5 -0
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +40 -1
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/index.html +1 -1
- package/dist/spa/package-lock.json +1208 -708
- package/dist/spa/package.json +34 -34
- package/dist/spa/src/App.vue +132 -174
- package/dist/spa/src/adminforth.ts +41 -17
- package/dist/spa/src/afcl/AreaChart.vue +0 -1
- package/dist/spa/src/afcl/BarChart.vue +2 -2
- package/dist/spa/src/afcl/Button.vue +3 -3
- package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
- package/dist/spa/src/afcl/Card.vue +25 -0
- package/dist/spa/src/afcl/Checkbox.vue +21 -13
- package/dist/spa/src/afcl/CountryFlag.vue +4 -1
- package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
- package/dist/spa/src/afcl/Dialog.vue +47 -27
- package/dist/spa/src/afcl/Dropzone.vue +145 -48
- package/dist/spa/src/afcl/Input.vue +14 -6
- package/dist/spa/src/afcl/JsonViewer.vue +25 -0
- package/dist/spa/src/afcl/LinkButton.vue +3 -3
- package/dist/spa/src/afcl/PieChart.vue +5 -5
- package/dist/spa/src/afcl/ProgressBar.vue +7 -7
- package/dist/spa/src/afcl/Select.vue +82 -34
- package/dist/spa/src/afcl/Skeleton.vue +6 -6
- package/dist/spa/src/afcl/Table.vue +313 -75
- package/dist/spa/src/afcl/Textarea.vue +31 -0
- package/dist/spa/src/afcl/Toggle.vue +32 -0
- package/dist/spa/src/afcl/Tooltip.vue +28 -18
- package/dist/spa/src/afcl/VerticalTabs.vue +21 -7
- package/dist/spa/src/afcl/index.ts +6 -3
- package/dist/spa/src/components/AcceptModal.vue +48 -14
- package/dist/spa/src/components/Breadcrumbs.vue +5 -5
- package/dist/spa/src/components/CallActionWrapper.vue +15 -0
- package/dist/spa/src/components/ColumnValueInput.vue +38 -18
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
- package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
- package/dist/spa/src/components/CustomRangePicker.vue +37 -21
- package/dist/spa/src/components/ErrorMessage.vue +21 -0
- package/dist/spa/src/components/Filters.vue +195 -132
- package/dist/spa/src/components/GroupsTable.vue +9 -8
- package/dist/spa/src/components/MenuLink.vue +95 -23
- package/dist/spa/src/components/ResourceForm.vue +99 -51
- package/dist/spa/src/components/ResourceListTable.vue +121 -95
- package/dist/spa/src/components/ResourceListTableVirtual.vue +119 -88
- package/dist/spa/src/components/ShowTable.vue +21 -15
- package/dist/spa/src/components/Sidebar.vue +472 -0
- package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
- package/dist/spa/src/components/SkeleteLoader.vue +3 -3
- package/dist/spa/src/components/ThreeDotsMenu.vue +84 -15
- package/dist/spa/src/components/Toast.vue +40 -29
- package/dist/spa/src/components/UserMenuSettingsButton.vue +69 -0
- package/dist/spa/src/components/ValueRenderer.vue +44 -17
- package/dist/spa/src/controls/BoolToggle.vue +34 -0
- package/dist/spa/src/i18n.ts +5 -3
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/renderers/CompactField.vue +1 -1
- package/dist/spa/src/renderers/CompactUUID.vue +1 -1
- package/dist/spa/src/router/index.ts +8 -0
- package/dist/spa/src/shims-vue.d.ts +5 -0
- package/dist/spa/src/spa_types/core.ts +13 -1
- package/dist/spa/src/stores/core.ts +15 -1
- package/dist/spa/src/stores/filters.ts +33 -2
- package/dist/spa/src/stores/modal.ts +6 -1
- package/dist/spa/src/stores/toast.ts +22 -3
- package/dist/spa/src/types/Back.ts +168 -23
- package/dist/spa/src/types/Common.ts +109 -32
- package/dist/spa/src/types/FrontendAPI.ts +32 -23
- package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
- package/dist/spa/src/types/adapters/EmailAdapter.ts +2 -4
- package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
- package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
- package/dist/spa/src/types/adapters/StorageAdapter.ts +4 -2
- package/dist/spa/src/types/adapters/index.ts +3 -0
- package/dist/spa/src/utils.ts +291 -11
- package/dist/spa/src/views/CreateView.vue +88 -22
- package/dist/spa/src/views/EditView.vue +55 -22
- package/dist/spa/src/views/ListView.vue +144 -87
- package/dist/spa/src/views/LoginView.vue +26 -35
- package/dist/spa/src/views/ResourceParent.vue +2 -2
- package/dist/spa/src/views/SettingsView.vue +121 -0
- package/dist/spa/src/views/ShowView.vue +83 -53
- package/dist/spa/src/websocket.ts +6 -1
- package/dist/spa/tsconfig.app.json +1 -1
- package/dist/spa/vite.config.ts +45 -2
- package/dist/types/Back.d.ts +151 -14
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js +15 -0
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +123 -29
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +32 -18
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
- package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
- package/dist/types/adapters/CaptchaAdapter.js +5 -0
- package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
- package/dist/types/adapters/EmailAdapter.d.ts +2 -3
- package/dist/types/adapters/EmailAdapter.d.ts.map +1 -1
- package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
- package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
- package/dist/types/adapters/ImageVisionAdapter.js +2 -0
- package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
- package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
- package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
- package/dist/types/adapters/KeyValueAdapter.js +2 -0
- package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
- package/dist/types/adapters/StorageAdapter.d.ts +2 -0
- package/dist/types/adapters/StorageAdapter.d.ts.map +1 -1
- package/dist/types/adapters/index.d.ts +3 -0
- package/dist/types/adapters/index.d.ts.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<aside
|
|
3
|
+
ref="sidebarAside"
|
|
4
|
+
@mouseover="!isTogglingSidebar && (isSidebarHovering = true)"
|
|
5
|
+
@mouseleave="!isTogglingSidebar && (isSidebarHovering = false)"
|
|
6
|
+
id="logo-lightSidebar"
|
|
7
|
+
class="sidebar-container fixed border-none top-0 left-0 z-40 h-screen transition-all duration-300 ease-in-out bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder sm:translate-x-0 dark:border-darkSidebarBorder"
|
|
8
|
+
:class="{
|
|
9
|
+
'-translate-x-full': !sideBarOpen,
|
|
10
|
+
'transform-none': sideBarOpen,
|
|
11
|
+
'sidebar-collapsed': iconOnlySidebarEnabled && isSidebarIconOnly && !isSidebarHovering,
|
|
12
|
+
'sidebar-expanded': !iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)
|
|
13
|
+
}"
|
|
14
|
+
aria-label="Sidebar"
|
|
15
|
+
>
|
|
16
|
+
<div class="h-full px-3 pb-20 md:pb-4 bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder dark:border-darkSidebarBorder pt-4" :class="{'sidebar-scroll':!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)}">
|
|
17
|
+
<div class="af-logo-title-wrapper flex relative transition-all duration-300 ease-in-out h-8 items-center" :class="{'mb-4': isSidebarIconOnly && !isSidebarHovering, 'mx-4 mb-4': !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)}">
|
|
18
|
+
<img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-logo h-8 me-3" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))) }" />
|
|
19
|
+
<img :src="loadFile(coreStore.config?.iconOnlySidebar?.logo || '')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-sidebar-icon-only-logo h-8 me-3" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && coreStore.config?.iconOnlySidebar?.logo && iconOnlySidebarEnabled && isSidebarIconOnly && !isSidebarHovering) }" />
|
|
20
|
+
<span
|
|
21
|
+
v-if="coreStore.config?.showBrandNameInSidebar && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))"
|
|
22
|
+
class="af-title self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText"
|
|
23
|
+
>
|
|
24
|
+
{{ coreStore.config?.brandName }}
|
|
25
|
+
</span>
|
|
26
|
+
<div v-if="!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)" class="flex items-center gap-2 w-auto" :class="{'w-full justify-end': coreStore.config?.showBrandLogoInSidebar === false}">
|
|
27
|
+
<component
|
|
28
|
+
v-for="c in coreStore?.config?.globalInjections?.sidebarTop || []"
|
|
29
|
+
:is="getCustomComponent(c)"
|
|
30
|
+
:meta="c.meta"
|
|
31
|
+
:adminUser="coreStore.adminUser"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="absolute top-1.5 -right-4 z-10 hidden sm:block" v-if="!forceIconOnly && iconOnlySidebarEnabled && (!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))">
|
|
35
|
+
<button class="text-sm text-lightSidebarIcons group-hover:text-lightSidebarIconsHover dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" @click="toggleSidebar">
|
|
36
|
+
<IconCloseSidebarSolid v-if="!isSidebarIconOnly" class="w-5 h-5 active:scale-95 transition-all duration-200 hover:text-lightSidebarIconsHover dark:hover:text-darkSidebarIconsHover" />
|
|
37
|
+
<IconOpenSidebarSolid v-else class="w-5 h-5 active:scale-95 transition-all duration-200 hover:text-lightSidebarIconsHover dark:hover:text-darkSidebarIconsHover" />
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<ul class="af-sidebar-container space-y-2 font-medium" >
|
|
43
|
+
<template v-if="!iconOnlySidebarEnabled || !isSidebarIconOnly" v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
|
|
44
|
+
<div v-if="item.type === 'divider'" class="border-t border-lightSidebarDevider dark:border-darkSidebarDevider"></div>
|
|
45
|
+
<div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
|
|
46
|
+
<div v-else-if="item.type === 'heading'" class="flex items-center justify-left pl-2 h-8 text-lightSidebarHeading dark:text-darkSidebarHeading
|
|
47
|
+
">{{ item.label }}</div>
|
|
48
|
+
<li v-else-if="item.children" class="af-sidebar-expand-container">
|
|
49
|
+
<button @click="clickOnMenuItem(i)" type="button" class="af-sidebar-expand-button flex items-center w-full px-3.5 py-2 text-base text-lightSidebarText rounded-default transition duration-75 group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarHover dark:hover:text-darkSidebarTextHover"
|
|
50
|
+
:class="opened.includes(i) ? 'af-sidebar-dropdown-expanded' : 'af-sidebar-dropdown-collapsed'"
|
|
51
|
+
:aria-controls="`dropdown-example${i}`"
|
|
52
|
+
:data-collapse-toggle="`dropdown-example${i}`"
|
|
53
|
+
>
|
|
54
|
+
<component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-lightSidebarIcons group-hover:text-lightSidebarIconsHover transition duration-75 dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" ></component>
|
|
55
|
+
<span class="overflow-hidden flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}
|
|
56
|
+
<span v-if="item.badge" class="inline-flex items-center justify-center w-3 h-3 p-3 ms-3 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
|
|
57
|
+
fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent">
|
|
58
|
+
<Tooltip v-if="item.badgeTooltip">
|
|
59
|
+
{{ item.badge }}
|
|
60
|
+
<template #tooltip>
|
|
61
|
+
{{ item.badgeTooltip }}
|
|
62
|
+
</template>
|
|
63
|
+
</Tooltip>
|
|
64
|
+
<template v-else>
|
|
65
|
+
{{ item.badge }}
|
|
66
|
+
</template>
|
|
67
|
+
</span>
|
|
68
|
+
</span>
|
|
69
|
+
|
|
70
|
+
<svg :class="{'rotate-180': opened.includes(i) }" class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
|
71
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
|
72
|
+
</svg>
|
|
73
|
+
</button>
|
|
74
|
+
|
|
75
|
+
<ul :id="`dropdown-example${i}`" role="none" class="af-sidebar-dropdown pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i) }">
|
|
76
|
+
<template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
|
|
77
|
+
<li class="af-sidebar-menu-link">
|
|
78
|
+
<MenuLink :item="child" isChild="true" @click="$emit('hideSidebar')"/>
|
|
79
|
+
</li>
|
|
80
|
+
</template>
|
|
81
|
+
</ul>
|
|
82
|
+
</li>
|
|
83
|
+
<li v-else class="af-sidebar-menu-link">
|
|
84
|
+
<MenuLink :item="item" @click="$emit('hideSidebar')"/>
|
|
85
|
+
</li>
|
|
86
|
+
</template>
|
|
87
|
+
<template v-if="iconOnlySidebarEnabled && isSidebarIconOnly" v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
|
|
88
|
+
<div v-if="item.type === 'divider'" class="border-t border-lightSidebarDevider dark:border-darkSidebarDevider"></div>
|
|
89
|
+
<div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
|
|
90
|
+
<div v-else-if="item.type === 'heading' && isSidebarHovering" class="flex items-center justify-left pl-2 h-8 text-lightSidebarHeading dark:text-darkSidebarHeading
|
|
91
|
+
">{{ item.label }}</div>
|
|
92
|
+
<div v-else-if="item.type === 'heading' && !isSidebarHovering" class="opacity-0 w-1 h-8">{{ item.label }}</div>
|
|
93
|
+
<li v-else-if="item.children" class="af-sidebar-expand-container">
|
|
94
|
+
<button @click="clickOnMenuItem(i)" type="button" class="af-sidebar-expand-button relative flex items-center h-10 w-full px-3.5 py-2 text-base text-lightSidebarText rounded-default group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarHover dark:hover:text-darkSidebarTextHover"
|
|
95
|
+
:class="opened.includes(i) ? 'af-sidebar-dropdown-expanded' : 'af-sidebar-dropdown-collapsed'"
|
|
96
|
+
:aria-controls="`dropdown-example${i}`"
|
|
97
|
+
:data-collapse-toggle="`dropdown-example${i}`"
|
|
98
|
+
>
|
|
99
|
+
<component v-if="item.icon" :is="getIcon(item.icon)" class="min-w-5 min-h-5 text-lightSidebarIcons group-hover:text-lightSidebarIconsHover transition duration-75 dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" ></component>
|
|
100
|
+
|
|
101
|
+
<span
|
|
102
|
+
class="overflow-hidden block ms-3 text-left rtl:text-right transition-all duration-200 ease-in-out"
|
|
103
|
+
:class="{
|
|
104
|
+
'opacity-0 ms-0 translate-x-4 flex-none': isSidebarIconOnly && !isSidebarHovering,
|
|
105
|
+
'opacity-100 ms-3 translate-x-0 flex-none': isSidebarIconOnly && isSidebarHovering,
|
|
106
|
+
'opacity-100 ms-3 translate-x-0 flex-1': !isSidebarIconOnly
|
|
107
|
+
}"
|
|
108
|
+
:style="isSidebarIconOnly ? {
|
|
109
|
+
minWidth: `calc(${expandedWidth} - 0.75rem*2 - 0.875rem*2 - 1.25rem - 0.75rem)`,
|
|
110
|
+
width: `calc(${expandedWidth} - 0.75rem*2 - 0.875rem*2 - 1.25rem - 0.75rem)`
|
|
111
|
+
} : {}"
|
|
112
|
+
>{{ item.label }}
|
|
113
|
+
|
|
114
|
+
<span v-if="item.badge" class="inline-flex items-center justify-center w-3 h-3 p-3 ms-3 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
|
|
115
|
+
fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent">
|
|
116
|
+
<Tooltip v-if="item.badgeTooltip">
|
|
117
|
+
{{ item.badge }}
|
|
118
|
+
<template #tooltip>
|
|
119
|
+
{{ item.badgeTooltip }}
|
|
120
|
+
</template>
|
|
121
|
+
</Tooltip>
|
|
122
|
+
<template v-else>
|
|
123
|
+
{{ item.badge }}
|
|
124
|
+
</template>
|
|
125
|
+
</span>
|
|
126
|
+
</span>
|
|
127
|
+
|
|
128
|
+
<svg :class="{'rotate-180': opened.includes(i) }" class="w-3 h-3 ml-2 absolute right-3.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
|
129
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
|
130
|
+
</svg>
|
|
131
|
+
</button>
|
|
132
|
+
|
|
133
|
+
<ul :id="`dropdown-example${i}`" role="none" class="af-sidebar-dropdown pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i), 'relative after:absolute after:-left-3 after:inset-y-0 after:w-0.5 after:bg-lightSidebarIcons/50 dark:after:bg-darkSidebarIcons/50 after:rounded-full': isSidebarIconOnly && !isSidebarHovering && opened.includes(i) }">
|
|
134
|
+
<template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
|
|
135
|
+
<li class="af-sidebar-menu-link">
|
|
136
|
+
<MenuLink :item="child" isChild="true" @click="$emit('hideSidebar')" :isSidebarIconOnly="isSidebarIconOnly" :isSidebarHovering="isSidebarHovering"/>
|
|
137
|
+
</li>
|
|
138
|
+
</template>
|
|
139
|
+
</ul>
|
|
140
|
+
</li>
|
|
141
|
+
<li v-else class="af-sidebar-menu-link">
|
|
142
|
+
<MenuLink :item="item" @click="$emit('hideSidebar')" :isSidebarIconOnly="isSidebarIconOnly" :isSidebarHovering="isSidebarHovering"/>
|
|
143
|
+
</li>
|
|
144
|
+
</template>
|
|
145
|
+
</ul>
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
<div id="dropdown-cta" class="p-4 mt-6 w-[230px] rounded-lg bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
|
|
149
|
+
fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent text-sm" role="alert"
|
|
150
|
+
v-if="(ctaBadge && !isSidebarIconOnly) || (ctaBadge && isSidebarIconOnly && isSidebarHovering)"
|
|
151
|
+
>
|
|
152
|
+
<div class="flex items-center mb-3" :class="!ctaBadge.title ? 'float-right' : ''">
|
|
153
|
+
<!-- <span class="bg-lightPrimaryOpacity dark:bg-darkPrimaryOpacity text-sm font-semibold me-2 px-2.5 py-0.5 rounded "
|
|
154
|
+
v-if="ctaBadge.title"
|
|
155
|
+
> -->
|
|
156
|
+
<span>
|
|
157
|
+
{{ctaBadge.title}}
|
|
158
|
+
</span>
|
|
159
|
+
<button type="button"
|
|
160
|
+
class="ms-auto -mx-1.5 -my-1.5 bg-lightPrimaryOpacity dark:bg-darkPrimaryOpacity inline-flex justify-center items-center w-6 h-6 rounded-lg p-1 hover:brightness-110"
|
|
161
|
+
|
|
162
|
+
data-dismiss-target="#dropdown-cta" aria-label="Close"
|
|
163
|
+
v-if="ctaBadge?.closable" @click="closeCTA"
|
|
164
|
+
>
|
|
165
|
+
<span class="sr-only">Close</span>
|
|
166
|
+
<svg class="w-2.5 h-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
167
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
168
|
+
</svg>
|
|
169
|
+
</button>
|
|
170
|
+
</div>
|
|
171
|
+
<p class="mb-3 text-sm " v-if="ctaBadge.html" v-html="ctaBadge.html"></p>
|
|
172
|
+
<p class="mb-3 text-sm fill-lightNavbarText dark:fill-darkPrimary text-lightNavbarText dark:text-darkNavbarPrimary" v-else>
|
|
173
|
+
{{ ctaBadge.text }}
|
|
174
|
+
</p>
|
|
175
|
+
<!-- <a class="text-sm text-lightPrimary underline font-medium hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300" href="#">Turn new navigation off</a> -->
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<component
|
|
179
|
+
v-for="c in coreStore?.config?.globalInjections?.sidebar || []"
|
|
180
|
+
:is="getCustomComponent(c)"
|
|
181
|
+
:meta="c.meta"
|
|
182
|
+
:adminUser="coreStore.adminUser"
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
</aside>
|
|
186
|
+
</template>
|
|
187
|
+
|
|
188
|
+
<style lang="scss" scoped>
|
|
189
|
+
/* Sidebar width animations */
|
|
190
|
+
.sidebar-container {
|
|
191
|
+
width: v-bind(expandedWidth); /* Default expanded width (w-64) */
|
|
192
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
193
|
+
overflow: hidden; /* Prevent content from showing during animation */
|
|
194
|
+
will-change: width, transform;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.sidebar-collapsed {
|
|
198
|
+
width: 4.5rem; /* Collapsed width (w-18) */
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.sidebar-expanded {
|
|
202
|
+
width: v-bind(expandedWidth); /* Expanded width (w-64) */
|
|
203
|
+
box-shadow: 3px 0px 12px -2px rgba(0, 0, 0, 0.15);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
:deep(.dark) .sidebar-collapsed {
|
|
207
|
+
box-shadow: 12px 0px 18px -8px rgba(0, 0, 0, 0.45);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Text visibility transitions */
|
|
211
|
+
.sidebar-collapsed .af-title {
|
|
212
|
+
opacity: 0;
|
|
213
|
+
transform: translateX(-12px);
|
|
214
|
+
transition: opacity 0.2s ease-out, transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
215
|
+
}
|
|
216
|
+
.sidebar-collapsed svg.w-3 {
|
|
217
|
+
opacity: 0;
|
|
218
|
+
transition: opacity 0.2s ease-out;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.sidebar-expanded .af-title {
|
|
222
|
+
opacity: 1;
|
|
223
|
+
transform: translateX(0);
|
|
224
|
+
transition: opacity 0.25s ease-in 0.1s, transform 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0.05s;
|
|
225
|
+
}
|
|
226
|
+
.sidebar-expanded svg.w-3 {
|
|
227
|
+
opacity: 1;
|
|
228
|
+
transition: opacity 0.25s ease-in 0.1s;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* Overlay scrollbar styling */
|
|
232
|
+
.sidebar-scroll {
|
|
233
|
+
overflow-y: auto;
|
|
234
|
+
overflow-x: hidden;
|
|
235
|
+
scrollbar-width: thin;
|
|
236
|
+
scrollbar-color: transparent transparent;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Webkit overlay scrollbar */
|
|
240
|
+
.sidebar-scroll::-webkit-scrollbar {
|
|
241
|
+
width: 8px;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.sidebar-scroll::-webkit-scrollbar-track {
|
|
245
|
+
background: transparent;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.sidebar-scroll::-webkit-scrollbar-thumb {
|
|
249
|
+
background-color: transparent;
|
|
250
|
+
border-radius: 4px;
|
|
251
|
+
transition: background-color 0.2s ease;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* Show scrollbar on hover/scroll */
|
|
255
|
+
.sidebar-scroll:hover::-webkit-scrollbar-thumb,
|
|
256
|
+
.sidebar-scroll:active::-webkit-scrollbar-thumb {
|
|
257
|
+
background-color: rgba(156, 163, 175, 0.4);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.sidebar-scroll:hover {
|
|
261
|
+
scrollbar-color: rgba(156, 163, 175, 0.4) transparent;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Dark mode scrollbar */
|
|
265
|
+
.dark .sidebar-scroll:hover {
|
|
266
|
+
scrollbar-color: rgba(75, 85, 99, 0.4) transparent;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.dark .sidebar-scroll:hover::-webkit-scrollbar-thumb,
|
|
270
|
+
.dark .sidebar-scroll:active::-webkit-scrollbar-thumb {
|
|
271
|
+
background-color: rgba(75, 85, 99, 0.4);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* For browsers that support overlay scrollbars natively */
|
|
275
|
+
@supports (overflow: overlay) {
|
|
276
|
+
.sidebar-scroll {
|
|
277
|
+
overflow-y: overlay;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
</style>
|
|
281
|
+
|
|
282
|
+
<script setup lang="ts">
|
|
283
|
+
import { computed, ref, watch, nextTick, onMounted, onUnmounted, type Ref } from 'vue';
|
|
284
|
+
import { useCoreStore } from '@/stores/core';
|
|
285
|
+
import MenuLink from './MenuLink.vue';
|
|
286
|
+
import { IconCloseSidebarSolid, IconOpenSidebarSolid } from '@iconify-prerendered/vue-flowbite';
|
|
287
|
+
import { getIcon, verySimpleHash, loadFile, getCustomComponent } from '@/utils';
|
|
288
|
+
import { Tooltip } from '@/afcl';
|
|
289
|
+
import type { AnnouncementBadgeResponse } from '@/types/Common';
|
|
290
|
+
import adminforth from '@/adminforth';
|
|
291
|
+
|
|
292
|
+
interface Props {
|
|
293
|
+
sideBarOpen: boolean;
|
|
294
|
+
forceIconOnly?: boolean;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const props = defineProps<Props>();
|
|
298
|
+
|
|
299
|
+
const emit = defineEmits<{
|
|
300
|
+
hideSidebar: [];
|
|
301
|
+
loadMenu: [];
|
|
302
|
+
sidebarStateChange: [{ isSidebarIconOnly: boolean; isSidebarHovering: boolean }];
|
|
303
|
+
}>();
|
|
304
|
+
|
|
305
|
+
const coreStore = useCoreStore();
|
|
306
|
+
|
|
307
|
+
//create a ref to store the opened menu items with ts type;
|
|
308
|
+
const opened = ref<(string|number)[]>([]);
|
|
309
|
+
const sidebarAside = ref(null);
|
|
310
|
+
|
|
311
|
+
const smQuery = window.matchMedia('(min-width: 640px)');
|
|
312
|
+
const isMobile = ref(!smQuery.matches);
|
|
313
|
+
const iconOnlySidebarEnabled = computed(() => props.forceIconOnly === true || coreStore.config?.iconOnlySidebar?.enabled !== false);
|
|
314
|
+
const isSidebarIconOnly = ref(false);
|
|
315
|
+
|
|
316
|
+
const expandedWidth = computed(() => coreStore.config?.iconOnlySidebar?.expandedSidebarWidth || '16.5rem');
|
|
317
|
+
|
|
318
|
+
function handleBreakpointChange(e: MediaQueryListEvent) {
|
|
319
|
+
isMobile.value = !e.matches;
|
|
320
|
+
if (isMobile.value) {
|
|
321
|
+
isSidebarIconOnly.value = false;
|
|
322
|
+
} else {
|
|
323
|
+
if (props.forceIconOnly === true) {
|
|
324
|
+
isSidebarIconOnly.value = true;
|
|
325
|
+
} else if (iconOnlySidebarEnabled.value && localStorage.getItem('afIconOnlySidebar') === 'true') {
|
|
326
|
+
isSidebarIconOnly.value = true;
|
|
327
|
+
} else {
|
|
328
|
+
isSidebarIconOnly.value = false;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
smQuery.addEventListener('change', handleBreakpointChange);
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
const isSidebarHovering = ref(false);
|
|
337
|
+
const isTogglingSidebar = ref(false);
|
|
338
|
+
|
|
339
|
+
function toggleSidebar() {
|
|
340
|
+
if (props.forceIconOnly) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (!iconOnlySidebarEnabled.value) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (isMobile.value) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
isTogglingSidebar.value = true;
|
|
350
|
+
isSidebarIconOnly.value = !isSidebarIconOnly.value;
|
|
351
|
+
if (isSidebarIconOnly.value) {
|
|
352
|
+
isSidebarHovering.value = false;
|
|
353
|
+
}
|
|
354
|
+
setTimeout(() => {
|
|
355
|
+
isTogglingSidebar.value = false;
|
|
356
|
+
}, 100);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function clickOnMenuItem(label: string | number) {
|
|
360
|
+
if (opened.value.includes(label)) {
|
|
361
|
+
opened.value = opened.value.filter((item) => item !== label);
|
|
362
|
+
} else {
|
|
363
|
+
opened.value.push(label);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
watch(()=>coreStore.menu, () => {
|
|
368
|
+
coreStore.menu.forEach((item, i) => {
|
|
369
|
+
if (item.open) {
|
|
370
|
+
opened.value.push(i);
|
|
371
|
+
};
|
|
372
|
+
});
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
watch(isSidebarIconOnly, (isIconOnly) => {
|
|
378
|
+
if (!isMobile.value && iconOnlySidebarEnabled.value && !props.forceIconOnly) {
|
|
379
|
+
localStorage.setItem('afIconOnlySidebar', isIconOnly.toString());
|
|
380
|
+
}
|
|
381
|
+
emit('sidebarStateChange', { isSidebarIconOnly: isIconOnly, isSidebarHovering: isSidebarHovering.value });
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
watch(isSidebarHovering, (hovering) => {
|
|
385
|
+
emit('sidebarStateChange', { isSidebarIconOnly: isSidebarIconOnly.value, isSidebarHovering: hovering });
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
watch(sidebarAside, (sidebarAside) => {
|
|
389
|
+
if (sidebarAside) {
|
|
390
|
+
coreStore.fetchMenuBadges();
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
const ctaBadge: Ref<(AnnouncementBadgeResponse & { hash: string; }) | null> = computed(() => {
|
|
395
|
+
const badge = coreStore.config?.announcementBadge;
|
|
396
|
+
if (!badge) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const hash = badge.closable ? verySimpleHash(JSON.stringify(badge)) : '';
|
|
400
|
+
if (badge.closable && window.localStorage.getItem(`ctaBadge-${hash}`)) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
return {...badge, hash};
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
function closeCTA() {
|
|
407
|
+
if (!ctaBadge.value) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const hash = ctaBadge.value.hash;
|
|
411
|
+
window.localStorage.setItem(`ctaBadge-${hash}`, '1');
|
|
412
|
+
nextTick( async() => {
|
|
413
|
+
emit('loadMenu');
|
|
414
|
+
await coreStore.fetchMenuBadges();
|
|
415
|
+
adminforth.menu.refreshMenuBadges();
|
|
416
|
+
})
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
onMounted(() => {
|
|
420
|
+
if (!iconOnlySidebarEnabled.value) {
|
|
421
|
+
isSidebarIconOnly.value = false;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
coreStore.menu.forEach((item, i) => {
|
|
425
|
+
if (item.open) {
|
|
426
|
+
opened.value.push(i);
|
|
427
|
+
};
|
|
428
|
+
});
|
|
429
|
+
// Emit initial state
|
|
430
|
+
emit('sidebarStateChange', { isSidebarIconOnly: isSidebarIconOnly.value, isSidebarHovering: isSidebarHovering.value });
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
onUnmounted(() => {
|
|
434
|
+
smQuery.removeEventListener('change', handleBreakpointChange);
|
|
435
|
+
if (isMobile.value && props.sideBarOpen) {
|
|
436
|
+
document.body.style.overflow = '';
|
|
437
|
+
document.body.style.position = '';
|
|
438
|
+
document.body.style.width = '';
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
watch(() => props.forceIconOnly, (force) => {
|
|
443
|
+
if (isMobile.value) {
|
|
444
|
+
isSidebarIconOnly.value = false;
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (props.forceIconOnly === true) {
|
|
448
|
+
isSidebarIconOnly.value = true;
|
|
449
|
+
} else if (iconOnlySidebarEnabled.value && localStorage.getItem('afIconOnlySidebar') === 'true') {
|
|
450
|
+
isSidebarIconOnly.value = true;
|
|
451
|
+
} else {
|
|
452
|
+
isSidebarIconOnly.value = false;
|
|
453
|
+
}
|
|
454
|
+
}, { immediate: true })
|
|
455
|
+
|
|
456
|
+
watch(() => props.sideBarOpen, (isOpen) => {
|
|
457
|
+
if (isMobile.value) {
|
|
458
|
+
if (isOpen) {
|
|
459
|
+
// Lock body scroll
|
|
460
|
+
document.body.style.overflow = 'hidden';
|
|
461
|
+
document.body.style.position = 'fixed';
|
|
462
|
+
document.body.style.width = '100%';
|
|
463
|
+
} else {
|
|
464
|
+
// Unlock body scroll
|
|
465
|
+
document.body.style.overflow = '';
|
|
466
|
+
document.body.style.position = '';
|
|
467
|
+
document.body.style.width = '';
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}, { immediate: true })
|
|
471
|
+
|
|
472
|
+
</script>
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
<div
|
|
3
3
|
role="status" class="max-w-sm animate-pulse"
|
|
4
4
|
>
|
|
5
|
-
<div class="h-2.5 bg-
|
|
6
|
-
<div class="h-2 bg-
|
|
7
|
-
<div class="h-2 bg-
|
|
8
|
-
<div class="h-2 bg-
|
|
9
|
-
<div class="h-2 bg-
|
|
10
|
-
<div class="h-2 bg-
|
|
5
|
+
<div class="h-2.5 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor w-48 mb-4"></div>
|
|
6
|
+
<div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[360px] mb-2.5"></div>
|
|
7
|
+
<div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor mb-2.5"></div>
|
|
8
|
+
<div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[330px] mb-2.5"></div>
|
|
9
|
+
<div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[300px] mb-2.5"></div>
|
|
10
|
+
<div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[360px]"></div>
|
|
11
11
|
<span class="sr-only">{{ $t('Loading...') }}</span>
|
|
12
12
|
</div>
|
|
13
13
|
</template>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
: '']"
|
|
13
13
|
>
|
|
14
14
|
<div role="status" class="max-w-sm animate-pulse">
|
|
15
|
-
<div class="h-2 bg-
|
|
15
|
+
<div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[360px]"></div>
|
|
16
16
|
</div>
|
|
17
17
|
</td>
|
|
18
18
|
</tr>
|
|
@@ -26,8 +26,8 @@ const props = withDefaults(defineProps<{
|
|
|
26
26
|
rowHeights?: number[];
|
|
27
27
|
columnWidths?: number[];
|
|
28
28
|
}>(), {
|
|
29
|
-
rowHeights: [],
|
|
30
|
-
columnWidths: [],
|
|
29
|
+
rowHeights: () => [],
|
|
30
|
+
columnWidths: () => [],
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
</script>
|