Skip to content

Commit 94fcbd9

Browse files
authored
Fix PageHeader collision with Sidebar, update dependencies, and clean up code (#44)
* fix: PageHeader overlaps Sidebar on small screens * refactor: move appName and appDescription to defineNuxtConfig * chore: replace clipboard with vueuse useClipboard * chore(deps): remove unused deps * chore: bump deps Add type conversion `... as Shape` to AutoForm and AutoFormFiledObject for compatibility with vue-tsc v3. Add `import ‘vue-sonner/style.css’` for compatibility with vue-sonner v2. Move @aws-sdk/types to dependencies. Format code.
1 parent bd3ea4b commit 94fcbd9

13 files changed

Lines changed: 1187 additions & 1827 deletions

‎app.config.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import navs from '~/config/navs'
2-
import type { AppConfig } from '~/types/app-config'
32

4-
export default defineAppConfig<AppConfig>({
5-
navs: navs,
6-
name: process.env.APP_NAME || 'RustFS',
7-
description: process.env.APP_DESCRIPTION || 'RustFS is a distributed file system written in Rust.',
3+
export default defineAppConfig({
4+
navs,
5+
icon: {
6+
mode: 'css',
7+
cssLayer: 'base',
8+
},
89
})

‎components/app-sidebar.vue‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,16 @@ import {
2222
import { computed } from 'vue'
2323
import { useI18n } from 'vue-i18n'
2424
import { useRoute } from 'vue-router'
25-
import type { AppConfig, NavItem } from '~/types/app-config'
25+
import type { NavItem } from '~/types/app-config'
2626
27-
const appConfig = useAppConfig() as unknown as AppConfig
27+
const appConfig = useAppConfig()
28+
const config = useRuntimeConfig()
2829
const route = useRoute()
2930
const { t } = useI18n()
3031
const { state } = useSidebar()
3132
const { isAdmin, canAccessPath } = usePermissions()
3233
const isCollapsed = computed(() => state.value === 'collapsed')
33-
const brandInitial = computed(() => appConfig.name?.charAt(0)?.toUpperCase() ?? 'R')
34+
const brandInitial = computed(() => config.public.appName.charAt(0).toUpperCase() ?? 'R')
3435
3536
const navGroups = computed(() => {
3637
const groups: NavItem[][] = []
@@ -42,11 +43,11 @@ const navGroups = computed(() => {
4243
if (nav.children?.length) {
4344
visibleChildren = nav.children.filter(child => {
4445
if (child.to && !canAccessPath(child.to)) return false
45-
46+
4647
// If child is admin only and user is not admin, but has permission (checked above), allow it.
4748
// If child has no path (unlikely for leaf) and is admin only, hide it.
4849
if (child.isAdminOnly && !isAdmin.value && !child.to) return false
49-
50+
5051
return true
5152
})
5253
@@ -59,10 +60,10 @@ const navGroups = computed(() => {
5960
if (nav.to && !canAccessPath(nav.to)) {
6061
continue
6162
}
62-
63+
6364
// If no path and no children (e.g. divider), check isAdminOnly
6465
if (!nav.to && nav.isAdminOnly && !isAdmin.value) {
65-
continue
66+
continue
6667
}
6768
}
6869

‎components/copy-input.vue‎

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import { Input } from '@/components/ui/input'
44
55
import { Icon } from '#components'
66
import { useMessage } from '@/lib/ui/message'
7-
import ClipboardJS from 'clipboard'
8-
import { onMounted, onUnmounted, ref } from 'vue'
97
import { useI18n } from 'vue-i18n'
108
119
const { t } = useI18n()
@@ -31,50 +29,41 @@ const props = defineProps({
3129
const model = defineModel<string>()
3230
3331
const message = useMessage()
34-
const clipboard = ref<ClipboardJS | null>(null)
3532
36-
onMounted(() => {
37-
clipboard.value = new ClipboardJS(props.copyIcon ? `#${props.id}-btn-icon` : `#${props.id}-btn`)
33+
const { copy } = useClipboard()
3834
39-
clipboard.value.on('success', function (e) {
40-
message.success(t('Copy Success'))
41-
e.clearSelection()
42-
})
35+
async function handleCopyGeneratedUrl() {
36+
try {
37+
if (!model.value) throw new Error('No value to copy')
4338
44-
clipboard.value.on('error', async function () {
45-
// If copy fails, use navigator.clipboard.writeText as fallback
46-
try {
47-
if (navigator.clipboard && model.value) {
48-
await navigator.clipboard.writeText(model.value)
49-
message.success(t('Copy Success'))
50-
} else {
51-
message.error(t('Copy Failed'))
52-
}
53-
} catch {
54-
message.error(t('Copy Failed'))
55-
}
56-
})
57-
})
39+
await copy(model.value)
5840
59-
onUnmounted(() => {
60-
clipboard.value?.destroy()
61-
clipboard.value = null
62-
})
41+
message.success(t('Copy Success'))
42+
} catch (error) {
43+
message.error(t('Copy Failed'))
44+
}
45+
}
6346
</script>
6447

6548
<template>
6649
<div class="flex h-full items-center gap-2">
6750
<Input v-model="model" :readonly="props.readonly" :id="props.id" class="flex-1" />
68-
<Button v-if="!props.copyIcon" :id="`${props.id}-btn`" :data-clipboard-target="`#${props.id}`" variant="default">
51+
<Button
52+
v-if="!props.copyIcon"
53+
@click="handleCopyGeneratedUrl"
54+
:id="`${props.id}-btn`"
55+
:data-clipboard-target="`#${props.id}`"
56+
variant="default"
57+
>
6958
{{ t('Copy') }}
7059
</Button>
7160
<Button
7261
v-else
62+
@click="handleCopyGeneratedUrl"
7363
:id="`${props.id}-btn-icon`"
7464
variant="ghost"
7565
size="sm"
7666
class="shrink-0 outline"
77-
:data-clipboard-target="`#${props.id}`"
7867
:title="t('Copy')"
7968
>
8069
<Icon :size="18" name="ri:file-copy-line" />

‎components/object/path-links.vue‎

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
</div>
1414
<Button
1515
v-if="props.objectKey"
16-
:id="`copy-path-btn-${buttonId}`"
1716
variant="ghost"
1817
size="sm"
1918
class="shrink-0"
20-
:data-clipboard-text="fullPath"
19+
@click="copy(fullPath)"
2120
:title="t('Copy Path')"
2221
>
2322
<Icon v-if="!copied" :size="16" name="ri:file-copy-line" />
@@ -30,8 +29,7 @@
3029
<script lang="ts" setup>
3130
import { Button } from '@/components/ui/button'
3231
import { Icon } from '#components'
33-
import ClipboardJS from 'clipboard'
34-
import { onMounted, onUnmounted, ref, computed } from 'vue'
32+
import { computed } from 'vue'
3533
import { useI18n } from 'vue-i18n'
3634
3735
const { t } = useI18n()
@@ -42,10 +40,6 @@ const props = defineProps<{
4240
onClick: (path: string) => any
4341
}>()
4442
45-
const clipboard = ref<ClipboardJS | null>(null)
46-
const buttonId = ref(`${Math.random().toString(36).substring(2, 15)}`)
47-
const copied = ref(false)
48-
4943
const fullPath = computed(() => {
5044
if (props.bucketName) {
5145
return `${props.bucketName}/${props.objectKey}`
@@ -65,6 +59,8 @@ const segments = computed(() =>
6559
})
6660
)
6761
62+
const { copy, copied } = useClipboard({ copiedDuring: 3000 })
63+
6864
const displaySegments = computed(() => {
6965
if (segments.value.length <= 6) {
7066
return segments.value
@@ -84,32 +80,4 @@ const handleOnClick = (segment: { value: string; index: number }) => {
8480
.join('/')
8581
props.onClick(path)
8682
}
87-
88-
const showCopiedState = () => {
89-
copied.value = true
90-
setTimeout(() => {
91-
copied.value = false
92-
}, 3000)
93-
}
94-
95-
onMounted(() => {
96-
clipboard.value = new ClipboardJS(`#copy-path-btn-${buttonId.value}`)
97-
98-
clipboard.value.on('success', function (e) {
99-
showCopiedState()
100-
e.clearSelection()
101-
})
102-
103-
clipboard.value.on('error', async function () {
104-
if (navigator.clipboard && fullPath.value) {
105-
await navigator.clipboard.writeText(fullPath.value)
106-
showCopiedState()
107-
}
108-
})
109-
})
110-
111-
onUnmounted(() => {
112-
clipboard.value?.destroy()
113-
clipboard.value = null
114-
})
11583
</script>

‎components/providers/AppUiProvider.vue‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Toaster } from '@/components/ui/sonner'
1212
import { createDialogController, dialogControllerKey } from '@/lib/ui/dialog'
1313
import { createMessageApi, messageInjectionKey } from '@/lib/ui/message'
1414
import { provide } from 'vue'
15+
import 'vue-sonner/style.css'
1516
1617
const dialogController = createDialogController()
1718
const messageApi = createMessageApi()

‎components/ui/auto-form/AutoForm.vue‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ const formComponentProps = computed(() => {
8080
<template>
8181
<component :is="formComponent" v-bind="formComponentProps">
8282
<slot name="customAutoForm" :fields="fields">
83-
<template v-for="(shape, key) of shapes" :key="key">
83+
<template v-for="(shape, key) in shapes" :key="key">
8484
<slot
8585
:shape="shape"
8686
:name="key.toString() as keyof z.infer<T>"
@@ -90,7 +90,7 @@ const formComponentProps = computed(() => {
9090
<AutoFormField
9191
:config="fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem"
9292
:field-name="key.toString()"
93-
:shape="shape"
93+
:shape="shape as Shape"
9494
/>
9595
</slot>
9696
</template>

‎components/ui/auto-form/AutoFormFieldArray.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ provide(FieldContextKey, fieldContext)
6262
</AccordionTrigger>
6363

6464
<AccordionContent>
65-
<template v-for="(field, index) of fields" :key="field.key">
65+
<template v-for="(field, index) in fields" :key="field.key">
6666
<div class="mb-4 p-1">
6767
<AutoFormField
6868
:field-name="`${fieldName}[${index}]`"

‎components/ui/auto-form/AutoFormFieldObject.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ provide(FieldContextKey, fieldContext)
6363
:config="config?.[key as keyof typeof config] as ConfigItem"
6464
:field-name="`${fieldName}.${key.toString()}`"
6565
:label="key.toString()"
66-
:shape="shape"
66+
:shape="shape as Shape"
6767
/>
6868
</template>
6969
</AccordionContent>

‎components/ui/sidebar/Sidebar.vue‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
6666
<div
6767
:class="
6868
cn(
69-
'duration-200 fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] ease-linear md:flex',
69+
'duration-200 fixed inset-y-0 z-20 hidden h-svh w-(--sidebar-width) transition-[left,right,width] ease-linear md:flex',
7070
side === 'left'
7171
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
7272
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',

‎nuxt.config.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import tailwindcss from '@tailwindcss/vite'
2+
23
const appName = process.env.APP_NAME || 'RustFS'
3-
const baseUrl = (process.env.BASE_URL || '/rustfs/console/').replace(/\/$/, '')
44
const appDescription = process.env.APP_DESCRIPTION || 'RustFS is a distributed file system written in Rust.'
5+
const baseUrl = (process.env.BASE_URL || '/rustfs/console/').replace(/\/$/, '')
56

67
// https://nuxt.com/docs/api/configuration/nuxt-config
78
export default defineNuxtConfig({
@@ -32,6 +33,8 @@ export default defineNuxtConfig({
3233
plugins: [],
3334
runtimeConfig: {
3435
public: {
36+
appName,
37+
appDescription,
3538
session: {
3639
// 临时凭证有效期
3740
durationSeconds: Number(process.env.SESSION_DURATION_SECONDS) || 3600 * 12,

0 commit comments

Comments
 (0)