From d5cb5b1ff50ac87b4138eddbbafeb7a9ce096b8c Mon Sep 17 00:00:00 2001 From: Mawoka Date: Wed, 24 May 2023 19:00:30 +0200 Subject: [PATCH] :sparkles: Shares Menu nearly feature complete --- classquiz/db/models.py | 6 +- classquiz/routers/quiztivity/__init__.py | 4 +- classquiz/routers/quiztivity/shares.py | 2 + frontend/src/lib/i18n/locales/en.json | 14 +++- .../src/lib/quiztivity/shares_popover.svelte | 81 ++++++++++++++++--- frontend/src/routes/+error.svelte | 2 +- .../src/routes/quiztivity/play/+error.svelte | 53 ++++++++++++ frontend/src/routes/quiztivity/play/+page.ts | 10 ++- .../share/[share_id]/+page.server.ts | 14 ++++ 9 files changed, 167 insertions(+), 19 deletions(-) create mode 100644 frontend/src/routes/quiztivity/play/+error.svelte create mode 100644 frontend/src/routes/quiztivity/share/[share_id]/+page.server.ts diff --git a/classquiz/db/models.py b/classquiz/db/models.py index 3105930..dffe88f 100644 --- a/classquiz/db/models.py +++ b/classquiz/db/models.py @@ -360,13 +360,15 @@ class OnlyId(BaseModel): class PublicQuizTivityShare(BaseModel): id: uuid.UUID name: str | None - expire_in: int + expire_in: int | None quiztivity: OnlyId user: OnlyId @classmethod def from_db_model(cls, data: QuizTivityShare): - expire_in = int((data.expire_at - datetime.now()).seconds / 60) + expire_in = None + if data.expire_at is not None: + expire_in = int((data.expire_at - datetime.now()).seconds / 60) return cls( id=data.id, name=data.name, diff --git a/classquiz/routers/quiztivity/__init__.py b/classquiz/routers/quiztivity/__init__.py index 5f3717b..c10458a 100644 --- a/classquiz/routers/quiztivity/__init__.py +++ b/classquiz/routers/quiztivity/__init__.py @@ -56,7 +56,9 @@ async def get_all_quiztivities(user: User = Depends(get_current_user)) -> list[Q @router.get("/{uuid}/shares") 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 = [] for share in shares: resp_shares.append(PublicQuizTivityShare.from_db_model(share)) diff --git a/classquiz/routers/quiztivity/shares.py b/classquiz/routers/quiztivity/shares.py index 54848ad..43b92ad 100644 --- a/classquiz/routers/quiztivity/shares.py +++ b/classquiz/routers/quiztivity/shares.py @@ -77,4 +77,6 @@ async def get_share(uuid: UUID) -> QuizTivity: share = await QuizTivityShare.objects.select_related("quiztivity").get_or_none(id=uuid) if share is None: 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 diff --git a/frontend/src/lib/i18n/locales/en.json b/frontend/src/lib/i18n/locales/en.json index 14b0cbf..414e577 100644 --- a/frontend/src/lib/i18n/locales/en.json +++ b/frontend/src/lib/i18n/locales/en.json @@ -357,7 +357,13 @@ "move_right": "Move right", "title_placeholder": "Enter title here", "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": { "editor": { @@ -370,6 +376,12 @@ "memory": { "try_count": "Tries: {{try_count}}" } + }, + "share_expired": "Share expired" + }, + "components": { + "popover": { + "copied_to_clipboard": "Copied to clipboard!" } } } diff --git a/frontend/src/lib/quiztivity/shares_popover.svelte b/frontend/src/lib/quiztivity/shares_popover.svelte index 897fbae..1d6e2b0 100644 --- a/frontend/src/lib/quiztivity/shares_popover.svelte +++ b/frontend/src/lib/quiztivity/shares_popover.svelte @@ -12,7 +12,7 @@ import SmallPopover from '$lib/components/popover/smalltop.svelte'; import { PopoverTypes } from '$lib/components/popover/smalltop'; import { onMount } from 'svelte'; - import { fade } from 'svelte/transition'; + import { fade, fly } from 'svelte/transition'; const { t } = getLocalization(); export let open = false; @@ -72,6 +72,38 @@ onMount(() => { 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; @@ -83,14 +115,41 @@
-
- {$t('quiztivity.editor.shares.add_new_share')} +
+ { + add_shares_open = !add_shares_open; + }}>{$t('quiztivity.editor.shares.add_new_share')} + {#if add_shares_open} +
+
+ +
+ + +
+
+ {$t('words.submit')} +
+ {/if}
- {#await load_shares()} + {#await loaded_shares} {:then shares} {#each shares as share} -
+
{ - console.log( - navigator.canShare({ - title: 'title', - url: `${window.location.origin}/quiztivity` - }) - ); navigator.share({ title: 'Quiztivity on ClassQuiz', text: 'Play this Quiztivity now on ClassQuiz!', @@ -173,7 +226,11 @@ {/if}

- {$t('words.delete')} + { + delete_share(share.id); + }}>{$t('words.delete')}
{/each} diff --git a/frontend/src/routes/+error.svelte b/frontend/src/routes/+error.svelte index 315c1d1..4a88970 100644 --- a/frontend/src/routes/+error.svelte +++ b/frontend/src/routes/+error.svelte @@ -29,7 +29,7 @@ {$t('error_page.404_text')}

{:else} -

+

{$t('error_page.unknown_error_text')}

{/if} diff --git a/frontend/src/routes/quiztivity/play/+error.svelte b/frontend/src/routes/quiztivity/play/+error.svelte new file mode 100644 index 0000000..10c68eb --- /dev/null +++ b/frontend/src/routes/quiztivity/play/+error.svelte @@ -0,0 +1,53 @@ + + + + + + + + + {$t('words.error')} - {status} + +

{status}

+ +{#if status === 404} +

+ {$t('error_page.404_text')} +

+{:else if status === 410} +

+ {$t('quiztivity.share_expired')} +

+{:else} +

+ {$t('error_page.unknown_error_text')} +

+{/if} + +
+ Cat representing the {status}-http error code +
diff --git a/frontend/src/routes/quiztivity/play/+page.ts b/frontend/src/routes/quiztivity/play/+page.ts index be54a5d..aaa8b58 100644 --- a/frontend/src/routes/quiztivity/play/+page.ts +++ b/frontend/src/routes/quiztivity/play/+page.ts @@ -10,12 +10,18 @@ import type { Data } from '$lib/quiztivity/types'; export const load = (async ({ url, fetch }) => { const id = url.searchParams.get('id'); + const share = url.searchParams.get('share') === 'true'; if (!id) { 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) { - throw error(404, 'quiztivity not found'); + throw error(resp.status); } const data: Data = await resp.json(); return { diff --git a/frontend/src/routes/quiztivity/share/[share_id]/+page.server.ts b/frontend/src/routes/quiztivity/share/[share_id]/+page.server.ts new file mode 100644 index 0000000..4762947 --- /dev/null +++ b/frontend/src/routes/quiztivity/share/[share_id]/+page.server.ts @@ -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;