@owdproject/nuxt-theme-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.
- package/dist/i18n/locales/en.d.ts +2 -0
- package/dist/i18n/locales/en.js +1 -0
- package/dist/module.d.mts +7 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +50 -0
- package/dist/runtime/apps/calculator/app.config.d.ts +34 -0
- package/dist/runtime/apps/calculator/app.config.js +35 -0
- package/dist/runtime/apps/calculator/components/Window/WindowCalculator.d.vue.ts +6 -0
- package/dist/runtime/apps/calculator/components/Window/WindowCalculator.vue +94 -0
- package/dist/runtime/apps/calculator/components/Window/WindowCalculator.vue.d.ts +6 -0
- package/dist/runtime/apps/calculator/plugin.d.ts +2 -0
- package/dist/runtime/apps/calculator/plugin.js +11 -0
- package/dist/runtime/apps/explorer/app.config.d.ts +36 -0
- package/dist/runtime/apps/explorer/app.config.js +39 -0
- package/dist/runtime/apps/explorer/components/Window/WindowExplorer.d.vue.ts +6 -0
- package/dist/runtime/apps/explorer/components/Window/WindowExplorer.vue +30 -0
- package/dist/runtime/apps/explorer/components/Window/WindowExplorer.vue.d.ts +6 -0
- package/dist/runtime/apps/explorer/plugin.d.ts +2 -0
- package/dist/runtime/apps/explorer/plugin.js +11 -0
- package/dist/runtime/apps/installBuiltInApps.d.ts +3 -0
- package/dist/runtime/apps/installBuiltInApps.js +28 -0
- package/dist/runtime/apps/notepad/app.config.d.ts +34 -0
- package/dist/runtime/apps/notepad/app.config.js +35 -0
- package/dist/runtime/apps/notepad/components/Window/WindowNotepad.d.vue.ts +6 -0
- package/dist/runtime/apps/notepad/components/Window/WindowNotepad.vue +47 -0
- package/dist/runtime/apps/notepad/components/Window/WindowNotepad.vue.d.ts +6 -0
- package/dist/runtime/apps/notepad/plugin.d.ts +2 -0
- package/dist/runtime/apps/notepad/plugin.js +11 -0
- package/dist/runtime/apps/registry.d.ts +8 -0
- package/dist/runtime/apps/registry.js +10 -0
- package/dist/runtime/components/Background/DesktopBackground.d.vue.ts +3 -0
- package/dist/runtime/components/Background/DesktopBackground.vue +3 -0
- package/dist/runtime/components/Background/DesktopBackground.vue.d.ts +3 -0
- package/dist/runtime/components/Desktop/DesktopContent.d.vue.ts +13 -0
- package/dist/runtime/components/Desktop/DesktopContent.vue +23 -0
- package/dist/runtime/components/Desktop/DesktopContent.vue.d.ts +13 -0
- package/dist/runtime/components/Desktop.client.d.vue.ts +23 -0
- package/dist/runtime/components/Desktop.client.vue +52 -0
- package/dist/runtime/components/Desktop.client.vue.d.ts +23 -0
- package/dist/runtime/components/NuxtLauncherOverlay.d.vue.ts +3 -0
- package/dist/runtime/components/NuxtLauncherOverlay.vue +110 -0
- package/dist/runtime/components/NuxtLauncherOverlay.vue.d.ts +3 -0
- package/dist/runtime/components/NuxtTopBar.d.vue.ts +3 -0
- package/dist/runtime/components/NuxtTopBar.vue +63 -0
- package/dist/runtime/components/NuxtTopBar.vue.d.ts +3 -0
- package/dist/runtime/components/Window/DesktopWindow.d.vue.ts +43 -0
- package/dist/runtime/components/Window/DesktopWindow.vue +40 -0
- package/dist/runtime/components/Window/DesktopWindow.vue.d.ts +43 -0
- package/dist/runtime/components/Window/DesktopWindowContent.d.vue.ts +13 -0
- package/dist/runtime/components/Window/DesktopWindowContent.vue +5 -0
- package/dist/runtime/components/Window/DesktopWindowContent.vue.d.ts +13 -0
- package/dist/runtime/components/Window/DesktopWindowIframe.d.vue.ts +29 -0
- package/dist/runtime/components/Window/DesktopWindowIframe.vue +13 -0
- package/dist/runtime/components/Window/DesktopWindowIframe.vue.d.ts +29 -0
- package/dist/runtime/components/Window/DesktopWindowNav.d.vue.ts +3 -0
- package/dist/runtime/components/Window/DesktopWindowNav.vue +105 -0
- package/dist/runtime/components/Window/DesktopWindowNav.vue.d.ts +3 -0
- package/dist/runtime/composables/useNuxtStartMenu.d.ts +9 -0
- package/dist/runtime/composables/useNuxtStartMenu.js +57 -0
- package/dist/types.d.mts +7 -0
- package/package.json +70 -4
- package/src/i18n/locales/en.ts +1 -0
- package/src/module.ts +69 -0
- package/src/runtime/apps/calculator/app.config.ts +35 -0
- package/src/runtime/apps/calculator/components/Window/WindowCalculator.vue +104 -0
- package/src/runtime/apps/calculator/plugin.ts +12 -0
- package/src/runtime/apps/explorer/app.config.ts +39 -0
- package/src/runtime/apps/explorer/components/Window/WindowExplorer.vue +34 -0
- package/src/runtime/apps/explorer/plugin.ts +12 -0
- package/src/runtime/apps/installBuiltInApps.ts +36 -0
- package/src/runtime/apps/notepad/app.config.ts +35 -0
- package/src/runtime/apps/notepad/components/Window/WindowNotepad.vue +50 -0
- package/src/runtime/apps/notepad/plugin.ts +12 -0
- package/src/runtime/apps/registry.ts +21 -0
- package/src/runtime/components/Background/DesktopBackground.vue +3 -0
- package/src/runtime/components/Desktop/DesktopContent.vue +37 -0
- package/src/runtime/components/Desktop.client.vue +136 -0
- package/src/runtime/components/NuxtLauncherOverlay.vue +117 -0
- package/src/runtime/components/NuxtTopBar.vue +68 -0
- package/src/runtime/components/Window/DesktopWindow.vue +44 -0
- package/src/runtime/components/Window/DesktopWindowContent.vue +5 -0
- package/src/runtime/components/Window/DesktopWindowIframe.vue +13 -0
- package/src/runtime/components/Window/DesktopWindowNav.vue +110 -0
- package/src/runtime/composables/useNuxtStartMenu.ts +72 -0
- package/README.md +0 -3
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
defineProps<{
|
|
5
|
+
window: any
|
|
6
|
+
}>()
|
|
7
|
+
|
|
8
|
+
const text = ref('')
|
|
9
|
+
|
|
10
|
+
function clear() {
|
|
11
|
+
text.value = ''
|
|
12
|
+
}
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<DesktopWindow :window="window">
|
|
17
|
+
<div class="flex h-full min-h-0 flex-col bg-white dark:bg-gray-950">
|
|
18
|
+
<!-- Toolbar -->
|
|
19
|
+
<div
|
|
20
|
+
class="flex flex-shrink-0 items-center gap-2 border-b border-gray-200 bg-gray-50 p-2 dark:border-gray-800 dark:bg-gray-900"
|
|
21
|
+
>
|
|
22
|
+
<UButton
|
|
23
|
+
icon="i-heroicons-document"
|
|
24
|
+
label="New"
|
|
25
|
+
color="gray"
|
|
26
|
+
variant="ghost"
|
|
27
|
+
size="xs"
|
|
28
|
+
class="gap-2"
|
|
29
|
+
@click="clear"
|
|
30
|
+
/>
|
|
31
|
+
<UButton
|
|
32
|
+
icon="i-heroicons-trash"
|
|
33
|
+
label="Clear"
|
|
34
|
+
color="red"
|
|
35
|
+
variant="ghost"
|
|
36
|
+
size="xs"
|
|
37
|
+
class="gap-2"
|
|
38
|
+
@click="clear"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Text area -->
|
|
43
|
+
<textarea
|
|
44
|
+
v-model="text"
|
|
45
|
+
class="w-full flex-1 min-h-0 resize-none border-0 bg-transparent p-4 font-mono text-sm text-gray-800 outline-none dark:text-gray-200"
|
|
46
|
+
placeholder="Type here..."
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
</DesktopWindow>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineNuxtPlugin } from 'nuxt/app'
|
|
2
|
+
import { defineDesktopApp } from '@owdproject/nuxt/kit/defineDesktopApp'
|
|
3
|
+
import configAppNotepad from './app.config'
|
|
4
|
+
|
|
5
|
+
export default defineNuxtPlugin({
|
|
6
|
+
name: 'desktop-theme-nuxt-notepad',
|
|
7
|
+
dependsOn: ['pinia', 'desktop-shell-init'],
|
|
8
|
+
async setup() {
|
|
9
|
+
if (import.meta.server) return
|
|
10
|
+
await defineDesktopApp(configAppNotepad)
|
|
11
|
+
},
|
|
12
|
+
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Nuxt } from '@nuxt/schema'
|
|
2
|
+
|
|
3
|
+
export type NuxtThemeBuiltInApp = {
|
|
4
|
+
name: string
|
|
5
|
+
autoload: boolean
|
|
6
|
+
when?: (nuxt: Nuxt) => boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function hasModuleFs(nuxt: Nuxt): boolean {
|
|
10
|
+
const desktop = nuxt.options.runtimeConfig.public.desktop as {
|
|
11
|
+
modules?: string[]
|
|
12
|
+
}
|
|
13
|
+
const modules = desktop?.modules ?? []
|
|
14
|
+
return modules.some((m) => String(m).includes('nuxt-fs'))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const NUXT_THEME_BUILTIN_APPS: NuxtThemeBuiltInApp[] = [
|
|
18
|
+
{ name: 'explorer', autoload: true },
|
|
19
|
+
{ name: 'calculator', autoload: true },
|
|
20
|
+
{ name: 'notepad', autoload: true },
|
|
21
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useAppConfig } from "nuxt/app";
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
|
|
5
|
+
const appConfig = useAppConfig() as any;
|
|
6
|
+
const classes = computed(() => {
|
|
7
|
+
const list = ["owd-desktop__content"];
|
|
8
|
+
const pos = appConfig.desktop?.systemBar?.position;
|
|
9
|
+
if (pos) {
|
|
10
|
+
list.push(`owd-desktop__content--system-bar-position-${pos}`);
|
|
11
|
+
}
|
|
12
|
+
return list;
|
|
13
|
+
});
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div :class="classes">
|
|
18
|
+
<slot />
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<style scoped>
|
|
23
|
+
.owd-desktop__content {
|
|
24
|
+
position: relative;
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100%;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
}
|
|
29
|
+
.owd-desktop__content--system-bar-position-top {
|
|
30
|
+
flex: 1 1 auto;
|
|
31
|
+
min-height: 0;
|
|
32
|
+
}
|
|
33
|
+
.owd-desktop__content--system-bar-position-bottom {
|
|
34
|
+
flex: 1 1 auto;
|
|
35
|
+
min-height: 0;
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, onMounted, onUnmounted } from "vue";
|
|
3
|
+
import { useDesktopWorkArea } from "@owdproject/nuxt/runtime/composables/useDesktopWorkArea";
|
|
4
|
+
import Background from "./Background/DesktopBackground.vue";
|
|
5
|
+
import DesktopContent from "./Desktop/DesktopContent.vue";
|
|
6
|
+
import NuxtTopBar from "./NuxtTopBar.vue";
|
|
7
|
+
import NuxtLauncherOverlay from "./NuxtLauncherOverlay.vue";
|
|
8
|
+
|
|
9
|
+
defineProps({
|
|
10
|
+
systemBar: { type: null, required: false },
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const shellStageRef = ref(null);
|
|
14
|
+
useDesktopWorkArea(shellStageRef);
|
|
15
|
+
|
|
16
|
+
const BODY_SHELL_CLASS = "owd-has-nuxt-shell";
|
|
17
|
+
|
|
18
|
+
onMounted(() => {
|
|
19
|
+
document.body.classList.add(BODY_SHELL_CLASS);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
onUnmounted(() => {
|
|
23
|
+
document.body.classList.remove(BODY_SHELL_CLASS);
|
|
24
|
+
});
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<DesktopCore
|
|
29
|
+
v-bind="$props"
|
|
30
|
+
class="owd-desktop--nuxt flex flex-col h-screen overflow-hidden select-none bg-gray-50 dark:bg-gray-950"
|
|
31
|
+
>
|
|
32
|
+
<NuxtTopBar />
|
|
33
|
+
|
|
34
|
+
<!--
|
|
35
|
+
`isolate` keeps every window's z-index inside this stacking context, so the
|
|
36
|
+
system bar and the launcher (siblings) always stay above open windows
|
|
37
|
+
instead of losing to the window manager's 100+ z-index values.
|
|
38
|
+
-->
|
|
39
|
+
<div ref="shellStageRef" class="flex-1 min-h-0 relative flex flex-col isolate">
|
|
40
|
+
<Background />
|
|
41
|
+
|
|
42
|
+
<DesktopContent>
|
|
43
|
+
<slot />
|
|
44
|
+
</DesktopContent>
|
|
45
|
+
<DesktopCoreApplicationRender />
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<NuxtLauncherOverlay />
|
|
49
|
+
|
|
50
|
+
<!-- Supports programmatically opened Nuxt UI modals -->
|
|
51
|
+
<UModals />
|
|
52
|
+
</DesktopCore>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<style>
|
|
56
|
+
/* Custom style rules for desktop shell */
|
|
57
|
+
.owd-desktop--nuxt {
|
|
58
|
+
--owd-system-bar-height: 60px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Desktop shell: no document scrollbars — scroll only inside windows/apps */
|
|
62
|
+
.owd-desktop--nuxt,
|
|
63
|
+
.owd-has-nuxt-shell {
|
|
64
|
+
overscroll-behavior: none;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* Window content layout. The core renders the `desktop-window__content` element
|
|
69
|
+
* and adds `--centered` / `--padded` when a window is opened with
|
|
70
|
+
* `:content="{ centered: true, padded: true }"`. These rules must be global
|
|
71
|
+
* (the element is the child component root, so scoped `:deep` does not match).
|
|
72
|
+
*/
|
|
73
|
+
.owd-desktop--nuxt .desktop-window__content {
|
|
74
|
+
display: flex;
|
|
75
|
+
flex-direction: column;
|
|
76
|
+
flex: 1 1 auto;
|
|
77
|
+
min-height: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.owd-desktop--nuxt .desktop-window__content--centered {
|
|
81
|
+
justify-content: center;
|
|
82
|
+
align-items: center;
|
|
83
|
+
text-align: center;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.owd-desktop--nuxt .desktop-window__content--centered > * {
|
|
87
|
+
max-width: 100%;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.owd-desktop--nuxt .desktop-window__content--padded {
|
|
91
|
+
padding: 12px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Thin, unobtrusive scrollbars inside windows and the launcher */
|
|
95
|
+
.owd-desktop--nuxt .desktop-window__content,
|
|
96
|
+
.owd-desktop--nuxt .owd-desktop__scroll {
|
|
97
|
+
scrollbar-width: thin;
|
|
98
|
+
scrollbar-color: rgb(0 0 0 / 0.18) transparent;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar,
|
|
102
|
+
.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar {
|
|
103
|
+
width: 10px;
|
|
104
|
+
height: 10px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-track,
|
|
108
|
+
.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-track {
|
|
109
|
+
background: transparent;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-thumb,
|
|
113
|
+
.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-thumb {
|
|
114
|
+
background-color: rgb(0 0 0 / 0.18);
|
|
115
|
+
border: 3px solid transparent;
|
|
116
|
+
background-clip: padding-box;
|
|
117
|
+
border-radius: 9999px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-thumb:hover,
|
|
121
|
+
.owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-thumb:hover {
|
|
122
|
+
background-color: rgb(0 0 0 / 0.3);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.owd-desktop--nuxt.dark .desktop-window__content,
|
|
126
|
+
.owd-desktop--nuxt.dark .owd-desktop__scroll,
|
|
127
|
+
.dark .owd-desktop--nuxt .desktop-window__content,
|
|
128
|
+
.dark .owd-desktop--nuxt .owd-desktop__scroll {
|
|
129
|
+
scrollbar-color: rgb(255 255 255 / 0.22) transparent;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.dark .owd-desktop--nuxt .desktop-window__content::-webkit-scrollbar-thumb,
|
|
133
|
+
.dark .owd-desktop--nuxt .owd-desktop__scroll::-webkit-scrollbar-thumb {
|
|
134
|
+
background-color: rgb(255 255 255 / 0.22);
|
|
135
|
+
}
|
|
136
|
+
</style>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
|
3
|
+
import { useNuxtStartMenu } from '../composables/useNuxtStartMenu'
|
|
4
|
+
|
|
5
|
+
const { open, searchQuery, filteredEntries, close, launchEntry } =
|
|
6
|
+
useNuxtStartMenu()
|
|
7
|
+
|
|
8
|
+
const searchInput = ref<HTMLInputElement | null>(null)
|
|
9
|
+
|
|
10
|
+
watch(open, (isOpen) => {
|
|
11
|
+
if (isOpen) {
|
|
12
|
+
// Wait for the panel to be mounted before focusing the field.
|
|
13
|
+
setTimeout(() => searchInput.value?.focus(), 50)
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
function onKeydown(event: KeyboardEvent) {
|
|
18
|
+
if (event.key === 'Escape' && open.value) {
|
|
19
|
+
close()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
onMounted(() => window.addEventListener('keydown', onKeydown))
|
|
24
|
+
onUnmounted(() => window.removeEventListener('keydown', onKeydown))
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<Transition
|
|
29
|
+
enter-active-class="transition duration-150 ease-out"
|
|
30
|
+
enter-from-class="opacity-0 -translate-y-1"
|
|
31
|
+
leave-active-class="transition duration-100 ease-in"
|
|
32
|
+
leave-to-class="opacity-0 -translate-y-1"
|
|
33
|
+
>
|
|
34
|
+
<div
|
|
35
|
+
v-if="open"
|
|
36
|
+
class="fixed inset-0 z-40 flex items-start justify-center pt-24"
|
|
37
|
+
@click="close"
|
|
38
|
+
>
|
|
39
|
+
<!-- Overlay scrim -->
|
|
40
|
+
<div class="absolute inset-0 bg-gray-950/10 dark:bg-black/40" />
|
|
41
|
+
|
|
42
|
+
<!-- Command palette -->
|
|
43
|
+
<div
|
|
44
|
+
class="relative w-full max-w-xl mx-4 flex flex-col bg-white/95 dark:bg-gray-900/95 backdrop-blur-xl border border-gray-200 dark:border-gray-800 rounded-xl shadow-2xl overflow-hidden"
|
|
45
|
+
@click.stop
|
|
46
|
+
>
|
|
47
|
+
<!-- Search -->
|
|
48
|
+
<div class="p-3 border-b border-gray-200 dark:border-gray-800">
|
|
49
|
+
<div
|
|
50
|
+
class="flex items-center gap-2.5 h-11 px-3.5 rounded-lg bg-gray-100/80 dark:bg-gray-800/60 ring-1 ring-inset ring-transparent focus-within:ring-primary-500/60 focus-within:bg-white dark:focus-within:bg-gray-900 transition"
|
|
51
|
+
>
|
|
52
|
+
<UIcon
|
|
53
|
+
name="i-heroicons-magnifying-glass"
|
|
54
|
+
class="w-4 h-4 flex-shrink-0 text-gray-400 dark:text-gray-500"
|
|
55
|
+
/>
|
|
56
|
+
<input
|
|
57
|
+
ref="searchInput"
|
|
58
|
+
v-model="searchQuery"
|
|
59
|
+
type="text"
|
|
60
|
+
placeholder="Search apps..."
|
|
61
|
+
autocomplete="off"
|
|
62
|
+
spellcheck="false"
|
|
63
|
+
class="flex-1 min-w-0 h-full bg-transparent border-0 outline-none focus:outline-none focus:ring-0 text-sm text-gray-900 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- Results -->
|
|
69
|
+
<div class="flex-1 overflow-y-auto owd-desktop__scroll max-h-[50vh] p-2">
|
|
70
|
+
<div
|
|
71
|
+
v-if="filteredEntries.length === 0"
|
|
72
|
+
class="text-center py-12 text-gray-500 dark:text-gray-400 text-sm"
|
|
73
|
+
>
|
|
74
|
+
No apps found.
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div class="grid grid-cols-1 gap-0.5">
|
|
78
|
+
<button
|
|
79
|
+
v-for="entry in filteredEntries"
|
|
80
|
+
:key="entry.application.id"
|
|
81
|
+
class="group flex items-center gap-3 p-2.5 rounded-lg hover:bg-gray-100/70 dark:hover:bg-gray-800/60 focus:bg-gray-100/70 dark:focus:bg-gray-800/60 focus:outline-none transition-colors text-left"
|
|
82
|
+
@click="launchEntry(entry)"
|
|
83
|
+
>
|
|
84
|
+
<div
|
|
85
|
+
class="flex-shrink-0 flex items-center justify-center w-9 h-9 bg-gray-100 dark:bg-gray-800 rounded-md group-hover:bg-white dark:group-hover:bg-gray-900 transition-colors"
|
|
86
|
+
>
|
|
87
|
+
<UIcon
|
|
88
|
+
v-if="entry.icon"
|
|
89
|
+
:name="entry.icon"
|
|
90
|
+
class="w-5 h-5 text-gray-700 dark:text-gray-300"
|
|
91
|
+
/>
|
|
92
|
+
<UIcon
|
|
93
|
+
v-else
|
|
94
|
+
name="i-heroicons-window"
|
|
95
|
+
class="w-5 h-5 text-gray-700 dark:text-gray-300"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="flex flex-col min-w-0">
|
|
99
|
+
<span
|
|
100
|
+
class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate"
|
|
101
|
+
>
|
|
102
|
+
{{ entry.title }}
|
|
103
|
+
</span>
|
|
104
|
+
<span
|
|
105
|
+
v-if="entry.description"
|
|
106
|
+
class="text-xs text-gray-500 dark:text-gray-400 truncate"
|
|
107
|
+
>
|
|
108
|
+
{{ entry.description }}
|
|
109
|
+
</span>
|
|
110
|
+
</div>
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</Transition>
|
|
117
|
+
</template>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
|
3
|
+
import { useColorMode } from '#imports'
|
|
4
|
+
import { useNuxtStartMenu } from '../composables/useNuxtStartMenu'
|
|
5
|
+
|
|
6
|
+
const { toggle: toggleStart, open: startOpen } = useNuxtStartMenu()
|
|
7
|
+
|
|
8
|
+
const time = ref('')
|
|
9
|
+
const dateLabel = ref('')
|
|
10
|
+
let timer: any
|
|
11
|
+
|
|
12
|
+
onMounted(() => {
|
|
13
|
+
const update = () => {
|
|
14
|
+
const d = new Date()
|
|
15
|
+
time.value = d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
16
|
+
dateLabel.value = d.toLocaleDateString([], { weekday: 'short', month: 'short', day: 'numeric' })
|
|
17
|
+
}
|
|
18
|
+
update()
|
|
19
|
+
timer = setInterval(update, 1000)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
onUnmounted(() => {
|
|
23
|
+
clearInterval(timer)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const colorMode = useColorMode()
|
|
27
|
+
const isDark = computed({
|
|
28
|
+
get () {
|
|
29
|
+
return colorMode.value === 'dark'
|
|
30
|
+
},
|
|
31
|
+
set () {
|
|
32
|
+
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<header class="h-[60px] w-full flex items-center justify-between px-4 sm:px-6 lg:px-8 bg-white/75 dark:bg-gray-900/75 backdrop-blur border-b border-gray-200 dark:border-gray-800 flex-shrink-0 select-none z-50">
|
|
39
|
+
<!-- Left Section: Logo/Start Button -->
|
|
40
|
+
<div class="flex items-center gap-4">
|
|
41
|
+
<button
|
|
42
|
+
@click="toggleStart"
|
|
43
|
+
class="flex items-center gap-2 hover:opacity-80 transition-opacity"
|
|
44
|
+
aria-label="Toggle Start Menu"
|
|
45
|
+
>
|
|
46
|
+
<UIcon name="i-heroicons-cube-transparent" class="w-6 h-6 text-green-500" />
|
|
47
|
+
<span class="font-bold text-gray-900 dark:text-white text-lg tracking-tight hidden sm:block">Nuxt Desktop</span>
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- Center Section: Title/Clock -->
|
|
52
|
+
<div class="flex flex-col items-center justify-center">
|
|
53
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ time }}</span>
|
|
54
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">{{ dateLabel }}</span>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Right Section: Quick controls -->
|
|
58
|
+
<div class="flex items-center gap-3">
|
|
59
|
+
<UButton
|
|
60
|
+
:icon="isDark ? 'i-heroicons-moon' : 'i-heroicons-sun'"
|
|
61
|
+
color="gray"
|
|
62
|
+
variant="ghost"
|
|
63
|
+
class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
|
64
|
+
@click="isDark = !isDark"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
</header>
|
|
68
|
+
</template>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useWindowDragHandlers } from "@owdproject/nuxt/runtime/composables/useWindowDragHandlers"
|
|
3
|
+
const props = defineProps({
|
|
4
|
+
config: { type: null, required: false },
|
|
5
|
+
window: { type: null, required: false },
|
|
6
|
+
content: { type: null, required: false }
|
|
7
|
+
})
|
|
8
|
+
const { onDragStart, onDragEnd } = useWindowDragHandlers(
|
|
9
|
+
() => props.window
|
|
10
|
+
)
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<DesktopCoreWindow
|
|
15
|
+
v-bind="$props"
|
|
16
|
+
v-show="window?.state?.active !== false"
|
|
17
|
+
@drag:start="onDragStart"
|
|
18
|
+
@drag:end="onDragEnd"
|
|
19
|
+
>
|
|
20
|
+
<div class="flex flex-col h-full bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-800 rounded-lg shadow-xl overflow-hidden">
|
|
21
|
+
<DesktopWindowNav>
|
|
22
|
+
<template #prepend>
|
|
23
|
+
<slot name="nav-prepend" />
|
|
24
|
+
</template>
|
|
25
|
+
<template #append>
|
|
26
|
+
<slot name="nav-append" />
|
|
27
|
+
</template>
|
|
28
|
+
</DesktopWindowNav>
|
|
29
|
+
<div class="flex-1 min-h-0 relative">
|
|
30
|
+
<DesktopWindowContent>
|
|
31
|
+
<slot />
|
|
32
|
+
</DesktopWindowContent>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</DesktopCoreWindow>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<style scoped>
|
|
39
|
+
.owd-window {
|
|
40
|
+
box-sizing: border-box;
|
|
41
|
+
min-width: 160px;
|
|
42
|
+
min-height: 160px;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps({
|
|
3
|
+
config: { type: null, required: false },
|
|
4
|
+
window: { type: null, required: false },
|
|
5
|
+
src: { type: String, required: true }
|
|
6
|
+
});
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<DesktopWindow v-bind="$props">
|
|
11
|
+
<iframe :src="src" class="w-full h-full border-0" />
|
|
12
|
+
</DesktopWindow>
|
|
13
|
+
</template>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { inject, computed } from "vue"
|
|
3
|
+
import { useToggleWindowMaximize } from "@owdproject/nuxt/runtime/composables/useToggleWindowMaximize"
|
|
4
|
+
|
|
5
|
+
const windowController = inject<any>("windowController")
|
|
6
|
+
const toggleWindowMaximize = useToggleWindowMaximize()
|
|
7
|
+
|
|
8
|
+
function onWindowMinimize() {
|
|
9
|
+
if (!windowController?.instanced) return
|
|
10
|
+
windowController.actions.minimize()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function onWindowMaximize() {
|
|
14
|
+
toggleWindowMaximize(windowController)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function onWindowNavDestroy() {
|
|
18
|
+
if (!windowController?.instanced) return
|
|
19
|
+
windowController.actions.destroy()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const dropdownItems = computed(() => [
|
|
23
|
+
[
|
|
24
|
+
{
|
|
25
|
+
label: 'Minimize',
|
|
26
|
+
icon: 'i-heroicons-minus',
|
|
27
|
+
click: onWindowMinimize
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
label: windowController?.state?.maximized ? 'Restore' : 'Maximize',
|
|
31
|
+
icon: 'i-heroicons-squares-2x2',
|
|
32
|
+
click: onWindowMaximize
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
{
|
|
37
|
+
label: 'Close',
|
|
38
|
+
icon: 'i-heroicons-x-mark',
|
|
39
|
+
click: onWindowNavDestroy
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
])
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<DesktopCoreWindowNav
|
|
47
|
+
class="flex items-center justify-between px-3 py-1.5 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 select-none cursor-default"
|
|
48
|
+
@dblclick="onWindowMaximize"
|
|
49
|
+
>
|
|
50
|
+
<div class="flex items-center space-x-2 min-w-0">
|
|
51
|
+
<UDropdown :items="dropdownItems" :popper="{ placement: 'bottom-start' }">
|
|
52
|
+
<UButton
|
|
53
|
+
v-if="windowController?.icon"
|
|
54
|
+
:icon="windowController.icon"
|
|
55
|
+
color="gray"
|
|
56
|
+
variant="ghost"
|
|
57
|
+
size="2xs"
|
|
58
|
+
class="h-5 w-5 p-0 flex items-center justify-center rounded"
|
|
59
|
+
/>
|
|
60
|
+
<UButton
|
|
61
|
+
v-else
|
|
62
|
+
icon="i-heroicons-cog-6-tooth"
|
|
63
|
+
color="gray"
|
|
64
|
+
variant="ghost"
|
|
65
|
+
size="2xs"
|
|
66
|
+
class="h-5 w-5 p-0 flex items-center justify-center rounded"
|
|
67
|
+
/>
|
|
68
|
+
</UDropdown>
|
|
69
|
+
|
|
70
|
+
<div
|
|
71
|
+
v-if="windowController?.title"
|
|
72
|
+
class="text-xs font-semibold text-gray-700 dark:text-gray-300 truncate"
|
|
73
|
+
v-text="windowController?.title"
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div class="flex items-center space-x-1 flex-shrink-0">
|
|
78
|
+
<UButton
|
|
79
|
+
v-if="!windowController?.instanced || windowController?.isMinimizable"
|
|
80
|
+
icon="i-heroicons-minus"
|
|
81
|
+
size="2xs"
|
|
82
|
+
color="gray"
|
|
83
|
+
variant="ghost"
|
|
84
|
+
class="h-5 w-5 p-0 flex items-center justify-center rounded"
|
|
85
|
+
@mousedown.stop
|
|
86
|
+
@click.stop="onWindowMinimize"
|
|
87
|
+
/>
|
|
88
|
+
<UButton
|
|
89
|
+
v-if="windowController?.isMaximizable"
|
|
90
|
+
icon="i-heroicons-squares-2x2"
|
|
91
|
+
size="2xs"
|
|
92
|
+
color="gray"
|
|
93
|
+
variant="ghost"
|
|
94
|
+
class="h-5 w-5 p-0 flex items-center justify-center rounded"
|
|
95
|
+
@mousedown.stop
|
|
96
|
+
@click.stop="onWindowMaximize"
|
|
97
|
+
/>
|
|
98
|
+
<UButton
|
|
99
|
+
v-if="!windowController?.instanced || windowController?.isDestroyable"
|
|
100
|
+
icon="i-heroicons-x-mark"
|
|
101
|
+
size="2xs"
|
|
102
|
+
color="red"
|
|
103
|
+
variant="ghost"
|
|
104
|
+
class="h-5 w-5 p-0 flex items-center justify-center rounded hover:bg-red-500 hover:text-white"
|
|
105
|
+
@mousedown.stop
|
|
106
|
+
@click.stop="onWindowNavDestroy"
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
</DesktopCoreWindowNav>
|
|
110
|
+
</template>
|