mirror of
https://github.com/ducbao414/win32.run.git
synced 2025-12-13 07:42:47 +09:00
init the awkward code
This commit is contained in:
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
.npmrc
|
||||
!.env.example
|
||||
.vscode/
|
||||
build.zip
|
||||
4
README.md
Normal file
4
README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# win32.run
|
||||
### Yet another fake Windows XP in the browser, but with a File System and comes with it, Programs.
|
||||
Microsoft and Windows XP trademarks & logos definitely belong to Microsoft Corporation. All the programs' names and logos (Foxit, Word, WinRar, Internet Explorer, etc.) are of their rightful copyright holders.
|
||||
**win32.run** is purely for the purpose of nostalgia. I have no intent and no right to monetize win32.run, but you may occasionally see ads when playing third-party games.
|
||||
58
gen/assets.js
Normal file
58
gen/assets.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import path from 'path';
|
||||
import dir from 'node-dir';
|
||||
import fs from 'fs';
|
||||
|
||||
let excluded_source_files = ['src/routes/xp/starting.svelte'];
|
||||
|
||||
let source_files = [
|
||||
...dir.files('./src/', {sync: true}),
|
||||
'static/json/hard_drive.json',
|
||||
'svelte.config.js',
|
||||
'tailwind.config.cjs',
|
||||
'vite.config.js'
|
||||
]
|
||||
.filter(el => ['.js', '.json', '.svelte', '.css', '.cjs', '.html'].includes(path.extname(el)))
|
||||
.filter(el => !excluded_source_files.includes(el));
|
||||
|
||||
|
||||
(async () => {
|
||||
|
||||
let remote_files = dir.files('./static/files/', {sync: true})
|
||||
.filter(file => ['.png', '.jpg', '.mp3'].includes(path.extname(file)))
|
||||
.filter(file => included(file))
|
||||
.map(file => file.replace(/^static/i, ''));
|
||||
|
||||
let images = dir.files('./static/images/', {sync: true})
|
||||
.filter(file => ['.png', '.jpg', '.svg', '.gif'].includes(path.extname(file)))
|
||||
.filter(file => included(file))
|
||||
.map(file => file.replace(/^static/i, ''));
|
||||
|
||||
let fonts = dir.files('./static/fonts/', {sync: true})
|
||||
.filter(file => ['.ttf'].includes(path.extname(file)))
|
||||
.filter(file => included(file))
|
||||
.map(file => file.replace(/^static/i, ''));
|
||||
|
||||
let audios = dir.files('./static/audio/', {sync: true})
|
||||
.filter(file => ['.mp3', '.wav'].includes(path.extname(file)))
|
||||
.filter(file => included(file))
|
||||
.map(file => file.replace(/^static/i, ''));
|
||||
|
||||
let empties = dir.files('./static/empty/', {sync: true})
|
||||
.filter(file => included(file))
|
||||
.map(file => file.replace(/^static/i, ''));
|
||||
|
||||
|
||||
let assets = {remote_files, images, audios, fonts, empties};
|
||||
for(let key of Object.keys(assets)){
|
||||
console.log('let ' + key + ' = ' + JSON.stringify(assets[key]) + ';\n');
|
||||
}
|
||||
})()
|
||||
|
||||
function included(asset){
|
||||
let basename = path.basename(asset);
|
||||
for(let file of source_files){
|
||||
let content = fs.readFileSync(file, 'utf-8');
|
||||
if(content.includes(basename)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
16
gen/imports.js
Normal file
16
gen/imports.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import path from 'path';
|
||||
import dir from 'node-dir';
|
||||
|
||||
(async () => {
|
||||
let files = dir.files('./src/routes/', {sync: true}).filter(file => path.extname(file) == '.svelte');
|
||||
let statements = '';
|
||||
for(let file of files){
|
||||
let import_path = file.split('src/routes/').join('./')
|
||||
statements = statements + `
|
||||
else if(url == '${import_path}'){
|
||||
page = (await import('${import_path}')).default;
|
||||
|
||||
}`
|
||||
}
|
||||
console.log(statements);
|
||||
})()
|
||||
7544
package-lock.json
generated
Normal file
7544
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
package.json
Normal file
49
package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "web-svelte",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"package": "svelte-kit package",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jhubbardsf/svelte-sortablejs": "^1.1.0",
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/adapter-node": "^1.0.0-next.94",
|
||||
"@sveltejs/kit": "next",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"svelte": "^3.44.0",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tailwindcss": "^3.1.5",
|
||||
"vite": "^3.0.4"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^7.5.0",
|
||||
"@tailwindcss/line-clamp": "^0.4.2",
|
||||
"@types/lodash": "^4.14.186",
|
||||
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
||||
"axios": "^0.27.2",
|
||||
"bcrypt": "^5.0.1",
|
||||
"buffer": "^6.0.3",
|
||||
"build-url": "^6.0.1",
|
||||
"docx": "^7.7.0",
|
||||
"dragselect": "^2.5.5",
|
||||
"fast-fuzzy": "^1.11.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"idb-keyval": "^6.2.0",
|
||||
"is-valid-http-url": "^1.0.3",
|
||||
"jsdom": "^20.0.3",
|
||||
"jszip": "^3.10.1",
|
||||
"link-preview-js": "^3.0.4",
|
||||
"lodash": "^4.17.21",
|
||||
"node-dir": "^0.1.17",
|
||||
"short-uuid": "^4.2.2",
|
||||
"srt-webvtt": "^2.0.0",
|
||||
"svelte-range-slider-pips": "^2.0.3",
|
||||
"tailwindcss-scoped-groups": "^2.0.0"
|
||||
}
|
||||
}
|
||||
13
postcss.config.cjs
Normal file
13
postcss.config.cjs
Normal file
@@ -0,0 +1,13 @@
|
||||
const tailwindcss = require("tailwindcss");
|
||||
const autoprefixer = require("autoprefixer");
|
||||
|
||||
const config = {
|
||||
plugins: [
|
||||
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
||||
tailwindcss(),
|
||||
//But others, like autoprefixer, need to run after,
|
||||
autoprefixer,
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
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);
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user