✨ Added translations
This commit is contained in:
@@ -209,6 +209,7 @@ async def list_images(
|
|||||||
storage_items = (
|
storage_items = (
|
||||||
await StorageItem.objects.filter(user=user)
|
await StorageItem.objects.filter(user=user)
|
||||||
.filter(StorageItem.uploaded_at > since)
|
.filter(StorageItem.uploaded_at > since)
|
||||||
|
.filter(StorageItem.deleted_at == None) # noqa: E711
|
||||||
.order_by(StorageItem.uploaded_at.desc())
|
.order_by(StorageItem.uploaded_at.desc())
|
||||||
.select_related([StorageItem.quizzes, StorageItem.quiztivities])
|
.select_related([StorageItem.quizzes, StorageItem.quiztivities])
|
||||||
.all()
|
.all()
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
export let data: EditorData;
|
export let data: EditorData;
|
||||||
export let selected_question: number;
|
export let selected_question: number;
|
||||||
export let video_upload = false;
|
export let video_upload = false;
|
||||||
|
export let library_enabled = true;
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
let video_popup: undefined | WindowProxy = undefined;
|
let video_popup: undefined | WindowProxy = undefined;
|
||||||
@@ -89,7 +90,6 @@
|
|||||||
modalOpen = false;
|
modalOpen = false;
|
||||||
selected_type = null;
|
selected_type = null;
|
||||||
});
|
});
|
||||||
console.log(edit_id);
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
window.addEventListener('storage', (e) => {
|
window.addEventListener('storage', (e) => {
|
||||||
@@ -138,14 +138,15 @@
|
|||||||
>
|
>
|
||||||
{#if selected_type === null}
|
{#if selected_type === null}
|
||||||
<div class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded">
|
<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>
|
<h1 class="text-3xl text-center mb-4">{$t('uploader.select_upload_type')}</h1>
|
||||||
<div class="flex flex-row gap-4">
|
<div class="flex flex-row gap-4">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<BrownButton
|
<BrownButton
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
selected_type = AvailableUploadTypes.Image;
|
selected_type = AvailableUploadTypes.Image;
|
||||||
}}>Image</BrownButton
|
}}
|
||||||
>
|
>{$t('words.image')}
|
||||||
|
</BrownButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<BrownButton
|
<BrownButton
|
||||||
@@ -153,17 +154,19 @@
|
|||||||
on:click={() => {
|
on:click={() => {
|
||||||
selected_type = AvailableUploadTypes.Video;
|
selected_type = AvailableUploadTypes.Video;
|
||||||
}}
|
}}
|
||||||
>Video
|
>{$t('words.video')}
|
||||||
</BrownButton>
|
</BrownButton>
|
||||||
</div>
|
</div>
|
||||||
|
{#if library_enabled}
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<BrownButton
|
<BrownButton
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
selected_type = AvailableUploadTypes.Library;
|
selected_type = AvailableUploadTypes.Library;
|
||||||
}}
|
}}
|
||||||
>Library
|
>{$t('words.library')}
|
||||||
</BrownButton>
|
</BrownButton>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if selected_type === AvailableUploadTypes.Image}
|
{:else if selected_type === AvailableUploadTypes.Image}
|
||||||
@@ -177,13 +180,15 @@
|
|||||||
class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded"
|
class="m-auto w-1/3 h-auto bg-white dark:bg-gray-700 p-4 rounded"
|
||||||
transition:fade|local={{ duration: 100 }}
|
transition:fade|local={{ duration: 100 }}
|
||||||
>
|
>
|
||||||
<h1 class="text-3xl text-center mb-4">Upload a Video</h1>
|
<h1 class="text-3xl text-center mb-4">{$t('uploader.upload_a_video')}</h1>
|
||||||
{#if video_popup}
|
{#if video_popup}
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
The popup is open; have a look at it for further information
|
{$t('uploader.upload_video_popup_notice')}
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<BrownButton on:click={upload_video} type="button">Upload video</BrownButton>
|
<BrownButton on:click={upload_video} type="button"
|
||||||
|
>{$t('uploader.upload_video')}</BrownButton
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if selected_type === AvailableUploadTypes.Library}
|
{:else if selected_type === AvailableUploadTypes.Library}
|
||||||
|
|||||||
@@ -9,11 +9,14 @@
|
|||||||
import Spinner from '$lib/Spinner.svelte';
|
import Spinner from '$lib/Spinner.svelte';
|
||||||
import type { EditorData } from '$lib/quiz_types';
|
import type { EditorData } from '$lib/quiz_types';
|
||||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||||
|
import { getLocalization } from '$lib/i18n';
|
||||||
|
|
||||||
export let data: EditorData;
|
export let data: EditorData;
|
||||||
export let selected_question: number;
|
export let selected_question: number;
|
||||||
export let modalOpen: boolean;
|
export let modalOpen: boolean;
|
||||||
|
|
||||||
|
const { t } = getLocalization();
|
||||||
|
|
||||||
const fetch_images = async (): Promise<PrivateImageData> => {
|
const fetch_images = async (): Promise<PrivateImageData> => {
|
||||||
const response = await fetch('/api/v1/storage/list/last?count=50');
|
const response = await fetch('/api/v1/storage/list/last?count=50');
|
||||||
return await response.json();
|
return await response.json();
|
||||||
@@ -53,7 +56,7 @@
|
|||||||
<BrownButton
|
<BrownButton
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
set_image(image.id);
|
set_image(image.id);
|
||||||
}}>Select</BrownButton
|
}}>{$t('words.select')}</BrownButton
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -175,7 +175,12 @@
|
|||||||
"quiz": "Quiz",
|
"quiz": "Quiz",
|
||||||
"quiztivity": "Quiztivity",
|
"quiztivity": "Quiztivity",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
"check_choice": "Check Choice"
|
"check_choice": "Check Choice",
|
||||||
|
"video": "Video",
|
||||||
|
"library": "Library",
|
||||||
|
"progress": "Progress",
|
||||||
|
"speed": "Speed",
|
||||||
|
"upload": "Upload"
|
||||||
},
|
},
|
||||||
"editor": {
|
"editor": {
|
||||||
"time_in_seconds": "Time in seconds",
|
"time_in_seconds": "Time in seconds",
|
||||||
@@ -277,7 +282,11 @@
|
|||||||
"unknown_error_text": "That shouldn't happen. It's probably my fault, not yours, but maybe you have a magical power to break stuff..."
|
"unknown_error_text": "That shouldn't happen. It's probably my fault, not yours, but maybe you have a magical power to break stuff..."
|
||||||
},
|
},
|
||||||
"uploader": {
|
"uploader": {
|
||||||
"add_image": "Add image"
|
"add_image": "Add image",
|
||||||
|
"select_upload_type": "Select the Upload Type",
|
||||||
|
"upload_a_video": "Upload a Video",
|
||||||
|
"upload_video_popup_notice": "The popup is open; have a look at it for further information",
|
||||||
|
"upload_video": "Upload Video"
|
||||||
},
|
},
|
||||||
"avatar_settings": {
|
"avatar_settings": {
|
||||||
"skin_color": "Skin color",
|
"skin_color": "Skin color",
|
||||||
@@ -388,5 +397,24 @@
|
|||||||
"public_user_page": {
|
"public_user_page": {
|
||||||
"joined_on": "Joined on {{date}}",
|
"joined_on": "Joined on {{date}}",
|
||||||
"no_original_quizzes": "This user doesn't have any original quizzes"
|
"no_original_quizzes": "This user doesn't have any original quizzes"
|
||||||
|
},
|
||||||
|
"file_dashboard": {
|
||||||
|
"not_available": "Not available",
|
||||||
|
"missing": "MISSING!",
|
||||||
|
"unset": "Unset",
|
||||||
|
"size": "Size: {{size}} Mib",
|
||||||
|
"caption": "Caption: {{caption}}",
|
||||||
|
"filename": "Filename: {{filename}}",
|
||||||
|
"uploaded": "Uploaded: {{date}}",
|
||||||
|
"Imported": "Imported: {{yes_or_no}}",
|
||||||
|
"edit_details": "Edit details",
|
||||||
|
"delete_image": "Delete image",
|
||||||
|
"edit_the_image": "Edit the image",
|
||||||
|
"filename_word": "Filename",
|
||||||
|
"alt_text": "Alt(ernate) text / Caption"
|
||||||
|
},
|
||||||
|
"video_uploader": {
|
||||||
|
"time_elapsed": "Time elapsed",
|
||||||
|
"time_remaining": "Time remaining"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
// import MediaComponent from '$lib/editor/MediaComponent.svelte';
|
// import MediaComponent from '$lib/editor/MediaComponent.svelte';
|
||||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import Uploader from './uploader.svelte';
|
||||||
|
import { getLocalization } from '$lib/i18n';
|
||||||
|
const { t } = getLocalization();
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
let edit_popup = null;
|
let edit_popup = null;
|
||||||
@@ -38,9 +41,15 @@
|
|||||||
edit_popup = null;
|
edit_popup = null;
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const delete_image = async (id: string) => {
|
||||||
|
await fetch(`/api/v1/storage/meta/${id}`, { method: 'DELETE' });
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<Uploader />
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 p-4 gap-4">
|
<div class="grid grid-cols-1 lg:grid-cols-2 p-4 gap-4">
|
||||||
{#each images as image}
|
{#each images as image}
|
||||||
<div
|
<div
|
||||||
@@ -51,19 +60,49 @@
|
|||||||
src="/api/v1/storage/download/{image.id}"
|
src="/api/v1/storage/download/{image.id}"
|
||||||
class="m-auto h-auto w-auto max-h-[30vh]"
|
class="m-auto h-auto w-auto max-h-[30vh]"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
alt={image.alt_text || 'Not available'}
|
alt={image.alt_text || $t('file_dashboard.not_available')}
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-col my-auto ml-4">
|
<div class="flex flex-col my-auto ml-4">
|
||||||
<p>Size: {(image.size / (1024 * 1024)).toFixed(2)} Mib</p>
|
<p>
|
||||||
<p>Caption: {image.alt_text ?? 'MISSING!'}</p>
|
{$t('file_dashboard.size', {
|
||||||
<p>Filename: {image.filename ?? 'Unset'}</p>
|
size: (image.size / (1024 * 1024)).toFixed(2)
|
||||||
<p>Uploaded: {new Date(image.uploaded_at).toLocaleString()}</p>
|
})}
|
||||||
<p>Imported: {image.imported ? 'Yes' : 'No'}</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
{$t('file_dashboard.caption', {
|
||||||
|
caption: image.alt_text ?? $t('file_dashboard.missing')
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{$t('file_dashboard.filename', {
|
||||||
|
filename: image.filename ?? $t('file_dashboard.missing')
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{$t('file_dashboard.uploaded', {
|
||||||
|
date: new Date(image.uploaded_at).toLocaleString()
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{$t('file_dashboard.imported', {
|
||||||
|
yes_or_no: image.imported ? $t('words.yes') : $t('words.no')
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
<BrownButton
|
<BrownButton
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
edit_popup = image;
|
edit_popup = image;
|
||||||
}}>Edit Details</BrownButton
|
}}
|
||||||
|
>{$t('file_dashboard.edit_details')}
|
||||||
|
</BrownButton>
|
||||||
|
{#if image.quiztivities.length === 0 && image.quizzes.length === 0}
|
||||||
|
<BrownButton
|
||||||
|
on:click={() => {
|
||||||
|
delete_image(image.id);
|
||||||
|
}}>{$t('file_dashboard.delete_image')}</BrownButton
|
||||||
>
|
>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -77,12 +116,13 @@
|
|||||||
on:click={close_popup_handler}
|
on:click={close_popup_handler}
|
||||||
>
|
>
|
||||||
<div class="w-auto h-auto m-auto rounded bg-white dark:bg-gray-700 p-4">
|
<div class="w-auto h-auto m-auto rounded bg-white dark:bg-gray-700 p-4">
|
||||||
<h1 class="text-2xl text-center">Edit the image</h1>
|
<h1 class="text-2xl text-center">{$t('file_dashboard.edit_the_image')}</h1>
|
||||||
<form class="flex flex-col" on:submit|preventDefault={save_image_metadata}>
|
<form class="flex flex-col" on:submit|preventDefault={save_image_metadata}>
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<div class="flex flex-col mr-4">
|
<div class="flex flex-col mr-4">
|
||||||
<label for="name" class="m-auto">Filename</label>
|
<label for="name" class="m-auto">{$t('file_dashboard.filename_word')}</label
|
||||||
<label for="alt_text" class="m-auto">Alt Text</label>
|
>
|
||||||
|
<label for="alt_text" class="m-auto">{$t('file_dashboard.alt_text')}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<input
|
<input
|
||||||
@@ -101,7 +141,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<BrownButton type="submit">Save</BrownButton>
|
<BrownButton type="submit">{$t('words.save')}</BrownButton>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<!--
|
||||||
|
- 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 Spinner from '$lib/Spinner.svelte';
|
||||||
|
|
||||||
|
let uppyOpen = false;
|
||||||
|
let edit_id = null;
|
||||||
|
let selected_question = undefined;
|
||||||
|
let data = { cover_image: undefined };
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (data.cover_image) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await import('$lib/editor/uploader.svelte')}
|
||||||
|
<Spinner my_20={false} />
|
||||||
|
{:then c}
|
||||||
|
<svelte:component
|
||||||
|
this={c.default}
|
||||||
|
bind:modalOpen={uppyOpen}
|
||||||
|
bind:edit_id
|
||||||
|
bind:data
|
||||||
|
bind:selected_question
|
||||||
|
video_upload={true}
|
||||||
|
library_enabled={false}
|
||||||
|
/>
|
||||||
|
{/await}
|
||||||
@@ -13,6 +13,8 @@
|
|||||||
import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg';
|
import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg';
|
||||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||||
import { navbarVisible } from '$lib/stores';
|
import { navbarVisible } from '$lib/stores';
|
||||||
|
import { getLocalization } from '$lib/i18n';
|
||||||
|
const { t } = getLocalization();
|
||||||
|
|
||||||
let file_input: HTMLInputElement;
|
let file_input: HTMLInputElement;
|
||||||
navbarVisible.set(false);
|
navbarVisible.set(false);
|
||||||
@@ -153,10 +155,10 @@
|
|||||||
{#if status === Status.Compressing || status === Status.CompressDone}
|
{#if status === Status.Compressing || status === Status.CompressDone}
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Speed</th>
|
<th>{$t('words.speed')}</th>
|
||||||
<th>Progress</th>
|
<th>{$t('words.progress')}</th>
|
||||||
<th>Time elapsed</th>
|
<th>{$t('video_uploader.time_elapsed')}</th>
|
||||||
<th>Time remaining</th>
|
<th>{$t('video_uploader.time_remaining')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{stats.speed}</td>
|
<td>{stats.speed}</td>
|
||||||
@@ -172,9 +174,9 @@
|
|||||||
{:else if status === Status.Uploading}
|
{:else if status === Status.Uploading}
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Progress</th>
|
<th>{$t('words.progress')}</th>
|
||||||
<th>Time elapsed</th>
|
<th>{$t('video_uploader.time_elapsed')}</th>
|
||||||
<th>Time remaining</th>
|
<th>{$t('video_uploader.time_remaining')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{Math.round(upload_stats.progress * 100)}%</td>
|
<td>{Math.round(upload_stats.progress * 100)}%</td>
|
||||||
@@ -188,7 +190,8 @@
|
|||||||
<div class="flex relative my-2">
|
<div class="flex relative my-2">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<BrownButton disabled={status !== Status.CompressDone} on:click={upload_video}>
|
<BrownButton disabled={status !== Status.CompressDone} on:click={upload_video}>
|
||||||
Upload {file_size_in_mi ? `${file_size_in_mi.toFixed(2)}Mi` : ''}</BrownButton
|
{$t('words.upload')}
|
||||||
|
{file_size_in_mi ? `(${file_size_in_mi.toFixed(2)} Mi)` : ''}</BrownButton
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
@@ -197,7 +200,8 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<BrownButton on:click={compress_video} disabled={status !== Status.Idle}>Submit</BrownButton
|
<BrownButton on:click={compress_video} disabled={status !== Status.Idle}
|
||||||
|
>{$t('words.submit')}</BrownButton
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user