@opengis/cms 0.0.2 → 0.0.3
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/.gitlab-ci.yml +36 -0
- package/config.example +21 -0
- package/docs/.vitepress/abbr.mjs +26 -0
- package/docs/.vitepress/config.mjs +119 -0
- package/docs/.vitepress/navigation.mjs +82 -0
- package/docs/.vitepress/theme/Layout.vue +17 -0
- package/docs/.vitepress/theme/components/NavigationLinks.vue +102 -0
- package/docs/.vitepress/theme/components/Panzoom.vue +169 -0
- package/docs/.vitepress/theme/index.mjs +15 -0
- package/docs/.vitepress/theme/style.scss +136 -0
- package/docs/abbr.json +4 -0
- package/docs/api/builder/cms.builder.delete.md +65 -0
- package/docs/api/builder/cms.builder.get.md +70 -0
- package/docs/api/builder/cms.builder.list.md +98 -0
- package/docs/api/builder/cms.builder.post.md +72 -0
- package/docs/api/builder/cms.builder.put.md +88 -0
- package/docs/api/category/cms.category.delete.md +60 -0
- package/docs/api/category/cms.category.get.md +61 -0
- package/docs/api/category/cms.category.list.md +77 -0
- package/docs/api/category/cms.category.post.md +62 -0
- package/docs/api/category/cms.category.put.md +78 -0
- package/docs/api/index.md +50 -0
- package/docs/api/manager/cms.manager.delete.md +64 -0
- package/docs/api/manager/cms.manager.get.md +72 -0
- package/docs/api/manager/cms.manager.list.md +96 -0
- package/docs/api/manager/cms.manager.post.md +70 -0
- package/docs/api/manager/cms.manager.put.md +86 -0
- package/docs/api/media/del.md +64 -0
- package/docs/api/media/edit.md +92 -0
- package/docs/api/media/list.md +70 -0
- package/docs/api/media/metadata.md +57 -0
- package/docs/api/media/preview.md +33 -0
- package/docs/api/media/upload.md +84 -0
- package/docs/db/erd.md +173 -0
- package/docs/db/index.md +7 -0
- package/docs/index.md +39 -0
- package/docs/public/logo-dark.svg +24 -0
- package/docs/public/logo-light.svg +24 -0
- package/docs/public/logo-short.svg +15 -0
- package/docs/public/logo.svg +19 -0
- package/docs/readme/index.md +6 -0
- package/docs/src/vs-button.vue +157 -0
- package/docs/vue/basic/button.md +144 -0
- package/docs/vue/index.md +9 -0
- package/index.html +14 -0
- package/package.json +2 -5
- package/server/app.js +25 -0
- package/server/config.js +5 -0
- package/server/index.js +23 -0
- package/server/migrations/media.sql +30 -0
- package/server/plugins/hook.js +91 -0
- package/server/plugins/vite.js +80 -0
- package/server/routes/builder/controllers/cms.builder.delete.js +21 -0
- package/server/routes/builder/controllers/cms.builder.get.js +17 -0
- package/server/routes/builder/controllers/cms.builder.list.js +16 -0
- package/server/routes/builder/controllers/cms.builder.post.js +21 -0
- package/server/routes/builder/controllers/cms.builder.put.js +23 -0
- package/server/routes/builder/index.mjs +22 -0
- package/server/routes/category/controllers/cms.category.delete.js +21 -0
- package/server/routes/category/controllers/cms.category.get.js +17 -0
- package/server/routes/category/controllers/cms.category.list.js +16 -0
- package/server/routes/category/controllers/cms.category.post.js +21 -0
- package/server/routes/category/controllers/cms.category.put.js +23 -0
- package/server/routes/category/index.mjs +22 -0
- package/server/routes/manager/controllers/cms.manager.delete.js +22 -0
- package/server/routes/manager/controllers/cms.manager.get.js +21 -0
- package/server/routes/manager/controllers/cms.manager.list.js +31 -0
- package/server/routes/manager/controllers/cms.manager.post.js +28 -0
- package/server/routes/manager/controllers/cms.manager.put.js +23 -0
- package/server/routes/manager/index.mjs +22 -0
- package/server/routes/media/controllers/delete.js +59 -0
- package/server/routes/media/controllers/edit.js +94 -0
- package/server/routes/media/controllers/list.js +74 -0
- package/server/routes/media/controllers/metadata.js +51 -0
- package/server/routes/media/controllers/preview.js +47 -0
- package/server/routes/media/controllers/upload.js +79 -0
- package/server/routes/media/index.mjs +16 -0
- package/server/routes/root.mjs +15 -0
- package/server/templates/cls/cms.category_type.json +10 -0
- package/server/templates/cls/cms.content_review_status.json +10 -0
- package/server/templates/cls/cms.content_status.json +10 -0
- package/server/templates/cls/cms.content_type.json +10 -0
- package/server/templates/cls/cms.lang.json +10 -0
- package/server/templates/page/login.html +59 -0
- package/server/templates/select/cms.category_id.sql +1 -0
- package/server/templates/select/cms.type_id.sql +1 -0
- package/src/App.vue +4 -0
- package/src/assets/tailwind/tailwind.js +62 -0
- package/src/assets/vue.svg +1 -0
- package/src/components/builder/vs-builder-content.vue +163 -0
- package/src/components/builder/vs-builder-menu.vue +142 -0
- package/src/components/formats/index.js +8 -0
- package/src/components/formats/vs-manager-table-date.vue +29 -0
- package/src/components/formats/vs-manager-table-switch.vue +16 -0
- package/src/components/icons/icon-actions.vue +24 -0
- package/src/components/icons/icon-arrow-left.vue +19 -0
- package/src/components/icons/icon-check.vue +23 -0
- package/src/components/icons/icon-chewron-right.vue +16 -0
- package/src/components/icons/icon-close.vue +22 -0
- package/src/components/icons/icon-edit.vue +22 -0
- package/src/components/icons/icon-folder.vue +18 -0
- package/src/components/icons/icon-folder2.vue +17 -0
- package/src/components/icons/icon-home.vue +16 -0
- package/src/components/icons/icon-image.vue +18 -0
- package/src/components/icons/icon-logo.vue +22 -0
- package/src/components/icons/icon-media.vue +22 -0
- package/src/components/icons/icon-point.vue +11 -0
- package/src/components/icons/icon-search.vue +22 -0
- package/src/components/icons/icon-table.vue +22 -0
- package/src/components/icons/icon-users.vue +18 -0
- package/src/components/icons/icon.plus.vue +18 -0
- package/src/components/manager/children/vs-manager-collection-content.vue +55 -0
- package/src/components/manager/children/vs-manager-collection-item-content.vue +116 -0
- package/src/components/manager/children/vs-manager-single-content.vue +112 -0
- package/src/components/manager/manager-table/vs-manager-colection-table-add.vue +84 -0
- package/src/components/manager/manager-table/vs-manager-collection-table.vue +59 -0
- package/src/components/manager/vs-manager-menu.vue +73 -0
- package/src/components/media/Breadcrumb.vue +73 -0
- package/src/components/shared-components/vs-not-data.vue +213 -0
- package/src/components/vs-main-menu.vue +53 -0
- package/src/helpers/debounce.js +10 -0
- package/src/helpers/translite.js +19 -0
- package/src/main.js +30 -0
- package/src/misc/import-file.js +32 -0
- package/src/pages/vs-builder.vue +22 -0
- package/src/pages/vs-layout.vue +17 -0
- package/src/pages/vs-manager.vue +30 -0
- package/src/pages/vs-media.vue +398 -0
- package/src/router/router.js +9 -0
- package/src/router/routes.config.js +40 -0
- package/src/style.css +0 -0
- package/src/templates/form-columns.js +70 -0
- package/src/templates/form-template.js +22 -0
- package/test/config.js +17 -0
- package/test/files/eye.svg +4 -0
- package/test/helper.js +30 -0
- package/test/routes/builder.test.js +99 -0
- package/test/routes/category.test.js +97 -0
- package/test/routes/manager.test.js +103 -0
- package/test/routes/media.test.js +252 -0
- package/vite.config.js +37 -0
- package/editor/dist/cms.js +0 -5900
- package/editor/dist/cms.umd.cjs +0 -19
- /package/{editor/dist → public}/vite.svg +0 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="w-full p-[20px] bg-gray-100 flex justify-center h-[calc(100vh-60px)]"
|
|
4
|
+
>
|
|
5
|
+
<div class="p-[20px] w-full max-w-[1640px]">
|
|
6
|
+
<div>
|
|
7
|
+
<div class="flex items-start justify-between">
|
|
8
|
+
<!-- Breadcrumb Component -->
|
|
9
|
+
<Breadcrumb :initialPath="currentDir" @update-path="updatePath" />
|
|
10
|
+
|
|
11
|
+
<!-- Search Input -->
|
|
12
|
+
<div class="flex gap-2 relative">
|
|
13
|
+
<div
|
|
14
|
+
class="flex items-center justify-between bg-white py-2 px-3 rounded-lg border border-gray-300 focus:outline-none focus:border-blue-500 text-sm w-[250px]"
|
|
15
|
+
>
|
|
16
|
+
<div class="flex items-center gap-[12px]">
|
|
17
|
+
<IconSearch />
|
|
18
|
+
<input
|
|
19
|
+
v-model="searchQuery"
|
|
20
|
+
type="text"
|
|
21
|
+
placeholder="Пошук за назвою..."
|
|
22
|
+
class="focus:outline-none"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
<IconClose
|
|
26
|
+
class="w-4 h-4 cursor-pointer"
|
|
27
|
+
v-if="searchQuery"
|
|
28
|
+
@click="clearSearch"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<button
|
|
33
|
+
ref="dropdownButton"
|
|
34
|
+
@click="isAddDirModalOpen = true"
|
|
35
|
+
class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium border border-gray-300 rounded-lg text-gray-800 hover:bg-gray-100 bg-white"
|
|
36
|
+
>
|
|
37
|
+
<IconFolder2 />
|
|
38
|
+
Додати папку
|
|
39
|
+
</button>
|
|
40
|
+
|
|
41
|
+
<!-- Upload Button -->
|
|
42
|
+
<label
|
|
43
|
+
for="file-upload"
|
|
44
|
+
class="py-2 px-3 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:bg-blue-700 cursor-pointer"
|
|
45
|
+
>
|
|
46
|
+
Завантажити
|
|
47
|
+
<input
|
|
48
|
+
id="file-upload"
|
|
49
|
+
type="file"
|
|
50
|
+
class="hidden"
|
|
51
|
+
@change="handleFileChange"
|
|
52
|
+
/>
|
|
53
|
+
</label>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- No data -->
|
|
58
|
+
<div
|
|
59
|
+
v-if="!filteredIcons?.length"
|
|
60
|
+
class="flex flex-col items-center justify-center h-64 bg-gray-50 rounded-lg border text-center"
|
|
61
|
+
>
|
|
62
|
+
<VsNotData text="" class="![&>div]:min-w-[100px] !w-auto" />
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- Grid of Icons -->
|
|
66
|
+
<div v-else>
|
|
67
|
+
<div class="grid grid-cols-2 lg:grid-cols-5 gap-3 xl:gap-5">
|
|
68
|
+
<div
|
|
69
|
+
v-for="icon in currentIcons"
|
|
70
|
+
:key="icon?.id"
|
|
71
|
+
class="flex flex-col bg-white border rounded-xl"
|
|
72
|
+
@click="icon.type === 'dir' ? navigateToDir(icon.name) : null"
|
|
73
|
+
:class="{
|
|
74
|
+
'cursor-pointer hover:border-blue-500': icon.type === 'dir',
|
|
75
|
+
'cursor-default': icon.type !== 'dir',
|
|
76
|
+
}"
|
|
77
|
+
>
|
|
78
|
+
<div class="relative group">
|
|
79
|
+
<div
|
|
80
|
+
class="w-full h-36 sm:h-[170px] object-cover rounded-t-xl flex items-center justify-center"
|
|
81
|
+
>
|
|
82
|
+
<div class="text-[#54aeff]" v-if="icon?.type === 'dir'">
|
|
83
|
+
<IconFolder />
|
|
84
|
+
</div>
|
|
85
|
+
<img
|
|
86
|
+
v-else
|
|
87
|
+
class="object-contain max-h-[40px]"
|
|
88
|
+
:src="`/api/cms-media/${icon?.token}`"
|
|
89
|
+
:alt="icon?.name"
|
|
90
|
+
:style="
|
|
91
|
+
icon?.path?.includes('.svg')
|
|
92
|
+
? 'width:250px;max-height:100px'
|
|
93
|
+
: ''
|
|
94
|
+
"
|
|
95
|
+
/>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div class="p-3 flex items-center gap-x-3 border-t">
|
|
100
|
+
<span
|
|
101
|
+
v-if="icon.type !== 'dir'"
|
|
102
|
+
class="flex shrink-0 justify-center items-center w-10 h-10 bg-white border border-solid text-gray-500 rounded-lg"
|
|
103
|
+
>
|
|
104
|
+
<IconImage />
|
|
105
|
+
</span>
|
|
106
|
+
<div class="grow truncate" :title="icon?.name">
|
|
107
|
+
<p
|
|
108
|
+
class="block truncate text-sm font-semibold text-gray-800"
|
|
109
|
+
:class="{ 'cursor-pointer': icon.type !== 'dir' }"
|
|
110
|
+
@click.stop="
|
|
111
|
+
icon.type !== 'dir' && copyToClipboard(icon?.name)
|
|
112
|
+
"
|
|
113
|
+
>
|
|
114
|
+
{{ icon?.name }}
|
|
115
|
+
</p>
|
|
116
|
+
<p class="block truncate text-xs text-gray-500">
|
|
117
|
+
{{ icon?.size }}
|
|
118
|
+
</p>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<!-- Pagination -->
|
|
125
|
+
<VsPagination
|
|
126
|
+
v-if="filteredIcons.length > iconsPerPage"
|
|
127
|
+
:defaultPage="currentPage"
|
|
128
|
+
class="mt-6 focus-visible:outline-blue-500 flex !justify-center"
|
|
129
|
+
:total="filteredIcons.length"
|
|
130
|
+
:pageSize="iconsPerPage"
|
|
131
|
+
:maxPages="3"
|
|
132
|
+
:goTo="false"
|
|
133
|
+
@pageChange="currentPage = $event"
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<VsDialog
|
|
139
|
+
v-model:visible="isAddDirModalOpen"
|
|
140
|
+
title="Додати папку"
|
|
141
|
+
size="small"
|
|
142
|
+
:closeClickBack="true"
|
|
143
|
+
@onClose="handeAddFolderClose"
|
|
144
|
+
>
|
|
145
|
+
<div class="p-4">
|
|
146
|
+
<VsText v-model="newDirName" placeholder="Назва нової папки.." />
|
|
147
|
+
<div class="flex justify-end">
|
|
148
|
+
<button
|
|
149
|
+
@click="addDir"
|
|
150
|
+
class="mt-4 py-2 px-4 text-white bg-blue-600 hover:bg-blue-700 rounded-lg"
|
|
151
|
+
>
|
|
152
|
+
Створити
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</VsDialog>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</template>
|
|
160
|
+
|
|
161
|
+
<script>
|
|
162
|
+
import Breadcrumb from "../components/media/Breadcrumb.vue";
|
|
163
|
+
import IconSearch from "../components/icons/icon-search.vue";
|
|
164
|
+
import IconFolder from "../components/icons/icon-folder.vue";
|
|
165
|
+
import IconFolder2 from "../components/icons/icon-folder2.vue";
|
|
166
|
+
import IconClose from "../components/icons/icon-close.vue";
|
|
167
|
+
import IconActions from "../components/icons/icon-actions.vue";
|
|
168
|
+
import axios from "axios";
|
|
169
|
+
import IconImage from "../components/icons/icon-image.vue";
|
|
170
|
+
import VsNotData from "../components/shared-components/vs-not-data.vue";
|
|
171
|
+
|
|
172
|
+
export default {
|
|
173
|
+
components: {
|
|
174
|
+
Breadcrumb,
|
|
175
|
+
IconSearch,
|
|
176
|
+
IconFolder,
|
|
177
|
+
IconFolder2,
|
|
178
|
+
IconClose,
|
|
179
|
+
IconImage,
|
|
180
|
+
IconActions,
|
|
181
|
+
VsNotData,
|
|
182
|
+
},
|
|
183
|
+
props: {
|
|
184
|
+
type: {
|
|
185
|
+
type: String,
|
|
186
|
+
default: "icon",
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
data() {
|
|
190
|
+
return {
|
|
191
|
+
icons: [],
|
|
192
|
+
currentPage: parseInt(this.$route.query.page, 10) || 1,
|
|
193
|
+
iconsPerPage: 15,
|
|
194
|
+
token: "",
|
|
195
|
+
searchQuery: "",
|
|
196
|
+
selectedFile: null,
|
|
197
|
+
isAddDirModalOpen: false,
|
|
198
|
+
newDirName: "",
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
watch: {
|
|
202
|
+
isAddDirModalOpen(n) {
|
|
203
|
+
if (!n) {
|
|
204
|
+
this.newDirName = "";
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
computed: {
|
|
209
|
+
currentDir() {
|
|
210
|
+
return this.$route.query.dir || "";
|
|
211
|
+
},
|
|
212
|
+
currentTab() {
|
|
213
|
+
return this.$route.query.tab || "";
|
|
214
|
+
},
|
|
215
|
+
filteredIcons() {
|
|
216
|
+
if (!this.searchQuery) return this.icons;
|
|
217
|
+
return this.icons.filter((icon) =>
|
|
218
|
+
icon.name.toLowerCase().includes(this.searchQuery.toLowerCase())
|
|
219
|
+
);
|
|
220
|
+
},
|
|
221
|
+
sortedIcons() {
|
|
222
|
+
return [...this.filteredIcons].sort((a, b) => {
|
|
223
|
+
if (a.type === "dir" && b.type !== "dir") return -1;
|
|
224
|
+
if (a.type !== "dir" && b.type === "dir") return 1;
|
|
225
|
+
return a.name.localeCompare(b.name);
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
currentIcons() {
|
|
229
|
+
const startIndex = (this.currentPage - 1) * this.iconsPerPage;
|
|
230
|
+
const endIndex = startIndex + this.iconsPerPage;
|
|
231
|
+
return this.sortedIcons.slice(startIndex, endIndex);
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
methods: {
|
|
235
|
+
async fetchIcons(dir) {
|
|
236
|
+
try {
|
|
237
|
+
const { data } = await axios.get(`/api/cms-media`, {
|
|
238
|
+
params: { dir },
|
|
239
|
+
});
|
|
240
|
+
this.icons = data?.data || [];
|
|
241
|
+
this.token = data?.token;
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error("Error fetching icons:", error);
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
async addDir() {
|
|
247
|
+
if (!this.newDirName.trim()) {
|
|
248
|
+
this.$notify({
|
|
249
|
+
type: "error",
|
|
250
|
+
title: "Помилка",
|
|
251
|
+
message: "Назва папки не може бути порожньою.",
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const response = await axios.post(`/api/cms-media/${this.token}`, {
|
|
258
|
+
name: this.newDirName,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
this.$notify({
|
|
262
|
+
type: "success",
|
|
263
|
+
title: "Успіх!",
|
|
264
|
+
message: `Папку "${this.newDirName}" успішно створено.`,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
this.fetchIcons(this.currentDir);
|
|
268
|
+
|
|
269
|
+
this.handeAddFolderClose();
|
|
270
|
+
this.newDirName = "";
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error("Error creating directory:", error);
|
|
273
|
+
this.$notify({
|
|
274
|
+
type: "error",
|
|
275
|
+
title: "Помилка",
|
|
276
|
+
message:
|
|
277
|
+
error.response?.data?.message || "Не вдалося створити папку.",
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
handeAddFolderClose() {
|
|
282
|
+
this.isAddDirModalOpen = false;
|
|
283
|
+
},
|
|
284
|
+
navigateToDir(dirName) {
|
|
285
|
+
const newDir = `${this.currentDir}/${dirName}`.replace(/\/+/g, "/");
|
|
286
|
+
this.updateURL(newDir);
|
|
287
|
+
},
|
|
288
|
+
updatePath(newPath) {
|
|
289
|
+
this.updateURL(newPath);
|
|
290
|
+
},
|
|
291
|
+
updateURL(dir) {
|
|
292
|
+
this.$router.push({
|
|
293
|
+
query: {
|
|
294
|
+
...this.$route.query,
|
|
295
|
+
dir,
|
|
296
|
+
page: this.currentPage,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
},
|
|
300
|
+
async handleFileChange(event) {
|
|
301
|
+
const file = event.target.files[0];
|
|
302
|
+
if (!file) return;
|
|
303
|
+
|
|
304
|
+
const formData = new FormData();
|
|
305
|
+
formData.append("file", file);
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
const response = await axios.post(
|
|
309
|
+
`/api/cms-media/${this.token}`,
|
|
310
|
+
formData,
|
|
311
|
+
{
|
|
312
|
+
headers: {
|
|
313
|
+
"Content-Type": "multipart/form-data",
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
this.$notify({
|
|
319
|
+
type: "success",
|
|
320
|
+
title: "Успіх!",
|
|
321
|
+
message: "Файл успішно додано!",
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
this.currentPage = 1;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error("Error uploading file:", error);
|
|
327
|
+
this.$notify({
|
|
328
|
+
type: "error",
|
|
329
|
+
title: "Помилка",
|
|
330
|
+
message: error.response.statustext || error.message || error,
|
|
331
|
+
});
|
|
332
|
+
} finally {
|
|
333
|
+
await this.fetchIcons(this.currentDir);
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
clearSearch() {
|
|
337
|
+
this.searchQuery = "";
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
copyToClipboard(textToCopy) {
|
|
341
|
+
try {
|
|
342
|
+
const tempTextArea = document.createElement("textarea");
|
|
343
|
+
tempTextArea.value = textToCopy;
|
|
344
|
+
document.body.appendChild(tempTextArea);
|
|
345
|
+
tempTextArea.select();
|
|
346
|
+
tempTextArea.setSelectionRange(0, textToCopy.length);
|
|
347
|
+
const successful = document.execCommand("copy");
|
|
348
|
+
document.body.removeChild(tempTextArea);
|
|
349
|
+
|
|
350
|
+
if (successful) {
|
|
351
|
+
this.$notify({
|
|
352
|
+
type: "success",
|
|
353
|
+
title: "Скопійовано!",
|
|
354
|
+
message: "Назву іконки успішно скопійовано.",
|
|
355
|
+
});
|
|
356
|
+
} else {
|
|
357
|
+
this.$notify({
|
|
358
|
+
type: "error",
|
|
359
|
+
title: "Помилка",
|
|
360
|
+
message: "Не вдалося скопіювати шлях.",
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
} catch (error) {
|
|
364
|
+
this.$notify({
|
|
365
|
+
type: "error",
|
|
366
|
+
title: "Помилка",
|
|
367
|
+
message: "Не вдалося скопіювати координати",
|
|
368
|
+
});
|
|
369
|
+
console.error("Copy failed", error);
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
watch: {
|
|
374
|
+
currentDir: {
|
|
375
|
+
immediate: true,
|
|
376
|
+
handler(newDir) {
|
|
377
|
+
this.fetchIcons(newDir);
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
currentTab: {
|
|
381
|
+
handler() {
|
|
382
|
+
this.currentPage = 1;
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
currentPage(newPage) {
|
|
386
|
+
this.$emit("update-url", {
|
|
387
|
+
query: {
|
|
388
|
+
...this.$route.query,
|
|
389
|
+
page: newPage,
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
async mounted() {
|
|
395
|
+
await this.fetchIcons(this.currentDir);
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
</script>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export default [
|
|
2
|
+
{
|
|
3
|
+
name: 'app',
|
|
4
|
+
path: '/',
|
|
5
|
+
redirect: '/cms/manager',
|
|
6
|
+
children: [
|
|
7
|
+
{
|
|
8
|
+
name: 'cms',
|
|
9
|
+
path: '/cms',
|
|
10
|
+
component: () => import('../pages/vs-layout.vue'),
|
|
11
|
+
children: [
|
|
12
|
+
{
|
|
13
|
+
name: 'builder',
|
|
14
|
+
path: 'builder',
|
|
15
|
+
component: () => import('../pages/vs-builder.vue'),
|
|
16
|
+
children: [
|
|
17
|
+
{ path: ':id', name: 'builder-item', component: () => import('../components/builder/vs-builder-content.vue') },
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'manager',
|
|
23
|
+
path: 'manager',
|
|
24
|
+
component: () => import('../pages/vs-manager.vue'),
|
|
25
|
+
children: [
|
|
26
|
+
{ path: 'collection/:template', name: 'manager-collection-item', component: () => import('../components/manager/children/vs-manager-collection-content.vue') },
|
|
27
|
+
{ path: 'collection/:template/:id', name: 'manager-collection-form', component: () => import('../components/manager/children/vs-manager-collection-item-content.vue') },
|
|
28
|
+
{ path: 'single/:template', name: 'manager-single-item', component: () => import('../components/manager/children/vs-manager-single-content.vue') },
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'media',
|
|
33
|
+
path: 'media',
|
|
34
|
+
component: () => import('../pages/vs-media.vue'),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
];
|
package/src/style.css
ADDED
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export default [
|
|
2
|
+
{
|
|
3
|
+
key: "title",
|
|
4
|
+
placeholder: "Назва колонки українською",
|
|
5
|
+
ua: "Назва колонки українською",
|
|
6
|
+
type: "text",
|
|
7
|
+
slots: {
|
|
8
|
+
row: '<div class="flex items-center gap-2"><img class="block" height="24" width="30" :src="`https://cdn.softpro.ua/data/npm/admin/column-types/icon-${row?.type?.toLowerCase()}.svg`"/> <span class="text-[12px]">{{ row?.title }}</span></div>',
|
|
9
|
+
},
|
|
10
|
+
validators: ["required"],
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
key: "type",
|
|
14
|
+
type: "radio",
|
|
15
|
+
ua: "Тип колонки",
|
|
16
|
+
view: "buttons",
|
|
17
|
+
style: {
|
|
18
|
+
class: "!w-[calc(50%-5px)]",
|
|
19
|
+
},
|
|
20
|
+
slots: {
|
|
21
|
+
label:
|
|
22
|
+
'<div class="flex items-center justify-start w-full gap-4"><img class="block" height="24" width="30" :src="`https://cdn.softpro.ua/data/npm/admin/column-types/icon-${id?.toLowerCase()}.svg`"/><div class="flex flex-col items-start gap-1"><span class="text-black text-[14px]">{{text}}</span><span class="text-[12px]">{{description}}</span></div></div>',
|
|
23
|
+
},
|
|
24
|
+
options: [
|
|
25
|
+
{
|
|
26
|
+
id: "Text",
|
|
27
|
+
text: "Текст",
|
|
28
|
+
description: "Маленький або довгий текст, наприклад заголовок або опис",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "Number",
|
|
32
|
+
text: "Цифри",
|
|
33
|
+
description: "Числа (цілі, з плаваючою точкою, десяткові)",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "Email",
|
|
37
|
+
text: "Електронна пошта",
|
|
38
|
+
description: "Поле електронної пошти з фоматом перевірки",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "Date",
|
|
42
|
+
text: "Дата",
|
|
43
|
+
description: "Вибір дати з годинами, хвилинами та секундами",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "Switcher",
|
|
47
|
+
text: "Так/Ні",
|
|
48
|
+
description: "Так чи ні, 1 або 0, вірно чи хибно",
|
|
49
|
+
},
|
|
50
|
+
{ id: "File", text: "Файл", description: "Різноманітні файли" },
|
|
51
|
+
{
|
|
52
|
+
id: "Select",
|
|
53
|
+
text: "Селект",
|
|
54
|
+
description: "Список значень, а потім виберіть одне",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "Badge",
|
|
58
|
+
text: "Бейдж",
|
|
59
|
+
description: "Стилізоване значення з переліку",
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
validators: ["required"],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
key: "enabled",
|
|
66
|
+
type: "Switcher",
|
|
67
|
+
ua: "Увімкнено",
|
|
68
|
+
col: 6,
|
|
69
|
+
},
|
|
70
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default [
|
|
2
|
+
{
|
|
3
|
+
key: "name",
|
|
4
|
+
type: "Text",
|
|
5
|
+
ua: "Назва",
|
|
6
|
+
placeholder: "Назва",
|
|
7
|
+
validators: ["required"],
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
key: "template",
|
|
11
|
+
type: "Text",
|
|
12
|
+
ua: "Шаблон",
|
|
13
|
+
placeholder: "Шаблон",
|
|
14
|
+
validators: ["required"],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
key: "description",
|
|
18
|
+
type: "Text",
|
|
19
|
+
ua: "Опис",
|
|
20
|
+
placeholder: "Опис",
|
|
21
|
+
},
|
|
22
|
+
]
|
package/test/config.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import config from '../server/config.js';
|
|
2
|
+
|
|
3
|
+
Object.assign(config, {
|
|
4
|
+
pg: config.pg || {
|
|
5
|
+
host: '192.168.3.160',
|
|
6
|
+
port: 5439,
|
|
7
|
+
database: 'ip_ukraine',
|
|
8
|
+
user: 'postgres',
|
|
9
|
+
password: 'postgres',
|
|
10
|
+
},
|
|
11
|
+
redis: config.redis || {
|
|
12
|
+
host: '192.168.3.160',
|
|
13
|
+
port: 6379,
|
|
14
|
+
family: 4,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
export default config;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M1.3335 8C1.3335 8 3.3335 3.33333 8.00016 3.33333C12.6668 3.33333 14.6668 8 14.6668 8C14.6668 8 12.6668 12.6667 8.00016 12.6667C3.3335 12.6667 1.3335 8 1.3335 8Z" stroke="#6B7280" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3
|
+
<path d="M8 10C9.10457 10 10 9.10457 10 8C10 6.89543 9.10457 6 8 6C6.89543 6 6 6.89543 6 8C6 9.10457 6.89543 10 8 10Z" stroke="#6B7280" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
</svg>
|
package/test/helper.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// This file contains code that we reuse
|
|
2
|
+
// between our tests.
|
|
3
|
+
import Fastify from 'fastify';
|
|
4
|
+
|
|
5
|
+
import { redisClients, pgClients } from '@opengis/fastify-table/utils.js';
|
|
6
|
+
|
|
7
|
+
import appService from '../server/app.js';
|
|
8
|
+
|
|
9
|
+
// automatically build and tear down our instance
|
|
10
|
+
async function build(t) {
|
|
11
|
+
// you can set all the options supported by the fastify CLI command
|
|
12
|
+
// const argv = [AppPath]
|
|
13
|
+
process.env.NODE_ENV = 'production';
|
|
14
|
+
const app = Fastify({ logger: false });
|
|
15
|
+
app.register(appService);
|
|
16
|
+
// close the app after we are done
|
|
17
|
+
t.after(() => {
|
|
18
|
+
Object.keys(pgClients).forEach((key) => {
|
|
19
|
+
pgClients[key].end();
|
|
20
|
+
});
|
|
21
|
+
Object.keys(redisClients || {}).forEach((key) => {
|
|
22
|
+
redisClients[key].quit();
|
|
23
|
+
});
|
|
24
|
+
app.close();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return app;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default build;
|