@owdproject/nuxt-kit-nuxt-ui 0.0.0-stage → 4.0.0-alpha.4

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.
Files changed (65) hide show
  1. package/dist/module.d.mts +14 -0
  2. package/dist/module.json +9 -0
  3. package/dist/module.mjs +112 -0
  4. package/dist/runtime/components/dialogs/NuxtUIAlertModal.d.vue.ts +8 -0
  5. package/dist/runtime/components/dialogs/NuxtUIAlertModal.vue +40 -0
  6. package/dist/runtime/components/dialogs/NuxtUIAlertModal.vue.d.ts +8 -0
  7. package/dist/runtime/components/dialogs/NuxtUIConfirmModal.d.vue.ts +12 -0
  8. package/dist/runtime/components/dialogs/NuxtUIConfirmModal.vue +47 -0
  9. package/dist/runtime/components/dialogs/NuxtUIConfirmModal.vue.d.ts +12 -0
  10. package/dist/runtime/components/explorer/AddressBar.d.vue.ts +17 -0
  11. package/dist/runtime/components/explorer/AddressBar.vue +39 -0
  12. package/dist/runtime/components/explorer/AddressBar.vue.d.ts +17 -0
  13. package/dist/runtime/components/explorer/Chrome.d.vue.ts +13 -0
  14. package/dist/runtime/components/explorer/Chrome.vue +5 -0
  15. package/dist/runtime/components/explorer/Chrome.vue.d.ts +13 -0
  16. package/dist/runtime/components/explorer/DefaultToolbar.d.vue.ts +31 -0
  17. package/dist/runtime/components/explorer/DefaultToolbar.vue +92 -0
  18. package/dist/runtime/components/explorer/DefaultToolbar.vue.d.ts +31 -0
  19. package/dist/runtime/components/explorer/FileContextMenu.d.vue.ts +18 -0
  20. package/dist/runtime/components/explorer/FileContextMenu.vue +90 -0
  21. package/dist/runtime/components/explorer/FileContextMenu.vue.d.ts +18 -0
  22. package/dist/runtime/components/explorer/FileEntry.d.vue.ts +109 -0
  23. package/dist/runtime/components/explorer/FileEntry.vue +263 -0
  24. package/dist/runtime/components/explorer/FileEntry.vue.d.ts +109 -0
  25. package/dist/runtime/components/explorer/FileIcon.d.vue.ts +29 -0
  26. package/dist/runtime/components/explorer/FileIcon.vue +41 -0
  27. package/dist/runtime/components/explorer/FileIcon.vue.d.ts +29 -0
  28. package/dist/runtime/components/explorer/FilePane.d.vue.ts +13 -0
  29. package/dist/runtime/components/explorer/FilePane.vue +5 -0
  30. package/dist/runtime/components/explorer/FilePane.vue.d.ts +13 -0
  31. package/dist/runtime/components/explorer/Frame.d.vue.ts +75 -0
  32. package/dist/runtime/components/explorer/Frame.vue +73 -0
  33. package/dist/runtime/components/explorer/Frame.vue.d.ts +75 -0
  34. package/dist/runtime/components/explorer/Nav.d.vue.ts +17 -0
  35. package/dist/runtime/components/explorer/Nav.vue +83 -0
  36. package/dist/runtime/components/explorer/Nav.vue.d.ts +17 -0
  37. package/dist/runtime/components/explorer/SelectableArea.d.vue.ts +33 -0
  38. package/dist/runtime/components/explorer/SelectableArea.vue +83 -0
  39. package/dist/runtime/components/explorer/SelectableArea.vue.d.ts +33 -0
  40. package/dist/runtime/components/explorer/Workspace.d.vue.ts +24 -0
  41. package/dist/runtime/components/explorer/Workspace.vue +116 -0
  42. package/dist/runtime/components/explorer/Workspace.vue.d.ts +24 -0
  43. package/dist/runtime/dialogs/createDesktopDialogs.d.ts +2 -0
  44. package/dist/runtime/dialogs/createDesktopDialogs.js +47 -0
  45. package/dist/runtime/plugins/01.desktop-nuxt-ui-dialogs.client.d.ts +2 -0
  46. package/dist/runtime/plugins/01.desktop-nuxt-ui-dialogs.client.js +13 -0
  47. package/dist/types.d.mts +9 -0
  48. package/package.json +78 -4
  49. package/src/kit/registerTailwindPath.ts +44 -0
  50. package/src/runtime/components/dialogs/NuxtUIAlertModal.vue +42 -0
  51. package/src/runtime/components/dialogs/NuxtUIConfirmModal.vue +49 -0
  52. package/src/runtime/components/explorer/AddressBar.vue +44 -0
  53. package/src/runtime/components/explorer/Chrome.vue +5 -0
  54. package/src/runtime/components/explorer/DefaultToolbar.vue +92 -0
  55. package/src/runtime/components/explorer/FileContextMenu.vue +99 -0
  56. package/src/runtime/components/explorer/FileEntry.vue +286 -0
  57. package/src/runtime/components/explorer/FileIcon.vue +43 -0
  58. package/src/runtime/components/explorer/FilePane.vue +5 -0
  59. package/src/runtime/components/explorer/Frame.vue +106 -0
  60. package/src/runtime/components/explorer/Nav.vue +87 -0
  61. package/src/runtime/components/explorer/SelectableArea.vue +93 -0
  62. package/src/runtime/components/explorer/Workspace.vue +119 -0
  63. package/src/runtime/dialogs/createDesktopDialogs.ts +56 -0
  64. package/src/runtime/plugins/01.desktop-nuxt-ui-dialogs.client.ts +14 -0
  65. package/README.md +0 -3
