Update a bunch of libraries to typescript

This commit is contained in:
2024-08-30 15:16:01 +02:00
parent c152f36f49
commit c75cbb2e3f
18 changed files with 370 additions and 222 deletions

View File

@@ -10,7 +10,8 @@
"dependencies": { "dependencies": {
"behave-js": "^1.5.0", "behave-js": "^1.5.0",
"chart.js": "^4.2.0", "chart.js": "^4.2.0",
"pure-color": "^1.3.0" "pure-color": "^1.3.0",
"tslib": "^2.7.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.22.20", "@babel/core": "^7.22.20",
@@ -19,6 +20,7 @@
"@rollup/plugin-commonjs": "^25.0.0", "@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-node-resolve": "^15.2.1", "@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-terser": "^0.4.3", "@rollup/plugin-terser": "^0.4.3",
"@rollup/plugin-typescript": "^11.1.6",
"@types/jsmediatags": "^3.9.3", "@types/jsmediatags": "^3.9.3",
"rollup": "^3.0.0", "rollup": "^3.0.0",
"rollup-plugin-livereload": "^2.0.0", "rollup-plugin-livereload": "^2.0.0",
@@ -1838,11 +1840,39 @@
} }
} }
}, },
"node_modules/@rollup/pluginutils": { "node_modules/@rollup/plugin-typescript": {
"version": "5.0.4", "version": "11.1.6",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.6.tgz",
"integrity": "sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==", "integrity": "sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.1.0",
"resolve": "^1.22.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.14.0||^3.0.0||^4.0.0",
"tslib": "*",
"typescript": ">=3.7.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
},
"tslib": {
"optional": true
}
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
"integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
"dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@types/estree": "^1.0.0", "@types/estree": "^1.0.0",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
@@ -1852,7 +1882,7 @@
"node": ">=14.0.0" "node": ">=14.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0" "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"rollup": { "rollup": {
@@ -3062,6 +3092,27 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/unicode-canonical-property-names-ecmascript": { "node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",

View File

@@ -13,6 +13,7 @@
"@rollup/plugin-commonjs": "^25.0.0", "@rollup/plugin-commonjs": "^25.0.0",
"@rollup/plugin-node-resolve": "^15.2.1", "@rollup/plugin-node-resolve": "^15.2.1",
"@rollup/plugin-terser": "^0.4.3", "@rollup/plugin-terser": "^0.4.3",
"@rollup/plugin-typescript": "^11.1.6",
"@types/jsmediatags": "^3.9.3", "@types/jsmediatags": "^3.9.3",
"rollup": "^3.0.0", "rollup": "^3.0.0",
"rollup-plugin-livereload": "^2.0.0", "rollup-plugin-livereload": "^2.0.0",
@@ -22,6 +23,7 @@
"dependencies": { "dependencies": {
"behave-js": "^1.5.0", "behave-js": "^1.5.0",
"chart.js": "^4.2.0", "chart.js": "^4.2.0",
"pure-color": "^1.3.0" "pure-color": "^1.3.0",
"tslib": "^2.7.0"
} }
} }

View File

@@ -4,6 +4,7 @@ import commonjs from '@rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload'; import livereload from 'rollup-plugin-livereload';
import terser from '@rollup/plugin-terser'; import terser from '@rollup/plugin-terser';
import babel from '@rollup/plugin-babel' import babel from '@rollup/plugin-babel'
import typescript from '@rollup/plugin-typescript'
const production = !process.env.ROLLUP_WATCH; const production = !process.env.ROLLUP_WATCH;
@@ -57,6 +58,7 @@ export default [
}), }),
commonjs(), commonjs(),
nodeResolve(), nodeResolve(),
typescript({ compilerOptions: { lib: ["es2015", "dom"] } }),
// In dev mode, call `npm run start` once // In dev mode, call `npm run start` once
// the bundle has been generated // the bundle has been generated

View File

@@ -1,6 +1,6 @@
<script> <script>
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { swipe_nav } from "./SwipeNavigate.svelte"; import { swipe_nav } from "./SwipeNavigate";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()
export const set_file = f => { export const set_file = f => {
@@ -65,11 +65,7 @@ const mouseup = (e) => {
} }
} }
let swipe_style = "" const on_load = () => dispatch("loading", false)
const on_load = () => {
dispatch("loading", false)
swipe_style = ""
}
</script> </script>
<svelte:window on:mousemove={mousemove} on:mouseup={mouseup} /> <svelte:window on:mousemove={mousemove} on:mouseup={mouseup} />
@@ -78,8 +74,7 @@ const on_load = () => {
bind:this={container} bind:this={container}
class="container" class="container"
class:zoom class:zoom
use:swipe_nav={!zoom && is_list} use:swipe_nav={{enabled: !zoom && is_list}}
on:style={e => swipe_style = e.detail}
on:prev on:prev
on:next on:next
> >
@@ -92,9 +87,9 @@ const on_load = () => {
on:mousedown={mousedown} on:mousedown={mousedown}
class="image" class="image"
class:zoom class:zoom
style={swipe_style}
src={file.get_href} src={file.get_href}
alt={file.name} /> alt={file.name}
/>
</div> </div>
<style> <style>

View File

@@ -1,41 +1,42 @@
<script context="module">
// Dead zone before the swipe action gets detected // Dead zone before the swipe action gets detected
const swipe_inital_offset = 25 const swipe_inital_offset = 25
// Amount of pixels after which the navigation triggers // Amount of pixels after which the navigation triggers
const swipe_trigger_offset = 75 const swipe_trigger_offset = 75
export const swipe_nav = (node, swipe_enabled) => { export const swipe_nav = (node: HTMLElement, props: { enabled: boolean, previous: boolean, next: boolean }) => {
let start_x = 0 let start_x = 0
let start_y = 0 let start_y = 0
let render_offset = 0 let render_offset = 0
let enabled = swipe_enabled let enabled = props.enabled === undefined ? true : props.enabled
let previous = props.previous === undefined ? true : props.previous
let next = props.next === undefined ? true : props.next
const touchstart = e => { const touchstart = (e: TouchEvent) => {
start_x = e.touches[0].clientX start_x = e.touches[0].clientX
start_y = e.touches[0].clientY start_y = e.touches[0].clientY
render_offset = 0 render_offset = 0
} }
const touchmove = e => { const touchmove = (e: TouchEvent) => {
if (!enabled) { const offset_x = e.touches[0].clientX - start_x
if (!enabled || (offset_x < 0 && !next) || (offset_x > 0 && !previous)) {
return return
} }
const offset_x = e.touches[0].clientX - start_x
const abs_x = Math.abs(offset_x) const abs_x = Math.abs(offset_x)
const abs_y = Math.abs(e.touches[0].clientY - start_y) const abs_y = Math.abs(e.touches[0].clientY - start_y)
const neg = offset_x < 0 ? -1 : 1 const neg = offset_x < 0 ? -1 : 1
// The cursor must have moved at least 50 pixels and three times as much // The cursor must have moved at least 50 pixels and three times as much
// on the x axis than the y axis for it to count as a swipe // on the x axis than the y axis for it to count as a swipe
if (abs_x > swipe_inital_offset && abs_y < abs_x/3) { if (abs_x > swipe_inital_offset && abs_y < abs_x / 3) {
set_offset((abs_x-swipe_inital_offset)*neg, false) set_offset((abs_x - swipe_inital_offset) * neg, false)
} else { } else {
set_offset(0, true) set_offset(0, true)
} }
} }
const touchend = e => { const touchend = (e: TouchEvent) => {
if (!enabled) { if (!enabled) {
return return
} }
@@ -51,32 +52,39 @@ export const swipe_nav = (node, swipe_enabled) => {
} }
} }
const set_offset = (off, animate) => { const set_offset = (off: number, animate: boolean) => {
render_offset = off render_offset = off
let detail = "transform: translateX("+off+"px);"
if (animate) {
detail += "transition: transform 400ms;"
}
// Clear the transformation if the offset is zero
if (off === 0) { if (off === 0) {
detail = "" // Clear the transformation if the offset is zero
node.style.transform = ""
node.style.transition = ""
} else {
node.style.transform = "translateX(" + off + "px)"
if (animate) {
node.style.transition = "transform 400ms"
}
} }
node.dispatchEvent(new CustomEvent("style", {detail: detail}))
} }
node.addEventListener("touchstart", touchstart) node.addEventListener("touchstart", touchstart)
node.addEventListener("touchmove", touchmove) node.addEventListener("touchmove", touchmove)
node.addEventListener("touchend", touchend) node.addEventListener("touchend", touchend)
return { // Get the child image so we can listen for the loaded event. When the
update(swipe_enabled) { // loaded event fires we clear the transformations so that the image appears
enabled = swipe_enabled // in the original position again
if (!enabled) { for (let i = 0; i < node.childNodes.length; i++) {
render_offset = 0 const child = node.childNodes.item(i)
if (child instanceof HTMLImageElement) {
child.addEventListener("load", () => set_offset(0, false))
} }
}
return {
update(enabled: boolean) {
// enabled = swipe_enabled
set_offset(0, false)
}, },
destroy() { destroy() {
node.removeEventListener("touchstart", touchstart) node.removeEventListener("touchstart", touchstart)
@@ -85,4 +93,3 @@ export const swipe_nav = (node, swipe_enabled) => {
} }
} }
} }
</script>

View File

@@ -8,10 +8,10 @@ import DetailsWindow from './DetailsWindow.svelte';
import FilePreview from './viewers/FilePreview.svelte'; import FilePreview from './viewers/FilePreview.svelte';
import SearchView from './SearchView.svelte'; import SearchView from './SearchView.svelte';
import UploadWidget from './upload_widget/UploadWidget.svelte'; import UploadWidget from './upload_widget/UploadWidget.svelte';
import { fs_path_url } from './FilesystemUtil.js'; import { fs_path_url } from './FilesystemUtil';
import { branding_from_path } from './edit_window/Branding.js' import { branding_from_path } from './edit_window/Branding.js'
import Menu from './Menu.svelte'; import Menu from './Menu.svelte';
import { Navigator } from "./Navigator.js" import { Navigator } from "./Navigator"
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
let file_viewer let file_viewer

View File

@@ -1,124 +0,0 @@
import { fs_path_url } from './FilesystemUtil.js'
export const fs_check_response = async resp => {
let text = await resp.text()
if (resp.status >= 400) {
let error
try {
error = JSON.parse(text)
} catch (err) {
error = text
}
throw error
}
return JSON.parse(text)
}
export const fs_mkdir = async (path, opts = null) => {
const form = new FormData()
form.append("action", "mkdir")
if (opts && opts.mode) {
form.append("mode", opts.mode)
}
return await fs_check_response(
await fetch(fs_path_url(path), { method: "POST", body: form })
)
}
export const fs_mkdirall = async (path, opts = null) => {
const form = new FormData()
form.append("action", "mkdirall")
if (opts && opts.mode) {
form.append("mode", opts.mode)
}
return await fs_check_response(
await fetch(fs_path_url(path), { method: "POST", body: form })
)
}
export const fs_get_node = async path => {
return await fs_check_response(
await fetch(fs_path_url(path) + "?stat")
)
}
// Updates a node's parameters. Available options are:
// - created, Date object
// - modified, Date object
// - mode, file mode formatted as octal string
// - shared, boolean. If true the node will receive a public ID
//
// Returns the modified filesystem node object
export const fs_update = async (path, opts) => {
const form = new FormData()
form.append("action", "update")
for (let key of Object.keys(opts)) {
if (key === "created" || key === "modified") {
form.append(key, opts[key].toISOString())
} else {
form.append(key, opts[key])
}
}
return await fs_check_response(
await fetch(fs_path_url(path), { method: "POST", body: form })
)
}
export const fs_rename = async (old_path, new_path) => {
const form = new FormData()
form.append("action", "rename")
form.append("target", new_path)
return await fs_check_response(
await fetch(fs_path_url(old_path), { method: "POST", body: form })
)
}
export const fs_delete = async path => {
return await fs_check_response(
await fetch(fs_path_url(path), { method: "DELETE" })
)
}
export const fs_delete_all = async path => {
return await fs_check_response(
await fetch(fs_path_url(path) + "?recursive", { method: "DELETE" })
)
}
export const fs_search = async (path, term, limit = 10) => {
return await fs_check_response(
await fetch(
fs_path_url(path) +
"?search=" + encodeURIComponent(term) +
"&limit=" + limit
)
)
}
export const fs_timeseries = async (path, start, end, interval = 60) => {
return await fs_check_response(
await fetch(
fs_path_url(path) +
"?timeseries" +
"&start=" + start.toISOString() +
"&end=" + end.toISOString() +
"&interval=" + interval
)
)
}
export const fs_import = async (parent_dir_path = "", filelist = []) => {
const form = new FormData()
form.append("action", "import")
form.append("files", JSON.stringify(filelist))
return await fs_check_response(
await fetch(fs_path_url(parent_dir_path), { method: "POST", body: form })
)
}

View File

@@ -0,0 +1,184 @@
import { fs_path_url } from './FilesystemUtil'
export type GenericResponse = {
value: string,
message: string,
}
export const fs_check_response = async (resp: Response) => {
let text = await resp.text()
if (resp.status >= 400) {
let error: any
try {
error = JSON.parse(text) as GenericResponse
} catch (err) {
error = text
}
throw error
}
return JSON.parse(text)
}
export type NodeOptions = {
mode: number | undefined,
created: Date | undefined,
modified: Date | undefined,
shared: boolean | undefined,
branding_enabled: boolean | undefined,
brand_input_color: string | undefined,
brand_highlight_color: string | undefined,
brand_danger_color: string | undefined,
brand_background_color: string | undefined,
brand_body_color: string | undefined,
brand_card_color: string | undefined,
brand_header_image: string | undefined,
brand_header_link: string | undefined,
brand_background_image: string | undefined,
}
// mkdir only supports the "mode" option
export const fs_mkdir = async (path: string, opts: NodeOptions) => {
const form = new FormData()
form.append("action", "mkdir")
if (opts && opts.mode) {
form.append("mode", opts.mode.toFixed(0))
}
return await fs_check_response(
await fetch(fs_path_url(path), { method: "POST", body: form })
)
}
export const fs_mkdirall = async (path: string, opts: NodeOptions) => {
const form = new FormData()
form.append("action", "mkdirall")
if (opts && opts.mode) {
form.append("mode", opts.mode.toFixed(0))
}
return await fs_check_response(
await fetch(fs_path_url(path), { method: "POST", body: form })
)
}
export type FSPath = {
path: Array<FSNode>,
base_index: number,
children: Array<FSNode>,
permissions: FSPermissions,
}
export type FSNode = {
type: string,
path: string,
name: string,
created: Date,
modified: Date,
mode_string: string,
mode_octal: string,
abuse_type: string | undefined,
abuse_report_time: Date | undefined,
file_size: number,
file_type: string,
sha256_sum: string,
id: string | undefined,
read_password: string | undefined,
write_password: string | undefined,
properties: {} | undefined,
}
export type FSPermissions = {
create: boolean,
read: boolean,
update: boolean,
delete: boolean,
}
export const fs_get_node = async (path: string) => {
return await fs_check_response(
await fetch(fs_path_url(path) + "?stat")
) as FSPath
}
// Updates a node's parameters. Available options are:
// - created, Date object
// - modified, Date object
// - mode, file mode formatted as octal string
// - shared, boolean. If true the node will receive a public ID
//
// Returns the modified filesystem node object
export const fs_update = async (path: string, opts: NodeOptions) => {
const form = new FormData()
form.append("action", "update")
for (let key of Object.keys(opts)) {
if ((key === "created" || key === "modified") && opts[key] !== undefined) {
form.append(key, opts[key].toISOString())
} else {
form.append(key, opts[key])
}
}
return await fs_check_response(
await fetch(fs_path_url(path), { method: "POST", body: form })
) as FSNode
}
export const fs_rename = async (old_path: string, new_path: string) => {
const form = new FormData()
form.append("action", "rename")
form.append("target", new_path)
return await fs_check_response(
await fetch(fs_path_url(old_path), { method: "POST", body: form })
) as FSNode
}
export const fs_delete = async (path: string) => {
return await fs_check_response(
await fetch(fs_path_url(path), { method: "DELETE" })
) as GenericResponse
}
export const fs_delete_all = async (path: string) => {
return await fs_check_response(
await fetch(fs_path_url(path) + "?recursive", { method: "DELETE" })
) as GenericResponse
}
export const fs_search = async (path: string, term: string, limit = 10) => {
return await fs_check_response(
await fetch(
fs_path_url(path) +
"?search=" + encodeURIComponent(term) +
"&limit=" + limit
)
) as Array<string>
}
export const fs_timeseries = async (path: string, start: Date, end: Date, interval = 60) => {
return await fs_check_response(
await fetch(
fs_path_url(path) +
"?timeseries" +
"&start=" + start.toISOString() +
"&end=" + end.toISOString() +
"&interval=" + interval
)
)
}
export const fs_import = async (parent_dir_path = "", filelist: Array<string>) => {
const form = new FormData()
form.append("action", "import")
form.append("files", JSON.stringify(filelist))
return await fs_check_response(
await fetch(fs_path_url(parent_dir_path), { method: "POST", body: form })
) as GenericResponse
}

View File

@@ -1,9 +1,11 @@
export const fs_split_path = path => { import { FSNode } from "./FilesystemAPI"
export const fs_split_path = (path: string) => {
let patharr = path.split("/") let patharr = path.split("/")
return { base: patharr.pop(), parent: patharr.join("/") } return { base: patharr.pop(), parent: patharr.join("/") }
} }
export const fs_encode_path = path => { export const fs_encode_path = (path: string) => {
// Encode all path elements separately to preserve forward slashes // Encode all path elements separately to preserve forward slashes
let split = path.split("/") let split = path.split("/")
for (let i = 0; i < split.length; i++) { for (let i = 0; i < split.length; i++) {
@@ -12,21 +14,26 @@ export const fs_encode_path = path => {
return split.join("/") return split.join("/")
} }
export const fs_path_url = path => { export const fs_path_url = (path: string) => {
if (!path || path.length === 0) { if (!path || path.length === 0) {
return "" return ""
} }
if (path[0] !== "/") { if (path[0] !== "/") {
path = "/" + path path = "/" + path
} }
return window.api_endpoint + "/filesystem" + fs_encode_path(path)
if (window["api_endpoint"] !== undefined) {
return window["api_endpoint"] + "/filesystem" + fs_encode_path(path)
} else {
throw Error("api_endpoint is undefined")
}
} }
export const fs_thumbnail_url = (path, width = 64, height = 64) => { export const fs_thumbnail_url = (path: string, width = 64, height = 64) => {
return fs_path_url(path) + "?thumbnail&width=" + width + "&height=" + height return fs_path_url(path) + "?thumbnail&width=" + width + "&height=" + height
} }
export const fs_node_type = node => { export const fs_node_type = (node: FSNode) => {
if (node.type === "dir") { if (node.type === "dir") {
return "dir" return "dir"
} else if (node.file_type === "application/bittorrent" || node.file_type === "application/x-bittorrent") { } else if (node.file_type === "application/bittorrent" || node.file_type === "application/x-bittorrent") {
@@ -62,7 +69,7 @@ export const fs_node_type = node => {
} }
} }
export const fs_node_icon = (node, width = 64, height = 64) => { export const fs_node_icon = (node: FSNode, width = 64, height = 64) => {
if (node.type === "dir") { if (node.type === "dir") {
// Folders with an ID are publically shared, use the shared folder icon // Folders with an ID are publically shared, use the shared folder icon
if (node.id) { if (node.id) {

View File

@@ -1,15 +1,16 @@
import { fs_get_node } from "./FilesystemAPI"; import { fs_get_node, FSNode, FSPath, FSPermissions } from "./FilesystemAPI";
import { fs_encode_path, fs_split_path } from "./FilesystemUtil"; import { fs_encode_path, fs_split_path } from "./FilesystemUtil";
import { Writable } from "svelte/store"
export class Navigator { export class Navigator {
// Parts of the raw API response // Parts of the raw API response
path = [] path: Array<FSNode> = []
base_index = 0 base_index: number = 0
children = [] children: Array<FSNode> = []
permissions = {} permissions: FSPermissions = <FSPermissions>{}
// base equals path[base_index]. It's updated every time the path updates // base equals path[base_index]. It's updated every time the path updates
base = {} base: FSNode = <FSNode>{}
// Initialized will be set to true when the first file or directory is loaded // Initialized will be set to true when the first file or directory is loaded
initialized = false initialized = false
@@ -36,9 +37,9 @@ export class Navigator {
// If you set the loading property to a boolean writable store the navigator // If you set the loading property to a boolean writable store the navigator
// will use it to publish its loading states // will use it to publish its loading states
loading = null loading: Writable<boolean> | null = null
set_loading(b) { set_loading(b: boolean) {
if (this.loading !== null && this.loading.set !== undefined) { if (this.loading !== null) {
this.loading.set(b) this.loading.set(b)
} }
} }
@@ -46,20 +47,18 @@ export class Navigator {
// The Navigator acts as a svelte store. This allows for DOM reactivity. // The Navigator acts as a svelte store. This allows for DOM reactivity.
// This works by implementing the store contract: // This works by implementing the store contract:
// https://svelte.dev/docs/svelte-components#script-4-prefix-stores-with-$-to-access-their-values // https://svelte.dev/docs/svelte-components#script-4-prefix-stores-with-$-to-access-their-values
subscribers = [] subscribers: Array<(nav: Navigator) => void> = []
subscribe(sub_func) { subscribe(sub_func: (nav: Navigator) => void) {
// Immediately return the current value // Immediately return the current value
sub_func(this) sub_func(this)
this.subscribers.push(sub_func) this.subscribers.push(sub_func)
// Return the unsubscribe function // Return the unsubscribe function
return () => { return () => this.subscribers.splice(this.subscribers.indexOf(sub_func), 1)
this.subscribers.splice(this.subscribers.indexOf(sub_func), 1)
}
} }
async navigate(path, push_history) { async navigate(path: string, push_history: boolean) {
if (path[0] !== "/") { if (path[0] !== "/") {
path = "/" + path path = "/" + path
} }
@@ -90,7 +89,7 @@ export class Navigator {
async navigate_up() { async navigate_up() {
if (this.path.length > 1) { if (this.path.length > 1) {
await this.navigate(this.path[this.path.length - 2].path) await this.navigate(this.path[this.path.length - 2].path, false)
} }
} }
@@ -98,7 +97,7 @@ export class Navigator {
await this.navigate(this.base.path, false) await this.navigate(this.base.path, false)
} }
open_node(node, push_history) { open_node(node: FSPath, push_history: boolean) {
// Update window title and navigation history. If push_history is false // Update window title and navigation history. If push_history is false
// we still replace the URL with replaceState. This way the user is not // we still replace the URL with replaceState. This way the user is not
// greeted to a 404 page when refreshing after renaming a file // greeted to a 404 page when refreshing after renaming a file
@@ -148,7 +147,7 @@ export class Navigator {
// directory is still the same. If it's different the siblings array is not // directory is still the same. If it's different the siblings array is not
// used // used
cached_siblings_path = "" cached_siblings_path = ""
cached_siblings = null cached_siblings: Array<FSNode> | null = null
async get_siblings() { async get_siblings() {
// Check if we already have siblings cached // Check if we already have siblings cached
@@ -173,13 +172,12 @@ export class Navigator {
// Opens a sibling of the currently open file. The offset is relative to the // Opens a sibling of the currently open file. The offset is relative to the
// file which is currently open. Give a positive number to move forward and // file which is currently open. Give a positive number to move forward and
// a negative number to move backward // a negative number to move backward
async open_sibling(offset) { async open_sibling(offset: number) {
if (this.path.length <= 1) { if (this.path.length <= 1) {
return return
} }
let siblings: Array<FSNode>
let siblings
try { try {
this.set_loading(true) this.set_loading(true)
siblings = await this.get_siblings() siblings = await this.get_siblings()
@@ -191,7 +189,7 @@ export class Navigator {
this.set_loading(false) this.set_loading(false)
} }
let next_sibling = null let next_sibling: FSNode | null = null
if (this.shuffle) { if (this.shuffle) {
// Shuffle is on, pick a random sibling // Shuffle is on, pick a random sibling
@@ -230,7 +228,7 @@ export class Navigator {
} }
} }
const sort_children = (children) => { const sort_children = (children: Array<FSNode>) => {
children.sort((a, b) => { children.sort((a, b) => {
// Sort directories before files // Sort directories before files
if (a.type !== b.type) { if (a.type !== b.type) {

View File

@@ -22,7 +22,7 @@ const delete_file = async e => {
alert(err) alert(err)
return return
} finally { } finally {
nac.set_loading(false) nav.set_loading(false)
} }
if (open_after_edit) { if (open_after_edit) {
@@ -32,7 +32,6 @@ const delete_file = async e => {
} }
visible = false visible = false
} }
</script> </script>
<h2>File settings</h2> <h2>File settings</h2>

View File

@@ -1,5 +1,5 @@
<script> <script>
import { fs_delete_all, fs_rename } from './../FilesystemAPI.js' import { fs_delete_all, fs_rename } from './../FilesystemAPI.ts'
import { createEventDispatcher, onMount } from 'svelte' import { createEventDispatcher, onMount } from 'svelte'
import CreateDirectory from './CreateDirectory.svelte' import CreateDirectory from './CreateDirectory.svelte'
import ListView from './ListView.svelte' import ListView from './ListView.svelte'
@@ -103,7 +103,9 @@ const delete_selected = async () => {
// Save all promises with deletion requests in an array // Save all promises with deletion requests in an array
let promises = [] let promises = []
nav.children.forEach(child => { nav.children.forEach(child => {
if (!child.fm_selected) { return } if (!child.fm_selected) {
return
}
promises.push(fs_delete_all(child.path)) promises.push(fs_delete_all(child.path))
}) })

View File

@@ -96,7 +96,7 @@ export let hide_branding = false
border-bottom: 1px solid var(--separator); border-bottom: 1px solid var(--separator);
} }
.node:hover:not(.node_selected) { .node:hover:not(.node_selected) {
background: var(--input_background); background: var(--input_hover_background);
color: var(--input_text); color: var(--input_text);
text-decoration: none; text-decoration: none;
} }

View File

@@ -13,12 +13,39 @@ const paste = (e) => {
} }
} }
const can_upload = e => {
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
return true
}
for (let i = 0; i < e.dataTransfer.items.length; i++) {
if (e.dataTransfer.items[i].kind === "file") {
return true
}
}
return false
}
const dragover = e => {
if (can_upload(e)) {
e.stopPropagation();
e.preventDefault();
dragging = true
console.log(e)
}
}
const dragleave = e => {
dragging = false
}
const drop = async e => { const drop = async e => {
dragging = false; dragging = false;
if (e.dataTransfer.files || e.dataTransfer.items) { if (can_upload(e)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} else {
return
} }
// if directory support is available // if directory support is available
@@ -26,7 +53,7 @@ const drop = async e => {
for (let i = 0; i < e.dataTransfer.items.length; i++) { for (let i = 0; i < e.dataTransfer.items.length; i++) {
let entry = await e.dataTransfer.items[i].webkitGetAsEntry(); let entry = await e.dataTransfer.items[i].webkitGetAsEntry();
if (entry) { if (entry) {
await read_dir_recursive(entry); read_dir_recursive(entry);
} }
} }
} else if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) { } else if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
@@ -50,9 +77,9 @@ const read_dir_recursive = item => {
</script> </script>
<svelte:window <svelte:window
on:dragover|preventDefault|stopPropagation={() => { dragging = true }} on:dragover={dragover}
on:dragenter|preventDefault|stopPropagation={() => { dragging = true }} on:dragenter={dragover}
on:dragleave|preventDefault|stopPropagation={() => { dragging = false }} on:dragleave={dragleave}
on:drop={drop} on:drop={drop}
on:paste={paste} on:paste={paste}
/> />

View File

@@ -1,6 +1,6 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import { swipe_nav } from "../../file_viewer/viewers/SwipeNavigate.svelte"; import { swipe_nav } from "../../file_viewer/viewers/SwipeNavigate.ts";
import { fs_path_url } from "../FilesystemUtil"; import { fs_path_url } from "../FilesystemUtil";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()
@@ -15,11 +15,7 @@ export const update = () => {
dispatch("loading", true) dispatch("loading", true)
} }
let swipe_style = "" const on_load = () => dispatch("loading", false)
const on_load = () => {
dispatch("loading", false)
swipe_style = ""
}
const mousedown = (e) => { const mousedown = (e) => {
if (!dragging && e.which === 1 && zoom) { if (!dragging && e.which === 1 && zoom) {
@@ -62,8 +58,7 @@ const mouseup = (e) => {
bind:this={container} bind:this={container}
class="container" class="container"
class:zoom class:zoom
use:swipe_nav={!zoom} use:swipe_nav={{enabled: !zoom, previous: false, next: true}}
on:style={e => swipe_style = e.detail}
on:prev={() => nav.open_sibling(-1)} on:prev={() => nav.open_sibling(-1)}
on:next={() => nav.open_sibling(1)} on:next={() => nav.open_sibling(1)}
> >
@@ -76,9 +71,9 @@ const mouseup = (e) => {
on:error={on_load} on:error={on_load}
class="image" class="image"
class:zoom class:zoom
style={swipe_style}
src={fs_path_url($nav.base.path)} src={fs_path_url($nav.base.path)}
alt="no description available" /> alt="no description available"
/>
</div> </div>
<style> <style>

View File

@@ -90,6 +90,7 @@ header > h1 {
.header_image_container { .header_image_container {
text-align: initial; text-align: initial;
margin: auto; margin: auto;
margin-bottom: 1.5em; /*Offset for menu button*/
height: 150px; height: 150px;
width: 500px; width: 500px;
max-width: 100%; max-width: 100%;

View File

@@ -408,7 +408,7 @@ const node_click = (index) => {
transition: background 0.2s; transition: background 0.2s;
} }
.node:hover:not(.node_selected) { .node:hover:not(.node_selected) {
background: var(--input_background); background: var(--input_hover_background);
color: var(--input_text); color: var(--input_text);
text-decoration: none; text-decoration: none;
} }

View File

@@ -557,7 +557,8 @@ footer, .footer_content,
.file, .file_button, .file, .file_button,
.upload_task, .upload_task,
.add_button, .add_button,
.expandable { .expandable,
.upload_widget {
border: none !important; border: none !important;
border-radius: 0 !important; border-radius: 0 !important;
box-shadow: inset -1px -1px #0a0a0a, box-shadow: inset -1px -1px #0a0a0a,
@@ -569,7 +570,8 @@ footer, .footer_content,
padding: 3px !important; padding: 3px !important;
} }
.window > .header, .window > .header,
.headerbar { .headerbar,
.upload_widget > .header {
background: linear-gradient(90deg,navy,#1084d0) !important; background: linear-gradient(90deg,navy,#1084d0) !important;
color: #fff !important; color: #fff !important;
} }