Skip to content

Commit

Permalink
Feat/UI stage progress improvement (#99)
Browse files Browse the repository at this point in the history
* feat: stage progress disabled and show stage name

* feat: conditionally render previous button in StageProgress, add color in next stage button

* style: update participant name display in HostView for better readability

* feat: enhance participant name management in HostView by using Firestore snapshots for real-time updates

* style: modify the resource upload button

* style: change the home image and title
  • Loading branch information
howard9199 authored Dec 25, 2024
1 parent 16b0e0e commit 673d969
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 39 deletions.
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.

0 comments on commit 673d969

Please sign in to comment.