Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/UI stage progress improvement #99

Merged
merged 6 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 37 additions & 15 deletions src/lib/components/session/HostView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
Timestamp,
query,
where,
limit
limit,
doc
} from 'firebase/firestore';
import { db } from '$lib/firebase';
import { writable } from 'svelte/store';
Expand All @@ -39,7 +40,7 @@
updatedAt: Timestamp | undefined;
};
let groups = writable<GroupWithId[]>([]);
let participantNames = $state(new Map<string, string>());
let participantNames = writable(new Map<string, string>());
type ParticipantProgress = {
displayName: string;
progress: number;
Expand Down Expand Up @@ -89,17 +90,40 @@
);
groups.set(groupsData);

groupsData.forEach(async (group) => {
for (const participant of group.participants) {
try {
const userData = await getUser(participant);
participantNames.set(participant, userData.displayName);
} catch (error) {
console.error(`無法獲取使用者 ${participant} 的資料:`, error);
participantNames.set(participant, '未知使用者');
const allParticipants = new Set<string>();
groupsData.forEach((group) => {
group.participants.forEach((participant) => {
allParticipants.add(participant);
});
});

allParticipants.forEach((participant) => {
const profileRef = doc(db, 'profiles', participant);
const unsubscribe = onSnapshot(
profileRef,
(doc) => {
const profile = doc.data();
if (profile) {
participantNames.update((names) => {
const newNames = new Map(names);
newNames.set(participant, profile.displayName);
return newNames;
});
}
},
(error) => {
console.error(`無法監聽使用者 ${participant} 的資料:`, error);
participantNames.update((names) => {
const newNames = new Map(names);
newNames.set(participant, '未知使用者');
return newNames;
});
}
}
);
unsubscribes.push(unsubscribe);
});

groupsData.forEach(async (group) => {
if (!groupChecked.has(group.id)) {
groupChecked.add(group.id);

Expand Down Expand Up @@ -545,16 +569,14 @@
<div class="flex items-center gap-1.5">
<div class="flex flex-1 items-center gap-1.5">
<span
class="min-w-[50px] cursor-pointer text-xs hover:text-primary-600"
class="min-w-[60px] max-w-[60px] cursor-pointer truncate text-xs hover:text-primary-600"
onclick={() => handleParticipantClick(group.id, participant)}
onkeydown={(e) =>
e.key === 'Enter' && handleParticipantClick(group.id, participant)}
role="button"
tabindex="0"
>
{#await getUser(participant) then userData}
{userData.displayName}
{/await}
{$participantNames.get(participant) || '載入中...'}
</span>
{#if participantProgress.has(participant)}
<div class="flex flex-1 items-center gap-1.5">
Expand Down
88 changes: 72 additions & 16 deletions src/lib/components/session/StageProgress.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { ArrowLeft, ArrowRight } from 'lucide-svelte';
import { Button } from 'flowbite-svelte';
import { ArrowLeft, ArrowRight, Loader2 } from 'lucide-svelte';
import { Button, Modal } from 'flowbite-svelte';
import type { Session } from '$lib/schema/session';

const stages = [
Expand All @@ -15,21 +15,43 @@
export let onStageChange: (stage: (typeof stages)[number]['id']) => void;
export let showAction = true;

let loadingPrevious = false;
let loadingNext = false;
let showEndedConfirmModal = false;

$: currentStageIndex = stages.findIndex((s) => s.id === session?.status);
$: canGoPrevious = currentStageIndex > 0;
$: canGoNext = currentStageIndex < stages.length - 1;
$: canGoPrevious = currentStageIndex > 0 && !loadingPrevious;
$: canGoNext = currentStageIndex < stages.length - 1 && !loadingNext;
$: nextStage = currentStageIndex < stages.length - 1 ? stages[currentStageIndex + 1] : null;

function handlePrevious() {
console.log('handlePrevious', canGoPrevious);
async function handlePrevious() {
if (canGoPrevious) {
console.log('handlePrevious', stages[currentStageIndex - 1].id);
onStageChange(stages[currentStageIndex - 1].id);
loadingPrevious = true;
try {
await onStageChange(stages[currentStageIndex - 1].id);
} finally {
loadingPrevious = false;
}
}
}

async function handleNext() {
if (!canGoNext) return;

if (nextStage?.id === 'ended') {
showEndedConfirmModal = true;
return;
}

await proceedToNextStage();
}

function handleNext() {
if (canGoNext) {
onStageChange(stages[currentStageIndex + 1].id);
async function proceedToNextStage() {
loadingNext = true;
try {
await onStageChange(stages[currentStageIndex + 1].id);
} finally {
loadingNext = false;
}
}
</script>
Expand Down Expand Up @@ -69,11 +91,45 @@

{#if showAction}
<div class="flex items-center gap-2">
<Button color="light" on:click={handlePrevious} disabled={!canGoPrevious}>
<ArrowLeft class="h-4 w-4" />
</Button>
<Button color="light" on:click={handleNext} disabled={!canGoNext}>
<ArrowRight class="h-4 w-4" />
{#if canGoPrevious}
<Button color="light" on:click={handlePrevious} disabled={!canGoPrevious || loadingPrevious}>
{#if loadingPrevious && currentStageIndex > 0}
<Loader2 class="h-4 w-4 animate-spin" />
{:else}
<ArrowLeft class="h-4 w-4" />
{/if}
<span class="ml-2">{stages[currentStageIndex - 1]?.name}</span>
</Button>
{/if}
<Button color="primary" on:click={handleNext} disabled={!canGoNext || loadingNext}>
<span class="ml-2">{stages[currentStageIndex + 1]?.name}</span>
<span class="ml-2"
>{#if loadingNext && currentStageIndex < stages.length - 1}
<Loader2 class="h-4 w-4 animate-spin" />
{:else}
<ArrowRight class="h-4 w-4" />
{/if}</span
>
</Button>
</div>
{/if}

<Modal bind:open={showEndedConfirmModal} size="sm" autoclose class="w-full">
<div class="text-center">
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-500">
進入 Ended Stage 將無法返回,確定要繼續嗎?
</h3>
<div class="flex justify-center gap-4">
<Button
color="red"
on:click={() => {
showEndedConfirmModal = false;
proceedToNextStage();
}}
>
確定
</Button>
<Button color="alternative" on:click={() => (showEndedConfirmModal = false)}>取消</Button>
</div>
</div>
</Modal>
30 changes: 28 additions & 2 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@
import { user } from '$lib/stores/auth';
import { Button, Card } from 'flowbite-svelte';
import { ArrowRight, Mic, Brain, GraduationCap, Github } from 'lucide-svelte';
import { onMount } from 'svelte';

let title = $state('Hinagiku');
let highlight = $state(0);

onMount(() => {
const interval = setInterval(() => {
let newHighlight;
do {
newHighlight = Math.floor(Math.random() * title.length);
} while (newHighlight === highlight);
highlight = newHighlight;
}, 800);

return () => clearInterval(interval);
});
</script>

<svelte:head>
Expand All @@ -13,8 +29,18 @@
<div class="mx-auto max-w-6xl px-4 py-24">
<div class="grid items-center gap-12 lg:grid-cols-2">
<div class="text-left">
<h1 class="mb-6 text-5xl font-bold leading-tight text-gray-900">
Transform Educational Discussions with <span class="text-primary-600">AI-Powered</span> Insights
<h1 class="mb-6 font-bold leading-tight">
<span class="mb-4 block text-2xl text-gray-900"
>Transform Educational Discussions with</span
>
<span class="text-8xl text-gray-500">
{#each title as c, i}
<span
class="transition-colors duration-500"
class:text-primary-600={i === highlight}>{c}</span
>
{/each}
</span>
</h1>
<p class="mb-8 text-xl text-gray-600">
Hinagiku helps educators facilitate more engaging and productive discussions through
Expand Down
18 changes: 12 additions & 6 deletions src/routes/template/[id]/ResourceList.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
// Imports
import { Button, Input, Textarea, Alert, Spinner } from 'flowbite-svelte';
import { Trash2, ChevronDown, ChevronUp, ExternalLink } from 'lucide-svelte';
import { Trash2, ChevronDown, ChevronUp, ExternalLink, FileText, Upload } from 'lucide-svelte';
import { page } from '$app/stores';
import type { Template } from '$lib/schema/template';
import { notifications } from '$lib/stores/notifications';
Expand Down Expand Up @@ -260,18 +260,24 @@
{#if resources.length < LIMITS.SOURCES}
<!-- initial state with two buttons -->
{#if uploadMode === 'none'}
<div class="flex h-48 justify-center gap-4">
<div class="flex h-28 justify-center gap-4">
<Button
class="h-full flex-1 bg-blue-200 text-2xl text-blue-700 hover:bg-blue-300"
class="h-full flex-1 rounded-lg bg-blue-200 text-xl text-blue-800 shadow-lg transition duration-300 ease-in-out hover:bg-blue-300"
on:click={() => (uploadMode = 'text')}
>
Click to Add Text Resource
<div class="flex flex-col items-center gap-2">
<FileText class="h-8 w-8" />
<span>Add Text Resource</span>
</div>
</Button>
<Button
class="h-full flex-1 bg-green-200 text-2xl text-green-700 hover:bg-green-300"
class="h-full flex-1 rounded-lg bg-emerald-200 text-xl text-emerald-800 shadow-lg transition duration-300 ease-in-out hover:bg-emerald-300"
on:click={() => (uploadMode = 'file')}
>
Click to Upload File Resource
<div class="flex flex-col items-center gap-2">
<Upload class="h-8 w-8" />
<span>Upload File Resource</span>
</div>
</Button>
</div>

Expand Down
Binary file modified static/home.webp
Binary file not shown.
Loading