support text editor (now it possible to view text files)

This commit is contained in:
Salem Yaslem 2024-01-18 03:49:12 +03:00
parent 8b19f257ed
commit dc764433ae
9 changed files with 119 additions and 20 deletions

14
app.vue
View File

@ -17,8 +17,6 @@ let filesGridList = ref<any>([])
let selectedList = ref<any>([]); let selectedList = ref<any>([]);
let filesManager = new FilesManager(filesList); let filesManager = new FilesManager(filesList);
let history = new HistoryManager(filesManager); let history = new HistoryManager(filesManager);
const videoExtensions = ['mp4', 'avi', 'mov', 'mkv'];
let mediaBlobUrl = ref(''); let mediaBlobUrl = ref('');
watchEffect(async () => { watchEffect(async () => {
@ -27,7 +25,6 @@ watchEffect(async () => {
filesList.value = []; filesList.value = [];
await filesManager.loadArchive(files.value?.[0]); await filesManager.loadArchive(files.value?.[0]);
loadingModel.value = false; loadingModel.value = false;
} }
}) })
@ -68,7 +65,6 @@ watchEffect(async () => {
// Experimental feature // Experimental feature
if (videoExtensions.includes(filesManager.getFile(selectedPath.value)?.extension?.toLowerCase())) { if (videoExtensions.includes(filesManager.getFile(selectedPath.value)?.extension?.toLowerCase())) {
mediaBlobUrl.value = await filesManager.getFileBlobUrl(selectedPath.value) as string; mediaBlobUrl.value = await filesManager.getFileBlobUrl(selectedPath.value) as string;
console.log(mediaBlobUrl.value)
} }
}) })
@ -162,15 +158,13 @@ function stepUp(path: string) {
</v-row> </v-row>
</v-toolbar> </v-toolbar>
<v-container> <v-container>
<template v-if="filesManager.getFile(selectedPath)?.isFolder || false"> <template v-if="filesManager.getFile(selectedPath)?.isFolder">
<v-list :selected="[selectedPath]"> <v-list :selected="[selectedPath]">
<v-row no-gutters> <v-row no-gutters>
<v-col cols="6" lg="2" md="3" sm="6" v-for="file of filesGridList" style="text-align: center;"> <v-col cols="6" lg="2" md="3" sm="6" v-for="file of filesGridList" style="text-align: center;">
<v-list-item class="ma-2 pa-5 selectable" active-color="light-blue-darken-4" :value="file.path" rounded <v-list-item class="ma-2 pa-5 selectable" active-color="light-blue-darken-4" :value="file.path" rounded
@click="selectedPath = file.path"> @click="selectedPath = file.path">
<v-avatar class="mb-2" :color="file.isFolder ? 'light-blue-accent-4' : 'blue-grey-darken-1'"> <file-logo class="mb-2" :file="file" :key="file.path" />
<v-icon color="white">{{ file.isFolder ? 'mdi-folder' : 'mdi-file' }}</v-icon>
</v-avatar>
<p>{{ file.name }}</p> <p>{{ file.name }}</p>
</v-list-item> </v-list-item>
</v-col> </v-col>
@ -181,6 +175,10 @@ function stepUp(path: string) {
v-if="!filesManager.getFile(selectedPath)?.isFolder && videoExtensions.includes(filesManager.getFile(selectedPath)?.extension)"> v-if="!filesManager.getFile(selectedPath)?.isFolder && videoExtensions.includes(filesManager.getFile(selectedPath)?.extension)">
<MediaVideoPlayer :src="mediaBlobUrl"></MediaVideoPlayer> <MediaVideoPlayer :src="mediaBlobUrl"></MediaVideoPlayer>
</template> </template>
<template
v-if="!filesManager.getFile(selectedPath)?.isFolder && files.length && !videoExtensions.includes(filesManager.getFile(selectedPath)?.extension)">
<TextEditor :file="filesManager.getFile(selectedPath)" :filesManager="filesManager"></TextEditor>
</template>
<template v-if="!files.length"> <template v-if="!files.length">
<!-- tutorial drag and drop zipped file here and review it securely --> <!-- tutorial drag and drop zipped file here and review it securely -->
<v-row align="center" justify="center"> <v-row align="center" justify="center">

27
components/file-logo.vue Normal file
View File

@ -0,0 +1,27 @@
<script lang="ts" setup>
import { iFile } from '~/composables/worker/7zip-manager';
interface Props {
file: iFile,
}
let { file } = defineProps<Props>()
let icon = computed(() => {
if (file.isFolder) return 'mdi-folder';
if (file.extension) {
if (videoExtensions.includes(file.extension!)) return 'mdi-video';
if (audioExtensions.includes(file.extension!)) return 'mdi-music';
if (imageExtensions.includes(file.extension!)) return 'mdi-image';
if (textExtensions.includes(file.extension!)) return 'mdi-file-document';
}
return 'mdi-file';
})
</script>
<template>
<v-avatar :color="file.isFolder ? 'light-blue-accent-4' : 'blue-grey-darken-1'">
<v-icon color="white">{{ icon }}</v-icon>
</v-avatar>
</template>

View File

@ -0,0 +1,40 @@
<script lang="ts" setup>
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
import { ref, onMounted } from 'vue'
import { FilesManager } from '~/composables/files-manager';
// @ts-ignore
import { iFile } from '~/composables/worker/7zip-manager';
interface Props {
file: iFile,
filesManager: FilesManager
}
const editor = ref()
let { file, filesManager } = defineProps<Props>()
// files manager
onMounted(async () => {
const darkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
let fileContent = await filesManager.getFileContent(file.path);
monaco.editor.create(editor.value, {
value: fileContent?.toString()!,
language: file.extension,
readOnly: true,
theme: darkMode ? 'vs-dark' : 'vs-light'
})
})
</script>
<template>
<div id="editor" ref="editor"></div>
</template>
<style scoped>
#editor {
width: 100vw;
height: 100vh;
}
</style>

View File

@ -14,15 +14,13 @@ let selectedPath = useSelectedPath();
<template> <template>
<v-list :selected="[selectedPath]" density="compact" :nav="nav"> <v-list :selected="[selectedPath]" density="compact" :nav="nav">
<template v-for="file in filesList" :key="file.path"> <template v-for="file in filesList" :key="file.path">
<v-list-item active-color="light-blue-darken-4" :active="selectedPath == file.path" :title="file.name" :subtitle="file.path" <v-list-item active-color="light-blue-darken-4" :active="selectedPath == file.path" :title="file.name"
:value="file.path" @click="() => { :subtitle="file.path" :value="file.path" @click="() => {
selectedPath = file.path; selectedPath = file.path;
file.toggle = !file.toggle; file.toggle = !file.toggle;
}"> }">
<template v-slot:prepend> <template v-slot:prepend>
<v-avatar :color="file.isFolder ? 'light-blue-accent-4' : 'blue-grey-darken-1'"> <file-logo :file="file" :key="file.path" />
<v-icon color="white">{{ file.isFolder ? 'mdi-folder' : 'mdi-file' }}</v-icon>
</v-avatar>
</template> </template>
<template v-if="file.isFolder" v-slot:append> <template v-if="file.isFolder" v-slot:append>
<v-icon>{{ `mdi-chevron-${file.toggle ? 'up' : 'down'}` }}</v-icon> <v-icon>{{ `mdi-chevron-${file.toggle ? 'up' : 'down'}` }}</v-icon>

View File

@ -6,6 +6,11 @@ import * as Comlink from "comlink";
import SevenZipWorker from "./worker/7zip-manager?worker"; import SevenZipWorker from "./worker/7zip-manager?worker";
import { SevenZipManager, iFile } from "./worker/7zip-manager"; import { SevenZipManager, iFile } from "./worker/7zip-manager";
export const videoExtensions = ['mp4', 'avi', 'mov', 'mkv'];
export const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
export const audioExtensions = ['mp3', 'wav', 'ogg', 'flac'];
export const textExtensions = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md', 'js', 'ts', 'php', 'c', 'cpp', 'py', 'html', 'css', 'scss', 'sass', 'less', 'json', 'xml', 'sql', 'java', 'go', 'rb', 'sh', 'bat', 'ps1', 'cmd', 'yml', 'yaml', 'ini', 'toml', 'csv', 'tsv', 'gitignore', 'lock', 'htaccess', 'htpasswd', 'env', 'dockerfile', 'gitattributes', 'gitmodules', 'editorconfig', 'babelrc', 'eslintrc', 'eslintignore', 'prettierrc', 'prettierignore', 'stylelintrc', 'stylelintignore', 'postcssrc', 'postcss.config', 'jsx', 'tsx', 'license']
export class FilesManager { export class FilesManager {
consoleOutputBuffer: string[] = []; consoleOutputBuffer: string[] = [];
path: Ref<string> = useSelectedPath(); path: Ref<string> = useSelectedPath();
@ -68,4 +73,9 @@ export class FilesManager {
if (!this.remoteSevenZipManager) return; if (!this.remoteSevenZipManager) return;
return await this.remoteSevenZipManager.generateBlobUrl(JSON.stringify(this.getFile(path)) as any); return await this.remoteSevenZipManager.generateBlobUrl(JSON.stringify(this.getFile(path)) as any);
} }
async getFileContent(path: string) {
if (!this.remoteSevenZipManager) return;
return await this.remoteSevenZipManager.getFileContent(JSON.stringify(this.getFile(path)) as any);
}
} }

