✨ Improved file uploader; uploads from the library can be selected
This commit is contained in:
@@ -222,6 +222,23 @@ async def list_images(
|
||||
return return_items
|
||||
|
||||
|
||||
@router.get("/list/last")
|
||||
async def get_latest_images(count: int = 50, user: User = Depends(get_current_user)) -> list[PrivateStorageItem]:
|
||||
if count > 50:
|
||||
count = 50
|
||||
items = (
|
||||
await StorageItem.objects.filter(user=user)
|
||||
.limit(count)
|
||||
.select_related([StorageItem.quizzes, StorageItem.quiztivities])
|
||||
.order_by(StorageItem.uploaded_at.desc())
|
||||
.all()
|
||||
)
|
||||
return_items: list[PrivateStorageItem] = []
|
||||
for item in items:
|
||||
return_items.append(PrivateStorageItem.from_db_model(item))
|
||||
return return_items
|
||||
|
||||
|
||||
class ReturnGetStorageLimit(BaseModel):
|
||||
limit: int
|
||||
limit_reached: bool
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full max-h-full pb-20 px-20 h-full">
|
||||
<div class="rounded-lg bg-white w-full h-full border-gray-500 drop-shadow-2xl dark:bg-gray-700">
|
||||
<div class="rounded-lg bg-white w-full h-full border-gray-500 dark:bg-gray-700 shadow-2xl">
|
||||
<div class="h-12 bg-gray-300 rounded-t-lg dark:bg-gray-500">
|
||||
<div class="flex align-middle p-4 gap-3">
|
||||
<span
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Compressor from '@uppy/compressor';
|
||||
import { fade } from 'svelte/transition';
|
||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||
|
||||
// CSS imports
|
||||
import '@uppy/core/dist/style.css';
|
||||
@@ -22,6 +23,7 @@
|
||||
import type { EditorData } from '../quiz_types';
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
import { onMount } from 'svelte';
|
||||
import Library from '$lib/editor/uploader/Library.svelte';
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
@@ -34,6 +36,18 @@
|
||||
// eslint-disable-next-line no-undef
|
||||
let video_popup: undefined | WindowProxy = undefined;
|
||||
|
||||
let selected_type: AvailableUploadTypes | null = null;
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
enum AvailableUploadTypes {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Image,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Video,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
Library
|
||||
}
|
||||
|
||||
const uppy = new Uppy()
|
||||
.use(DropTarget, {
|
||||
target: document.body
|
||||
@@ -73,6 +87,7 @@
|
||||
console.log(selected_question, data);
|
||||
|
||||
modalOpen = false;
|
||||
selected_type = null;
|
||||
});
|
||||
console.log(edit_id);
|
||||
|
||||
@@ -83,6 +98,7 @@
|
||||
}
|
||||
localStorage.removeItem('video_upload_id');
|
||||
data.questions[selected_question].image = e.newValue;
|
||||
selected_type = null;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,42 +113,84 @@
|
||||
video_popup = undefined;
|
||||
});
|
||||
};
|
||||
|
||||
const handle_on_click = (e: Event) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
modalOpen = false;
|
||||
selected_type = null;
|
||||
}
|
||||
};
|
||||
onMount(() => {
|
||||
window.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
modalOpen = false;
|
||||
selected_type = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if modalOpen}
|
||||
<div
|
||||
class="w-full h-full absolute top-0 left-0 bg-opacity-60 z-20 flex justify-center"
|
||||
transition:fade|local
|
||||
class="w-screen h-screen fixed top-0 left-0 bg-opacity-50 bg-black z-20 flex justify-center"
|
||||
on:click={handle_on_click}
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-t-lg bg-black text-white px-1"
|
||||
on:click={() => {
|
||||
modalOpen = false;
|
||||
}}
|
||||
>Close
|
||||
</button>
|
||||
{#if video_upload}
|
||||
<button
|
||||
class="rounded-t-lg bg-black text-white px-1"
|
||||
on:click={upload_video}
|
||||
type="button"
|
||||
>Upload video
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if video_popup}
|
||||
<div class="flex w-full h-1/3 bg-white dark:bg-gray-700 p-4">
|
||||
<h2 class="text-4xl m-auto">Popup open</h2>
|
||||
{#if selected_type === null}
|
||||
<div class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded">
|
||||
<h1 class="text-3xl text-center mb-4">Select the Upload Type</h1>
|
||||
<div class="flex flex-row gap-4">
|
||||
<div class="w-full">
|
||||
<BrownButton
|
||||
on:click={() => {
|
||||
selected_type = AvailableUploadTypes.Image;
|
||||
}}>Image</BrownButton
|
||||
>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<BrownButton
|
||||
disabled={!video_upload}
|
||||
on:click={() => {
|
||||
selected_type = AvailableUploadTypes.Video;
|
||||
}}
|
||||
>Video
|
||||
</BrownButton>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<BrownButton
|
||||
on:click={() => {
|
||||
selected_type = AvailableUploadTypes.Library;
|
||||
}}
|
||||
>Library
|
||||
</BrownButton>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
</div>
|
||||
{:else if selected_type === AvailableUploadTypes.Image}
|
||||
<div class="m-auto w-1/3 h-5/6" transition:fade|local={{ duration: 100 }}>
|
||||
<div>
|
||||
<SvelteDashboard {uppy} width="100%" {props} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else if selected_type === AvailableUploadTypes.Video}
|
||||
<div
|
||||
class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded"
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
>
|
||||
<h1 class="text-3xl text-center mb-4">Upload a Video</h1>
|
||||
{#if video_popup}
|
||||
<p class="text-center">
|
||||
The popup is open; have a look at it for further information
|
||||
</p>
|
||||
{:else}
|
||||
<BrownButton on:click={upload_video} type="button">Upload video</BrownButton>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if selected_type === AvailableUploadTypes.Library}
|
||||
<div>
|
||||
<Library bind:data {selected_question} bind:modalOpen />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex justify-center w-full pt-10" transition:fade|local>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<!--
|
||||
- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { PrivateImageData } from '$lib/quiz_types';
|
||||
import Spinner from '$lib/Spinner.svelte';
|
||||
import type { EditorData } from '$lib/quiz_types';
|
||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||
|
||||
export let data: EditorData;
|
||||
export let selected_question: number;
|
||||
export let modalOpen: boolean;
|
||||
|
||||
const fetch_images = async (): Promise<PrivateImageData> => {
|
||||
const response = await fetch('/api/v1/storage/list/last?count=50');
|
||||
return await response.json();
|
||||
};
|
||||
let image_fetch = fetch_images();
|
||||
|
||||
const set_image = (id: string) => {
|
||||
if (selected_question === undefined) {
|
||||
data.cover_image = id;
|
||||
} else if (selected_question === -1) {
|
||||
data.background_image = id;
|
||||
} else {
|
||||
data.questions[selected_question].image = id;
|
||||
}
|
||||
modalOpen = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
{#await image_fetch}
|
||||
<Spinner />
|
||||
{:then images}
|
||||
<div class="flex w-screen p-8 h-screen">
|
||||
<div
|
||||
class="flex flex-col w-1/3 m-auto overflow-scroll h-full rounded p-4 gap-4 bg-white dark:bg-gray-700"
|
||||
>
|
||||
{#each images as image}
|
||||
<div class="rounded border-2 border-[#B07156] p-2 flex-col flex gap-2">
|
||||
<div>
|
||||
<img
|
||||
src="/api/v1/storage/download/{image.id}"
|
||||
loading="lazy"
|
||||
alt={image.alt_text}
|
||||
class="object-contain w-full h-full max-h-full rounded"
|
||||
/>
|
||||
</div>
|
||||
<p class="text-center">{image.filename ?? 'No name available'}</p>
|
||||
<BrownButton
|
||||
on:click={() => {
|
||||
set_image(image.id);
|
||||
}}>Select</BrownButton
|
||||
>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
||||
@@ -88,3 +88,19 @@ export interface EditorData {
|
||||
background_color?: string;
|
||||
background_image?: string;
|
||||
}
|
||||
|
||||
export interface PrivateImageData {
|
||||
id: string;
|
||||
uploaded_at: string;
|
||||
mime_type: string;
|
||||
hash?: string;
|
||||
size?: number;
|
||||
deleted_at?: string;
|
||||
alt_text?: string;
|
||||
filename?: string;
|
||||
thumbhash?: string;
|
||||
server?: string;
|
||||
imported: boolean;
|
||||
quizzes: { id: string }[];
|
||||
quiztivities: { id: string }[];
|
||||
}
|
||||
|
||||
@@ -4,26 +4,11 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export interface ImageData {
|
||||
id: string;
|
||||
uploaded_at: string;
|
||||
mime_type: string;
|
||||
hash?: string;
|
||||
size?: number;
|
||||
deleted_at?: string;
|
||||
alt_text?: string;
|
||||
filename?: string;
|
||||
thumbhash?: string;
|
||||
server?: string;
|
||||
imported: boolean;
|
||||
quizzes: { id: string }[];
|
||||
quiztivities: { id: string }[];
|
||||
}
|
||||
import type { PrivateImageData } from '$lib/quiz_types';
|
||||
|
||||
export const load = (async ({ fetch }) => {
|
||||
const res = await fetch('/api/v1/storage/list');
|
||||
const json: ImageData[] = await res.json();
|
||||
const json: PrivateImageData[] = await res.json();
|
||||
return {
|
||||
images: json
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user