@@ -0,0 +1,286 @@
1
+ <script setup lang="ts">
2
+ import { explorerEntryAbsolutePath } from "@owdproject/nuxt-module-fs/runtime/utils/utilExplorerEntryPath";
3
+ import { fetchExplorerEntryMetadata } from "@owdproject/nuxt-module-fs/runtime/utils/utilExplorerEntryMetadata";
4
+ import { ref, computed, watch } from "vue";
5
+ import FileContextMenu from "./FileContextMenu.vue";
6
+ import FileIcon from "./FileIcon.vue";
7
+ import { useExplorerInternalDrag } from "@owdproject/nuxt-module-fs/runtime/composables/useExplorerInternalDrag";
8
+ import { useExplorerFolderDropTarget } from "@owdproject/nuxt-module-fs/runtime/composables/useExplorerFolderDropTarget";
9
+
10
+ const props = defineProps({
11
+ basePath: { type: String, required: true },
12
+ fileName: { type: String, required: true },
13
+ layout: { type: String, required: true },
14
+ selected: { type: Boolean, required: false },
15
+ cutted: { type: Boolean, required: false },
16
+ window: { type: Object, required: true },
17
+ contextMenuComponent: { type: null, required: false },
18
+ openPathInNewTab: { type: Function, required: false },
19
+ dragEnabled: { type: Boolean, required: false }
20
+ });
21
+
22
+ const menuImpl = computed(
23
+ () => props.contextMenuComponent ?? FileContextMenu
24
+ );
25
+
26
+ const emit = defineEmits([
27
+ "openDirectory",
28
+ "openFile",
29
+ "rename"
30
+ ]);
31
+
32
+ const path = computed(
33
+ () => explorerEntryAbsolutePath(props.basePath, props.fileName)
34
+ );
35
+
36
+ const pathStats = ref<any>(null);
37
+ const isDirectory = ref(false);
38
+ const isFile = ref();
39
+ const fileContent = ref(null);
40
+ const contextMenuRef = ref<any>();
41
+
42
+ function fetchStatsAndContent(currentPath: string) {
43
+ fetchExplorerEntryMetadata(currentPath, (meta) => {
44
+ pathStats.value = meta.stats;
45
+ isFile.value = meta.isFile;
46
+ isDirectory.value = meta.isDirectory;
47
+ fileContent.value = meta.fileContent;
48
+ });
49
+ }
50
+
51
+ function onFileOpen() {
52
+ if (isDirectory.value) {
53
+ props.window.fsExplorer.openDirectory(props.fileName);
54
+ } else {
55
+ props.window.fsExplorer.openFile(props.fileName);
56
+ }
57
+ }
58
+
59
+ function onRightClick(event: MouseEvent) {
60
+ contextMenuRef.value?.show(event);
61
+ }
62
+
63
+ function onAuxClick(event: MouseEvent) {
64
+ if (event.button !== 1) return;
65
+ if (!isDirectory.value || !props.openPathInNewTab) return;
66
+ event.preventDefault();
67
+ event.stopPropagation();
68
+ props.openPathInNewTab(path.value);
69
+ }
70
+
71
+ watch(
72
+ path,
73
+ (p) => {
74
+ fetchStatsAndContent(p);
75
+ },
76
+ { immediate: true }
77
+ );
78
+
79
+ const isRenaming = ref(false);
80
+ const editableName = ref(props.fileName);
81
+
82
+ function renameCommit() {
83
+ const newName = editableName.value.trim();
84
+ if (newName && newName !== props.fileName) {
85
+ emit("rename", { oldName: props.fileName, newName });
86
+ }
87
+ isRenaming.value = false;
88
+ }
89
+
90
+ function renameStart() {
91
+ editableName.value = props.fileName;
92
+ isRenaming.value = true;
93
+ }
94
+
95
+ function renameCancel() {
96
+ editableName.value = props.fileName;
97
+ isRenaming.value = false;
98
+ }
99
+
100
+ const isRowLayout = computed(
101
+ () => props.layout === "list" || props.layout === "details"
102
+ );
103
+
104
+ function formatBytes(n: number) {
105
+ if (!Number.isFinite(n) || n < 0) return "—";
106
+ if (n < 1024) return `${n} B`;
107
+ const units = ["KB", "MB", "GB", "TB"];
108
+ let v = n / 1024;
109
+ let i = 0;
110
+ while (v >= 1024 && i < units.length - 1) {
111
+ v /= 1024;
112
+ i++;
113
+ }
114
+ const rounded = v < 10 && i > 0 ? v.toFixed(1) : String(Math.round(v));
115
+ return `${rounded} ${units[i]}`;
116
+ }
117
+
118
+ function statToDate(stats: any) {
119
+ if (!stats) return null;
120
+ const m = stats.mtime;
121
+ if (m instanceof Date) return m;
122
+ if (typeof m === "number") return new Date(m);
123
+ return null;
124
+ }
125
+
126
+ const modifiedLabel = computed(() => {
127
+ const d = statToDate(pathStats.value);
128
+ if (!d || Number.isNaN(d.getTime())) return "—";
129
+ return d.toLocaleString(undefined, {
130
+ dateStyle: "short",
131
+ timeStyle: "short"
132
+ });
133
+ });
134
+
135
+ const typeLabel = computed(() => {
136
+ if (isDirectory.value) return "File folder";
137
+ const parts = props.fileName.split(".");
138
+ if (parts.length < 2) return "File";
139
+ return `${parts.pop()?.toUpperCase()} file`;
140
+ });
141
+
142
+ const sizeLabel = computed(() => {
143
+ if (!pathStats.value || isDirectory.value) return "—";
144
+ if (typeof pathStats.value.isFile === 'function' && !pathStats.value.isFile()) return "—";
145
+ return formatBytes(pathStats.value.size);
146
+ });
147
+
148
+ const dragEnabledValue = computed(() => props.dragEnabled ?? true);
149
+ const { onDragStart } = useExplorerInternalDrag({
150
+ entryPath: () => path.value,
151
+ selectedFiles: () => props.window.fsExplorer?.selectedFiles.value ?? [],
152
+ dragEnabled: () => dragEnabledValue.value
153
+ });
154
+
155
+ const folderDrop = useExplorerFolderDropTarget({
156
+ fsExplorer: props.window.fsExplorer,
157
+ folderPath: () => path.value,
158
+ enabled: () => dragEnabledValue.value && isDirectory.value
159
+ });
160
+
161
+ const classes = computed(() => {
162
+ const list: any[] = [
163
+ "owd-file select-none rounded-lg transition-all cursor-pointer p-1.5 flex flex-col items-center",
164
+ isRowLayout.value ? "w-full text-left" : "w-[90px] text-center"
165
+ ];
166
+ if (props.selected) {
167
+ list.push("bg-primary-50 dark:bg-primary-950/30 ring-1 ring-primary-500/50");
168
+ } else {
169
+ list.push("hover:bg-gray-100 dark:hover:bg-gray-800/50");
170
+ }
171
+ if (props.cutted) {
172
+ list.push("opacity-50");
173
+ }
174
+ if (folderDrop.isDragOver.value) {
175
+ list.push("bg-blue-100/50 dark:bg-blue-900/30 ring-2 ring-dashed ring-blue-500");
176
+ }
177
+ return list;
178
+ });
179
+ </script>
180
+
181
+ <template>
182
+ <div
183
+ :class="classes"
184
+ :title="fileName"
185
+ :draggable="dragEnabledValue"
186
+ @dragstart.stop="onDragStart"
187
+ @dragenter="folderDrop.onDragEnter"
188
+ @dragover="folderDrop.onDragOver"
189
+ @dragleave="folderDrop.onDragLeave"
190
+ @drop="folderDrop.onDrop"
191
+ @dblclick="onFileOpen"
192
+ @contextmenu.prevent="onRightClick"
193
+ @auxclick.prevent="onAuxClick"
194
+ >
195
+ <template v-if="isRowLayout">
196
+ <div class="flex items-center w-full space-x-3">
197
+ <div class="w-8 flex-shrink-0 flex items-center justify-center">
198
+ <slot
199
+ name="icon"
200
+ :file-name="fileName"
201
+ :is-directory="isDirectory"
202
+ :layout="layout"
203
+ >
204
+ <FileIcon
205
+ :file-name="fileName"
206
+ :is-directory="isDirectory"
207
+ :layout="layout"
208
+ />
209
+ </slot>
210
+ </div>
211
+
212
+ <div class="flex-1 min-w-0">
213
+ <span
214
+ v-if="!isRenaming"
215
+ @dblclick.stop="renameStart"
216
+ class="text-sm truncate block font-medium text-gray-700 dark:text-gray-200"
217
+ v-text="fileName"
218
+ />
219
+
220
+ <input
221
+ v-else
222
+ v-model="editableName"
223
+ class="text-sm truncate border border-primary-500 rounded px-1 w-full bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 outline-none"
224
+ @blur="renameCommit"
225
+ @keyup.enter="renameCommit"
226
+ @keyup.esc="renameCancel"
227
+ autofocus
228
+ />
229
+ </div>
230
+
231
+ <template v-if="layout === 'details'">
232
+ <div class="text-xs text-gray-400 w-32 text-right truncate tabular-nums">{{ modifiedLabel }}</div>
233
+ <div class="text-xs text-gray-400 w-24 truncate" :title="typeLabel">{{ typeLabel }}</div>
234
+ <div class="text-xs text-gray-400 w-20 text-right truncate tabular-nums">{{ sizeLabel }}</div>
235
+ </template>
236
+ </div>
237
+ </template>
238
+
239
+ <template v-else>
240
+ <div class="p-1 rounded flex items-center justify-center mb-1">
241
+ <slot
242
+ name="icon"
243
+ :file-name="fileName"
244
+ :is-directory="isDirectory"
245
+ :layout="layout"
246
+ >
247
+ <FileIcon
248
+ :file-name="fileName"
249
+ :is-directory="isDirectory"
250
+ :layout="layout"
251
+ />
252
+ </slot>
253
+ </div>
254
+
255
+ <div class="w-full text-center">
256
+ <span
257
+ v-if="!isRenaming"
258
+ @dblclick.stop="renameStart"
259
+ class="text-xs block truncate font-medium text-gray-700 dark:text-gray-200 px-1"
260
+ v-text="fileName"
261
+ />
262
+
263
+ <input
264
+ v-else
265
+ v-model="editableName"
266
+ class="text-xs truncate border border-primary-500 rounded px-1 w-full bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 outline-none text-center"
267
+ @blur="renameCommit"
268
+ @keyup.enter="renameCommit"
269
+ @keyup.esc="renameCancel"
270
+ autofocus
271
+ />
272
+ </div>
273
+ </template>
274
+
275
+ <component
276
+ :is="menuImpl"
277
+ ref="contextMenuRef"
278
+ :file-name="fileName"
279
+ :window="window"
280
+ :is-directory="!!isDirectory"
281
+ :open-path-in-new-tab="openPathInNewTab"
282
+ @open="onFileOpen"
283
+ @rename="renameStart"
284
+ />
285
+ </div>
286
+ </template>
@@ -0,0 +1,43 @@
1
+ <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ const props = defineProps({
4
+ fileName: { type: String, required: true },
5
+ isDirectory: { type: Boolean, required: true },
6
+ layout: { type: String, required: true }
7
+ });
8
+ const imageIcon = "data:image/x-icon;base64,AAABAAIAICAQAAEABADoAgAAJgAAABAQEAABAAQAKAEAAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAACHd3d3d3d3d3d3d3AAAAAAj/////////////9wAAAAAI//////////////cAAAAACP/////////////3AAAAAAj/////////////9wAAAAAI//////////////cAAAAACP/wAP/wAP/wAA/3AAAAAAj/83MAC3sAA3MP9wAAAAAI//O3t7e3t7ezD/cAAAAACP//O3MzMzM7MP/3AAAAAAj///Ow////Nw//9wAAAAAI///zcP///zsP//cAAAAACP//M7D/AAAAMP/3AAAAAAj//ztw8CKnAAD/9wAAAAAI//8zsKqvL3AA//cAAAAACP//83Cqr/KIAP/3AAAAAAj///OwqqqvOPsP9wAAAAAI//87cKqq/zuPsAcAAAAACP/zN7AAAAA3OPkQAAAAAAj/83t7e3t7e3MdmQAAAAAI//OzMzOzMzOzAdmQAAAACP/zM//zM//zMw8dmQAAAAj/////////////8dmQAAAI//////////////cdmQAACP//////////gAAAAdmQAAj//////////4/3gAAdmQAI//////////+PeAAAAdmQCP//////////h4AAAAAdmQj//////////4gAAAAAAdkI//////////+AAAAAAAARCIiIiIiIiIiIgAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAfwAAAD8AAAAfAAAADwAAAAcAAAGDAAADwQAAB+AAAA/wAAAf+AAAP/8oAAAAEAAAACAAAAABAAQAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAh3d3d3d3AACP//////cAAI/wAAAA9wAAj/c3NzD3AACP8w//MPcAAI/3DyIA9wAAj/MCd4cHAACP9wqqOAEAAI/zCqowmRAAj/cAADD5kQCP83NzMPeZEI/////wAAmRj/////f4AJGP////94AAAIiIiIiIAAAAAAeIiAAH//8AB///AAf//wAHAKAAB70AAAe9AAAHIgAABwAAAAcACAAD/CIAAYgAAAAAAAAMAAAAH2b2AD8AAA==";
9
+ const audioIcon = "data:image/x-icon;base64,AAABAAIAICAQAAEABADoAgAAJgAAABAQEAABAAQAKAEAAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAAAIACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvwAAvwAAAL+/AL8AAAC/AL8Av78AAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACHd3d3d3d3d3dwAAAAAAAAj///////////cAAAAAAAAI///////////3AAAAAAAACP//d///////9wAAAAAAAAj/93d///////cAAAAAAAAI/wB3d//////3AAAAAAAACPAwh3f//4//9wAAAAAAAAgDB3h3/4j///cAAAAAAABwOI/4d4j////3AAAAAAAHA7B/+Hj/////9wAAAAAAADvwf/h3//////cAAAAAADi/sA/4d//////3AAAAAAO3+/CA+HiIiP//9wAAAAADt7+wcPh3//////cAAAAAA/f78A/4d//////3AAAAAAAzv7B/+Hj/////9wAAAAAAADvwf/h/iP////cAAAAAAAADuIf4f/+I///3AAAAAAAACDsHd////4//9wAAAAAAAAjzgI////////cAAAAAAAAI/wD////////3AAAAAAAACP////////gAAAAAAAAAAAj////////4/4AAAAAAAAAI////////+PgAAAAAAAAACP////////iAAAAAAAAAAAj////////4AAAAAAAAAAAIiIiIiIiIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AAAP/wAAD/8AAA//AAAP/wAAD/8AAA//AAAP/wAAD/8AAA/+AAAP/AAAD/gAAA/wAAAP8AAAD/AAAA/wAAAP+AAAD/4AAA//AAAP/wAAD/8AAA//AAAP/wAAD/8AAB//AAA//wAAf/8AAP//AAH///////////8oAAAAEAAAACAAAAABAAQAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvwAAvwAAAL+/AL8AAAC/AL8Av78AAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAACHd3d3d3AAAI//////cAAAgAD///9wAAADMI/4/3AAgDsPj4//cAADsH+P//9wC4vwD4+Ij3A/e/CPj///cAM/sH+Pj/9wADP4D4/4/3AAAD8Hj///cAAAg4D//wAAAACP////f4AAAI////94AAAAiIiIiIAA8AAAAPAAAADwAAAA8AAAAPAAAADAAAAAgAAAAAAAAAAAAAAAgAAAAMAAAADwAAAA8AAAAPABAADwAwAA8AcAAA==";
10
+ const videoIcon = "data:image/x-icon;base64,AAABAAIAICAQAAEABADoAgAAJgAAABAQEAABAAQAKAEAAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAAAIACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvwAAvwAAAL+/AL8AAAC/AL8Av78AAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACHd3d3d3d3d3dwAAAAAAAAj///////////cAAAAAAAB4d3d3d3d3d//3AAAAAAAHeHd3d3d3d3f/9wAAAAAAAAAAAAAAAAB3//cAAAAACIiIiIiIiIiAd//3AAAAAAj/////////gHf/9wAAAAAI//n/zM/y/4B3//cAAAAACP+fn8//8v+Ad//3AAAAAAj/+f/8//L/gHf/9wAAAAAI/5+fz88i/4B3//cAAAAACP/5//z/8v+Ad//3AAAAAAj/////////gHf/9wAAAAAAAP8A/wD/AIB///cAAAAAAz8A/wD/AP8A///3AAAAAI/4CACIAIgAiP//9wAAAACLuAB3d/////////cAAAAACID/AHd3///////3AAAAAAAIAP8Ad3f/////9wAAAAAAAAgA/wB3d/////cAAAAAAAAI+AD/AHd3///3AAAAAAAACP/4AP8Af/gAAAAAAAAAAAj///gA/w/4/4AAAAAAAAAI////+ACP+PgAAAAAAAAACP/////4//iAAAAAAAAAAAj////////4AAAAAAAAAAAIiIiIiIiIiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AAAP/wAAD/8AAA/+AAAP/AAAD/gAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP4AAAD+AAAA/wAAAP/AAAD/8AAA//AAAP/wAAD/8AAB//AAA//wAAf/8AAP//AAH///////////8oAAAAEAAAACAAAAABAAQAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvwAAvwAAAL+/AL8AAAC/AL8Av78AAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAACHd3d3d3AAAI//////cAAAAAAAD/9wiIiIiIgP/3CP////+A//cI+Z/PL4D/9wj/////gP/3CPDw8PCA//cACAgICA//9wiwAP/////3CIj/AA////cAAIiP8P/wAAAACPiP//f4AAAI////94AAAAiIiIiIAA8AAAAPAAAADwAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAPABAADwAwAA8AcAAA==";
11
+ const iconMap: Record<string, string> = {
12
+ mp4: videoIcon,
13
+ webm: videoIcon,
14
+ mp3: audioIcon,
15
+ wav: audioIcon,
16
+ iso: "data:image/x-icon;base64,AAABAAEAICAQAAEABADoAgAAFgAAACgAAAAgAAAAQAAAAAEABAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAwMDAAICAgAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAId3d3d3d3d3d3AAAAAAAACP//////////9wAAAAAAAAj///////////cAAAAAAAAI///////////3AAAAAAAACP/3d3f/////9wAAAAAAAAj3d3d3f/////cAAAAAAAAIiAAHd3f////3AAAAAAAAiHd6oAd3f///9wAAAAAACHd3equwd3f///cAAAAAAI93d3q77gd3///3AAAAAACH93d6vucHd///9wAAAAAId393+r53cHf///cAAAAACHd3+Ajnd3B3///3AAAAAAh3d6Dwd3dwd///9wAAAAAId3roCHd3cHf///cAAAAACHeq63f3d3B////3AAAAAACKrrt3f3cHf///9wAAAAAAiu67d3f3D/////cAAAAAAAjru3d3cP/////3AAAAAAAAiLt3cA//////9wAAAAAAAAiIiI////////cAAAAAAAAI////////+AAAAAAAAAAACP////////j/gAAAAAAAAAj////////4+AAAAAAAAAAI////////+IAAAAAAAAAACP////////gAAAAAAAAAAAiIiIiIiIiIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8AAA//AAAP/wAAD/8AAA//AAAP/wAAD/8AAA//AAAP/gAAD/wAAA/4AAAP+AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+AAAD/gAAA/8AAAP/gAAD/8AAA//AAAP/wAAH/8AAD//AAB//wAA//8AAf///////////w==",
17
+ webp: imageIcon,
18
+ gif: imageIcon,
19
+ jpg: imageIcon,
20
+ png: imageIcon,
21
+ txt: "data:image/x-icon;base64,AAABAAIAICAQAAEABADoAgAAJgAAABAQEAABAAQAKAEAAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAAAIACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvwAAvwAAAL+/AL8AAAC/AL8Av78AAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAACHd3d3d3d3d3d3dwcAAAAI//////////////9wcAAACP//////////////cHAAAAj/AAAA/////////3BwAAAI//////////////9wcAAACP8AAAD/////////cHAAAAj//////////////3BwAAAI//////////////9wcAAACP8AAAAAAAAAAAD/cHAAAAj//////////////3BwAAAI/wAAAAAAAAAAAP9wcAAACP//////////////cHAAAAj/AAAAAAAAAAAA/3BwAAAI//////////////9wcAAACP8AAAAAAAAAAAD/cHAAAAj//////////////3BwAAAI//////////////9wcAAACP8AAAD/////////cHAAAAj//////////////3BwAAAI//////////////9wcAAACP//////////////cHAAAAj/AAAA/////////3BwAAAI//////////////9wcAAACP8AAAD////w8AD/cHAAAAj//////////////3BwAAAI//////////////9wcAAACP//////////////cHAAAAjw/w/w/w/w/w/w/4BwAAAI8P8P8P8P8P8P8P+AcAAAAI+I+I+I+I+I+I+I+AAAAAAAAAAAAAAAAAAAAAAAAA8AAAH+AAAA/AAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAHwAAAB8AAAAfAAAAH4AAAD/JJJL8oAAAAEAAAACAAAAABAAQAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvwAAvwAAAL+/AL8AAAC/AL8Av78AAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAh3d3d3d3AACP//////cAAI//////9wAAj/AAAA/3AACP//////cAAI/wAAAP9wAAj//////3AACP8AAAD/cAAI//////9wAAj/AA8A/3AACP//////cAAI//////9wAAjw8PDw8PAAAA+Pj4+PAAAAAAAAAAAAAAgA8AAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAAAABwAAAAcAAAAHAACADwAA1V8AAA==",
22
+ pdf: "/icons/pdf.ico",
23
+ default: "data:image/x-icon;base64,AAABAAEAICAQAAEABADoAgAAFgAAACgAAAAgAAAAQAAAAAEABAAAAAAAgAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAHd3d3d3d3d3d3d3dwAAAAAAAAAAAAAAAAAAAAcAAAAA//////////////8HAAAAAP//////////////BwAAAAD//////////////wcAAAAA//////////////8HAAAAAP//////////////BwAAAAD//////////////wcAAAAA//////////////8HAAAAAP//////////////BwAAAAD//////////////wcAAAAA//////////////8HAAAAAP//////////////BwAAAAD//////////////wcAAAAA//////////////8HAAAAAP//////////////BwAAAAD//////////////wcAAAAA//////////////8HAAAAAP//////////////BwAAAAD//////////////wcAAAAA//////////////8HAAAAAP//////////////BwAAAAD//////////////wcAAAAA//////////////8HAAAAAP//////////////BwAAAAD//////////wAAAAAAAAAA//////////8P+HAAAAAAAP//////////D4cAAAAAAAD//////////whwAAAAAAAA//////////8HAAAAAAAAAP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAAH8AAAB/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAAB/AAAAfwAAAH8AAAD/AAAB/wAAA/8AAAf/AAAP/wAAH/8AAD/w=="
24
+ };
25
+ const directoryIcon = "data:image/x-icon;base64,AAABAAIAICAQAAEABADoAgAAJgAAABAQEAABAAQAKAEAAA4DAAAoAAAAIAAAAEAAAAABAAQAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACDMzMzMzMzMzMzMzMzMzMwj7e3t7e3t7e3t7e3t7e3wCHd3d3d3d3d4iIiIiIiIgACPt7e3t7e3gAAAAAAAAAAACPt7e3t7eAAAAAAAAAAAAACP/////4AAAAAAAAAAAAAACIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////gAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAP//wAH//+AD///wB/////////////8oAAAAEAAAACAAAAABAAQAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAMDAwACAgIAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIiIiIiIiIgAj7e3t7e3uACPe3t7e3t4AI+3t7e3t7gAj3t7e3t7eACPt7e3t7e4AI97e3t7e3gAj7e3t7e3uACP///////4AIe3t7eIiIgACHt7eAAAAAAAiIiAAAAAAAAAAAAAAAAA//8AAP//AACAAQCgAAHOAAABzgAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAMAAID/AADB/wAA//8AAA==";
26
+
27
+ const getFileExtension = (name: string) => name.split(".").pop()?.toLowerCase() ?? "";
28
+
29
+ const iconSrc = computed(() => {
30
+ if (props.isDirectory) return directoryIcon;
31
+ const ext = getFileExtension(props.fileName);
32
+ return iconMap[ext] || iconMap.default;
33
+ });
34
+ </script>
35
+
36
+ <template>
37
+ <img
38
+ :src="iconSrc"
39
+ draggable="false"
40
+ alt=""
41
+ class="mx-auto"
42
+ />
43
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <div class="flex-1 min-h-0 relative flex overflow-auto">
3
+ <slot />
4
+ </div>
5
+ </template>
@@ -0,0 +1,106 @@
1
+ <script setup lang="ts">
2
+ import { useSlots, computed, useAttrs } from 'vue'
3
+ import Nav from './Nav.vue'
4
+
5
+ const props = defineProps({
6
+ window: { type: Object, required: false },
7
+ content: { type: null, required: false },
8
+ chromePadding: { type: Boolean, required: false, default: true },
9
+ frameClass: { type: String, required: false },
10
+ })
11
+
12
+ defineEmits([
13
+ 'drag:start',
14
+ 'drag:move',
15
+ 'drag:end',
16
+ 'resize:start',
17
+ 'resize:move',
18
+ 'resize:end',
19
+ ])
20
+
21
+ defineOptions({ inheritAttrs: false })
22
+
23
+ const attrs = useAttrs()
24
+ const windowRootClass = computed(
25
+ () => [attrs.class as string, props.frameClass].filter(Boolean),
26
+ )
27
+ const slots = useSlots()
28
+ const hasHeaderBelowNavSlot = computed(
29
+ () => typeof slots['header-below-nav'] === 'function',
30
+ )
31
+ const hasNavSlot = computed(() => typeof slots.nav === 'function')
32
+ </script>
33
+
34
+ <template>
35
+ <DesktopCoreWindow
36
+ :class="windowRootClass"
37
+ :window="props.window"
38
+ :content="props.content"
39
+ v-show="props.window?.state?.active ?? true"
40
+ @drag:start="$emit('drag:start', $event)"
41
+ @drag:move="$emit('drag:move', $event)"
42
+ @drag:end="$emit('drag:end', $event)"
43
+ @resize:start="$emit('resize:start', $event)"
44
+ @resize:move="$emit('resize:move', $event)"
45
+ @resize:end="$emit('resize:end', $event)"
46
+ >
47
+ <div :class="['flex flex-col h-full bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 rounded-lg shadow-lg overflow-hidden', props.chromePadding === false ? '' : 'p-2']">
48
+ <div class="desktop-explorer-frame__header-stack">
49
+ <slot v-if="hasNavSlot" name="nav" />
50
+ <Nav v-else>
51
+ <template #prepend>
52
+ <slot name="nav-prepend" />
53
+ </template>
54
+ <template v-slot:title v-if="$slots['nav-title']">
55
+ <slot name="nav-title" />
56
+ </template>
57
+ <template #append>
58
+ <slot name="nav-append" />
59
+ </template>
60
+ </Nav>
61
+ <div
62
+ v-if="hasHeaderBelowNavSlot"
63
+ class="desktop-explorer-frame__header-below-nav"
64
+ >
65
+ <slot name="header-below-nav" />
66
+ </div>
67
+ </div>
68
+ <div class="flex-1 min-h-0 flex flex-col">
69
+ <slot />
70
+ </div>
71
+ </div>
72
+ </DesktopCoreWindow>
73
+ </template>
74
+
75
+ <style scoped>
76
+ .desktop-explorer-frame__header-stack {
77
+ display: flex;
78
+ flex-direction: column;
79
+ min-width: 0;
80
+ }
81
+
82
+ .desktop-explorer-frame__header-below-nav {
83
+ min-width: 0;
84
+ }
85
+
86
+ .owd-window.resizable-component {
87
+ box-sizing: border-box;
88
+ display: inline-block;
89
+ min-width: 160px;
90
+ min-height: 160px;
91
+ text-align: left;
92
+ cursor: default;
93
+ }
94
+ @media (max-width: 599px) {
95
+ .owd-window.resizable-component {
96
+ top: 0 !important;
97
+ bottom: 0 !important;
98
+ left: 0 !important;
99
+ right: 0 !important;
100
+ max-width: 100vw;
101
+ max-height: 100vh;
102
+ height: 100vh !important;
103
+ z-index: 9999999999 !important;
104
+ }
105
+ }
106
+ </style>
@@ -0,0 +1,87 @@
1
+ <script setup lang="ts">
2
+ import { inject, useSlots, computed } from "vue"
3
+ import { useToggleWindowMaximize } from "@owdproject/nuxt/runtime/composables/useToggleWindowMaximize"
4
+
5
+ const windowController = inject<any>("windowController")
6
+ const toggleWindowMaximize = useToggleWindowMaximize()
7
+ const slots = useSlots()
8
+ const hasCustomTitle = computed(() => typeof slots.title === "function")
9
+
10
+ function onWindowMinimize() {
11
+ if (!windowController?.instanced) return
12
+ windowController.actions.minimize()
13
+ }
14
+
15
+ function onWindowMaximize() {
16
+ toggleWindowMaximize(windowController)
17
+ }
18
+
19
+ function onWindowNavDestroy() {
20
+ if (!windowController?.instanced) return
21
+ windowController.actions.destroy()
22
+ }
23
+ </script>
24
+
25
+ <template>
26
+ <DesktopCoreWindowNav
27
+ class="flex items-center justify-between px-3 py-1.5 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 select-none cursor-default"
28
+ @dblclick="onWindowMaximize"
29
+ >
30
+ <div class="flex items-center space-x-2 min-w-0">
31
+ <slot name="prepend" />
32
+
33
+ <slot v-if="hasCustomTitle" name="title" />
34
+
35
+ <template v-else>
36
+ <UIcon
37
+ v-if="windowController?.icon"
38
+ :name="windowController.icon"
39
+ class="w-4 h-4 text-gray-500 dark:text-gray-400 flex-shrink-0"
40
+ />
41
+
42
+ <span
43
+ v-if="windowController?.title"
44
+ class="text-xs font-semibold text-gray-700 dark:text-gray-300 truncate"
45
+ v-text="windowController?.title"
46
+ />
47
+ </template>
48
+ </div>
49
+
50
+ <div class="flex items-center space-x-1 flex-shrink-0">
51
+ <slot name="append" />
52
+
53
+ <UButton
54
+ v-if="!windowController?.instanced || windowController?.isMinimizable"
55
+ icon="i-heroicons-minus"
56
+ size="2xs"
57
+ color="gray"
58
+ variant="ghost"
59
+ class="h-5 w-5 p-0 flex items-center justify-center rounded"
60
+ @mousedown.stop
61
+ @click.stop="onWindowMinimize"
62
+ />
63
+ <UButton
64
+ v-slot:default
65
+ v-if="windowController?.isMaximizable"
66
+ icon="i-heroicons-squares-2x2"
67
+ size="2xs"
68
+ color="gray"
69
+ variant="ghost"
70
+ class="h-5 w-5 p-0 flex items-center justify-center rounded"
71
+ @mousedown.stop
72
+ @click.stop="onWindowMaximize"
73
+ />
74
+ <UButton
75
+ v-slot:default
76
+ v-if="!windowController?.instanced || windowController?.isDestroyable"
77
+ icon="i-heroicons-x-mark"
78
+ size="2xs"
79
+ color="red"
80
+ variant="ghost"
81
+ class="h-5 w-5 p-0 flex items-center justify-center rounded hover:bg-red-500 hover:text-white"
82
+ @mousedown.stop
83
+ @click.stop="onWindowNavDestroy"
84
+ />
85
+ </div>
86
+ </DesktopCoreWindowNav>
87
+ </template>
@@ -0,0 +1,93 @@
1
+ <script setup lang="ts">
2
+ import { ref, onMounted, useTemplateRef, computed, watch } from 'vue'
3
+ import { VueSelecto } from 'vue3-selecto'
4
+ import { useExplorerExternalDrop } from '@owdproject/nuxt-module-fs/runtime/composables/useExplorerExternalDrop'
5
+ import { explorerEntryAbsolutePath } from '@owdproject/nuxt-module-fs/runtime/utils/utilExplorerEntryPath'
6
+
7
+ const props = defineProps({
8
+ fsExplorer: { type: Object, required: true },
9
+ dropEnabled: { type: Boolean, required: false },
10
+ })
11
+
12
+ const dropEnabled = computed(() => props.dropEnabled ?? true)
13
+ const { isDragOver, onDragEnter, onDragOver, onDragLeave, onDrop } =
14
+ useExplorerExternalDrop(props.fsExplorer, {
15
+ enabled: () => dropEnabled.value,
16
+ })
17
+
18
+ const selectoContainer = useTemplateRef('selectoContainer')
19
+ const windowContentContainer = ref<any>()
20
+ const container = typeof document !== 'undefined' ? document.body : undefined
21
+ const files = ref<string[]>([])
22
+
23
+ watch(
24
+ () => props.fsExplorer.selectedFiles.value,
25
+ (newSelected) => {
26
+ files.value = newSelected ? [...newSelected] : []
27
+ },
28
+ { deep: true, immediate: true }
29
+ )
30
+
31
+ function resolveEntryPath(fileName: string) {
32
+ return explorerEntryAbsolutePath(props.fsExplorer.basePath.value, fileName)
33
+ }
34
+
35
+ function onSelect(e: any) {
36
+ e.added.forEach((el: any) => {
37
+ const name = el.getAttribute('data-filename')
38
+ if (!name) return
39
+ const path = resolveEntryPath(name)
40
+ if (!files.value.includes(path)) {
41
+ files.value.push(path)
42
+ }
43
+ props.fsExplorer.selectFiles(files.value)
44
+ })
45
+
46
+ e.removed.forEach((el: any) => {
47
+ const name = el.getAttribute('data-filename')
48
+ if (!name) return
49
+ const path = resolveEntryPath(name)
50
+ const fileIndex = files.value.indexOf(path)
51
+ if (fileIndex > -1) {
52
+ files.value.splice(fileIndex, 1)
53
+ }
54
+ props.fsExplorer.selectFiles(files.value)
55
+ })
56
+ }
57
+
58
+ onMounted(() => {
59
+ const root = selectoContainer.value
60
+ if (root) {
61
+ windowContentContainer.value =
62
+ root.closest('.owd-window__content') ?? undefined
63
+ }
64
+ })
65
+ </script>
66
+
67
+ <template>
68
+ <div
69
+ ref="selectoContainer"
70
+ class="desktop-explorer-dropzone flex-1 flex flex-wrap p-4 select-none min-h-full items-start content-start"
71
+ :class="{ 'desktop-explorer-dropzone--active bg-blue-50/30 dark:bg-blue-900/10': isDragOver }"
72
+ @dragenter="onDragEnter"
73
+ @dragover="onDragOver"
74
+ @dragleave="onDragLeave"
75
+ @drop="onDrop"
76
+ >
77
+ <VueSelecto
78
+ v-if="container && windowContentContainer"
79
+ :rootContainer="windowContentContainer"
80
+ :dragContainer="windowContentContainer"
81
+ :selectableTargets='[".owd-file"]'
82
+ :selectByClick="true"
83
+ :selectFromInside="true"
84
+ :continueSelect="false"
85
+ toggleContinueSelect="ctrl"
86
+ :keyContainer="container"
87
+ :hitRate="60"
88
+ @select="onSelect"
89
+ />
90
+
91
+ <slot />
92
+ </div>
93
+ </template>