View File

@ -1,2 +1,4 @@
import { FilesManager } from "./files-manager";
let selectedPath = ref("/") let selectedPath = ref("/")
export const useSelectedPath = () => useState("selected-path", () => selectedPath) export const useSelectedPath = () => useState("selected-path", () => selectedPath)

View File

@ -66,7 +66,7 @@ export class SevenZipManager {
name: file.groups!.path.lastIndexOf('/') > -1 ? file.groups!.path.substring(file.groups!.path.lastIndexOf('/') + 1) : file.groups!.path, name: file.groups!.path.lastIndexOf('/') > -1 ? file.groups!.path.substring(file.groups!.path.lastIndexOf('/') + 1) : file.groups!.path,
path: `/${file.groups!.path}`, path: `/${file.groups!.path}`,
isFolder: isFolder ? true : false, isFolder: isFolder ? true : false,
extension: isFolder ? "" : file.groups!.path.substring(file.groups!.path.lastIndexOf('.') + 1), extension: isFolder ? "" : file.groups!.path.substring(file.groups!.path.lastIndexOf('.') + 1).toLowerCase(),
content: isFolder ? [] as any[] : undefined, content: isFolder ? [] as any[] : undefined,
} }
}); });
@ -161,6 +161,23 @@ export class SevenZipManager {
return blobUrl; return blobUrl;
} }
// get content from buffer (Experimental)
async getFileContent(file: iFile) {
if (!this.sevenZip) return;
file = typeof file === "string" ? JSON.parse(file) : file;
// extract file from archive
this.execute(['x', '-y', this.archiveName, file.path.substring(1)]);
this.sevenZip.FS.chmod(file.path, 0o777);
// get file buffer
const buffer = this.sevenZip.FS.readFile(file.path, { encoding: "utf8" });
// remove the file after extract local blob url
this.sevenZip.FS.unlink(file.path);
return buffer;
}
} }
Comlink.expose(SevenZipManager); Comlink.expose(SevenZipManager);

