Compare commits
2
Commits
b3c6dad1b1
...
a34e27cc65
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a34e27cc65 | ||
|
|
de2e53cbcf |
No files matched your search
@@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<div class="miao-message-provider" ref="rootRef">
|
||||||
|
<slot></slot>
|
||||||
|
<transition-group name="message-fade" tag="div" class="miao-message-provider-container">
|
||||||
|
<div class="miao-message" v-for="msg in messageList" :key="msg.id"
|
||||||
|
:style="{ maxWidth: maxMessageWidth + 'px' }">
|
||||||
|
<n-icon class="miao-message-item" size="18" :color="msg.iconColor">
|
||||||
|
<component :is="msg.icon"></component>
|
||||||
|
</n-icon>
|
||||||
|
<div class="miao-message-item miao-message-item-content">{{ msg.content }}</div>
|
||||||
|
</div>
|
||||||
|
</transition-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import generateId from '@/hooks/generateId';
|
||||||
|
import { NIcon } from 'naive-ui';
|
||||||
|
import { computed, onMounted, ref, shallowReactive } from 'vue';
|
||||||
|
import { InformationCircle, CheckmarkCircle, CloseCircle } from '@vicons/ionicons5'
|
||||||
|
|
||||||
|
interface MessageOptions {
|
||||||
|
type?: 'info' | 'success' | 'error'
|
||||||
|
timeout?: number
|
||||||
|
id?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
icon: any,
|
||||||
|
timer: any,
|
||||||
|
iconColor: string,
|
||||||
|
content: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconMap = {
|
||||||
|
'info': { icon: InformationCircle, color: 'blue' },
|
||||||
|
'success': { icon: CheckmarkCircle, color: 'green' },
|
||||||
|
'error': { icon: CloseCircle, color: 'red' },
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageMap = shallowReactive<Record<string, Message | undefined>>({})
|
||||||
|
const rootRef = ref<HTMLDivElement>()
|
||||||
|
const maxMessageWidth = ref<number>(-1)
|
||||||
|
|
||||||
|
const messageList = computed(() => {
|
||||||
|
return Object.keys(messageMap).filter(v => messageMap[v] !== undefined).map(v => ({
|
||||||
|
id: v,
|
||||||
|
...messageMap[v],
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
const message = (content: string, options?: MessageOptions) => {
|
||||||
|
let timer: any
|
||||||
|
const { timeout = 0, type = 'info', id = generateId() } = options ?? {}
|
||||||
|
if (timeout > 0) {
|
||||||
|
messageMap[id]?.timer && clearTimeout(messageMap[id]?.timer)
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
messageMap[id] = undefined
|
||||||
|
}, timeout)
|
||||||
|
}
|
||||||
|
messageMap[id] = {
|
||||||
|
icon: iconMap[type].icon,
|
||||||
|
iconColor: iconMap[type].color,
|
||||||
|
content,
|
||||||
|
timer
|
||||||
|
}
|
||||||
|
return (newContent: string, newOptions?: MessageOptions) => {
|
||||||
|
newOptions = newOptions ?? {}
|
||||||
|
newOptions.id = id
|
||||||
|
message(newContent, newOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// const set = message('test', { timeout: 5000 })
|
||||||
|
// setTimeout(() => {
|
||||||
|
// set('miao', { type: 'success' })
|
||||||
|
// }, 1000);
|
||||||
|
const observer = new ResizeObserver(entries => {
|
||||||
|
for (let entry of entries) {
|
||||||
|
maxMessageWidth.value = entry.contentRect.width * 0.8;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (rootRef.value) {
|
||||||
|
observer.observe(rootRef.value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
defineExpose({ message });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.miao-message-provider {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.miao-message-provider-container {
|
||||||
|
position: absolute;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
left: 50%;
|
||||||
|
top: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
.miao-message {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: max-content;
|
||||||
|
background-color: rgb(249, 249, 249);
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: rgb(255 255 255);
|
||||||
|
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, .12), 0 6px 16px 0 rgba(0, 0, 0, .08), 0 9px 28px 8px rgba(0, 0, 0, .05);
|
||||||
|
border-radius: 5px;
|
||||||
|
user-select: none;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.miao-message-item {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.miao-message-item-content {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-fade-move,
|
||||||
|
.message-fade-enter-active,
|
||||||
|
.message-fade-leave-active {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-fade-enter-from,
|
||||||
|
.message-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-fade-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+7
-4
@@ -106,13 +106,16 @@ export default function init() {
|
|||||||
if (vDirs.length === 0 && vFiles.length === 0) {
|
if (vDirs.length === 0 && vFiles.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
miao: for (let vf of vFiles)
|
if( vDirs.length ) {
|
||||||
for (let end of [".xbm", ".tif", "pjp", ".svgz", "jpg", "jpeg", "ico", "tiff", ".gif", "svg", ".jfif", ".webp", ".png", ".bmp", "pjpeg", ".avif"]) {
|
return true
|
||||||
|
}
|
||||||
|
for (let vf of vFiles)
|
||||||
|
for (let end of ["xbm", "tif", "pjp", "svgz", "jpg", "jpeg", "ico", "tiff", "gif", "svg", "jfif", "webp", "png", ".bmp", "pjpeg", "avif"]) {
|
||||||
if (vf.name.endsWith(end)) {
|
if (vf.name.endsWith(end)) {
|
||||||
continue miao;
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -43,5 +43,5 @@ export const filePicker = (() => {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
filePicker
|
filePicker,
|
||||||
};
|
};
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="codemirror-container" ref="rootRef" :class="`codemirror-container-${theme}`">
|
<miao-message-provider ref="miaoMessage">
|
||||||
<div class="codemirror-container-top">
|
<div class="codemirror-container" ref="rootRef" :class="`codemirror-container-${theme}`">
|
||||||
<div class="codemirror-container-btn codemirror-container-btn-save" @click="handleSave">保存</div>
|
<div class="codemirror-container-top">
|
||||||
<div class="codemirror-container-btn codemirror-container-btn-save" @click="handleReset">恢复到上次保存</div>
|
<div class="codemirror-container-btn codemirror-container-btn-save" @click="handleSave">保存</div>
|
||||||
|
<div class="codemirror-container-btn codemirror-container-btn-save" @click="handleReset">恢复到上次保存</div>
|
||||||
|
</div>
|
||||||
|
<div class="codemirror-container-main">
|
||||||
|
<NScrollbar style="max-height: calc( 100% );">
|
||||||
|
<codemirror v-model="codeData" :extensions="extensions" />
|
||||||
|
</NScrollbar>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NScrollbar>
|
</miao-message-provider>
|
||||||
<codemirror v-model="codeData" :extensions="extensions" :style="{ height: '100%' }" />
|
|
||||||
</NScrollbar>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import miaoMessageProvider from '@/components/miaoMessageProvider.vue'
|
||||||
import { VirtualFile } from '@/class/VirtualDirectory'
|
import { VirtualFile } from '@/class/VirtualDirectory'
|
||||||
import useMiaoFetchApi from '@/hooks/useMiaoFetchApi'
|
import useMiaoFetchApi from '@/hooks/useMiaoFetchApi'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
@@ -23,6 +28,7 @@ const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
|
|||||||
required: true
|
required: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const miaoMessage = ref<InstanceType<typeof miaoMessageProvider>>()
|
||||||
const vFile = ref<VirtualFile>()
|
const vFile = ref<VirtualFile>()
|
||||||
const rootRef = ref<HTMLDivElement>()
|
const rootRef = ref<HTMLDivElement>()
|
||||||
// const _key = ref(Math.random())
|
// const _key = ref(Math.random())
|
||||||
@@ -34,6 +40,10 @@ const theme = ref<'light' | 'dark'>('light')
|
|||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (bakData.value === codeData.value || !vFile.value) {
|
if (bakData.value === codeData.value || !vFile.value) {
|
||||||
|
miaoMessage.value?.message('内容未改动', {
|
||||||
|
timeout: 3000,
|
||||||
|
type: 'info'
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const parentDir = vFile.value?.parent
|
const parentDir = vFile.value?.parent
|
||||||
@@ -43,8 +53,14 @@ const handleSave = async () => {
|
|||||||
)
|
)
|
||||||
const file = new File([new Blob([codeData.value ?? ''])], vFile.value.name)
|
const file = new File([new Blob([codeData.value ?? ''])], vFile.value.name)
|
||||||
bakData.value = codeData.value
|
bakData.value = codeData.value
|
||||||
miaoFetchApi.upload(file, parentDir)
|
const { response } = miaoFetchApi.upload(file, parentDir)
|
||||||
false && miaoFetchApi.upload(file_bak, parentDir)
|
false && miaoFetchApi.upload(file_bak, parentDir)
|
||||||
|
await response
|
||||||
|
miaoMessage.value?.message('保存成功', {
|
||||||
|
timeout: 5000,
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReset = async () => {
|
const handleReset = async () => {
|
||||||
@@ -139,8 +155,10 @@ onMounted(async () => {
|
|||||||
.codemirror-container {
|
.codemirror-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
.codemirror-container-top {
|
.codemirror-container-top {
|
||||||
|
height: 25px;
|
||||||
display: flex;
|
display: flex;
|
||||||
border-bottom: 1px solid #000;
|
border-bottom: 1px solid #000;
|
||||||
}
|
}
|
||||||
@@ -160,6 +178,11 @@ onMounted(async () => {
|
|||||||
background-color: #e8e8e8;
|
background-color: #e8e8e8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.codemirror-container-main {
|
||||||
|
height: calc(100% - 25px);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.codemirror-container-dark {
|
.codemirror-container-dark {
|
||||||
|
|||||||
+23
-17
@@ -65,7 +65,7 @@ const emit = defineEmits<{
|
|||||||
exit: []
|
exit: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const popupInput = ref()
|
const popupInput = ref<InstanceType<typeof MiaoPopupInput>>()
|
||||||
// 是否显示模态框
|
// 是否显示模态框
|
||||||
const showModel = ref(0)
|
const showModel = ref(0)
|
||||||
const searchText = ref<string>('')
|
const searchText = ref<string>('')
|
||||||
@@ -210,9 +210,9 @@ const setCurrentDirectory = async (virtualDirectory: VirtualDirectory) => {
|
|||||||
handleSearchReset()
|
handleSearchReset()
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
miaoDirectoryItemRef.value.scrollTo({
|
miaoDirectoryItemRef.value.scrollTo({
|
||||||
top: 0,
|
top: 0,
|
||||||
behavior: "instant"
|
behavior: "instant"
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
currentDirectories.value[0] = virtualDirectory
|
currentDirectories.value[0] = virtualDirectory
|
||||||
@@ -302,9 +302,8 @@ const handleItemDelete = async (_item: VirtualDirectory | VirtualFile) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleItemRename = async (item: VirtualDirectory | VirtualFile) => {
|
const handleItemRename = async (item: VirtualDirectory | VirtualFile) => {
|
||||||
const popupRes = await popupInput.value.popup({
|
const popupRes = await popupInput.value?.popup({
|
||||||
title: '重命名',
|
title: '重命名',
|
||||||
showInput: true,
|
|
||||||
inputValue: item.name,
|
inputValue: item.name,
|
||||||
inputProps: {
|
inputProps: {
|
||||||
placeholder: item.name,
|
placeholder: item.name,
|
||||||
@@ -322,10 +321,13 @@ const handleItemRename = async (item: VirtualDirectory | VirtualFile) => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
if (popupRes.key === false) {
|
if (popupRes?.key === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const newName = popupRes?.text
|
||||||
|
if (!newName) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const newName = popupRes.text
|
|
||||||
const { response } = miaoFetchApi.rename(item, newName, {
|
const { response } = miaoFetchApi.rename(item, newName, {
|
||||||
retry: 5,
|
retry: 5,
|
||||||
})
|
})
|
||||||
@@ -362,7 +364,7 @@ const handleDropVDirectory = async (vDirs: VirtualDirectory[]) => {
|
|||||||
if (currentDirectory.value.getParents.includes(vDir)) {
|
if (currentDirectory.value.getParents.includes(vDir)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if(vDir.parent === currentDirectory.value) {
|
if (vDir.parent === currentDirectory.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,9 +415,8 @@ const handlePickFilesUpload = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAddNewItem = async () => {
|
const handleAddNewItem = async () => {
|
||||||
const popupRes = await popupInput.value.popup({
|
const popupRes = await popupInput.value?.popup({
|
||||||
title: '新建文件/文件夹',
|
title: '新建文件/文件夹',
|
||||||
showInput: true,
|
|
||||||
inputProps: {
|
inputProps: {
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
},
|
},
|
||||||
@@ -438,12 +439,16 @@ const handleAddNewItem = async () => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
// todo: 上传前如重名文件夹等等的错误提前检查
|
// todo: 上传前如重名文件夹等等的错误提前检查
|
||||||
if (popupRes.key === 'cancel' || popupRes.text === '') {
|
if (popupRes?.key === 'cancel' || popupRes?.text === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const _currDir = currentDirectory.value
|
const _currDir = currentDirectory.value
|
||||||
const name = popupRes.text
|
const name = popupRes?.text
|
||||||
const opType: 'file' | 'dir' = popupRes.key
|
if (!name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const opType: 'file' | 'dir' = popupRes?.key
|
||||||
if (opType === 'dir') {
|
if (opType === 'dir') {
|
||||||
const res = await miaoFetchApi.mkdir(currentDirectory.value, name)
|
const res = await miaoFetchApi.mkdir(currentDirectory.value, name)
|
||||||
if (res.message === 'success') {
|
if (res.message === 'success') {
|
||||||
@@ -467,9 +472,8 @@ const handleItemSelect = (item: VirtualDirectory | VirtualFile) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSearch = async () => {
|
const handleSearch = async () => {
|
||||||
const popupRes = await popupInput.value.popup({
|
const popupRes = await popupInput.value?.popup({
|
||||||
title: '搜索当前文件夹',
|
title: '搜索当前文件夹',
|
||||||
showInput: true,
|
|
||||||
inputValue: searchText.value,
|
inputValue: searchText.value,
|
||||||
inputProps: {
|
inputProps: {
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
@@ -492,7 +496,9 @@ const handleSearch = async () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
console.log(popupRes)
|
if (!popupRes) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (popupRes.key === 'search') {
|
if (popupRes.key === 'search') {
|
||||||
searchText.value = popupRes.text
|
searchText.value = popupRes.text
|
||||||
} else if (popupRes.key === 'reset') {
|
} else if (popupRes.key === 'reset') {
|
||||||
|
|||||||
@@ -98,14 +98,17 @@ const handleDrop = (vItems: (VirtualFile | VirtualDirectory)[]) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateImageSrcList = async (source: VirtualFile | VirtualDirectory) => {
|
const updateImageSrcList = async (source: VirtualFile | VirtualDirectory) => {
|
||||||
let srcList: string[]
|
let newSrcList: string[]
|
||||||
if (source.type === 'directory') {
|
if (source.type === 'directory') {
|
||||||
await source.update()
|
await source.update()
|
||||||
srcList = source.files?.filter(v => imageSuffixList.includes(v.name.split('.').pop() ?? '')).map(v => v.url) ?? []
|
newSrcList = source.files?.filter(v => imageSuffixList.includes(v.name.split('.').pop() ?? '')).map(v => v.url) ?? []
|
||||||
} else {
|
} else {
|
||||||
srcList = [source.url]
|
if (!imageSuffixList.includes(source.name.split('.').pop() ?? '')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newSrcList = [source.url]
|
||||||
}
|
}
|
||||||
imageSrcList.value = uniq([...imageSrcList.value, ...srcList])
|
imageSrcList.value = uniq([...imageSrcList.value, ...newSrcList])
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
Reference in new issue
Block a user