✨ Shares Menu nearly feature complete
This commit is contained in:
@@ -360,12 +360,14 @@ class OnlyId(BaseModel):
|
|||||||
class PublicQuizTivityShare(BaseModel):
|
class PublicQuizTivityShare(BaseModel):
|
||||||
id: uuid.UUID
|
id: uuid.UUID
|
||||||
name: str | None
|
name: str | None
|
||||||
expire_in: int
|
expire_in: int | None
|
||||||
quiztivity: OnlyId
|
quiztivity: OnlyId
|
||||||
user: OnlyId
|
user: OnlyId
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_db_model(cls, data: QuizTivityShare):
|
def from_db_model(cls, data: QuizTivityShare):
|
||||||
|
expire_in = None
|
||||||
|
if data.expire_at is not None:
|
||||||
expire_in = int((data.expire_at - datetime.now()).seconds / 60)
|
expire_in = int((data.expire_at - datetime.now()).seconds / 60)
|
||||||
return cls(
|
return cls(
|
||||||
id=data.id,
|
id=data.id,
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ async def get_all_quiztivities(user: User = Depends(get_current_user)) -> list[Q
|
|||||||
|
|
||||||
@router.get("/{uuid}/shares")
|
@router.get("/{uuid}/shares")
|
||||||
async def get_shares(uuid: UUID, user: User = Depends(get_current_user)) -> list[PublicQuizTivityShare]:
|
async def get_shares(uuid: UUID, user: User = Depends(get_current_user)) -> list[PublicQuizTivityShare]:
|
||||||
shares = await QuizTivityShare.objects.filter(quiztivity=uuid, user=user).all()
|
shares = (
|
||||||
|
await QuizTivityShare.objects.filter(quiztivity=uuid, user=user).order_by(QuizTivityShare.expire_at.asc()).all()
|
||||||
|
)
|
||||||
resp_shares = []
|
resp_shares = []
|
||||||
for share in shares:
|
for share in shares:
|
||||||
resp_shares.append(PublicQuizTivityShare.from_db_model(share))
|
resp_shares.append(PublicQuizTivityShare.from_db_model(share))
|
||||||
|
|||||||
@@ -77,4 +77,6 @@ async def get_share(uuid: UUID) -> QuizTivity:
|
|||||||
share = await QuizTivityShare.objects.select_related("quiztivity").get_or_none(id=uuid)
|
share = await QuizTivityShare.objects.select_related("quiztivity").get_or_none(id=uuid)
|
||||||
if share is None:
|
if share is None:
|
||||||
raise HTTPException(status_code=404, detail="Share not found")
|
raise HTTPException(status_code=404, detail="Share not found")
|
||||||
|
if share.expire_at < datetime.now():
|
||||||
|
raise HTTPException(status_code=410, detail="Already expired")
|
||||||
return share.quiztivity
|
return share.quiztivity
|
||||||
|
|||||||
@@ -357,7 +357,13 @@
|
|||||||
"move_right": "Move right",
|
"move_right": "Move right",
|
||||||
"title_placeholder": "Enter title here",
|
"title_placeholder": "Enter title here",
|
||||||
"add_new": "Add new",
|
"add_new": "Add new",
|
||||||
"delete": "Delete"
|
"delete": "Delete",
|
||||||
|
"open_shares_menu": "Open Shares menu",
|
||||||
|
"shares": {
|
||||||
|
"add_new_share": "Add new Share",
|
||||||
|
"expires_on": "Expires on {{date}}",
|
||||||
|
"never_expires": "Never expires"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"editor": {
|
"editor": {
|
||||||
@@ -370,6 +376,12 @@
|
|||||||
"memory": {
|
"memory": {
|
||||||
"try_count": "Tries: {{try_count}}"
|
"try_count": "Tries: {{try_count}}"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"share_expired": "Share expired"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"popover": {
|
||||||
|
"copied_to_clipboard": "Copied to clipboard!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
import SmallPopover from '$lib/components/popover/smalltop.svelte';
|
import SmallPopover from '$lib/components/popover/smalltop.svelte';
|
||||||
import { PopoverTypes } from '$lib/components/popover/smalltop';
|
import { PopoverTypes } from '$lib/components/popover/smalltop';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade, fly } from 'svelte/transition';
|
||||||
|
|
||||||
const { t } = getLocalization();
|
const { t } = getLocalization();
|
||||||
export let open = false;
|
export let open = false;
|
||||||
@@ -72,6 +72,38 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
document.body.addEventListener('keydown', close_start_game_if_esc_is_pressed);
|
document.body.addEventListener('keydown', close_start_game_if_esc_is_pressed);
|
||||||
});
|
});
|
||||||
|
let never_expires_checked = true;
|
||||||
|
let selected_date = undefined;
|
||||||
|
const create_share = async () => {
|
||||||
|
if (!selected_date && !never_expires_checked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(selected_date);
|
||||||
|
await fetch('/api/v1/quiztivity/shares/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: undefined,
|
||||||
|
quiztivity: id,
|
||||||
|
expire_in: never_expires_checked
|
||||||
|
? undefined
|
||||||
|
: Math.floor(Math.abs(new Date() - new Date(selected_date)) / 1000 / 60)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
loaded_shares = load_shares();
|
||||||
|
};
|
||||||
|
let loaded_shares = load_shares();
|
||||||
|
|
||||||
|
const delete_share = async (id: string) => {
|
||||||
|
if (!confirm('Do you really want to delete this Share?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetch(`/api/v1/quiztivity/shares/${id}`, { method: 'DELETE' });
|
||||||
|
loaded_shares = load_shares();
|
||||||
|
};
|
||||||
|
let add_shares_open = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SmallPopover bind:open={popover_open} type={PopoverTypes.Copy} />
|
<SmallPopover bind:open={popover_open} type={PopoverTypes.Copy} />
|
||||||
@@ -83,14 +115,41 @@
|
|||||||
<div
|
<div
|
||||||
class="m-auto bg-white dark:bg-gray-600 rounded shadow-2xl flex p-4 flex-col w-2/3 h-5/6 gap-2 overflow-scroll"
|
class="m-auto bg-white dark:bg-gray-600 rounded shadow-2xl flex p-4 flex-col w-2/3 h-5/6 gap-2 overflow-scroll"
|
||||||
>
|
>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center flex-col">
|
||||||
<BrownButton>{$t('quiztivity.editor.shares.add_new_share')}</BrownButton>
|
<BrownButton
|
||||||
|
on:click={() => {
|
||||||
|
add_shares_open = !add_shares_open;
|
||||||
|
}}>{$t('quiztivity.editor.shares.add_new_share')}</BrownButton
|
||||||
|
>
|
||||||
|
{#if add_shares_open}
|
||||||
|
<form
|
||||||
|
class="flex justify-center p-2 border-b-2 border-l-2 border-r-2 border-[#B07156] flex-col gap-2"
|
||||||
|
transition:fly|local={{ duration: 100, y: -10 }}
|
||||||
|
on:submit|preventDefault={create_share}
|
||||||
|
>
|
||||||
|
<div class="grid grid-cols-2">
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
class="dark:text-black transition-all mx-auto"
|
||||||
|
disabled={never_expires_checked}
|
||||||
|
bind:value={selected_date}
|
||||||
|
/>
|
||||||
|
<div class="mx-auto">
|
||||||
|
<label for="cb">{$t('quiztivity.editor.shares.never_expires')}</label>
|
||||||
|
<input type="checkbox" id="cb" bind:checked={never_expires_checked} />
|
||||||
</div>
|
</div>
|
||||||
{#await load_shares()}
|
</div>
|
||||||
|
<BrownButton type="submit" disabled={!selected_date && !never_expires_checked}
|
||||||
|
>{$t('words.submit')}</BrownButton
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#await loaded_shares}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
{:then shares}
|
{:then shares}
|
||||||
{#each shares as share}
|
{#each shares as share}
|
||||||
<div class="grid grid-cols-4 w-full gap-2">
|
<div class="grid grid-cols-4 w-full gap-2" in:fade={{ duration: 50 }}>
|
||||||
<!-- <p>{share.name ?? "..."}</p>-->
|
<!-- <p>{share.name ?? "..."}</p>-->
|
||||||
<div class="w-full mx-auto">
|
<div class="w-full mx-auto">
|
||||||
<BrownButton
|
<BrownButton
|
||||||
@@ -102,12 +161,6 @@
|
|||||||
})
|
})
|
||||||
: false}
|
: false}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
console.log(
|
|
||||||
navigator.canShare({
|
|
||||||
title: 'title',
|
|
||||||
url: `${window.location.origin}/quiztivity`
|
|
||||||
})
|
|
||||||
);
|
|
||||||
navigator.share({
|
navigator.share({
|
||||||
title: 'Quiztivity on ClassQuiz',
|
title: 'Quiztivity on ClassQuiz',
|
||||||
text: 'Play this Quiztivity now on ClassQuiz!',
|
text: 'Play this Quiztivity now on ClassQuiz!',
|
||||||
@@ -173,7 +226,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
<div class="w-fit my-auto ml-auto">
|
<div class="w-fit my-auto ml-auto">
|
||||||
<BrownButton>{$t('words.delete')}</BrownButton>
|
<BrownButton
|
||||||
|
on:click={() => {
|
||||||
|
delete_share(share.id);
|
||||||
|
}}>{$t('words.delete')}</BrownButton
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
{$t('error_page.404_text')}
|
{$t('error_page.404_text')}
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p>
|
<p class="text-center">
|
||||||
{$t('error_page.unknown_error_text')}
|
{$t('error_page.unknown_error_text')}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<!--
|
||||||
|
- 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 { navbarVisible } from '$lib/stores';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { getLocalization } from '$lib/i18n';
|
||||||
|
|
||||||
|
navbarVisible.set(true);
|
||||||
|
let status = $page.status;
|
||||||
|
const { t } = getLocalization();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
- 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/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
- 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/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>{$t('words.error')} - {status}</title>
|
||||||
|
</svelte:head>
|
||||||
|
<h1 class="text-6xl text-center">{status}</h1>
|
||||||
|
|
||||||
|
{#if status === 404}
|
||||||
|
<p class="text-center">
|
||||||
|
{$t('error_page.404_text')}
|
||||||
|
</p>
|
||||||
|
{:else if status === 410}
|
||||||
|
<p class="text-center">
|
||||||
|
{$t('quiztivity.share_expired')}
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<p class="text-center">
|
||||||
|
{$t('error_page.unknown_error_text')}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex justify-center mt-8">
|
||||||
|
<img
|
||||||
|
class="rounded-lg"
|
||||||
|
src="https://http.cat/{status}"
|
||||||
|
alt="Cat representing the {status}-http error code"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@@ -10,12 +10,18 @@ import type { Data } from '$lib/quiztivity/types';
|
|||||||
|
|
||||||
export const load = (async ({ url, fetch }) => {
|
export const load = (async ({ url, fetch }) => {
|
||||||
const id = url.searchParams.get('id');
|
const id = url.searchParams.get('id');
|
||||||
|
const share = url.searchParams.get('share') === 'true';
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw error(400, 'id missing');
|
throw error(400, 'id missing');
|
||||||
}
|
}
|
||||||
const resp = await fetch(`/api/v1/quiztivity/${id}`);
|
let resp: Response;
|
||||||
|
if (share) {
|
||||||
|
resp = await fetch(`/api/v1/quiztivity/shares/${id}`);
|
||||||
|
} else {
|
||||||
|
resp = await fetch(`/api/v1/quiztivity/${id}`);
|
||||||
|
}
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
throw error(404, 'quiztivity not found');
|
throw error(resp.status);
|
||||||
}
|
}
|
||||||
const data: Data = await resp.json();
|
const data: Data = await resp.json();
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { redirect } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load = (({ params }) => {
|
||||||
|
const quiz_id = params.share_id;
|
||||||
|
throw redirect(301, `/quiztivity/play?id=${quiz_id}&share=true`);
|
||||||
|
}) satisfies PageServerLoad;
|
||||||
Reference in New Issue
Block a user