mirror of https://github.com/nocodb/nocodb
Raju Udava
5 months ago
177 changed files with 5639 additions and 1559 deletions
After Width: | Height: | Size: 982 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,135 @@
|
||||
<script setup lang="ts"> |
||||
import { useAttachmentCell } from './utils' |
||||
|
||||
const props = defineProps<{ |
||||
value: boolean |
||||
}>() |
||||
|
||||
const dialogShow = useVModel(props, 'value') |
||||
|
||||
const { onDrop: saveAttachment, isPublic, stopCamera } = useAttachmentCell()! |
||||
|
||||
const activeMenu = ref('local') |
||||
|
||||
const selectMenu = (option: string) => { |
||||
activeMenu.value = option |
||||
} |
||||
|
||||
const closeModal = (value: boolean) => { |
||||
dialogShow.value = value |
||||
} |
||||
|
||||
const saveAttachments = async (files: File[]) => { |
||||
await saveAttachment(files, {} as any) |
||||
dialogShow.value = false |
||||
} |
||||
|
||||
watch(activeMenu, (newVal, oldValue) => { |
||||
// Stop camera when switching to another menu |
||||
if (oldValue === 'webcam' && newVal !== 'webcam') { |
||||
// When the menu is switched when the startCamera function is called, the videoStream might not have initialized yet |
||||
// So, we need to wait for a while before stopping the camera |
||||
setTimeout(() => { |
||||
stopCamera() |
||||
}, 1000) |
||||
} |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<NcModal |
||||
v-model:visible="dialogShow" |
||||
:show-separator="false" |
||||
size="medium" |
||||
width="50rem" |
||||
wrap-class-name="nc-modal-attachment-create" |
||||
class="!rounded-md" |
||||
@keydown.esc="dialogShow = false" |
||||
> |
||||
<div class="flex h-full flex-row"> |
||||
<div style="border-top-left-radius: 1rem; border-bottom-left-radius: 1rem" class="px-2 !-full flex-grow bg-gray-100"> |
||||
<NcMenu class="!h-full !bg-gray-100"> |
||||
<NcMenuItem |
||||
key="local" |
||||
:class="{ |
||||
'active-menu': activeMenu === 'local', |
||||
}" |
||||
@click="selectMenu('local')" |
||||
> |
||||
<div class="flex gap-2 items-center"> |
||||
<GeneralIcon icon="file" /> |
||||
{{ $t('title.localFiles') }} |
||||
</div> |
||||
</NcMenuItem> |
||||
<NcMenuItem |
||||
v-if="!isPublic" |
||||
key="url" |
||||
:class="{ |
||||
'active-menu': activeMenu === 'url', |
||||
}" |
||||
@click="selectMenu('url')" |
||||
> |
||||
<div class="flex gap-2 items-center"> |
||||
<GeneralIcon icon="link2" /> |
||||
{{ $t('title.uploadViaUrl') }} |
||||
</div> |
||||
</NcMenuItem> |
||||
<NcMenuItem |
||||
key="webcam" |
||||
:class="{ |
||||
'active-menu': activeMenu === 'webcam', |
||||
}" |
||||
@click="selectMenu('webcam')" |
||||
> |
||||
<div class="flex gap-2 items-center"> |
||||
<GeneralIcon icon="camera" /> |
||||
{{ $t('title.webcam') }} |
||||
</div> |
||||
</NcMenuItem> |
||||
</NcMenu> |
||||
</div> |
||||
|
||||
<div style="height: 425px" class="w-full p-2"> |
||||
<LazyCellAttachmentUploadProvidersLocal |
||||
v-show="activeMenu === 'local'" |
||||
@update:visible="closeModal" |
||||
@upload="(e) => saveAttachments(e)" |
||||
/> |
||||
|
||||
<LazyCellAttachmentUploadProvidersCamera |
||||
v-if="activeMenu === 'webcam'" |
||||
@update:visible="closeModal" |
||||
@upload="(e) => saveAttachments(e)" |
||||
/> |
||||
|
||||
<LazyCellAttachmentUploadProvidersUrl |
||||
v-if="activeMenu === 'url'" |
||||
@update:visible="closeModal" |
||||
@upload="(e) => saveAttachments(e)" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</NcModal> |
||||
</template> |
||||
|
||||
<style lang="scss"> |
||||
.nc-modal-attachment-create { |
||||
.active-menu { |
||||
@apply !bg-gray-200 font-sembold text-brand-500 rounded-md; |
||||
} |
||||
} |
||||
|
||||
.nc-modal-attachment-create { |
||||
.nc-modal { |
||||
@apply !p-0; |
||||
} |
||||
} |
||||
</style> |
||||
|
||||
<style scoped lang="scss"> |
||||
:deep(.ant-menu-inline), |
||||
:deep(.ant-menu-vertical), |
||||
:deep(.ant-menu-vertical-left) { |
||||
border-right: none !important; |
||||
} |
||||
</style> |
@ -0,0 +1,147 @@
|
||||
<script setup lang="ts"> |
||||
import { useAttachmentCell } from '../utils' |
||||
|
||||
const emits = defineEmits<{ |
||||
'update:visible': [value: boolean] |
||||
'upload': [fileList: File[]] |
||||
}>() |
||||
|
||||
const { isLoading, startCamera: _startCamera, stopCamera: _stopCamera, videoStream, permissionGranted } = useAttachmentCell()! |
||||
|
||||
const capturedImage = ref<null | File>(null) |
||||
const videoRef = ref<HTMLVideoElement | undefined>() |
||||
const canvasRef = ref<HTMLCanvasElement | undefined>() |
||||
|
||||
const startCamera = async () => { |
||||
try { |
||||
await _startCamera() |
||||
if (!videoRef.value || !videoStream.value) return |
||||
videoRef.value.srcObject = videoStream.value |
||||
} catch (error) {} |
||||
} |
||||
|
||||
const stopCamera = () => { |
||||
_stopCamera() |
||||
if (videoRef.value) { |
||||
videoRef.value.srcObject = null |
||||
} |
||||
} |
||||
|
||||
const retakeImage = () => { |
||||
capturedImage.value = null |
||||
startCamera() |
||||
} |
||||
|
||||
const captureImage = () => { |
||||
const video = videoRef.value |
||||
const canvas = canvasRef.value |
||||
|
||||
if (!video || !canvas) return |
||||
|
||||
canvas.width = video.videoWidth |
||||
canvas.height = video.videoHeight |
||||
const context = canvas.getContext('2d') |
||||
|
||||
if (context) { |
||||
canvas.style.display = 'block' |
||||
context.translate(canvas.width, 0) |
||||
context.scale(-1, 1) |
||||
context.drawImage(video, 0, 0, canvas.width, canvas.height) |
||||
canvas.toBlob((blob) => { |
||||
if (!blob) return |
||||
capturedImage.value = new File([blob], `${new Date().toDateString()}.png`, { type: 'image/png' }) |
||||
}, 'image/png') |
||||
stopCamera() |
||||
} |
||||
} |
||||
|
||||
const closeMenu = () => { |
||||
emits('update:visible', false) |
||||
} |
||||
|
||||
onMounted(() => { |
||||
startCamera() |
||||
}) |
||||
|
||||
onBeforeUnmount(() => { |
||||
stopCamera() |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="w-full relative h-full"> |
||||
<NcTooltip class="absolute top-3 right-2"> |
||||
<NcButton type="text" class="!border-0" size="xsmall" @click="closeMenu"> |
||||
<GeneralIcon icon="close" /> |
||||
</NcButton> |
||||
|
||||
<template #title> {{ $t('general.close') }} </template> |
||||
</NcTooltip> |
||||
<div v-if="!permissionGranted" class="w-full h-full flex bg-gray-50 items-center justify-center"> |
||||
<div |
||||
class="flex flex-col hover:bg-white p-2 cursor-pointer rounded-md !transition-all transition-ease-in-out duration-300 gap-2 items-center justify-center" |
||||
@click="startCamera" |
||||
> |
||||
<div class="p-5 bg-white rounded-md shadow-sm"> |
||||
<mdi-camera class="text-4xl text-gray-800" /> |
||||
</div> |
||||
<h1 class="text-gray-800 font-semibold text-center text-xl"> |
||||
{{ $t('labels.allowAccessToYourCamera') }} |
||||
</h1> |
||||
</div> |
||||
</div> |
||||
<div |
||||
v-else |
||||
:class="{ |
||||
'py-8': !capturedImage, |
||||
'pt-8 pb-2': capturedImage, |
||||
}" |
||||
class="w-full gap-3 h-full flex-col flex items-center justify-between" |
||||
> |
||||
<div v-show="!capturedImage" class="w-full gap-3 h-full flex-col flex items-center justify-between"> |
||||
<video ref="videoRef" class="rounded-md" style="width: 400px" autoplay></video> |
||||
|
||||
<NcButton class="!rounded-full !px-0" @click="captureImage"> |
||||
<mdi-camera class="text-xl" /> |
||||
</NcButton> |
||||
</div> |
||||
|
||||
<div v-show="capturedImage" class="flex group flex-col gap-1"> |
||||
<canvas ref="canvasRef" class="rounded-md" style="width: 400px; display: none"></canvas> |
||||
|
||||
<div class="relative text-[12px] font-semibold text-gray-800 flex"> |
||||
<div class="flex-auto truncate line-height-4"> |
||||
{{ capturedImage?.name }} |
||||
</div> |
||||
<div class="flex-none hide-ui transition-all transition-ease-in-out !h-4 flex items-center bg-white"> |
||||
<NcTooltip placement="bottom"> |
||||
<template #title> {{ $t('title.removeFile') }} </template> |
||||
<component :is="iconMap.delete" class="!text-red-500 cursor-pointer" @click="retakeImage" /> |
||||
</NcTooltip> |
||||
</div> |
||||
</div> |
||||
<div class="flex-none text-[10px] font-semibold text-gray-500"> |
||||
{{ formatBytes(capturedImage?.size, 0) }} |
||||
</div> |
||||
</div> |
||||
<div v-show="capturedImage" class="flex gap-2 pr-2 bottom-1 relative w-full items-center justify-end"> |
||||
<NcButton :disabled="isLoading" type="secondary" size="small" @click="closeMenu"> |
||||
{{ $t('labels.cancel') }} |
||||
</NcButton> |
||||
|
||||
<NcButton :loading="isLoading" size="small" @click="emits('upload', [capturedImage] as File[])"> |
||||
<template v-if="!isLoading"> {{ $t('labels.uploadImage') }} </template> |
||||
|
||||
<template v-else> {{ $t('labels.uploading') }} </template> |
||||
</NcButton> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
video { |
||||
-webkit-transform: scaleX(-1); |
||||
transform: scaleX(-1); |
||||
} |
||||
</style> |
@ -0,0 +1,190 @@
|
||||
<script setup lang="ts"> |
||||
import { useAttachmentCell } from '../utils' |
||||
|
||||
const emits = defineEmits<{ |
||||
'update:visible': [value: boolean] |
||||
'upload': [fileList: File[]] |
||||
}>() |
||||
|
||||
const dropZoneRef = ref<HTMLDivElement>() |
||||
|
||||
const tempFiles = ref<File[]>([]) |
||||
|
||||
const { isLoading } = useAttachmentCell()! |
||||
|
||||
const { files, open: _open } = useFileDialog({ |
||||
reset: true, |
||||
}) |
||||
|
||||
watch(files, (newFiles) => { |
||||
if (!newFiles) return |
||||
Object.values(newFiles).forEach((file) => { |
||||
tempFiles.value.push(file) |
||||
}) |
||||
}) |
||||
|
||||
const onDrop = (files: File[], event: DragEvent) => { |
||||
tempFiles.value.push(...files) |
||||
event.preventDefault() |
||||
event.stopPropagation() |
||||
} |
||||
|
||||
const thumbnails = computedAsync(async () => { |
||||
const map = new Map() |
||||
await Promise.all( |
||||
tempFiles.value.map(async (file) => { |
||||
const thumbnail = await createThumbnail(file) |
||||
if (thumbnail) { |
||||
map.set(file, thumbnail) |
||||
} |
||||
}), |
||||
) |
||||
return map |
||||
}) |
||||
|
||||
const onRemoveFileClick = (file: File) => { |
||||
tempFiles.value = tempFiles.value.filter((f) => f !== file) |
||||
} |
||||
|
||||
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop) |
||||
|
||||
const clearAll = () => { |
||||
tempFiles.value = [] |
||||
} |
||||
|
||||
const open = () => { |
||||
_open() |
||||
} |
||||
|
||||
const closeMenu = () => { |
||||
emits('update:visible', false) |
||||
} |
||||
|
||||
onBeforeUnmount(() => { |
||||
tempFiles.value = [] |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div |
||||
:class="{ |
||||
'flex flex-col relative justify-center items-center': !tempFiles.length, |
||||
}" |
||||
class="w-full p-2 h-full" |
||||
> |
||||
<NcTooltip v-if="tempFiles.length === 0" class="absolute top-3 right-3"> |
||||
<NcButton type="text" class="!border-0" size="xsmall" @click="closeMenu"> |
||||
<GeneralIcon icon="close" /> |
||||
</NcButton> |
||||
|
||||
<template #title> {{ $t('general.close') }} </template> |
||||
</NcTooltip> |
||||
|
||||
<div v-if="tempFiles.length > 0" class="flex w-full border-b-1 py-1 h-9.5 items-center justify-between top-0"> |
||||
<NcButton type="text" size="small" @click="clearAll"> |
||||
{{ $t('labels.clearAllFiles') }} |
||||
</NcButton> |
||||
|
||||
<span class="text-xs"> |
||||
{{ tempFiles.length }} files, total size: |
||||
{{ |
||||
formatBytes( |
||||
tempFiles.reduce((acc, file) => acc + file.size, 0), |
||||
0, |
||||
) |
||||
}} |
||||
</span> |
||||
|
||||
<NcButton type="text" size="small" @click="open"> |
||||
<div class="flex gap-1 items-center"> |
||||
<component :is="iconMap.plus" /> |
||||
{{ $t('labels.addMore') }} |
||||
</div> |
||||
</NcButton> |
||||
</div> |
||||
<div |
||||
ref="dropZoneRef" |
||||
:class="{ |
||||
'border-brand-500': isOverDropZone, |
||||
'border-dashed border-1': !tempFiles.length, |
||||
}" |
||||
data-testid="attachment-drop-zone" |
||||
:style="`height: ${tempFiles.length > 0 ? '324px' : '100%'}`" |
||||
class="flex flex-col items-center justify-center h-full w-full flex-grow-1 rounded-lg" |
||||
@click="tempFiles.length > 0 ? () => {} : open()" |
||||
> |
||||
<div v-if="!tempFiles.length" class="flex cursor-pointer items-center justify-center flex-col gap-2"> |
||||
<template v-if="!isOverDropZone"> |
||||
<component :is="iconMap.upload" class="w-5 h-5" /> |
||||
<h1> |
||||
{{ $t('labels.clickTo') }} |
||||
|
||||
<span class="font-semibold"> {{ $t('labels.browseFiles') }} </span> |
||||
{{ $t('general.or') }} |
||||
<span class="font-semibold"> {{ $t('labels.dragFilesHere') }} </span> |
||||
|
||||
{{ $t('labels.toUpload') }} |
||||
</h1> |
||||
</template> |
||||
<template v-if="isOverDropZone"> |
||||
<component :is="iconMap.upload" class="w-5 text-brand-500 h-5" /> |
||||
<h1 class="text-brand-500 font-bold">{{ $t('labels.dropHere') }}</h1> |
||||
</template> |
||||
</div> |
||||
<template v-else> |
||||
<div |
||||
class="grid overflow-y-auto flex-grow-1 nc-scrollbar-md grid-cols-4 w-full h-full items-start py-2 justify-center gap-4" |
||||
> |
||||
<div v-for="file in tempFiles" :key="file.name" class="flex gap-1.5 group min-w-34 max-w-28 pb-4 flex-col relative"> |
||||
<div |
||||
v-if="!thumbnails.get(file)" |
||||
style="height: 140px" |
||||
class="flex items-center justify-center rounded-md bg-gray-300" |
||||
> |
||||
<component :is="iconMap.file" class="w-16 h-16" /> |
||||
</div> |
||||
<img v-else :src="thumbnails.get(file)" style="height: 140px" alt="thumbnail" class="rounded-md object-cover" /> |
||||
|
||||
<div class="relative text-[12px] font-semibold items-center text-gray-800 flex"> |
||||
<NcTooltip class="flex-auto truncate" placement="bottom"> |
||||
<template #title> {{ file.name }} </template> |
||||
{{ file.name }} |
||||
</NcTooltip> |
||||
|
||||
<div class="flex-none hide-ui transition-all transition-ease-in-out !h-4 flex items-center bg-white"> |
||||
<NcTooltip placement="bottom"> |
||||
<template #title> {{ $t('title.removeFile') }} </template> |
||||
<component :is="iconMap.delete" class="!text-red-500 w-3 h-3 cursor-pointer" @click="onRemoveFileClick(file)" /> |
||||
</NcTooltip> |
||||
</div> |
||||
</div> |
||||
<div class="flex-none text-[10px] font-semibold text-gray-500"> |
||||
{{ formatBytes(file.size, 0) }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</div> |
||||
<div v-if="tempFiles.length" class="flex gap-2 pt-1 bg-white w-full items-center justify-end"> |
||||
<NcButton :disabled="isLoading" type="secondary" size="small" @click="closeMenu"> |
||||
{{ $t('labels.cancel') }} |
||||
</NcButton> |
||||
|
||||
<NcButton :loading="isLoading" data-testid="nc-upload-file" size="small" @click="emits('upload', tempFiles)"> |
||||
<template v-if="isLoading"> |
||||
{{ $t('labels.uploading') }} |
||||
</template> |
||||
<template v-else> {{ $t('general.upload') }} {{ tempFiles.length }} {{ $t('objects.files') }} </template> |
||||
</NcButton> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss"> |
||||
.hide-ui { |
||||
@apply h-0 w-0 overflow-hidden whitespace-nowrap; |
||||
.group:hover & { |
||||
@apply h-auto w-auto overflow-visible whitespace-normal; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,173 @@
|
||||
<script setup lang="ts"> |
||||
import { useAttachmentCell } from '../utils' |
||||
|
||||
const emits = defineEmits<{ |
||||
'update:visible': [value: boolean] |
||||
}>() |
||||
|
||||
const { openAttachment } = useAttachment() |
||||
|
||||
const { uploadViaUrl, updateModelValue } = useAttachmentCell()! |
||||
|
||||
const closeMenu = () => { |
||||
emits('update:visible', false) |
||||
} |
||||
|
||||
const inputRef = ref<HTMLInputElement | null>(null) |
||||
|
||||
const tempAttachments = ref< |
||||
{ |
||||
url?: string |
||||
mimetype: string |
||||
title: string |
||||
path?: string |
||||
size: number |
||||
}[] |
||||
>([]) |
||||
|
||||
const onSave = async () => { |
||||
updateModelValue(tempAttachments.value) |
||||
closeMenu() |
||||
} |
||||
|
||||
const url = ref('') |
||||
|
||||
const isParsing = ref(false) |
||||
|
||||
const deleteAttachment = (index: number) => { |
||||
tempAttachments.value.splice(index, 1) |
||||
} |
||||
|
||||
const isValidUrl = ref(true) |
||||
|
||||
const errorMessage = ref('') |
||||
|
||||
const uploadAndParseUrl = async () => { |
||||
if (!isValidURL(url.value)) { |
||||
isValidUrl.value = false |
||||
return |
||||
} |
||||
isValidUrl.value = true |
||||
|
||||
try { |
||||
isParsing.value = true |
||||
const data = await uploadViaUrl({ url: url.value }, true) |
||||
|
||||
if (typeof data !== 'string' && data?.length) { |
||||
tempAttachments.value = [...data, ...tempAttachments.value] |
||||
url.value = '' |
||||
} else { |
||||
isValidUrl.value = false |
||||
errorMessage.value = data |
||||
} |
||||
} finally { |
||||
isParsing.value = false |
||||
} |
||||
|
||||
await nextTick(() => { |
||||
inputRef.value?.focus() |
||||
}) |
||||
} |
||||
|
||||
watch(url, () => { |
||||
isValidUrl.value = true |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="py-2 px-2 h-full flex gap-2 flex-col"> |
||||
<div class="flex w-full bg-white border-b-1 py-1 justify-between"> |
||||
<h1 class="font-semibold"> |
||||
{{ $t('title.uploadViaUrl') }} |
||||
</h1> |
||||
|
||||
<NcTooltip> |
||||
<NcButton type="secondary" class="!border-0" size="xsmall" @click="closeMenu"> |
||||
<GeneralIcon icon="close" /> |
||||
</NcButton> |
||||
|
||||
<template #title> {{ $t('general.close') }} </template> |
||||
</NcTooltip> |
||||
</div> |
||||
|
||||
<div class="flex-grow bg-white"> |
||||
<h1 class="text-gray-800 font-semibold"> |
||||
{{ $t('labels.addFilesFromUrl') }} |
||||
</h1> |
||||
<div class="flex bg-white gap-2"> |
||||
<a-input |
||||
ref="inputRef" |
||||
v-model:value="url" |
||||
type="url" |
||||
:disabled="isParsing" |
||||
class="flex-grow" |
||||
placeholder="www.google.com/hello.png" |
||||
@keydown.enter="uploadAndParseUrl" |
||||
/> |
||||
|
||||
<NcButton :disabled="!isValidUrl" :loading="isParsing" size="small" class="!h-10 !px-4" @click="uploadAndParseUrl"> |
||||
{{ $t('general.upload') }} |
||||
</NcButton> |
||||
</div> |
||||
<span v-if="url.length > 0 && !isValidUrl" class="text-red-500 text-[13px]"> |
||||
{{ errorMessage.length > 0 ? errorMessage : $t('labels.enterValidUrl') }} |
||||
</span> |
||||
<template v-if="tempAttachments.length > 0"> |
||||
<div :style="`height: ${!isValidUrl ? '208px' : '230px'}`" class="overflow-y-auto bg-white mt-1 !max-h-[250px]"> |
||||
<h1 class="font-semibold capitalize sticky top-0 bg-white text-gray-800"> |
||||
{{ $t('objects.files') }} |
||||
</h1> |
||||
|
||||
<div |
||||
v-for="(file, index) in tempAttachments" |
||||
:key="index" |
||||
class="flex w-full items-center mt-2 h-10 px-2 py-1 border-1 rounded-md" |
||||
> |
||||
<div class="flex w-full items-center gap-2"> |
||||
<GeneralIcon icon="file" /> |
||||
|
||||
{{ file.title }} |
||||
<NcTooltip class="hover:underline"> |
||||
<NuxtLink class="flex items-center" target="_blank" @click="openAttachment(file)"> |
||||
<component :is="iconMap.externalLink" class="w-3.5 h-3.5 text-gray-500" /> |
||||
</NuxtLink> |
||||
|
||||
<template #title> {{ $t('labels.openFile') }} </template> |
||||
</NcTooltip> |
||||
</div> |
||||
|
||||
<div class="flex-grow-1"></div> |
||||
|
||||
<NcTooltip> |
||||
<template #title> {{ $t('title.removeFile') }} </template> |
||||
|
||||
<NcButton type="text" size="xsmall" @click="deleteAttachment(index)"> |
||||
<GeneralIcon icon="close" /> |
||||
</NcButton> |
||||
</NcTooltip> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</div> |
||||
|
||||
<div class="flex gap-2 items-center justify-end"> |
||||
<NcButton :disabled="isParsing" type="secondary" size="small" @click="closeMenu"> {{ $t('labels.cancel') }} </NcButton> |
||||
<NcButton :disabled="isParsing || tempAttachments.length === 0" size="small" @click="onSave"> |
||||
{{ $t('activity.addFiles') }}</NcButton |
||||
> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
.ant-input::placeholder { |
||||
@apply text-gray-500; |
||||
} |
||||
|
||||
.ant-input { |
||||
@apply px-4 rounded-lg py-2 w-full border-1 focus:border-brand-500 border-gray-200 !ring-0; |
||||
} |
||||
a { |
||||
@apply !text-gray-700 !no-underline !hover:underline; |
||||
} |
||||
</style> |
@ -0,0 +1,5 @@
|
||||
<script setup lang="ts"></script> |
||||
|
||||
<template></template> |
||||
|
||||
<style scoped lang="scss"></style> |
@ -0,0 +1,13 @@
|
||||
<script setup lang="ts"> |
||||
defineProps<{ |
||||
group: any |
||||
maxDepth: any |
||||
scrollLeft: number |
||||
}>() |
||||
</script> |
||||
|
||||
<template> |
||||
<div></div> |
||||
</template> |
||||
|
||||
<style scoped lang="scss"></style> |
@ -0,0 +1,15 @@
|
||||
const useServerConfig = () => { |
||||
const getConfig = async () => {} |
||||
|
||||
const checkMaintenance = async () => {} |
||||
|
||||
const dismissMaintenance = () => {} |
||||
|
||||
return { |
||||
getConfig, |
||||
checkMaintenance, |
||||
dismissMaintenance, |
||||
} |
||||
} |
||||
|
||||
export default useServerConfig |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue