Add path to bookmarks, add logout button

This commit is contained in:
2025-10-10 00:12:14 +02:00
parent 06d04a1abc
commit 9a72c85019
18 changed files with 206 additions and 647 deletions

View File

@@ -4,8 +4,9 @@ import { copy_text } from "util/Util.svelte";
import FileStats from "./FileStats.svelte";
import type { FSNavigator } from "./FSNavigator";
import EditWindow from "./edit_window/EditWindow.svelte";
import { fs_share_url } from "lib/FilesystemAPI";
import { fs_share_url, path_is_shared } from "lib/FilesystemAPI";
import ShareDialog from "./ShareDialog.svelte";
import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bookmarks";
let dispatch = createEventDispatcher()
@@ -14,10 +15,9 @@ export let details_visible = false
export let edit_window: EditWindow
export let edit_visible = false
let share_dialog: ShareDialog
$: share_url = fs_share_url($nav.path)
let link_copied = false
export const copy_link = () => {
const share_url = fs_share_url($nav.path)
if (share_url === "") {
edit_window.edit(nav.base, true, "share")
return
@@ -50,7 +50,19 @@ export const copy_link = () => {
<span>Download</span>
</button>
{#if share_url !== ""}
{#if is_bookmark($bookmarks_store, $nav.base.id)}
<button on:click={() => bookmark_del($nav.base.id)}>
<i class="icon">bookmark_remove</i>
<span>Bookmark</span>
</button>
{:else}
<button on:click={() => bookmark_add($nav.base)}>
<i class="icon">bookmark_add</i>
<span>Bookmark</span>
</button>
{/if}
{#if path_is_shared($nav.path)}
<button on:click={copy_link} class:button_highlight={link_copied}>
<i class="icon">content_copy</i>
<span><u>C</u>opy link</span>
@@ -58,7 +70,7 @@ export const copy_link = () => {
{/if}
<!-- Share button is enabled when: The browser has a sharing API, or the user can edit the file (to enable sharing)-->
{#if $nav.base.id !== "me" && (navigator.share !== undefined || $nav.permissions.write === true)}
{#if navigator.share !== undefined || $nav.permissions.write === true}
<button on:click={(e) => share_dialog.open(e, nav.path)}>
<i class="icon">share</i>
<span>Share</span>

View File

@@ -7,6 +7,7 @@ import FileOptions from "./FileOptions.svelte";
import SharingOptions from "./SharingOptions.svelte";
import AccessControl from "./AccessControl.svelte";
import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading";
export let nav: FSNavigator
let file: FSNode = {} as FSNode
@@ -64,7 +65,7 @@ const save = async (keep_editing = false) => {
let new_file: FSNode
try {
nav.set_loading(true)
loading_start()
options.branding_enabled = JSON.stringify(branding_enabled)
new_file = await fs_update(file.path, options)
@@ -88,7 +89,7 @@ const save = async (keep_editing = false) => {
}
return
} finally {
nav.set_loading(false)
loading_finish()
}
if (open_after_edit) {

View File

@@ -3,6 +3,7 @@ import Button from "layout/Button.svelte";
import { fs_delete_all, type FSNode } from "lib/FilesystemAPI";
import PathLink from "filesystem/util/PathLink.svelte";
import type { FSNavigator } from "filesystem/FSNavigator";
import { loading_finish, loading_start } from "lib/Loading";
export let nav: FSNavigator
export let file: FSNode = {} as FSNode
@@ -16,14 +17,14 @@ const delete_file = async (e: MouseEvent) => {
e.preventDefault()
try {
nav.set_loading(true)
loading_start()
await fs_delete_all(file.path)
} catch (err) {
console.error(err)
alert(err)
return
} finally {
nav.set_loading(false)
loading_finish()
}
if (open_after_edit) {

View File

@@ -14,6 +14,7 @@ import FsUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
import EditWindow from "filesystem/edit_window/EditWindow.svelte";
import { FileAction, type FileEvent } from "./FileManagerLib";
import FileMenu from "./FileMenu.svelte";
import { loading_finish, loading_start } from "lib/Loading";
export let nav: FSNavigator
export let upload_widget: FsUploadWidget
@@ -117,9 +118,9 @@ const delete_selected = async () => {
return
}
nav.set_loading(true)
try {
loading_start()
// Save all promises with deletion requests in an array
let promises = []
nav.children.forEach(child => {
@@ -136,7 +137,7 @@ const delete_selected = async () => {
alert("Delete failed: " + err.message + " ("+err.value+")")
} finally {
viewing_mode()
nav.reload()
loading_finish()
}
}
@@ -264,11 +265,10 @@ const move_start = () => {
}
const move_here = async () => {
nav.set_loading(true)
let target_dir = nav.base.path + "/"
const target_dir = nav.base.path + "/"
try {
loading_start()
let promises = []
moving_items.forEach(item => {
console.log("moving", item.path, "to", target_dir + item.name)
@@ -283,6 +283,7 @@ const move_here = async () => {
} finally {
viewing_mode()
nav.reload()
loading_finish()
}
}
@@ -424,6 +425,7 @@ onMount(() => {
padding: 0;
overflow: auto;
display: block;
height: 100%; /* Used for drop target */
}
.width_container {
position: sticky;

View File

@@ -3,49 +3,31 @@ import EditWindow from "filesystem/edit_window/EditWindow.svelte";
import type { FSNavigator } from "filesystem/FSNavigator";
import Button from "layout/Button.svelte";
import Dialog from "layout/Dialog.svelte";
import { bookmark_add, bookmark_del, bookmarks_store } from "lib/Bookmarks";
import { bookmark_add, bookmark_del, bookmarks_store, is_bookmark } from "lib/Bookmarks";
import { fs_download, type FSNode } from "lib/FilesystemAPI";
import { tick } from "svelte";
export let nav: FSNavigator
export let edit_window: EditWindow
let dialog: Dialog
let node: FSNode
let is_bookmark: boolean = false
let node: FSNode = null
export const open = async (n: FSNode, target: EventTarget) => {
node = n
is_bookmark = false
for (const bm of $bookmarks_store) {
console.log(bm)
if (bm.id === n.id) {
is_bookmark = true
break
}
}
// Wait for the view to update, so the dialog gets the proper measurements
await tick()
dialog.open((target as Element).closest("button").getBoundingClientRect())
}
const bookmark = () => {
bookmark_add({
id: node.id,
icon: "folder_shared",
label: node.name,
})
}
</script>
<Dialog bind:this={dialog}>
<div class="menu">
<Button click={() => {dialog.close(); fs_download(node)}} icon="save" label="Download"/>
{#if is_bookmark}
{#if node !== null && is_bookmark($bookmarks_store, node.id)}
<Button click={() => {dialog.close(); bookmark_del(node.id)}} icon="bookmark_remove" label="Remove bookmark"/>
{:else}
<Button click={() => {dialog.close(); bookmark()}} icon="bookmark_add" label="Add bookmark"/>
<Button click={() => {dialog.close(); bookmark_add(node)}} icon="bookmark_add" label="Add bookmark"/>
{/if}
{#if $nav.permissions.write}
<Button click={() => {dialog.close(); edit_window.edit(node, false, "file")}} icon="edit" label="Edit"/>

View File

@@ -1,11 +1,13 @@
import { writable } from "svelte/store"
import { fs_check_response, fs_path_url } from "./FilesystemAPI"
import { fs_check_response, fs_path_url, type FSNode } from "./FilesystemAPI"
import { loading_finish, loading_start } from "lib/Loading"
import { get_user } from "./PixeldrainAPI"
const bookmarks_file = "/me/.fnx/bookmarks.json"
export type Bookmark = {
id: string,
path: string,
icon: string,
label: string,
}
@@ -22,25 +24,54 @@ export let bookmarks_store = writable<Bookmark[]>(
)
export const bookmarks_get = async (): Promise<Bookmark[]> => {
let bookmarks: Bookmark[] = []
try {
bookmarks = await fs_check_response(
await fetch(fs_path_url(bookmarks_file), { cache: "no-store" })
)
} catch (err) {
// If the bookmarks were not found when we return an empty bookmarks
// list
if (err.value !== "path_not_found") {
throw err
loading_start()
const user = await get_user()
if (
user.username === undefined ||
user.username === "" ||
user.subscription.filesystem_access === undefined ||
user.subscription.filesystem_access === false
) {
return []
}
const bookmarks = await fs_check_response(
await fetch(fs_path_url(bookmarks_file), { cache: "no-store" })
) as Bookmark[]
// Sanity checks
for (const bookmark of bookmarks) {
if (typeof bookmark.id !== "string") { bookmark.id = "" }
if (typeof bookmark.path !== "string") { bookmark.path = "" }
if (typeof bookmark.icon !== "string") { bookmark.icon = "" }
if (typeof bookmark.label !== "string") { bookmark.label = "" }
}
console.debug("Fetched", bookmarks.length, "bookmarks:", bookmarks)
bookmarks_store.set(bookmarks)
return bookmarks
} catch (err) {
// If the bookmarks were not found when we return an empty bookmarks
// list
if (
err.value !== "path_not_found" &&
err.value !== "forbidden" &&
err.value !== "authentication_required"
) {
throw err
} else {
return []
}
} finally {
loading_finish()
}
}
export const bookmarks_save = async (bookmarks: Bookmark[]) => {
try {
loading_start()
await fs_check_response(
await fetch(
fs_path_url(bookmarks_file) + "?make_parents=true",
@@ -49,25 +80,32 @@ export const bookmarks_save = async (bookmarks: Bookmark[]) => {
)
console.debug("Saved", bookmarks.length, "bookmarks:", bookmarks)
bookmarks_store.set(bookmarks)
} finally {
loading_finish()
}
}
export const bookmark_add = async (bm: Bookmark): Promise<Bookmark[]> => {
let bookmarks: Bookmark[] = []
export const bookmark_add = async (node: FSNode): Promise<Bookmark[]> => {
try {
loading_start()
// Get bookmarks
bookmarks = await bookmarks_get()
const bookmarks = await bookmarks_get()
// Add new bookmark
bookmarks.push(bm)
bookmarks.push({
id: node.id,
path: node.path,
icon: "bookmark",
label: node.name,
})
// Save new bookmarks
await bookmarks_save(bookmarks)
return bookmarks
} finally {
loading_finish()
}
return bookmarks
}
export const bookmark_del = async (id: string): Promise<Bookmark[]> => {
@@ -94,3 +132,12 @@ export const bookmark_del = async (id: string): Promise<Bookmark[]> => {
}
return bookmarks
}
export const is_bookmark = (bookmarks: Bookmark[], id: string): boolean => {
for (const bm of bookmarks) {
if (bm.id === id) {
return true
}
}
return false
}

View File

@@ -55,6 +55,12 @@ export const drop_target = (
const entry: FileSystemEntry | null = e.dataTransfer.items[i].webkitGetAsEntry();
if (entry !== null) {
read_dir_recursive(entry);
continue
}
const file: File | null = e.dataTransfer.items[i].getAsFile();
if (file !== null) {
args.upload([file]);
continue
}
}
} else if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {

View File

@@ -55,6 +55,14 @@ export const node_is_shared = (node: FSNode): boolean => {
}
return false
}
export const path_is_shared = (path: FSNode[]): boolean => {
for (let i = 0; i < path.length; i++) {
if (node_is_shared(path[i])) {
return true
}
}
return false
}
export type FSNodeProperties = {
branding_enabled?: string,
@@ -364,7 +372,7 @@ export const fs_share_path = (path: FSNode[]): string => {
// Find the last node in the path that has a public ID
for (let i = path.length - 1; i >= 0; i--) {
if (path[i].id !== undefined && path[i].id !== "me") {
if (node_is_shared(path[i])) {
bucket_idx = i
break
}

View File

@@ -132,6 +132,16 @@ export const put_user = async (data: Object) => {
}
}
export const logout_user = async (redirect_path: string) => {
check_response(await fetch(
get_endpoint() + "/user/session",
{ method: "DELETE" },
))
document.cookie = "pd_auth_key=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
window.location.pathname = redirect_path
}
export type VATRate = {
name: string,
vat: number,

View File

@@ -1,3 +1,7 @@
<script>
import Button from "layout/Button.svelte";
import { logout_user } from "lib/PixeldrainAPI";
</script>
<ul>
<li>Username: {window.user.username}</li>
{#if window.user.email === ""}
@@ -26,10 +30,10 @@
Filesystem
</a>
{/if}
<a href="/logout" class="button">
<button on:click={()=> logout_user("/")}>
<i class="icon">logout</i>
Log out
</a>
</button>
</div>
<style>
@@ -37,7 +41,7 @@
display: flex;
flex-wrap: wrap;
}
.button_row > a {
.button_row > * {
flex: 1 1 auto;
}
ul {

View File

@@ -1,150 +0,0 @@
<script>
import { onMount } from "svelte";
import { FSNavigator } from "filesystem/FSNavigator.ts"
import { fs_encode_path, fs_node_icon, node_is_shared } from "lib/FilesystemAPI";
import Button from "layout/Button.svelte";
import CreateDirectory from "filesystem/filemanager/CreateDirectory.svelte";
import FSUploadWidget from "filesystem/upload_widget/FSUploadWidget.svelte";
import { drop_target } from "lib/DropTarget.ts"
const nav = new FSNavigator(false)
let upload_widget
var show_hidden = false
var creating_dir = false
onMount(() => nav.navigate("/me", false))
</script>
<div class="wrapper" use:drop_target={{upload: (files) => upload_widget.upload_files(files)}}>
<div class="toolbar">
{#if $nav.permissions.write}
<Button
click={() => upload_widget.pick_files()}
icon="cloud_upload"
title="Upload files to this directory"
label="Upload files"
/>
<Button
click={() => {creating_dir = !creating_dir}}
highlight={creating_dir}
icon="create_new_folder"
title="Create folder"
label="Create folder"
/>
{/if}
<div class="toolbar_spacer"></div>
<Button
click={() => {show_hidden = !show_hidden}}
highlight={show_hidden}
icon={show_hidden ? "visibility_off" : "visibility"}
title="Show hidden files and directories"
/>
<Button
click={() => nav.reload()}
icon="refresh"
title="Refresh directory listing"
/>
</div>
{#if creating_dir}
<CreateDirectory nav={nav} />
{/if}
<div class="directory">
{#each $nav.children as child (child.path)}
<a
href={"/d"+fs_encode_path(child.path)}
class="node"
class:node_selected={child.fm_selected}
class:hidden={child.name.startsWith(".") && show_hidden === false}
>
<img src={fs_node_icon(child, 64, 64)} class="node_icon" alt="icon"/>
<div class="node_name">
{child.name}
</div>
{#if node_is_shared(child)}
<a href="/d/{child.id}" class="button action_button">
<i class="icon" title="This file / directory is shared. Click to open public link">share</i>
</a>
{/if}
</a>
{/each}
</div>
</div>
<FSUploadWidget nav={nav} bind:this={upload_widget} />
<style>
.wrapper {
border-radius: 4px;
}
.toolbar {
display: flex;
flex-direction: row;
width: 100%;
justify-content: center;
align-items: center;
}
.toolbar > * { flex: 0 0 auto; }
.toolbar_spacer {
flex: 1 1 auto;
text-align: center;
}
.directory {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 6px;
margin-top: 6px;
}
.node {
display: flex;
flex: 1 1 auto;
flex-direction: row;
align-content: center;
align-items: center;
justify-content: center;
text-decoration: none;
color: var(--body_text-color);
gap: 2px;
padding: 2px;
border-radius: 8px;
width: 250px;
max-width: 100%;
border: 1px solid var(--input_background);
}
.node:hover {
background: var(--input_hover_background);
color: var(--input_text);
text-decoration: none;
}
.node > * {
flex: 0 0 auto;
}
.node_icon {
height: 32px;
width: 32px;
vertical-align: middle;
border-radius: 4px;
}
.node_name {
flex: 1 1 auto;
display: flex;
align-items: center;
word-break: break-all;
line-height: 1.2em;
}
.action_button {
margin: 0;
background: none;
color: var(--body_text_color);
box-shadow: none;
}
.hidden {
display: none;
}
</style>

View File

@@ -7,7 +7,6 @@ import CardSubscription from "./CardSubscription.svelte";
import CardUsage from "./CardUsage.svelte";
import CardActivity from "./CardActivity.svelte";
import CardPrepaidTransactions from "./CardPrepaidTransactions.svelte";
import CardFsHome from "./CardFSHome.svelte";
import AddressReputation from "home_page/AddressReputation.svelte";
import { flip } from "svelte/animate";
@@ -60,51 +59,37 @@ const swap_card = (idx1, idx2) => {
}
onMount(() => {
cards = []
if (window.user.subscription.filesystem_access === true) {
cards.push({
id: "filesystem_home",
elem: CardFsHome,
title: "Filesystem home",
link: "/d/me",
})
}
cards.push({
cards = [
{
id: "account",
elem: CardAccount,
title: "Account",
link: "/user/settings",
})
cards.push({
},{
id: "subscription",
elem: CardSubscription,
title: "Subscription",
link: "/user/subscription",
})
if (window.user.subscription.type === "prepaid") {
cards.push({
}, {
id: "prepaid_transactions",
elem: CardPrepaidTransactions,
title: "Prepaid transactions",
link: "/user/prepaid/transactions",
})
}
cards.push({
}, {
id: "usage",
elem: CardUsage,
title: "Usage",
})
cards.push({
}, {
id: "statistics",
elem: CardStatistics,
title: "Statistics",
})
cards.push({
}, {
id: "activity",
elem: CardActivity,
title: "Activity",
link: "/user/activity",
})
}
]
// Apply the view settings from localstorage
try {

View File

@@ -1,51 +0,0 @@
<script>
import { createEventDispatcher } from "svelte";
import { fade } from "svelte/transition";
let dispatch = createEventDispatcher()
let dragging = false
const drop = (e) => {
dragging = false;
if (e.dataTransfer && e.dataTransfer.items.length > 0) {
e.preventDefault()
e.stopPropagation()
dispatch("upload", e.dataTransfer.files)
}
}
const paste = (e) => {
if (e.clipboardData.files.length !== 0) {
e.preventDefault();
e.stopPropagation();
dispatch("upload", e.clipboardData.files)
}
}
</script>
<svelte:window
on:dragover|preventDefault|stopPropagation={() => { dragging = true }}
on:dragenter|preventDefault|stopPropagation={() => { dragging = true }}
on:dragleave|preventDefault|stopPropagation={() => { dragging = false }}
on:drop={drop}
on:paste={paste}
/>
{#if dragging}
<div class="drag_target" transition:fade={{duration: 200}}>
Drop files here to upload them
</div>
{/if}
<style>
.drag_target {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 50px;
font-size: 2em;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 100px;
box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.5);
}
</style>

View File

@@ -1,68 +0,0 @@
// Uploads a file to the logged in user's pixeldrain account. If no user is
// logged in the file is uploaded anonymously.
//
// on_progress reports progress on the file upload, parameter 1 is the uploaded
// file size and parameter 2 is the total file size
//
// on_success is called when the upload is done, the only parameter is the file
// ID
//
// on_error is called when the upload has failed. The parameters are the error
// code and an error message
export const upload_file = (file, name, on_progress, on_success, on_error) => {
// Check the file size limit. For free accounts it's 20 GB
if (window.user.subscription.file_size_limit === 0) {
window.user.subscription.file_size_limit = 20e9
}
if (file.size > window.user.subscription.file_size_limit) {
on_failure(
"file_too_large",
"This file is too large. Check out the Pro subscription to increase the file size limit"
)
return
}
let xhr = new XMLHttpRequest();
xhr.open("PUT", window.api_endpoint + "/file/" + encodeURIComponent(name), true);
xhr.timeout = 86400000; // 24 hours, to account for slow connections
xhr.upload.addEventListener("progress", evt => {
if (on_progress && evt.lengthComputable) {
on_progress(evt.loaded, evt.total)
}
});
xhr.onreadystatechange = () => {
// readystate 4 means the upload is done
if (xhr.readyState !== 4) {
return
}
if (xhr.status >= 100 && xhr.status < 400) {
// Request is a success
on_success(JSON.parse(xhr.response).id)
} else if (xhr.status >= 400) {
// Request failed
console.log("Upload error. status: " + xhr.status + " response: " + xhr.response);
let resp;
if (xhr.status === 429) {
resp = {
value: "too_many_requests",
message: "Too many requests. Please wait a few seconds",
}
} else {
resp = JSON.parse(xhr.response)
}
on_error(resp.value, resp.message)
} else if (xhr.status === 0) {
on_error("request_failed", "Your request did not arrive, check your network connection")
} else {
on_error(xhr.responseText, xhr.responseText)
}
};
xhr.send(file);
}

View File

@@ -1,66 +0,0 @@
<script>
import { createEventDispatcher } from "svelte";
import ProgressBar from "util/ProgressBar.svelte";
import { upload_file } from "util/upload_widget/UploadFunc.js";
let dispatch = createEventDispatcher()
export let job = {
file: null,
name: "",
id: "",
status: "",
}
export let total = 0
export let loaded = 0
let error_code = ""
let error_message = ""
export const start = () => {
upload_file(
job.file,
job.name,
(prog_loaded, prog_total) => {
loaded = prog_loaded
total = prog_total
},
async (id) => {
console.log("finsished", id)
job.status = "finished"
job.id = id
dispatch("finished")
},
(code, message) => {
console.log("error", code, message)
error_code = code
error_message = message
job.status = "error"
dispatch("finished")
},
)
job.status = "uploading"
}
</script>
<div class="upload_progress" class:error={job.status === "error"}>
{job.name}<br/>
{#if error_code !== ""}
{error_message}<br/>
{error_code}<br/>
{/if}
<ProgressBar total={total} used={loaded}/>
</div>
<style>
.upload_progress {
display: block;
padding: 2px 4px 1px 4px;
margin: 4px;
border-radius: 4px;
}
.error {
background: var(--danger_color);
color: var(--highlight_text_color);
}
</style>

View File

@@ -1,183 +0,0 @@
<script>
import { createEventDispatcher, tick } from "svelte";
import { fade } from "svelte/transition";
import DropUpload from "./DropUpload.svelte";
import UploadProgress from "./UploadProgress.svelte";
let dispatch = createEventDispatcher()
let file_input_field;
let file_input_change = e => {
// Start uploading the files async
upload_files(e.target.files)
// This resets the file input field
file_input_field.nodeValue = ""
}
export const pick_files = () => {
file_input_field.click()
}
export let drop_upload = false
let visible = false
let upload_queue = [];
let task_id_counter = 0
export const upload_files = async (files) => {
if (files.length === 0) {
return
}
// Add files to the queue
for (let i = 0; i < files.length; i++) {
if (files[i].type === "" && files[i].size === 0) {
continue
}
upload_queue.push({
task_id: task_id_counter,
file: files[i],
name: files[i].name,
component: null,
id: "",
status: "queued",
total_size: files[i].size,
loaded_size: 0,
})
task_id_counter++
}
// Reassign array and wait for tick to complete. After the tick is completed
// each upload progress bar will have bound itself to its array item
upload_queue = upload_queue
visible = true
await tick()
start_upload()
}
let active_uploads = 0
let state = "idle"
const start_upload = () => {
for (let i = 0; i < upload_queue.length && active_uploads < 3; i++) {
if (upload_queue[i]) {
if (upload_queue[i].status === "queued") {
active_uploads++
upload_queue[i].component.start()
upload_queue[i].status = "uploading"
}
}
}
if (active_uploads === 0) {
state = "finished"
let file_ids = []
upload_queue.forEach(job => {
if (job.status === "finished" && job.id !== "") {
file_ids.push(job.id)
}
})
dispatch("uploads_finished", file_ids)
upload_queue = []
visible = false
} else {
state = "uploading"
}
}
const finish_upload = (e) => {
active_uploads--
upload_queue = upload_queue
start_upload()
}
const leave_confirmation = e => {
if (state === "uploading") {
e.preventDefault()
e.returnValue = "If you close this page your files will stop uploading. Do you want to continue?"
return e.returnValue
} else {
return null
}
}
</script>
<svelte:window on:beforeunload={leave_confirmation} />
<input
bind:this={file_input_field}
on:change={file_input_change}
class="upload_input" type="file" name="file" multiple="multiple"
/>
{#if visible}
<div class="upload_widget" transition:fade={{duration: 200}}>
<div class="header">
{#if state === "idle"}
Waiting for files
{:else if state === "uploading"}
Uploading files...
{:else if state === "finished"}
Done
{/if}
</div>
<div class="body">
{#each upload_queue as job}
{#if job.status !== "finished"}
<UploadProgress bind:this={job.component} job={job} on:finished={finish_upload}/>
{/if}
{/each}
</div>
</div>
{/if}
{#if drop_upload}
<DropUpload on:upload={e => upload_files(e.detail)}/>
{/if}
<style>
.upload_input {
visibility: hidden;
position: fixed;
width: 0;
height: 0;
}
.upload_widget {
position: fixed;
display: flex;
flex-direction: column;
width: 500px;
max-width: 80%;
height: auto;
max-height: 50%;
right: 20px;
bottom: 20px;
border-radius: 20px 20px 8px 8px;
overflow: hidden;
box-shadow: 1px 1px 8px var(--shadow_color);
}
.header {
flex: 0 0 auto;
background: var(--background_color);
color: var(--background_text_color);
text-align: center;
font-size: 1.2em;
padding: 4px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.body {
flex: 1 1 auto;
background: var(--body_color);
color: var(--body_text_color);
overflow-y: auto;
text-align: left;
}
</style>

View File

@@ -1,11 +1,12 @@
<script lang="ts">
import { bookmark_del, bookmarks_store } from "lib/Bookmarks";
import { fs_encode_path } from "lib/FilesystemAPI";
import { highlight_current_page } from "lib/HighlightCurrentPage";
</script>
{#each $bookmarks_store as bookmark}
<div class="row">
<a class="button" href="/d/{bookmark.id}" use:highlight_current_page>
<a class="button" href="/d{fs_encode_path(bookmark.path)}" use:highlight_current_page>
<i class="icon">{bookmark.icon}</i>
<span class="hide_small">{bookmark.label}</span>
</a>

View File

@@ -10,9 +10,14 @@ import { fs_get_node } from "lib/FilesystemAPI";
import { css_from_path } from "filesystem/edit_window/Branding";
import { loading_run, loading_store } from "lib/Loading";
import Spinner from "util/Spinner.svelte";
import { get_user, logout_user } from "lib/PixeldrainAPI";
onMount(async () => {
await loading_run(async () => {
const user = await get_user()
if (user.username === undefined || user.username === "") {
return
}
const root = await fs_get_node("/me")
document.documentElement.style = css_from_path(root.path)
})
@@ -21,10 +26,20 @@ onMount(async () => {
<div class="nav_container">
<nav class="nav">
<a class="button" href="/" use:highlight_current_page>
<i class="icon">home</i>
<span class="hide_small">Home</span>
</a>
{#if $user.username !== undefined && $user.username !== ""}
<div class="separator hide_small"></div>
<div class="username hide_small">
{$user.username}
</div>
<div class="separator"></div>
<div class="stats_table hide_small">
<div>Subscription</div>
<div>{$user.subscription.name}</div>
@@ -39,14 +54,28 @@ onMount(async () => {
<div>Transfer used</div>
<div>{formatDataVolume($user.monthly_transfer_used, 3)}</div>
</div>
<div class="separator hide_small"></div>
{/if}
<a class="button" href="/" use:highlight_current_page>
<i class="icon">home</i>
<span class="hide_small">Home</span>
<div class="separator hide_small"></div>
<a class="button" href="/d/me" use:highlight_current_page>
<i class="icon">folder</i>
<span class="hide_small">Filesystem</span>
</a>
{#if !$user.username}
<a class="button" href="/user" use:highlight_current_page>
<i class="icon">dashboard</i>
<span class="hide_small">Dashboard</span>
</a>
{#if $user.is_admin}
<a class="button" href="/admin" use:highlight_current_page>
<i class="icon">admin_panel_settings</i>
<span class="hide_small">Admin Panel</span>
</a>
{/if}
<button class="button" on:click={()=> logout_user("/")}>
<i class="icon">logout</i>
<span class="hide_small">Log out</span>
</button>
{:else}
<a class="button" href="/login" use:highlight_current_page>
<i class="icon">login</i>
<span class="hide_small">Login</span>
@@ -55,22 +84,10 @@ onMount(async () => {
<i class="icon">how_to_reg</i>
<span class="hide_small">Register</span>
</a>
{:else}
<a class="button" href="/user" use:highlight_current_page>
<i class="icon">dashboard</i>
<span class="hide_small">Dashboard</span>
</a>
<a class="button" href="/d/me" use:highlight_current_page>
<i class="icon">folder</i>
<span class="hide_small">Filesystem</span>
</a>
{#if $user.is_admin}
<a class="button" href="/admin" use:highlight_current_page>
<i class="icon">admin_panel_settings</i>
<span class="hide_small">Admin Panel</span>
</a>
{/if}
{/if}
<div class="separator"></div>
<a class="button" href="/speedtest" use:highlight_current_page>
<i class="icon">speed</i>
<span class="hide_small">Speedtest</span>
@@ -80,7 +97,8 @@ onMount(async () => {
<span class="hide_small">Themes</span>
</a>
<div class="separator hide_small"></div>
<div class="separator"></div>
<Bookmarks/>
</nav>
</div>