16
package-lock.json generated
View File

@ -15,13 +15,14 @@
"7z-wasm": "^1.0.2", "7z-wasm": "^1.0.2",
"comlink": "^4.4.1", "comlink": "^4.4.1",
"mime": "^3.0.0", "mime": "^3.0.0",
"monaco-editor": "^0.45.0",
"moveable": "^0.52.0", "moveable": "^0.52.0",
"sass": "^1.62.1", "sass": "^1.62.1",
"selecto": "^1.26.2", "selecto": "^1.26.2",
"stateshot": "^1.3.5", "stateshot": "^1.3.5",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vue-plyr": "^7.0.0", "vue-plyr": "^7.0.0",
"vue3-selecto": "^1.12.2", "vue3-selecto": "^1.12.3",
"vuetify": "^3.2.4" "vuetify": "^3.2.4"
}, },
"devDependencies": { "devDependencies": {
@ -5254,6 +5255,11 @@
"ufo": "^1.1.2" "ufo": "^1.1.2"
} }
}, },
"node_modules/monaco-editor": {
"version": "0.45.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.45.0.tgz",
"integrity": "sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA=="
},
"node_modules/moveable": { "node_modules/moveable": {
"version": "0.52.0", "version": "0.52.0",
"resolved": "https://registry.npmjs.org/moveable/-/moveable-0.52.0.tgz", "resolved": "https://registry.npmjs.org/moveable/-/moveable-0.52.0.tgz",
@ -8499,11 +8505,11 @@
} }
}, },
"node_modules/vue3-selecto": { "node_modules/vue3-selecto": {
"version": "1.12.2", "version": "1.12.3",
"resolved": "https://registry.npmjs.org/vue3-selecto/-/vue3-selecto-1.12.2.tgz", "resolved": "https://registry.npmjs.org/vue3-selecto/-/vue3-selecto-1.12.3.tgz",
"integrity": "sha512-GamgTxYLMnQ8/N1/lFeoyjcfzG7h7J9UUI1f+DH4CXBLq2EVOZ83CgG0TuOm1juxgNXbbmXcDIN72FRvUmTqKQ==", "integrity": "sha512-zIQLwuvjTNaivITLbBAnm6Sh8BFRG8QocNZLzKvqRIqnpRa2lvlIw9+l1wdI/84msh8cSNIdiWsgote62cgtHA==",
"dependencies": { "dependencies": {
"selecto": "~1.26.2" "selecto": "~1.26.3"
} }
}, },
"node_modules/vuetify": { "node_modules/vuetify": {

View File

@ -25,13 +25,14 @@
"7z-wasm": "^1.0.2", "7z-wasm": "^1.0.2",
"comlink": "^4.4.1", "comlink": "^4.4.1",
"mime": "^3.0.0", "mime": "^3.0.0",
"monaco-editor": "^0.45.0",
"moveable": "^0.52.0", "moveable": "^0.52.0",
"sass": "^1.62.1", "sass": "^1.62.1",
"selecto": "^1.26.2", "selecto": "^1.26.2",
"stateshot": "^1.3.5", "stateshot": "^1.3.5",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vue-plyr": "^7.0.0", "vue-plyr": "^7.0.0",
"vue3-selecto": "^1.12.2", "vue3-selecto": "^1.12.3",
"vuetify": "^3.2.4" "vuetify": "^3.2.4"
} }
} }