mirror of
https://github.com/ducbao414/win32.run.git
synced 2025-12-18 10:12:50 +09:00
init the awkward code
This commit is contained in:
225
src/app.css
Normal file
225
src/app.css
Normal file
File diff suppressed because one or more lines are too long
225
src/app.html
Normal file
225
src/app.html
Normal file
@@ -0,0 +1,225 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="" style="background:black">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Microsoft Windows XP Professional</title>
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<link rel="stylesheet" id="theme" href=""/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<meta name="description" content="Yet another Windows XP in the browser, but with a File System and comes with it, Programs."/>
|
||||
%sveltekit.head%
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
section {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.blink {
|
||||
animation: blink-animation 1s steps(5, start) infinite;
|
||||
-webkit-animation: blink-animation 1s steps(5, start) infinite;
|
||||
}
|
||||
|
||||
@keyframes blink-animation {
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes blink-animation {
|
||||
to {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
.actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.float-right {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding-right: 2em;
|
||||
}
|
||||
|
||||
#bios {
|
||||
display: block;
|
||||
background: #000;
|
||||
color: #ccc;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#loader {
|
||||
display: none;
|
||||
background: #000;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.loader {
|
||||
position: relative;
|
||||
padding-top: 100px;
|
||||
width: 40px;
|
||||
margin: auto;
|
||||
}
|
||||
.loader .circle {
|
||||
position: absolute;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
opacity: 0;
|
||||
transform: rotate(225deg);
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: orbit;
|
||||
animation-duration: 5.5s;
|
||||
}
|
||||
.loader .circle:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
/* Pick a color */
|
||||
}
|
||||
.loader .circle:nth-child(2) {
|
||||
animation-delay: 240ms;
|
||||
}
|
||||
.loader .circle:nth-child(3) {
|
||||
animation-delay: 480ms;
|
||||
}
|
||||
.loader .circle:nth-child(4) {
|
||||
animation-delay: 720ms;
|
||||
}
|
||||
.loader .circle:nth-child(5) {
|
||||
animation-delay: 960ms;
|
||||
}
|
||||
|
||||
@keyframes orbit {
|
||||
0% {
|
||||
transform: rotate(225deg);
|
||||
opacity: 1;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
7% {
|
||||
transform: rotate(345deg);
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
30% {
|
||||
transform: rotate(455deg);
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
39% {
|
||||
transform: rotate(690deg);
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
70% {
|
||||
transform: rotate(815deg);
|
||||
opacity: 1;
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
75% {
|
||||
transform: rotate(945deg);
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
76% {
|
||||
transform: rotate(945deg);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: rotate(945deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="iframe-preload" style="position: absolute;inset: 0;display: none;"></div>
|
||||
|
||||
<div style="position:absolute;inset:0;">%sveltekit.body%</div>
|
||||
|
||||
<div id="pos_loader" style="position:absolute;inset:0;padding:10px;background-color:black;font-size: 18px;">
|
||||
<!-- Bootup by Kyle Stephens -->
|
||||
<!-- https://codepen.io/kylestephens/pen/zYOgLrr -->
|
||||
<section id="bios">
|
||||
<p>PhoenixBIOS 1.4 Release 6.0</p>
|
||||
<p>Copyright 1985-2001 Phoenix Technologies Ltd.</p>
|
||||
<p>All Rights Reserved</p>
|
||||
<p>Copyright 2001-2003 VMware. Inc.</p>
|
||||
<p>VMware BIOS build 314</p>
|
||||
<br />
|
||||
<p>ATAPI CD-ROM: VMware Virtual IDECDROM Drive</p>
|
||||
<p>Initializing <span class="blink">...</span></p>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="loader">
|
||||
<div class='loader'>
|
||||
<div class='circle'></div>
|
||||
<div class='circle'></div>
|
||||
<div class='circle'></div>
|
||||
<div class='circle'></div>
|
||||
<div class='circle'></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.addEventListener('contextmenu', (event) => {
|
||||
event.preventDefault();
|
||||
})
|
||||
</script>
|
||||
|
||||
<script src="//unpkg.com/loadjs@latest/dist/loadjs.min.js"></script>
|
||||
|
||||
<script>
|
||||
function load_assets(assets, completion){
|
||||
Promise
|
||||
.all(assets.map(asset=>fetch(asset)))
|
||||
.then(responses =>
|
||||
Promise.all(responses.map(res => res.blob()))
|
||||
).then(completion);
|
||||
}
|
||||
|
||||
load_assets([
|
||||
'/images/xp_loading_logo.jpg',
|
||||
'/images/xp_loading_mslogo.jpg',
|
||||
'/images/xp_logo_horizontal.png',
|
||||
'/images/xp_logo.png',
|
||||
'/fonts/levi.ttf',
|
||||
'/fonts/ms_sans_serif_bold.ttf',
|
||||
'/fonts/ms_sans_serif.ttf'], () => {
|
||||
document.querySelector('#pos_loader').style.display = 'none';
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
document.addEventListener('dragover', function(e){
|
||||
e.preventDefault();
|
||||
console.log('dragover');
|
||||
})
|
||||
|
||||
document.addEventListener('drop', function(e){
|
||||
e.preventDefault();
|
||||
console.log('drop');
|
||||
})
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
6
src/hooks.js
Normal file
6
src/hooks.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export const handle = async ({ event, resolve }) => {
|
||||
const response = await resolve(event, {
|
||||
ssr: false
|
||||
});
|
||||
return response;
|
||||
};
|
||||
19
src/lib/components/95/Button.svelte
Normal file
19
src/lib/components/95/Button.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<script>
|
||||
export let on_click = (event) => {
|
||||
|
||||
}
|
||||
|
||||
export let title = '';
|
||||
export let style = '';
|
||||
export let disabled = false;
|
||||
|
||||
</script>
|
||||
|
||||
<button on:click={(event) => on_click(event)} disabled={disabled} style="
|
||||
{style};
|
||||
background: silver;
|
||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf;"
|
||||
class="px-2 py-1 text-center min-w-[50px] min-h-[20px] font-MSSS text-sm text-black
|
||||
focus:outline-dotted outline-black outline-offset-[-4px] disabled:text-gray-500">
|
||||
{title}
|
||||
</button>
|
||||
39
src/lib/components/95/TitleBar.svelte
Normal file
39
src/lib/components/95/TitleBar.svelte
Normal file
@@ -0,0 +1,39 @@
|
||||
<script>
|
||||
|
||||
export let options = {};
|
||||
|
||||
let {close_btn=true, maximize_btn=true, minimize_btn=true,
|
||||
close_btn_disabled=false, maximize_btn_disabled=false, minimize_btn_disabled=false,
|
||||
title=''} = options;
|
||||
|
||||
export let on_click_maximize = () => {}
|
||||
export let on_click_minimize = () => {}
|
||||
export let on_click_close = () => {}
|
||||
</script>
|
||||
|
||||
<div class="titlebar flex items-center justify-between bg-[linear-gradient(90deg,navy,#1084d0)] p-0.5 font-MSSS">
|
||||
<p class="text-white font-semibold mr-4 text-sm ml-1 mb-1">{title}</p>
|
||||
<div class="flex">
|
||||
{#if minimize_btn}
|
||||
<button disabled={minimize_btn_disabled} on:click={on_click_minimize}
|
||||
class="group w-4 h-4 ml-1 bg-[#c0c0c0]" style="box-shadow:inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf">
|
||||
<svg class="fill-black group-disabled:fill-gray-500 w-3 h-3 m-0.5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M32 416c-17.7 0-32 14.3-32 32s14.3 32 32 32H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H32z"/></svg>
|
||||
</button>
|
||||
{/if}
|
||||
{#if maximize_btn}
|
||||
<button disabled={maximize_btn_disabled} on:click={on_click_maximize}
|
||||
class="group w-4 h-4 ml-1 bg-[#c0c0c0]" style="box-shadow:inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf">
|
||||
<svg class="fill-black group-disabled:fill-gray-500 w-3 h-3 m-0.5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M7.724 65.49C13.36 55.11 21.79 46.47 32 40.56C39.63 36.15 48.25 33.26 57.46 32.33C59.61 32.11 61.79 32 64 32H448C483.3 32 512 60.65 512 96V416C512 451.3 483.3 480 448 480H64C28.65 480 0 451.3 0 416V96C0 93.79 .112 91.61 .3306 89.46C1.204 80.85 3.784 72.75 7.724 65.49V65.49zM48 416C48 424.8 55.16 432 64 432H448C456.8 432 464 424.8 464 416V224H48V416z"/></svg>
|
||||
</button>
|
||||
{/if}
|
||||
{#if close_btn}
|
||||
<button disabled={close_btn_disabled} on:click={on_click_close}
|
||||
class="group w-4 h-4 ml-1 bg-[#c0c0c0]" style="box-shadow:inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf">
|
||||
<svg class="fill-black group-disabled:fill-gray-500 w-3 h-3 m-0.5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"/></svg>
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
125
src/lib/components/95/Window.svelte
Normal file
125
src/lib/components/95/Window.svelte
Normal file
@@ -0,0 +1,125 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte';
|
||||
import TitleBar from './TitleBar.svelte';
|
||||
import * as utils from '../../utils';
|
||||
import _ from 'lodash';
|
||||
|
||||
export let self;
|
||||
export let options = {something: 'some value'};
|
||||
|
||||
let node_ref;
|
||||
let saved_position;
|
||||
let maximized;
|
||||
let minimized;
|
||||
|
||||
onMount(() => {
|
||||
|
||||
if(options.top == null){
|
||||
options.top = (node_ref.parentNode.offsetHeight - node_ref.offsetHeight)/2;
|
||||
}
|
||||
if(options.left == null){
|
||||
options.left = (node_ref.parentNode.offsetWidth - node_ref.offsetWidth)/2;
|
||||
}
|
||||
set_position({top: options.top, left: options.left, width: node_ref.width, height: node_ref.height});
|
||||
|
||||
|
||||
// enable_resize();
|
||||
enable_drag();
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
let on_click_close = () => {
|
||||
self.$destroy();
|
||||
}
|
||||
|
||||
export let on_click_maximize = () => {
|
||||
if(maximized){
|
||||
set_position(saved_position);
|
||||
maximized = false;
|
||||
} else {
|
||||
let rect = utils.relative_rect(node_ref.parentNode.getBoundingClientRect(), node_ref.getBoundingClientRect());
|
||||
console.log(rect);
|
||||
saved_position = {top: rect.top, left: rect.left, width: node_ref.offsetWidth, height: node_ref.offsetHeight};
|
||||
set_position({top: 0, left: 0, width: node_ref.parentNode.offsetWidth, height: node_ref.parentNode.offsetHeight});
|
||||
maximized = true;
|
||||
}
|
||||
}
|
||||
|
||||
function set_position({top, left, width, height, absolute_values}){
|
||||
if(absolute_values){
|
||||
let parent_top = node_ref.parentNode.getBoundingClientRect().top;
|
||||
let parent_left = node_ref.parentNode.getBoundingClientRect().left;
|
||||
top = top - parent_top;
|
||||
left = left - parent_left;
|
||||
}
|
||||
node_ref.style.top = `${top}px`;
|
||||
node_ref.style.left = `${left}px`;
|
||||
node_ref.style.width = `${width}px`;
|
||||
node_ref.style.height = `${height}px`;
|
||||
}
|
||||
|
||||
function enable_resize(){
|
||||
interact(node_ref)
|
||||
.resizable({
|
||||
edges: { top: true, left: true, bottom: true, right: true },
|
||||
listeners: {
|
||||
move: function (event) {
|
||||
let { x, y } = event.target.dataset;
|
||||
x = (parseFloat(x) || 0) + event.deltaRect.left;
|
||||
y = (parseFloat(y) || 0) + event.deltaRect.top;
|
||||
let rect = node_ref.getBoundingClientRect();
|
||||
set_position({top: y + rect.top, left: x + rect.left, width: event.rect.width, height: event.rect.height, absolute_values: true});
|
||||
}
|
||||
},
|
||||
modifiers: [
|
||||
// keep the edges inside the parent
|
||||
interact.modifiers.restrictEdges({
|
||||
outer: 'parent'
|
||||
}),
|
||||
|
||||
// minimum size
|
||||
interact.modifiers.restrictSize({
|
||||
min: { width: 100, height: 50 }
|
||||
})
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
function enable_drag(){
|
||||
jQuery(node_ref).draggable({
|
||||
containment: 'parent',
|
||||
handle: '.titlebar'
|
||||
})
|
||||
jQuery(node_ref).resizable({
|
||||
minWidth: options.min_width,
|
||||
minHeight: options.min_height,
|
||||
containment: 'parent',
|
||||
handles: 'all',
|
||||
classes: {
|
||||
'ui-resizable-se': ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<div bind:this={node_ref} style="
|
||||
position: absolute;
|
||||
background: silver;
|
||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #dfdfdf, inset -2px -2px grey, inset 2px 2px #fff;
|
||||
padding: 3px" class="absolute flex flex-col"
|
||||
style:min-width="{options.min_width}px" style:min-height="{options.min_height}px">
|
||||
|
||||
<div class="shrink-0">
|
||||
<TitleBar options={options}
|
||||
on_click_close={on_click_close} on_click_maximize={on_click_maximize}>
|
||||
</TitleBar>
|
||||
</div>
|
||||
|
||||
<div class="grow shrink-0 relative">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
28
src/lib/components/SMenu.svelte
Normal file
28
src/lib/components/SMenu.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script>
|
||||
export let style = '';
|
||||
export let items = [];
|
||||
export let group_class = 'group';
|
||||
</script>
|
||||
|
||||
|
||||
<div class="absolute left-[90%] bottom-0 w-[250px] shadow-xl border-t border-l-4 border-blue-500 hidden group-sub1-hover:block bg-slate-50">
|
||||
{#each items as item}
|
||||
{#if item == null}
|
||||
<div class="my-0.5 mx-auto w-5/6 h-[1px] bg-slate-200 shrink-0"></div>
|
||||
{:else}
|
||||
<div class="flex flex-row items-center grow p-1 group-sub2 hover:bg-blue-500 relative">
|
||||
<div class="w-5 h-5 bg-contain mr-1 shrink-0"
|
||||
style:background-image="url({item.icon})">
|
||||
</div>
|
||||
<div class="text-[11px] text-slate-800 grow group-sub12hover:text-white">
|
||||
{item.name}
|
||||
</div>
|
||||
<div class="w-[10px] shrink-0">
|
||||
{#if item.items != null}
|
||||
<svg class="fill-slate-900 group-sub2-hover:fill-slate-50 w-[10px] h-[10px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M246.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8 16.6-19.8 29.6l0 256c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l128-128z"/></svg>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
42
src/lib/components/dos/status_bar.svelte
Normal file
42
src/lib/components/dos/status_bar.svelte
Normal file
@@ -0,0 +1,42 @@
|
||||
<script>
|
||||
export let l_message = '';
|
||||
export let r_message = '';
|
||||
export let show_separator = false;
|
||||
|
||||
export async function display(messages){
|
||||
for(let {l, r, d} of messages){
|
||||
if(!l) l = '';
|
||||
if(!r) r = '';
|
||||
if(!d) d = 1000;
|
||||
l_message = l;
|
||||
r_message = r;
|
||||
await sleep(d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div class="absolute bottom-0 left-0 right-0 h-8 bg-slate-200 text-slate-900 flex flex-row overflow-hidden font-MSSS">
|
||||
<div class="grow h-full pb-1 font-bold px-4">
|
||||
{l_message}
|
||||
</div>
|
||||
{#if show_separator}
|
||||
<div class="w-1 h-full bg-slate-900"></div>
|
||||
{/if}
|
||||
<div class="basis-1/4 shrink-0 pb-1 font-bold px-4">
|
||||
{r_message}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
13
src/lib/components/dos_loader.svelte
Normal file
13
src/lib/components/dos_loader.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<script>
|
||||
|
||||
export let show = false;
|
||||
|
||||
</script>
|
||||
|
||||
<div class="absolute top-0 left-0 right-0 bottom-0 w-screen h-screen bg-black overflow-hidden {show ? '' : 'hidden'} font-MSSS">
|
||||
<div class="mt-12 ml-8 text-lg">
|
||||
<div class="w-6 h-1 animate-cursor bg-slate-50">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
54
src/lib/components/xp/Button.svelte
Normal file
54
src/lib/components/xp/Button.svelte
Normal file
@@ -0,0 +1,54 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte';
|
||||
export let on_click = (event) => {}
|
||||
|
||||
export let title = '';
|
||||
export let style = '';
|
||||
export let disabled = false;
|
||||
export let focus = false;
|
||||
|
||||
let node_ref;
|
||||
|
||||
onMount(() => {
|
||||
if(focus){
|
||||
node_ref.focus();
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<button bind:this={node_ref} on:click={(event) => on_click(event)}
|
||||
disabled={disabled} style="{style};" class="button disabled:opacity-30">
|
||||
{title}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
.button {
|
||||
font-family: 'MSSS', Arial, Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 11px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #003c74;
|
||||
background: linear-gradient(180deg,#fff,#ecebe5 86%,#d8d0c4);
|
||||
box-shadow: none;
|
||||
border-radius: 3px;
|
||||
min-width: 75px;
|
||||
min-height: 23px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button:focus {
|
||||
outline: 1px dotted #000;
|
||||
outline-offset: -4px;
|
||||
box-shadow: inset -1px 1px #cee7ff, inset 1px 2px #98b8ea, inset -2px 2px #bcd4f6, inset 1px -1px #89ade4, inset 2px -2px #89ade4;
|
||||
}
|
||||
|
||||
button:not(:disabled):hover {
|
||||
box-shadow: inset -1px 1px #fff0cf, inset 1px 2px #fdd889, inset -2px 2px #fbc761, inset 2px -2px #e5a01a;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
28
src/lib/components/xp/CheckBox.svelte
Normal file
28
src/lib/components/xp/CheckBox.svelte
Normal file
@@ -0,0 +1,28 @@
|
||||
<script>
|
||||
export let style = '';
|
||||
export let checkmark = true;
|
||||
export let size = 12;
|
||||
export let checked = false;
|
||||
export let label = "";
|
||||
|
||||
function on_click(){
|
||||
checked = !checked;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row items-start" on:click={on_click} style="{style}">
|
||||
<div class="border border-slate-500 bg-slate-50 inline-block box-content"
|
||||
style:width="{size}px" style:height="{size}px">
|
||||
{#if checked}
|
||||
{#if checkmark}
|
||||
<img alt="" src="/images/xp/checkmark.png" style:width="{size-2}px" style:height="{size-2}px" style:margin="1px">
|
||||
|
||||
{:else}
|
||||
<div class="bg-gradient-to-r from-green-600 to-green-500 block"
|
||||
style:width="{size-4}px" style:height="{size-4}px" style:margin="2px">
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<span class="text-[11px] ml-1">{label}</span>
|
||||
</div>
|
||||
128
src/lib/components/xp/ContextMenu.svelte
Normal file
128
src/lib/components/xp/ContextMenu.svelte
Normal file
@@ -0,0 +1,128 @@
|
||||
<script>
|
||||
let menu = [];
|
||||
let required_width = 0;
|
||||
let required_height = 0;
|
||||
|
||||
import { click_outside } from '../../utils';
|
||||
import { contextMenu } from '../../store';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { filter } from 'lodash';
|
||||
|
||||
let top = 0;
|
||||
let left = 0;
|
||||
let visible = false;
|
||||
let screenWidth = 700;
|
||||
let screenHeight = 700;
|
||||
|
||||
let unsubscriber = contextMenu.subscribe(async obj => {
|
||||
if(obj == null){
|
||||
visible = false;
|
||||
return;
|
||||
}
|
||||
let {x, y, originator, type } = obj;
|
||||
|
||||
if(type == null) return;
|
||||
|
||||
let menu_obj;
|
||||
if(type == 'ProgramTile'){
|
||||
menu_obj = (await import('./context_menu/CMProgramTile')).make({type, originator});
|
||||
|
||||
} else if(type == 'Desktop'){
|
||||
menu_obj = (await import('./context_menu/CMDesktop')).make({type, originator});
|
||||
} else if(type == 'FSItem'){
|
||||
menu_obj = (await import('./context_menu/CMFSItem')).make({type, originator});
|
||||
} else if(type == 'FSVoid'){
|
||||
menu_obj = (await import('./context_menu/CMFSVoid')).make({type, originator});
|
||||
} else if(type == 'RecycleBin'){
|
||||
menu_obj = (await import('./context_menu/RecycleBin')).make({type, originator});
|
||||
}
|
||||
|
||||
menu = menu_obj.menu;
|
||||
required_width = menu_obj.required_width;
|
||||
required_height = menu_obj.required_height;
|
||||
|
||||
screenWidth = document.body.offsetWidth;
|
||||
screenHeight = document.body.offsetHeight;
|
||||
|
||||
left = Math.min(x, screenWidth - required_width);
|
||||
top = Math.min(y, screenHeight - required_height);
|
||||
visible = true;
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscriber();
|
||||
})
|
||||
|
||||
|
||||
export let hide = () => {
|
||||
visible = false;
|
||||
contextMenu.set(null);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div use:click_outside on:click_outside={() => hide()}
|
||||
class="context-menu z-20 pt-0.5 absolute border-2 border-slate-200 bg-slate-50 text-slate-900 w-[180px] text-[11px] {visible ? '' : 'hidden' }"
|
||||
style:top="{top}px" style:left="{left}px">
|
||||
{#each menu.filter(el => el.length > 0) as group, group_index}
|
||||
<div class="w-full border-slate-200 {group_index == menu.length - 1 ? '' : 'border-b'}">
|
||||
{#each group as item}
|
||||
<div class="py-1 w-full flex flex-row items-center {item.disabled? '' : 'hover:bg-blue-600'} relative group
|
||||
{item.disabled ? 'pointer-events-none' : ''}"
|
||||
on:click={() => {
|
||||
if(!item.disabled){
|
||||
if(item.items != null) return;
|
||||
hide();
|
||||
item.action();
|
||||
}
|
||||
}}>
|
||||
<div class="w-[20px] ml-1 shrink-0">
|
||||
{#if item.icon}
|
||||
<img src="{item.icon}"
|
||||
class="w-[17px] h-[17px] {item.icon_type == 'monotone' ? 'group-hover:invert' : ''}
|
||||
{item.disabled && item.icon_type == 'monotone' ? 'contrast-50 invert' : ''}"
|
||||
style:width="{item.icon_size}px" style:height="{item.icon_size}px" alt="">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grow {item.font == 'bold' ? 'font-bold' : ''} {item.disabled ? 'text-slate-400' : 'group-hover:text-slate-50'}">
|
||||
<p>{item.name}</p>
|
||||
</div>
|
||||
<div class="w-[10px]">
|
||||
{#if item.items != null}
|
||||
<svg class="fill-slate-900 group-hover:fill-slate-50 w-[10px] h-[10px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M246.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-9.2-9.2-22.9-11.9-34.9-6.9s-19.8 16.6-19.8 29.6l0 256c0 12.9 7.8 24.6 19.8 29.6s25.7 2.2 34.9-6.9l128-128z"/></svg>
|
||||
{/if}
|
||||
</div>
|
||||
{#if item.items != null}
|
||||
<!-- submenu items -->
|
||||
<div class="absolute {left > screenWidth - 2*required_width ? '-left-full' : 'left-full'}
|
||||
py-0.5 hidden group-hover:flex flex-col w-[180px] bg-slate-50 border-slate-200 border-2"
|
||||
style:top="{top > screenHeight - 2*required_height ? `-${(item.items.length-1)*100}%` : '0'} ">
|
||||
{#each item.items as item}
|
||||
<div class="py-1 w-full flex flex-row items-center {item.disabled ? '' : 'hover:bg-blue-600'} relative group-sub
|
||||
{item.disabled ? 'pointer-events-none' : ''}"
|
||||
on:click={() => {
|
||||
if(!item.disabled){
|
||||
hide();
|
||||
item.action();
|
||||
}
|
||||
}}>
|
||||
<div class="w-[20px] ml-1 shrink-0">
|
||||
{#if item.icon}
|
||||
<img src="{item.icon}" width="17px" height="17px">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grow {item.font == 'bold' ? 'font-bold' : ''} {item.disabled ? 'text-slate-400' : 'group-sub-hover:text-slate-50'}">
|
||||
<p>{item.name}</p>
|
||||
</div>
|
||||
<div class="w-[10px]">
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
53
src/lib/components/xp/Dialog.svelte
Normal file
53
src/lib/components/xp/Dialog.svelte
Normal file
@@ -0,0 +1,53 @@
|
||||
<script>
|
||||
import * as utils from '../../utils';
|
||||
|
||||
import _, { find, isEqual } from 'lodash';
|
||||
import TitleBar from './TitleBar.svelte';
|
||||
import Button from './Button.svelte';
|
||||
|
||||
|
||||
export let self;
|
||||
export let title = '';
|
||||
export let message = '';
|
||||
export let icon = '';
|
||||
export let buttons = [];
|
||||
export let button_align = 'right';//center, right
|
||||
|
||||
|
||||
export function destroy(){
|
||||
console.log(self);
|
||||
self.$destroy();
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="z-20 dialog absolute inset-0 bg-slate-50/10 rounded-t-lg" on:click|self={(e) => {
|
||||
e.target.querySelector('div').classList.add('animate-blink');
|
||||
setTimeout(() => {
|
||||
e.target.querySelector('div').classList.remove('animate-blink');
|
||||
}, 400);
|
||||
}}>
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col"
|
||||
style:width="400px" style:height="150px">
|
||||
<TitleBar options={{title: title, maximize_btn: false, minimize_btn: false}} on_click_close={destroy}></TitleBar>
|
||||
<div class="grow p-2 bg-xp-yellow overflow-hidden flex flex-col justify-between border-t-0 border-2 border-blue-600">
|
||||
<div class="grow flex flex-row text-[11px] p-2 text-slate-800">
|
||||
{#if icon.length > 0}
|
||||
<div class="w-8 h-8 mr-4 shrink-0 bg-contain" style:background-image="url({icon})"></div>
|
||||
{/if}
|
||||
<div>
|
||||
{message}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row pb-1 items-center {button_align == 'center' ? 'justify-center' : 'justify-end'}">
|
||||
{#each buttons as button}
|
||||
<Button title={button.name} on_click={button.action} focus={button.focus} style="margin-left:7px;margin-right:7px;"></Button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
24
src/lib/components/xp/DumbProgress.svelte
Normal file
24
src/lib/components/xp/DumbProgress.svelte
Normal file
@@ -0,0 +1,24 @@
|
||||
<script>
|
||||
import ProgressBar from "./ProgressBar.svelte";
|
||||
import * as utils from '../../utils';
|
||||
import {onMount} from 'svelte';
|
||||
|
||||
export let style = '';
|
||||
export let increment = 5;
|
||||
|
||||
let progress_bar;
|
||||
onMount(async () => {
|
||||
while(progress_bar != null){
|
||||
progress_bar.value = Math.min(progress_bar.value + increment, 100);
|
||||
if(progress_bar.value >= 100){
|
||||
progress_bar.value = 0;
|
||||
}
|
||||
await utils.sleep(500);
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<div style="{style}">
|
||||
<ProgressBar bind:this={progress_bar} value={10} style="width:100%;height:100%;"></ProgressBar>
|
||||
</div>
|
||||
73
src/lib/components/xp/Menu.svelte
Normal file
73
src/lib/components/xp/Menu.svelte
Normal file
@@ -0,0 +1,73 @@
|
||||
<script>
|
||||
|
||||
|
||||
import { click_outside } from '../../utils'
|
||||
export let menu = [
|
||||
{
|
||||
name: 'File',
|
||||
items: [//group of items
|
||||
[{name: 'Open'}],
|
||||
[{name: 'Save'}, {name:'Save as'}],
|
||||
[{name: 'Exit'}]
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Edit',
|
||||
items: [
|
||||
[{name: 'Undo'}, {name: 'Redo'}],
|
||||
[{name: 'Cut'}, {name: 'Copy'}, {name: 'Paste', disabled: true}, {name: 'Delete'}],
|
||||
[{name: 'Find'}, {name: 'Find Next'}, {name: 'Replace'}, {name: 'Goto'}]
|
||||
]
|
||||
}
|
||||
];
|
||||
export let style = '';
|
||||
|
||||
let active = false;
|
||||
|
||||
function hide(){
|
||||
active = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="toolbar-menu flex flex-row items-center justify-evenly w-min font-MSSS z-10" style="{style}"
|
||||
use:click_outside on:click_outside={() => active = false}>
|
||||
{#each menu as menu_group}
|
||||
<div class="text-[11px] text-slate-900 hover:bg-blue-600 hover:text-slate-50 relative group">
|
||||
<div class="px-2 py-1" on:click={() => active = true}>
|
||||
{menu_group.name}
|
||||
</div>
|
||||
{#if menu_group.items != null}
|
||||
<div class="absolute w-[150px] border-slate-500 shadow hidden {active ? 'group-hover:block' : 'inactive-class'} border border-slate-200 bg-slate-50 left-0 top-[25px]">
|
||||
{#each menu_group.items as item_group, group_index}
|
||||
<div class="w-full border-slate-200 {group_index == menu_group.items.length - 1 ? '' : 'border-b'}">
|
||||
{#each item_group as item}
|
||||
<div class="py-1 w-full flex flex-row items-center {item.disabled? '' : 'hover:bg-blue-600'} relative group-sub"
|
||||
on:click={() => {
|
||||
if(!item.disabled){
|
||||
hide();
|
||||
console.log(active);
|
||||
item.action();
|
||||
}
|
||||
}}>
|
||||
<div class="w-[20px] ml-1 shrink-0">
|
||||
{#if item.icon}
|
||||
<img src="{item.icon}" width="17px" height="17px">
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grow text-slate-900 {item.font == 'bold' ? 'font-bold' : ''} {item.disabled ? 'text-slate-400' : 'group-sub-hover:text-slate-50'}">
|
||||
<p class="line-clamp-1">{item.name}</p>
|
||||
</div>
|
||||
<div class="w-[10px]">
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
91
src/lib/components/xp/OpenModal.svelte
Normal file
91
src/lib/components/xp/OpenModal.svelte
Normal file
@@ -0,0 +1,91 @@
|
||||
<script>
|
||||
import Window from './Window.svelte';
|
||||
import { hardDrive, selectingItems} from '../../store';
|
||||
import {my_pictures_id, my_music_id, desktop_folder} from '../../system';
|
||||
import * as utils from '../../utils';
|
||||
import * as finder from '../../finder'
|
||||
|
||||
import _, { find, isEqual, toLower } from 'lodash';
|
||||
import TitleBar from './TitleBar.svelte';
|
||||
import Viewer2 from './Viewer2.svelte';
|
||||
|
||||
|
||||
export let self;
|
||||
export let selected_items = [];
|
||||
|
||||
export let viewer;
|
||||
export let filetypes = [];
|
||||
export let filetypes_desc = 'All Files';
|
||||
export let multiple = true;
|
||||
|
||||
let left_side_places = [
|
||||
{
|
||||
id: desktop_folder,
|
||||
name: 'Desktop',
|
||||
icon: '/images/xp/icons/Desktop.png'
|
||||
},
|
||||
{
|
||||
id: my_pictures_id,
|
||||
name: 'My Pictures',
|
||||
icon: '/images/xp/icons/MyPictures.png'
|
||||
},
|
||||
{
|
||||
id: my_music_id,
|
||||
name: 'My Music',
|
||||
icon: '/images/xp/icons/MyMusic.png'
|
||||
},
|
||||
{
|
||||
id: null,
|
||||
name: 'My Computer',
|
||||
icon: '/images/xp/icons/MyComputer.png'
|
||||
}
|
||||
]
|
||||
export function destroy(){
|
||||
console.log(self);
|
||||
self.$destroy();
|
||||
}
|
||||
|
||||
export let on_open = () => {}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="absolute inset-0 bg-slate-50/40 rounded-t-lg z-10" on:click|self={(e) => {
|
||||
e.target.querySelector('div').classList.add('animate-blink');
|
||||
setTimeout(() => {
|
||||
e.target.querySelector('div').classList.remove('animate-blink');
|
||||
}, 400);
|
||||
}}>
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col"
|
||||
style:width="600px" style:height="500px">
|
||||
<TitleBar options={{title: 'Open', maximize_btn: false, minimize_btn: false}} on_click_close={destroy}></TitleBar>
|
||||
<div class="grow p-2 pb-1 bg-xp-yellow overflow-hidden flex flex-row shadow-lg border-t-0 border-2 border-blue-600">
|
||||
<div class="shrink-0 pt-1 pr-1 w-[100px]">
|
||||
<div class="h-7 mr-2 flex flex-row justify-end items-center">
|
||||
<span class="text-[11px] text-black">Look in:</span>
|
||||
</div>
|
||||
<div class="bg-xp-yellow-light shadow rounded w-full">
|
||||
{#each left_side_places as place}
|
||||
<div class="w-full h-[80px] flex flex-col items-center p-2 hover:bg-slate-100 rounded"
|
||||
on:click={() => viewer.open(place.id)}>
|
||||
<div class="w-8 h-8 bg-contain bg-no-repeat" style:background-image="url({place.icon})"></div>
|
||||
<span class="mt-1 text-[12px] text-black">{place.name}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grow flex flex-col relative">
|
||||
<Viewer2 bind:this={viewer} filetypes_desc={filetypes_desc} filetypes={filetypes.map(el => el.toLowerCase())} multiple={multiple}
|
||||
on_open={() => {
|
||||
selected_items = viewer.selectingItems;
|
||||
on_open();
|
||||
}}
|
||||
on_cancel={destroy}>
|
||||
</Viewer2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
41
src/lib/components/xp/Previewable.svelte
Normal file
41
src/lib/components/xp/Previewable.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<script>
|
||||
import * as fs from '../../fs';
|
||||
import {onMount} from 'svelte';
|
||||
|
||||
export let default_icon;
|
||||
export let fs_id;
|
||||
let preview_url;
|
||||
let node_ref;
|
||||
|
||||
let observer;
|
||||
onMount(async () => {
|
||||
observer = new IntersectionObserver(intersect_callback, {
|
||||
root: null,
|
||||
threshold: 1
|
||||
})
|
||||
observer.observe(node_ref);
|
||||
})
|
||||
|
||||
let intersect_callback = (entries, observer) => {
|
||||
entries.forEach((entry) => {
|
||||
let { target, isIntersecting } = entry;
|
||||
|
||||
if(isIntersecting){
|
||||
load_preview();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
async function load_preview(){
|
||||
if(preview_url != null) return;
|
||||
if(fs_id == null) return;
|
||||
let url = await fs.get_url(fs_id);
|
||||
// console.log('load', fs_id, 'with url', url);
|
||||
preview_url = `url(${url})`;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div bind:this={node_ref} class="w-[50px] h-[50px] shrink-0 bg-contain bg-no-repeat bg-center"
|
||||
style:background-image="{preview_url || default_icon}">
|
||||
</div>
|
||||
43
src/lib/components/xp/ProgramTile.svelte
Normal file
43
src/lib/components/xp/ProgramTile.svelte
Normal file
@@ -0,0 +1,43 @@
|
||||
<script>
|
||||
import { zIndex, runningPrograms, contextMenu } from '../../store';
|
||||
let node_ref;
|
||||
|
||||
export let program;
|
||||
|
||||
function on_rightclick(ev){
|
||||
contextMenu.set(null);
|
||||
console.log(program);
|
||||
console.log({maximized: program.window.maximized, minimized: program.window.minimized})
|
||||
contextMenu.set({x: ev.x, y: ev.y, type: 'ProgramTile', originator: program});
|
||||
}
|
||||
|
||||
function on_mousedown(ev){
|
||||
// program.window.focus();
|
||||
}
|
||||
|
||||
function on_click(ev){
|
||||
if(!program.window.minimized){
|
||||
if(program.window.z_index == $zIndex){
|
||||
console.log('minimize');
|
||||
program.window.on_click_minimize();
|
||||
} else {
|
||||
program.window.focus();
|
||||
}
|
||||
|
||||
} else {
|
||||
program.window.restore();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div bind:this={node_ref} on:contextmenu={on_rightclick} on:mousedown={on_mousedown} on:click={on_click}
|
||||
program-id="{program.id}"
|
||||
class="program-tile h-full w-[150px] min-w-[70px] flex flex-row items-center max-w-[200px] overflow-hidden rounded-sm hover:brightness-125"
|
||||
style:background="{program.window.z_index == $zIndex? 'rgb(30,82,183)' : 'rgb(60,129,243)'}"
|
||||
style:box-shadow="{program.window.z_index == $zIndex? 'rgb(0 0 0 / 20%) 0px 0px 1px 1px inset, rgb(0 0 0 / 70%) 1px 0px 1px inset' : 'rgb(0 0 0 / 30%) -1px 0px inset, rgb(255 255 255 / 20%) 1px 1px 1px inset'}"
|
||||
>
|
||||
<img src="{program.options.icon}" width="15px" height="15px" class="shrink-0 ml-2 pointer-events-none" alt="">
|
||||
<p class="text-[11px] text-slate-50 text-ellipsis mx-1 line-clamp-1 leading-none">{program.options.title}</p>
|
||||
</div>
|
||||
13
src/lib/components/xp/ProgressBar.svelte
Normal file
13
src/lib/components/xp/ProgressBar.svelte
Normal file
@@ -0,0 +1,13 @@
|
||||
<script>
|
||||
export let value = 0;
|
||||
export let total = 100;
|
||||
export let style = '';
|
||||
</script>
|
||||
|
||||
<div class="bg-slate-100 border border-slate-500 rounded-sm w-10 h-1 p-0.5 overflow-hidden" style="{style}">
|
||||
<div style:width="{100*value/total}%"
|
||||
class="h-full bg-[url(/images/xp/battery_cell.png)] bg-contain bg-repeat-x">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
31
src/lib/components/xp/RButton.svelte
Normal file
31
src/lib/components/xp/RButton.svelte
Normal file
@@ -0,0 +1,31 @@
|
||||
<script>
|
||||
import {tooltip} from './tooltip';
|
||||
export let title = '';
|
||||
export let icon = '';
|
||||
export let style = '';
|
||||
export let tooltip_message = '';
|
||||
export let disabled = false;
|
||||
export let expandable = false;
|
||||
|
||||
export let on_click = () => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="p-2 h-min flex flex-row bg-xp-yellow rounded items-center {!disabled ? 'hover:brightness-105 hover:shadow' : ''}"
|
||||
use:tooltip tooltip="{tooltip_message}"
|
||||
on:click={() => {
|
||||
if(!disabled){
|
||||
on_click();
|
||||
}
|
||||
}}>
|
||||
<div class="h-[25px] w-[25px] bg-contain bg-no-repeat {disabled? 'grayscale' : ''}" style:background-image="url({icon})">
|
||||
</div>
|
||||
{#if title.trim() != ''}
|
||||
<div class="text-[12px] text-slate-900 ml-2">{title}</div>
|
||||
{/if}
|
||||
{#if expandable}
|
||||
<div class="w-[10px] ml-1">
|
||||
<svg class="{disabled ? 'fill-gray-400' : 'fill-slate-700'} group-hover:fill-slate-50 w-[10px] h-[10px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z"/></svg>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
20
src/lib/components/xp/RadioBtn.svelte
Normal file
20
src/lib/components/xp/RadioBtn.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script>
|
||||
export let checked = false;
|
||||
export let label = '';
|
||||
export let size = 15;
|
||||
export let in_progress = false;
|
||||
</script>
|
||||
<div class="flex flex-row">
|
||||
<div class="group bg-[linear-gradient(135deg,#dcdcd7,#fff)] shrink-0 rounded-full border border-[#1d5281] relative"
|
||||
style:width='{size}px' style:height='{size}px'>
|
||||
<div class="{in_progress ? 'bg-gradient-to-r' : ''} group-hover:bg-gradient-to-r from-orange-300 to-orange-200 absolute inset-0 rounded-full p-[2px]">
|
||||
<div class="w-full h-full bg-[linear-gradient(135deg,#dcdcd7,#fff)] opacity-70 rounded-full"></div>
|
||||
</div>
|
||||
{#if checked}
|
||||
<div class="bg-[url(/images/xp/radio_check.png)] bg-cover absolute inset-1/4"></div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="ml-2 leading-none {in_progress ? 'text-orange-400' : ''}">{label}</div>
|
||||
</div>
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
33
src/lib/components/xp/RecycleBin.svelte
Normal file
33
src/lib/components/xp/RecycleBin.svelte
Normal file
@@ -0,0 +1,33 @@
|
||||
<script>
|
||||
import { hardDrive, queueProgram, contextMenu } from '../../store';
|
||||
import { recycle_bin_id} from '../../system';
|
||||
export let style;
|
||||
export let classes;
|
||||
|
||||
$: icon = $hardDrive[recycle_bin_id]?.files.length > 0 || $hardDrive[recycle_bin_id]?.folders.length > 0 ?
|
||||
'url(/images/xp/icons/RecycleBinfull.png)' : 'url(/images/xp/icons/RecycleBinempty.png)';
|
||||
|
||||
function on_dbclick(){
|
||||
let fs_item = $hardDrive[recycle_bin_id];
|
||||
queueProgram.set({
|
||||
path: './programs/my_computer.svelte',
|
||||
fs_item: fs_item
|
||||
})
|
||||
}
|
||||
|
||||
function on_rightclick(ev){
|
||||
contextMenu.set(null);
|
||||
contextMenu.set({x: ev.x, y: ev.y, type: 'RecycleBin', originator: null});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col items-center absolute bottom-2 right-2 {classes}"
|
||||
style="{style}"
|
||||
on:dblclick={on_dbclick}
|
||||
on:contextmenu={on_rightclick}
|
||||
>
|
||||
<div class="w-[40px] h-[40px] bg-contain" style:background-image="{icon}"></div>
|
||||
<p class="text-center text-[11px] font-MSSS text-white" style="text-shadow: 1px 1px 2px black;">Recycle Bin</p>
|
||||
|
||||
</div>
|
||||
97
src/lib/components/xp/SaveModal.svelte
Normal file
97
src/lib/components/xp/SaveModal.svelte
Normal file
@@ -0,0 +1,97 @@
|
||||
<script>
|
||||
import Window from './Window.svelte';
|
||||
import { hardDrive, selectingItems} from '../../store';
|
||||
import {my_pictures_id, my_music_id, desktop_folder} from '../../system';
|
||||
import * as utils from '../../utils';
|
||||
import * as finder from '../../finder'
|
||||
|
||||
import _, { find, isEqual } from 'lodash';
|
||||
import TitleBar from './TitleBar.svelte';
|
||||
import Viewer3 from './Viewer3.svelte';
|
||||
|
||||
|
||||
export let self;
|
||||
export let id;
|
||||
|
||||
export let viewer;
|
||||
export let filetypes = [];
|
||||
export let selected_filetype;
|
||||
export let filename;
|
||||
export let parent_id;
|
||||
|
||||
let left_side_places = [
|
||||
{
|
||||
id: desktop_folder,
|
||||
name: 'Desktop',
|
||||
icon: '/images/xp/icons/Desktop.png'
|
||||
},
|
||||
{
|
||||
id: my_pictures_id,
|
||||
name: 'My Pictures',
|
||||
icon: '/images/xp/icons/MyPictures.png'
|
||||
},
|
||||
{
|
||||
id: my_music_id,
|
||||
name: 'My Music',
|
||||
icon: '/images/xp/icons/MyMusic.png'
|
||||
},
|
||||
{
|
||||
id: null,
|
||||
name: 'My Computer',
|
||||
icon: '/images/xp/icons/MyComputer.png'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
export function destroy(){
|
||||
console.log(self);
|
||||
self.$destroy();
|
||||
}
|
||||
|
||||
export let on_save = () => {}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="absolute inset-0 bg-slate-50/40 rounded-t-lg z-10" on:click|self={(e) => {
|
||||
e.target.querySelector('div').classList.add('animate-blink');
|
||||
setTimeout(() => {
|
||||
e.target.querySelector('div').classList.remove('animate-blink');
|
||||
}, 400);
|
||||
}}>
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col"
|
||||
style:width="600px" style:height="500px">
|
||||
<TitleBar options={{title: 'Save', maximize_btn: false, minimize_btn: false}} on_click_close={destroy}></TitleBar>
|
||||
<div class="absolute inset-0 top-[28px] bg-xp-yellow shadow-lg border-t-0 border-2 border-blue-600">
|
||||
<div class="absolute top-1 left-1 bottom-0 w-[100px]">
|
||||
<div class="h-7 mr-2 flex flex-row justify-end items-center">
|
||||
<span class="text-[11px] text-black">Look in:</span>
|
||||
</div>
|
||||
<div class="bg-xp-yellow-light shadow rounded w-full">
|
||||
{#each left_side_places as place}
|
||||
<div class="w-full h-[80px] flex flex-col items-center p-2 hover:bg-slate-100 rounded"
|
||||
on:click={() => viewer.open(place.id)}>
|
||||
<div class="w-8 h-8 bg-contain bg-no-repeat" style:background-image="url({place.icon})"></div>
|
||||
<span class="mt-1 text-[12px] text-black">{place.name}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute top-1 left-[110px] right-1 bottom-1">
|
||||
<Viewer3 bind:this={viewer} id={id} filetypes={filetypes} selected_filetype={selected_filetype}
|
||||
on_save={() => {
|
||||
parent_id = viewer.id;
|
||||
filename = viewer.filename;
|
||||
selected_filetype = viewer.select_box.items[viewer.select_box.selected_index];
|
||||
console.log(selected_filetype);
|
||||
on_save();
|
||||
}}
|
||||
on_cancel={destroy}>
|
||||
</Viewer3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
73
src/lib/components/xp/SelectBox.svelte
Normal file
73
src/lib/components/xp/SelectBox.svelte
Normal file
@@ -0,0 +1,73 @@
|
||||
<script>
|
||||
|
||||
import * as utils from '../../utils';
|
||||
const {click_outside} = utils;
|
||||
import {onMount} from 'svelte';
|
||||
|
||||
export let items = []
|
||||
export let selected_index = 0;
|
||||
export let style = '';
|
||||
export let direction;
|
||||
|
||||
let item_height = 24;
|
||||
let expand = false;
|
||||
let node_ref;
|
||||
let dropbox_pos = '';
|
||||
|
||||
function on_click_expand(){
|
||||
cal_dropbox_pos();
|
||||
expand = true;
|
||||
console.log({expand});
|
||||
}
|
||||
|
||||
function cal_dropbox_pos(){
|
||||
if(direction == 'bottom'){
|
||||
dropbox_pos = 'top:100%;'
|
||||
} else if(direction == 'top'){
|
||||
dropbox_pos = 'bottom:100%;'
|
||||
} else {
|
||||
if(document.body.offsetHeight - node_ref.getBoundingClientRect().y > 150){
|
||||
dropbox_pos = 'top:100%;'
|
||||
} else {
|
||||
dropbox_pos = 'bottom:100%;'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div bind:this={node_ref} class="bg-slate-50 h-6 text-slate-800 border border-blue-300 p-1 text-[11px] absolute" style="{style}"
|
||||
use:click_outside on:click_outside={()=> expand = false}>
|
||||
|
||||
<div class="absolute bg-slate-50 w-full left-0 max-h-[100px] overflow-y-auto
|
||||
box-content border border-slate-300 {expand ? 'block' : 'hidden'}"
|
||||
style:height="{expand ? Math.min(items.length*item_height, 100) : 0}px" style="{dropbox_pos}">
|
||||
{#each items as item, index}
|
||||
<div class="box-border w-full flex flex-row p-0.5 pl-2 items-center hover:bg-blue-600 hover:text-slate-50
|
||||
{index != 0 ? 'border-t' : ''} border-slate-300"
|
||||
style:height="{item_height}px"
|
||||
on:click={() => {
|
||||
selected_index = index;
|
||||
expand = false;
|
||||
}}>
|
||||
{item.name}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="absolute inset-0 flex flex-row items-center pl-2"
|
||||
on:click={on_click_expand}>
|
||||
{items[selected_index]?.name}
|
||||
</div>
|
||||
{#if items.length > 1}
|
||||
<div class="absolute right-[1px] top-[1px] bottom-[1px] p-1 rounded bg-blue-200 border-2 border-slate-50 box-border"
|
||||
style:width="{item_height-2}px" on:click={on_click_expand}>
|
||||
<svg class="w-full h-full fill-blue-700" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
46
src/lib/components/xp/Tab.svelte
Normal file
46
src/lib/components/xp/Tab.svelte
Normal file
@@ -0,0 +1,46 @@
|
||||
<script>
|
||||
export let items = [];
|
||||
export let selected;
|
||||
export let size = 'sm';
|
||||
export let style = 'margin-left:2px;';
|
||||
</script>
|
||||
|
||||
|
||||
<div class="w-min shrink-0 flex flex-row items-center justify-evenly" style="{style}">
|
||||
{#each items as item}
|
||||
<div on:click={() => selected = item}
|
||||
class="grow text-slate-800 tab-item {selected == item ? 'selected' : ''}
|
||||
{size == 'sm' ? 'text-[11px]' : ''} {size == 'md' ? 'text-base' : ''} {size == 'lg' ? 'text-lg' : ''}">
|
||||
<span>{item}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tab-item {
|
||||
background: linear-gradient(180deg,#fff,#fafaf9 26%,#f0f0ea 95%,#ecebe5);
|
||||
margin-left: -1px;
|
||||
margin-right: 2px;
|
||||
border-radius: 0;
|
||||
border-color: #91a7b4;
|
||||
border-top-right-radius: 3px;
|
||||
border-top-left-radius: 3px;
|
||||
padding: 0 12px 3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
border-top: 1px solid #e68b2c;
|
||||
box-shadow: inset 0 2px #ffc73c;
|
||||
}
|
||||
|
||||
.tab-item.selected {
|
||||
background: #fcfcfe;
|
||||
border-color: #919b9c;
|
||||
margin-right: 0px;
|
||||
border-bottom: 1px solid transparent;
|
||||
border-top: 1px solid #e68b2c;
|
||||
box-shadow: inset 0 2px #ffc73c;
|
||||
}
|
||||
|
||||
</style>
|
||||
60
src/lib/components/xp/TitleBar.svelte
Normal file
60
src/lib/components/xp/TitleBar.svelte
Normal file
@@ -0,0 +1,60 @@
|
||||
<script>
|
||||
|
||||
export let options = {};
|
||||
|
||||
let {close_btn=true, maximize_btn=true, minimize_btn=true,
|
||||
close_btn_disabled=false, maximize_btn_disabled=false, minimize_btn_disabled=false} = options;
|
||||
|
||||
|
||||
export let inactive = false;
|
||||
export let maximized = false;
|
||||
|
||||
export let on_click_maximize = () => {}
|
||||
export let on_click_minimize = () => {}
|
||||
export let on_click_close = () => {}
|
||||
|
||||
export function update_icon(icon){
|
||||
options.icon = icon;
|
||||
}
|
||||
|
||||
export function update_title(title){
|
||||
options.title = title;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="titlebar shrink-0 flex rounded-tl-lg rounded-tr-lg items-center justify-between h-7 p-1 font-Trebuchet
|
||||
{inactive ? 'bg-[linear-gradient(var(--titlebar-gradient-inactive))]' : 'bg-[linear-gradient(var(--titlebar-gradient))]'}">
|
||||
{#if options.icon}
|
||||
<img src="{options.icon}" width="20px" height="20px" class="ml-1" alt="">
|
||||
{/if}
|
||||
<p class="text-white font-semibold mr-4 text-[12px] grow ml-1 leading-tight line-clamp-1 text-ellipsis">{options.title}</p>
|
||||
<div class="flex mr-0.5 shrink-0">
|
||||
{#if minimize_btn}
|
||||
<button disabled={minimize_btn_disabled} on:click={on_click_minimize}
|
||||
class="group w-5 h-5 ml-1 group">
|
||||
<img src="/images/xp/icons/Minimize.png" class="w-full h-full {minimize_btn_disabled ? 'contrast-75' : 'group-hover:brightness-110'}" >
|
||||
</button>
|
||||
{/if}
|
||||
{#if maximize_btn}
|
||||
<button disabled={maximize_btn_disabled} on:click={on_click_maximize}
|
||||
class="group w-5 h-5 ml-1 group" >
|
||||
{#if maximized}
|
||||
<img src="/images/xp/icons/Restore.png" class="w-full h-full {maximize_btn_disabled ? 'contrast-75' : 'group-hover:brightness-110'}" >
|
||||
{:else}
|
||||
<img src="/images/xp/icons/Maximize.png" class="w-full h-full {maximize_btn_disabled ? 'contrast-75' : 'group-hover:brightness-110'}" >
|
||||
{/if}
|
||||
|
||||
|
||||
</button>
|
||||
{/if}
|
||||
{#if close_btn}
|
||||
<button disabled={close_btn_disabled} on:click={on_click_close}
|
||||
class="group w-5 h-5 ml-1 group">
|
||||
<img src="/images/xp/icons/Exit.png" class="w-full h-full {close_btn_disabled ? 'contrast-75' : 'group-hover:brightness-110'}" >
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
29
src/lib/components/xp/TrayIcon.svelte
Normal file
29
src/lib/components/xp/TrayIcon.svelte
Normal file
@@ -0,0 +1,29 @@
|
||||
<script>
|
||||
import {tooltip} from './tooltip';
|
||||
export let style = '';
|
||||
export let tooltip_message;
|
||||
export let icon;
|
||||
|
||||
export let on_click = () => {
|
||||
console.log('on click')
|
||||
}
|
||||
|
||||
function on_mouseenter(e){
|
||||
if(tooltip_message == null) return;
|
||||
let position = {left: e.x, top: e.y - 40};
|
||||
if(tooltip_message.length >= 20){
|
||||
position.top = position.top - 20;
|
||||
}
|
||||
tooltip.set({message: tooltip_message, position});
|
||||
}
|
||||
|
||||
function on_mouseleave(e){
|
||||
tooltip.set(null);
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="mr-1 w-4 h-4 bg-no-repeat bg-contain" style:background-image="url({icon})"
|
||||
on:click={on_click} use:tooltip tooltip="{tooltip_message}">
|
||||
</div>
|
||||
250
src/lib/components/xp/Viewer2.svelte
Normal file
250
src/lib/components/xp/Viewer2.svelte
Normal file
@@ -0,0 +1,250 @@
|
||||
<script>
|
||||
import { contextMenu, hardDrive } from '../../store'
|
||||
import {my_computer} from '../../../lib/system';
|
||||
|
||||
import * as finder from '../../finder';
|
||||
import * as utils from '../../utils';
|
||||
import { doctypes, icons, hidden_items } from '../../system';
|
||||
import * as fs from '../../fs';
|
||||
const {click_outside} = utils;
|
||||
import { createEventDispatcher, onMount, tick } from 'svelte';
|
||||
import short from 'short-uuid';
|
||||
import {get, set} from 'idb-keyval';
|
||||
import { filter, map } from 'lodash';
|
||||
import Button from './Button.svelte';
|
||||
let dispatch = createEventDispatcher();
|
||||
|
||||
|
||||
let history = [null];
|
||||
let page_index = 0;
|
||||
$: url = finder.to_url(history[page_index]) || 'My Computer';
|
||||
export let id;
|
||||
|
||||
$: folders = $hardDrive[id] == null ? [] : $hardDrive[id].folders.map(id => $hardDrive[id]);
|
||||
$: files = $hardDrive[id] == null ? [] : $hardDrive[id].files.map(id => $hardDrive[id]);
|
||||
|
||||
$: items = [...files, ...folders]
|
||||
.filter(el => el != null)
|
||||
.filter(el => !hidden_items.includes(el.id));
|
||||
|
||||
let computer = my_computer.map(el => $hardDrive[el]);
|
||||
|
||||
export let selectingItems = [];
|
||||
export let filetypes_desc;
|
||||
export let filetypes = [];
|
||||
export let multiple = true;
|
||||
|
||||
function on_click(ev, item){
|
||||
if(!is_desired(item)) return;
|
||||
if(item.type != 'file') return;
|
||||
|
||||
let selected = selectingItems.includes(item.id);
|
||||
if((ev.ctrlKey || ev.metaKey) && multiple){
|
||||
console.log('ctrl key pressed');
|
||||
if(selected){
|
||||
selectingItems = selectingItems.filter(el => el != item.id);
|
||||
} else {
|
||||
selectingItems = [...selectingItems, item.id];
|
||||
}
|
||||
|
||||
} else {
|
||||
selectingItems = selected ? [] : [item.id];
|
||||
}
|
||||
}
|
||||
|
||||
function clear_selection(){
|
||||
console.log(selectingItems);
|
||||
console.log('clear_selection');
|
||||
selectingItems = [];
|
||||
|
||||
}
|
||||
|
||||
function is_desired(item){
|
||||
if(item.type != 'file') return true;
|
||||
if(filetypes.length == 0) return true;
|
||||
return filetypes.includes(item.ext);
|
||||
}
|
||||
|
||||
|
||||
export function open(item_id){
|
||||
clear_selection();
|
||||
let fs_item = $hardDrive[item_id];
|
||||
if(fs_item?.type == 'file'){
|
||||
selectingItems = [item_id];
|
||||
on_open();
|
||||
} else {
|
||||
history = [...history.slice(0, page_index+1), item_id];
|
||||
page_index = history.length - 1;
|
||||
id = history[page_index];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function back(){
|
||||
page_index = Math.max(0, page_index - 1);
|
||||
id = history[page_index];
|
||||
}
|
||||
|
||||
function next(){
|
||||
page_index = Math.min(history.length - 1, page_index + 1);
|
||||
id = history[page_index];
|
||||
}
|
||||
|
||||
function up(){
|
||||
let parent_id = $hardDrive[history[page_index]].parent;
|
||||
open(parent_id);
|
||||
}
|
||||
|
||||
function file_icon(item){
|
||||
if(item == null) return null;
|
||||
if(item.icon != null){
|
||||
return `url(${item.icon})`
|
||||
}
|
||||
if(icons[item.ext] != null){
|
||||
return `url(/images/xp/icons/${icons[item.ext]})`
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function on_user_input(e){
|
||||
if(e.key == 'Enter'){
|
||||
let id = finder.to_id(e.target.value);
|
||||
|
||||
if(id == null){
|
||||
id = finder.to_id_nocase(e.target.value);
|
||||
}
|
||||
console.log('found id', id);
|
||||
if(id){
|
||||
open(id);
|
||||
e.target.blur();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export let on_open = () => {}
|
||||
export let on_cancel = () => {}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div class="absolute inset-0 overflow-auto bg-xp-yellow flex flex-col"
|
||||
use:click_outside on:click_outside={() => clear_selection()}>
|
||||
<div class="h-6 mb-2 flex flex-row items-center text-[11px]">
|
||||
<div class="h-full w-[300px] relative">
|
||||
<input class="absolute inset-0 w-[300px] pl-7 border border-blue-300 outline-none" type="text"
|
||||
on:click={(e) => e.target.select()} on:keyup={on_user_input} value="{url}">
|
||||
<div class="w-[17px] h-[17px] absolute top-[4px] left-[4px] bg-no-repeat
|
||||
{id == null ? 'bg-[url(/images/xp/icons/MyComputer.png)]' : 'bg-[url(/images/xp/icons/FolderClosed.png)]'} bg-contain"
|
||||
style:background-image="{file_icon($hardDrive[id])}">
|
||||
</div>
|
||||
</div>
|
||||
<button class="mx-1.5 ml-4 w-4 h-4 bg-[url(/images/xp/icons/Back.png)] bg-contain disabled:grayscale"
|
||||
disabled={page_index == 0}
|
||||
on:click={back}>
|
||||
</button>
|
||||
|
||||
<button class="mx-1.5 w-4 h-4 bg-[url(/images/xp/icons/Forward.png)] bg-contain disabled:grayscale"
|
||||
disabled={page_index == history.length-1}
|
||||
on:click={next}>
|
||||
</button>
|
||||
|
||||
<button class="mx-1.5 w-5 h-5 bg-[url(/images/xp/icons/Up.png)] bg-contain disabled:grayscale"
|
||||
disabled={history[page_index] == null}
|
||||
on:click={up}>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="w-full bg-slate-50 grow border overflow-auto border-blue-300" class:hidden={id == null}
|
||||
on:click|self={clear_selection}>
|
||||
{#each items as item}
|
||||
<div fs-id="{item.id}" class="w-[100px] overflow-hidden m-2 inline-flex flex-row items-center font-MSSS relative
|
||||
{is_desired(item) ? '' : 'opacity-50'}"
|
||||
on:dblclick={() => open(item.id)}
|
||||
on:click={(e) => on_click(e, item)}>
|
||||
<div class="w-[30px] h-[30px] shrink-0 bg-contain bg-no-repeat
|
||||
{item.type == 'folder' ? 'bg-[url(/images/xp/icons/FolderClosed.png)]' : 'bg-[url(/images/xp/icons/Default.png)]'} "
|
||||
style:background-image="{file_icon(item)}">
|
||||
</div>
|
||||
<p class="px-1 text-[11px] break-words line-clamp-2 text-ellipsis leading-tight
|
||||
{selectingItems?.includes(item.id) ? 'bg-blue-600 text-slate-50' : ''}">
|
||||
{item.name}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="w-full bg-slate-50 grow border overflow-auto border-blue-300" class:hidden={id != null}>
|
||||
|
||||
<p class="ml-2 mt-0.5 font-MSSS text-black text-[11px] font-bold">Files Stored on This Computer</p>
|
||||
<div class="mb-4 w-[300px] h-[2px] bg-gradient-to-r from-blue-500 to-slate-50"></div>
|
||||
{#each computer.filter(el => el.type == 'folder') as item}
|
||||
<div class="w-[150px] ml-4 mr-8 overflow-hidden inline-flex flex-row items-center font-MSSS"
|
||||
on:dblclick={() => open(item.id)}>
|
||||
<div class="w-[40px] h-[40px] shrink-0 bg-[url(/images/xp/icons/FolderClosed.png)] bg-contain"
|
||||
style:background-image="{item.icon == null ? '' : `url(${item.icon})`}">
|
||||
</div>
|
||||
<div class="px-1 text-[11px] line-clamp-2 text-ellipsis leading-tight">
|
||||
{item.display_name != null ? item.display_name : item.name}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<p class="ml-2 mt-4 font-MSSS text-black text-[11px] font-bold">Hard Disk Drives</p>
|
||||
<div class="mb-4 w-[300px] h-[2px] bg-gradient-to-r from-blue-500 to-slate-50"></div>
|
||||
{#each computer.filter(el => el.type == 'drive') as item}
|
||||
<div class="w-[150px] ml-4 mr-8 overflow-hidden inline-flex flex-row items-center font-MSSS"
|
||||
on:dblclick={() => open(item.id)}>
|
||||
<div class="w-[50px] h-[50px] shrink-0 bg-[url(/images/xp/icons/LocalDisk.png)] bg-contain">
|
||||
</div>
|
||||
<div class="px-1 text-[11px] line-clamp-2 text-ellipsis leading-tight">
|
||||
{item.display_name != null ? item.display_name : item.name}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<p class="ml-2 mt-4 font-MSSS text-black text-[11px] font-bold">Devices with Removable Storage</p>
|
||||
<div class="mb-4 w-[300px] h-[2px] bg-gradient-to-r from-blue-500 to-slate-50"></div>
|
||||
{#each computer.filter(el => el.type == 'removable_storage') as item}
|
||||
<div class="w-[150px] ml-4 mr-8 overflow-hidden inline-flex flex-row items-center font-MSSS"
|
||||
on:dblclick={() => open(item.id)}>
|
||||
<div class="w-[50px] h-[50px] shrink-0 bg-[url(/images/xp/icons/RemovableMedia.png)] bg-contain">
|
||||
</div>
|
||||
<div class="px-1 text-[11px] line-clamp-2 text-ellipsis leading-tight">
|
||||
{item.display_name != null ? item.display_name : item.name}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="shrink-0 w-full h-[70px] text-[11px] text-black">
|
||||
<div class="w-full flex flex-row items-center my-2">
|
||||
<div class="w-[100px] shrink-0">File name:</div>
|
||||
<div class="grow">
|
||||
<input disabled type="text"
|
||||
value="{selectingItems.map(el => $hardDrive[el].name).join(', ')}"
|
||||
class="w-full h-6 text-[11px] outline-none border border-blue-300 disabled:bg-slate-50">
|
||||
</div>
|
||||
<div class="w-[100px] shrink-0 flex flex-row justify-end ">
|
||||
<Button title="Open" on_click={on_open} disabled={selectingItems.length == 0}></Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex flex-row items-center my-2">
|
||||
<div class="w-[100px] shrink-0">Files of type:</div>
|
||||
<div class="grow">
|
||||
<input disabled type="text"
|
||||
value="{filetypes_desc} ({filetypes.join(', ')})"
|
||||
class="w-full h-6 p-0.5 text-[11px] outline-none border border-blue-300 disabled:bg-slate-50">
|
||||
</div>
|
||||
<div class="w-[100px] shrink-0 flex flex-row justify-end ">
|
||||
<Button title="Cancel" on_click={on_cancel}></Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
223
src/lib/components/xp/Viewer3.svelte
Normal file
223
src/lib/components/xp/Viewer3.svelte
Normal file
@@ -0,0 +1,223 @@
|
||||
<script>
|
||||
import { contextMenu, hardDrive } from '../../store'
|
||||
import {my_computer} from '../../../lib/system';
|
||||
|
||||
import * as finder from '../../finder';
|
||||
import * as utils from '../../utils';
|
||||
import { doctypes, icons, hidden_items } from '../../system';
|
||||
import * as fs from '../../fs';
|
||||
const {click_outside} = utils;
|
||||
import { createEventDispatcher, onMount, tick } from 'svelte';
|
||||
import short from 'short-uuid';
|
||||
import {get, set} from 'idb-keyval';
|
||||
import { filter, indexOf, map } from 'lodash';
|
||||
import Button from './Button.svelte';
|
||||
import SelectBox from './SelectBox.svelte';
|
||||
let dispatch = createEventDispatcher();
|
||||
|
||||
|
||||
export let id;
|
||||
let history = [id];
|
||||
let page_index = 0;
|
||||
$: url = finder.to_url(history[page_index]) || 'My Computer';
|
||||
|
||||
|
||||
$: folders = $hardDrive[id] == null ? [] : $hardDrive[id].folders.map(id => $hardDrive[id]);
|
||||
$: files = $hardDrive[id] == null ? [] : $hardDrive[id].files.map(id => $hardDrive[id]);
|
||||
|
||||
$: items = [...files, ...folders]
|
||||
.filter(el => el != null)
|
||||
.filter(el => !hidden_items.includes(el.id));
|
||||
|
||||
let computer = my_computer.map(el => $hardDrive[el]);
|
||||
|
||||
export let selected_filetype;
|
||||
export let filetypes = [];
|
||||
export let filename = '';
|
||||
export let select_box;
|
||||
|
||||
function is_desired(item){
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
export function open(item_id){
|
||||
let fs_item = $hardDrive[item_id];
|
||||
if(fs_item?.type == 'file'){
|
||||
|
||||
} else {
|
||||
history = [...history.slice(0, page_index+1), item_id];
|
||||
page_index = history.length - 1;
|
||||
id = history[page_index];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function back(){
|
||||
page_index = Math.max(0, page_index - 1);
|
||||
id = history[page_index];
|
||||
}
|
||||
|
||||
function next(){
|
||||
page_index = Math.min(history.length - 1, page_index + 1);
|
||||
id = history[page_index];
|
||||
}
|
||||
|
||||
function up(){
|
||||
let parent_id = $hardDrive[history[page_index]].parent;
|
||||
open(parent_id);
|
||||
}
|
||||
|
||||
function file_icon(item){
|
||||
if(item == null) return null;
|
||||
if(item.icon != null){
|
||||
return `url(${item.icon})`
|
||||
}
|
||||
if(icons[item.ext] != null){
|
||||
return `url(/images/xp/icons/${icons[item.ext]})`
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function on_user_input(e){
|
||||
if(e.key == 'Enter'){
|
||||
let id = finder.to_id(e.target.value);
|
||||
|
||||
if(id == null){
|
||||
id = finder.to_id_nocase(e.target.value);
|
||||
}
|
||||
console.log('found id', id);
|
||||
if(id){
|
||||
open(id);
|
||||
e.target.blur();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export let on_save = () => {}
|
||||
export let on_cancel = () => {}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div class="absolute inset-0 bg-xp-yellow">
|
||||
<div class="absolute inset-1 top-0.5 h-6 mb-2 flex flex-row items-center text-[11px]">
|
||||
<div class="h-full w-[300px] relative">
|
||||
<input class="absolute inset-0 w-[300px] pl-7 border border-blue-300 outline-none" type="text"
|
||||
on:click={(e) => e.target.select()} on:keyup={on_user_input} value="{url}">
|
||||
<div class="w-[17px] h-[17px] absolute top-[4px] left-[4px] bg-no-repeat
|
||||
{id == null ? 'bg-[url(/images/xp/icons/MyComputer.png)]' : 'bg-[url(/images/xp/icons/FolderClosed.png)]'} bg-contain"
|
||||
style:background-image="{file_icon($hardDrive[id])}">
|
||||
</div>
|
||||
</div>
|
||||
<button class="mx-1.5 ml-4 w-4 h-4 bg-[url(/images/xp/icons/Back.png)] bg-contain disabled:grayscale"
|
||||
disabled={page_index == 0}
|
||||
on:click={back}>
|
||||
</button>
|
||||
|
||||
<button class="mx-1.5 w-4 h-4 bg-[url(/images/xp/icons/Forward.png)] bg-contain disabled:grayscale"
|
||||
disabled={page_index == history.length-1}
|
||||
on:click={next}>
|
||||
</button>
|
||||
|
||||
<button class="mx-1.5 w-5 h-5 bg-[url(/images/xp/icons/Up.png)] bg-contain disabled:grayscale"
|
||||
disabled={history[page_index] == null}
|
||||
on:click={up}>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="absolute top-7 left-1 right-1 h-[360px] overflow-auto bg-slate-50 border border-blue-300" class:hidden={id == null}>
|
||||
{#each items as item}
|
||||
<div fs-id="{item.id}" class="w-[100px] overflow-hidden m-2 inline-flex flex-row items-center font-MSSS relative
|
||||
{is_desired(item) ? '' : 'opacity-50'}"
|
||||
on:dblclick={() => open(item.id)}>
|
||||
<div class="w-[30px] h-[30px] shrink-0 bg-contain bg-no-repeat
|
||||
{item.type == 'folder' ? 'bg-[url(/images/xp/icons/FolderClosed.png)]' : 'bg-[url(/images/xp/icons/Default.png)]'} "
|
||||
style:background-image="{file_icon(item)}">
|
||||
</div>
|
||||
<p class="px-1 text-[11px] break-words line-clamp-2 text-ellipsis leading-tight">
|
||||
{item.name}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="absolute top-7 left-1 right-1 h-[360px] overflow-auto bg-slate-50 border border-blue-300" class:hidden={id != null}>
|
||||
|
||||
<p class="ml-2 mt-0.5 font-MSSS text-black text-[11px] font-bold">Files Stored on This Computer</p>
|
||||
<div class="mb-4 w-[300px] h-[2px] bg-gradient-to-r from-blue-500 to-slate-50"></div>
|
||||
{#each computer.filter(el => el.type == 'folder') as item}
|
||||
<div class="w-[150px] ml-4 mr-8 overflow-hidden inline-flex flex-row items-center font-MSSS"
|
||||
on:dblclick={() => open(item.id)}>
|
||||
<div class="w-[40px] h-[40px] shrink-0 bg-[url(/images/xp/icons/FolderClosed.png)] bg-contain"
|
||||
style:background-image="{item.icon == null ? '' : `url(${item.icon})`}">
|
||||
</div>
|
||||
<div class="px-1 text-[11px] line-clamp-2 text-ellipsis leading-tight">
|
||||
{item.display_name != null ? item.display_name : item.name}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<p class="ml-2 mt-4 font-MSSS text-black text-[11px] font-bold">Hard Disk Drives</p>
|
||||
<div class="mb-4 w-[300px] h-[2px] bg-gradient-to-r from-blue-500 to-slate-50"></div>
|
||||
{#each computer.filter(el => el.type == 'drive') as item}
|
||||
<div class="w-[150px] ml-4 mr-8 overflow-hidden inline-flex flex-row items-center font-MSSS"
|
||||
on:dblclick={() => open(item.id)}>
|
||||
<div class="w-[50px] h-[50px] shrink-0 bg-[url(/images/xp/icons/LocalDisk.png)] bg-contain">
|
||||
</div>
|
||||
<div class="px-1 text-[11px] line-clamp-2 text-ellipsis leading-tight">
|
||||
{item.display_name != null ? item.display_name : item.name}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<p class="ml-2 mt-4 font-MSSS text-black text-[11px] font-bold">Devices with Removable Storage</p>
|
||||
<div class="mb-4 w-[300px] h-[2px] bg-gradient-to-r from-blue-500 to-slate-50"></div>
|
||||
{#each computer.filter(el => el.type == 'removable_storage') as item}
|
||||
<div class="w-[150px] ml-4 mr-8 overflow-hidden inline-flex flex-row items-center font-MSSS"
|
||||
on:dblclick={() => open(item.id)}>
|
||||
<div class="w-[50px] h-[50px] shrink-0 bg-[url(/images/xp/icons/RemovableMedia.png)] bg-contain">
|
||||
</div>
|
||||
<div class="px-1 text-[11px] line-clamp-2 text-ellipsis leading-tight">
|
||||
{item.display_name != null ? item.display_name : item.name}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-1 right-1 left-1 h-[70px] text-[11px] text-black">
|
||||
<div class="absolute top-0 right-0 left-0 h-[35px] flex flex-row items-center">
|
||||
<div class="w-[100px] shrink-0">File name:</div>
|
||||
<div class="grow">
|
||||
<input type="text" bind:value={filename}
|
||||
on:keyup={(e) => {
|
||||
if(e.key == 'Enter' && id != null && filename.length > 0){
|
||||
on_save();
|
||||
}
|
||||
}}
|
||||
class="w-full h-6 text-[11px] outline-none border border-blue-300 disabled:bg-slate-50">
|
||||
</div>
|
||||
<div class="w-[100px] shrink-0 flex flex-row justify-end ">
|
||||
<Button title="Save" on_click={on_save} disabled={filename.length == 0 || id == null}></Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute bottom-0 right-0 left-0 h-[35px] flex flex-row items-center justify-between">
|
||||
<div class="w-[100px] shrink-0">Save as type:</div>
|
||||
<SelectBox
|
||||
style="left:100px;right:100px;bottom:5px;"
|
||||
bind:this={select_box} items={filetypes}
|
||||
selected_index={filetypes.indexOf(selected_filetype) >= 0 ? filetypes.indexOf(selected_filetype) : 0}>
|
||||
</SelectBox>
|
||||
<div class="w-[100px] shrink-0 flex flex-row justify-end ">
|
||||
<Button title="Cancel" on_click={on_cancel}></Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
258
src/lib/components/xp/Window.svelte
Normal file
258
src/lib/components/xp/Window.svelte
Normal file
@@ -0,0 +1,258 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte';
|
||||
import TitleBar from './TitleBar.svelte';
|
||||
import * as utils from '../../utils';
|
||||
const {click_outside} = utils;
|
||||
import _ from 'lodash';
|
||||
import {get, set} from 'idb-keyval';
|
||||
import { zIndex, runningPrograms } from '../../store';
|
||||
|
||||
export let options = {};
|
||||
|
||||
let titlebar;
|
||||
export let node_ref;
|
||||
let saved_position;
|
||||
export let maximized;
|
||||
export let minimized;
|
||||
let translateX = '';
|
||||
let translateY = '';
|
||||
let animation_enabled = false;
|
||||
|
||||
export let on_focused = () => {
|
||||
}
|
||||
|
||||
export let z_index = 0;
|
||||
|
||||
onMount(async () => {
|
||||
if(options.exec_path != null){
|
||||
let rect = await get(options.exec_path);
|
||||
console.log(rect);
|
||||
if(rect){
|
||||
let workspace = document.querySelector('#work-space');
|
||||
if(rect.left + rect.width <= workspace.offsetWidth
|
||||
&& rect.top + rect.height <= workspace.offsetHeight){
|
||||
options.top = rect.top;
|
||||
options.left = rect.left;
|
||||
options.width = rect.width;
|
||||
options.height = rect.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(options.top == null){
|
||||
options.top = (node_ref.parentNode.offsetHeight - node_ref.offsetHeight)/2;
|
||||
}
|
||||
if(options.left == null){
|
||||
options.left = (node_ref.parentNode.offsetWidth - node_ref.offsetWidth)/2;
|
||||
}
|
||||
set_position({top: options.top, left: options.left, width: node_ref.width, height: node_ref.height});
|
||||
|
||||
if(options.resizable == null){
|
||||
options.resizable = true;
|
||||
}
|
||||
if(options.draggable == null){
|
||||
options.draggable = true;
|
||||
}
|
||||
|
||||
|
||||
setup_gestures();
|
||||
zIndex.update(value => value + 1);
|
||||
z_index = $zIndex;
|
||||
|
||||
node_ref.style.removeProperty('opacity');
|
||||
setTimeout(() => {
|
||||
animation_enabled = true;
|
||||
}, 500)
|
||||
|
||||
})
|
||||
|
||||
|
||||
export let on_click_close = () => {
|
||||
}
|
||||
|
||||
export let on_click_maximize = () => {
|
||||
if(!options.resizable) return;
|
||||
minimized = false;
|
||||
if(maximized){
|
||||
set_position(saved_position);
|
||||
maximized = false;
|
||||
} else {
|
||||
// let rect = utils.relative_rect(node_ref.parentNode.getBoundingClientRect(), node_ref.getBoundingClientRect());
|
||||
// console.log(rect);
|
||||
saved_position = {top: node_ref.offsetTop, left: node_ref.offsetLeft, width: node_ref.offsetWidth, height: node_ref.offsetHeight};
|
||||
set_position({top: 0, left: 0, width: node_ref.parentNode.offsetWidth, height: node_ref.parentNode.offsetHeight});
|
||||
maximized = true;
|
||||
}
|
||||
focus();
|
||||
}
|
||||
|
||||
export let on_click_minimize = () => {
|
||||
let window_center = get_center_point(node_ref.getBoundingClientRect());
|
||||
let tile_center = get_center_point(document.querySelector(`.program-tile[program-id="${options.id}"]`)?.getBoundingClientRect());
|
||||
|
||||
translateX = `translateX(${tile_center.x-window_center.x}px)`;
|
||||
translateY = `translateY(${tile_center.y-window_center.y}px)`;
|
||||
console.log(`${translateX} ${translateY} scale(0.1)`);
|
||||
|
||||
minimized = true;
|
||||
loose_focus();
|
||||
}
|
||||
|
||||
export function restore(){
|
||||
if(minimized){
|
||||
minimized = false;
|
||||
} else if(maximized) {
|
||||
on_click_maximize();
|
||||
}
|
||||
focus();
|
||||
}
|
||||
|
||||
export function focus(){
|
||||
if(z_index != $zIndex){
|
||||
zIndex.update(value => value + 1);
|
||||
z_index = $zIndex;
|
||||
on_focused();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function loose_focus(){
|
||||
if(z_index == $zIndex){
|
||||
console.log('loose focus');
|
||||
zIndex.update(value => value + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function get_center_point(rect){
|
||||
if(rect == null){
|
||||
return {x: document.body.offsetWidth*0.5, y: document.body.offsetHeight*0.5}
|
||||
}
|
||||
return {x: rect.x + rect.width*0.5, y: rect.y+rect.height*0.5}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function set_position({top, left, width, height}){
|
||||
|
||||
node_ref.style.top = `${top}px`;
|
||||
node_ref.style.left = `${left}px`;
|
||||
node_ref.style.width = `${width}px`;
|
||||
node_ref.style.height = `${height}px`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function setup_gestures(){
|
||||
if(options.draggable){
|
||||
jQuery(node_ref).draggable({
|
||||
containment: 'parent',
|
||||
handle: '.titlebar',
|
||||
stop: async () => {
|
||||
if(options.exec_path){
|
||||
await set(options.exec_path, node_ref.getBoundingClientRect())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if(options.resizable){
|
||||
jQuery(node_ref).resizable({
|
||||
minWidth: options.min_width,
|
||||
minHeight: options.min_height,
|
||||
aspectRatio: options.aspect_ratio,
|
||||
containment: 'parent',
|
||||
handles: 'all',
|
||||
classes: {
|
||||
"ui-resizable-se": "ui-icon ui-icon-gripsmall-diagonal-se opacity-0"
|
||||
},
|
||||
stop: async () => {
|
||||
if(options.exec_path){
|
||||
await set(options.exec_path, node_ref.getBoundingClientRect())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function update_icon(icon){
|
||||
titlebar.update_icon(icon);
|
||||
}
|
||||
|
||||
export function update_title(title){
|
||||
runningPrograms.update(values => {
|
||||
let program = values.find(el => el.options.id == options.id);
|
||||
let index = values.indexOf(program);
|
||||
if(index >= 0){
|
||||
values[index].options.title = title;
|
||||
}
|
||||
|
||||
return values;
|
||||
})
|
||||
titlebar.update_title(title);
|
||||
}
|
||||
|
||||
export function show_toast({theme='dark', message}){
|
||||
let toast = document.createElement('div');
|
||||
toast.style.position = 'absolute';
|
||||
toast.style.transform = 'translate(-50%)';
|
||||
toast.style.left = '50%';
|
||||
toast.style.top = '50%';
|
||||
toast.style.padding = '10px';
|
||||
toast.innerText = message;
|
||||
toast.style.borderRadius = '7px';
|
||||
toast.style.opacity = 1;
|
||||
toast.style.fontSize = '12px';
|
||||
toast.style.minHeight = '30px';
|
||||
toast.style.minWidth = '70px';
|
||||
toast.style.zIndex = 99999;
|
||||
if(theme == 'dark'){
|
||||
toast.style.backgroundColor = '#0f172a';
|
||||
toast.style.border = '1px solid #f1f5f9';
|
||||
toast.style.color = '#f8fafc';
|
||||
} else {
|
||||
toast.style.backgroundColor = '#f1f5f9';
|
||||
toast.style.border = '1px solid #1e293b';
|
||||
toast.style.color = '#0f172a';
|
||||
}
|
||||
node_ref.append(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.remove()
|
||||
}, 3000);
|
||||
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<div
|
||||
on:mousedown={focus}
|
||||
use:click_outside on:click_outside={loose_focus}
|
||||
bind:this={node_ref} style="
|
||||
opacity:0;
|
||||
position: absolute;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
padding: 0px;
|
||||
-webkit-font-smoothing: antialiased;"
|
||||
program-id="{options.id}"
|
||||
class="window absolute flex flex-col bg-xp-yellow {animation_enabled ? 'transition duration-300' : ''} {minimized ? `opacity-0` : ''}"
|
||||
style:width="{options.width}px" style:height="{options.height}px"
|
||||
style:min-width="{options.min_width}px" style:min-height="{options.min_height}px"
|
||||
style:transform="{minimized ? `${translateX} ${translateY} scale(0.1)` : 'none'}"
|
||||
style:background="{options.background}"
|
||||
style:z-index="{z_index}" style:box-shadow="{z_index < $zIndex ? 'var(--window-box-shadow-inactive)' : 'var(--window-box-shadow)'}">
|
||||
|
||||
<div class="shrink-0">
|
||||
<TitleBar bind:this={titlebar} options={options} inactive={z_index < $zIndex} maximized={maximized}
|
||||
on_click_close={on_click_close} on_click_maximize={on_click_maximize} on_click_minimize={on_click_minimize}>
|
||||
</TitleBar>
|
||||
</div>
|
||||
|
||||
<div class="grow shrink-0 relative shadow-xl">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<svelte:options accessors={true}></svelte:options>
|
||||
121
src/lib/components/xp/context_menu/CMDesktop.js
Normal file
121
src/lib/components/xp/context_menu/CMDesktop.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import { queueProgram, clipboard, hardDrive } from '../../../store';
|
||||
import { get } from 'svelte/store';
|
||||
import { recycle_bin_id, protected_items } from '../../../system';
|
||||
import * as fs from '../../../fs';
|
||||
|
||||
export let make = ({type, originator}) => {
|
||||
//originator: program
|
||||
return {
|
||||
required_width: 180 + 20,
|
||||
required_height: 27*6 + 20,
|
||||
menu: [
|
||||
[
|
||||
{
|
||||
name: 'Arrange Icons By',
|
||||
items: [
|
||||
{
|
||||
name: 'Name'
|
||||
},
|
||||
{
|
||||
name: 'Size'
|
||||
},
|
||||
{
|
||||
name: 'Type'
|
||||
},
|
||||
{
|
||||
name: 'Modified'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Refresh',
|
||||
action: () => {
|
||||
console.log('refresh');
|
||||
let nodes = document.querySelectorAll('.fs-item');
|
||||
for(let node of nodes){
|
||||
node.classList.add('animate-blink');
|
||||
}
|
||||
setTimeout(() => {
|
||||
for(let node of nodes){
|
||||
node.classList.remove('animate-blink');
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'Paste',
|
||||
disabled: get(clipboard).length == 0,
|
||||
action: () => {
|
||||
fs.paste(originator.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Paste Shortcut',
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'New',
|
||||
items: [
|
||||
{
|
||||
name: 'Folder',
|
||||
icon: '/images/xp/icons/FolderClosed.png',
|
||||
action: () => {
|
||||
fs.new_fs_item('folder', '', 'New Folder', originator.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Shortcut',
|
||||
icon: '/images/xp/icons/Shortcutoverlay.png'
|
||||
},
|
||||
{
|
||||
name: 'Briefcase',
|
||||
icon: '/images/xp/icons/Briefcase.png'
|
||||
},
|
||||
{
|
||||
name: 'Bitmap Image',
|
||||
icon: '/images/xp/icons/Bitmap.png',
|
||||
action: () => {
|
||||
fs.new_fs_item('file', '.bmp', 'New Bitmap Image', originator.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Text Document',
|
||||
icon: '/images/xp/icons/TXT.png',
|
||||
action: () => {
|
||||
fs.new_fs_item('file', '.txt', 'New Text Document', originator.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Wave Sound',
|
||||
icon: '/images/xp/icons/WMV.png',
|
||||
action: () => {
|
||||
fs.new_fs_item('file', '.wav', 'New Sound', originator.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Compressed (zipped) Folder',
|
||||
icon: '/images/xp/icons/Zipfolder.png'
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'Properties',
|
||||
action: () => {
|
||||
queueProgram.set({
|
||||
name: 'Display Properties',
|
||||
icon: 'DisplayProperties.png',
|
||||
path: './programs/display_properties.svelte'
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
267
src/lib/components/xp/context_menu/CMFSItem.js
Normal file
267
src/lib/components/xp/context_menu/CMFSItem.js
Normal file
@@ -0,0 +1,267 @@
|
||||
import { queueProgram, clipboard, selectingItems, hardDrive, clipboard_op, wallpaper } from '../../../store';
|
||||
import { recycle_bin_id, protected_items, wallpapers_folder, supported_wallpaper_filetypes, doctypes, archive_exts } from '../../../system';
|
||||
import * as utils from '../../../utils';
|
||||
import { get } from 'svelte/store';
|
||||
import * as fs from '../../../fs';
|
||||
import short from 'short-uuid';
|
||||
import FileSaver from 'file-saver';
|
||||
export let make = ({type, originator}) => {
|
||||
//originator: a wrapped fs item, i.e, file, folder, drive
|
||||
// {item: item, open: fn(), my_computer_instance: obj})
|
||||
|
||||
return {
|
||||
required_width: 180 + 20,
|
||||
required_height: 27*11 + 20,
|
||||
menu: [
|
||||
[
|
||||
...originator.item.parent != recycle_bin_id ? [
|
||||
{
|
||||
name: 'Open',
|
||||
action: () => {originator.open(originator.item.id);},
|
||||
font: 'bold',
|
||||
},
|
||||
{
|
||||
name: 'Explore',
|
||||
},
|
||||
{
|
||||
name: 'Search...',
|
||||
disabled: originator.type == 'file'
|
||||
}
|
||||
] : [],
|
||||
...originator.item.parent != recycle_bin_id
|
||||
&& doctypes[originator.item.ext] != null
|
||||
&& doctypes[originator.item.ext].length >= 2 ? [{
|
||||
name: 'Open With',
|
||||
items: doctypes[originator.item.ext].map(el => {
|
||||
return {
|
||||
name: el.name,
|
||||
icon: el.icon,
|
||||
action: () => queueProgram.set({
|
||||
path: el.path,
|
||||
fs_item: originator.item
|
||||
})
|
||||
}
|
||||
})
|
||||
}] : [],
|
||||
...supported_wallpaper_filetypes.includes(originator.item.ext) ? [
|
||||
{
|
||||
name: 'Set as Desktop Wallpaper',
|
||||
action: () => {
|
||||
let new_id = short.generate();
|
||||
fs.clone_fs(originator.item.id, wallpapers_folder, new_id);
|
||||
wallpaper.set(new_id);
|
||||
}
|
||||
}
|
||||
] : []
|
||||
],
|
||||
[
|
||||
...archive_exts.includes(originator.item.ext) && originator.item.parent != recycle_bin_id ? [
|
||||
{
|
||||
name: 'Extract here...',
|
||||
icon: '/images/xp/icons/RAR.png',
|
||||
action: () => {
|
||||
queueProgram.set({
|
||||
path: './programs/winrar.svelte',
|
||||
fs_item: originator.item
|
||||
})
|
||||
}
|
||||
}
|
||||
] : [],
|
||||
...['file', 'folder'].includes(originator.item.type) && originator.item.parent != recycle_bin_id ? [
|
||||
{
|
||||
name: 'Add to archive...',
|
||||
icon: '/images/xp/icons/RAR.png',
|
||||
action: () => {
|
||||
queueProgram.set({
|
||||
path: './programs/zip.svelte',
|
||||
fs_item: originator.item
|
||||
})
|
||||
}
|
||||
}
|
||||
] : []
|
||||
],
|
||||
[
|
||||
...originator.item.parent != recycle_bin_id ? [
|
||||
{
|
||||
name: 'Send To',
|
||||
items: [
|
||||
...originator.item.type == 'file'
|
||||
&& originator.item.storage_type != 'fake' ? [{
|
||||
name: 'Local Computer (Download)',
|
||||
icon: '/images/xp/icons/CopyingConflict.png',
|
||||
action: async () => {
|
||||
let file = await fs.get_file(originator.item.id);
|
||||
let download = new File([file], originator.item.name, {
|
||||
type: utils.ext_to_mime(originator.item.name)
|
||||
});
|
||||
FileSaver.saveAs(download);
|
||||
}
|
||||
}] : [],
|
||||
{
|
||||
name: 'Compressed (Zipped) Folder',
|
||||
icon: '/images/xp/icons/Zipfolder.png',
|
||||
action: () => {
|
||||
queueProgram.set({
|
||||
path: './programs/zip.svelte',
|
||||
fs_item: originator.item
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Desktop (create shortcut)',
|
||||
icon: '/images/xp/icons/Desktop.png'
|
||||
},
|
||||
{
|
||||
name: 'Mail Recipient',
|
||||
icon: '/images/xp/icons/Email.png'
|
||||
},
|
||||
{
|
||||
name: 'Floppy (A:)',
|
||||
icon: '/images/xp/icons/FloppyDisk.png'
|
||||
}
|
||||
]
|
||||
}
|
||||
] : []
|
||||
],
|
||||
[
|
||||
...protected_items.includes(originator.item.id) ? [] : [
|
||||
{
|
||||
name: 'Cut',
|
||||
disabled: get(selectingItems).length == 0,
|
||||
action: () => {
|
||||
fs.cut();
|
||||
}
|
||||
}
|
||||
],
|
||||
...originator.item.type == 'drive' || originator.item.type == 'removable_storage' ? [] : [
|
||||
{
|
||||
name: 'Copy',
|
||||
disabled: get(selectingItems).length == 0,
|
||||
action: () => {
|
||||
fs.copy();
|
||||
}
|
||||
}
|
||||
],
|
||||
... originator.item.type != 'file' && originator.item.parent != recycle_bin_id ? [{
|
||||
name: 'Paste',
|
||||
disabled: get(clipboard).length == 0,
|
||||
action: () => {
|
||||
fs.paste(originator.item.id);
|
||||
}
|
||||
}] : [],
|
||||
],
|
||||
[
|
||||
...protected_items.includes(originator.item.id) ? [] : [
|
||||
{
|
||||
name: 'Delete',
|
||||
action: () => {
|
||||
let items = [...get(selectingItems)];
|
||||
console.log(items)
|
||||
|
||||
let yes_action = () => {
|
||||
if(originator.item.parent == recycle_bin_id){
|
||||
for(let id of items){
|
||||
fs.del_fs(id);
|
||||
}
|
||||
} else {
|
||||
for(let id of items){
|
||||
fs.clone_fs(id, recycle_bin_id, null);
|
||||
fs.del_fs(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
let filename = originator.item.name.length > 70 ? originator.item.name.slice(0,70) + '...' : originator.item.name;
|
||||
|
||||
let message = '';
|
||||
let plural = '';
|
||||
if(items.length == 1){
|
||||
plural = '';
|
||||
} else if(items.length == 2){
|
||||
plural = ' and 1 other item';
|
||||
} else if(items.length > 2){
|
||||
plural = ` and ${items.length-1} other items`;
|
||||
}
|
||||
if(originator.item.parent == recycle_bin_id){
|
||||
message = `Do you want to permanently delete ${filename}${plural}? This action can't be undone?`
|
||||
} else {
|
||||
message = `Do you want to move ${filename}${plural} to the Recycle Bin?`
|
||||
}
|
||||
|
||||
let icon = originator.item.parent == recycle_bin_id ? '/images/xp/icons/DeleteConfirmation.png' : '/images/xp/icons/RecycleBinempty.png';
|
||||
|
||||
confirm_delete({
|
||||
node_ref: originator.my_computer_instance?.window.node_ref || document.body,
|
||||
title: 'Confirm Delete File',
|
||||
icon,
|
||||
message,
|
||||
yes_action: yes_action,
|
||||
no_action: () => {}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
],
|
||||
...protected_items.includes(originator.item.id) || originator.item.parent == recycle_bin_id ? [] : [
|
||||
{
|
||||
name: 'Rename',
|
||||
action: () => {
|
||||
selectingItems.set([originator.item.id]);
|
||||
originator.rename();
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'Properties',
|
||||
action: () => {
|
||||
if(originator.item.type == 'drive' || originator.item.type == 'removable_storage'){
|
||||
queueProgram.set({
|
||||
path: './programs/disk_properties.svelte',
|
||||
fs_item: originator.item
|
||||
})
|
||||
} else {
|
||||
queueProgram.set({
|
||||
path: './programs/properties.svelte',
|
||||
fs_item: originator.item
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function confirm_delete({node_ref, title, message, icon, yes_action, no_action}){
|
||||
const Dialog = (await import('../Dialog.svelte')).default;
|
||||
let buttons = [
|
||||
{
|
||||
name: 'OK',
|
||||
action: () => {
|
||||
yes_action();
|
||||
dialog.destroy();
|
||||
},
|
||||
focus: true
|
||||
},
|
||||
{
|
||||
name: 'Cancel',
|
||||
action: () => {
|
||||
no_action();
|
||||
dialog.destroy();
|
||||
},
|
||||
}
|
||||
]
|
||||
let dialog = new Dialog({
|
||||
target: node_ref,
|
||||
props:{
|
||||
icon,
|
||||
title,
|
||||
message,
|
||||
buttons,
|
||||
}
|
||||
})
|
||||
dialog.self = dialog;
|
||||
}
|
||||
138
src/lib/components/xp/context_menu/CMFSVoid.js
Normal file
138
src/lib/components/xp/context_menu/CMFSVoid.js
Normal file
@@ -0,0 +1,138 @@
|
||||
import { queueProgram, clipboard, hardDrive } from '../../../store';
|
||||
import { recycle_bin_id} from '../../../system';
|
||||
import { get } from 'svelte/store';
|
||||
import * as fs from '../../../fs';
|
||||
|
||||
export let make = ({type, originator}) => {
|
||||
|
||||
//originator: viewer
|
||||
|
||||
|
||||
//originator: program
|
||||
return {
|
||||
required_width: 180 + 20,
|
||||
required_height: 27*6 + 20,
|
||||
menu: [
|
||||
[
|
||||
{
|
||||
name: 'Arrange Icons By',
|
||||
items: [
|
||||
{
|
||||
name: 'Name'
|
||||
},
|
||||
{
|
||||
name: 'Size'
|
||||
},
|
||||
{
|
||||
name: 'Type'
|
||||
},
|
||||
{
|
||||
name: 'Modified'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Refresh',
|
||||
action: () => {
|
||||
console.log('refresh');
|
||||
let nodes = document.querySelectorAll('.fs-item');
|
||||
for(let node of nodes){
|
||||
node.classList.add('animate-blink');
|
||||
}
|
||||
setTimeout(() => {
|
||||
for(let node of nodes){
|
||||
node.classList.remove('animate-blink');
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
}
|
||||
],
|
||||
[
|
||||
...originator.id != recycle_bin_id ? [
|
||||
{
|
||||
name: 'Paste',
|
||||
disabled: get(clipboard).length == 0,
|
||||
action: () => {
|
||||
fs.paste(originator.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Paste Shortcut',
|
||||
disabled: true
|
||||
}
|
||||
] : []
|
||||
],
|
||||
[
|
||||
...originator.id != recycle_bin_id ? [
|
||||
{
|
||||
name: 'New',
|
||||
items: [
|
||||
{
|
||||
name: 'Folder',
|
||||
icon: '/images/xp/icons/FolderClosed.png',
|
||||
action: () => {
|
||||
fs.new_fs_item('folder', '', 'New Folder', originator.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Shortcut',
|
||||
icon: '/images/xp/icons/Shortcutoverlay.png'
|
||||
},
|
||||
{
|
||||
name: 'Briefcase',
|
||||
icon: '/images/xp/icons/Briefcase.png'
|
||||
},
|
||||
{
|
||||
name: 'Bitmap Image',
|
||||
icon: '/images/xp/icons/Bitmap.png',
|
||||
action: () => {
|
||||
fs.new_fs_item('file', '.bmp', 'New Bitmap Image', originator.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Text Document',
|
||||
icon: '/images/xp/icons/TXT.png',
|
||||
action: () => {
|
||||
fs.new_fs_item('file', '.txt', 'New Text Document', originator.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Wave Sound',
|
||||
icon: '/images/xp/icons/WMV.png',
|
||||
action: () => {
|
||||
fs.new_fs_item('file', '.wav', 'New Sound', originator.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Compressed (zipped) Folder',
|
||||
icon: '/images/xp/icons/Zipfolder.png'
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
] : []
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'Properties',
|
||||
action: () => {
|
||||
let fs_item = get(hardDrive)[originator.id];
|
||||
if(fs_item.type == 'drive' || fs_item.type == 'removable_storage'){
|
||||
queueProgram.set({
|
||||
path: './programs/disk_properties.svelte',
|
||||
fs_item
|
||||
})
|
||||
} else {
|
||||
queueProgram.set({
|
||||
path: './programs/properties.svelte',
|
||||
fs_item
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
43
src/lib/components/xp/context_menu/CMProgramTile.js
Normal file
43
src/lib/components/xp/context_menu/CMProgramTile.js
Normal file
@@ -0,0 +1,43 @@
|
||||
export let make = ({type, originator}) => {
|
||||
//originator: program
|
||||
return {
|
||||
required_width: 180 + 20,
|
||||
required_height: 27*4 + 20,
|
||||
menu: [
|
||||
[
|
||||
{
|
||||
name: 'Minimize',
|
||||
action: () => {originator.window.on_click_minimize();},
|
||||
disabled: originator.window.minimized,
|
||||
icon: '/images/xp/icons/tile_minimize.png',
|
||||
icon_size: 10,
|
||||
icon_type: 'monotone'
|
||||
},
|
||||
{
|
||||
name: 'Restore',
|
||||
action: () => {originator.window.restore();},
|
||||
disabled: !originator.window.maximized && !originator.window.minimized,
|
||||
icon: '/images/xp/icons/tile_restore.png',
|
||||
icon_size: 10,
|
||||
icon_type: 'monotone'
|
||||
},
|
||||
{
|
||||
name: 'Maximize',
|
||||
action: () => {originator.window.on_click_maximize();},
|
||||
disabled: originator.window.maximized || !originator.window.options.resizable,
|
||||
icon: '/images/xp/icons/tile_maximize.png',
|
||||
icon_size: 10,
|
||||
icon_type: 'monotone'
|
||||
},
|
||||
{
|
||||
name: 'Close',
|
||||
font: 'bold',
|
||||
action: () => {originator.window.on_click_close()},
|
||||
icon: '/images/xp/icons/tile_close.png',
|
||||
icon_size: 10,
|
||||
icon_type: 'monotone'
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
93
src/lib/components/xp/context_menu/RecycleBin.js
Normal file
93
src/lib/components/xp/context_menu/RecycleBin.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import { queueProgram, clipboard, selectingItems, hardDrive, clipboard_op } from '../../../store';
|
||||
import { recycle_bin_id, protected_items } from '../../../system';
|
||||
import { get } from 'svelte/store';
|
||||
import * as fs from '../../../fs';
|
||||
export let make = ({type, originator}) => {
|
||||
//originator: a wrapped fs item, i.e, file, folder, drive
|
||||
// {item: item, open: fn(), my_computer_instance: obj}
|
||||
|
||||
|
||||
return {
|
||||
required_width: 180 + 20,
|
||||
required_height: 27*3 + 20,
|
||||
menu: [
|
||||
[
|
||||
{
|
||||
name: 'Open',
|
||||
action: () => {
|
||||
let fs_item = get(hardDrive)[recycle_bin_id];
|
||||
queueProgram.set({
|
||||
path: './programs/my_computer.svelte',
|
||||
fs_item: fs_item
|
||||
})
|
||||
},
|
||||
font: 'bold',
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'Empty Recycle Bin',
|
||||
action: () => {
|
||||
let yes_action = () => {
|
||||
let files = get(hardDrive)[recycle_bin_id].files;
|
||||
let folders = get(hardDrive)[recycle_bin_id].folders;
|
||||
for(let id of [...files, ...folders]){
|
||||
fs.del_fs(id);
|
||||
}
|
||||
}
|
||||
|
||||
confirm_delete({
|
||||
node_ref: document.body,
|
||||
title: 'Confirm Delete File',
|
||||
icon: '/images/xp/icons/DeleteConfirmation.png' ,
|
||||
message: 'Do you want to permanently delete all files in the Recycle Bin? This action cannot be undone.',
|
||||
yes_action: yes_action,
|
||||
no_action: () => {}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Properties',
|
||||
action: () => {
|
||||
// queueProgram.set({
|
||||
// path: './programs/properties.svelte',
|
||||
// fs_item: originator.item
|
||||
// })
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function confirm_delete({node_ref, title, message, icon, yes_action, no_action}){
|
||||
const Dialog = (await import('../Dialog.svelte')).default;
|
||||
let buttons = [
|
||||
{
|
||||
name: 'OK',
|
||||
action: () => {
|
||||
yes_action();
|
||||
dialog.destroy();
|
||||
},
|
||||
focus: true
|
||||
},
|
||||
{
|
||||
name: 'Cancel',
|
||||
action: () => {
|
||||
no_action();
|
||||
dialog.destroy();
|
||||
}
|
||||
}
|
||||
]
|
||||
let dialog = new Dialog({
|
||||
target: node_ref,
|
||||
props:{
|
||||
icon,
|
||||
title,
|
||||
message,
|
||||
buttons
|
||||
}
|
||||
})
|
||||
dialog.self = dialog;
|
||||
}
|
||||
71
src/lib/components/xp/tooltip.js
Normal file
71
src/lib/components/xp/tooltip.js
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
export function tooltip(element) {
|
||||
|
||||
let comp;
|
||||
let timeout;
|
||||
function mouseEnter(event) {
|
||||
if(event.target !== element) return;
|
||||
|
||||
let tooltip_message = element.getAttribute('tooltip');
|
||||
if(tooltip_message == null || tooltip_message == '') return;
|
||||
|
||||
comp = document.createElement('div');
|
||||
|
||||
let rect = element.getBoundingClientRect();
|
||||
let estimated_width = 150;
|
||||
let estimated_height = 30;
|
||||
let screen_width = document.body.offsetWidth;
|
||||
let screen_height = document.body.offsetHeight;
|
||||
|
||||
if(rect.y + rect.height > screen_height - estimated_height){
|
||||
comp.style.bottom = `${rect.height+15}px`;
|
||||
} else {
|
||||
comp.style.top = `${rect.y + rect.height + 10}px`;
|
||||
}
|
||||
|
||||
if(rect.x + rect.width > screen_width - estimated_width){
|
||||
comp.style.right = `${20}px`;
|
||||
} else {
|
||||
comp.style.left = `${rect.x}px`;
|
||||
}
|
||||
|
||||
comp.style.maxWidth = '150px';
|
||||
comp.style.pointerEvents = 'none';
|
||||
comp.style.position = 'absolute';
|
||||
comp.style.zIndex = '999';
|
||||
comp.style.backgroundColor = '#ece8cf';
|
||||
comp.style.boxShadow = '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)';
|
||||
comp.style.overflow = 'hidden';
|
||||
comp.style.padding = '4px';
|
||||
comp.style.fontFamily = 'MSSS';
|
||||
comp.innerHTML = `
|
||||
<p class=" line-clamp-1 leading-tight text-ellipsis" style="font-size:11px;">
|
||||
${tooltip_message}
|
||||
</p>`;
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
document.body.appendChild(comp);
|
||||
setTimeout(() => {
|
||||
comp.remove();
|
||||
}, 3000);
|
||||
}, 2000);
|
||||
|
||||
}
|
||||
|
||||
function mouseLeave(event) {
|
||||
clearTimeout(timeout);
|
||||
if(comp != null){
|
||||
comp.remove();
|
||||
}
|
||||
}
|
||||
|
||||
element.addEventListener('mouseenter', mouseEnter);
|
||||
element.addEventListener('mouseleave', mouseLeave);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
element.removeEventListener('mouseenter', mouseEnter);
|
||||
element.removeEventListener('mouseleave', mouseLeave);
|
||||
}
|
||||
}
|
||||
}
|
||||
147
src/lib/dir_parser.js
Normal file
147
src/lib/dir_parser.js
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* Traversing directory using promises
|
||||
**/
|
||||
|
||||
const traverseDirectory = (entry) => {
|
||||
const reader = entry.createReader();
|
||||
return new Promise((resolveDirectory) => {
|
||||
const iterationAttempts = [];
|
||||
const errorHandler = () => {};
|
||||
|
||||
function readEntries() {
|
||||
reader.readEntries((batchEntries) => {
|
||||
if (!batchEntries.length) {
|
||||
resolveDirectory(Promise.all(iterationAttempts))
|
||||
} else {
|
||||
iterationAttempts.push(Promise.all(batchEntries.map((batchEntry) => {
|
||||
if (batchEntry.isDirectory) {
|
||||
return traverseDirectory(batchEntry);
|
||||
}
|
||||
return Promise.resolve(batchEntry);
|
||||
})));
|
||||
|
||||
readEntries();
|
||||
}
|
||||
}, errorHandler);
|
||||
}
|
||||
|
||||
readEntries();
|
||||
});
|
||||
}
|
||||
|
||||
const packageFile = (file, entry) => {
|
||||
let object = {
|
||||
fileObject: file,
|
||||
fullPath: entry ? entry.fullPath : '',
|
||||
lastModified: file.lastModified,
|
||||
lastModifiedDate: file.lastModifiedDate,
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
webkitRelativePath: file.webkitRelativePath
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
const getFile = (entry) => {
|
||||
return new Promise((resolve) => {
|
||||
entry.file((file) => {
|
||||
resolve(packageFile(file, entry));
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleFilePromises = (promises, fileList) => {
|
||||
return Promise.all(promises).then((files) => {
|
||||
files.forEach((file) => {
|
||||
fileList.push(file);
|
||||
});
|
||||
return fileList;
|
||||
})
|
||||
}
|
||||
|
||||
const getDataTransferFiles = (dataTransfer) => {
|
||||
const dataTransferFiles = [];
|
||||
const folderPromises = [];
|
||||
const filePromises = [];
|
||||
|
||||
[].slice.call(dataTransfer.items).forEach((listItem) => {
|
||||
let supported_method;
|
||||
if(typeof listItem['webkitGetAsEntry'] === 'function'){
|
||||
supported_method = 'webkitGetAsEntry';
|
||||
} else {
|
||||
supported_method = 'getAsEntry'
|
||||
}
|
||||
|
||||
const entry = listItem[supported_method]();
|
||||
|
||||
if (entry) {
|
||||
if (entry.isDirectory) {
|
||||
folderPromises.push(traverseDirectory(entry));
|
||||
} else {
|
||||
filePromises.push(getFile(entry));
|
||||
}
|
||||
} else {
|
||||
dataTransferFiles.push(listItem);
|
||||
}
|
||||
});
|
||||
if (folderPromises.length) {
|
||||
const flatten = (array) => array.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
|
||||
return Promise.all(folderPromises).then((fileEntries) => {
|
||||
const flattenedEntries = flatten(fileEntries);
|
||||
flattenedEntries.forEach((fileEntry) => {
|
||||
filePromises.push(getFile(fileEntry));
|
||||
});
|
||||
return handleFilePromises(filePromises, dataTransferFiles);
|
||||
});
|
||||
} else if (filePromises.length) {
|
||||
return handleFilePromises(filePromises, dataTransferFiles);
|
||||
}
|
||||
|
||||
return Promise.resolve(dataTransferFiles);
|
||||
}
|
||||
|
||||
// Use this function by passing the drop or change event.
|
||||
const getDroppedOrSelectedFiles = (event) => {
|
||||
const dataTransfer = event.dataTransfer;
|
||||
if (dataTransfer && dataTransfer.items) {
|
||||
return getDataTransferFiles(dataTransfer)
|
||||
.then((fileList) => {
|
||||
return Promise.resolve(fileList);
|
||||
})
|
||||
}
|
||||
|
||||
const files = [];
|
||||
const dragDropFileList = dataTransfer && dataTransfer.files;
|
||||
const inputFieldFileList = event.target && event.target.files;
|
||||
const fileList = dragDropFileList || inputFieldFileList || [];
|
||||
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
files.push(packageFile(fileList[i]));
|
||||
}
|
||||
|
||||
return Promise.resolve(files);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function parse_dir(e){
|
||||
let files = await getDroppedOrSelectedFiles(e);
|
||||
|
||||
let result = {};
|
||||
for(let file of files){
|
||||
let comps = file.fullPath.split('/').filter(el => el != '');
|
||||
comps.reduce((r, el, index) => {
|
||||
if(index != comps.length - 1){
|
||||
if(r[el] == null){
|
||||
r[el] = {}
|
||||
}
|
||||
} else {
|
||||
r[el] = file.fileObject;
|
||||
}
|
||||
return r[el];
|
||||
}, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
56
src/lib/docx/docx2html.js
Normal file
56
src/lib/docx/docx2html.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const monospaceFonts = ["consolas", "courier", "courier new"];
|
||||
|
||||
let options = {
|
||||
transformDocument: mammoth.transforms.paragraph(transformParagraph),
|
||||
preserveColors: true,
|
||||
preserveFonts: true,
|
||||
styleMap: [
|
||||
"p[style-name='Title'] => h1:fresh",
|
||||
"p[style-name='Heading 1'] => h1:fresh",
|
||||
"p[style-name='Heading 2'] => h2:fresh",
|
||||
"p[style-name='Heading 3'] => h3:fresh",
|
||||
"p[style-name='Heading 4'] => h4:fresh",
|
||||
"p[style-name='Heading 5'] => h5:fresh",
|
||||
"p[style-name='Heading 6'] => h6:fresh",
|
||||
],
|
||||
convertImage: mammoth.images.imgElement(function(image) {
|
||||
return image.read().then(function(buffer) {
|
||||
let file = new File([buffer], {type: image.contentType})
|
||||
let url = URL.createObjectURL(file);
|
||||
return {
|
||||
src: url,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
style: image.style
|
||||
};
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export async function docx2html(arrayBuffer){
|
||||
try {
|
||||
let result = await mammoth.convertToHtml({arrayBuffer}, options);
|
||||
return result.value;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function transformParagraph(paragraph) {
|
||||
|
||||
var runs = mammoth.transforms.getDescendantsOfType(paragraph, "run");
|
||||
|
||||
var isMatch = runs.length > 0 && runs.every(function(run) {
|
||||
return run.font && monospaceFonts.indexOf(run.font.toLowerCase()) !== -1;
|
||||
});
|
||||
if (isMatch) {
|
||||
return {
|
||||
...paragraph,
|
||||
styleId: "code",
|
||||
styleName: "Code"
|
||||
};
|
||||
} else {
|
||||
return paragraph;
|
||||
}
|
||||
}
|
||||
666
src/lib/docx/html2docx.js
Normal file
666
src/lib/docx/html2docx.js
Normal file
@@ -0,0 +1,666 @@
|
||||
import { Document, Packer, Paragraph, TextRun, ImageRun, AlignmentType, ExternalHyperlink,
|
||||
Table, TableRow, TableCell, WidthType, HeadingLevel, ConcreteNumbering, Numbering,
|
||||
HorizontalPositionRelativeFrom, VerticalPositionRelativeFrom, HorizontalPositionAlign, VerticalPositionAlign,
|
||||
TextWrappingType, TextWrappingSide } from 'docx';
|
||||
import * as fs from '../fs';
|
||||
|
||||
const COLORS = {
|
||||
"black": "000000",
|
||||
"silver": "c0c0c0",
|
||||
"gray": "808080",
|
||||
"white": "ffffff",
|
||||
"maroon": "800000",
|
||||
"red": "ff0000",
|
||||
"purple": "800080",
|
||||
"fuchsia": "ff00ff",
|
||||
"green": "008000",
|
||||
"lime": "00ff00",
|
||||
"olive": "808000",
|
||||
"yellow": "ffff00",
|
||||
"navy": "000080",
|
||||
"blue": "0000ff",
|
||||
"teal": "008080",
|
||||
"aqua": "00ffff",
|
||||
"aliceblue": "f0f8ff",
|
||||
"antiquewhite": "faebd7",
|
||||
"aquamarine": "7fffd4",
|
||||
"azure": "f0ffff",
|
||||
"beige": "f5f5dc",
|
||||
"bisque": "ffe4c4",
|
||||
"blanchedalmond": "ffebcd",
|
||||
"blueviolet": "8a2be2",
|
||||
"brown": "a52a2a",
|
||||
"burlywood": "deb887",
|
||||
"cadetblue": "5f9ea0",
|
||||
"chartreuse": "7fff00",
|
||||
"chocolate": "d2691e",
|
||||
"coral": "ff7f50",
|
||||
"cornflowerblue": "6495ed",
|
||||
"cornsilk": "fff8dc",
|
||||
"crimson": "dc143c",
|
||||
"cyan": "00ffff",
|
||||
"darkblue": "00008b",
|
||||
"darkcyan": "008b8b",
|
||||
"darkgoldenrod": "b8860b",
|
||||
"darkgray": "a9a9a9",
|
||||
"darkgreen": "006400",
|
||||
"darkgrey": "a9a9a9",
|
||||
"darkkhaki": "bdb76b",
|
||||
"darkmagenta": "8b008b",
|
||||
"darkolivegreen": "556b2f",
|
||||
"darkorange": "ff8c00",
|
||||
"darkorchid": "9932cc",
|
||||
"darkred": "8b0000",
|
||||
"darksalmon": "e9967a",
|
||||
"darkseagreen": "8fbc8f",
|
||||
"darkslateblue": "483d8b",
|
||||
"darkslategray": "2f4f4f",
|
||||
"darkslategrey": "2f4f4f",
|
||||
"darkturquoise": "00ced1",
|
||||
"darkviolet": "9400d3",
|
||||
"deeppink": "ff1493",
|
||||
"deepskyblue": "00bfff",
|
||||
"dimgray": "696969",
|
||||
"dimgrey": "696969",
|
||||
"dodgerblue": "1e90ff",
|
||||
"firebrick": "b22222",
|
||||
"floralwhite": "fffaf0",
|
||||
"forestgreen": "228b22",
|
||||
"gainsboro": "dcdcdc",
|
||||
"ghostwhite": "f8f8ff",
|
||||
"gold": "ffd700",
|
||||
"goldenrod": "daa520",
|
||||
"greenyellow": "adff2f",
|
||||
"grey": "808080",
|
||||
"honeydew": "f0fff0",
|
||||
"hotpink": "ff69b4",
|
||||
"indianred": "cd5c5c",
|
||||
"indigo": "4b0082",
|
||||
"ivory": "fffff0",
|
||||
"khaki": "f0e68c",
|
||||
"lavender": "e6e6fa",
|
||||
"lavenderblush": "fff0f5",
|
||||
"lawngreen": "7cfc00",
|
||||
"lemonchiffon": "fffacd",
|
||||
"lightblue": "add8e6",
|
||||
"lightcoral": "f08080",
|
||||
"lightcyan": "e0ffff",
|
||||
"lightgoldenrodyellow": "fafad2",
|
||||
"lightgray": "d3d3d3",
|
||||
"lightgreen": "90ee90",
|
||||
"lightgrey": "d3d3d3",
|
||||
"lightpink": "ffb6c1",
|
||||
"lightsalmon": "ffa07a",
|
||||
"lightseagreen": "20b2aa",
|
||||
"lightskyblue": "87cefa",
|
||||
"lightslategray": "778899",
|
||||
"lightslategrey": "778899",
|
||||
"lightsteelblue": "b0c4de",
|
||||
"lightyellow": "ffffe0",
|
||||
"limegreen": "32cd32",
|
||||
"linen": "faf0e6",
|
||||
"magenta": "ff00ff",
|
||||
"mediumaquamarine": "66cdaa",
|
||||
"mediumblue": "0000cd",
|
||||
"mediumorchid": "ba55d3",
|
||||
"mediumpurple": "9370db",
|
||||
"mediumseagreen": "3cb371",
|
||||
"mediumslateblue": "7b68ee",
|
||||
"mediumspringgreen": "00fa9a",
|
||||
"mediumturquoise": "48d1cc",
|
||||
"mediumvioletred": "c71585",
|
||||
"midnightblue": "191970",
|
||||
"mintcream": "f5fffa",
|
||||
"mistyrose": "ffe4e1",
|
||||
"moccasin": "ffe4b5",
|
||||
"navajowhite": "ffdead",
|
||||
"oldlace": "fdf5e6",
|
||||
"olivedrab": "6b8e23",
|
||||
"orange": "ffa500",
|
||||
"orangered": "ff4500",
|
||||
"orchid": "da70d6",
|
||||
"palegoldenrod": "eee8aa",
|
||||
"palegreen": "98fb98",
|
||||
"paleturquoise": "afeeee",
|
||||
"palevioletred": "db7093",
|
||||
"papayawhip": "ffefd5",
|
||||
"peachpuff": "ffdab9",
|
||||
"peru": "cd853f",
|
||||
"pink": "ffc0cb",
|
||||
"plum": "dda0dd",
|
||||
"powderblue": "b0e0e6",
|
||||
"rosybrown": "bc8f8f",
|
||||
"royalblue": "4169e1",
|
||||
"saddlebrown": "8b4513",
|
||||
"salmon": "fa8072",
|
||||
"sandybrown": "f4a460",
|
||||
"seagreen": "2e8b57",
|
||||
"seashell": "fff5ee",
|
||||
"sienna": "a0522d",
|
||||
"skyblue": "87ceeb",
|
||||
"slateblue": "6a5acd",
|
||||
"slategray": "708090",
|
||||
"slategrey": "708090",
|
||||
"snow": "fffafa",
|
||||
"springgreen": "00ff7f",
|
||||
"steelblue": "4682b4",
|
||||
"tan": "d2b48c",
|
||||
"thistle": "d8bfd8",
|
||||
"tomato": "ff6347",
|
||||
"turquoise": "40e0d0",
|
||||
"violet": "ee82ee",
|
||||
"wheat": "f5deb3",
|
||||
"whitesmoke": "f5f5f5",
|
||||
"yellowgreen": "9acd32"
|
||||
}
|
||||
|
||||
export async function html2docx(html){
|
||||
|
||||
let document = (new DOMParser()).parseFromString(html,'text/html');
|
||||
|
||||
let docx_elements = [];
|
||||
let nodes = Array.from(document.querySelectorAll('p,pre,table,h1,h2,h3,h4,h5,h6,ul,ol'));
|
||||
|
||||
nodes = nodes.filter(node => {
|
||||
return !nodes
|
||||
.filter(el => el != node)
|
||||
.some(el => el.contains(node));
|
||||
});
|
||||
|
||||
for(let node of nodes){
|
||||
let instance = nodes.indexOf(node);
|
||||
|
||||
if(node.nodeName == 'P' || node.nodeName == 'PRE'){
|
||||
docx_elements.push(await build_paragraph(node));
|
||||
|
||||
} else if(node.nodeName == 'TABLE'){
|
||||
docx_elements.push(await build_table(node));
|
||||
|
||||
} else if(['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(node.nodeName)){
|
||||
docx_elements.push(await build_heading(node));
|
||||
} else if(node.nodeName == 'UL'){
|
||||
docx_elements.push(...await build_ul(node, instance));
|
||||
} else if(node.nodeName == 'OL'){
|
||||
docx_elements.push(...await build_ol(node, instance));
|
||||
}
|
||||
}
|
||||
|
||||
let docx = new Document({
|
||||
sections: [{
|
||||
children: docx_elements
|
||||
}],
|
||||
numbering:{
|
||||
config:[{
|
||||
reference: 'arabic',
|
||||
levels: [
|
||||
{
|
||||
level: 0,
|
||||
format: "decimal",
|
||||
text: "%1",
|
||||
alignment: AlignmentType.START,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 300, hanging: 200 },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
level: 1,
|
||||
format: "decimal",
|
||||
text: "%1.%2",
|
||||
alignment: AlignmentType.START,
|
||||
style: {
|
||||
paragraph: {
|
||||
indent: { left: 600, hanging: 200 },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}]
|
||||
},
|
||||
})
|
||||
|
||||
let blob = await Packer.toBlob(docx);
|
||||
return blob;
|
||||
}
|
||||
|
||||
|
||||
async function build_paragraph(node){
|
||||
let style = parse_style(node);
|
||||
if(style.size == null) style.size = 24;
|
||||
if(style.font == null && node.nodeName == 'PRE') style.font = 'Courier New';
|
||||
|
||||
let children = await build_child_nodes(node, style);
|
||||
|
||||
let alignment = get_align(node);
|
||||
let border = parse_border(node);
|
||||
if(node.parentElement.nodeName == 'BLOCKQUOTE'){
|
||||
if(border.left == null){
|
||||
border.left = {color: 'cbd5e1', size: 16, space: 1, style: 'single'}
|
||||
}
|
||||
if(style.indent == null || style.indent.left == 0){
|
||||
style.indent = {left: 80}
|
||||
}
|
||||
}
|
||||
|
||||
let paragraph = new Paragraph({
|
||||
alignment,
|
||||
indent: style.indent,
|
||||
children,
|
||||
border
|
||||
});
|
||||
return paragraph;
|
||||
}
|
||||
async function build_table(node){
|
||||
let rows = [];
|
||||
for(let row of node.querySelectorAll('tr')){
|
||||
let cells = [];
|
||||
for(let cell of row.querySelectorAll('th, td')){
|
||||
|
||||
cells.push(new TableCell({
|
||||
children: [new Paragraph({children: await build_child_nodes(cell)})]
|
||||
}));
|
||||
}
|
||||
rows.push(new TableRow({
|
||||
children: cells
|
||||
}))
|
||||
}
|
||||
let number_of_columns = node.querySelector('tr').querySelectorAll('th, td').length;
|
||||
let table = new Table({
|
||||
rows,
|
||||
width: 0,
|
||||
columnWidths: Array(number_of_columns).fill(Math.floor(9638/number_of_columns), 0, number_of_columns)
|
||||
});
|
||||
return table;
|
||||
}
|
||||
|
||||
async function build_heading(node){
|
||||
let style = parse_style(node);
|
||||
let children = await build_child_nodes(node, style);
|
||||
let alignment = get_align(node);
|
||||
let heading;
|
||||
switch (node.nodeName) {
|
||||
case 'H1':
|
||||
heading = HeadingLevel.HEADING_1
|
||||
break;
|
||||
case 'H2':
|
||||
heading = HeadingLevel.HEADING_2;
|
||||
break;
|
||||
case 'H3':
|
||||
heading = HeadingLevel.HEADING_3;
|
||||
break;
|
||||
case 'H4':
|
||||
heading = HeadingLevel.HEADING_4;
|
||||
break;
|
||||
case 'H5':
|
||||
heading = HeadingLevel.HEADING_5;
|
||||
break;
|
||||
case 'H6':
|
||||
heading = HeadingLevel.HEADING_6;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
let border = parse_border(node);
|
||||
let paragraph = new Paragraph({
|
||||
alignment,
|
||||
children,
|
||||
indent: style.indent,
|
||||
heading,
|
||||
border
|
||||
});
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
async function build_ul(node, instance){
|
||||
let list = [];
|
||||
for(let li of node.querySelectorAll(':scope > li')){
|
||||
list.push(new Paragraph({
|
||||
children: await build_child_nodes(li),
|
||||
bullet: {level: 0, instance}
|
||||
}))
|
||||
if(li.querySelectorAll == null) continue;
|
||||
for(let sub_li of li.querySelectorAll('ul li')){
|
||||
list.push(new Paragraph({
|
||||
children: await build_child_nodes(sub_li),
|
||||
bullet: {level: 1, instance}
|
||||
}))
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
async function build_ol(node, instance){
|
||||
let list = [];
|
||||
for(let li of node.querySelectorAll(':scope > li')){
|
||||
list.push(new Paragraph({
|
||||
children: await build_child_nodes(li),
|
||||
numbering: {reference: 'arabic', instance, level: 0}
|
||||
}))
|
||||
if(li.querySelectorAll == null) continue;
|
||||
for(let sub_li of li.querySelectorAll('ol li')){
|
||||
list.push(new Paragraph({
|
||||
children: await build_child_nodes(sub_li),
|
||||
numbering: {reference: 'arabic', instance, level: 1}
|
||||
}))
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
async function build_child_nodes(node, inherit_attr){
|
||||
if(inherit_attr == null) inherit_attr = {};
|
||||
let values = [];
|
||||
let children = node.childNodes;
|
||||
for(let child of children){
|
||||
if(child.nodeName == '#text'){
|
||||
let text_run = new TextRun({
|
||||
text: child.nodeValue,
|
||||
bold: inherit_attr.bold,
|
||||
italics: inherit_attr.italics,
|
||||
subScript: inherit_attr.subScript,
|
||||
superScript: inherit_attr.superScript,
|
||||
strike: inherit_attr.strike,
|
||||
underline: inherit_attr.underline ? {} : null,
|
||||
color: inherit_attr.color,
|
||||
shading: inherit_attr.shading,
|
||||
size: inherit_attr.size,
|
||||
allCaps: inherit_attr.allCaps,
|
||||
font: inherit_attr.font,
|
||||
style: inherit_attr.style
|
||||
})
|
||||
values = [...values, text_run];
|
||||
} else if(child.nodeName == 'A' && child.getAttribute('href')){
|
||||
let link = new ExternalHyperlink({
|
||||
children: await build_child_nodes(child, {style: 'Hyperlink'}),
|
||||
link: child.getAttribute('href')
|
||||
})
|
||||
values = [...values, link];
|
||||
} else if(child.nodeName == 'IMG') {
|
||||
let buffer = await fs.buffer_from_url(child.src);
|
||||
let floating = parse_image_floating(child);
|
||||
let image_run = new ImageRun({
|
||||
data: buffer,
|
||||
transformation: {width: child.width, height: child.height},
|
||||
floating
|
||||
})
|
||||
values = [...values, image_run];
|
||||
} else if(node.childNodes.length > 0 && !['UL', 'OL'].includes(node.nodeName)){
|
||||
let passed_down_style = {...inherit_attr, ...parse_style(child)};
|
||||
|
||||
if(child.nodeName == 'STRONG') passed_down_style.bold = true;
|
||||
if(child.nodeName == 'EM') passed_down_style.italics = true;
|
||||
if(child.nodeName == 'SUB') passed_down_style.subScript = true;
|
||||
if(child.nodeName == 'SUP') passed_down_style.superScript = true;
|
||||
if(child.nodeName == 'S') passed_down_style.strike = true;
|
||||
if(child.nodeName == 'U') passed_down_style.underline = true;
|
||||
|
||||
if(child.nodeName == 'A') {
|
||||
passed_down_style.anchor = child.getAttribute('href');
|
||||
passed_down_style.underline = true;
|
||||
}
|
||||
|
||||
values = [...values, ...await build_child_nodes(child, passed_down_style)];
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function parse_style(node){
|
||||
|
||||
let style = {};
|
||||
|
||||
let raw_style = (node.getAttribute('style') || '').split(';');
|
||||
|
||||
for(let el of raw_style){
|
||||
let values = el.trim().split(':');
|
||||
if(values.length == 2){
|
||||
style[values[0].trim()] = values[1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
let fill = to_hex(style['background-color']);
|
||||
if(fill){
|
||||
style['shading'] = {fill};
|
||||
}
|
||||
|
||||
style['color'] = to_hex(style['color']);
|
||||
|
||||
if(style['font-family']){
|
||||
style['font'] = style['font-family'].split(',')[0];
|
||||
if(style['font']){
|
||||
style['font'] = style['font'].split('\'').join('');
|
||||
}
|
||||
}
|
||||
|
||||
style['size'] = to_halfpoint(style['font-size']);
|
||||
let indent_left = to_halfpoint(style['padding-left']);
|
||||
if(!isNaN(indent_left)){
|
||||
//indent_left is in halfpoint,
|
||||
//indentations in OpenXML are measured in 1/20 of a point
|
||||
style['indent'] = {left: indent_left*10}
|
||||
}
|
||||
|
||||
if(style['text-transform'] == 'uppercase'){
|
||||
style['allCaps'] = true;
|
||||
}
|
||||
if(style['text-transform'] == 'capitalize'){
|
||||
style['smallCaps'] = true;
|
||||
}
|
||||
if(style['text-decoration'] == 'line-through'){
|
||||
style['strike'] = true;
|
||||
}
|
||||
if(style['text-decoration'] == 'underline'){
|
||||
style['underline'] = true;
|
||||
}
|
||||
if(style['font-style'] == 'italic'){
|
||||
style['italics'] = true;
|
||||
}
|
||||
if(style['font-weight'] == 'bold' || parseInt(style['font-weight']) >= 700){
|
||||
style['bold'] = true;
|
||||
}
|
||||
|
||||
|
||||
let allow_attrs = ['color', 'shading', 'size', 'indent', 'allCaps', 'allCaps', 'strike', 'font', 'italics','underline', 'bold'];
|
||||
for(let key of Object.keys(style)){
|
||||
if(!allow_attrs.includes(key) || style[key] == null){
|
||||
delete style[key];
|
||||
}
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
function to_halfpoint(str){
|
||||
if(str == null || str == '') return null;
|
||||
str = str.trim();
|
||||
|
||||
let unit;
|
||||
if(str.endsWith('pt')){
|
||||
unit = 'pt'
|
||||
} else if(str.endsWith('px')){
|
||||
unit = 'px';
|
||||
}
|
||||
if(unit){
|
||||
let value = parseInt(str.split(unit).join(''));
|
||||
if(isNaN(value)) return null;
|
||||
if(unit == 'px'){
|
||||
value = 2*Math.ceil((72*value)/96)
|
||||
} else if(unit == 'pt'){
|
||||
value = 2*value;
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function to_hex(str){
|
||||
if(str == null || str == '') return null;
|
||||
str = str.trim();
|
||||
|
||||
if(COLORS[str] != null) return COLORS[str];
|
||||
|
||||
let color;
|
||||
if(str.includes('rgb')){
|
||||
color = rgb_to_hex(str);
|
||||
} else {
|
||||
color = str.split('#').join('').trim();
|
||||
}
|
||||
|
||||
if(color.length == 3){
|
||||
color = color + color;
|
||||
}
|
||||
if(color == null || !/^[0-9A-F]{6}$/i.test(color)){
|
||||
color == null
|
||||
}
|
||||
return color;
|
||||
}
|
||||
function rgb_to_hex(rgb) {
|
||||
// Choose correct separator
|
||||
let sep = rgb.indexOf(",") > -1 ? "," : " ";
|
||||
// Turn "rgb(r,g,b)" into [r,g,b]
|
||||
rgb = rgb.substr(4).split(")")[0].split(sep);
|
||||
|
||||
let r = (+rgb[0]).toString(16),
|
||||
g = (+rgb[1]).toString(16),
|
||||
b = (+rgb[2]).toString(16);
|
||||
|
||||
if (r.length == 1)
|
||||
r = "0" + r;
|
||||
if (g.length == 1)
|
||||
g = "0" + g;
|
||||
if (b.length == 1)
|
||||
b = "0" + b;
|
||||
|
||||
return r + g + b;
|
||||
}
|
||||
|
||||
function parse_border(node){
|
||||
let style = {};
|
||||
let raw_style = (node.getAttribute('style') || '').split(';');
|
||||
|
||||
for(let el of raw_style){
|
||||
let values = el.trim().split(':');
|
||||
if(values.length == 2){
|
||||
style[values[0].trim()] = values[1].trim();
|
||||
}
|
||||
}
|
||||
let top, right, bottom, left;
|
||||
if(style['border'] != null){
|
||||
let [size, border_style, ...color] = style['border'].split(' ');
|
||||
color = to_hex(color.join('').trim());
|
||||
size = to_halfpoint(size)*4;
|
||||
top = {color,size, space:1,style: 'single'}
|
||||
right = {color,size, space:1,style: 'single'}
|
||||
bottom = {color,size, space:1,style: 'single'}
|
||||
left = {color,size, space:1,style: 'single'}
|
||||
}
|
||||
if(style['border-left'] != null){
|
||||
let [size, border_style, ...color] = style['border-left'].split(' ');
|
||||
color = to_hex(color.join('').trim());
|
||||
size = to_halfpoint(size)*4;
|
||||
left = {color,size, space:1,style: 'single'}
|
||||
}
|
||||
if(style['border-right'] != null){
|
||||
let [size, border_style, ...color] = style['border-right'].split(' ');
|
||||
color = to_hex(color.join('').trim());
|
||||
size = to_halfpoint(size)*4;
|
||||
right = {color,size, space:1,style: 'single'}
|
||||
}
|
||||
if(style['border-top'] != null){
|
||||
let [size, border_style, ...color] = style['border-top'].split(' ');
|
||||
color = to_hex(color.join('').trim());
|
||||
size = to_halfpoint(size)*4;
|
||||
top = {color,size, space:1,style: 'single'}
|
||||
}
|
||||
if(style['border-bottom'] != null){
|
||||
let [size, border_style, ...color] = style['border-bottom'].split(' ');
|
||||
color = to_hex(color.join('').trim());
|
||||
size = to_halfpoint(size)*4;
|
||||
bottom = {color,size, space:1,style: 'single'}
|
||||
}
|
||||
|
||||
return {top, right, bottom, left};
|
||||
}
|
||||
|
||||
function parse_image_floating(node){
|
||||
let style = {};
|
||||
|
||||
let raw_style = (node.getAttribute('style') || '').split(';');
|
||||
|
||||
for(let el of raw_style){
|
||||
let values = el.trim().split(':');
|
||||
if(values.length == 2){
|
||||
style[values[0].trim()] = values[1].trim();
|
||||
}
|
||||
}
|
||||
let verticalPosition = {
|
||||
relative: VerticalPositionRelativeFrom.TOP_MARGIN,
|
||||
align: VerticalPositionAlign.TOP
|
||||
}
|
||||
|
||||
let margin = {
|
||||
top: 360000,
|
||||
right: 360000,
|
||||
bottom: 360000,
|
||||
left: 360000
|
||||
}
|
||||
if(style['margin-left'] == 'auto' && style['margin-right'] == 'auto'){
|
||||
return {
|
||||
horizontalPosition: {
|
||||
relative: HorizontalPositionRelativeFrom.COLUMN,
|
||||
align: HorizontalPositionAlign.CENTER,
|
||||
},
|
||||
verticalPosition, margin,
|
||||
wrap: {type: TextWrappingType.TOP_AND_BOTTOM, side: TextWrappingSide.BOTH_SIDES}
|
||||
}
|
||||
}
|
||||
if(style['float'] == 'left'){
|
||||
return {
|
||||
horizontalPosition: {
|
||||
relative: HorizontalPositionRelativeFrom.COLUMN,
|
||||
align: HorizontalPositionAlign.LEFT,
|
||||
},
|
||||
verticalPosition, margin,
|
||||
wrap: {type:TextWrappingType.SQUARE, side: TextWrappingSide.RIGHT}
|
||||
}
|
||||
}
|
||||
if(style['float'] == 'right'){
|
||||
return {
|
||||
horizontalPosition: {
|
||||
relative: HorizontalPositionRelativeFrom.COLUMN,
|
||||
align: HorizontalPositionAlign.RIGHT,
|
||||
},
|
||||
verticalPosition, margin,
|
||||
wrap: {type:TextWrappingType.SQUARE, side: TextWrappingSide.LEFT}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function get_align(node){
|
||||
let raw_style = node.getAttribute('style');
|
||||
if(raw_style == null) return AlignmentType.LEFT;
|
||||
let style = {};
|
||||
for(let pair of raw_style.split(';')){
|
||||
if(pair.split(':').length != 2) continue;
|
||||
let key = pair.split(':')[0].trim();
|
||||
let value = pair.split(':')[1].trim();
|
||||
style[key] = value;
|
||||
}
|
||||
switch (style['text-align']) {
|
||||
case 'left':
|
||||
return AlignmentType.LEFT;
|
||||
case 'center':
|
||||
return AlignmentType.CENTER;
|
||||
case 'justify':
|
||||
return AlignmentType.JUSTIFIED;
|
||||
case 'right':
|
||||
return AlignmentType.RIGHT;
|
||||
default:
|
||||
return AlignmentType.LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
2
src/lib/docx/index.js
Normal file
2
src/lib/docx/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { docx2html } from './docx2html';
|
||||
export { html2docx } from './html2docx';
|
||||
88
src/lib/finder.js
Normal file
88
src/lib/finder.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { hardDrive } from './store';
|
||||
import { my_computer} from './system';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
let computer = my_computer.map(el => get(hardDrive)[el]);
|
||||
let drives = computer.filter(item => item.type == 'drive' || item.type == 'removable_storage');
|
||||
|
||||
export function to_url(id){
|
||||
if(id == null || get(hardDrive)[id] == null) return null;
|
||||
let url = '';
|
||||
let current_location = get(hardDrive)[id];
|
||||
url = current_location.name + '\\' + url;
|
||||
|
||||
if(current_location.parent == null) return url;
|
||||
|
||||
do {
|
||||
|
||||
current_location = get(hardDrive)[current_location.parent];
|
||||
url = current_location.name + '\\' + url;
|
||||
|
||||
console.log(current_location);
|
||||
} while (current_location.parent != null && current_location.parent.length != 0)
|
||||
|
||||
if(url[url.length - 1] == '\\'){
|
||||
url = url.slice(0, url.length-1);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
export function to_id_nocase(url){
|
||||
if(url == null || url.trim().length == 0) return null;
|
||||
|
||||
let path_components = url.split('\\').filter(item => item.trim().length > 0).map(item => item.trim());
|
||||
if(path_components.length == 0) return null;
|
||||
|
||||
let drive = drives.find(item => item.name.toLowerCase() == path_components[0].toLowerCase());
|
||||
|
||||
if(drive == null) return null;
|
||||
if(path_components.length == 1) return drive.id;
|
||||
|
||||
drive = get(hardDrive)[drive.id];
|
||||
|
||||
let current_location = drive;
|
||||
for(let i = 1; i < path_components.length; i++){
|
||||
console.log(i);
|
||||
console.log(path_components[i]);
|
||||
current_location = [
|
||||
...current_location.files.map(id => get(hardDrive)[id]),
|
||||
...current_location.folders.map(id => get(hardDrive)[id])
|
||||
]
|
||||
.find(item => item?.name?.toLowerCase() == path_components[i].toLowerCase());
|
||||
console.log(current_location);
|
||||
if(current_location == null) return null;
|
||||
if(i == path_components.length - 1) return current_location.id;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function to_id(url){
|
||||
if(url == null || url.trim().length == 0) return null;
|
||||
|
||||
let path_components = url.split('\\').filter(item => item.trim().length > 0).map(item => item.trim());
|
||||
console.log(path_components);
|
||||
if(path_components.length == 0) return null;
|
||||
|
||||
let drive = drives.find(item => item.name == path_components[0]);
|
||||
|
||||
if(drive == null) return null;
|
||||
if(path_components.length == 1) return drive.id;
|
||||
|
||||
drive = get(hardDrive)[drive.id];
|
||||
|
||||
let current_location = drive;
|
||||
for(let i = 1; i < path_components.length; i++){
|
||||
console.log(i);
|
||||
console.log(path_components[i]);
|
||||
current_location = [
|
||||
...current_location.files.map(id => get(hardDrive)[id]),
|
||||
...current_location.folders.map(id => get(hardDrive)[id])
|
||||
]
|
||||
.find(item => item?.name == path_components[i]);
|
||||
console.log(current_location);
|
||||
if(current_location == null) return null;
|
||||
if(i == path_components.length - 1) return current_location.id;
|
||||
}
|
||||
|
||||
}
|
||||
399
src/lib/fs.js
Normal file
399
src/lib/fs.js
Normal file
@@ -0,0 +1,399 @@
|
||||
import { queueProgram, clipboard, selectingItems, hardDrive, clipboard_op } from './store';
|
||||
import { recycle_bin_id, protected_items } from './system';
|
||||
import * as utils from './utils';
|
||||
import { get } from 'svelte/store';
|
||||
import short from 'short-uuid';
|
||||
import * as util from './utils';
|
||||
import * as idb from 'idb-keyval';
|
||||
import * as finder from './finder';
|
||||
import {Buffer} from 'buffer';
|
||||
|
||||
export function copy(){
|
||||
clipboard_op.set('copy');
|
||||
clipboard.set(get(selectingItems));
|
||||
console.log('copy');
|
||||
}
|
||||
|
||||
export function cut(){
|
||||
clipboard_op.set('cut');
|
||||
clipboard.set(get(selectingItems));
|
||||
console.log('cut');
|
||||
}
|
||||
|
||||
export function paste(id, new_id=null){
|
||||
console.log('paste to', id);
|
||||
console.log('clipboard_op', get(clipboard_op));
|
||||
console.log(get(hardDrive)[id]);
|
||||
if(get(hardDrive)[id] == null || get(hardDrive)[id].type == 'file'){
|
||||
console.log('target is not a dir');
|
||||
return;
|
||||
}
|
||||
|
||||
if(get(clipboard).length == 0){
|
||||
console.log('clipboard is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
for(let fs_id of get(clipboard)){
|
||||
clone_fs(fs_id, id, new_id);
|
||||
|
||||
if(get(clipboard_op) == 'cut'){
|
||||
del_fs(fs_id);
|
||||
}
|
||||
}
|
||||
|
||||
clipboard_op.set('copy')
|
||||
clipboard.set([]);
|
||||
}
|
||||
|
||||
export function del_fs(id){
|
||||
if(protected_items.includes(id)){
|
||||
console.log(id, 'is protected');
|
||||
return;
|
||||
}
|
||||
let obj = get(hardDrive)[id];
|
||||
|
||||
let child_ids = [
|
||||
...obj.files,
|
||||
...obj.folders
|
||||
]
|
||||
if(get(hardDrive)[obj.parent] != null){
|
||||
console.log('delete from parent', obj.parent)
|
||||
|
||||
hardDrive.update(data => {
|
||||
data[obj.parent].files = data[obj.parent].files.filter(el => el != obj.id);
|
||||
data[obj.parent].folders = data[obj.parent].folders.filter(el => el != obj.id);
|
||||
return data;
|
||||
})
|
||||
}
|
||||
|
||||
hardDrive.update(data => {
|
||||
delete data[id];
|
||||
return data;
|
||||
})
|
||||
|
||||
for(let child_id of child_ids){
|
||||
del_fs(child_id);
|
||||
}
|
||||
}
|
||||
|
||||
export function clone_fs(obj_current_id, parent_id, new_id=null){
|
||||
let obj = {...get(hardDrive)[obj_current_id]};
|
||||
|
||||
if(new_id == null){
|
||||
obj.id = short.generate();
|
||||
} else {
|
||||
obj.id = new_id;
|
||||
}
|
||||
|
||||
obj.parent = parent_id;
|
||||
|
||||
let parent_items_names = [
|
||||
...get(hardDrive)[parent_id].files.map(el => get(hardDrive)[el].name),
|
||||
...get(hardDrive)[parent_id].folders.map(el => get(hardDrive)[el].name),
|
||||
]
|
||||
let appendix = 2;
|
||||
let basename = obj.basename;
|
||||
while(parent_items_names.includes(basename + obj.ext)){
|
||||
basename = obj.basename + ' ' + appendix;
|
||||
appendix++;
|
||||
}
|
||||
obj.basename = basename;
|
||||
obj.name = basename + obj.ext;
|
||||
|
||||
//backup files & folders
|
||||
console.log(obj)
|
||||
let files = [...obj.files];
|
||||
let folders = [...obj.folders];
|
||||
obj.files = [];
|
||||
obj.folders = [];
|
||||
|
||||
//save to hard drive
|
||||
hardDrive.update(data => {
|
||||
data[obj.id] = obj;
|
||||
return data;
|
||||
})
|
||||
console.log('cloning', obj.id)
|
||||
|
||||
if(obj.type == 'file'){
|
||||
|
||||
hardDrive.update(data => {
|
||||
data[parent_id].files.push(obj.id);
|
||||
return data;
|
||||
})
|
||||
} else if(obj.type == 'folder'){
|
||||
hardDrive.update(data => {
|
||||
data[parent_id].folders.push(obj.id);
|
||||
return data;
|
||||
})
|
||||
}
|
||||
|
||||
//recursively clone child items
|
||||
for(let child of [...files, ...folders]){
|
||||
clone_fs(child, obj.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function new_fs_item(type, ext, seedname, parent_id, file=null){
|
||||
if(type == null || seedname == null || parent_id == null){
|
||||
return;
|
||||
}
|
||||
|
||||
let item = {
|
||||
"id": short.generate(),
|
||||
"type": type,
|
||||
"path": "",
|
||||
"name": "",
|
||||
"storage_type": "local",
|
||||
"url": short.generate(),
|
||||
"ext": ext,
|
||||
"level": 0,
|
||||
"parent": parent_id,
|
||||
"size": 1,
|
||||
"files": [],
|
||||
"folders": [],
|
||||
"basename": ""
|
||||
}
|
||||
|
||||
let files = get(hardDrive)[parent_id].files.map(el => get(hardDrive)[el]);
|
||||
let folders = get(hardDrive)[parent_id].folders.map(el => get(hardDrive)[el]);
|
||||
|
||||
let parent_items_names = [
|
||||
...files.map(el => el.name),
|
||||
...folders.map(el => el.name)
|
||||
]
|
||||
|
||||
let appendix = 2;
|
||||
seedname = utils.sanitize_filename(seedname);
|
||||
let basename = seedname;
|
||||
while(parent_items_names.includes(basename + ext)){
|
||||
basename = seedname + ' ' + appendix;
|
||||
appendix++;
|
||||
}
|
||||
item.basename = basename;
|
||||
item.name = basename + item.ext;
|
||||
|
||||
if(file != null){
|
||||
await idb.set(item.url, file);
|
||||
item.size = Math.ceil(file.size/1024);
|
||||
|
||||
} else if(type == 'file'){
|
||||
console.log('fetch empty file')
|
||||
file = await file_from_url(`/empty/empty${item.ext}`, item.name);
|
||||
await idb.set(item.url, file);
|
||||
item.size = Math.ceil(file.size/1024);
|
||||
|
||||
} else {
|
||||
item.url = '';
|
||||
}
|
||||
|
||||
|
||||
hardDrive.update(data => {
|
||||
data[item.id] = item;
|
||||
return data;
|
||||
})
|
||||
if(type == 'file'){
|
||||
hardDrive.update(data => {
|
||||
data[parent_id].files.push(item.id);
|
||||
return data;
|
||||
})
|
||||
} else if (type == 'folder'){
|
||||
hardDrive.update(data => {
|
||||
data[parent_id].folders.push(item.id);
|
||||
return data;
|
||||
})
|
||||
}
|
||||
|
||||
return item.id;
|
||||
}
|
||||
|
||||
export async function new_fs_item_raw(item, parent_id){
|
||||
if(parent_id == null){
|
||||
return;
|
||||
}
|
||||
item.id = short.generate();
|
||||
item.parent = parent_id;
|
||||
|
||||
if(!['file', 'folder'].includes(item.type)){
|
||||
item.type = 'file';
|
||||
}
|
||||
if(item.storage_type == null){
|
||||
item.storage_type = 'local'
|
||||
}
|
||||
if(item.ext == null){
|
||||
item.ext = '';
|
||||
}
|
||||
if(item.icon == null){
|
||||
item.icon = '/images/xp/icons/ApplicationWindow.png'
|
||||
}
|
||||
if(item.files == null){
|
||||
item.files = [];
|
||||
}
|
||||
if(item.folders == null){
|
||||
item.folders = [];
|
||||
}
|
||||
|
||||
let files = get(hardDrive)[parent_id].files.map(el => get(hardDrive)[el]);
|
||||
let folders = get(hardDrive)[parent_id].folders.map(el => get(hardDrive)[el]);
|
||||
|
||||
let parent_items_names = [
|
||||
...files.map(el => el.name),
|
||||
...folders.map(el => el.name)
|
||||
]
|
||||
|
||||
let appendix = 2;
|
||||
let seedname = utils.sanitize_filename(item.basename);
|
||||
let basename = seedname;
|
||||
while(parent_items_names.includes(basename + item.ext)){
|
||||
basename = seedname + ' ' + appendix;
|
||||
appendix++;
|
||||
}
|
||||
item.basename = basename;
|
||||
item.name = basename + item.ext;
|
||||
|
||||
if(item.file != null){
|
||||
item.url = short.generate();
|
||||
await idb.set(item.url, item.file);
|
||||
item.size = Math.ceil(file.size/1024);
|
||||
delete item.file;
|
||||
} else if(item.executable){
|
||||
item.url = './programs/webapp.svelte';
|
||||
}
|
||||
|
||||
|
||||
hardDrive.update(data => {
|
||||
data[item.id] = item;
|
||||
return data;
|
||||
})
|
||||
if(item.type == 'file'){
|
||||
hardDrive.update(data => {
|
||||
data[parent_id].files.push(item.id);
|
||||
return data;
|
||||
})
|
||||
} else if (item.type == 'folder'){
|
||||
hardDrive.update(data => {
|
||||
data[parent_id].folders.push(item.id);
|
||||
return data;
|
||||
})
|
||||
}
|
||||
|
||||
return item.id;
|
||||
}
|
||||
|
||||
export function get_path(id){
|
||||
return finder.to_url(id);
|
||||
}
|
||||
|
||||
export async function save_file(fs_id, file){
|
||||
if(get(hardDrive)[fs_id] == null){
|
||||
console.log(fs_id, 'not exist');
|
||||
return;
|
||||
}
|
||||
let url = short.generate();
|
||||
await idb.set(url, file);
|
||||
hardDrive.update(data => {
|
||||
data[fs_id].url = url;
|
||||
data[fs_id].storage_type = 'local';
|
||||
return data;
|
||||
})
|
||||
}
|
||||
|
||||
export async function save_file_as(basename, ext, file, parent_id, new_id=null){
|
||||
ext = ext.toLowerCase();
|
||||
if(util.extname(basename) == ext){
|
||||
basename = util.basename(basename, ext);
|
||||
}
|
||||
|
||||
let url = short.generate();
|
||||
await idb.set(url, file);
|
||||
|
||||
if(new_id == null){
|
||||
new_id = short.generate();
|
||||
}
|
||||
|
||||
let obj = {
|
||||
"id": new_id,
|
||||
"type": 'file',
|
||||
"path": "",
|
||||
"name": basename + ext,
|
||||
"storage_type": "local",
|
||||
"url": url,
|
||||
"ext": ext,
|
||||
"level": 0,
|
||||
"parent": parent_id,
|
||||
"size": Math.round(file.size/1024),
|
||||
"files": [],
|
||||
"folders": [],
|
||||
"basename": basename
|
||||
}
|
||||
|
||||
let parent_items_names = [
|
||||
...get(hardDrive)[parent_id].files.map(el => get(hardDrive)[el].name),
|
||||
...get(hardDrive)[parent_id].folders.map(el => get(hardDrive)[el].name),
|
||||
]
|
||||
let appendix = 2;
|
||||
basename = obj.basename;
|
||||
while(parent_items_names.includes(basename + obj.ext)){
|
||||
basename = obj.basename + ' ' + appendix;
|
||||
appendix++;
|
||||
}
|
||||
obj.basename = basename;
|
||||
obj.name = basename + obj.ext;
|
||||
|
||||
|
||||
hardDrive.update(data => {
|
||||
data[obj.id] = obj;
|
||||
data[parent_id].files.push(obj.id);
|
||||
return data;
|
||||
})
|
||||
}
|
||||
|
||||
export async function get_file(id){
|
||||
let fs_item = get(hardDrive)[id];
|
||||
let file;
|
||||
if(fs_item.storage_type == 'remote'){
|
||||
file = await file_from_url(fs_item.url);
|
||||
} else if(fs_item.storage_type == 'local') {
|
||||
file = await idb.get(fs_item.url);
|
||||
console.log(file);
|
||||
}
|
||||
file = new File([file], fs_item.name, {type: file.type})
|
||||
return file;
|
||||
}
|
||||
|
||||
export async function get_url(id){
|
||||
let fs_item = get(hardDrive)[id];
|
||||
|
||||
if(fs_item.storage_type == 'remote'){
|
||||
return fs_item.url;
|
||||
} else if(fs_item.storage_type == 'local') {
|
||||
let file = await idb.get(fs_item.url);
|
||||
return URL.createObjectURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
export async function file_from_url(url, name, defaultType = 'image/jpeg'){
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const data = await response.blob();
|
||||
return new File([data], name, {
|
||||
type: data.type || defaultType,
|
||||
});
|
||||
} catch (error) {
|
||||
return new File([''], 'empty.txt', {
|
||||
type: 'text/plain'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function array_buffer_from_url(url){
|
||||
let file = await file_from_url(url);
|
||||
return await file.arrayBuffer();
|
||||
}
|
||||
|
||||
export async function buffer_from_url(url){
|
||||
let array_buffer = await array_buffer_from_url(url);
|
||||
console.log(array_buffer)
|
||||
return Buffer.from(array_buffer);
|
||||
}
|
||||
18
src/lib/libarchive.js/.eslintrc.js
Normal file
18
src/lib/libarchive.js/.eslintrc.js
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
}
|
||||
};
|
||||
1
src/lib/libarchive.js/.gitignore
vendored
Normal file
1
src/lib/libarchive.js/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
3
src/lib/libarchive.js/.travis.yml
Normal file
3
src/lib/libarchive.js/.travis.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "node"
|
||||
15
src/lib/libarchive.js/dist/wasm-gen/libarchive.js
vendored
Normal file
15
src/lib/libarchive.js/dist/wasm-gen/libarchive.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
src/lib/libarchive.js/dist/wasm-gen/libarchive.wasm
vendored
Normal file
BIN
src/lib/libarchive.js/dist/wasm-gen/libarchive.wasm
vendored
Normal file
Binary file not shown.
1
src/lib/libarchive.js/dist/worker-bundle.js
vendored
Normal file
1
src/lib/libarchive.js/dist/worker-bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
180
src/lib/libarchive.js/jest.config.js
Normal file
180
src/lib/libarchive.js/jest.config.js
Normal file
@@ -0,0 +1,180 @@
|
||||
// For a detailed explanation regarding each configuration property, visit:
|
||||
// https://jestjs.io/docs/en/configuration.html
|
||||
|
||||
module.exports = {
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// Stop running tests after the first failure
|
||||
// bail: false,
|
||||
|
||||
// Respect "browser" field in package.json when resolving modules
|
||||
// browser: false,
|
||||
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "C:\\Users\\Nika\\AppData\\Local\\Temp\\jest",
|
||||
|
||||
// Automatically clear mock calls and instances between every test
|
||||
// clearMocks: false,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
// collectCoverage: false,
|
||||
|
||||
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||
// collectCoverageFrom: null,
|
||||
|
||||
// The directory where Jest should output its coverage files
|
||||
// coverageDirectory: null,
|
||||
|
||||
// An array of regexp pattern strings used to skip coverage collection
|
||||
// coveragePathIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\"
|
||||
// ],
|
||||
|
||||
// A list of reporter names that Jest uses when writing coverage reports
|
||||
// coverageReporters: [
|
||||
// "json",
|
||||
// "text",
|
||||
// "lcov",
|
||||
// "clover"
|
||||
// ],
|
||||
|
||||
// An object that configures minimum threshold enforcement for coverage results
|
||||
// coverageThreshold: null,
|
||||
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// Force coverage collection from ignored files usin a array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
// A path to a module which exports an async function that is triggered once before all test suites
|
||||
// globalSetup: null,
|
||||
|
||||
// A path to a module which exports an async function that is triggered once after all test suites
|
||||
// globalTeardown: null,
|
||||
|
||||
// A set of global variables that need to be available in all test environments
|
||||
// globals: {},
|
||||
|
||||
// An array of directory names to be searched recursively up from the requiring module's location
|
||||
// moduleDirectories: [
|
||||
// "node_modules"
|
||||
// ],
|
||||
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "json",
|
||||
// "jsx",
|
||||
// "node"
|
||||
// ],
|
||||
|
||||
// A map from regular expressions to module names that allow to stub out resources with a single module
|
||||
// moduleNameMapper: {},
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
// modulePathIgnorePatterns: [],
|
||||
|
||||
// Activates notifications for test results
|
||||
// notify: false,
|
||||
|
||||
// An enum that specifies notification mode. Requires { notify: true }
|
||||
// notifyMode: "always",
|
||||
|
||||
// A preset that is used as a base for Jest's configuration
|
||||
// preset: null,
|
||||
|
||||
// Run tests from one or more projects
|
||||
// projects: null,
|
||||
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state between every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
// resolver: null,
|
||||
|
||||
// Automatically restore mock state between every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
// rootDir: null,
|
||||
|
||||
// A list of paths to directories that Jest should use to search for files in
|
||||
// roots: [
|
||||
// "<rootDir>"
|
||||
// ],
|
||||
|
||||
// Allows you to use a custom runner instead of Jest's default test runner
|
||||
// runner: "jest-runner",
|
||||
|
||||
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||
// setupFiles: [],
|
||||
|
||||
// The path to a module that runs some code to configure or set up the testing framework before each test
|
||||
// setupTestFrameworkScriptFile: null,
|
||||
|
||||
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
testEnvironment: "node",
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
|
||||
// Adds a location field to test results
|
||||
// testLocationInResults: false,
|
||||
|
||||
// The glob patterns Jest uses to detect test files
|
||||
testMatch: [
|
||||
//"**/test/**/*.js?(x)",
|
||||
"**/test/**/?(*.)+(spec|test).js?(x)"
|
||||
],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||
// testPathIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\"
|
||||
// ],
|
||||
|
||||
// The regexp pattern Jest uses to detect test files
|
||||
// testRegex: "",
|
||||
|
||||
// This option allows the use of a custom results processor
|
||||
// testResultsProcessor: null,
|
||||
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jasmine2",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: null,
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
// "\\\\node_modules\\\\"
|
||||
// ],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
// Indicates whether each individual test should be reported during the run
|
||||
// verbose: null,
|
||||
|
||||
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||
// watchPathIgnorePatterns: [],
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
};
|
||||
BIN
src/lib/libarchive.js/lib/build/libarchive.a
Normal file
BIN
src/lib/libarchive.js/lib/build/libarchive.a
Normal file
Binary file not shown.
15
src/lib/libarchive.js/lib/build/libarchive.js
Normal file
15
src/lib/libarchive.js/lib/build/libarchive.js
Normal file
File diff suppressed because one or more lines are too long
BIN
src/lib/libarchive.js/lib/build/libarchive.wasm
Normal file
BIN
src/lib/libarchive.js/lib/build/libarchive.wasm
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/lib/build/main.o
Normal file
BIN
src/lib/libarchive.js/lib/build/main.o
Normal file
Binary file not shown.
11
src/lib/libarchive.js/lib/tools/build.sh
Normal file
11
src/lib/libarchive.js/lib/tools/build.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
emcc ../wrapper/main.c -I /usr/local/include/ -o ../build/main.o #-g4
|
||||
|
||||
emcc ../build/main.o /usr/local/lib/libarchive.a /usr/local/lib/liblzma.a /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a \
|
||||
-o ../build/libarchive.js \
|
||||
-s USE_ZLIB=1 -s USE_BZIP2=1 -s MODULARIZE=1 -s EXPORT_ES6=1 -s EXPORT_NAME=libarchive -s WASM=1 -O3 -s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap","allocate","intArrayFromString"]' -s EXPORTED_FUNCTIONS=@$PWD/lib.exports -s ERROR_ON_UNDEFINED_SYMBOLS=0
|
||||
|
||||
cp ../build/libarchive.js ../../src/webworker/wasm-gen/
|
||||
cp ../build/libarchive.wasm ../../src/webworker/wasm-gen/
|
||||
|
||||
echo Done
|
||||
51
src/lib/libarchive.js/lib/tools/docker/Dockerfile
Normal file
51
src/lib/libarchive.js/lib/tools/docker/Dockerfile
Normal file
@@ -0,0 +1,51 @@
|
||||
FROM trzeci/emscripten
|
||||
|
||||
WORKDIR /opt
|
||||
|
||||
ADD https://github.com/libarchive/libarchive/releases/download/v3.4.0/libarchive-3.4.0.zip /opt
|
||||
ADD https://github.com/madler/zlib/archive/v1.2.11.zip /opt
|
||||
ADD https://netix.dl.sourceforge.net/project/lzmautils/xz-5.2.4.tar.gz /opt
|
||||
ADD https://netix.dl.sourceforge.net/project/bzip2/bzip2-1.0.6.tar.gz /opt
|
||||
ADD https://www.openssl.org/source/openssl-1.0.2s.tar.gz /opt
|
||||
|
||||
RUN unzip /opt/libarchive-3.4.0.zip && rm /opt/libarchive-3.4.0.zip && \
|
||||
unzip /opt/v1.2.11.zip && rm /opt/v1.2.11.zip && \
|
||||
tar xf /opt/xz-5.2.4.tar.gz && rm /opt/xz-5.2.4.tar.gz && \
|
||||
tar xf /opt/bzip2-1.0.6.tar.gz && rm /opt/bzip2-1.0.6.tar.gz && \
|
||||
tar xf /opt/openssl-1.0.2s.tar.gz && rm /opt/openssl-1.0.2s.tar.gz
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y locate vim file
|
||||
|
||||
ENV CPPFLAGS "-I/usr/local/include/ -I/opt/zlib-1.2.11 -I/opt/bzip2-1.0.6 -I/opt/openssl-1.0.2s/include -I/opt/openssl-1.0.2s/test"
|
||||
ENV LDLIBS "-lz -lssl -lcrypto"
|
||||
ENV LDFLAGS "-L/usr/local/lib"
|
||||
|
||||
# compile openSSL to LLVM
|
||||
WORKDIR /opt/openssl-1.0.2s
|
||||
RUN cd /opt/openssl-1.0.2s && emmake bash -c "./Configure -no-asm -no-apps no-ssl2 no-ssl3 no-hw no-deprecated shared no-dso linux-generic32" && \
|
||||
sed -i 's/CC= $(CROSS_COMPILE)\/emsdk_portable\/sdk\/emcc/CC= $(CROSS_COMPILE)cc/' Makefile && \
|
||||
emmake make && \
|
||||
cd /usr/local/lib && \
|
||||
ln -s /opt/openssl-1.0.2s/libssl.a && \
|
||||
ln -s /opt/openssl-1.0.2s/libcrypto.a
|
||||
|
||||
# compile LZMA to LLVM
|
||||
WORKDIR /opt/xz-5.2.4
|
||||
RUN cd /opt/xz-5.2.4 && emconfigure ./configure --disable-assembler --enable-threads=no --enable-static=yes 2>&1 | tee conf.out && \
|
||||
emmake make 2>&1 | tee make.out && emmake make install
|
||||
|
||||
# compile libarchive to LLVM
|
||||
WORKDIR /opt/libarchive-3.4.0
|
||||
RUN cd /opt/libarchive-3.4.0 && emconfigure ./configure --enable-static --disable-shared --enable-bsdtar=static --enable-bsdcat=static \
|
||||
--enable-bsdcpio=static --enable-posix-regex-lib=libc \
|
||||
--disable-xattr --disable-acl --without-nettle --without-lzo2 \
|
||||
--without-cng --without-lz4 \
|
||||
--without-xml2 --without-expat 2>&1 | tee conf.out && \
|
||||
emmake make 2>&1 | tee make.out && emmake make install
|
||||
|
||||
#--without-openssl
|
||||
#--without-bz2lib --without-iconv --without-libiconv-prefix --without-lzma
|
||||
|
||||
WORKDIR /var/local/lib/tools
|
||||
CMD ["bash","/var/local/lib/tools/build.sh"]
|
||||
18
src/lib/libarchive.js/lib/tools/lib.exports
Normal file
18
src/lib/libarchive.js/lib/tools/lib.exports
Normal file
@@ -0,0 +1,18 @@
|
||||
[
|
||||
"_get_version",
|
||||
"_archive_open",
|
||||
"_get_next_entry",
|
||||
"_get_filedata",
|
||||
"_archive_close",
|
||||
"_archive_entry_filetype",
|
||||
"_archive_entry_pathname",
|
||||
"_archive_entry_pathname_utf8",
|
||||
"_archive_entry_size",
|
||||
"_archive_read_data_skip",
|
||||
"_archive_error_string",
|
||||
"_archive_entry_is_encrypted",
|
||||
"_archive_read_has_encrypted_entries",
|
||||
"_archive_read_add_passphrase",
|
||||
"_free",
|
||||
"_malloc"
|
||||
]
|
||||
2
src/lib/libarchive.js/lib/tools/wasm_gen.sh
Normal file
2
src/lib/libarchive.js/lib/tools/wasm_gen.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
if [ ! -f "./package.json" ]; then echo "you should run this from project root"; exit 1; fi
|
||||
docker run -it -v `pwd`:/var/local libarchive-llvm
|
||||
128
src/lib/libarchive.js/lib/wrapper/main.c
Normal file
128
src/lib/libarchive.js/lib/wrapper/main.c
Normal file
@@ -0,0 +1,128 @@
|
||||
#define LIBARCHIVE_STATIC
|
||||
//#include "emscripten.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
#define EMSCRIPTEN_KEEPALIVE
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
const char * get_version(){
|
||||
return archive_version_string();
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void* archive_open( const void *buf, size_t size, const char * passphrase ){
|
||||
struct archive *a;
|
||||
int r;
|
||||
|
||||
a = archive_read_new();
|
||||
archive_read_support_filter_all(a);
|
||||
archive_read_support_format_all(a);
|
||||
|
||||
if( passphrase ){
|
||||
archive_read_add_passphrase(a, passphrase);
|
||||
}
|
||||
|
||||
r = archive_read_open_memory(a, buf, size);
|
||||
if (r != ARCHIVE_OK){
|
||||
fprintf(stderr, "Memory read error %d\n",r);
|
||||
fprintf(stderr, "%s\n",archive_error_string(a));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
const void* get_next_entry(void *archive){
|
||||
struct archive_entry *entry;
|
||||
if( archive_read_next_header(archive,&entry) == ARCHIVE_OK ){
|
||||
return entry;
|
||||
}else{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void* get_filedata(void *archive,size_t buffsize){
|
||||
void *buff = malloc( buffsize );
|
||||
int read_size = archive_read_data(archive,buff,buffsize);
|
||||
if( read_size < 0 ){
|
||||
fprintf(stderr, "Error occured while reading file");
|
||||
return (void*) read_size;
|
||||
}else{
|
||||
return buff;
|
||||
}
|
||||
}
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
void archive_close( void *archive ){
|
||||
int r = archive_read_free(archive);
|
||||
if (r != ARCHIVE_OK){
|
||||
fprintf(stderr, "Error read free %d\n",r);
|
||||
fprintf(stderr, "%s\n",archive_error_string(archive));
|
||||
}
|
||||
}
|
||||
/*
|
||||
#define MAXBUFLEN 1000000
|
||||
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
int main(){
|
||||
char source[MAXBUFLEN + 1];
|
||||
FILE *fp = fopen("addon.zip", "r");
|
||||
if (fp != NULL) {
|
||||
size_t newLen = fread(source, sizeof(char), MAXBUFLEN, fp);
|
||||
if ( ferror( fp ) != 0 ) {
|
||||
printf("Error reading file");
|
||||
} else {
|
||||
source[newLen++] = '\0';
|
||||
void* arch = archive_open(source,newLen);
|
||||
printf("arch: %d",arch);
|
||||
void* entry = get_next_entry(arch);
|
||||
size_t fsize = archive_entry_size(entry);
|
||||
void* file = get_filedata(arch,fsize);
|
||||
printf("file: %d",file);
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
}*/
|
||||
|
||||
/*
|
||||
EMSCRIPTEN_KEEPALIVE
|
||||
char* list_files( const void * buf, size_t size ){
|
||||
|
||||
printf("list_files start\n");
|
||||
struct archive *a;
|
||||
struct archive_entry *entry;
|
||||
int r;
|
||||
char* fname = NULL;
|
||||
const char* tmp;
|
||||
printf("variables initialized\n");
|
||||
a = archive_read_new();
|
||||
archive_read_support_filter_all(a);
|
||||
archive_read_support_format_all(a);
|
||||
printf("libarchive initialized\n");
|
||||
r = archive_read_open_memory(a, buf, size);
|
||||
if (r != ARCHIVE_OK){
|
||||
printf("Memory read error %d\n",r);
|
||||
printf("%s\n",archive_error_string(a));
|
||||
exit(1);
|
||||
}
|
||||
printf("start read\n");
|
||||
while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
|
||||
tmp = archive_entry_pathname(entry);
|
||||
free(fname);
|
||||
fname = malloc(strlen(tmp));
|
||||
strcpy(fname,tmp);
|
||||
archive_read_data_skip(a);
|
||||
}
|
||||
printf("finish read\n");
|
||||
r = archive_read_free(a);
|
||||
if (r != ARCHIVE_OK){
|
||||
printf("Error read free %d\n",r);
|
||||
printf("%s\n",archive_error_string(a));
|
||||
exit(1);
|
||||
}
|
||||
return fname;
|
||||
}
|
||||
*/
|
||||
2
src/lib/libarchive.js/main.js
Normal file
2
src/lib/libarchive.js/main.js
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
export { Archive } from './src/libarchive.js';
|
||||
19
src/lib/libarchive.js/rollup.config.js
Normal file
19
src/lib/libarchive.js/rollup.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import copy from 'rollup-plugin-copy-assets';
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
|
||||
export default {
|
||||
input: 'src/webworker/worker.js',
|
||||
output: [
|
||||
{
|
||||
file: 'dist/worker-bundle.js',
|
||||
format: 'iife'
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
copy({
|
||||
assets: [
|
||||
'./src/webworker/wasm-gen'
|
||||
],
|
||||
}),
|
||||
].concat( process.env.BUILD === 'production' ? [terser()] : [] ),
|
||||
};
|
||||
35
src/lib/libarchive.js/src/compressed-file.js
Normal file
35
src/lib/libarchive.js/src/compressed-file.js
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
/**
|
||||
* Represents compressed file before extraction
|
||||
*/
|
||||
export class CompressedFile{
|
||||
|
||||
constructor(name,size,path,archiveRef){
|
||||
this._name = name;
|
||||
this._size = size;
|
||||
this._path = path;
|
||||
this._archiveRef = archiveRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* file name
|
||||
*/
|
||||
get name(){
|
||||
return this._name;
|
||||
}
|
||||
/**
|
||||
* file size
|
||||
*/
|
||||
get size(){
|
||||
return this._size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract file from archive
|
||||
* @returns {Promise<File>} extracted file
|
||||
*/
|
||||
extract(){
|
||||
return this._archiveRef.extractSingleFile(this._path);
|
||||
}
|
||||
|
||||
}
|
||||
225
src/lib/libarchive.js/src/libarchive.js
Normal file
225
src/lib/libarchive.js/src/libarchive.js
Normal file
@@ -0,0 +1,225 @@
|
||||
import { CompressedFile } from "./compressed-file.js";
|
||||
|
||||
|
||||
export class Archive{
|
||||
|
||||
/**
|
||||
* Initialize libarchivejs
|
||||
* @param {Object} options
|
||||
*/
|
||||
static init(options = {}){
|
||||
Archive._options = {
|
||||
workerUrl: '../dist/worker-bundle.js',
|
||||
...options
|
||||
};
|
||||
return Archive._options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new archive instance from browser native File object
|
||||
* @param {File} file
|
||||
* @param {object} options
|
||||
* @returns {Archive}
|
||||
*/
|
||||
static open(file, options = null){
|
||||
options = options ||
|
||||
Archive._options ||
|
||||
Archive.init() && console.warn('Automatically initializing using options: ', Archive._options);
|
||||
const arch = new Archive(file,options);
|
||||
return arch.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new archive
|
||||
* @param {File} file
|
||||
* @param {Object} options
|
||||
*/
|
||||
constructor(file,options){
|
||||
this._worker = new Worker(options.workerUrl);
|
||||
this._worker.addEventListener('message', this._workerMsg.bind(this));
|
||||
this._callbacks = [];
|
||||
this._content = {};
|
||||
this._processed = 0;
|
||||
this._file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares file for reading
|
||||
* @returns {Promise<Archive>} archive instance
|
||||
*/
|
||||
async open(){
|
||||
await this._postMessage({type: 'HELLO'},(resolve,reject,msg) => {
|
||||
if( msg.type === 'READY' ){
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
return await this._postMessage({type: 'OPEN', file: this._file}, (resolve,reject,msg) => {
|
||||
if(msg.type === 'OPENED'){
|
||||
resolve(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* detect if archive has encrypted data
|
||||
* @returns {boolean|null} null if could not be determined
|
||||
*/
|
||||
hasEncryptedData(){
|
||||
return this._postMessage({type: 'CHECK_ENCRYPTION'},
|
||||
(resolve,reject,msg) => {
|
||||
if( msg.type === 'ENCRYPTION_STATUS' ){
|
||||
resolve(msg.status);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* set password to be used when reading archive
|
||||
*/
|
||||
usePassword(archivePassword){
|
||||
return this._postMessage({type: 'SET_PASSPHRASE', passphrase: archivePassword},
|
||||
(resolve,reject,msg) => {
|
||||
if( msg.type === 'PASSPHRASE_STATUS' ){
|
||||
resolve(msg.status);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns object containing directory structure and file information
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
getFilesObject(){
|
||||
if( this._processed > 0 ){
|
||||
return Promise.resolve().then( () => this._content );
|
||||
}
|
||||
return this._postMessage({type: 'LIST_FILES'}, (resolve,reject,msg) => {
|
||||
if( msg.type === 'ENTRY' ){
|
||||
const entry = msg.entry;
|
||||
const [ target, prop ] = this._getProp(this._content,entry.path);
|
||||
if( entry.type === 'FILE' ){
|
||||
target[prop] = new CompressedFile(entry.fileName,entry.size,entry.path,this);
|
||||
}
|
||||
return true;
|
||||
}else if( msg.type === 'END' ){
|
||||
this._processed = 1;
|
||||
resolve(this._cloneContent(this._content));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getFilesArray(){
|
||||
return this.getFilesObject().then( (obj) => {
|
||||
return this._objectToArray(obj);
|
||||
});
|
||||
}
|
||||
|
||||
extractSingleFile(target){
|
||||
return this._postMessage({type: 'EXTRACT_SINGLE_FILE', target: target},
|
||||
(resolve,reject,msg) => {
|
||||
if( msg.type === 'FILE' ){
|
||||
const file = new File([msg.entry.fileData], msg.entry.fileName, {
|
||||
type: 'application/octet-stream'
|
||||
});
|
||||
resolve(file);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns object containing directory structure and extracted File objects
|
||||
* @param {Function} extractCallback
|
||||
*
|
||||
*/
|
||||
extractFiles(extractCallback){
|
||||
if( this._processed > 1 ){
|
||||
return Promise.resolve().then( () => this._content );
|
||||
}
|
||||
return this._postMessage({type: 'EXTRACT_FILES'}, (resolve,reject,msg) => {
|
||||
if( msg.type === 'ENTRY' ){
|
||||
const [ target, prop ] = this._getProp(this._content,msg.entry.path);
|
||||
if( msg.entry.type === 'FILE' ){
|
||||
target[prop] = new File([msg.entry.fileData], msg.entry.fileName, {
|
||||
type: 'application/octet-stream'
|
||||
});
|
||||
if (extractCallback !== undefined) {
|
||||
setTimeout(extractCallback.bind(null,{
|
||||
file: target[prop],
|
||||
path: msg.entry.path,
|
||||
}));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}else if( msg.type === 'END' ){
|
||||
this._processed = 2;
|
||||
this._worker.terminate();
|
||||
resolve(this._cloneContent(this._content));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_cloneContent(obj){
|
||||
if( obj instanceof File || obj instanceof CompressedFile || obj === null ) return obj;
|
||||
const o = {};
|
||||
for( const prop of Object.keys(obj) ){
|
||||
o[prop] = this._cloneContent(obj[prop]);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
_objectToArray(obj,path = ''){
|
||||
const files = [];
|
||||
for( const key of Object.keys(obj) ){
|
||||
if( obj[key] instanceof File || obj[key] instanceof CompressedFile || obj[key] === null ){
|
||||
files.push({
|
||||
file: obj[key] || key,
|
||||
path: path
|
||||
});
|
||||
}else{
|
||||
files.push( ...this._objectToArray(obj[key],`${path}${key}/`) );
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
_getProp(obj,path){
|
||||
const parts = path.split('/');
|
||||
if( parts[parts.length -1] === '' ) parts.pop();
|
||||
let cur = obj, prev = null;
|
||||
for( const part of parts ){
|
||||
cur[part] = cur[part] || {};
|
||||
prev = cur;
|
||||
cur = cur[part];
|
||||
}
|
||||
return [ prev, parts[parts.length-1] ];
|
||||
}
|
||||
|
||||
_postMessage(msg,callback){
|
||||
this._worker.postMessage(msg);
|
||||
return new Promise((resolve,reject) => {
|
||||
this._callbacks.push( this._msgHandler.bind(this,callback,resolve,reject) );
|
||||
});
|
||||
}
|
||||
|
||||
_msgHandler(callback,resolve,reject,msg){
|
||||
if( msg.type === 'BUSY' ){
|
||||
reject('worker is busy');
|
||||
}else if( msg.type === 'ERROR' ){
|
||||
reject(msg.error);
|
||||
}else{
|
||||
return callback(resolve,reject,msg);
|
||||
}
|
||||
}
|
||||
|
||||
_workerMsg({data: msg}){
|
||||
const callback = this._callbacks[this._callbacks.length -1];
|
||||
const next = callback(msg);
|
||||
if( !next ){
|
||||
this._callbacks.pop();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
136
src/lib/libarchive.js/src/webworker/archive-reader.js
Normal file
136
src/lib/libarchive.js/src/webworker/archive-reader.js
Normal file
@@ -0,0 +1,136 @@
|
||||
|
||||
const TYPE_MAP = {
|
||||
32768: 'FILE',
|
||||
16384: 'DIR',
|
||||
40960: 'SYMBOLIC_LINK',
|
||||
49152: 'SOCKET',
|
||||
8192: 'CHARACTER_DEVICE',
|
||||
24576: 'BLOCK_DEVICE',
|
||||
4096: 'NAMED_PIPE',
|
||||
};
|
||||
|
||||
export class ArchiveReader{
|
||||
/**
|
||||
* archive reader
|
||||
* @param {WasmModule} wasmModule emscripten module
|
||||
*/
|
||||
constructor(wasmModule){
|
||||
this._wasmModule = wasmModule;
|
||||
this._runCode = wasmModule.runCode;
|
||||
this._file = null;
|
||||
this._passphrase = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* open archive, needs to closed manually
|
||||
* @param {File} file
|
||||
*/
|
||||
open(file){
|
||||
if( this._file !== null ){
|
||||
console.warn('Closing previous file');
|
||||
this.close();
|
||||
}
|
||||
const { promise, resolve, reject } = this._promiseHandles();
|
||||
this._file = file;
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => this._loadFile(reader.result,resolve,reject);
|
||||
reader.readAsArrayBuffer(file);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* close archive
|
||||
*/
|
||||
close(){
|
||||
this._runCode.closeArchive(this._archive);
|
||||
this._wasmModule._free(this._filePtr);
|
||||
this._file = null;
|
||||
this._filePtr = null;
|
||||
this._archive = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* detect if archive has encrypted data
|
||||
* @returns {boolean|null} null if could not be determined
|
||||
*/
|
||||
hasEncryptedData(){
|
||||
this._archive = this._runCode.openArchive( this._filePtr, this._fileLength, this._passphrase );
|
||||
this._runCode.getNextEntry(this._archive);
|
||||
const status = this._runCode.hasEncryptedEntries(this._archive);
|
||||
if( status === 0 ){
|
||||
return false;
|
||||
} else if( status > 0 ){
|
||||
return true;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set passphrase to be used with archive
|
||||
* @param {*} passphrase
|
||||
*/
|
||||
setPassphrase(passphrase){
|
||||
this._passphrase = passphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* get archive entries
|
||||
* @param {boolean} skipExtraction
|
||||
* @param {string} except don't skip this entry
|
||||
*/
|
||||
*entries(skipExtraction = false, except = null){
|
||||
this._archive = this._runCode.openArchive( this._filePtr, this._fileLength, this._passphrase );
|
||||
let entry;
|
||||
while( true ){
|
||||
entry = this._runCode.getNextEntry(this._archive);
|
||||
if( entry === 0 ) break;
|
||||
|
||||
const entryData = {
|
||||
size: this._runCode.getEntrySize(entry),
|
||||
path: this._runCode.getEntryName(entry),
|
||||
type: TYPE_MAP[this._runCode.getEntryType(entry)],
|
||||
ref: entry,
|
||||
};
|
||||
|
||||
if( entryData.type === 'FILE' ){
|
||||
let fileName = entryData.path.split('/');
|
||||
entryData.fileName = fileName[fileName.length - 1];
|
||||
}
|
||||
|
||||
if( skipExtraction && except !== entryData.path ){
|
||||
this._runCode.skipEntry(this._archive);
|
||||
}else{
|
||||
const ptr = this._runCode.getFileData(this._archive,entryData.size);
|
||||
if( ptr < 0 ){
|
||||
throw new Error(this._runCode.getError(this._archive));
|
||||
}
|
||||
entryData.fileData = this._wasmModule.HEAP8.slice(ptr,ptr+entryData.size);
|
||||
this._wasmModule._free(ptr);
|
||||
}
|
||||
yield entryData;
|
||||
}
|
||||
}
|
||||
|
||||
_loadFile(fileBuffer,resolve,reject){
|
||||
try{
|
||||
const array = new Uint8Array(fileBuffer);
|
||||
this._fileLength = array.length;
|
||||
this._filePtr = this._runCode.malloc(this._fileLength);
|
||||
this._wasmModule.HEAP8.set(array, this._filePtr);
|
||||
resolve();
|
||||
}catch(error){
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
|
||||
_promiseHandles(){
|
||||
let resolve = null,reject = null;
|
||||
const promise = new Promise((_resolve,_reject) => {
|
||||
resolve = _resolve;
|
||||
reject = _reject;
|
||||
});
|
||||
return { promise, resolve, reject };
|
||||
}
|
||||
|
||||
}
|
||||
15
src/lib/libarchive.js/src/webworker/wasm-gen/libarchive.js
Normal file
15
src/lib/libarchive.js/src/webworker/wasm-gen/libarchive.js
Normal file
File diff suppressed because one or more lines are too long
BIN
src/lib/libarchive.js/src/webworker/wasm-gen/libarchive.wasm
Normal file
BIN
src/lib/libarchive.js/src/webworker/wasm-gen/libarchive.wasm
Normal file
Binary file not shown.
97
src/lib/libarchive.js/src/webworker/wasm-module.js
Normal file
97
src/lib/libarchive.js/src/webworker/wasm-module.js
Normal file
@@ -0,0 +1,97 @@
|
||||
/* eslint-disable no-undef */
|
||||
import libarchive from './wasm-gen/libarchive.js';
|
||||
|
||||
export class WasmModule{
|
||||
constructor(){
|
||||
this.preRun = [];
|
||||
this.postRun = [];
|
||||
this.totalDependencies = 0;
|
||||
}
|
||||
|
||||
print(...text){
|
||||
console.log(text);
|
||||
}
|
||||
|
||||
printErr(...text){
|
||||
console.error(text);
|
||||
}
|
||||
|
||||
initFunctions(){
|
||||
this.runCode = {
|
||||
// const char * get_version()
|
||||
getVersion: this.cwrap('get_version', 'string', []),
|
||||
// void * archive_open( const void * buffer, size_t buffer_size)
|
||||
// retuns archive pointer
|
||||
openArchive: this.cwrap('archive_open', 'number', ['number','number','string']),
|
||||
// void * get_entry(void * archive)
|
||||
// return archive entry pointer
|
||||
getNextEntry: this.cwrap('get_next_entry', 'number', ['number']),
|
||||
// void * get_filedata( void * archive, size_t bufferSize )
|
||||
getFileData: this.cwrap('get_filedata', 'number', ['number','number']),
|
||||
// int archive_read_data_skip(struct archive *_a)
|
||||
skipEntry: this.cwrap('archive_read_data_skip', 'number', ['number']),
|
||||
// void archive_close( void * archive )
|
||||
closeArchive: this.cwrap('archive_close', null, ['number'] ),
|
||||
// la_int64_t archive_entry_size( struct archive_entry * )
|
||||
getEntrySize: this.cwrap('archive_entry_size', 'number', ['number']),
|
||||
// const char * archive_entry_pathname( struct archive_entry * )
|
||||
getEntryName: this.cwrap('archive_entry_pathname', 'string', ['number']),
|
||||
// __LA_MODE_T archive_entry_filetype( struct archive_entry * )
|
||||
/*
|
||||
#define AE_IFMT ((__LA_MODE_T)0170000)
|
||||
#define AE_IFREG ((__LA_MODE_T)0100000) // Regular file
|
||||
#define AE_IFLNK ((__LA_MODE_T)0120000) // Sybolic link
|
||||
#define AE_IFSOCK ((__LA_MODE_T)0140000) // Socket
|
||||
#define AE_IFCHR ((__LA_MODE_T)0020000) // Character device
|
||||
#define AE_IFBLK ((__LA_MODE_T)0060000) // Block device
|
||||
#define AE_IFDIR ((__LA_MODE_T)0040000) // Directory
|
||||
#define AE_IFIFO ((__LA_MODE_T)0010000) // Named pipe
|
||||
*/
|
||||
getEntryType: this.cwrap('archive_entry_filetype', 'number', ['number']),
|
||||
// const char * archive_error_string(struct archive *);
|
||||
getError: this.cwrap('archive_error_string', 'string', ['number']),
|
||||
|
||||
/*
|
||||
* Returns 1 if the archive contains at least one encrypted entry.
|
||||
* If the archive format not support encryption at all
|
||||
* ARCHIVE_READ_FORMAT_ENCRYPTION_UNSUPPORTED is returned.
|
||||
* If for any other reason (e.g. not enough data read so far)
|
||||
* we cannot say whether there are encrypted entries, then
|
||||
* ARCHIVE_READ_FORMAT_ENCRYPTION_DONT_KNOW is returned.
|
||||
* In general, this function will return values below zero when the
|
||||
* reader is uncertain or totally incapable of encryption support.
|
||||
* When this function returns 0 you can be sure that the reader
|
||||
* supports encryption detection but no encrypted entries have
|
||||
* been found yet.
|
||||
*
|
||||
* NOTE: If the metadata/header of an archive is also encrypted, you
|
||||
* cannot rely on the number of encrypted entries. That is why this
|
||||
* function does not return the number of encrypted entries but#
|
||||
* just shows that there are some.
|
||||
*/
|
||||
// __LA_DECL int archive_read_has_encrypted_entries(struct archive *);
|
||||
entryIsEncrypted: this.cwrap('archive_entry_is_encrypted', 'number', ['number']),
|
||||
hasEncryptedEntries: this.cwrap('archive_read_has_encrypted_entries', 'number', ['number']),
|
||||
// __LA_DECL int archive_read_add_passphrase(struct archive *, const char *);
|
||||
addPassphrase: this.cwrap('archive_read_add_passphrase', 'number', ['number','string']),
|
||||
//this.stringToUTF(str), //
|
||||
string: (str) => this.allocate(this.intArrayFromString(str), 'i8', 0),
|
||||
malloc: this.cwrap('malloc', 'number', ['number']),
|
||||
free: this.cwrap('free', null, ['number']),
|
||||
};
|
||||
//console.log(this.runCode.getVersion());
|
||||
}
|
||||
|
||||
monitorRunDependencies(){}
|
||||
|
||||
locateFile(path /* ,prefix */ ){
|
||||
return `wasm-gen/${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getWasmModule(cb){
|
||||
libarchive( new WasmModule() ).then( (module) => {
|
||||
module.initFunctions();
|
||||
cb(module);
|
||||
});
|
||||
}
|
||||
69
src/lib/libarchive.js/src/webworker/worker.js
Normal file
69
src/lib/libarchive.js/src/webworker/worker.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import {ArchiveReader} from './archive-reader';
|
||||
import {getWasmModule} from './wasm-module';
|
||||
|
||||
let reader = null;
|
||||
let busy = false;
|
||||
|
||||
getWasmModule( (wasmModule) => {
|
||||
reader = new ArchiveReader(wasmModule);
|
||||
busy = false;
|
||||
self.postMessage({type: 'READY'});
|
||||
});
|
||||
|
||||
self.onmessage = async ({data: msg}) => {
|
||||
|
||||
if( busy ){
|
||||
self.postMessage({ type: 'BUSY' });
|
||||
return;
|
||||
}
|
||||
|
||||
let skipExtraction = false;
|
||||
busy = true;
|
||||
try{
|
||||
switch(msg.type){
|
||||
case 'HELLO': // module will respond READY when it's ready
|
||||
break;
|
||||
case 'OPEN':
|
||||
await reader.open(msg.file);
|
||||
self.postMessage({ type: 'OPENED' });
|
||||
break;
|
||||
case 'LIST_FILES':
|
||||
skipExtraction = true;
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'EXTRACT_FILES':
|
||||
for( const entry of reader.entries(skipExtraction) ){
|
||||
self.postMessage({ type: 'ENTRY', entry });
|
||||
}
|
||||
self.postMessage({ type: 'END' });
|
||||
break;
|
||||
case 'EXTRACT_SINGLE_FILE':
|
||||
for( const entry of reader.entries(true,msg.target) ){
|
||||
if( entry.fileData ){
|
||||
self.postMessage({ type: 'FILE', entry });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'CHECK_ENCRYPTION':
|
||||
self.postMessage({ type: 'ENCRYPTION_STATUS', status: reader.hasEncryptedData() });
|
||||
break;
|
||||
case 'SET_PASSPHRASE':
|
||||
reader.setPassphrase( msg.passphrase );
|
||||
self.postMessage({ type: 'PASSPHRASE_STATUS', status: true });
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid Command');
|
||||
}
|
||||
}catch(err){
|
||||
self.postMessage({
|
||||
type: 'ERROR',
|
||||
error: {
|
||||
message: err.message,
|
||||
name: err.name,
|
||||
stack: err.stack
|
||||
}
|
||||
});
|
||||
}finally{
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
busy = false;
|
||||
}
|
||||
};
|
||||
2
src/lib/libarchive.js/test/archive_content/test/.gitignore
vendored
Normal file
2
src/lib/libarchive.js/test/archive_content/test/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.vscode
|
||||
.idea
|
||||
@@ -0,0 +1 @@
|
||||
# Adjaranet plugin for Kodi
|
||||
120
src/lib/libarchive.js/test/archive_content/test/addon/addon.py
Normal file
120
src/lib/libarchive.js/test/archive_content/test/addon/addon.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import simplejson as json
|
||||
from httplib2 import Http
|
||||
import re
|
||||
import urllib2
|
||||
import sys
|
||||
import urllib
|
||||
import urlparse
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
import xbmcplugin
|
||||
|
||||
API_BASE = 'http://net.adjara.com/'
|
||||
STATIC_FILES = 'http://staticnet.adjara.com/'
|
||||
CATEGORY_MAP = {
|
||||
'new_release': 'Search/SearchResults?ajax=1&display=15&startYear=1900&endYear=2018&offset=0&orderBy=date&order%5Border%5D=data&order%5Bdata%5D=premiere&order%5Bmeta%5D=desc',
|
||||
'top_movies': 'Search/SearchResults?ajax=1&display=15&startYear=1900&endYear=2018&offset=15&orderBy=date&order%5Border%5D=data&order%5Bdata%5D=views&order%5Bmeta%5D=views-week'
|
||||
}
|
||||
|
||||
base_url = sys.argv[0]
|
||||
addon_handle = int(sys.argv[1])
|
||||
args = urlparse.parse_qs(sys.argv[2][1:])
|
||||
find_var_regex = re.compile(r"""movieUrlEmpty\s*=\s*[\'\"](.+)[\'\"]""")
|
||||
|
||||
xbmcplugin.setContent(addon_handle, 'movies')
|
||||
|
||||
def get_icon(movie_id):
|
||||
movie_id = str(movie_id)
|
||||
return STATIC_FILES + 'moviecontent/%s/covers/157x236-%s.jpg' % (movie_id,movie_id)
|
||||
def get_cover(movie_id):
|
||||
movie_id = str(movie_id)
|
||||
return STATIC_FILES + 'moviecontent/%s/covers/1920x1080-%s.jpg' % (movie_id,movie_id)
|
||||
|
||||
def build_url(query):
|
||||
return base_url + '?' + urllib.urlencode(query)
|
||||
|
||||
def add_category(label,category,iconImage = 'DefaultFolder.png', url = None):
|
||||
if url is None:
|
||||
url = build_url({'mode': 'category', 'category': category})
|
||||
li = xbmcgui.ListItem(label, iconImage=iconImage)
|
||||
xbmcplugin.addDirectoryItem(handle=addon_handle, url=url,
|
||||
listitem=li, isFolder=True)
|
||||
|
||||
def main_screen():
|
||||
add_category('Search',None,'DefaultAddonsSearch.png',build_url({'mode': 'search'}))
|
||||
add_category('New Releases','new_release')
|
||||
add_category('Top Movies','top_movies')
|
||||
xbmcplugin.endOfDirectory(addon_handle)
|
||||
|
||||
def load_category(category):
|
||||
cat_url = API_BASE + CATEGORY_MAP[category]
|
||||
try:
|
||||
(rsp_headers, json_data) = Http().request(cat_url)
|
||||
data = json.loads(json_data)
|
||||
for item in data['data']:
|
||||
url = build_url({'mode': 'movie', 'id': item['id']})
|
||||
li = xbmcgui.ListItem(item['title_en'], iconImage=item['poster'])
|
||||
li.setProperty('IsPlayable', 'true')
|
||||
xbmcplugin.addDirectoryItem(handle=addon_handle, url=url, listitem=li, isFolder=False)
|
||||
|
||||
except Exception, e:
|
||||
xbmc.log('adjaranet: got http error fetching %s \n %s' % (cat_url, str(e)), xbmc.LOGWARNING)
|
||||
finally:
|
||||
xbmcplugin.endOfDirectory(addon_handle)
|
||||
|
||||
def search():
|
||||
kb = xbmc.Keyboard('', 'Search for movie')
|
||||
kb.doModal()
|
||||
if (kb.isConfirmed()):
|
||||
search_term = kb.getText()
|
||||
else:
|
||||
return
|
||||
|
||||
search_url = API_BASE + 'Home/quick_search?ajax=1&search=' + search_term
|
||||
try:
|
||||
(rsp_headers, json_data) = Http().request(search_url)
|
||||
data = json.loads(json_data)
|
||||
for item in data['movies']['data']:
|
||||
url = build_url({'mode': 'movie', 'id': item['id']})
|
||||
li = xbmcgui.ListItem(item['title_en'])
|
||||
li.setArt({
|
||||
'icon': get_icon(item['id']),
|
||||
'landscape': get_cover(item['id'])
|
||||
})
|
||||
li.setProperty('IsPlayable', 'true')
|
||||
xbmcplugin.addDirectoryItem(handle=addon_handle, url=url, listitem=li, isFolder=False)
|
||||
except Exception, e:
|
||||
xbmc.log('adjaranet: got http error fetching %s \n %s' % (search_url, str(e)), xbmc.LOGWARNING)
|
||||
finally:
|
||||
xbmcplugin.endOfDirectory(addon_handle)
|
||||
|
||||
|
||||
def load_movie(movie_id):
|
||||
script_url = API_BASE + 'Movie/main?id='+ movie_id +'&js=1'
|
||||
try:
|
||||
(rsp_headers, html_data) = Http().request(script_url)
|
||||
match = re.search(find_var_regex,html_data)
|
||||
if not match:
|
||||
xbmc.log('can not find url at %s' % (script_url), xbmc.LOGWARNING)
|
||||
raise Exception('url not found')
|
||||
|
||||
url = match.group(1).replace('{lang}','English').replace('{quality}','1500')
|
||||
xbmc.log(url, xbmc.LOGWARNING)
|
||||
|
||||
play_item = xbmcgui.ListItem(path=url)
|
||||
xbmcplugin.setResolvedUrl(addon_handle, True, listitem=play_item)
|
||||
except Exception, e:
|
||||
xbmc.log('adjaranet: got http error fetching %s \n %s' % (script_url, str(e)), xbmc.LOGWARNING)
|
||||
|
||||
mode = args.get('mode', None)
|
||||
|
||||
if mode is None:
|
||||
main_screen()
|
||||
elif mode[0] == 'category':
|
||||
category = args.get('category','new_release')
|
||||
load_category(category[0])
|
||||
elif mode[0] == 'search':
|
||||
search()
|
||||
elif mode[0] == 'movie':
|
||||
movie_id = args.get('id', None)
|
||||
load_movie(movie_id[0])
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<addon id="plugin.addon.adjaranet.client" name="adjaranet client" version="0.0.1" provider-name="You">
|
||||
<requires>
|
||||
<import addon="xbmc.python" version="2.1.0"/>
|
||||
<import addon="script.module.simplejson" />
|
||||
<import addon="script.module.httplib2" />
|
||||
</requires>
|
||||
<extension point="xbmc.python.pluginsource" library="addon.py">
|
||||
<provides>video</provides>
|
||||
</extension>
|
||||
<extension point="xbmc.addon.metadata">
|
||||
<summary lang="en_GB">Adjaranet.com client</summary>
|
||||
<description lang="en_GB">enable adjaranet inside kodi</description>
|
||||
<disclaimer lang="en_GB"></disclaimer>
|
||||
<language></language>
|
||||
<platform>all</platform>
|
||||
<license></license>
|
||||
<forum></forum>
|
||||
<website></website>
|
||||
<email></email>
|
||||
<source></source>
|
||||
<news></news>
|
||||
<assets>
|
||||
<icon></icon>
|
||||
<fanart></fanart>
|
||||
<banner></banner>
|
||||
<clearlogo></clearlogo>
|
||||
<screenshot></screenshot>
|
||||
</assets>
|
||||
</extension>
|
||||
</addon>
|
||||
9
src/lib/libarchive.js/test/checksum.js
Normal file
9
src/lib/libarchive.js/test/checksum.js
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
module.exports.checksum = {
|
||||
'.gitignore':'d1e8d4fa856e17b2ad54a216aae527a880873df76cc30a85d6ba6b32d2ee23cc',
|
||||
'addon':{
|
||||
'addon.py':'e0ab20fe5fd7ab5c2b38511d81d93b9cb6246e300d0893face50e8a5b9485b90',
|
||||
'addon.xml':'d26a8bdf02e7ab2eaeadf2ab603a1d11b2a5bfe57a6ac672d1a1c4940958eba8'
|
||||
},
|
||||
'README.md':'b4555fd8dd6e81599625c1232e58d5e09fc36f3f6614bf792a6978b30cfe65bb'
|
||||
};
|
||||
BIN
src/lib/libarchive.js/test/files/archives/7z/bzip2.7z
Normal file
BIN
src/lib/libarchive.js/test/files/archives/7z/bzip2.7z
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/7z/lzma.7z
Normal file
BIN
src/lib/libarchive.js/test/files/archives/7z/lzma.7z
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/7z/lzma2.7z
Normal file
BIN
src/lib/libarchive.js/test/files/archives/7z/lzma2.7z
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/encrypted.zip
Normal file
BIN
src/lib/libarchive.js/test/files/archives/encrypted.zip
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/rar/test-v4.rar
Normal file
BIN
src/lib/libarchive.js/test/files/archives/rar/test-v4.rar
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/rar/test-v5.rar
Normal file
BIN
src/lib/libarchive.js/test/files/archives/rar/test-v5.rar
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/tar/test.tar
Normal file
BIN
src/lib/libarchive.js/test/files/archives/tar/test.tar
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/tar/test.tar.bz2
Normal file
BIN
src/lib/libarchive.js/test/files/archives/tar/test.tar.bz2
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/tar/test.tar.gz
Normal file
BIN
src/lib/libarchive.js/test/files/archives/tar/test.tar.gz
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/tar/test.tar.xz
Normal file
BIN
src/lib/libarchive.js/test/files/archives/tar/test.tar.xz
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/test.7z
Normal file
BIN
src/lib/libarchive.js/test/files/archives/test.7z
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/test.zip
Normal file
BIN
src/lib/libarchive.js/test/files/archives/test.zip
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/zip/bzip2.zip
Normal file
BIN
src/lib/libarchive.js/test/files/archives/zip/bzip2.zip
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/zip/deflate.zip
Normal file
BIN
src/lib/libarchive.js/test/files/archives/zip/deflate.zip
Normal file
Binary file not shown.
BIN
src/lib/libarchive.js/test/files/archives/zip/lzma.zip
Normal file
BIN
src/lib/libarchive.js/test/files/archives/zip/lzma.zip
Normal file
Binary file not shown.
80
src/lib/libarchive.js/test/files/encryption.html
Normal file
80
src/lib/libarchive.js/test/files/encryption.html
Normal file
@@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>test webworker</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="file" id="file" />
|
||||
<script type="module" >
|
||||
|
||||
function hex(buffer) {
|
||||
const hexCodes = [];
|
||||
const view = new DataView(buffer);
|
||||
for (let i = 0; i < view.byteLength; i += 4) {
|
||||
const value = view.getUint32(i)
|
||||
const stringValue = value.toString(16)
|
||||
const padding = '00000000'
|
||||
const paddedValue = (padding + stringValue).slice(-padding.length)
|
||||
hexCodes.push(paddedValue);
|
||||
}
|
||||
return hexCodes.join("");
|
||||
}
|
||||
|
||||
function getChecksum(file){
|
||||
return new Promise((resolve,reject) => {
|
||||
try{
|
||||
const reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
crypto.subtle.digest("SHA-256", reader.result).then(function (hash) {
|
||||
resolve(hex(hash));
|
||||
});
|
||||
}
|
||||
reader.readAsArrayBuffer(file);
|
||||
}catch(err){
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function finish(){
|
||||
const d = document.createElement('div');
|
||||
d.setAttribute('id','done');
|
||||
d.textContent = 'Done.';
|
||||
document.body.appendChild(d);
|
||||
}
|
||||
|
||||
async function fileChecksums(obj){
|
||||
for( const [key,val] of Object.entries(obj) ){
|
||||
obj[key] = val instanceof File ?
|
||||
await getChecksum(val) : await fileChecksums(val);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
import {Archive} from '../../src/libarchive.js';
|
||||
|
||||
Archive.init({
|
||||
workerUrl: '../../dist/worker-bundle.js'
|
||||
});
|
||||
|
||||
window.Archive = Archive;
|
||||
|
||||
document.getElementById('file').addEventListener('change', async (e) => {
|
||||
let obj = null, encEntries = false;
|
||||
try{
|
||||
const file = e.currentTarget.files[0];
|
||||
const archive = await Archive.open(file);
|
||||
encEntries = await archive.hasEncryptedData();
|
||||
await archive.usePassword("nika");
|
||||
obj = await archive.extractFiles();
|
||||
obj = await fileChecksums(obj);
|
||||
}catch(err){
|
||||
console.error(err);
|
||||
}finally{
|
||||
window.obj = {files: obj, encrypted: encEntries};
|
||||
finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
82
src/lib/libarchive.js/test/files/index.html
Normal file
82
src/lib/libarchive.js/test/files/index.html
Normal file
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>test webworker</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="file" id="file" />
|
||||
<script type="module" >
|
||||
|
||||
function hex(buffer) {
|
||||
const hexCodes = [];
|
||||
const view = new DataView(buffer);
|
||||
for (let i = 0; i < view.byteLength; i += 4) {
|
||||
const value = view.getUint32(i)
|
||||
const stringValue = value.toString(16)
|
||||
const padding = '00000000'
|
||||
const paddedValue = (padding + stringValue).slice(-padding.length)
|
||||
hexCodes.push(paddedValue);
|
||||
}
|
||||
return hexCodes.join("");
|
||||
}
|
||||
|
||||
function getChecksum(file){
|
||||
return new Promise((resolve,reject) => {
|
||||
try{
|
||||
const reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
crypto.subtle.digest("SHA-256", reader.result).then(function (hash) {
|
||||
resolve(hex(hash));
|
||||
});
|
||||
}
|
||||
reader.readAsArrayBuffer(file);
|
||||
}catch(err){
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function finish(){
|
||||
const d = document.createElement('div');
|
||||
d.setAttribute('id','done');
|
||||
d.textContent = 'Done.';
|
||||
document.body.appendChild(d);
|
||||
}
|
||||
|
||||
async function fileChecksums(obj){
|
||||
for( const [key,val] of Object.entries(obj) ){
|
||||
obj[key] = val instanceof File ?
|
||||
await getChecksum(val) : await fileChecksums(val);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
import {Archive} from '../../src/libarchive.js';
|
||||
|
||||
Archive.init({
|
||||
workerUrl: '../../dist/worker-bundle.js'
|
||||
});
|
||||
|
||||
window.Archive = Archive;
|
||||
|
||||
document.getElementById('file').addEventListener('change', async (e) => {
|
||||
let obj = null;
|
||||
try{
|
||||
const file = e.currentTarget.files[0];
|
||||
const archive = await Archive.open(file);
|
||||
//console.log( await archive.getFilesObject() );
|
||||
//console.log( await archive.getFilesArray() );
|
||||
obj = await archive.extractFiles();
|
||||
//console.log( await archive.getFilesObject() );
|
||||
//console.log( await archive.getFilesArray() );
|
||||
obj = await fileChecksums(obj);
|
||||
}catch(err){
|
||||
console.error(err);
|
||||
}finally{
|
||||
window.obj = obj;
|
||||
finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
78
src/lib/libarchive.js/test/files/test-single.html
Normal file
78
src/lib/libarchive.js/test/files/test-single.html
Normal file
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>test webworker</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="file" id="file" />
|
||||
<script type="module" >
|
||||
|
||||
function hex(buffer) {
|
||||
const hexCodes = [];
|
||||
const view = new DataView(buffer);
|
||||
for (let i = 0; i < view.byteLength; i += 4) {
|
||||
const value = view.getUint32(i)
|
||||
const stringValue = value.toString(16)
|
||||
const padding = '00000000'
|
||||
const paddedValue = (padding + stringValue).slice(-padding.length)
|
||||
hexCodes.push(paddedValue);
|
||||
}
|
||||
return hexCodes.join("");
|
||||
}
|
||||
|
||||
function getChecksum(file){
|
||||
return new Promise((resolve,reject) => {
|
||||
try{
|
||||
const reader = new FileReader();
|
||||
reader.onload = function() {
|
||||
crypto.subtle.digest("SHA-256", reader.result).then(function (hash) {
|
||||
resolve(hex(hash));
|
||||
});
|
||||
}
|
||||
reader.readAsArrayBuffer(file);
|
||||
}catch(err){
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function finish(){
|
||||
const d = document.createElement('div');
|
||||
d.setAttribute('id','done');
|
||||
d.textContent = 'Done.';
|
||||
document.body.appendChild(d);
|
||||
}
|
||||
|
||||
async function fileChecksums(obj){
|
||||
for( const [key,val] of Object.entries(obj) ){
|
||||
obj[key] = val instanceof File ?
|
||||
await getChecksum(val) : await fileChecksums(val);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
import {Archive} from '../../src/libarchive.js';
|
||||
|
||||
Archive.init({
|
||||
workerUrl: '../../dist/worker-bundle.js'
|
||||
});
|
||||
|
||||
window.Archive = Archive;
|
||||
|
||||
document.getElementById('file').addEventListener('change', async (e) => {
|
||||
let objAfter,objBefore,fileObj;
|
||||
try{
|
||||
const file = e.currentTarget.files[0];
|
||||
const archive = await Archive.open(file);
|
||||
const files = await archive.getFilesArray();
|
||||
fileObj = await files[0].file.extract();
|
||||
}catch(err){
|
||||
console.error(err);
|
||||
}finally{
|
||||
window.obj = await getChecksum(fileObj);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
36
src/lib/libarchive.js/test/formats/7z.test.js
Normal file
36
src/lib/libarchive.js/test/formats/7z.test.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/* eslint-disable no-undef */
|
||||
const {checksum} = require('../checksum');
|
||||
const {navigate,inputFile,response,setup,cleanup} = require('../testutils');
|
||||
|
||||
let browser,page;
|
||||
|
||||
beforeAll(async () => {
|
||||
let tmp = await setup();
|
||||
browser = tmp.browser;
|
||||
page = tmp.page;
|
||||
});
|
||||
|
||||
describe("Extract 7Z files with various compressions", () => {
|
||||
test("Extract 7Z with LZMA", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/7z/lzma.7z',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
test("Extract 7Z with LZMA2", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/7z/lzma2.7z',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
test("Extract 7Z with BZIP2", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/7z/bzip2.7z',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanup(browser);
|
||||
});
|
||||
30
src/lib/libarchive.js/test/formats/rar.test.js
Normal file
30
src/lib/libarchive.js/test/formats/rar.test.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/* eslint-disable no-undef */
|
||||
const {checksum} = require('../checksum');
|
||||
const {navigate,inputFile,response,setup,cleanup} = require('../testutils');
|
||||
|
||||
let browser,page;
|
||||
|
||||
beforeAll(async () => {
|
||||
let tmp = await setup();
|
||||
browser = tmp.browser;
|
||||
page = tmp.page;
|
||||
});
|
||||
|
||||
describe("Extract RAR files", () => {
|
||||
test("Extract RAR v4", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/rar/test-v4.rar',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
test("Extract RAR v5", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/rar/test-v5.rar',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanup(browser);
|
||||
});
|
||||
42
src/lib/libarchive.js/test/formats/tar.test.js
Normal file
42
src/lib/libarchive.js/test/formats/tar.test.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/* eslint-disable no-undef */
|
||||
const {checksum} = require('../checksum');
|
||||
const {navigate,inputFile,response,setup,cleanup} = require('../testutils');
|
||||
|
||||
let browser,page;
|
||||
|
||||
beforeAll(async () => {
|
||||
let tmp = await setup();
|
||||
browser = tmp.browser;
|
||||
page = tmp.page;
|
||||
});
|
||||
|
||||
describe("Extract TAR files with various compressions", () => {
|
||||
test("Extract TAR without compression", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/tar/test.tar',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
test("Extract TAR BZIP2", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/tar/test.tar.bz2',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
test("Extract TAR GZIP", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/tar/test.tar.gz',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
test("Extract TAR LZMA2", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/tar/test.tar.xz',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanup(browser);
|
||||
});
|
||||
42
src/lib/libarchive.js/test/formats/zip.test.js
Normal file
42
src/lib/libarchive.js/test/formats/zip.test.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/* eslint-disable no-undef */
|
||||
const {checksum} = require('../checksum');
|
||||
const {navigate,inputFile,response,setup,cleanup} = require('../testutils');
|
||||
|
||||
let browser,page;
|
||||
|
||||
beforeAll(async () => {
|
||||
let tmp = await setup();
|
||||
browser = tmp.browser;
|
||||
page = tmp.page;
|
||||
});
|
||||
|
||||
describe("Extract ZIP files with various compressions", () => {
|
||||
test("Extract ZIP deflate", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/zip/deflate.zip',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
/* test("Extract ZIP deflate64", async () => { // not support
|
||||
await navigate(page);
|
||||
await inputFile('archives/zip/deflate64.zip',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000); */
|
||||
test("Extract ZIP bzip2", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/zip/bzip2.zip',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
test("Extract ZIP lzma", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/zip/lzma.zip',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanup(browser);
|
||||
});
|
||||
39
src/lib/libarchive.js/test/main.test.js
Normal file
39
src/lib/libarchive.js/test/main.test.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/* eslint-disable no-undef */
|
||||
const {checksum} = require('./checksum');
|
||||
const {navigate,inputFile,response,setup,cleanup} = require('./testutils');
|
||||
|
||||
let browser,page;
|
||||
|
||||
beforeAll(async () => {
|
||||
let tmp = await setup();
|
||||
browser = tmp.browser;
|
||||
page = tmp.page;
|
||||
});
|
||||
|
||||
describe("extract various compression types", () => {
|
||||
test("extract 7z file", async () => {
|
||||
await navigate(page);
|
||||
await inputFile('archives/test.7z',page);
|
||||
const files = await response(page);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
|
||||
test("extract single file from zip", async () => {
|
||||
await navigate(page,'test-single.html');
|
||||
await inputFile('archives/test.zip',page);
|
||||
const file = await response(page);
|
||||
expect(file).toEqual(checksum['.gitignore']);
|
||||
}, 16000);
|
||||
|
||||
test("extract encrypted zip", async () => {
|
||||
await navigate(page,'encryption.html');
|
||||
await inputFile('archives/encrypted.zip',page);
|
||||
const {files,encrypted} = await response(page);
|
||||
expect(encrypted).toEqual(true);
|
||||
expect(files).toEqual(checksum);
|
||||
}, 16000);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanup(browser);
|
||||
});
|
||||
46
src/lib/libarchive.js/test/testutils.js
Normal file
46
src/lib/libarchive.js/test/testutils.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const StaticServer = require('static-server');
|
||||
const puppeteer = require('puppeteer');
|
||||
const port = 8787;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
const server = new StaticServer({
|
||||
rootPath: '.',
|
||||
port: port,
|
||||
cors: '*',
|
||||
});
|
||||
|
||||
const startServer = () => new Promise((resolve) => {
|
||||
server.start( () => {
|
||||
console.log('Server listening to', port);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
setup: async () => {
|
||||
let browser = await puppeteer.launch();
|
||||
let page = await browser.newPage();
|
||||
await page.setViewport({ width, height });
|
||||
await startServer();
|
||||
page.on('console', msg => {
|
||||
for (let i = 0; i < msg.args().length; ++i) console.log(`${i}: ${msg.args()[i]}`);
|
||||
});
|
||||
return {browser,page};
|
||||
},
|
||||
cleanup: (browser) => {
|
||||
server.stop();
|
||||
browser.close();
|
||||
},
|
||||
navigate: async function (page, path = 'index.html') {
|
||||
await page.goto(`http://127.0.0.1:${port}/test/files/${path}`);
|
||||
},
|
||||
inputFile: async function (file,page){
|
||||
const fileInp = await page.$('#file');
|
||||
fileInp.uploadFile('test/files/'+file);
|
||||
},
|
||||
response: async function (page){
|
||||
await page.waitForSelector('#done');
|
||||
return await page.evaluate(`window.obj`);
|
||||
}
|
||||
};
|
||||
|
||||
367
src/lib/mime.json
Normal file
367
src/lib/mime.json
Normal file
@@ -0,0 +1,367 @@
|
||||
[
|
||||
{
|
||||
"name": "AAC audio",
|
||||
"mime": "audio/aac",
|
||||
"ext": ".aac"
|
||||
},
|
||||
{
|
||||
"name": "AbiWord document",
|
||||
"mime": "application/x-abiword",
|
||||
"ext": ".abw"
|
||||
},
|
||||
{
|
||||
"name": "Archive document (multiple files embedded)",
|
||||
"mime": "application/x-freearc",
|
||||
"ext": ".arc"
|
||||
},
|
||||
{
|
||||
"name": "AVIF image",
|
||||
"mime": "image/avif",
|
||||
"ext": ".avif"
|
||||
},
|
||||
{
|
||||
"name": "AVI: Audio Video Interleave",
|
||||
"mime": "video/x-msvideo",
|
||||
"ext": ".avi"
|
||||
},
|
||||
{
|
||||
"name": "Amazon Kindle eBook format",
|
||||
"mime": "application/vnd.amazon.ebook",
|
||||
"ext": ".azw"
|
||||
},
|
||||
{
|
||||
"name": "Any kind of binary data",
|
||||
"mime": "application/octet-stream",
|
||||
"ext": ".bin"
|
||||
},
|
||||
{
|
||||
"name": "Windows OS/2 Bitmap Graphics",
|
||||
"mime": "image/bmp",
|
||||
"ext": ".bmp"
|
||||
},
|
||||
{
|
||||
"name": "BZip archive",
|
||||
"mime": "application/x-bzip",
|
||||
"ext": ".bz"
|
||||
},
|
||||
{
|
||||
"name": "BZip2 archive",
|
||||
"mime": "application/x-bzip2",
|
||||
"ext": ".bz2"
|
||||
},
|
||||
{
|
||||
"name": "CD audio",
|
||||
"mime": "application/x-cdf",
|
||||
"ext": ".cda"
|
||||
},
|
||||
{
|
||||
"name": "C-Shell script",
|
||||
"mime": "application/x-csh",
|
||||
"ext": ".csh"
|
||||
},
|
||||
{
|
||||
"name": "Cascading Style Sheets (CSS)",
|
||||
"mime": "text/css",
|
||||
"ext": ".css"
|
||||
},
|
||||
{
|
||||
"name": "Comma-separated values (CSV)",
|
||||
"mime": "text/csv",
|
||||
"ext": ".csv"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft Word",
|
||||
"mime": "application/msword",
|
||||
"ext": ".doc"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft Word (OpenXML)",
|
||||
"mime": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"ext": ".docx"
|
||||
},
|
||||
{
|
||||
"name": "MS Embedded OpenType fonts",
|
||||
"mime": "application/vnd.ms-fontobject",
|
||||
"ext": ".eot"
|
||||
},
|
||||
{
|
||||
"name": "Electronic publication (EPUB)",
|
||||
"mime": "application/epub+zip",
|
||||
"ext": ".epub"
|
||||
},
|
||||
{
|
||||
"name": "GZip Compressed Archive",
|
||||
"mime": "application/gzip",
|
||||
"ext": ".gz"
|
||||
},
|
||||
{
|
||||
"name": "Graphics Interchange Format (GIF)",
|
||||
"mime": "image/gif",
|
||||
"ext": ".gif"
|
||||
},
|
||||
{
|
||||
"name": "HyperText Markup Language (HTML)",
|
||||
"mime": "text/html",
|
||||
"ext": ".html"
|
||||
},
|
||||
{
|
||||
"name": "Icon format",
|
||||
"mime": "image/vnd.microsoft.icon",
|
||||
"ext": ".ico"
|
||||
},
|
||||
{
|
||||
"name": "iCalendar format",
|
||||
"mime": "text/calendar",
|
||||
"ext": ".ics"
|
||||
},
|
||||
{
|
||||
"name": "Java Archive (JAR)",
|
||||
"mime": "application/java-archive",
|
||||
"ext": ".jar"
|
||||
},
|
||||
{
|
||||
"name": "JPEG images",
|
||||
"mime": "image/jpeg",
|
||||
"ext": ".jpg"
|
||||
},
|
||||
{
|
||||
"name": "JPEG images",
|
||||
"mime": "image/jpeg",
|
||||
"ext": ".jpeg"
|
||||
},
|
||||
{
|
||||
"name": "JavaScript",
|
||||
"mime": "text/javascript (Specifications: HTML and RFC 9239)",
|
||||
"ext": ".js"
|
||||
},
|
||||
{
|
||||
"name": "JSON format",
|
||||
"mime": "application/json",
|
||||
"ext": ".json"
|
||||
},
|
||||
{
|
||||
"name": "JSON-LD format",
|
||||
"mime": "application/ld+json",
|
||||
"ext": ".jsonld"
|
||||
},
|
||||
{
|
||||
"name": "Musical Instrument Digital Interface (MIDI)",
|
||||
"mime": "audio/midi audio/x-midi",
|
||||
"ext": ".mid .midi"
|
||||
},
|
||||
{
|
||||
"name": "JavaScript module",
|
||||
"mime": "text/javascript",
|
||||
"ext": ".mjs"
|
||||
},
|
||||
{
|
||||
"name": "MP3 audio",
|
||||
"mime": "audio/mpeg",
|
||||
"ext": ".mp3"
|
||||
},
|
||||
{
|
||||
"name": "MP4 video",
|
||||
"mime": "video/mp4",
|
||||
"ext": ".mp4"
|
||||
},
|
||||
{
|
||||
"name": "MPEG Video",
|
||||
"mime": "video/mpeg",
|
||||
"ext": ".mpeg"
|
||||
},
|
||||
{
|
||||
"name": "Apple Installer Package",
|
||||
"mime": "application/vnd.apple.installer+xml",
|
||||
"ext": ".mpkg"
|
||||
},
|
||||
{
|
||||
"name": "OpenDocument presentation document",
|
||||
"mime": "application/vnd.oasis.opendocument.presentation",
|
||||
"ext": ".odp"
|
||||
},
|
||||
{
|
||||
"name": "OpenDocument spreadsheet document",
|
||||
"mime": "application/vnd.oasis.opendocument.spreadsheet",
|
||||
"ext": ".ods"
|
||||
},
|
||||
{
|
||||
"name": "OpenDocument text document",
|
||||
"mime": "application/vnd.oasis.opendocument.text",
|
||||
"ext": ".odt"
|
||||
},
|
||||
{
|
||||
"name": "OGG audio",
|
||||
"mime": "audio/ogg",
|
||||
"ext": ".oga"
|
||||
},
|
||||
{
|
||||
"name": "OGG video",
|
||||
"mime": "video/ogg",
|
||||
"ext": ".ogv"
|
||||
},
|
||||
{
|
||||
"name": "OGG",
|
||||
"mime": "application/ogg",
|
||||
"ext": ".ogx"
|
||||
},
|
||||
{
|
||||
"name": "Opus audio",
|
||||
"mime": "audio/opus",
|
||||
"ext": ".opus"
|
||||
},
|
||||
{
|
||||
"name": "OpenType font",
|
||||
"mime": "font/otf",
|
||||
"ext": ".otf"
|
||||
},
|
||||
{
|
||||
"name": "Portable Network Graphics",
|
||||
"mime": "image/png",
|
||||
"ext": ".png"
|
||||
},
|
||||
{
|
||||
"name": "Adobe Portable Document Format (PDF)",
|
||||
"mime": "application/pdf",
|
||||
"ext": ".pdf"
|
||||
},
|
||||
{
|
||||
"name": "Hypertext Preprocessor (Personal Home Page)",
|
||||
"mime": "application/x-httpd-php",
|
||||
"ext": ".php"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft PowerPoint",
|
||||
"mime": "application/vnd.ms-powerpoint",
|
||||
"ext": ".ppt"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft PowerPoint (OpenXML)",
|
||||
"mime": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"ext": ".pptx"
|
||||
},
|
||||
{
|
||||
"name": "RAR archive",
|
||||
"mime": "application/vnd.rar",
|
||||
"ext": ".rar"
|
||||
},
|
||||
{
|
||||
"name": "Rich Text Format (RTF)",
|
||||
"mime": "application/rtf",
|
||||
"ext": ".rtf"
|
||||
},
|
||||
{
|
||||
"name": "Bourne shell script",
|
||||
"mime": "application/x-sh",
|
||||
"ext": ".sh"
|
||||
},
|
||||
{
|
||||
"name": "Scalable Vector Graphics (SVG)",
|
||||
"mime": "image/svg+xml",
|
||||
"ext": ".svg"
|
||||
},
|
||||
{
|
||||
"name": "Tape Archive (TAR)",
|
||||
"mime": "application/x-tar",
|
||||
"ext": ".tar"
|
||||
},
|
||||
{
|
||||
"name": "Tagged Image File Format (TIFF)",
|
||||
"mime": "image/tiff",
|
||||
"ext": ".tif .tiff"
|
||||
},
|
||||
{
|
||||
"name": "MPEG transport stream",
|
||||
"mime": "video/mp2t",
|
||||
"ext": ".ts"
|
||||
},
|
||||
{
|
||||
"name": "TrueType Font",
|
||||
"mime": "font/ttf",
|
||||
"ext": ".ttf"
|
||||
},
|
||||
{
|
||||
"name": "Text, (generally ASCII or ISO 8859-n)",
|
||||
"mime": "text/plain",
|
||||
"ext": ".txt"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft Visio",
|
||||
"mime": "application/vnd.visio",
|
||||
"ext": ".vsd"
|
||||
},
|
||||
{
|
||||
"name": "Waveform Audio Format",
|
||||
"mime": "audio/wav",
|
||||
"ext": ".wav"
|
||||
},
|
||||
{
|
||||
"name": "WEBM audio",
|
||||
"mime": "audio/webm",
|
||||
"ext": ".weba"
|
||||
},
|
||||
{
|
||||
"name": "WEBM video",
|
||||
"mime": "video/webm",
|
||||
"ext": ".webm"
|
||||
},
|
||||
{
|
||||
"name": "WEBP image",
|
||||
"mime": "image/webp",
|
||||
"ext": ".webp"
|
||||
},
|
||||
{
|
||||
"name": "Web Open Font Format (WOFF)",
|
||||
"mime": "font/woff",
|
||||
"ext": ".woff"
|
||||
},
|
||||
{
|
||||
"name": "Web Open Font Format (WOFF)",
|
||||
"mime": "font/woff2",
|
||||
"ext": ".woff2"
|
||||
},
|
||||
{
|
||||
"name": "XHTML",
|
||||
"mime": "application/xhtml+xml",
|
||||
"ext": ".xhtml"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft Excel",
|
||||
"mime": "application/vnd.ms-excel",
|
||||
"ext": ".xls"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft Excel (OpenXML)",
|
||||
"mime": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"ext": ".xlsx"
|
||||
},
|
||||
{
|
||||
"name": "XML",
|
||||
"mime": "application/xml",
|
||||
"ext": ".xml"
|
||||
},
|
||||
{
|
||||
"name": "XUL",
|
||||
"mime": "application/vnd.mozilla.xul+xml",
|
||||
"ext": ".xul"
|
||||
},
|
||||
{
|
||||
"name": "ZIP archive",
|
||||
"mime": "application/zip",
|
||||
"ext": ".zip"
|
||||
},
|
||||
{
|
||||
"name": "3GPP audio/video container",
|
||||
"mime": "video/3gpp; audio/3gpp if it doesn't contain video",
|
||||
"ext": ".3gp"
|
||||
},
|
||||
{
|
||||
"name": "3GPP2 audio/video container",
|
||||
"mime": "video/3gpp2",
|
||||
"ext": ".3g2"
|
||||
},
|
||||
{
|
||||
"name": "7-zip archive",
|
||||
"mime": "application/x-7z-compressed",
|
||||
"ext": ".7z"
|
||||
}
|
||||
]
|
||||
18
src/lib/store.js
Normal file
18
src/lib/store.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { writable } from "svelte/store";
|
||||
import { default_wallpapers } from './system';
|
||||
|
||||
export let queueProgram = writable({});
|
||||
export let runningPrograms = writable([]);
|
||||
|
||||
export let selectingItems = writable([]);
|
||||
export let contextMenu = writable(null);
|
||||
export let zIndex = writable(0);
|
||||
|
||||
export let wallpaper = writable(null);
|
||||
|
||||
export let systemVolume = writable(1);
|
||||
|
||||
export let hardDrive = writable(null);
|
||||
export let clipboard = writable([]);
|
||||
export let clipboard_op = writable('copy');
|
||||
export let queueCommand = writable(null);
|
||||
233
src/lib/system.js
Normal file
233
src/lib/system.js
Normal file
@@ -0,0 +1,233 @@
|
||||
export let default_wallpapers = [
|
||||
{
|
||||
"name": "Ascent",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Ascent.jpg",
|
||||
"css": "url(/images/xp/Ascent.jpg)"
|
||||
},
|
||||
{
|
||||
"name": "Autumn",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Autumn.jpg",
|
||||
"css": "url(/images/xp/Autumn.jpg)"
|
||||
},
|
||||
{
|
||||
"name": "Azul",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Azul.jpg",
|
||||
"css": "url(/images/xp/Azul.jpg)"
|
||||
},
|
||||
{
|
||||
"name": "Bliss",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Bliss.jpg",
|
||||
"css": "url(/images/xp/Bliss.jpg)"
|
||||
},
|
||||
{
|
||||
"name": "Follow",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Follow.jpg",
|
||||
"css": "url(/images/xp/Follow.jpg)"
|
||||
},
|
||||
{
|
||||
"name": "Friend",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Friend.jpg",
|
||||
"css": "url(/images/xp/Friend.jpg)"
|
||||
},
|
||||
{
|
||||
"name": "Moon flower",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Moonflower.jpg",
|
||||
"css": "url(/images/xp/Moonflower.jpg)"
|
||||
},
|
||||
{
|
||||
"name": "Radiance",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Radiance.jpg",
|
||||
"css": "url(/images/xp/Radiance.jpg)"
|
||||
},
|
||||
{
|
||||
"name": "Red moon desert",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Redmoondesert.jpg",
|
||||
"css": "url(/images/xp/Redmoondesert.jpg)"
|
||||
},
|
||||
{
|
||||
"name": "Tulips",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Tulips.jpg",
|
||||
"css": "url(/images/xp/Tulips.jpg)"
|
||||
},
|
||||
{
|
||||
"name": "Vortec space",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Vortecspace.jpg",
|
||||
"css": "url(/images/xp/Vortecspace.jpg)"
|
||||
},
|
||||
{
|
||||
"name": "Wind",
|
||||
"type": "remote",
|
||||
"path": "/images/xp/Wind.jpg",
|
||||
"css": "url(/images/xp/Wind.jpg)"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
export let my_music_id = 'tjhEdnks6c4wPBWcqyoWQz';
|
||||
export let my_pictures_id = 'neRHxqN8SPnG1xrivxXxRq';
|
||||
|
||||
export let my_computer = [
|
||||
'cTbkbrM4qjwF3UfmCoFkEK',//c drive
|
||||
'ejq5mVcfZA2fzR1uwYUC6n',//d drive
|
||||
'o1owmZuXKQdXR5vFxaBBW3',//f removable storage
|
||||
my_music_id,//my music
|
||||
my_pictures_id//my pictures
|
||||
]
|
||||
|
||||
export let recycle_bin_id = "aEF1hjqok52tpJPsNeXMGP";
|
||||
|
||||
export let desktop_folder = 'nt1QdU9Sws26H26UNQZcQU';
|
||||
|
||||
export let wallpapers_folder = 'uZ7fBbvbzFvQgAmJZpVbEb';
|
||||
|
||||
export let bliss_wallpaper= 'w38WCkdn67K6JsvjdGug6y';
|
||||
|
||||
export let protected_items = [...my_computer, recycle_bin_id, desktop_folder, wallpapers_folder];
|
||||
|
||||
export let hidden_items = [recycle_bin_id,desktop_folder, wallpapers_folder];
|
||||
|
||||
export let supported_wallpaper_filetypes = ['.jpg', '.jpeg', '.png', '.webp'];
|
||||
|
||||
let image_viewer = {
|
||||
path: './programs/image_viewer.svelte',
|
||||
icon: '/images/xp/icons/WindowsPictureandFaxViewer.png',
|
||||
name: 'Image Viewer'
|
||||
}
|
||||
|
||||
let paint_program = {
|
||||
path: './programs/paint.svelte',
|
||||
icon: '/images/xp/icons/Paint.png',
|
||||
name: 'Paint'
|
||||
}
|
||||
let photon_program = {
|
||||
path: './programs/photon.svelte',
|
||||
icon: '/images/xp/icons/Photon.png',
|
||||
name: 'Photon Editor'
|
||||
}
|
||||
let mpc_program = {
|
||||
path: './programs/media_player_classic.svelte',
|
||||
icon: '/images/xp/icons/MPC.png',
|
||||
name: 'Media Player Classic'
|
||||
}
|
||||
let foxit_reader_program = {
|
||||
path: './programs/foxit_reader.svelte',
|
||||
icon: '/images/xp/icons/PDF.png',
|
||||
name: 'Foxit Reader'
|
||||
}
|
||||
let msword_program = {
|
||||
path: './programs/microsoft_word.svelte',
|
||||
icon: '/images/xp/icons/DOC.png',
|
||||
name: 'Microsoft Word 2003'
|
||||
}
|
||||
let koodo_program = {
|
||||
path: './programs/koodo.svelte',
|
||||
icon: '/images/xp/icons/Koodo.png',
|
||||
name: 'Koodo Reader'
|
||||
}
|
||||
let notepad_program = {
|
||||
path: './programs/notepad.svelte',
|
||||
icon: '/images/xp/icons/Notepad.png',
|
||||
name: 'Notepad'
|
||||
}
|
||||
let ie_program = {
|
||||
path: './programs/internet_explorer.svelte',
|
||||
icon: '/images/xp/icons/InternetExplorer6.png',
|
||||
name: 'Microsoft Internet Explorer'
|
||||
}
|
||||
let winrar_program = {
|
||||
path: './programs/winrar.svelte',
|
||||
icon: '/images/xp/icons/RAR.png',
|
||||
name: 'WinRAR'
|
||||
}
|
||||
|
||||
let flash_player_program = {
|
||||
path: './programs/flash_player.svelte',
|
||||
icon: '/images/xp/icons/FlashPlayer.png',
|
||||
name: 'Flash Player'
|
||||
}
|
||||
|
||||
export let doctypes = {
|
||||
'.wav': [mpc_program],
|
||||
'.mp4': [mpc_program],
|
||||
'.mp3': [mpc_program],
|
||||
'.webp': [image_viewer],
|
||||
'.bmp': [image_viewer, paint_program, photon_program],
|
||||
'.png': [image_viewer, photon_program,paint_program],
|
||||
'.jpg': [image_viewer, photon_program,paint_program ],
|
||||
'.jpeg': [image_viewer, photon_program,paint_program],
|
||||
'.gif': [image_viewer],
|
||||
'.pdf': [foxit_reader_program,koodo_program],
|
||||
'.docx': [msword_program,koodo_program],
|
||||
'.epub': [koodo_program],
|
||||
'.azw3': [koodo_program],
|
||||
'.mobi': [koodo_program],
|
||||
'.djvu': [koodo_program],
|
||||
'.mht': [koodo_program],
|
||||
'.rtf': [koodo_program],
|
||||
'.fb2': [koodo_program],
|
||||
'.cbr': [koodo_program],
|
||||
'.cbz': [koodo_program],
|
||||
'.html': [ie_program, koodo_program, notepad_program],
|
||||
'.js': [notepad_program],
|
||||
'.vbs': [notepad_program],
|
||||
'.css': [notepad_program],
|
||||
'.txt': [notepad_program],
|
||||
'.zip': [winrar_program],
|
||||
'.rar': [winrar_program],
|
||||
'.tar': [winrar_program],
|
||||
'.7z': [winrar_program],
|
||||
'.swf': [flash_player_program]
|
||||
}
|
||||
|
||||
export let icons = {
|
||||
'.mp3': 'MPC_audio.png',
|
||||
'.wav': 'MPC_audio.png',
|
||||
'.mp4': 'MPC_video.png',
|
||||
'.ogg': 'MPC_video.png',
|
||||
'.webm': 'MPC_video.png',
|
||||
'.exe': 'ApplicationWindow.png',
|
||||
'.xml': 'XML.png',
|
||||
'.dll': 'DLL.png',
|
||||
'.rtf': 'RTF.png',
|
||||
'.tiff': 'TIFF.png',
|
||||
'.vbs': 'VBS.png',
|
||||
'.ttf': 'Font.png',
|
||||
'.bat': 'Bat.png',
|
||||
'.txt': 'TXT.png',
|
||||
'.jpg': 'JPG.png',
|
||||
'.jpeg': 'JPG.png',
|
||||
'.png': 'TIFF.png',
|
||||
'.webp': 'TIFF.png',
|
||||
'.bmp': 'Bitmap.png',
|
||||
'.pdf': 'PDF.png',
|
||||
'.docx': 'DOC.png',
|
||||
'.epub': 'BOOK.png',
|
||||
'.azw3': 'BOOK.png',
|
||||
'.mobi': 'BOOK.png',
|
||||
'.html': 'URL.png',
|
||||
'.js': 'JavaScript.png',
|
||||
'.vbs': 'VBS.png',
|
||||
'.css': 'CSS.png',
|
||||
'.rar': 'RAR.png',
|
||||
'.zip': 'RAR.png',
|
||||
'.7z': 'RAR.png',
|
||||
'.tar': 'RAR.png',
|
||||
'.srt': 'SUB.png',
|
||||
'.vtt': 'SUB.png',
|
||||
'.gif': 'GIF.png',
|
||||
'.swf': 'SWF.png'
|
||||
}
|
||||
|
||||
export let archive_exts = ['.rar', '.zip', '.7z', '.tar']
|
||||
export let previewable_exts = ['.jpeg', '.jpg', '.png', '.webp', '.bmp']
|
||||
341
src/lib/utils.js
Normal file
341
src/lib/utils.js
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user