Files
Umbrella/frontend/src/routes/files/Index.svelte
Stephan Richter cf485055a6
All checks were successful
Build Docker Image / Docker-Build (push) Successful in 2m31s
Build Docker Image / Clean-Registry (push) Successful in 1s
improved file module GUI: now pushing a notification when markdown was copied
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
2026-01-16 15:48:56 +01:00

227 lines
6.6 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import { api } from '../../urls.svelte';
import { error, warn, yikes } from '../../warn.svelte';
import { t } from '../../translations.svelte';
import { user } from '../../user.svelte';
const image_extensions = ['jpg','jpeg','gif','png','svg','webp'];
const router = useTinyRouter();
let children = $state({});
let new_dir = $state(null);
let files = $state();
let parent = $state(false);
let form = $state(false);
let path = $state(null);
let delete_allowed = $state(false);
let available = $derived.by(isAvailable);
async function create_dir(ev){
ev.preventDefault();
ev.stopPropagation();
const url = api('files'+path+'/'+new_dir);
const res = await fetch(url,{
credentials: 'include',
method: 'POST'
});
if (res.ok) {
handleDirectory(res);
new_dir = null;
} else {
error(res);
}
return false;
}
function dropDir(p,name){
if (confirm(t('confirm_delete',{element:name})+"\n\n"+t('deletes_nested'))) dropElement(p);
}
async function dropElement(url){
const res = await fetch(url,{
credentials: 'include',
method: 'DELETE'
});
if (res.ok) {
handleDirectory(res);
} else {
error(res);
}
}
function dropFile(p,name){
if (confirm(t('confirm_delete',{element:name}))) dropElement(p);
}
async function handleDirectory(res){
let json = await res.json();
children.dirs = json.dirs ? val_sort(json.dirs) : {};
children.files = json.files ? val_sort(json.files) : {};
children.title = json.title ? json.title : path;
delete_allowed = json.delete;
yikes();
}
function isAvailable(){
if (!new_dir) return false;
if (children){
if (children.dirs) {
for (let key of Object.values(children.dirs)){
if (key == new_dir) return false;
}
}
if (children.files) {
for (let key of Object.values(children.files)){
if (key == new_dir) return false;
}
}
}
return true;
}
function is_image(file){
let parts = file.toLowerCase().split('.');
let ext = parts.pop();
return image_extensions.includes(ext);
}
async function loadChildren(p){
p = p.substring(6);
if (p == '') p = '/';
children = { dirs : [], files : [], title : p};
path = p;
if (p == '/'){
children.dirs = [
{ path : `/user/${user.id}`, name : t('my files') },
{ path : '/project', name : t('projects')},
{ path : '/company', name : t('companies')},
]
parent = false;
form = false;
} else {
const url = api(`files${p}`);
const res = await fetch(url,{credentials:'include'});
if (res.ok){
parent = p.substring(0, p.lastIndexOf("/"));
if (parent == '/user'||p=='/project'||p=='/company') parent = '/';
form = !(p=='/company'||p=='/project'||p=='/user');
handleDirectory(res);
} else {
error(res);
}
}
}
function markdown(file){
let parts = file.split('/');
let path = `/api/files${file}`;
path = encodeURI(path);
let md = `![${parts.pop()}](${path})`;
navigator.clipboard.writeText(md);
warn(t('Markdown has been copied to clipboard!'));
setTimeout(yikes, 2500);
}
function onclick(ev){
ev.preventDefault();
ev.stopPropagation();
var target = ev.target;
while (target && !target.href) target=target.parentNode;
let href = target.getAttribute('href');
if (href) {
router.navigate(href);
loadChildren(href);
}
return false;
}
async function upload_file(ev){
ev.preventDefault();
const url = api('files'+path+'/'+files[0].name);
const resp = await fetch(url, {
credentials: 'include',
method: 'POST',
headers: {
"Content-Type": "application/unknown",
},
body: files[0]
});
if (resp.ok){
handleDirectory(resp);
files = null;
} else {
error(resp);
}
return false;
}
function val_sort(map){
return Object.entries(map)
.map(item => ({name:item[1],path:item[0]}))
.sort((a,b) => a.name.localeCompare(b.name));
}
onMount(() => loadChildren(window.location.pathname));
</script>
<h1>{t('files')} {children?.title}</h1>
<ul>
{#if parent}
<li class="dir parent">
<span class="symbol"></span>
<a href={'/files'+parent} {onclick}>..</a>
</li>
{/if}
{#if children?.dirs}
{#each children.dirs as dir}
<li class="dir">
<span class="symbol"></span>
<a href={'/files'+dir.path} {onclick}>{dir.name}</a>
{#if delete_allowed}
<button class="symbol" onclick={e => dropDir(`/api/files${dir.path}`,dir.name)}></button>
{/if}
</li>
{/each}
{/if}
{#if form}
<li class="action">
<form onsubmit={create_dir} >
<span class="symbol">+</span>
<input type="text" bind:value={new_dir} />
<button type="submit" disabled={!available} >{t('create_new_object',{object:t('directory')})}</button>
</form>
</li>
{/if}
{#if children.files}
{#each children.files as file}
<li class="file">
<span class="symbol"></span>
<a href={`/api/files${file.path}`} target="_blank">{file.name}</a>
{#if is_image(file.path)}
<button class="symbol" title={'markdown_code'} onclick={e => markdown(file.path)}></button>
{/if}
{#if delete_allowed}
<button class="symbol" title={t('delete_object',{'object':t('file')})} onclick={e => dropFile(`/api/files${file.path}`,file.name)}></button>
{/if}
</li>
{/each}
{/if}
{#if form}
<li class="action">
<form onsubmit={upload_file}>
<span class="symbol">+</span>
<input type="file" bind:files />
<button disabled={!files}>{t('upload_file')}</button>
</form>
</li>
{/if}
</ul>