You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

637 lines
15 KiB

<template>
<template v-if="mode === 'detail'">
<div class="file-item file-bar mode-detail">
<div class="file-item_img"></div>
<div class="file-item_title"></div>
<div class="file-item_type">
<span>{{ t("size") }}</span>
</div>
<div class="file-item_type">
<span>{{ t("creation.time") }}</span>
</div>
<div class="file-item_type">
<span>{{ t("modification.time") }}</span>
</div>
<div class="file-item_type">
<span>{{ t("permission") }}</span>
</div>
</div>
</template>
<div draggable="true" class="file-item" :class="{
chosen: chosenIndexs.includes(index),
'no-chosen': !chosenIndexs.includes(index),
'mode-icon': mode === 'icon',
'mode-list': mode === 'list',
'mode-big': mode === 'big',
'mode-middle': mode === 'middle',
'mode-detail': mode === 'detail',
'drag-over': hoverIndex === index,
}" :style="{
'--theme-color': theme === 'light' ? '#ffffff6b' : '#3bdbff3d',
}" v-for="(item, index) in fileList" :key="item.path" @dblclick="handleOnOpen(item)"
@touchstart.passive="doubleTouch($event, item)" @contextmenu.stop.prevent="handleRightClick($event, item, index)"
@drop="hadnleDrop($event, item.path)" @dragenter.prevent="handleDragEnter(index)" @dragover.prevent
@dragleave="handleDragLeave()" @dragstart.stop="startDragApp($event, item)" @click="handleClick(index)"
@mousedown.stop :ref="(ref: any) => {
if (ref) {
appPositions[index] = markRaw(ref as Element);
}
}
">
<div class="file-item_img">
<FileIcon :file="item" />
</div>
<span v-if="editIndex !== index" class="file-item_title">
{{ getName(item) }}
</span>
<textarea autofocus draggable="false" @dragover.stop @dragstart.stop @dragenter.stop @mousedown.stop @dblclick.stop
@click.stop @blur="onEditNameEnd" v-if="editIndex === index" class="file-item_title file-item_editing"
v-model="editName"></textarea>
<template v-if="mode === 'detail'">
<div class="file-item_type">
<span>{{ item.isDirectory ? "-" : dealSize(item.size) }}</span>
</div>
<div class="file-item_type">
<span>{{ item.birthtime.toLocaleString() }}</span>
</div>
<div class="file-item_type">
<span>{{ item.mtime.toLocaleString() }}</span>
</div>
<div class="file-item_type">
<span>{{ item.mode?.toString?.(8) || "unknow" }}</span>
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import { useSystem, basename, OsFileWithoutContent, Notify, BrowserWindow } from '@/system/index.ts';
import { getSystemKey } from '@/system/config'
import { emitEvent, mountEvent } from '@/system/event';
import { useContextMenu } from '@/hook/useContextMenu.ts';
import { t, dealSystemName } from '@/i18n';
import { useFileDrag } from '@/hook/useFileDrag';
import { useAppMenu } from '@/hook/useAppMenu';
import { onMounted, ref, markRaw } from 'vue';
import { Rect } from '@/hook/useRectChosen';
import { throttle } from '@/util/debounce';
import { dealSize } from '@/util/file';
import { Menu } from '@/system/menu/Menu';
import { useChooseStore } from "@/stores/choose";
const { openPropsWindow, copyFile, createLink, deleteFile } = useContextMenu();
const sys = useSystem();
const { startDrag, folderDrop } = useFileDrag(sys);
const choose = useChooseStore()
const props = defineProps({
onChosen: {
type: Function,
required: true,
},
onOpen: {
type: Function,
default: () => {
//
},
},
onRefresh: {
type: Function,
default: () => {
//
},
},
fileList: {
type: Array<OsFileWithoutContent>,
default: () => [],
},
theme: {
type: String || Object,
default: 'light',
},
mode: {
type: String,
default: 'icon',
},
});
function getName(item: any) {
const name = dealSystemName(basename(item.path))
// console.log(name)
// console.log(item.path)
if (name.endsWith('.exe')) {
return t(name.replace('.exe', ""))
} else {
return name
}
}
function handleOnOpen(item: OsFileWithoutContent) {
// props.onOpen(item);
// emitEvent('desktop.app.open');
chosenIndexs.value = [];
if (choose.ifShow && !item.isDirectory) {
choose.path.push(item.path)
choose.close()
} else {
props.onOpen(item);
emitEvent('desktop.app.open');
}
}
function hadnleDrop(mouse: DragEvent, path: string) {
hoverIndex.value = -1;
folderDrop(mouse, path);
chosenIndexs.value = [];
}
let expired: number | null = null;
function doubleTouch(e: TouchEvent, item: OsFileWithoutContent) {
if (e.touches.length === 1) {
if (!expired) {
expired = e.timeStamp + 400;
} else if (e.timeStamp <= expired) {
// remove the default of this event ( Zoom )
handleOnOpen(item);
e.preventDefault();
// then reset the variable for other "double Touches" event
expired = null;
} else {
// if the second touch was expired, make it as it's the first
expired = e.timeStamp + 400;
}
}
}
const editIndex = ref<number>(-1);
const editName = ref<string>('');
function onEditNameEnd() {
const editEndName = editName.value.trim();
if (editEndName && editIndex.value >= 0) {
const editpath: any = props.fileList[editIndex.value].path.toString()
let newPath: any;
let sp = "/"
if (editpath.indexOf("/") === -1) {
sp = "\\"
}
newPath = editpath?.split(sp);
newPath.pop()
newPath.push(editEndName)
newPath = newPath.join(sp)
sys?.fs.rename(
editpath,
newPath
);
props.onRefresh();
if (newPath.indexOf("Desktop") !== -1) {
sys.refershAppList()
}
}
editIndex.value = -1;
}
mountEvent('edit.end', () => {
onEditNameEnd();
});
const hoverIndex = ref<number>(-1);
const appPositions = ref<Array<Element>>([]);
const chosenIndexs = ref<Array<number>>([]);
function handleClick(index: number) {
chosenIndexs.value = [index];
}
onMounted(() => {
chosenIndexs.value = [];
props.onChosen(
throttle((rect: Rect) => {
const tempChosen: number[] = [];
appPositions.value.forEach((el, index) => {
const rect2 = el.getBoundingClientRect();
const rect2Center = {
x: rect2.left + rect2.width / 2,
y: rect2.top + rect2.height / 2,
};
if (
rect2Center.x > rect.left &&
rect2Center.x < rect.left + rect.width &&
rect2Center.y > rect.top &&
rect2Center.y < rect.top + rect.height
) {
tempChosen.push(index);
}
});
chosenIndexs.value = tempChosen;
}, 100)
);
});
function startDragApp(mouse: DragEvent, item: OsFileWithoutContent) {
if (chosenIndexs.value.length) {
startDrag(
mouse,
chosenIndexs.value.map((index) => {
return props.fileList[index];
}),
() => {
chosenIndexs.value = [];
}
);
} else {
startDrag(mouse, [item], () => {
chosenIndexs.value = [];
});
}
}
function handleRightClick(mouse: MouseEvent, item: OsFileWithoutContent, index: number) {
if (chosenIndexs.value.length <= 1) {
chosenIndexs.value = [props.fileList.findIndex((app) => app.path === item.path)];
}
const ext = item.name.split(".").pop();
const zipSucess = (res: any) => {
if (!res || res.code < 0) {
new Notify({
title: t('tips'),
content: t('error'),
});
} else {
props.onRefresh();
new Notify({
title: t('tips'),
content: t('file.zip.success'),
});
if (item.parentPath == '/C/Users/Desktop') {
sys.refershAppList()
}
}
};
// eslint-disable-next-line prefer-const
let menuArr: any = [
{
label: t('open'),
click: () => {
chosenIndexs.value = [];
props.onOpen(item);
},
},
// {
// label: t('open.with'),
// click: () => {
// chosenIndexs.value = [];
// openWith(item);
// },
// },
];
if (item.isDirectory) {
if (getSystemKey('storeType') == 'local') {
menuArr.push({
label: t('zip'),
submenu: [
{
label: 'zip',
click: () => {
sys.fs.zip(item.path, 'zip').then((res: any) => {
zipSucess(res)
});
},
},
{
label: 'tar',
click: () => {
sys.fs.zip(item.path, 'tar').then((res: any) => {
zipSucess(res)
});
},
},
{
label: 'gz',
click: () => {
sys.fs.zip(item.path, 'gz').then((res: any) => {
zipSucess(res)
});
},
},
],
})
} else {
menuArr.push({
label: t('zip'),
click: () => {
sys.fs.zip(item.path, 'zip').then((res: any) => {
zipSucess(res)
});
},
})
}
}
if (choose.ifShow) {
menuArr.push({
label: "选中发送",
click: () => {
const paths: any = []
chosenIndexs.value.forEach((index) => {
const item = props.fileList[index];
paths.push(item.path)
})
if (paths.length > 0) {
choose.path = paths
choose.close()
}
chosenIndexs.value = [];
},
})
}
// eslint-disable-next-line prefer-const
let extMenus = useAppMenu(item, sys, props);
if (extMenus && extMenus.length > 0) {
// eslint-disable-next-line prefer-spread
menuArr.push.apply(menuArr, extMenus)
}
if (ext != 'exe') {
const fileMenus = [
{
label: t('rename'),
click: () => {
editIndex.value = index;
editName.value = basename(item.path);
chosenIndexs.value = [];
},
},
{
label: t('copy'),
click: () => {
//if(["/","/B"].includes(item.path)) return;
copyFile(chosenIndexs.value.map((index) => props.fileList[index]));
chosenIndexs.value = [];
},
},
{
label: t('delete'),
click: async () => {
for (let i = 0; i < chosenIndexs.value.length; i++) {
await deleteFile(props.fileList[chosenIndexs.value[i]]);
}
chosenIndexs.value = [];
props.onRefresh();
if (item.path.indexOf('Desktop') > -1) {
sys.refershAppList()
}
},
},
];
const userType = sys.getConfig('userType');
if (userType == 'member') {
menuArr.push(
{
label: '分享给...',
click: () => {
const win = new BrowserWindow({
title: '分享',
content: "ShareFiles",
config: {
path: item.path,
},
width: 500,
height: 500,
center: true,
});
win.show();
},
}
)
menuArr.push(
{
label: '评论',
click: () => {
const win = new BrowserWindow({
title: '评论',
content: "CommentsFiles",
config: {
path: item.path,
},
width: 350,
height: 400,
center: true,
});
win.show();
},
}
)
}
// eslint-disable-next-line prefer-spread
menuArr.push.apply(menuArr, fileMenus)
}
const sysEndMenu = [
{
label: t('create.shortcut'),
click: () => {
createLink(item.path)?.then(() => {
chosenIndexs.value = [];
props.onRefresh();
});
},
},
{
label: t('props'),
click: () => {
chosenIndexs.value.forEach((index) => {
openPropsWindow(props.fileList[index].path);
chosenIndexs.value = [];
});
},
}
];
// eslint-disable-next-line prefer-spread
menuArr.push.apply(menuArr, sysEndMenu)
//console.log(item)
//console.log(ext)
Menu.buildFromTemplate(menuArr).popup(mouse);
}
function handleDragEnter(index: number) {
hoverIndex.value = index;
}
function handleDragLeave() {
hoverIndex.value = -1;
}
// function dealtName(name: string) {
// return name;
// }
</script>
<style lang="scss" scoped>
.file-item {
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
width: var(--desk-item-size);
height: var(--desk-item-size);
font-size: var(--ui-font-size);
color: var(--icon-title-color);
padding-top: 4px;
border: 1px solid transparent;
margin: 6px;
.file-item_img {
width: 60%;
height: 60%;
pointer-events: none;
}
.file-item_type {
display: none;
}
.file-item_title {
pointer-events: none;
}
.file-item_editing {
display: inline-block !important;
outline: none;
pointer-events: all;
padding: 0;
margin: 0;
min-width: 0;
height: min-content !important;
width: min-content !important;
resize: none;
border-radius: 0;
}
}
.file-item:hover {
background-color: #b1f1ff4c;
}
.chosen {
border: 1px dashed #3bdbff3d;
// background-color: #ffffff6b;
background-color: var(--theme-color);
.file-item_title {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
}
.no-chosen {
.file-item_title {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
}
.drag-over {
border: 1px dashed #3bdbff3d;
// background-color: #ffffff6b;
background-color: var(--theme-color);
}
.mode-icon {
.file-item_img {
width: 60%;
height: calc(0.6 * var(--desk-item-size));
margin: 0px auto;
user-select: none;
flex-shrink: 0;
}
.file-item_title {
// color: var(--color-ui-desk-item-title);
// height: calc(0.4 * var(--desk-item-size));
// display: block;
text-align: center;
word-break: break-all;
flex-grow: 0;
}
}
.mode-list {
display: flex;
flex-direction: row;
justify-content: flex-start;
height: var(--menulist-item-height);
width: var(--menulist-width);
.file-item_img {
width: var(--menulist-item-height);
height: calc(0.6 * var(--menulist-item-height));
flex-shrink: 0;
user-select: none;
}
.file-item_title {
height: min-content;
word-break: break-all;
}
}
.mode-icon {
width: var(--desk-item-size);
height: var(--desk-item-size);
}
.mode-big {
width: calc(var(--desk-item-size) * 2.5);
height: calc(var(--desk-item-size) * 2.5);
}
.mode-middle {
width: calc(var(--desk-item-size) * 1.5);
height: calc(var(--desk-item-size) * 1.5);
}
.mode-detail {
display: flex;
flex-direction: row;
justify-content: flex-start;
height: var(--menulist-item-height);
width: 100%;
margin: 2px;
.file-item_img {
width: 30px;
}
.file-item_title {
width: 40%;
display: flex;
align-items: center;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.file-item_type {
display: block;
color: var(--color-dark-hover);
width: 20%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.file-bar:hover {
background-color: unset;
user-select: none;
}
</style>