mirror of
https://github.com/xlmnxp/extractify.zip.git
synced 2024-11-24 01:24:35 +03:00
Compare commits
No commits in common. "3059a2f252ab720f5e441889595de5748a2c7eff" and "c8209c5799f9e6562077be28863ba0c8c9a88b77" have entirely different histories.
3059a2f252
...
c8209c5799
123
app.vue
123
app.vue
@ -1,9 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { getElementInfo } from "moveable";
|
||||||
|
import { VueSelecto } from "vue3-selecto";
|
||||||
import { useDisplay } from 'vuetify/lib/framework.mjs';
|
import { useDisplay } from 'vuetify/lib/framework.mjs';
|
||||||
import { HistoryManager } from './composables/history-manager';
|
import { HistoryManager } from './composables/history-manager';
|
||||||
import { FilesManager, supportedExtensions, imageExtensions } from './composables/files-manager';
|
import { FilesManager } from './composables/files-manager';
|
||||||
import type { iFile } from "composables/worker/7zip-manager"
|
import type { iFile } from "composables/worker/7zip-manager"
|
||||||
import { videoExtensions, binaryExtensions } from '#imports';
|
|
||||||
|
|
||||||
let display = useDisplay();
|
let display = useDisplay();
|
||||||
let drawer = ref(!display.mdAndDown.value);
|
let drawer = ref(!display.mdAndDown.value);
|
||||||
@ -17,21 +18,13 @@ let selectedList = ref<any>([]);
|
|||||||
let filesManager = new FilesManager(filesList);
|
let filesManager = new FilesManager(filesList);
|
||||||
let history = new HistoryManager(filesManager);
|
let history = new HistoryManager(filesManager);
|
||||||
let mediaBlobUrl = ref('');
|
let mediaBlobUrl = ref('');
|
||||||
let errorDialog = ref(false);
|
|
||||||
let errorMessage = ref('');
|
|
||||||
|
|
||||||
watchEffect(async () => {
|
watchEffect(async () => {
|
||||||
if (files.value?.[0]) {
|
if (files.value?.[0]) {
|
||||||
loadingModel.value = true;
|
loadingModel.value = true;
|
||||||
filesList.value = [];
|
filesList.value = [];
|
||||||
|
|
||||||
try {
|
await filesManager.loadArchive(files.value?.[0]);
|
||||||
await filesManager.loadArchive(files.value?.[0]);
|
|
||||||
} catch (error: any) {
|
|
||||||
errorMessage.value = error.message;
|
|
||||||
errorDialog.value = true;
|
|
||||||
files.value = [];
|
|
||||||
}
|
|
||||||
loadingModel.value = false;
|
loadingModel.value = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -64,13 +57,41 @@ watchEffect(async () => {
|
|||||||
const file = filesManager.getFile(selectedPath.value);
|
const file = filesManager.getFile(selectedPath.value);
|
||||||
filesGridList.value = file?.content;
|
filesGridList.value = file?.content;
|
||||||
|
|
||||||
// Update to handle both video and image files
|
selectedList.value = [];
|
||||||
if (videoExtensions.includes(filesManager.getFile(selectedPath.value)?.extension?.toLowerCase()) ||
|
for (const selectedElement of document.querySelectorAll(".selectable.selected")) {
|
||||||
imageExtensions.includes(filesManager.getFile(selectedPath.value)?.extension?.toLowerCase())) {
|
selectedElement.classList.remove("selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Experimental feature
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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
|
// step up from current path
|
||||||
function stepUp(path: string) {
|
function stepUp(path: string) {
|
||||||
const pathArray = path.split("/");
|
const pathArray = path.split("/");
|
||||||
@ -102,8 +123,9 @@ function stepUp(path: string) {
|
|||||||
</div>
|
</div>
|
||||||
</v-footer>
|
</v-footer>
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
<v-main style="height: 100dvh;">
|
<v-main class="select-area" style="height: 100dvh;">
|
||||||
<v-toolbar class="px-5" height="auto">
|
<v-toolbar class="px-5" height="auto">
|
||||||
|
|
||||||
<v-row align="center" justify="center">
|
<v-row align="center" justify="center">
|
||||||
<v-col cols="12" lg="2" md="12" style="display: inline-flex;">
|
<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"
|
<v-btn title="Back" aria-label="Back" icon="mdi-arrow-left" :disabled="!history.hasUndo.value"
|
||||||
@ -139,27 +161,44 @@ function stepUp(path: string) {
|
|||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<v-container>
|
<v-container>
|
||||||
<template v-if="filesManager.getFile(selectedPath)?.isFolder">
|
<template v-if="filesManager.getFile(selectedPath)?.isFolder">
|
||||||
<FolderViewer :filesManager="filesManager" :filesGridList="filesGridList" :selectedList="selectedList"></FolderViewer>
|
<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>
|
||||||
</template>
|
</template>
|
||||||
<template
|
<template
|
||||||
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
|
<template
|
||||||
v-if="!filesManager.getFile(selectedPath)?.isFolder && imageExtensions.includes(filesManager.getFile(selectedPath)?.extension)">
|
v-if="!filesManager.getFile(selectedPath)?.isFolder && files.length && !videoExtensions.includes(filesManager.getFile(selectedPath)?.extension)">
|
||||||
<MediaImageViewer :src="mediaBlobUrl"></MediaImageViewer>
|
<TextEditor :file="filesManager.getFile(selectedPath)" :filesManager="filesManager"></TextEditor>
|
||||||
</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>
|
||||||
<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" style="height: calc(100vh - 120px)">
|
<v-row align="center" justify="center">
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-card variant="flat" class="mx-auto" max-width="768">
|
<v-card variant="flat" class="mx-auto" max-width="768">
|
||||||
<!-- v-icon for file -->
|
<!-- v-icon for file -->
|
||||||
@ -175,14 +214,17 @@ function stepUp(path: string) {
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<!-- file input -->
|
<!-- file input -->
|
||||||
<v-file-input class="mx-5" v-model="files"
|
<v-file-input class="mx-5" v-model="files" accept=".zip,.7z,.rar,.tar.bz2,.tar.gz,.tar.xz"
|
||||||
:accept="supportedExtensions.map(extension => `.${extension}`).join(',')" label="or select a file..."
|
label="or select a file..." variant="outlined"></v-file-input>
|
||||||
variant="outlined"></v-file-input>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</template>
|
</template>
|
||||||
</v-container>
|
</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-main>
|
||||||
<v-dialog v-model="loadingModel" persistent width="auto">
|
<v-dialog v-model="loadingModel" persistent width="auto">
|
||||||
<v-card>
|
<v-card>
|
||||||
@ -192,22 +234,6 @@ function stepUp(path: string) {
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</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>
|
</v-layout>
|
||||||
</template>
|
</template>
|
||||||
<style>
|
<style>
|
||||||
@ -218,9 +244,4 @@ function stepUp(path: string) {
|
|||||||
.selected {
|
.selected {
|
||||||
background: rgba(48, 150, 243, 0.1);
|
background: rgba(48, 150, 243, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-container {
|
|
||||||
height: calc(100vh - 120px);
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
@ -1,79 +0,0 @@
|
|||||||
<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>
|
|
@ -1,166 +0,0 @@
|
|||||||
<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>
|
|
@ -1,71 +0,0 @@
|
|||||||
<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,6 +2,7 @@
|
|||||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { FilesManager } from '~/composables/files-manager';
|
import { FilesManager } from '~/composables/files-manager';
|
||||||
|
// @ts-ignore
|
||||||
import { iFile } from '~/composables/worker/7zip-manager';
|
import { iFile } from '~/composables/worker/7zip-manager';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -17,7 +18,6 @@ let { file, filesManager } = defineProps<Props>()
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const darkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
|
const darkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
|
||||||
if(!file) return;
|
|
||||||
let fileContent = await filesManager.getFileContent(file.path);
|
let fileContent = await filesManager.getFileContent(file.path);
|
||||||
monaco.editor.create(editor.value, {
|
monaco.editor.create(editor.value, {
|
||||||
value: fileContent?.toString()!,
|
value: fileContent?.toString()!,
|
||||||
@ -35,6 +35,6 @@ onMounted(async () => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
#editor {
|
#editor {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: calc(100vh - 120px);
|
height: 100vh;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -10,14 +10,7 @@ import mime from 'mime';
|
|||||||
export const videoExtensions = ['mp4', 'avi', 'mov', 'mkv'];
|
export const videoExtensions = ['mp4', 'avi', 'mov', 'mkv'];
|
||||||
export const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
export const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||||
export const audioExtensions = ['mp3', 'wav', 'ogg', 'flac'];
|
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 {
|
export class FilesManager {
|
||||||
consoleOutputBuffer: string[] = [];
|
consoleOutputBuffer: string[] = [];
|
||||||
@ -34,12 +27,6 @@ export class FilesManager {
|
|||||||
|
|
||||||
async loadArchive(file: File) {
|
async loadArchive(file: File) {
|
||||||
if (!this.remoteSevenZipManager) return;
|
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) || [];
|
this.filesList.value = await this.remoteSevenZipManager.loadArchive(file) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,10 +166,9 @@ export class SevenZipManager {
|
|||||||
async getFileContent(file: iFile, encoding: "utf8" | "binary" = "utf8") {
|
async getFileContent(file: iFile, encoding: "utf8" | "binary" = "utf8") {
|
||||||
if (!this.sevenZip) return;
|
if (!this.sevenZip) return;
|
||||||
file = typeof file === "string" ? JSON.parse(file) : file;
|
file = typeof file === "string" ? JSON.parse(file) : file;
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
// extract file from archive
|
// 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);
|
this.sevenZip.FS.chmod(file.path, 0o777);
|
||||||
|
|
||||||
// get file buffer
|
// get file buffer
|
||||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -2662,9 +2662,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001677",
|
"version": "1.0.30001487",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz",
|
||||||
"integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==",
|
"integrity": "sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user