From 0667ac6916a8fe68d25a667c11d00c1634ea6f24 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Mon, 16 Sep 2024 11:33:30 +0200 Subject: [PATCH 01/24] Updated styling for Preview for lessons --- .../components/lessons/lesson/Preview.svelte | 97 ++++++++++++++++--- 1 file changed, 84 insertions(+), 13 deletions(-) diff --git a/src/src/lib/components/lessons/lesson/Preview.svelte b/src/src/lib/components/lessons/lesson/Preview.svelte index f07d7c14..bd7176ea 100644 --- a/src/src/lib/components/lessons/lesson/Preview.svelte +++ b/src/src/lib/components/lessons/lesson/Preview.svelte @@ -1,18 +1,41 @@ -
- -
+
+ +
+ {#each floatingElements as element} +
+ {/each} +
+ + +
Check your audio and video
-
+
-
- Microphone +
+ Microphone -
+
- Camera + Camera - - +
+ + +
-
+
+ + \ No newline at end of file From 1b54d37546559fee759eeb7be118e51cfd2e33ce Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Mon, 16 Sep 2024 13:04:17 +0200 Subject: [PATCH 02/24] Removed UI for live lessons to work with responsiveness --- .../lib/components/lessons/lesson/Chat.svelte | 182 +++++++----------- .../components/lessons/lesson/Controls.svelte | 2 + .../components/lessons/lesson/Header.svelte | 21 ++ .../components/lessons/lesson/Layout.svelte | 40 +++- .../lessons/lesson/SpeedDial.svelte | 0 5 files changed, 136 insertions(+), 109 deletions(-) create mode 100644 src/src/lib/components/lessons/lesson/Header.svelte create mode 100644 src/src/lib/components/lessons/lesson/SpeedDial.svelte diff --git a/src/src/lib/components/lessons/lesson/Chat.svelte b/src/src/lib/components/lessons/lesson/Chat.svelte index 75a3305b..7bdf90f5 100644 --- a/src/src/lib/components/lessons/lesson/Chat.svelte +++ b/src/src/lib/components/lessons/lesson/Chat.svelte @@ -1,109 +1,75 @@ - -{#if !state} -
-

Connecting...

-
-{:else} -
-
- - - -
- - {#if activeTab === 'Chat'} -
-
- {#each messages as message (message.id)} -
-
- - -
-
-

- {message.user?.name} -

- - - {new Date(message.created_at).toLocaleTimeString([], { - hour: '2-digit', - minute: '2-digit' - })} - -
-

{message.text}

-
-
-
- {/each} -
-
- -
-
- - -
-
- {:else} - - {/if} -
-{/if} + import { Section, Comment, CommentItem } from 'flowbite-svelte-blocks'; + import { Button, Textarea, Label, Dropdown, DropdownItem } from 'flowbite-svelte'; + import { DotsHorizontalOutline } from 'flowbite-svelte-icons'; + const comments = [ + { + id: "comment1", + commenter: { + name: "Michael Gough", + profilePicture: "https://flowbite.com/docs/images/people/profile-picture-2.jpg", + }, + date: "Feb. 8, 2022", + content: "Very straight-to-point article. Really worth time reading. Thank you! But tools are just the instruments for the UX designers. The knowledge of the design tools are as important as the creation of the design strategy.", + replies: [ + { + id: "reply1", + commenter: { + name: "Jese Leos", + profilePicture: "https://flowbite.com/docs/images/people/profile-picture-5.jpg", + }, + date: "Feb. 12, 2022", + content: "Much appreciated! Glad you liked it ☺️", + }, + ], + }, + { + id: "comment2", + commenter: { + name: "Bonnie Green", + profilePicture: "https://flowbite.com/docs/images/people/profile-picture-3.jpg", + }, + date: "Mar. 12, 2022", + content: "The article covers the essentials, challenges, myths and stages the UX designer should consider while creating the design strategy.", + replies: [], + }, + { + id: "comment3", + commenter: { + name: "Helene Engels", + profilePicture: "https://flowbite.com/docs/images/people/profile-picture-4.jpg", + }, + date: "Jun. 23, 2022", + content: "Thanks for sharing this. I do came from the Backend development and explored some of the tools to design my Side Projects.", + replies: [], + }, + // Add more comments and replies here + ]; + + + +
+ +
+ + + +
+ {#each comments as comment, i} + + + + + Edit + Remove + Report + + + + {/each} +
+
\ No newline at end of file diff --git a/src/src/lib/components/lessons/lesson/Controls.svelte b/src/src/lib/components/lessons/lesson/Controls.svelte index da681187..5fee7301 100644 --- a/src/src/lib/components/lessons/lesson/Controls.svelte +++ b/src/src/lib/components/lessons/lesson/Controls.svelte @@ -1,3 +1,4 @@ + diff --git a/src/src/lib/components/lessons/lesson/Header.svelte b/src/src/lib/components/lessons/lesson/Header.svelte new file mode 100644 index 00000000..e9b5fef9 --- /dev/null +++ b/src/src/lib/components/lessons/lesson/Header.svelte @@ -0,0 +1,21 @@ + + + + + Flowbite Logo + Lesson Name + +
+ + +
+ + Home + About + Navbar + Pricing + Contact + +
\ No newline at end of file diff --git a/src/src/lib/components/lessons/lesson/Layout.svelte b/src/src/lib/components/lessons/lesson/Layout.svelte index 2c62b589..83674c4b 100644 --- a/src/src/lib/components/lessons/lesson/Layout.svelte +++ b/src/src/lib/components/lessons/lesson/Layout.svelte @@ -1,3 +1,4 @@ + \ No newline at end of file diff --git a/src/src/lib/components/lessons/lesson/SpeedDial.svelte b/src/src/lib/components/lessons/lesson/SpeedDial.svelte new file mode 100644 index 00000000..e69de29b From ac5374f12ec8fd37293421698c4f110a06d98a0f Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Mon, 16 Sep 2024 17:55:27 +0200 Subject: [PATCH 03/24] Updated styling for navbar --- src/src/lib/components/common/Navbar.svelte | 61 +++++++++++++++++---- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/src/src/lib/components/common/Navbar.svelte b/src/src/lib/components/common/Navbar.svelte index 396993bd..02714130 100644 --- a/src/src/lib/components/common/Navbar.svelte +++ b/src/src/lib/components/common/Navbar.svelte @@ -1,9 +1,7 @@ - + ClassConnect Logo {#each currentLinks as { name, href }} - - {name} + + {name} {/each} {#each commonLinks as { name, href }} - - {name} + + {name} {/each} + + + + + From 12bce4c2ec65934ebcfd9d3c3cf954a33c77415c Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Mon, 16 Sep 2024 18:51:50 +0200 Subject: [PATCH 04/24] Updated sidebar styling to match Navbar styling --- src/src/lib/components/common/Navbar.svelte | 2 +- .../workspaces/workspace/Sidebar.svelte | 128 ++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/src/src/lib/components/common/Navbar.svelte b/src/src/lib/components/common/Navbar.svelte index 02714130..8490aa3d 100644 --- a/src/src/lib/components/common/Navbar.svelte +++ b/src/src/lib/components/common/Navbar.svelte @@ -33,7 +33,7 @@ }; - + ClassConnect Logo + import { Navbar, NavBrand, NavLi, NavUl, NavHamburger, DarkMode } from 'flowbite-svelte'; + import { + BullhornSolid, + BriefcaseSolid, + UsersGroupSolid, + ChartPieSolid, + ArrowLeftOutline, + ClipboardListSolid, + GlobeSolid, + BookOpenSolid, + VideoCameraSolid + } from 'flowbite-svelte-icons'; + import { page } from '$app/stores'; + import { writingQuiz } from '$lib/store/sidebar'; + import { onDestroy } from 'svelte'; + + let isOpen = true; + + const unsubscribeWritingQuiz = writingQuiz.subscribe((value) => { + isOpen = !value; + }); + + onDestroy(() => { + unsubscribeWritingQuiz(); + }); + + export let workspace; + export let role: 'lecturer' | 'student'; + + let workspaceURL = `/workspaces/${$page.params.workspace}`; + + const navLinks = { + lecturer: [ + { name: 'Dashboard', href: workspaceURL + '/dashboard' }, + { name: 'Grade Center', href: workspaceURL + '/gradecenter' }, + { name: 'Announcements', href: workspaceURL + '/announcements' }, + { name: 'Materials', href: workspaceURL + '/materials' }, + { name: 'Lessons', href: workspaceURL + '/lessons' }, + { name: 'Quizzes', href: workspaceURL + '/quizzes' }, + { name: 'Environments', href: workspaceURL + '/environments' } + ], + student: [ + { name: 'Announcements', href: workspaceURL + '/announcements' }, + { name: 'Activities', href: workspaceURL + '/activities' }, + { name: 'Materials', href: workspaceURL + '/materials' }, + { name: 'Lessons', href: workspaceURL + '/lessons' }, + { name: 'Quizzes', href: workspaceURL + '/quizzes' }, + { name: 'Environments', href: workspaceURL + '/environments' }, + { name: 'Grades', href: workspaceURL + '/grades' } + ] + }; + + $: role; + $: workspace; + $: currentLinks = navLinks[role]; + let activeUrl = ''; + const signout = () => { + window.location.href = '/signout'; + }; + + + + + ClassConnect owl logo + {workspace.name} + + + + {#each currentLinks as { icon, name, href }} + + + {name} + + {/each} + + + + + + + + + + + \ No newline at end of file From cd51cc56b0b51224c0a4e41b3da26bcbd461037d Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 11:51:32 +0200 Subject: [PATCH 05/24] Updated styling for Workspaces Page --- src/src/lib/components/common/Navbar.svelte | 191 +++++++++--------- src/src/lib/components/workspaces/Main.svelte | 119 +++++++++-- .../workspaces/workspace/Card.svelte | 36 ++-- src/src/routes/(app)/+layout.svelte | 2 +- 4 files changed, 223 insertions(+), 125 deletions(-) diff --git a/src/src/lib/components/common/Navbar.svelte b/src/src/lib/components/common/Navbar.svelte index 8490aa3d..f805ae6a 100644 --- a/src/src/lib/components/common/Navbar.svelte +++ b/src/src/lib/components/common/Navbar.svelte @@ -1,99 +1,108 @@ - - - ClassConnect Logo - ClassConnect - - - - {#each currentLinks as { name, href }} - - {name} - - {/each} - {#each commonLinks as { name, href }} - - {name} - - {/each} - - - - + + + ClassConnect Logo + ClassConnect + + {#if screenWidth <= 915} + + {/if} + + + {#each currentLinks as { name, href }} + + {name} + + {/each} + {#each commonLinks as { name, href }} + + {name} + + {/each} + + + + + .nav-link::after { + content: ''; + position: absolute; + width: 100%; + height: 2px; + bottom: -4px; + left: 0; + background-color: currentColor; + transform: scaleX(0); + transform-origin: bottom right; + transition: transform 0.3s ease-out; + } + + .nav-link:hover::after { + transform: scaleX(1); + transform-origin: bottom left; + } + + @media (min-width: 1024px) { + :global(.desktop-navbar) { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + } + } + + @media (max-width: 768px) { + :global(.mobile-nav-link) { + padding-left: 1.5rem !important; + } + } + \ No newline at end of file diff --git a/src/src/lib/components/workspaces/Main.svelte b/src/src/lib/components/workspaces/Main.svelte index 9159b1bd..36d4cc7d 100644 --- a/src/src/lib/components/workspaces/Main.svelte +++ b/src/src/lib/components/workspaces/Main.svelte @@ -1,31 +1,108 @@
-

Workspaces

- -
- {#if data.workspaces && data.workspaces.length > 0} -
- {#each data.workspaces as workspace} - - {/each} -
- {:else} -
-

- You haven't been assigned to any workspaces yet. -

- -

- Please contact your administrator if you believe this is an error. -

-
- {/if} + +
+ {#each floatingElements as element} +
+ {/each} +
+ + +
+

Workspaces

+ +
+ {#if data.workspaces && data.workspaces.length > 0} +
+ {#each data.workspaces as workspace} + + {/each} +
+ {:else} +
+

+ You haven't been assigned to any workspaces yet. +

+ +

+ Please contact your administrator if you believe this is an error. +

+
+ {/if} +
+ + \ No newline at end of file diff --git a/src/src/lib/components/workspaces/workspace/Card.svelte b/src/src/lib/components/workspaces/workspace/Card.svelte index a0bb4845..32e18edf 100644 --- a/src/src/lib/components/workspaces/workspace/Card.svelte +++ b/src/src/lib/components/workspaces/workspace/Card.svelte @@ -1,6 +1,6 @@ - -

- {name} -

+
+
+
+
+ {name} +
+

+ {name} +

-
- {description} -
+
+ {description} +
- - + +
+
+ + \ No newline at end of file diff --git a/src/src/routes/(app)/+layout.svelte b/src/src/routes/(app)/+layout.svelte index db5804e8..084f498e 100644 --- a/src/src/routes/(app)/+layout.svelte +++ b/src/src/routes/(app)/+layout.svelte @@ -29,7 +29,7 @@ {/if}
-
+
From 181913678626fcf5e129ab2560e51a377fbb21d4 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 13:03:02 +0200 Subject: [PATCH 06/24] Updated styling for Workspaces Page --- src/src/lib/components/common/Navbar.svelte | 66 ++++++++----------- src/src/lib/components/workspaces/Main.svelte | 4 +- src/src/routes/(app)/+layout.svelte | 11 ++-- 3 files changed, 37 insertions(+), 44 deletions(-) diff --git a/src/src/lib/components/common/Navbar.svelte b/src/src/lib/components/common/Navbar.svelte index f805ae6a..84bb0efd 100644 --- a/src/src/lib/components/common/Navbar.svelte +++ b/src/src/lib/components/common/Navbar.svelte @@ -1,12 +1,15 @@ - + ClassConnect Logo - ClassConnect + + ClassConnect + - {#if screenWidth <= 915} - - {/if} - + {#if screenWidth <= 915} + + {/if} + {#each currentLinks as { name, href }} - + {name} + + + {/each} {#each commonLinks as { name, href }} - + {name} + + + {/each} @@ -70,29 +81,10 @@ \ No newline at end of file + diff --git a/src/src/lib/components/workspaces/Main.svelte b/src/src/lib/components/workspaces/Main.svelte index 36d4cc7d..101aa3a9 100644 --- a/src/src/lib/components/workspaces/Main.svelte +++ b/src/src/lib/components/workspaces/Main.svelte @@ -41,10 +41,10 @@
-
+
{#each floatingElements as element}
{#if isWorkspacePage} +
{:else} -
- {/if} - -
-
+ +
+ {/if} + +
From 042de43db41a84fe095dd1c0b4d0bacb819d0c73 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 13:08:55 +0200 Subject: [PATCH 07/24] Run Formatting and Linting --- src/src/lib/components/common/Navbar.svelte | 192 ++++++++------ .../lib/components/lessons/lesson/Chat.svelte | 133 +++++----- .../components/lessons/lesson/Header.svelte | 42 +-- .../components/lessons/lesson/Layout.svelte | 2 +- .../components/lessons/lesson/Preview.svelte | 44 +++- src/src/lib/components/workspaces/Main.svelte | 2 +- .../workspaces/workspace/Card.svelte | 17 +- .../workspaces/workspace/Sidebar.svelte | 242 +++++++++--------- 8 files changed, 372 insertions(+), 302 deletions(-) diff --git a/src/src/lib/components/common/Navbar.svelte b/src/src/lib/components/common/Navbar.svelte index 84bb0efd..fe7f8c05 100644 --- a/src/src/lib/components/common/Navbar.svelte +++ b/src/src/lib/components/common/Navbar.svelte @@ -1,100 +1,122 @@ - - - ClassConnect Logo - - ClassConnect - - - {#if screenWidth <= 915} - - {/if} + + + ClassConnect Logo + + ClassConnect + + + {#if screenWidth <= 915} + + {/if} - - {#each currentLinks as { name, href }} - - {name} - - - - - {/each} - {#each commonLinks as { name, href }} - - {name} - - - - - {/each} - - - - + + {#each currentLinks as { name, href }} + + {name} + + + + + {/each} + {#each commonLinks as { name, href }} + + {name} + + + + + {/each} + + + + diff --git a/src/src/lib/components/lessons/lesson/Chat.svelte b/src/src/lib/components/lessons/lesson/Chat.svelte index 7bdf90f5..fe1f1751 100644 --- a/src/src/lib/components/lessons/lesson/Chat.svelte +++ b/src/src/lib/components/lessons/lesson/Chat.svelte @@ -3,73 +3,80 @@ import { Button, Textarea, Label, Dropdown, DropdownItem } from 'flowbite-svelte'; import { DotsHorizontalOutline } from 'flowbite-svelte-icons'; const comments = [ - { - id: "comment1", - commenter: { - name: "Michael Gough", - profilePicture: "https://flowbite.com/docs/images/people/profile-picture-2.jpg", - }, - date: "Feb. 8, 2022", - content: "Very straight-to-point article. Really worth time reading. Thank you! But tools are just the instruments for the UX designers. The knowledge of the design tools are as important as the creation of the design strategy.", - replies: [ - { - id: "reply1", + { + id: 'comment1', commenter: { - name: "Jese Leos", - profilePicture: "https://flowbite.com/docs/images/people/profile-picture-5.jpg", + name: 'Michael Gough', + profilePicture: 'https://flowbite.com/docs/images/people/profile-picture-2.jpg' }, - date: "Feb. 12, 2022", - content: "Much appreciated! Glad you liked it ☺️", - }, - ], - }, - { - id: "comment2", - commenter: { - name: "Bonnie Green", - profilePicture: "https://flowbite.com/docs/images/people/profile-picture-3.jpg", + date: 'Feb. 8, 2022', + content: + 'Very straight-to-point article. Really worth time reading. Thank you! But tools are just the instruments for the UX designers. The knowledge of the design tools are as important as the creation of the design strategy.', + replies: [ + { + id: 'reply1', + commenter: { + name: 'Jese Leos', + profilePicture: 'https://flowbite.com/docs/images/people/profile-picture-5.jpg' + }, + date: 'Feb. 12, 2022', + content: 'Much appreciated! Glad you liked it ☺️' + } + ] }, - date: "Mar. 12, 2022", - content: "The article covers the essentials, challenges, myths and stages the UX designer should consider while creating the design strategy.", - replies: [], - }, - { - id: "comment3", - commenter: { - name: "Helene Engels", - profilePicture: "https://flowbite.com/docs/images/people/profile-picture-4.jpg", + { + id: 'comment2', + commenter: { + name: 'Bonnie Green', + profilePicture: 'https://flowbite.com/docs/images/people/profile-picture-3.jpg' + }, + date: 'Mar. 12, 2022', + content: + 'The article covers the essentials, challenges, myths and stages the UX designer should consider while creating the design strategy.', + replies: [] }, - date: "Jun. 23, 2022", - content: "Thanks for sharing this. I do came from the Backend development and explored some of the tools to design my Side Projects.", - replies: [], - }, - // Add more comments and replies here + { + id: 'comment3', + commenter: { + name: 'Helene Engels', + profilePicture: 'https://flowbite.com/docs/images/people/profile-picture-4.jpg' + }, + date: 'Jun. 23, 2022', + content: + 'Thanks for sharing this. I do came from the Backend development and explored some of the tools to design my Side Projects.', + replies: [] + } + // Add more comments and replies here ]; - - - -
+ + +
-
- - - -
- {#each comments as comment, i} - - - - - Edit - Remove - Report - - - - {/each} +
+ + + +
+ {#each comments as comment, i} + + + + + Edit + Remove + Report + + + + {/each}
-
\ No newline at end of file +
diff --git a/src/src/lib/components/lessons/lesson/Header.svelte b/src/src/lib/components/lessons/lesson/Header.svelte index e9b5fef9..eb6da1c5 100644 --- a/src/src/lib/components/lessons/lesson/Header.svelte +++ b/src/src/lib/components/lessons/lesson/Header.svelte @@ -1,21 +1,23 @@ - - - - Flowbite Logo - Lesson Name - -
- - -
- - Home - About - Navbar - Pricing - Contact - -
\ No newline at end of file + import { Navbar, NavBrand, NavLi, NavUl, NavHamburger, Button, Input } from 'flowbite-svelte'; + + + + + Flowbite Logo + Lesson Name + +
+ + +
+ + Home + About + Navbar + Pricing + Contact + +
diff --git a/src/src/lib/components/lessons/lesson/Layout.svelte b/src/src/lib/components/lessons/lesson/Layout.svelte index 83674c4b..5969997b 100644 --- a/src/src/lib/components/lessons/lesson/Layout.svelte +++ b/src/src/lib/components/lessons/lesson/Layout.svelte @@ -95,4 +95,4 @@ } } ---> \ No newline at end of file +--> diff --git a/src/src/lib/components/lessons/lesson/Preview.svelte b/src/src/lib/components/lessons/lesson/Preview.svelte index bd7176ea..3b95ed60 100644 --- a/src/src/lib/components/lessons/lesson/Preview.svelte +++ b/src/src/lib/components/lessons/lesson/Preview.svelte @@ -89,7 +89,9 @@ } -
+
{#each floatingElements as element} @@ -100,8 +102,12 @@ {/each}
- -
+ +
Check your audio and video
@@ -116,8 +122,15 @@ />
-
- Microphone +
+ Microphone
- Camera + Camera
- - + +
@@ -161,4 +187,4 @@ :global(.dark) { color-scheme: dark; } - \ No newline at end of file + diff --git a/src/src/lib/components/workspaces/Main.svelte b/src/src/lib/components/workspaces/Main.svelte index 101aa3a9..19d3270f 100644 --- a/src/src/lib/components/workspaces/Main.svelte +++ b/src/src/lib/components/workspaces/Main.svelte @@ -105,4 +105,4 @@ :global(.dark) { color-scheme: dark; } - \ No newline at end of file + diff --git a/src/src/lib/components/workspaces/workspace/Card.svelte b/src/src/lib/components/workspaces/workspace/Card.svelte index 32e18edf..3e2794ef 100644 --- a/src/src/lib/components/workspaces/workspace/Card.svelte +++ b/src/src/lib/components/workspaces/workspace/Card.svelte @@ -17,10 +17,16 @@
-
+
- {name} + {name}

{name} @@ -30,7 +36,10 @@ {description}

-
@@ -40,4 +49,4 @@ :global(.dark) { color-scheme: dark; } - \ No newline at end of file + diff --git a/src/src/lib/components/workspaces/workspace/Sidebar.svelte b/src/src/lib/components/workspaces/workspace/Sidebar.svelte index 133ef525..8f46f939 100644 --- a/src/src/lib/components/workspaces/workspace/Sidebar.svelte +++ b/src/src/lib/components/workspaces/workspace/Sidebar.svelte @@ -1,128 +1,95 @@ - - - ClassConnect owl logo - {workspace.name} - - - - {#each currentLinks as { icon, name, href }} - - - {name} - - {/each} + + + ClassConnect owl logo + {workspace.name} + + + + {#each currentLinks as { icon, name, href }} + + + {name} + + {/each} - - - + + + - - - - \ No newline at end of file +--> + + From d332d2aee3e4914d3234278ecdaffe9676d4cad3 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 14:50:15 +0200 Subject: [PATCH 08/24] Created a way for the user to navigate back to the original Navbar --- src/src/lib/components/common/Navbar.svelte | 58 +-- .../components/materials/MaterialsTab.svelte | 11 +- .../workspaces/workspace/Sidebar.svelte | 422 ++++++++---------- 3 files changed, 208 insertions(+), 283 deletions(-) diff --git a/src/src/lib/components/common/Navbar.svelte b/src/src/lib/components/common/Navbar.svelte index fe7f8c05..d54481df 100644 --- a/src/src/lib/components/common/Navbar.svelte +++ b/src/src/lib/components/common/Navbar.svelte @@ -1,5 +1,5 @@ ClassConnect Logo - + ClassConnect @@ -60,42 +58,19 @@ {/if} - {#each currentLinks as { name, href }} + {#each [...currentLinks, ...commonLinks] as { name, href }} - {name} - - - - - {/each} - {#each commonLinks as { name, href }} - - {name} - - - + {name} + + {/each} - @@ -103,19 +78,12 @@ + import { + Navbar, + NavBrand, + NavLi, + NavUl, + NavHamburger, + Dropdown, + DropdownItem, + DropdownDivider + } from 'flowbite-svelte'; + import { ChevronDownOutline } from 'flowbite-svelte-icons'; + import { page } from '$app/stores'; + import { writingQuiz } from '$lib/store/sidebar'; + import { onDestroy } from 'svelte'; + + let isOpen = true; + + const unsubscribeWritingQuiz = writingQuiz.subscribe((value) => { + isOpen = !value; + }); + + onDestroy(() => { + unsubscribeWritingQuiz(); + }); + + export let workspace; + export let role: 'lecturer' | 'student'; + + let workspaceURL = `/workspaces/${$page.params.workspace}`; + + const navLinks = { + lecturer: { + dashboard: [ + { name: 'Dashboard', href: workspaceURL + '/dashboard' } + ], + management: [ + { name: 'Grade Center', href: workspaceURL + '/gradecenter' }, + { name: 'Announcements', href: workspaceURL + '/announcements' }, + { name: 'Materials', href: workspaceURL + '/materials' }, + { name: 'Lessons', href: workspaceURL + '/lessons' }, + { name: 'Quizzes', href: workspaceURL + '/quizzes' } + ], + resources: [ + { name: 'Environments', href: workspaceURL + '/environments' } + ] + }, + student: { + courseWork: [ + { name: 'Announcements', href: workspaceURL + '/announcements' }, + { name: 'Activities', href: workspaceURL + '/activities' }, + { name: 'Materials', href: workspaceURL + '/materials' }, + { name: 'Lessons', href: workspaceURL + '/lessons' }, + { name: 'Quizzes', href: workspaceURL + '/quizzes' } + ], + additional: [ + { name: 'Environments', href: workspaceURL + '/environments' }, + { name: 'Grades', href: workspaceURL + '/grades' } + ] + } + }; + + $: role; + $: workspace; + $: currentLinks = navLinks[role]; + + let activeUrl = ''; + const signout = () => { + window.location.href = '/signout'; + }; + + const backToMain = () => { + window.location.href = '/'; + }; + + let isHovered = false; + + + + + ClassConnect owl logo + {workspace.name} + + + + {#if role === 'lecturer'} + {#if currentLinks.dashboard.length > 1} + + Dashboard + + + {#each currentLinks.dashboard as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.dashboard[0].name} + + {/if} + + {#if currentLinks.management.length > 1} + + Management + + + {#each currentLinks.management as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.management[0].name} + + {/if} + + {#if currentLinks.resources.length > 1} + + Resources + + + {#each currentLinks.resources as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.resources[0].name} + + {/if} + {/if} + + {#if role === 'student'} + {#if currentLinks.courseWork.length > 1} + + Course Work + + + {#each currentLinks.courseWork as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.courseWork[0].name} + + {/if} + + {#if currentLinks.additional.length > 1} + + Additional + + + {#each currentLinks.additional as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.additional[0].name} + + {/if} + {/if} + + + + + + + + + + From cc5593f03ae171041f28b644486ac03711443576 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 15:12:35 +0200 Subject: [PATCH 09/24] Fixed errors from Sidebar.svelte --- .../workspaces/workspace/Sidebar.svelte | 72 +++++++++++-------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/src/lib/components/workspaces/workspace/Sidebar.svelte b/src/src/lib/components/workspaces/workspace/Sidebar.svelte index 2062dbd2..f6793309 100644 --- a/src/src/lib/components/workspaces/workspace/Sidebar.svelte +++ b/src/src/lib/components/workspaces/workspace/Sidebar.svelte @@ -7,7 +7,6 @@ NavHamburger, Dropdown, DropdownItem, - DropdownDivider } from 'flowbite-svelte'; import { ChevronDownOutline } from 'flowbite-svelte-icons'; import { page } from '$app/stores'; @@ -24,12 +23,30 @@ unsubscribeWritingQuiz(); }); - export let workspace; + export let workspace: { name: string; image: string }; export let role: 'lecturer' | 'student'; let workspaceURL = `/workspaces/${$page.params.workspace}`; - const navLinks = { + type NavLink = { name: string; href: string }; + + interface LecturerLinks { + dashboard: NavLink[]; + management: NavLink[]; + resources: NavLink[]; + } + + interface StudentLinks { + courseWork: NavLink[]; + additional: NavLink[]; + } + + type NavLinks = { + lecturer: LecturerLinks; + student: StudentLinks; + }; + + const navLinks: NavLinks = { lecturer: { dashboard: [ { name: 'Dashboard', href: workspaceURL + '/dashboard' } @@ -60,25 +77,31 @@ } }; - $: role; - $: workspace; $: currentLinks = navLinks[role]; + function isLecturerLinks(links: LecturerLinks | StudentLinks): links is LecturerLinks { + return 'dashboard' in links; + } + + function isStudentLinks(links: LecturerLinks | StudentLinks): links is StudentLinks { + return 'courseWork' in links; + } + let activeUrl = ''; const signout = () => { window.location.href = '/signout'; }; - + const backToMain = () => { - window.location.href = '/'; + window.location.href = '/'; }; - + let isHovered = false; - ClassConnect owl logo + {`${workspace.name} {workspace.name} - {#if role === 'lecturer'} + {#if isLecturerLinks(currentLinks)} {#if currentLinks.dashboard.length > 1} Dashboard {#each currentLinks.dashboard as { name, href }} - {name} + {name} {/each} {:else} @@ -108,7 +131,7 @@ {#each currentLinks.management as { name, href }} - {name} + {name} {/each} {:else} @@ -123,7 +146,7 @@ {#each currentLinks.resources as { name, href }} - {name} + {name} {/each} {:else} @@ -131,16 +154,14 @@ {currentLinks.resources[0].name} {/if} - {/if} - - {#if role === 'student'} + {:else if isStudentLinks(currentLinks)} {#if currentLinks.courseWork.length > 1} Course Work {#each currentLinks.courseWork as { name, href }} - {name} + {name} {/each} {:else} @@ -155,7 +176,7 @@ {#each currentLinks.additional as { name, href }} - {name} + {name} {/each} {:else} @@ -165,25 +186,18 @@ {/if} {/if} - - - - + + \ No newline at end of file From 0883c5887085f4e160e25d69f9898943109a596b Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 15:33:46 +0200 Subject: [PATCH 10/24] Updating workspaces styling --- .../lib/components/announcements/Main.svelte | 2 +- src/src/lib/components/workspaces/Main.svelte | 79 ++++++++++++------- .../[workspace]/dashboard/+page.svelte | 2 +- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/src/src/lib/components/announcements/Main.svelte b/src/src/lib/components/announcements/Main.svelte index 3293c726..62629010 100644 --- a/src/src/lib/components/announcements/Main.svelte +++ b/src/src/lib/components/announcements/Main.svelte @@ -12,7 +12,7 @@ $: ({ id, role, announcements } = data); -
+
{#if announcements.length === 0}
{ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); @@ -35,13 +46,22 @@ // Listen for changes in color scheme preference mediaQuery.addEventListener('change', (e) => updateTheme(e.matches)); - // Cleanup listener on component destroy - return () => mediaQuery.removeEventListener('change', updateTheme); + // Calculate initial content height + calculateContentHeight(); + + // Recalculate on window resize + window.addEventListener('resize', calculateContentHeight); + + // Cleanup listeners on component destroy + return () => { + mediaQuery.removeEventListener('change', updateTheme); + window.removeEventListener('resize', calculateContentHeight); + }; });
@@ -54,29 +74,32 @@
-
-

Workspaces

- -
- {#if data.workspaces && data.workspaces.length > 0} -
- {#each data.workspaces as workspace} - - {/each} -
- {:else} -
-

- You haven't been assigned to any workspaces yet. -

- -

- Please contact your administrator if you believe this is an error. -

-
- {/if} +
+
+

Workspaces

+ +
+ {#if data.workspaces && data.workspaces.length > 0} +
+ {#each data.workspaces as workspace} + + {/each} +
+ {:else} +
+

+ You haven't been assigned to any workspaces yet. +

+ +

+ Please contact your administrator if you believe this is an error. +

+
+ {/if} +
@@ -99,10 +122,10 @@ } :global(body) { - overflow: auto; + overflow: hidden; } :global(.dark) { color-scheme: dark; } - + \ No newline at end of file diff --git a/src/src/routes/(app)/workspaces/[workspace]/dashboard/+page.svelte b/src/src/routes/(app)/workspaces/[workspace]/dashboard/+page.svelte index 4a2915cf..a0c3b950 100644 --- a/src/src/routes/(app)/workspaces/[workspace]/dashboard/+page.svelte +++ b/src/src/routes/(app)/workspaces/[workspace]/dashboard/+page.svelte @@ -189,7 +189,7 @@ } -
+
From 4e46567263b2a546c9766a5028e2010aa209fb8d Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 15:36:35 +0200 Subject: [PATCH 11/24] Run Formatting and Linting --- src/src/lib/components/common/Navbar.svelte | 22 +- .../lib/components/lessons/lesson/Chat.svelte | 7 +- .../components/lessons/lesson/Header.svelte | 23 - .../components/materials/MaterialsTab.svelte | 11 +- src/src/lib/components/workspaces/Main.svelte | 2 +- .../workspaces/workspace/Sidebar.svelte | 425 +++++++++--------- .../[workspace]/dashboard/+page.svelte | 2 +- 7 files changed, 237 insertions(+), 255 deletions(-) delete mode 100644 src/src/lib/components/lessons/lesson/Header.svelte diff --git a/src/src/lib/components/common/Navbar.svelte b/src/src/lib/components/common/Navbar.svelte index d54481df..f2e8d322 100644 --- a/src/src/lib/components/common/Navbar.svelte +++ b/src/src/lib/components/common/Navbar.svelte @@ -49,7 +49,9 @@ > ClassConnect Logo - + ClassConnect @@ -65,12 +67,18 @@ class="nav-link1 group relative mt-2 flex items-center px-4 font-semibold text-white transition-colors duration-300 hover:text-white dark:text-gray-100 dark:hover:text-gray-100" > {name} - - + + {/each} - @@ -78,9 +86,9 @@ \ No newline at end of file + diff --git a/src/src/lib/components/workspaces/workspace/Sidebar.svelte b/src/src/lib/components/workspaces/workspace/Sidebar.svelte index f6793309..c82113ff 100644 --- a/src/src/lib/components/workspaces/workspace/Sidebar.svelte +++ b/src/src/lib/components/workspaces/workspace/Sidebar.svelte @@ -1,208 +1,219 @@ - - - - {`${workspace.name} - {workspace.name} - - - - {#if isLecturerLinks(currentLinks)} - {#if currentLinks.dashboard.length > 1} - - Dashboard - - - {#each currentLinks.dashboard as { name, href }} - {name} - {/each} - - {:else} - - {currentLinks.dashboard[0].name} - - {/if} - - {#if currentLinks.management.length > 1} - - Management - - - {#each currentLinks.management as { name, href }} - {name} - {/each} - - {:else} - - {currentLinks.management[0].name} - - {/if} - - {#if currentLinks.resources.length > 1} - - Resources - - - {#each currentLinks.resources as { name, href }} - {name} - {/each} - - {:else} - - {currentLinks.resources[0].name} - - {/if} - {:else if isStudentLinks(currentLinks)} - {#if currentLinks.courseWork.length > 1} - - Course Work - - - {#each currentLinks.courseWork as { name, href }} - {name} - {/each} - - {:else} - - {currentLinks.courseWork[0].name} - - {/if} - - {#if currentLinks.additional.length > 1} - - Additional - - - {#each currentLinks.additional as { name, href }} - {name} - {/each} - - {:else} - - {currentLinks.additional[0].name} - - {/if} - {/if} - - - - - - - \ No newline at end of file + import { + Navbar, + NavBrand, + NavLi, + NavUl, + NavHamburger, + Dropdown, + DropdownItem + } from 'flowbite-svelte'; + import { ChevronDownOutline } from 'flowbite-svelte-icons'; + import { page } from '$app/stores'; + + export let workspace: { name: string; image: string }; + export let role: 'lecturer' | 'student'; + + let workspaceURL = `/workspaces/${$page.params.workspace}`; + + type NavLink = { name: string; href: string }; + + interface LecturerLinks { + dashboard: NavLink[]; + management: NavLink[]; + resources: NavLink[]; + } + + interface StudentLinks { + courseWork: NavLink[]; + additional: NavLink[]; + } + + type NavLinks = { + lecturer: LecturerLinks; + student: StudentLinks; + }; + + const navLinks: NavLinks = { + lecturer: { + dashboard: [{ name: 'Dashboard', href: workspaceURL + '/dashboard' }], + management: [ + { name: 'Grade Center', href: workspaceURL + '/gradecenter' }, + { name: 'Announcements', href: workspaceURL + '/announcements' }, + { name: 'Materials', href: workspaceURL + '/materials' }, + { name: 'Lessons', href: workspaceURL + '/lessons' }, + { name: 'Quizzes', href: workspaceURL + '/quizzes' } + ], + resources: [{ name: 'Environments', href: workspaceURL + '/environments' }] + }, + student: { + courseWork: [ + { name: 'Announcements', href: workspaceURL + '/announcements' }, + { name: 'Activities', href: workspaceURL + '/activities' }, + { name: 'Materials', href: workspaceURL + '/materials' }, + { name: 'Lessons', href: workspaceURL + '/lessons' }, + { name: 'Quizzes', href: workspaceURL + '/quizzes' } + ], + additional: [ + { name: 'Environments', href: workspaceURL + '/environments' }, + { name: 'Grades', href: workspaceURL + '/grades' } + ] + } + }; + + $: currentLinks = navLinks[role]; + + function isLecturerLinks(links: LecturerLinks | StudentLinks): links is LecturerLinks { + return 'dashboard' in links; + } + + function isStudentLinks(links: LecturerLinks | StudentLinks): links is StudentLinks { + return 'courseWork' in links; + } + + let activeUrl = ''; + + const backToMain = () => { + window.location.href = '/'; + }; + + + + + {`${workspace.name} + {workspace.name} + + + + {#if isLecturerLinks(currentLinks)} + {#if currentLinks.dashboard.length > 1} + + Dashboard + + + {#each currentLinks.dashboard as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.dashboard[0].name} + + {/if} + + {#if currentLinks.management.length > 1} + + Management + + + {#each currentLinks.management as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.management[0].name} + + {/if} + + {#if currentLinks.resources.length > 1} + + Resources + + + {#each currentLinks.resources as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.resources[0].name} + + {/if} + {:else if isStudentLinks(currentLinks)} + {#if currentLinks.courseWork.length > 1} + + Course Work + + + {#each currentLinks.courseWork as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.courseWork[0].name} + + {/if} + + {#if currentLinks.additional.length > 1} + + Additional + + + {#each currentLinks.additional as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.additional[0].name} + + {/if} + {/if} + + + + + + + diff --git a/src/src/routes/(app)/workspaces/[workspace]/dashboard/+page.svelte b/src/src/routes/(app)/workspaces/[workspace]/dashboard/+page.svelte index a0c3b950..9b9ce52f 100644 --- a/src/src/routes/(app)/workspaces/[workspace]/dashboard/+page.svelte +++ b/src/src/routes/(app)/workspaces/[workspace]/dashboard/+page.svelte @@ -189,7 +189,7 @@ } -
+
From f63eb55ccb9453d0a9da52d4659b942da3fc72f1 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 21:09:18 +0200 Subject: [PATCH 12/24] Added tests for main navbar --- .../navbar/navbar-workspace.test.ts | 0 .../navbar/navbar-worskpaces.test.ts | 96 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/src/tests/unit/components/navbar/navbar-workspace.test.ts create mode 100644 src/src/tests/unit/components/navbar/navbar-worskpaces.test.ts diff --git a/src/src/tests/unit/components/navbar/navbar-workspace.test.ts b/src/src/tests/unit/components/navbar/navbar-workspace.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/src/tests/unit/components/navbar/navbar-worskpaces.test.ts b/src/src/tests/unit/components/navbar/navbar-worskpaces.test.ts new file mode 100644 index 00000000..5c79e854 --- /dev/null +++ b/src/src/tests/unit/components/navbar/navbar-worskpaces.test.ts @@ -0,0 +1,96 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import NavbarWorkspaces from '$lib/components/common/Navbar.svelte'; + +describe('NavbarWorkspaces', () => { + beforeEach(() => { + vi.mock('svelte', async () => { + const actual = await vi.importActual('svelte'); + return { + ...actual, + onMount: vi.fn((callback) => callback()), + }; + }); + + // Mock window.innerWidth + Object.defineProperty(window, 'innerWidth', { + writable: true, + configurable: true, + value: 1024, + }); + + // Mock window.location + const originalLocation = window.location; + delete (window as any).location; + window.location = { + ...originalLocation, + href: '', + }; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('renders the Navbar component', () => { + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + expect(screen.getByText('ClassConnect')).toBeTruthy(); + }); + + it('displays correct links for student role', () => { + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + expect(screen.getByText('Dashboard')).toBeTruthy(); + expect(screen.getByText('Workspaces')).toBeTruthy(); + expect(screen.getByText('Announcements')).toBeTruthy(); + expect(screen.getByText('Grades')).toBeTruthy(); + }); + + it('displays correct links for lecturer role', () => { + render(NavbarWorkspaces, { props: { role: 'lecturer', activeUrl: '/workspaces' } }); + expect(screen.getByText('Workspaces')).toBeTruthy(); + expect(screen.getByText('Announcements')).toBeTruthy(); + expect(screen.queryByText('Dashboard')).toBeFalsy(); + }); + + it('displays correct links for admin role', () => { + render(NavbarWorkspaces, { props: { role: 'admin', activeUrl: '/dashboard' } }); + expect(screen.getByText('Dashboard')).toBeTruthy(); + expect(screen.getByText('Announcements')).toBeTruthy(); + expect(screen.getByText('Organisation')).toBeTruthy(); + expect(screen.getByText('Workspaces')).toBeTruthy(); + expect(screen.getByText('Admins')).toBeTruthy(); + expect(screen.getByText('Lecturers')).toBeTruthy(); + expect(screen.getByText('Students')).toBeTruthy(); + }); + + it('displays common links for all roles', () => { + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + expect(screen.getByText('Settings')).toBeTruthy(); + expect(screen.getByText('FAQ')).toBeTruthy(); + }); + + it('displays the hamburger menu on small screens', () => { + window.innerWidth = 800; + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + expect(screen.getByRole('button', { name: /toggle menu/i })).toBeTruthy(); + }); + + it('does not display the hamburger menu on large screens', () => { + window.innerWidth = 1024; + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + expect(screen.queryByRole('button', { name: /toggle menu/i })).toBeFalsy(); + }); + + it('highlights the active link', () => { + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + const activeLink = screen.getByText('Dashboard').closest('a'); + expect(activeLink).toHaveAttribute('aria-current', 'page'); + }); + + it('redirects to sign out page when sign out button is clicked', () => { + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + const signOutButton = screen.getByText('Sign Out'); + signOutButton.click(); + expect(window.location.href).toBe('/signout'); + }); +}); \ No newline at end of file From f4b46597ce5c6db011229d6e1849647b729f4522 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 21:14:04 +0200 Subject: [PATCH 13/24] Added tests for workspace navbar --- .../navbar/navbar-workspace.test.ts | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/src/tests/unit/components/navbar/navbar-workspace.test.ts b/src/src/tests/unit/components/navbar/navbar-workspace.test.ts index e69de29b..3610984c 100644 --- a/src/src/tests/unit/components/navbar/navbar-workspace.test.ts +++ b/src/src/tests/unit/components/navbar/navbar-workspace.test.ts @@ -0,0 +1,66 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import NavbarWorkspace from '$lib/components/workspaces/workspace/Sidebar.svelte'; + +// Mock $app/stores +vi.mock('$app/stores', () => ({ + page: { + subscribe: vi.fn().mockImplementation((fn) => { + fn({ params: { workspace: 'test-workspace' } }); + return () => {}; + }) + } +})); + +describe('NavbarWorkspace', () => { + const mockWorkspace = { name: 'Test Workspace', image: 'test-image.jpg' }; + + beforeEach(() => { + // Mock window.location + vi.stubGlobal('location', { href: '' }); + }); + + it('renders the Navbar component with workspace name and image', () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + expect(screen.getByText('Test Workspace')).toBeTruthy(); + expect(screen.getByAltText('Test Workspace logo')).toBeTruthy(); + }); + + it('displays correct links for lecturer role', () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + expect(screen.getByText('Dashboard')).toBeTruthy(); + expect(screen.getByText('Management')).toBeTruthy(); + expect(screen.getByText('Resources')).toBeTruthy(); + }); + + it('displays correct links for student role', () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'student' } }); + expect(screen.getByText('Course Work')).toBeTruthy(); + expect(screen.getByText('Additional')).toBeTruthy(); + }); + + it('redirects to main page when "Leave" button is clicked', () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + const leaveButton = screen.getByText('Leave Test Workspace'); + leaveButton.click(); + expect(window.location.href).toBe('/'); + }); + + it('renders dropdown for Management links', async () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + const managementDropdown = screen.getByText('Management'); + expect(managementDropdown).toBeTruthy(); + }); + + it('renders dropdown for Course Work links for student', async () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'student' } }); + const courseWorkDropdown = screen.getByText('Course Work'); + expect(courseWorkDropdown).toBeTruthy(); + }); + + it('renders hamburger menu', () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + const hamburgerMenu = screen.getByRole('button', { name: /toggle menu/i }); + expect(hamburgerMenu).toBeTruthy(); + }); +}); \ No newline at end of file From 02892898caf4ff5fce4503cbc21d4c991b6641e3 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 21:22:29 +0200 Subject: [PATCH 14/24] Added tests for workspaces cards --- .../unit/components/workspaces/card.test.ts | 73 +++++++++++++++++ src/src/tests/unit/page/workspaces.test.ts | 79 +++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 src/src/tests/unit/components/workspaces/card.test.ts create mode 100644 src/src/tests/unit/page/workspaces.test.ts diff --git a/src/src/tests/unit/components/workspaces/card.test.ts b/src/src/tests/unit/components/workspaces/card.test.ts new file mode 100644 index 00000000..89248f34 --- /dev/null +++ b/src/src/tests/unit/components/workspaces/card.test.ts @@ -0,0 +1,73 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render, fireEvent, screen } from '@testing-library/svelte'; +import Card from '$lib/components/workspaces/workspace/Card.svelte'; + +// Mock $app/navigation +vi.mock('$app/navigation', () => ({ + goto: vi.fn() +})); + +describe('Card', () => { + const mockWorkspace = { + id: '1', + name: 'Test Workspace', + image: 'test-image.jpg', + description: 'This is a test workspace' + }; + + it('renders the Card component with correct content', () => { + render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); + + expect(screen.getByText('Test Workspace')).toBeTruthy(); + expect(screen.getByText('This is a test workspace')).toBeTruthy(); + expect(screen.getByAltText('Test Workspace')).toBeTruthy(); + expect(screen.getByRole('button', { name: /Open/i })).toBeTruthy(); + }); + + it('navigates to correct URL for student role when button is clicked', async () => { + const { goto } = await import('$app/navigation'); + render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); + + const openButton = screen.getByRole('button', { name: /Open/i }); + await fireEvent.click(openButton); + + expect(goto).toHaveBeenCalledWith('/workspaces/1/announcements'); + }); + + it('navigates to correct URL for lecturer role when button is clicked', async () => { + const { goto } = await import('$app/navigation'); + render(Card, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + + const openButton = screen.getByRole('button', { name: /Open/i }); + await fireEvent.click(openButton); + + expect(goto).toHaveBeenCalledWith('/workspaces/1/dashboard'); + }); + + it('applies hover styles to image', async () => { + const { container } = render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); + + const image = container.querySelector('img'); + expect(image).toBeTruthy(); + expect(image?.classList.contains('group-hover:scale-110')).toBeTruthy(); + }); + + it('applies correct background styles', () => { + const { container } = render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); + + const backgroundDiv = container.querySelector('.absolute.inset-0'); + expect(backgroundDiv).toBeTruthy(); + expect(backgroundDiv?.classList.contains('bg-gradient-to-br')).toBeTruthy(); + expect(backgroundDiv?.classList.contains('backdrop-blur-md')).toBeTruthy(); + expect(backgroundDiv?.classList.contains('group-hover:backdrop-blur-lg')).toBeTruthy(); + }); + + it('applies correct button styles', () => { + render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); + + const button = screen.getByRole('button', { name: /Open/i }); + expect(button.classList.contains('bg-gradient-to-r')).toBeTruthy(); + expect(button.classList.contains('from-green-400')).toBeTruthy(); + expect(button.classList.contains('to-emerald-600')).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/src/src/tests/unit/page/workspaces.test.ts b/src/src/tests/unit/page/workspaces.test.ts new file mode 100644 index 00000000..f4b46a17 --- /dev/null +++ b/src/src/tests/unit/page/workspaces.test.ts @@ -0,0 +1,79 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, screen } from '@testing-library/svelte'; +import Workspaces from './Workspaces.svelte'; + +describe('Workspaces', () => { + beforeEach(() => { + // Mock window methods and properties + vi.stubGlobal('matchMedia', vi.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + }))); + + vi.stubGlobal('innerHeight', 1000); + + // Mock document methods + document.querySelector = vi.fn().mockReturnValue({ offsetHeight: 100 }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('renders the Workspaces component', () => { + const { container } = render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); + expect(container.querySelector('main')).toBeTruthy(); + expect(screen.getByText('Workspaces')).toBeTruthy(); + }); + + it('displays message when no workspaces are available', () => { + render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); + expect(screen.getByText("You haven't been assigned to any workspaces yet.")).toBeTruthy(); + }); + + it('renders workspace cards when workspaces are available', () => { + const mockWorkspaces = [ + { id: '1', name: 'Workspace 1', image: 'image1.jpg' }, + { id: '2', name: 'Workspace 2', image: 'image2.jpg' }, + ]; + render(Workspaces, { props: { data: { workspaces: mockWorkspaces, role: 'student' } } }); + expect(screen.getByText('Workspace 1')).toBeTruthy(); + expect(screen.getByText('Workspace 2')).toBeTruthy(); + }); + + it('calculates content height correctly', () => { + const { container } = render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); + const contentDiv = container.querySelector('.rounded-xl'); + expect(contentDiv).toBeTruthy(); + expect(contentDiv?.getAttribute('style')).toContain('height: 852px'); // 1000 - 100 - 48 + }); + + it('generates floating elements', () => { + const { container } = render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); + const floatingElements = container.querySelectorAll('.animate-float'); + expect(floatingElements.length).toBe(20); + }); + + it('updates theme based on system preference', () => { + vi.stubGlobal('matchMedia', vi.fn().mockImplementation(query => ({ + matches: true, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + }))); + + render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); + expect(document.documentElement.classList.contains('dark')).toBe(true); + }); + +}); \ No newline at end of file From 5daeb4547dc7147ee847faba27d282579daf5910 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 21:27:24 +0200 Subject: [PATCH 15/24] Added tests for preview page --- .../unit/components/lessons/preview.test.ts | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/src/tests/unit/components/lessons/preview.test.ts diff --git a/src/src/tests/unit/components/lessons/preview.test.ts b/src/src/tests/unit/components/lessons/preview.test.ts new file mode 100644 index 00000000..576a6552 --- /dev/null +++ b/src/src/tests/unit/components/lessons/preview.test.ts @@ -0,0 +1,96 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, fireEvent, cleanup } from '@testing-library/svelte'; +import { tick } from 'svelte'; +import Preview from '$lib/components/lessons/lesson/Preview.svelte'; // Assuming the component file is named Preview.svelte +import { goto } from '$app/navigation'; + +// Mock the necessary modules and functions +vi.mock('$app/navigation', () => ({ + goto: vi.fn(), +})); + +describe('Preview Component', () => { + beforeEach(() => { + // Mock navigator.mediaDevices + Object.defineProperty(global.navigator, 'mediaDevices', { + value: { + getUserMedia: vi.fn().mockResolvedValue({ + getTracks: () => [{ enabled: true }], + }), + }, + writable: true, + }); + + // Mock AudioContext + global.AudioContext = vi.fn().mockImplementation(() => ({ + createMediaStreamSource: vi.fn().mockReturnValue({ + connect: vi.fn(), + }), + createAnalyser: vi.fn().mockReturnValue({ + frequencyBinCount: 1024, + getByteFrequencyData: vi.fn(), + }), + close: vi.fn(), + })); + + // Mock HTMLVideoElement + global.HTMLVideoElement.prototype.play = vi.fn(); + }); + + afterEach(() => { + cleanup(); + vi.resetAllMocks(); + }); + + it('renders correctly', async () => { + const { getByText, getByRole } = render(Preview, { onJoin: vi.fn() }); + + expect(getByText('Check your audio and video')).toBeTruthy(); + expect(getByRole('video')).toBeTruthy(); + expect(getByText('Microphone')).toBeTruthy(); + expect(getByText('Camera')).toBeTruthy(); + expect(getByText('Join')).toBeTruthy(); + expect(getByText('Exit')).toBeTruthy(); + }); + + it('toggles microphone', async () => { + const { getByText } = render(Preview, { onJoin: vi.fn() }); + const micToggle = getByText('Microphone'); + + await fireEvent.click(micToggle); + await tick(); + + // Check if the microphone track's enabled property was toggled + expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ audio: true }); + }); + + it('toggles camera', async () => { + const { getByText } = render(Preview, { onJoin: vi.fn() }); + const cameraToggle = getByText('Camera'); + + await fireEvent.click(cameraToggle); + await tick(); + + // Check if the video track's enabled property was toggled + expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ video: true }); + }); + + it('calls onJoin when Join button is clicked', async () => { + const onJoinMock = vi.fn(); + const { getByText } = render(Preview, { onJoin: onJoinMock }); + const joinButton = getByText('Join'); + + await fireEvent.click(joinButton); + + expect(onJoinMock).toHaveBeenCalledWith(true, true); + }); + + it('navigates to home when Exit button is clicked', async () => { + const { getByText } = render(Preview, { onJoin: vi.fn() }); + const exitButton = getByText('Exit'); + + await fireEvent.click(exitButton); + + expect(vi.mocked(goto)).toHaveBeenCalledWith('/'); + }); +}); \ No newline at end of file From a4c43e66f7cc39e81fabae11bd2e3bb279cea4d0 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Tue, 17 Sep 2024 21:28:31 +0200 Subject: [PATCH 16/24] Run Formatting and Linting --- .../unit/components/lessons/preview.test.ts | 170 +++++++++--------- .../navbar/navbar-workspace.test.ts | 98 +++++----- .../navbar/navbar-worskpaces.test.ts | 158 ++++++++-------- .../unit/components/workspaces/card.test.ts | 128 ++++++------- src/src/tests/unit/page/workspaces.test.ts | 139 +++++++------- 5 files changed, 352 insertions(+), 341 deletions(-) diff --git a/src/src/tests/unit/components/lessons/preview.test.ts b/src/src/tests/unit/components/lessons/preview.test.ts index 576a6552..73d0f511 100644 --- a/src/src/tests/unit/components/lessons/preview.test.ts +++ b/src/src/tests/unit/components/lessons/preview.test.ts @@ -6,91 +6,91 @@ import { goto } from '$app/navigation'; // Mock the necessary modules and functions vi.mock('$app/navigation', () => ({ - goto: vi.fn(), + goto: vi.fn() })); describe('Preview Component', () => { - beforeEach(() => { - // Mock navigator.mediaDevices - Object.defineProperty(global.navigator, 'mediaDevices', { - value: { - getUserMedia: vi.fn().mockResolvedValue({ - getTracks: () => [{ enabled: true }], - }), - }, - writable: true, - }); - - // Mock AudioContext - global.AudioContext = vi.fn().mockImplementation(() => ({ - createMediaStreamSource: vi.fn().mockReturnValue({ - connect: vi.fn(), - }), - createAnalyser: vi.fn().mockReturnValue({ - frequencyBinCount: 1024, - getByteFrequencyData: vi.fn(), - }), - close: vi.fn(), - })); - - // Mock HTMLVideoElement - global.HTMLVideoElement.prototype.play = vi.fn(); - }); - - afterEach(() => { - cleanup(); - vi.resetAllMocks(); - }); - - it('renders correctly', async () => { - const { getByText, getByRole } = render(Preview, { onJoin: vi.fn() }); - - expect(getByText('Check your audio and video')).toBeTruthy(); - expect(getByRole('video')).toBeTruthy(); - expect(getByText('Microphone')).toBeTruthy(); - expect(getByText('Camera')).toBeTruthy(); - expect(getByText('Join')).toBeTruthy(); - expect(getByText('Exit')).toBeTruthy(); - }); - - it('toggles microphone', async () => { - const { getByText } = render(Preview, { onJoin: vi.fn() }); - const micToggle = getByText('Microphone'); - - await fireEvent.click(micToggle); - await tick(); - - // Check if the microphone track's enabled property was toggled - expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ audio: true }); - }); - - it('toggles camera', async () => { - const { getByText } = render(Preview, { onJoin: vi.fn() }); - const cameraToggle = getByText('Camera'); - - await fireEvent.click(cameraToggle); - await tick(); - - // Check if the video track's enabled property was toggled - expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ video: true }); - }); - - it('calls onJoin when Join button is clicked', async () => { - const onJoinMock = vi.fn(); - const { getByText } = render(Preview, { onJoin: onJoinMock }); - const joinButton = getByText('Join'); - - await fireEvent.click(joinButton); - - expect(onJoinMock).toHaveBeenCalledWith(true, true); - }); - - it('navigates to home when Exit button is clicked', async () => { - const { getByText } = render(Preview, { onJoin: vi.fn() }); - const exitButton = getByText('Exit'); - - await fireEvent.click(exitButton); - - expect(vi.mocked(goto)).toHaveBeenCalledWith('/'); - }); -}); \ No newline at end of file + beforeEach(() => { + // Mock navigator.mediaDevices + Object.defineProperty(global.navigator, 'mediaDevices', { + value: { + getUserMedia: vi.fn().mockResolvedValue({ + getTracks: () => [{ enabled: true }] + }) + }, + writable: true + }); + + // Mock AudioContext + global.AudioContext = vi.fn().mockImplementation(() => ({ + createMediaStreamSource: vi.fn().mockReturnValue({ + connect: vi.fn() + }), + createAnalyser: vi.fn().mockReturnValue({ + frequencyBinCount: 1024, + getByteFrequencyData: vi.fn() + }), + close: vi.fn() + })); + + // Mock HTMLVideoElement + global.HTMLVideoElement.prototype.play = vi.fn(); + }); + + afterEach(() => { + cleanup(); + vi.resetAllMocks(); + }); + + it('renders correctly', async () => { + const { getByText, getByRole } = render(Preview, { onJoin: vi.fn() }); + + expect(getByText('Check your audio and video')).toBeTruthy(); + expect(getByRole('video')).toBeTruthy(); + expect(getByText('Microphone')).toBeTruthy(); + expect(getByText('Camera')).toBeTruthy(); + expect(getByText('Join')).toBeTruthy(); + expect(getByText('Exit')).toBeTruthy(); + }); + + it('toggles microphone', async () => { + const { getByText } = render(Preview, { onJoin: vi.fn() }); + const micToggle = getByText('Microphone'); + + await fireEvent.click(micToggle); + await tick(); + + // Check if the microphone track's enabled property was toggled + expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ audio: true }); + }); + + it('toggles camera', async () => { + const { getByText } = render(Preview, { onJoin: vi.fn() }); + const cameraToggle = getByText('Camera'); + + await fireEvent.click(cameraToggle); + await tick(); + + // Check if the video track's enabled property was toggled + expect(navigator.mediaDevices.getUserMedia).toHaveBeenCalledWith({ video: true }); + }); + + it('calls onJoin when Join button is clicked', async () => { + const onJoinMock = vi.fn(); + const { getByText } = render(Preview, { onJoin: onJoinMock }); + const joinButton = getByText('Join'); + + await fireEvent.click(joinButton); + + expect(onJoinMock).toHaveBeenCalledWith(true, true); + }); + + it('navigates to home when Exit button is clicked', async () => { + const { getByText } = render(Preview, { onJoin: vi.fn() }); + const exitButton = getByText('Exit'); + + await fireEvent.click(exitButton); + + expect(vi.mocked(goto)).toHaveBeenCalledWith('/'); + }); +}); diff --git a/src/src/tests/unit/components/navbar/navbar-workspace.test.ts b/src/src/tests/unit/components/navbar/navbar-workspace.test.ts index 3610984c..dcdbebfe 100644 --- a/src/src/tests/unit/components/navbar/navbar-workspace.test.ts +++ b/src/src/tests/unit/components/navbar/navbar-workspace.test.ts @@ -4,63 +4,63 @@ import NavbarWorkspace from '$lib/components/workspaces/workspace/Sidebar.svelte // Mock $app/stores vi.mock('$app/stores', () => ({ - page: { - subscribe: vi.fn().mockImplementation((fn) => { - fn({ params: { workspace: 'test-workspace' } }); - return () => {}; - }) - } + page: { + subscribe: vi.fn().mockImplementation((fn) => { + fn({ params: { workspace: 'test-workspace' } }); + return () => {}; + }) + } })); describe('NavbarWorkspace', () => { - const mockWorkspace = { name: 'Test Workspace', image: 'test-image.jpg' }; + const mockWorkspace = { name: 'Test Workspace', image: 'test-image.jpg' }; - beforeEach(() => { - // Mock window.location - vi.stubGlobal('location', { href: '' }); - }); + beforeEach(() => { + // Mock window.location + vi.stubGlobal('location', { href: '' }); + }); - it('renders the Navbar component with workspace name and image', () => { - render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); - expect(screen.getByText('Test Workspace')).toBeTruthy(); - expect(screen.getByAltText('Test Workspace logo')).toBeTruthy(); - }); + it('renders the Navbar component with workspace name and image', () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + expect(screen.getByText('Test Workspace')).toBeTruthy(); + expect(screen.getByAltText('Test Workspace logo')).toBeTruthy(); + }); - it('displays correct links for lecturer role', () => { - render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); - expect(screen.getByText('Dashboard')).toBeTruthy(); - expect(screen.getByText('Management')).toBeTruthy(); - expect(screen.getByText('Resources')).toBeTruthy(); - }); + it('displays correct links for lecturer role', () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + expect(screen.getByText('Dashboard')).toBeTruthy(); + expect(screen.getByText('Management')).toBeTruthy(); + expect(screen.getByText('Resources')).toBeTruthy(); + }); - it('displays correct links for student role', () => { - render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'student' } }); - expect(screen.getByText('Course Work')).toBeTruthy(); - expect(screen.getByText('Additional')).toBeTruthy(); - }); + it('displays correct links for student role', () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'student' } }); + expect(screen.getByText('Course Work')).toBeTruthy(); + expect(screen.getByText('Additional')).toBeTruthy(); + }); - it('redirects to main page when "Leave" button is clicked', () => { - render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); - const leaveButton = screen.getByText('Leave Test Workspace'); - leaveButton.click(); - expect(window.location.href).toBe('/'); - }); + it('redirects to main page when "Leave" button is clicked', () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + const leaveButton = screen.getByText('Leave Test Workspace'); + leaveButton.click(); + expect(window.location.href).toBe('/'); + }); - it('renders dropdown for Management links', async () => { - render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); - const managementDropdown = screen.getByText('Management'); - expect(managementDropdown).toBeTruthy(); - }); + it('renders dropdown for Management links', async () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + const managementDropdown = screen.getByText('Management'); + expect(managementDropdown).toBeTruthy(); + }); - it('renders dropdown for Course Work links for student', async () => { - render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'student' } }); - const courseWorkDropdown = screen.getByText('Course Work'); - expect(courseWorkDropdown).toBeTruthy(); - }); + it('renders dropdown for Course Work links for student', async () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'student' } }); + const courseWorkDropdown = screen.getByText('Course Work'); + expect(courseWorkDropdown).toBeTruthy(); + }); - it('renders hamburger menu', () => { - render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); - const hamburgerMenu = screen.getByRole('button', { name: /toggle menu/i }); - expect(hamburgerMenu).toBeTruthy(); - }); -}); \ No newline at end of file + it('renders hamburger menu', () => { + render(NavbarWorkspace, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + const hamburgerMenu = screen.getByRole('button', { name: /toggle menu/i }); + expect(hamburgerMenu).toBeTruthy(); + }); +}); diff --git a/src/src/tests/unit/components/navbar/navbar-worskpaces.test.ts b/src/src/tests/unit/components/navbar/navbar-worskpaces.test.ts index 5c79e854..466e1bb8 100644 --- a/src/src/tests/unit/components/navbar/navbar-worskpaces.test.ts +++ b/src/src/tests/unit/components/navbar/navbar-worskpaces.test.ts @@ -3,94 +3,94 @@ import { render, screen } from '@testing-library/svelte'; import NavbarWorkspaces from '$lib/components/common/Navbar.svelte'; describe('NavbarWorkspaces', () => { - beforeEach(() => { - vi.mock('svelte', async () => { - const actual = await vi.importActual('svelte'); - return { - ...actual, - onMount: vi.fn((callback) => callback()), - }; - }); + beforeEach(() => { + vi.mock('svelte', async () => { + const actual = await vi.importActual('svelte'); + return { + ...actual, + onMount: vi.fn((callback) => callback()) + }; + }); - // Mock window.innerWidth - Object.defineProperty(window, 'innerWidth', { - writable: true, - configurable: true, - value: 1024, - }); + // Mock window.innerWidth + Object.defineProperty(window, 'innerWidth', { + writable: true, + configurable: true, + value: 1024 + }); - // Mock window.location - const originalLocation = window.location; - delete (window as any).location; - window.location = { - ...originalLocation, - href: '', - }; - }); + // Mock window.location + const originalLocation = window.location; + delete (window as any).location; + window.location = { + ...originalLocation, + href: '' + }; + }); - afterEach(() => { - vi.restoreAllMocks(); - }); + afterEach(() => { + vi.restoreAllMocks(); + }); - it('renders the Navbar component', () => { - render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); - expect(screen.getByText('ClassConnect')).toBeTruthy(); - }); + it('renders the Navbar component', () => { + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + expect(screen.getByText('ClassConnect')).toBeTruthy(); + }); - it('displays correct links for student role', () => { - render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); - expect(screen.getByText('Dashboard')).toBeTruthy(); - expect(screen.getByText('Workspaces')).toBeTruthy(); - expect(screen.getByText('Announcements')).toBeTruthy(); - expect(screen.getByText('Grades')).toBeTruthy(); - }); + it('displays correct links for student role', () => { + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + expect(screen.getByText('Dashboard')).toBeTruthy(); + expect(screen.getByText('Workspaces')).toBeTruthy(); + expect(screen.getByText('Announcements')).toBeTruthy(); + expect(screen.getByText('Grades')).toBeTruthy(); + }); - it('displays correct links for lecturer role', () => { - render(NavbarWorkspaces, { props: { role: 'lecturer', activeUrl: '/workspaces' } }); - expect(screen.getByText('Workspaces')).toBeTruthy(); - expect(screen.getByText('Announcements')).toBeTruthy(); - expect(screen.queryByText('Dashboard')).toBeFalsy(); - }); + it('displays correct links for lecturer role', () => { + render(NavbarWorkspaces, { props: { role: 'lecturer', activeUrl: '/workspaces' } }); + expect(screen.getByText('Workspaces')).toBeTruthy(); + expect(screen.getByText('Announcements')).toBeTruthy(); + expect(screen.queryByText('Dashboard')).toBeFalsy(); + }); - it('displays correct links for admin role', () => { - render(NavbarWorkspaces, { props: { role: 'admin', activeUrl: '/dashboard' } }); - expect(screen.getByText('Dashboard')).toBeTruthy(); - expect(screen.getByText('Announcements')).toBeTruthy(); - expect(screen.getByText('Organisation')).toBeTruthy(); - expect(screen.getByText('Workspaces')).toBeTruthy(); - expect(screen.getByText('Admins')).toBeTruthy(); - expect(screen.getByText('Lecturers')).toBeTruthy(); - expect(screen.getByText('Students')).toBeTruthy(); - }); + it('displays correct links for admin role', () => { + render(NavbarWorkspaces, { props: { role: 'admin', activeUrl: '/dashboard' } }); + expect(screen.getByText('Dashboard')).toBeTruthy(); + expect(screen.getByText('Announcements')).toBeTruthy(); + expect(screen.getByText('Organisation')).toBeTruthy(); + expect(screen.getByText('Workspaces')).toBeTruthy(); + expect(screen.getByText('Admins')).toBeTruthy(); + expect(screen.getByText('Lecturers')).toBeTruthy(); + expect(screen.getByText('Students')).toBeTruthy(); + }); - it('displays common links for all roles', () => { - render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); - expect(screen.getByText('Settings')).toBeTruthy(); - expect(screen.getByText('FAQ')).toBeTruthy(); - }); + it('displays common links for all roles', () => { + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + expect(screen.getByText('Settings')).toBeTruthy(); + expect(screen.getByText('FAQ')).toBeTruthy(); + }); - it('displays the hamburger menu on small screens', () => { - window.innerWidth = 800; - render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); - expect(screen.getByRole('button', { name: /toggle menu/i })).toBeTruthy(); - }); + it('displays the hamburger menu on small screens', () => { + window.innerWidth = 800; + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + expect(screen.getByRole('button', { name: /toggle menu/i })).toBeTruthy(); + }); - it('does not display the hamburger menu on large screens', () => { - window.innerWidth = 1024; - render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); - expect(screen.queryByRole('button', { name: /toggle menu/i })).toBeFalsy(); - }); + it('does not display the hamburger menu on large screens', () => { + window.innerWidth = 1024; + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + expect(screen.queryByRole('button', { name: /toggle menu/i })).toBeFalsy(); + }); - it('highlights the active link', () => { - render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); - const activeLink = screen.getByText('Dashboard').closest('a'); - expect(activeLink).toHaveAttribute('aria-current', 'page'); - }); + it('highlights the active link', () => { + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + const activeLink = screen.getByText('Dashboard').closest('a'); + expect(activeLink).toHaveAttribute('aria-current', 'page'); + }); - it('redirects to sign out page when sign out button is clicked', () => { - render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); - const signOutButton = screen.getByText('Sign Out'); - signOutButton.click(); - expect(window.location.href).toBe('/signout'); - }); -}); \ No newline at end of file + it('redirects to sign out page when sign out button is clicked', () => { + render(NavbarWorkspaces, { props: { role: 'student', activeUrl: '/dashboard' } }); + const signOutButton = screen.getByText('Sign Out'); + signOutButton.click(); + expect(window.location.href).toBe('/signout'); + }); +}); diff --git a/src/src/tests/unit/components/workspaces/card.test.ts b/src/src/tests/unit/components/workspaces/card.test.ts index 89248f34..cdd68a09 100644 --- a/src/src/tests/unit/components/workspaces/card.test.ts +++ b/src/src/tests/unit/components/workspaces/card.test.ts @@ -4,70 +4,70 @@ import Card from '$lib/components/workspaces/workspace/Card.svelte'; // Mock $app/navigation vi.mock('$app/navigation', () => ({ - goto: vi.fn() + goto: vi.fn() })); describe('Card', () => { - const mockWorkspace = { - id: '1', - name: 'Test Workspace', - image: 'test-image.jpg', - description: 'This is a test workspace' - }; - - it('renders the Card component with correct content', () => { - render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); - - expect(screen.getByText('Test Workspace')).toBeTruthy(); - expect(screen.getByText('This is a test workspace')).toBeTruthy(); - expect(screen.getByAltText('Test Workspace')).toBeTruthy(); - expect(screen.getByRole('button', { name: /Open/i })).toBeTruthy(); - }); - - it('navigates to correct URL for student role when button is clicked', async () => { - const { goto } = await import('$app/navigation'); - render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); - - const openButton = screen.getByRole('button', { name: /Open/i }); - await fireEvent.click(openButton); - - expect(goto).toHaveBeenCalledWith('/workspaces/1/announcements'); - }); - - it('navigates to correct URL for lecturer role when button is clicked', async () => { - const { goto } = await import('$app/navigation'); - render(Card, { props: { workspace: mockWorkspace, role: 'lecturer' } }); - - const openButton = screen.getByRole('button', { name: /Open/i }); - await fireEvent.click(openButton); - - expect(goto).toHaveBeenCalledWith('/workspaces/1/dashboard'); - }); - - it('applies hover styles to image', async () => { - const { container } = render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); - - const image = container.querySelector('img'); - expect(image).toBeTruthy(); - expect(image?.classList.contains('group-hover:scale-110')).toBeTruthy(); - }); - - it('applies correct background styles', () => { - const { container } = render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); - - const backgroundDiv = container.querySelector('.absolute.inset-0'); - expect(backgroundDiv).toBeTruthy(); - expect(backgroundDiv?.classList.contains('bg-gradient-to-br')).toBeTruthy(); - expect(backgroundDiv?.classList.contains('backdrop-blur-md')).toBeTruthy(); - expect(backgroundDiv?.classList.contains('group-hover:backdrop-blur-lg')).toBeTruthy(); - }); - - it('applies correct button styles', () => { - render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); - - const button = screen.getByRole('button', { name: /Open/i }); - expect(button.classList.contains('bg-gradient-to-r')).toBeTruthy(); - expect(button.classList.contains('from-green-400')).toBeTruthy(); - expect(button.classList.contains('to-emerald-600')).toBeTruthy(); - }); -}); \ No newline at end of file + const mockWorkspace = { + id: '1', + name: 'Test Workspace', + image: 'test-image.jpg', + description: 'This is a test workspace' + }; + + it('renders the Card component with correct content', () => { + render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); + + expect(screen.getByText('Test Workspace')).toBeTruthy(); + expect(screen.getByText('This is a test workspace')).toBeTruthy(); + expect(screen.getByAltText('Test Workspace')).toBeTruthy(); + expect(screen.getByRole('button', { name: /Open/i })).toBeTruthy(); + }); + + it('navigates to correct URL for student role when button is clicked', async () => { + const { goto } = await import('$app/navigation'); + render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); + + const openButton = screen.getByRole('button', { name: /Open/i }); + await fireEvent.click(openButton); + + expect(goto).toHaveBeenCalledWith('/workspaces/1/announcements'); + }); + + it('navigates to correct URL for lecturer role when button is clicked', async () => { + const { goto } = await import('$app/navigation'); + render(Card, { props: { workspace: mockWorkspace, role: 'lecturer' } }); + + const openButton = screen.getByRole('button', { name: /Open/i }); + await fireEvent.click(openButton); + + expect(goto).toHaveBeenCalledWith('/workspaces/1/dashboard'); + }); + + it('applies hover styles to image', async () => { + const { container } = render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); + + const image = container.querySelector('img'); + expect(image).toBeTruthy(); + expect(image?.classList.contains('group-hover:scale-110')).toBeTruthy(); + }); + + it('applies correct background styles', () => { + const { container } = render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); + + const backgroundDiv = container.querySelector('.absolute.inset-0'); + expect(backgroundDiv).toBeTruthy(); + expect(backgroundDiv?.classList.contains('bg-gradient-to-br')).toBeTruthy(); + expect(backgroundDiv?.classList.contains('backdrop-blur-md')).toBeTruthy(); + expect(backgroundDiv?.classList.contains('group-hover:backdrop-blur-lg')).toBeTruthy(); + }); + + it('applies correct button styles', () => { + render(Card, { props: { workspace: mockWorkspace, role: 'student' } }); + + const button = screen.getByRole('button', { name: /Open/i }); + expect(button.classList.contains('bg-gradient-to-r')).toBeTruthy(); + expect(button.classList.contains('from-green-400')).toBeTruthy(); + expect(button.classList.contains('to-emerald-600')).toBeTruthy(); + }); +}); diff --git a/src/src/tests/unit/page/workspaces.test.ts b/src/src/tests/unit/page/workspaces.test.ts index f4b46a17..60d8e34d 100644 --- a/src/src/tests/unit/page/workspaces.test.ts +++ b/src/src/tests/unit/page/workspaces.test.ts @@ -3,77 +3,88 @@ import { render, screen } from '@testing-library/svelte'; import Workspaces from './Workspaces.svelte'; describe('Workspaces', () => { - beforeEach(() => { - // Mock window methods and properties - vi.stubGlobal('matchMedia', vi.fn().mockImplementation(query => ({ - matches: false, - media: query, - onchange: null, - addListener: vi.fn(), - removeListener: vi.fn(), - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - dispatchEvent: vi.fn(), - }))); + beforeEach(() => { + // Mock window methods and properties + vi.stubGlobal( + 'matchMedia', + vi.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn() + })) + ); - vi.stubGlobal('innerHeight', 1000); + vi.stubGlobal('innerHeight', 1000); - // Mock document methods - document.querySelector = vi.fn().mockReturnValue({ offsetHeight: 100 }); - }); + // Mock document methods + document.querySelector = vi.fn().mockReturnValue({ offsetHeight: 100 }); + }); - afterEach(() => { - vi.restoreAllMocks(); - }); + afterEach(() => { + vi.restoreAllMocks(); + }); - it('renders the Workspaces component', () => { - const { container } = render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); - expect(container.querySelector('main')).toBeTruthy(); - expect(screen.getByText('Workspaces')).toBeTruthy(); - }); + it('renders the Workspaces component', () => { + const { container } = render(Workspaces, { + props: { data: { workspaces: [], role: 'student' } } + }); + expect(container.querySelector('main')).toBeTruthy(); + expect(screen.getByText('Workspaces')).toBeTruthy(); + }); - it('displays message when no workspaces are available', () => { - render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); - expect(screen.getByText("You haven't been assigned to any workspaces yet.")).toBeTruthy(); - }); + it('displays message when no workspaces are available', () => { + render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); + expect(screen.getByText("You haven't been assigned to any workspaces yet.")).toBeTruthy(); + }); - it('renders workspace cards when workspaces are available', () => { - const mockWorkspaces = [ - { id: '1', name: 'Workspace 1', image: 'image1.jpg' }, - { id: '2', name: 'Workspace 2', image: 'image2.jpg' }, - ]; - render(Workspaces, { props: { data: { workspaces: mockWorkspaces, role: 'student' } } }); - expect(screen.getByText('Workspace 1')).toBeTruthy(); - expect(screen.getByText('Workspace 2')).toBeTruthy(); - }); + it('renders workspace cards when workspaces are available', () => { + const mockWorkspaces = [ + { id: '1', name: 'Workspace 1', image: 'image1.jpg' }, + { id: '2', name: 'Workspace 2', image: 'image2.jpg' } + ]; + render(Workspaces, { props: { data: { workspaces: mockWorkspaces, role: 'student' } } }); + expect(screen.getByText('Workspace 1')).toBeTruthy(); + expect(screen.getByText('Workspace 2')).toBeTruthy(); + }); - it('calculates content height correctly', () => { - const { container } = render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); - const contentDiv = container.querySelector('.rounded-xl'); - expect(contentDiv).toBeTruthy(); - expect(contentDiv?.getAttribute('style')).toContain('height: 852px'); // 1000 - 100 - 48 - }); + it('calculates content height correctly', () => { + const { container } = render(Workspaces, { + props: { data: { workspaces: [], role: 'student' } } + }); + const contentDiv = container.querySelector('.rounded-xl'); + expect(contentDiv).toBeTruthy(); + expect(contentDiv?.getAttribute('style')).toContain('height: 852px'); // 1000 - 100 - 48 + }); - it('generates floating elements', () => { - const { container } = render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); - const floatingElements = container.querySelectorAll('.animate-float'); - expect(floatingElements.length).toBe(20); - }); + it('generates floating elements', () => { + const { container } = render(Workspaces, { + props: { data: { workspaces: [], role: 'student' } } + }); + const floatingElements = container.querySelectorAll('.animate-float'); + expect(floatingElements.length).toBe(20); + }); - it('updates theme based on system preference', () => { - vi.stubGlobal('matchMedia', vi.fn().mockImplementation(query => ({ - matches: true, - media: query, - onchange: null, - addListener: vi.fn(), - removeListener: vi.fn(), - addEventListener: vi.fn(), - removeEventListener: vi.fn(), - dispatchEvent: vi.fn(), - }))); + it('updates theme based on system preference', () => { + vi.stubGlobal( + 'matchMedia', + vi.fn().mockImplementation((query) => ({ + matches: true, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn() + })) + ); - render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); - expect(document.documentElement.classList.contains('dark')).toBe(true); - }); - -}); \ No newline at end of file + render(Workspaces, { props: { data: { workspaces: [], role: 'student' } } }); + expect(document.documentElement.classList.contains('dark')).toBe(true); + }); +}); From 790ad859370c1be3a22003b770685248d0de8886 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Fri, 20 Sep 2024 17:49:08 +0200 Subject: [PATCH 17/24] Update Navbar stylings, Card styling and workspace stylings as well as default layout --- src/src/lib/components/common/Navbar.svelte | 8 +- src/src/lib/components/workspaces/Main.svelte | 146 ++-------- .../workspaces/workspace/Card.svelte | 50 +--- .../workspaces/workspace/Sidebar.svelte | 272 ++++++++---------- src/src/routes/(app)/+layout.svelte | 31 +- 5 files changed, 183 insertions(+), 324 deletions(-) diff --git a/src/src/lib/components/common/Navbar.svelte b/src/src/lib/components/common/Navbar.svelte index 93ca76de..a8233cb8 100644 --- a/src/src/lib/components/common/Navbar.svelte +++ b/src/src/lib/components/common/Navbar.svelte @@ -1,5 +1,5 @@
- + ClassConnect Logo - Flowbite + ClassConnect @@ -50,7 +50,7 @@ {name} diff --git a/src/src/lib/components/workspaces/Main.svelte b/src/src/lib/components/workspaces/Main.svelte index 0ce827f4..0e2b75aa 100644 --- a/src/src/lib/components/workspaces/Main.svelte +++ b/src/src/lib/components/workspaces/Main.svelte @@ -1,131 +1,29 @@ -
- -
- {#each floatingElements as element} -
- {/each} -
- - -
-
-

Workspaces

- -
- {#if data.workspaces && data.workspaces.length > 0} -
- {#each data.workspaces as workspace} - - {/each} -
- {:else} -
-

- You haven't been assigned to any workspaces yet. -

- -

- Please contact your administrator if you believe this is an error. -

-
- {/if} +
+

+ Your Workspaces +

+
+ {#if data.workspaces && data.workspaces.length > 0} +
+ {#each data.workspaces as workspace} + handleMouseEnter(workspace.name)} /> + {/each} +
+ {:else} +
+

+ No learning spaces assigned yet. +

+

+ If you think this is an error, please contact your instructor or administrator. +

-
+ {/if}
-
- - +
\ No newline at end of file diff --git a/src/src/lib/components/workspaces/workspace/Card.svelte b/src/src/lib/components/workspaces/workspace/Card.svelte index 3e2794ef..9b05b8fd 100644 --- a/src/src/lib/components/workspaces/workspace/Card.svelte +++ b/src/src/lib/components/workspaces/workspace/Card.svelte @@ -1,12 +1,13 @@ -
-
-
-
- {name} -
-

- {name} -

- -
- {description} -
- -
-
- - + \ No newline at end of file diff --git a/src/src/lib/components/workspaces/workspace/Sidebar.svelte b/src/src/lib/components/workspaces/workspace/Sidebar.svelte index c82113ff..8057cd45 100644 --- a/src/src/lib/components/workspaces/workspace/Sidebar.svelte +++ b/src/src/lib/components/workspaces/workspace/Sidebar.svelte @@ -1,15 +1,8 @@ - - - {`${workspace.name} - {workspace.name} - - - - {#if isLecturerLinks(currentLinks)} - {#if currentLinks.dashboard.length > 1} - - Dashboard - - - {#each currentLinks.dashboard as { name, href }} - {name} - {/each} - - {:else} - - {currentLinks.dashboard[0].name} - - {/if} - - {#if currentLinks.management.length > 1} - - Management - - - {#each currentLinks.management as { name, href }} - {name} - {/each} - - {:else} - - {currentLinks.management[0].name} - - {/if} - - {#if currentLinks.resources.length > 1} - - Resources - - - {#each currentLinks.resources as { name, href }} - {name} - {/each} - - {:else} - - {currentLinks.resources[0].name} - +
+ + + ClassConnect Logo + ClassConnect + + + + {#if isLecturerLinks(currentLinks)} + {#if currentLinks.dashboard.length > 1} + + Dashboard + + + {#each currentLinks.dashboard as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.dashboard[0].name} + + {/if} + + {#if currentLinks.management.length > 1} + + Management + + + {#each currentLinks.management as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.management[0].name} + + {/if} + + {#if currentLinks.resources.length > 1} + + Resources + + + {#each currentLinks.resources as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.resources[0].name} + + {/if} + {:else if isStudentLinks(currentLinks)} + {#if currentLinks.courseWork.length > 1} + + Course Work + + + {#each currentLinks.courseWork as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.courseWork[0].name} + + {/if} + + {#if currentLinks.additional.length > 1} + + Additional + + + {#each currentLinks.additional as { name, href }} + {name} + {/each} + + {:else} + + {currentLinks.additional[0].name} + + {/if} {/if} - {:else if isStudentLinks(currentLinks)} - {#if currentLinks.courseWork.length > 1} - - Course Work - - - {#each currentLinks.courseWork as { name, href }} - {name} - {/each} - - {:else} - + - - - - - + Leave {workspace.name} + + + + +
\ No newline at end of file diff --git a/src/src/routes/(app)/+layout.svelte b/src/src/routes/(app)/+layout.svelte index c66291f3..3f1dbf1a 100644 --- a/src/src/routes/(app)/+layout.svelte +++ b/src/src/routes/(app)/+layout.svelte @@ -1,7 +1,6 @@ -
+
{#if isWorkspacePage} - -
- +
+
{:else} - -
- -
+ +
+ +
{/if} - - -
- -
+ + \ No newline at end of file From c2783752a219424f84e71840bfd165762904ef36 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Fri, 20 Sep 2024 18:08:02 +0200 Subject: [PATCH 18/24] Updated layout to be the same for all pages --- src/src/lib/components/materials/MaterialsTab.svelte | 2 +- src/src/routes/(app)/+layout.svelte | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/src/lib/components/materials/MaterialsTab.svelte b/src/src/lib/components/materials/MaterialsTab.svelte index 487c824f..3e3a29dc 100644 --- a/src/src/lib/components/materials/MaterialsTab.svelte +++ b/src/src/lib/components/materials/MaterialsTab.svelte @@ -155,7 +155,7 @@ - + {tabName}
{#if isWorkspacePage}
-
@@ -34,9 +33,3 @@
{/if}
- - \ No newline at end of file From 71c23ac9b72c0449cbd646156e9287dd782708ef Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Fri, 20 Sep 2024 21:44:46 +0200 Subject: [PATCH 19/24] Updated materials to scale resolution based on hardware --- .../components/materials/MaterialsTab.svelte | 225 +++++++++++------- .../workspaces/workspace/Sidebar.svelte | 2 +- 2 files changed, 138 insertions(+), 89 deletions(-) diff --git a/src/src/lib/components/materials/MaterialsTab.svelte b/src/src/lib/components/materials/MaterialsTab.svelte index 3e3a29dc..87652c1c 100644 --- a/src/src/lib/components/materials/MaterialsTab.svelte +++ b/src/src/lib/components/materials/MaterialsTab.svelte @@ -1,5 +1,5 @@ {tabName} -
+
@@ -195,31 +244,31 @@
{#if filteredItems && filteredItems.length > 0} -
- {#each filteredItems as material (material.id)} -
handleMouseEnter(material)} - on:mouseleave={handleMouseLeave} - role="button" - tabindex="0" - on:keydown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - handleMouseEnter(material); - } - }} - > -
- {#if hoveredMaterial === material && material.type} -
- {:else} - {material.title} - {/if} -
+
+ {#each filteredItems as material (material.id)} +
handleMouseEnter(material)} + on:mouseleave={handleMouseLeave} + role="button" + tabindex="0" + on:keydown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + handleMouseEnter(material); + } + }} + > +
+ {#if hoveredMaterial === material && material.type} +
+ {:else} + {material.title} + {/if} +

@@ -272,9 +321,9 @@

{/each}
- {:else} -

No materials available

- {/if} + {:else} +

No materials available

+ {/if} (uploadModal = false)} /> diff --git a/src/src/lib/components/workspaces/workspace/Sidebar.svelte b/src/src/lib/components/workspaces/workspace/Sidebar.svelte index 8057cd45..b377b3ad 100644 --- a/src/src/lib/components/workspaces/workspace/Sidebar.svelte +++ b/src/src/lib/components/workspaces/workspace/Sidebar.svelte @@ -71,7 +71,7 @@ }; -
+
ClassConnect Logo From 92f04b6b8a9b695c26d58f2dd3b88577c9a062c7 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Mon, 23 Sep 2024 14:11:42 +0200 Subject: [PATCH 20/24] Experimenting with Live Lesson Responsive Design --- src/.DS_Store | Bin 0 -> 6148 bytes src/bun.lockb | Bin 526399 -> 471727 bytes src/package.json | 181 +++++------ .../lib/components/lessons/lesson/Chat.svelte | 139 ++++---- .../lessons/lesson/ChatAndParticipant.svelte | 44 +++ .../components/lessons/lesson/Controls.svelte | 297 ++++-------------- .../lessons/lesson/Environment.svelte | 80 +++-- .../components/lessons/lesson/Layout.svelte | 163 +++++----- .../lessons/lesson/Participant.svelte | 134 ++------ .../lib/components/lessons/lesson/Room.svelte | 98 +++--- .../components/lessons/lesson/Scene.svelte | 160 ++++------ 11 files changed, 536 insertions(+), 760 deletions(-) create mode 100644 src/.DS_Store create mode 100644 src/src/lib/components/lessons/lesson/ChatAndParticipant.svelte diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e467e8a1415d4dc3f1d3a1cde7a28a6fd58ad885 GIT binary patch literal 6148 zcmeHKOG*Pl5UtV(1G)&faoO(N<_2vT_d*V!#0d(SaRMP5R|DR`%eZmnSv-cXK7Iy= zfG$L&3c6o)cU5)GgX!)fBA#xl3DKB{3N%3$Wk6(laB0VlM?lsX7t~No6>X><2=o`H zB=-Zl)vOovIsXN%>+8wvs%=)1`0?H2dYf(2mgT%|=ddZ3FMAJ%=g+6x?W=FuuWmka zIk|sA=#XF_7zhS}fneZA4B*ZdDfSJc4+esPVBnnrIUf?5V0P>b_2@vSB>+&K(JIiT zmJpxhm>oMqtU%aYf#%9yVz9YmKDk|X>JE&DfD#g2Fz^cuyaKvnH!A=D literal 0 HcmV?d00001 diff --git a/src/bun.lockb b/src/bun.lockb index 8b850a93801a5cefb28659f70deee6941c379bb4..46241f491a953584bef400f8798e5119d058ce0e 100755 GIT binary patch delta 95756 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 delta 129232 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 Zq`|h<4Dff4wDd9lYX)02b^e=&{2vPYtB(Kx diff --git a/src/package.json b/src/package.json index 7749650b..4dbbb6a9 100644 --- a/src/package.json +++ b/src/package.json @@ -1,92 +1,93 @@ { - "name": "class-connect", - "version": "0.3.1", - "scripts": { - "dev": "vite dev --open", - "build": "vite build", - "preview": "vite preview", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "prettier --check . && eslint .", - "format": "prettier --write .", - "cy:open-ct": "cypress open --component", - "cy:run-ct": "cypress run --e2e", - "test": "vitest run", - "test:ui": "vitest --ui", - "test:watch": "vitest", - "test:coverage": "vitest run --coverage" - }, - "devDependencies": { - "@rollup/plugin-image": "^3.0.3", - "@sveltejs/adapter-auto": "^3.2.2", - "@sveltejs/adapter-node": "^5.2.0", - "@sveltejs/kit": "^2.5.18", - "@sveltejs/vite-plugin-svelte": "^3.1.1", - "@tailwindcss/typography": "^0.5.13", - "@testing-library/jest-dom": "^6.4.8", - "@testing-library/svelte": "^5.2.0", - "@types/eslint": "^8.56.10", - "@typescript-eslint/eslint-plugin": "^7.15.0", - "@typescript-eslint/parser": "^7.15.0", - "@vitest/coverage-v8": "^2.0.5", - "@vitest/ui": "^2.0.5", - "amplify-adapter": "^0.1.2", - "autoprefixer": "^10.4.19", - "cypress": "^13.13.0", - "dotenv": "^16.4.5", - "eslint": "^8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-svelte": "^2.41.0", - "flowbite": "^2.4.1", - "flowbite-svelte": "^0.46.15", - "flowbite-svelte-blocks": "^1.1.3", - "flowbite-svelte-icons": "^1.6.1", - "flowbite-typography": "^1.0.3", - "jsdom": "^24.1.0", - "lucia": "^3.2.0", - "postcss": "^8.4.39", - "prettier": "^3.3.2", - "prettier-plugin-svelte": "^3.2.5", - "prettier-plugin-tailwindcss": "^0.5.14", - "readable-stream": "^4.5.2", - "rollup-plugin-terser": "^7.0.2", - "svelte": "^4.2.18", - "svelte-adapter-bun": "^0.5.2", - "svelte-check": "^3.8.4", - "tailwindcss": "^3.4.4", - "tslib": "^2.6.3", - "typescript": "^5.5.3", - "vite": "^5.3.3", - "vite-plugin-compression": "^0.5.1", - "vite-plugin-imagemin": "^0.4.6", - "vite-plugin-node-polyfills": "^0.2.0", - "vitest": "^2.0.5" - }, - "type": "module", - "dependencies": { - "@lucia-auth/adapter-mongodb": "^1.0.3", - "@node-rs/argon2": "^1.8.3", - "@sendgrid/mail": "^8.1.3", - "@stream-io/node-sdk": "^0.2.6", - "@stream-io/video-client": "^1.4.4", - "@sveltejs/adapter-vercel": "^5.4.3", - "@threlte/core": "^7.3.1", - "@threlte/extras": "^8.11.4", - "@threlte/xr": "^0.1.4", - "@types/canvas-confetti": "^1.6.4", - "@types/three": "^0.168.0", - "@vercel/analytics": "^1.3.1", - "@vercel/speed-insights": "^1.0.12", - "aws-sdk": "^2.1670.0", - "axios": "^1.7.2", - "bcrypt": "^5.1.1", - "canvas-confetti": "^1.9.3", - "mongodb": "^6.8.0", - "mongoose": "^8.4.5", - "playroomkit": "^0.0.79", - "stream-chat": "^8.37.0", - "svelte-french-toast": "^1.2.0", - "svelte-tweakpane-ui": "^1.3.0", - "three": "^0.168.0" - } + "name": "class-connect", + "version": "0.3.1", + "scripts": { + "dev": "vite dev --open", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "lint": "prettier --check . && eslint .", + "format": "prettier --write .", + "cy:open-ct": "cypress open --component", + "cy:run-ct": "cypress run --e2e", + "test": "vitest run", + "test:ui": "vitest --ui", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" + }, + "devDependencies": { + "@rollup/plugin-image": "^3.0.3", + "@sveltejs/adapter-auto": "^3.2.2", + "@sveltejs/adapter-node": "^5.2.0", + "@sveltejs/kit": "^2.5.18", + "@sveltejs/vite-plugin-svelte": "^3.1.1", + "@tailwindcss/typography": "^0.5.13", + "@testing-library/jest-dom": "^6.4.8", + "@testing-library/svelte": "^5.2.0", + "@types/eslint": "^8.56.10", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", + "@vitest/coverage-v8": "^2.0.5", + "@vitest/ui": "^2.0.5", + "amplify-adapter": "^0.1.2", + "autoprefixer": "^10.4.19", + "cypress": "^13.13.0", + "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-svelte": "^2.41.0", + "flowbite": "^2.4.1", + "flowbite-svelte": "^0.46.15", + "flowbite-svelte-blocks": "^1.1.3", + "flowbite-svelte-icons": "^1.6.1", + "flowbite-typography": "^1.0.3", + "jsdom": "^24.1.0", + "lucia": "^3.2.0", + "postcss": "^8.4.39", + "prettier": "^3.3.2", + "prettier-plugin-svelte": "^3.2.5", + "prettier-plugin-tailwindcss": "^0.5.14", + "readable-stream": "^4.5.2", + "rollup-plugin-terser": "^7.0.2", + "svelte": "^4.2.18", + "svelte-adapter-bun": "^0.5.2", + "svelte-check": "^3.8.4", + "svelte-heros-v2": "^1.3.0", + "tailwindcss": "^3.4.4", + "tslib": "^2.6.3", + "typescript": "^5.5.3", + "vite": "^5.3.3", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-imagemin": "^0.4.6", + "vite-plugin-node-polyfills": "^0.2.0", + "vitest": "^2.0.5" + }, + "type": "module", + "dependencies": { + "@lucia-auth/adapter-mongodb": "^1.0.3", + "@node-rs/argon2": "^1.8.3", + "@sendgrid/mail": "^8.1.3", + "@stream-io/node-sdk": "^0.2.6", + "@stream-io/video-client": "^1.4.4", + "@sveltejs/adapter-vercel": "^5.4.3", + "@threlte/core": "^7.3.1", + "@threlte/extras": "^8.11.4", + "@threlte/xr": "^0.1.4", + "@types/canvas-confetti": "^1.6.4", + "@types/three": "^0.168.0", + "@vercel/analytics": "^1.3.1", + "@vercel/speed-insights": "^1.0.12", + "aws-sdk": "^2.1670.0", + "axios": "^1.7.2", + "bcrypt": "^5.1.1", + "canvas-confetti": "^1.9.3", + "mongodb": "^6.8.0", + "mongoose": "^8.4.5", + "playroomkit": "^0.0.79", + "stream-chat": "^8.37.0", + "svelte-french-toast": "^1.2.0", + "svelte-tweakpane-ui": "^1.3.0", + "three": "^0.168.0" + } } diff --git a/src/src/lib/components/lessons/lesson/Chat.svelte b/src/src/lib/components/lessons/lesson/Chat.svelte index 59733d77..6253afb1 100644 --- a/src/src/lib/components/lessons/lesson/Chat.svelte +++ b/src/src/lib/components/lessons/lesson/Chat.svelte @@ -1,77 +1,64 @@ - -
- -
- - - -
- {#each comments as comment, i} - - - - - Edit - Remove - Report - - - - {/each} -
-
+ import { Button, Avatar, Input } from 'flowbite-svelte'; + import { format } from 'date-fns'; + + export let messages: string | any[] = []; + let newMessage = ''; + + function sendMessage() { + if (newMessage.trim()) { + messages = [...messages, { + id: messages.length + 1, + sender: 'You', + avatar: '/path/to/your-avatar.jpg', + text: newMessage, + timestamp: new Date() + }]; + newMessage = ''; + } + } + + function handleKeydown(event: { key: string; shiftKey: any; preventDefault: () => void; }) { + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); + sendMessage(); + } + } + + +
+
+ {#each messages as message (message.id)} +
+
+
+ {#if message.sender !== 'You'} + + {/if} + {message.sender} + {#if message.sender === 'You'} + + {/if} +
+
+

{message.text}

+
+ + {format(message.timestamp, 'HH:mm')} + +
+
+ {/each} +
+
+
+ + +
+
+
\ No newline at end of file diff --git a/src/src/lib/components/lessons/lesson/ChatAndParticipant.svelte b/src/src/lib/components/lessons/lesson/ChatAndParticipant.svelte new file mode 100644 index 00000000..0d3cdae7 --- /dev/null +++ b/src/src/lib/components/lessons/lesson/ChatAndParticipant.svelte @@ -0,0 +1,44 @@ + + +
+ + activeTab = 'chat'}> + + + + + Chat + + + activeTab = 'participants'}> + + + + + Participants + + + + +
+ {#if activeTab === 'chat'} + + {:else if activeTab === 'participants'} +
+ {#each participants as participant} + + {/each} +
+ {/if} +
+
\ No newline at end of file diff --git a/src/src/lib/components/lessons/lesson/Controls.svelte b/src/src/lib/components/lessons/lesson/Controls.svelte index 5fee7301..d957f3fb 100644 --- a/src/src/lib/components/lessons/lesson/Controls.svelte +++ b/src/src/lib/components/lessons/lesson/Controls.svelte @@ -1,259 +1,96 @@ - + + + {isMicOn ? 'Turn off microphone' : 'Turn on microphone'} + + + + + + {isCameraOn ? 'Turn off camera' : 'Turn on camera'} + -
-
- + + + + {isScreenSharing ? 'Stop screen sharing' : 'Share screen'} + - + + + + {is3DModelActive ? 'Exit 3D model' : 'Interact with 3D model'} + - {#if role === 'lecturer'} - + {isRecording ? 'Stop recording' : 'Start recording'} + - + End call + - {#if isRecording} - - {:else} - - {/if} - {/if} -
---> diff --git a/src/src/lib/components/lessons/lesson/Environment.svelte b/src/src/lib/components/lessons/lesson/Environment.svelte index 6cfea9d6..bd76655d 100644 --- a/src/src/lib/components/lessons/lesson/Environment.svelte +++ b/src/src/lib/components/lessons/lesson/Environment.svelte @@ -1,51 +1,45 @@ -
-
- - +
+
+ + +
+
+
-{:else} -
-

Connecting...

+ + {:else} +
+ +

Connecting to the room...

+

Please wait while we establish the connection.

+
-{/if} + {/if} \ No newline at end of file diff --git a/src/src/lib/components/lessons/lesson/Scene.svelte b/src/src/lib/components/lessons/lesson/Scene.svelte index aca42dcd..d12978ab 100644 --- a/src/src/lib/components/lessons/lesson/Scene.svelte +++ b/src/src/lib/components/lessons/lesson/Scene.svelte @@ -1,97 +1,73 @@ - - - - - - - - - - - - - - -{#if selectedObject} - console.error('Error loading GLTF:', error)} + + function updateLecturerCamera(event: { target: { position: { x: any; y: any; z: any; }; }; }) { + lecturerCameraPosition = { + x: event.target.position.x, + y: event.target.position.y, + z: event.target.position.z + }; + // Here you would typically sync this position with other participants + } + + + + -{/if} - - + + + + + + + + + + {#if selectedObjectUrl} + + {/if} + + + + + + + \ No newline at end of file From 4b8a88b1c09baa2c0d6d261a1ebef6f25c9d3fe0 Mon Sep 17 00:00:00 2001 From: Joshua Wereley Date: Mon, 23 Sep 2024 15:37:02 +0200 Subject: [PATCH 21/24] Updated the code for Controls.svelte --- .../components/lessons/lesson/Controls.svelte | 231 ++++++++++++++---- 1 file changed, 189 insertions(+), 42 deletions(-) diff --git a/src/src/lib/components/lessons/lesson/Controls.svelte b/src/src/lib/components/lessons/lesson/Controls.svelte index 3d0a2829..3c4402a2 100644 --- a/src/src/lib/components/lessons/lesson/Controls.svelte +++ b/src/src/lib/components/lessons/lesson/Controls.svelte @@ -1,56 +1,204 @@ + +
- - - {is3DModelActive ? 'Exit 3D model' : 'Interact with 3D model'} + {isEnvironmentOn ? 'Exit 3D model' : 'Interact with 3D model'} - {isRecording ? 'Stop recording' : 'Start recording'} - + + + {/if} -
{:else} -
-
+
+ +
+ {#if activeTab === 'Chat'} -
+
{#each messages as message (message.id)} -
+
-
-
+
+

{message.user?.name}

@@ -96,12 +99,8 @@
- - + +
{:else} diff --git a/src/src/lib/components/lessons/lesson/Controls.svelte b/src/src/lib/components/lessons/lesson/Controls.svelte index 3c4402a2..a6cf6ccd 100644 --- a/src/src/lib/components/lessons/lesson/Controls.svelte +++ b/src/src/lib/components/lessons/lesson/Controls.svelte @@ -1,270 +1,247 @@ + -
-
- - - {isMicOn ? 'Turn off microphone' : 'Turn on microphone'} - +
+
+ - - - {isCameraOn ? 'Turn off camera' : 'Turn on camera'} - + {#if role === 'lecturer'} - - - {isScreenSharing ? 'Stop screen sharing' : 'Share screen'} - - - {isEnvironmentOn ? 'Exit 3D model' : 'Interact with 3D model'} - - - - - {isRecording ? 'Stop recording' : 'Start recording'} - + {#if isRecording} + + {:else} + + {/if} {/if} - - - End call - +
diff --git a/src/src/lib/components/lessons/lesson/Environment.svelte b/src/src/lib/components/lessons/lesson/Environment.svelte index 64f4b437..6cfea9d6 100644 --- a/src/src/lib/components/lessons/lesson/Environment.svelte +++ b/src/src/lib/components/lessons/lesson/Environment.svelte @@ -1,50 +1,51 @@ -
-
- + {#each materials as material} + {#if material.type === true} + + {/if} + {/each} +
- - - - - - - {#if modelUrl} - - - - {/if} - - - - - +
+ + diff --git a/src/src/lib/components/lessons/lesson/Layout.svelte b/src/src/lib/components/lessons/lesson/Layout.svelte index 0386f28b..c0ff888b 100644 --- a/src/src/lib/components/lessons/lesson/Layout.svelte +++ b/src/src/lib/components/lessons/lesson/Layout.svelte @@ -1,119 +1,62 @@ -
-
- -
- {#if activeView === 'video'} -
- -

- Video Content -

-
- {:else if activeView === 'screen'} -
- -

- Screen Share Content -

-
- {:else if activeView === '3d'} - - {/if} +
+ {#if isEnvironmentOn} + + {:else if screenSharingParticipant} + + {:else} +
+ {#each participants as participant (participant.sessionId)} + + {/each}
- - - {#if showSidebar} -
-
-
- - -
-
-
- {#if activeView === 'chat'} - - {:else if activeView === 'attendance'} - - {/if} -
-
- {/if} -
- - - - - {#if isMobile} - {/if} -
+
diff --git a/src/src/lib/components/lessons/lesson/Participant.svelte b/src/src/lib/components/lessons/lesson/Participant.svelte index 404e2d56..2fba76f8 100644 --- a/src/src/lib/components/lessons/lesson/Participant.svelte +++ b/src/src/lib/components/lessons/lesson/Participant.svelte @@ -1,30 +1,123 @@ - - -
- {#if isCameraOn && videoStream} -