mirror of
https://github.com/xlmnxp/extractify.zip.git
synced 2024-11-24 01:24:35 +03:00
Compare commits
7 Commits
c8209c5799
...
3059a2f252
Author | SHA1 | Date | |
---|---|---|---|
3059a2f252 | |||
8b1b7d7aa2 | |||
249cc552ec | |||
dac3982370 | |||
21dc8009ba | |||
816f50a452 | |||
5a739b543d |
123
app.vue
123
app.vue
@ -1,10 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { getElementInfo } from "moveable";
|
||||
import { VueSelecto } from "vue3-selecto";
|
||||
import { useDisplay } from 'vuetify/lib/framework.mjs';
|
||||
import { HistoryManager } from './composables/history-manager';
|
||||
import { FilesManager } from './composables/files-manager';
|
||||
import { FilesManager, supportedExtensions, imageExtensions } from './composables/files-manager';
|
||||
import type { iFile } from "composables/worker/7zip-manager"
|
||||
import { videoExtensions, binaryExtensions } from '#imports';
|
||||
|
||||
let display = useDisplay();
|
||||
let drawer = ref(!display.mdAndDown.value);
|
||||
@ -18,13 +17,21 @@ let selectedList = ref<any>([]);
|
||||
let filesManager = new FilesManager(filesList);
|
||||
let history = new HistoryManager(filesManager);
|
||||
let mediaBlobUrl = ref('');
|
||||
let errorDialog = ref(false);
|
||||
let errorMessage = ref('');
|
||||
|
||||
watchEffect(async () => {
|
||||
if (files.value?.[0]) {
|
||||
loadingModel.value = true;
|
||||
filesList.value = [];
|
||||
|
||||
await filesManager.loadArchive(files.value?.[0]);
|
||||
try {
|
||||
await filesManager.loadArchive(files.value?.[0]);
|
||||
} catch (error: any) {
|
||||
errorMessage.value = error.message;
|
||||
errorDialog.value = true;
|
||||
files.value = [];
|
||||
}
|
||||
loadingModel.value = false;
|
||||
}
|
||||
})
|
||||
@ -57,41 +64,13 @@ watchEffect(async () => {
|
||||
const file = filesManager.getFile(selectedPath.value);
|
||||
filesGridList.value = file?.content;
|
||||
|
||||
selectedList.value = [];
|
||||
for (const selectedElement of document.querySelectorAll(".selectable.selected")) {
|
||||
selectedElement.classList.remove("selected");
|
||||
}
|
||||
|
||||
// Experimental feature
|
||||
if (videoExtensions.includes(filesManager.getFile(selectedPath.value)?.extension?.toLowerCase())) {
|
||||
// Update to handle both video and image files
|
||||
if (videoExtensions.includes(filesManager.getFile(selectedPath.value)?.extension?.toLowerCase()) ||
|
||||
imageExtensions.includes(filesManager.getFile(selectedPath.value)?.extension?.toLowerCase())) {
|
||||
mediaBlobUrl.value = await filesManager.getFileBlobUrl(selectedPath.value) as string;
|
||||
}
|
||||
})
|
||||
|
||||
const dragContainer = document.querySelector(".select-area");
|
||||
|
||||
function onSelectStart(e: any) {
|
||||
e.added.forEach((el: any) => {
|
||||
el.classList.add("selected");
|
||||
selectedList.value = [...selectedList.value, el?.__vnode?.ctx?.props?.value];
|
||||
});
|
||||
e.removed.forEach((el: any) => {
|
||||
el.classList.remove("selected");
|
||||
selectedList.value = selectedList.value.filter((value: string) => value != el?.__vnode?.ctx?.props?.value)
|
||||
});
|
||||
}
|
||||
|
||||
function onSelectEnd(e: any) {
|
||||
e.afterAdded.forEach((el: any) => {
|
||||
el.classList.add("selected");
|
||||
selectedList.value = [...selectedList.value, el?.__vnode?.ctx?.props?.value];
|
||||
});
|
||||
e.afterRemoved.forEach((el: any) => {
|
||||
el.classList.remove("selected");
|
||||
selectedList.value = selectedList.value.filter((value: string) => value != el?.__vnode?.ctx?.props?.value)
|
||||
});
|
||||
}
|
||||
|
||||
// step up from current path
|
||||
function stepUp(path: string) {
|
||||
const pathArray = path.split("/");
|
||||
@ -123,9 +102,8 @@ function stepUp(path: string) {
|
||||
</div>
|
||||
</v-footer>
|
||||
</v-navigation-drawer>
|
||||
<v-main class="select-area" style="height: 100dvh;">
|
||||
<v-main style="height: 100dvh;">
|
||||
<v-toolbar class="px-5" height="auto">
|
||||
|
||||
<v-row align="center" justify="center">
|
||||
<v-col cols="12" lg="2" md="12" style="display: inline-flex;">
|
||||
<v-btn title="Back" aria-label="Back" icon="mdi-arrow-left" :disabled="!history.hasUndo.value"
|
||||
@ -161,44 +139,27 @@ function stepUp(path: string) {
|
||||
</v-toolbar>
|
||||
<v-container>
|
||||
<template v-if="filesManager.getFile(selectedPath)?.isFolder">
|
||||
<v-list :selected="[selectedPath]">
|
||||
<v-row no-gutters>
|
||||
<v-col cols="6" lg="2" md="3" sm="6" v-for="file of filesGridList" style="text-align: center;">
|
||||
<v-list-item class="position-relative ma-2 pa-5 selectable" active-color="light-blue-darken-4"
|
||||
:value="file.path" rounded @click="selectedPath = file.path">
|
||||
<v-menu v-if="!file.isFolder">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn class="position-absolute" style="right: 0; top: 0;" icon="mdi-dots-vertical" variant="text"
|
||||
v-bind="props"></v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item title="Download" aria-label="Download" icon="mdi-download"
|
||||
@click="filesManager.downloadFile(file.path)">
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-download"></v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<file-logo class="mb-2" :file="file" :key="file.path" />
|
||||
<p>{{ file.name }}</p>
|
||||
</v-list-item>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-list>
|
||||
<FolderViewer :filesManager="filesManager" :filesGridList="filesGridList" :selectedList="selectedList"></FolderViewer>
|
||||
</template>
|
||||
<template
|
||||
v-if="!filesManager.getFile(selectedPath)?.isFolder && videoExtensions.includes(filesManager.getFile(selectedPath)?.extension)">
|
||||
<MediaVideoPlayer :src="mediaBlobUrl"></MediaVideoPlayer>
|
||||
</template>
|
||||
<template
|
||||
v-if="!filesManager.getFile(selectedPath)?.isFolder && files.length && !videoExtensions.includes(filesManager.getFile(selectedPath)?.extension)">
|
||||
<TextEditor :file="filesManager.getFile(selectedPath)" :filesManager="filesManager"></TextEditor>
|
||||
v-if="!filesManager.getFile(selectedPath)?.isFolder && imageExtensions.includes(filesManager.getFile(selectedPath)?.extension)">
|
||||
<MediaImageViewer :src="mediaBlobUrl"></MediaImageViewer>
|
||||
</template>
|
||||
<template
|
||||
v-if="!filesManager.getFile(selectedPath)?.isFolder && files.length && !imageExtensions.includes(filesManager.getFile(selectedPath)?.extension) && !videoExtensions.includes(filesManager.getFile(selectedPath)?.extension) && !binaryExtensions.includes(filesManager.getFile(selectedPath)?.extension)">
|
||||
<MediaTextEditor :file="filesManager.getFile(selectedPath)" :filesManager="filesManager"></MediaTextEditor>
|
||||
</template>
|
||||
<template
|
||||
v-if="!filesManager.getFile(selectedPath)?.isFolder && files.length && binaryExtensions.includes(filesManager.getFile(selectedPath)?.extension)">
|
||||
<MediaBinaryViewer :file="filesManager.getFile(selectedPath)" :filesManager="filesManager"></MediaBinaryViewer>
|
||||
</template>
|
||||
<template v-if="!files.length">
|
||||
<!-- tutorial drag and drop zipped file here and review it securely -->
|
||||
<v-row align="center" justify="center">
|
||||
<v-row align="center" justify="center" style="height: calc(100vh - 120px)">
|
||||
<v-col cols="12">
|
||||
<v-card variant="flat" class="mx-auto" max-width="768">
|
||||
<!-- v-icon for file -->
|
||||
@ -214,17 +175,14 @@ function stepUp(path: string) {
|
||||
</v-card-text>
|
||||
|
||||
<!-- file input -->
|
||||
<v-file-input class="mx-5" v-model="files" accept=".zip,.7z,.rar,.tar.bz2,.tar.gz,.tar.xz"
|
||||
label="or select a file..." variant="outlined"></v-file-input>
|
||||
<v-file-input class="mx-5" v-model="files"
|
||||
:accept="supportedExtensions.map(extension => `.${extension}`).join(',')" label="or select a file..."
|
||||
variant="outlined"></v-file-input>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-container>
|
||||
|
||||
<VueSelecto :selectableTargets="['.selectable']" :dragContainer="dragContainer" :hitRate="0"
|
||||
:selectFromInside="false" :toggleContinueSelect="'ctrl'" @select="onSelectStart" @selectStart="onSelectStart"
|
||||
:get-element-rect="getElementInfo" @selectEnd="onSelectEnd" :select-by-click="false" />
|
||||
</v-main>
|
||||
<v-dialog v-model="loadingModel" persistent width="auto">
|
||||
<v-card>
|
||||
@ -234,6 +192,22 @@ function stepUp(path: string) {
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="errorDialog" width="auto">
|
||||
<v-card>
|
||||
<v-card-title class="text-error">
|
||||
Error
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
{{ errorMessage }}
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" variant="text" @click="errorDialog = false">
|
||||
Close
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-layout>
|
||||
</template>
|
||||
<style>
|
||||
@ -244,4 +218,9 @@ function stepUp(path: string) {
|
||||
.selected {
|
||||
background: rgba(48, 150, 243, 0.1);
|
||||
}
|
||||
|
||||
.v-container {
|
||||
height: calc(100vh - 120px);
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
79
components/folder-viewer.vue
Normal file
79
components/folder-viewer.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<script lang="ts" setup>
|
||||
import { getElementInfo } from "moveable";
|
||||
import { VueSelecto } from "vue3-selecto";
|
||||
import { FilesManager } from '~/composables/files-manager';
|
||||
import { iFile } from '~/composables/worker/7zip-manager';
|
||||
|
||||
interface Props {
|
||||
filesManager: FilesManager;
|
||||
filesGridList: iFile[];
|
||||
selectedList: Ref<string[]>
|
||||
};
|
||||
|
||||
const { filesManager, filesGridList, selectedList } = defineProps<Props>();
|
||||
|
||||
const selectedPath = useSelectedPath();
|
||||
|
||||
const dragContainer = document.querySelector(".select-area");
|
||||
|
||||
function onSelectStart(e: any) {
|
||||
e.added.forEach((el: any) => {
|
||||
el.classList.add("selected");
|
||||
selectedList.value = [...selectedList.value, el?.__vnode?.ctx?.props?.value];
|
||||
});
|
||||
e.removed.forEach((el: any) => {
|
||||
el.classList.remove("selected");
|
||||
selectedList.value = selectedList.value.filter((value: string) => value != el?.__vnode?.ctx?.props?.value)
|
||||
});
|
||||
}
|
||||
|
||||
function onSelectEnd(e: any) {
|
||||
e.afterAdded.forEach((el: any) => {
|
||||
el.classList.add("selected");
|
||||
selectedList.value = [...selectedList.value, el?.__vnode?.ctx?.props?.value];
|
||||
});
|
||||
e.afterRemoved.forEach((el: any) => {
|
||||
el.classList.remove("selected");
|
||||
selectedList.value = selectedList.value.filter((value: string) => value != el?.__vnode?.ctx?.props?.value)
|
||||
});
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
selectedList.value = [];
|
||||
for (const selectedElement of document.querySelectorAll(".selectable.selected")) {
|
||||
selectedElement.classList.remove("selected");
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-list class="select-area" :selected="[selectedPath]" style="height: calc(100vh - 120px);">
|
||||
<v-row no-gutters>
|
||||
<v-col cols="6" lg="2" md="3" sm="6" v-for="file of filesGridList" style="text-align: center;">
|
||||
<v-list-item class="position-relative ma-2 pa-5 selectable" active-color="light-blue-darken-4"
|
||||
:value="file.path" rounded @click="selectedPath = file.path">
|
||||
<v-menu v-if="!file.isFolder">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn class="position-absolute" style="right: 0; top: 0;" icon="mdi-dots-vertical"
|
||||
variant="text" v-bind="props"></v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item title="Download" aria-label="Download" icon="mdi-download"
|
||||
@click="filesManager.downloadFile(file.path)">
|
||||
<template v-slot:prepend>
|
||||
<v-icon icon="mdi-download"></v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<file-logo class="mb-2" :file="file" :key="file.path" />
|
||||
<p>{{ file.name }}</p>
|
||||
</v-list-item>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<VueSelecto :selectableTargets="['.selectable']" :dragContainer="dragContainer" :hitRate="0"
|
||||
:selectFromInside="false" :toggleContinueSelect="'ctrl'" @select="onSelectStart" @selectStart="onSelectStart"
|
||||
:get-element-rect="getElementInfo" @selectEnd="onSelectEnd" :select-by-click="false" />
|
||||
</v-list>
|
||||
</template>
|
166
components/media/binary-viewer.vue
Normal file
166
components/media/binary-viewer.vue
Normal file
@ -0,0 +1,166 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { FilesManager } from '~/composables/files-manager';
|
||||
import { iFile } from '~/composables/worker/7zip-manager';
|
||||
import { VBtn } from 'vuetify/components';
|
||||
|
||||
interface Props {
|
||||
file: iFile,
|
||||
filesManager: FilesManager
|
||||
}
|
||||
|
||||
const { file, filesManager } = defineProps<Props>()
|
||||
const buffer = ref<Uint8Array>()
|
||||
const containerRef = ref<HTMLElement>()
|
||||
const isLoading = ref(true)
|
||||
const rowHeight = 24 // pixels per row
|
||||
const visibleRows = ref(0)
|
||||
const scrollTop = ref(0)
|
||||
|
||||
// Calculate total rows and visible window
|
||||
const totalRows = computed(() => buffer.value ? Math.ceil(buffer.value.length / 16) : 0)
|
||||
const startRow = computed(() => Math.floor(scrollTop.value / rowHeight))
|
||||
const endRow = computed(() => Math.min(startRow.value + visibleRows.value + 50, totalRows.value))
|
||||
|
||||
// Get only the visible rows data
|
||||
const visibleData = computed(() => {
|
||||
if (!buffer.value) return []
|
||||
|
||||
const rows = []
|
||||
for (let i = startRow.value; i < endRow.value; i++) {
|
||||
const offset = i * 16
|
||||
const chunk = buffer.value.slice(offset, offset + 16)
|
||||
const hex = Array.from(chunk).map(b => b.toString(16).padStart(2, '0')).join(' ')
|
||||
const ascii = Array.from(chunk).map(b => b >= 32 && b <= 126 ? String.fromCharCode(b) : '.').join('')
|
||||
|
||||
rows.push({
|
||||
address: offset.toString(16).padStart(8, '0'),
|
||||
hex: hex.padEnd(48, ' '),
|
||||
ascii
|
||||
})
|
||||
}
|
||||
return rows
|
||||
})
|
||||
|
||||
// Handle scroll events
|
||||
function onScroll(event: Event) {
|
||||
const container = event.target as HTMLElement
|
||||
scrollTop.value = container.scrollTop
|
||||
}
|
||||
|
||||
// Calculate visible rows based on container height
|
||||
function updateVisibleRows() {
|
||||
if (containerRef.value) {
|
||||
visibleRows.value = Math.ceil(containerRef.value.clientHeight / rowHeight)
|
||||
}
|
||||
}
|
||||
|
||||
function downloadBinaryFile() {
|
||||
filesManager.downloadFile(file.path);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
buffer.value = await filesManager.getFileContent(file.path, "binary") as Uint8Array
|
||||
updateVisibleRows()
|
||||
window.addEventListener('resize', updateVisibleRows)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="binary-viewer">
|
||||
<div class="toolbar">
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
prepend-icon="mdi-download"
|
||||
@click="downloadBinaryFile"
|
||||
size="small"
|
||||
>
|
||||
Download {{ file.name }}
|
||||
</v-btn>
|
||||
</div>
|
||||
<div v-if="isLoading" class="loading">
|
||||
Loading...
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
ref="containerRef"
|
||||
class="hex-container"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<div
|
||||
class="scroll-content"
|
||||
:style="{
|
||||
height: `${totalRows * rowHeight}px`,
|
||||
paddingTop: `${startRow * rowHeight}px`
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-for="row in visibleData"
|
||||
:key="row.address"
|
||||
class="hex-line"
|
||||
>
|
||||
<span class="address">{{ row.address }}</span>
|
||||
<span class="hex">{{ row.hex }}</span>
|
||||
<span class="ascii">{{ row.ascii }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.binary-viewer {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
padding: 1rem;
|
||||
background-color: var(--v-theme-surface);
|
||||
height: calc(100vh - 148px);
|
||||
}
|
||||
|
||||
.hex-container {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.scroll-content {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hex-line {
|
||||
line-height: 1.5;
|
||||
height: v-bind(rowHeight + 'px');
|
||||
}
|
||||
|
||||
.address {
|
||||
color: #666;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.hex {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.ascii {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
71
components/media/image-viewer.vue
Normal file
71
components/media/image-viewer.vue
Normal file
@ -0,0 +1,71 @@
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps<{
|
||||
src: string;
|
||||
}>();
|
||||
|
||||
const scale = ref(1);
|
||||
const rotation = ref(0);
|
||||
|
||||
function zoomIn() {
|
||||
scale.value = Math.min(scale.value + 0.1, 3);
|
||||
}
|
||||
|
||||
function zoomOut() {
|
||||
scale.value = Math.max(scale.value - 0.1, 0.1);
|
||||
}
|
||||
|
||||
function resetZoom() {
|
||||
scale.value = 1;
|
||||
rotation.value = 0;
|
||||
}
|
||||
|
||||
function rotateImage() {
|
||||
rotation.value = (rotation.value + 90) % 360;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="image-viewer">
|
||||
<div class="image-controls">
|
||||
<v-btn icon="mdi-plus" @click="zoomIn" title="Zoom In"></v-btn>
|
||||
<v-btn icon="mdi-minus" @click="zoomOut" title="Zoom Out"></v-btn>
|
||||
<v-btn icon="mdi-rotate-right" @click="rotateImage" title="Rotate"></v-btn>
|
||||
<v-btn icon="mdi-refresh" @click="resetZoom" title="Reset"></v-btn>
|
||||
</div>
|
||||
<div class="image-container">
|
||||
<img :src="src" :style="{
|
||||
transform: `scale(${scale}) rotate(${rotation}deg)`,
|
||||
transition: 'transform 0.2s ease-in-out'
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.image-viewer {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.image-controls {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-container img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
@ -2,7 +2,6 @@
|
||||
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 {
|
||||
@ -18,6 +17,7 @@ let { file, filesManager } = defineProps<Props>()
|
||||
onMounted(async () => {
|
||||
const darkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
|
||||
if(!file) return;
|
||||
let fileContent = await filesManager.getFileContent(file.path);
|
||||
monaco.editor.create(editor.value, {
|
||||
value: fileContent?.toString()!,
|
||||
@ -35,6 +35,6 @@ onMounted(async () => {
|
||||
<style scoped>
|
||||
#editor {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
height: calc(100vh - 120px);
|
||||
}
|
||||
</style>
|
@ -10,7 +10,14 @@ import mime from 'mime';
|
||||
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 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 const binaryExtensions = ['exe', 'dll', 'so', 'dylib', 'bin', 'dat', 'db', 'sqlite', 'o', 'class', 'pyc'];
|
||||
export const supportedExtensions = [
|
||||
'7z', 'xz', 'bz2', 'gz', 'tar', 'zip', 'wim',
|
||||
'apfs', 'ar', 'arj', 'cab', 'chm', 'cpio', 'dmg', 'ext', 'fat', 'gpt', 'hfs',
|
||||
'ihex', 'iso', 'lzh', 'lzma', 'mbr', 'msi', 'nsis', 'ntfs', 'qcow2', 'rar',
|
||||
'rpm', 'squashfs', 'udf', 'uefi', 'vdi', 'vhd', 'vhdx', 'vmdk', 'xar', 'z'
|
||||
];
|
||||
|
||||
export class FilesManager {
|
||||
consoleOutputBuffer: string[] = [];
|
||||
@ -27,6 +34,12 @@ export class FilesManager {
|
||||
|
||||
async loadArchive(file: File) {
|
||||
if (!this.remoteSevenZipManager) return;
|
||||
|
||||
const extension = file.name.split('.').pop()?.toLowerCase();
|
||||
if (!extension || !supportedExtensions.includes(extension)) {
|
||||
throw new Error('Unsupported file format. Please use a supported archive format.');
|
||||
}
|
||||
|
||||
this.filesList.value = await this.remoteSevenZipManager.loadArchive(file) || [];
|
||||
}
|
||||
|
||||
|
@ -166,9 +166,10 @@ export class SevenZipManager {
|
||||
async getFileContent(file: iFile, encoding: "utf8" | "binary" = "utf8") {
|
||||
if (!this.sevenZip) return;
|
||||
file = typeof file === "string" ? JSON.parse(file) : file;
|
||||
if (!file) return;
|
||||
|
||||
// extract file from archive
|
||||
this.execute(['x', '-y', this.archiveName, file.path.substring(1)]);
|
||||
this.execute(['x', '-y', this.archiveName, file?.path.substring(1)]);
|
||||
this.sevenZip.FS.chmod(file.path, 0o777);
|
||||
|
||||
// get file buffer
|
||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -2662,9 +2662,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001487",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz",
|
||||
"integrity": "sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==",
|
||||
"version": "1.0.30001677",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz",
|
||||
"integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user