-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #76 from 21CSM/42-score-metadata
42 score metadata
- Loading branch information
Showing
23 changed files
with
1,065 additions
and
121 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<script lang="ts"> | ||
import type { Score } from '$lib/types'; | ||
import TagBadge from './TagBadge.svelte'; | ||
export let score: Score; | ||
export let onClick: (id: string) => void; | ||
</script> | ||
|
||
<div | ||
class="card card-hover overflow-hidden cursor-pointer" | ||
on:click={() => onClick(score.id)} | ||
on:keydown={(e) => e.key === 'Enter' && onClick(score.id)} | ||
tabindex="0" | ||
role="button" | ||
> | ||
<img src={score.thumbnailUrl} alt={score.title} class="w-full h-48 object-cover" /> | ||
<div class="p-4"> | ||
<h3 class="h3 mb-2 truncate">{score.title}</h3> | ||
<div class="flex flex-wrap gap-1 mb-2"> | ||
{#if score.tags && score.tags.length > 0} | ||
{#each score.tags as tag} | ||
<TagBadge {tag} /> | ||
{/each} | ||
{:else} | ||
<span class="text-sm text-gray-500">No tags</span> | ||
{/if} | ||
</div> | ||
<p class="text-sm text-gray-500 mt-2">Last modified: {score.lastModified}</p> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<script lang="ts"> | ||
import { createEventDispatcher } from 'svelte'; | ||
import IconMagnify from '~icons/mdi/magnify'; | ||
export let value = ''; | ||
const dispatch = createEventDispatcher(); | ||
function handleInput(event: Event) { | ||
const target = event.target as HTMLInputElement; | ||
dispatch('input', target.value); | ||
} | ||
</script> | ||
|
||
<div class="relative flex-grow"> | ||
<input | ||
type="text" | ||
placeholder="Search scores..." | ||
class="input pl-10 pr-4 py-2 w-full h-10" | ||
{value} | ||
on:input={handleInput} | ||
/> | ||
<IconMagnify | ||
class="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-surface-500" | ||
/> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<script lang="ts"> | ||
import { getTagColor, getContrastColor } from '$lib/utils/colorUtils'; | ||
export let tag: string; | ||
$: bgColor = getTagColor(tag); | ||
$: textColor = getContrastColor(bgColor); | ||
</script> | ||
|
||
<span class="badge text-xs" style="background-color: {bgColor}; color: {textColor};"> | ||
{tag} | ||
</span> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<script lang="ts"> | ||
import { createEventDispatcher } from 'svelte'; | ||
import IconFilterOutline from '~icons/mdi/filter-outline'; | ||
import IconChevronDown from '~icons/mdi/chevron-down'; | ||
export let tags: string[]; | ||
export let selectedTag = 'All'; | ||
const dispatch = createEventDispatcher(); | ||
function handleChange(event: Event) { | ||
const target = event.target as HTMLSelectElement; | ||
dispatch('change', target.value); | ||
} | ||
</script> | ||
|
||
<div class="relative w-48"> | ||
<select | ||
bind:value={selectedTag} | ||
on:change={handleChange} | ||
class="select pl-10 pr-8 py-2 w-full h-10 appearance-none" | ||
> | ||
{#each tags as tag} | ||
<option value={tag}>{tag}</option> | ||
{/each} | ||
</select> | ||
<IconFilterOutline | ||
class="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-surface-500" | ||
/> | ||
<IconChevronDown | ||
class="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-surface-500" | ||
/> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<script lang="ts"> | ||
import { AppShell } from '@skeletonlabs/skeleton'; | ||
import Header from './Header.svelte'; | ||
</script> | ||
|
||
<AppShell> | ||
<svelte:fragment slot="header"> | ||
<Header/> | ||
</svelte:fragment> | ||
<slot/> | ||
</AppShell> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
<script lang="ts"> | ||
import { AppBar, LightSwitch, Avatar } from '@skeletonlabs/skeleton'; | ||
import { modeCurrent, setModeUserPrefers } from '@skeletonlabs/skeleton'; | ||
import IconMenu from '~icons/mdi/menu'; | ||
import IconAccount from '~icons/mdi/account'; | ||
import IconLogout from '~icons/mdi/logout'; | ||
import IconFileMusic from '~icons/mdi/file-music'; | ||
import IconBookshelf from '~icons/mdi/bookshelf'; | ||
import IconGear from '~icons/mdi/gear'; | ||
import { userStore } from '$lib/stores/user'; | ||
import { auth } from '$lib/firebase/auth'; | ||
import { goto } from '$app/navigation'; | ||
import logo from '$lib/assets/logo.svg'; | ||
let isOpen = false; | ||
let userAvatar: string | null = null; | ||
let userName: string | null = null; | ||
let isDropdownOpen = false; | ||
function toggleMenu() { | ||
isOpen = !isOpen; | ||
} | ||
function toggleDropdown() { | ||
isDropdownOpen = !isDropdownOpen; | ||
} | ||
function handleLightSwitch() { | ||
setModeUserPrefers($modeCurrent); | ||
} | ||
async function signOut() { | ||
console.log('signOut function called'); | ||
try { | ||
await auth.signOut(); | ||
console.log('Sign out successful'); | ||
goto('/'); | ||
} catch (error) { | ||
console.error('Error signing out:', error); | ||
} finally { | ||
isDropdownOpen = false; | ||
} | ||
} | ||
userStore.subscribe((state) => { | ||
if (state.user) { | ||
userAvatar = state.user.photoURL || null; | ||
userName = state.user.displayName || state.user.email || 'User'; | ||
} else { | ||
userAvatar = null; | ||
userName = null; | ||
} | ||
}); | ||
function clickOutside(node: HTMLElement) { | ||
const handleClick = (event: MouseEvent) => { | ||
const target = event.target as Node; | ||
// Add a condition to allow clicks within dropdown elements without closing it | ||
if (node && !node.contains(target) && !(target as Element).closest('.dropdown-content')) { | ||
node.dispatchEvent(new CustomEvent('clickOutside')); | ||
} | ||
}; | ||
document.addEventListener('click', handleClick, true); | ||
return { | ||
destroy() { | ||
document.removeEventListener('click', handleClick, true); | ||
} | ||
}; | ||
} | ||
function handleSignOutClick(event: MouseEvent) { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
console.log('Sign Out button clicked'); | ||
signOut(); | ||
} | ||
</script> | ||
|
||
<AppBar> | ||
<svelte:fragment slot="lead"> | ||
<a href="/" class="flex items-center space-x-2"> | ||
<img src={logo} alt="Ars Antiqua Online" class="h-10 {$modeCurrent ? '' : 'invert'}" /> | ||
</a> | ||
</svelte:fragment> | ||
<svelte:fragment slot="trail"> | ||
<div class="hidden lg:flex items-center space-x-4"> | ||
<nav class="flex space-x-2"> | ||
<a href="/browse" class="btn btn-sm variant-ghost-surface"> | ||
<IconBookshelf class="w-5 h-5 mr-2" /> | ||
Browse All Scores | ||
</a> | ||
<a href="/manage" class="btn btn-sm variant-ghost-surface"> | ||
<IconFileMusic class="w-5 h-5 mr-2" /> | ||
Manage My Scores | ||
</a> | ||
</nav> | ||
<div class="border-l border-surface-500/30 h-6"></div> | ||
<LightSwitch on:change={handleLightSwitch} /> | ||
<div class="border-l border-surface-500/30 h-6"></div> | ||
{#if userName} | ||
<div class="relative" use:clickOutside on:clickOutside={() => (isDropdownOpen = false)}> | ||
<button | ||
class="flex items-center space-x-2 dropdown-toggle" | ||
on:click={toggleDropdown} | ||
aria-haspopup="true" | ||
aria-expanded={isDropdownOpen} | ||
> | ||
<span class="text-sm font-medium">{userName}</span> | ||
<Avatar | ||
border="border-4 border-surface-300-600-token hover:!border-primary-500" | ||
cursor="cursor-pointer" | ||
width="w-10" | ||
src={userAvatar ?? ''} | ||
background="bg-surface-500" | ||
> | ||
<IconAccount class="w-6 h-6" /> | ||
</Avatar> | ||
</button> | ||
{#if isDropdownOpen} | ||
<div | ||
class="absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-surface-100-800-token dropdown-content" | ||
> | ||
<div class="py-1"> | ||
<button | ||
class="block w-full text-left px-4 py-2 text-sm hover:bg-surface-hover" | ||
on:click={() => console.log('Settings clicked')} | ||
> | ||
<IconGear class="w-5 h-5 mr-2 inline-block" /> | ||
Settings | ||
</button> | ||
<button | ||
class="block w-full text-left px-4 py-2 text-sm hover:bg-error-500 text-error-500 hover:text-error-50" | ||
on:click={handleSignOutClick} | ||
> | ||
<IconLogout class="w-5 h-5 mr-2 inline-block" /> | ||
Sign Out | ||
</button> | ||
</div> | ||
</div> | ||
{/if} | ||
</div> | ||
{/if} | ||
</div> | ||
<div class="lg:hidden flex items-center space-x-4"> | ||
<LightSwitch on:change={handleLightSwitch} /> | ||
{#if userName} | ||
<div class="relative" use:clickOutside on:clickOutside={() => (isDropdownOpen = false)}> | ||
<button | ||
on:click={toggleDropdown} | ||
class="dropdown-toggle" | ||
aria-haspopup="true" | ||
aria-expanded={isDropdownOpen} | ||
> | ||
<Avatar | ||
border="border-4 border-surface-300-600-token hover:!border-primary-500" | ||
cursor="cursor-pointer" | ||
width="w-10" | ||
src={userAvatar ?? ''} | ||
background="bg-surface-500" | ||
> | ||
<IconAccount class="w-5 h-5" /> | ||
</Avatar> | ||
</button> | ||
{#if isDropdownOpen} | ||
<div | ||
class="absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-surface-100-800-token dropdown-content" | ||
> | ||
<div class="py-1"> | ||
<span class="block px-4 py-2 text-sm font-medium">{userName}</span> | ||
<button | ||
class="block w-full text-left px-4 py-2 text-sm hover:bg-surface-hover" | ||
on:click={() => console.log('Settings clicked')} | ||
> | ||
<IconGear class="w-5 h-5 mr-2 inline-block" /> | ||
Settings | ||
</button> | ||
<button | ||
class="block w-full text-left px-4 py-2 text-sm hover:bg-error-500 text-error-500 hover:text-error-50" | ||
on:click={handleSignOutClick} | ||
> | ||
<IconLogout class="w-5 h-5 mr-2 inline-block" /> | ||
Sign Out | ||
</button> | ||
</div> | ||
</div> | ||
{/if} | ||
</div> | ||
{/if} | ||
<button class="btn btn-sm variant-ghost-surface" on:click={toggleMenu}> | ||
<IconMenu class="w-5 h-5" /> | ||
</button> | ||
</div> | ||
</svelte:fragment> | ||
</AppBar> | ||
|
||
{#if isOpen} | ||
<nav class="lg:hidden p-4 space-y-2 bg-surface-100-800-token"> | ||
<a href="/browse" class="btn btn-sm variant-ghost-surface w-full justify-start"> | ||
<IconBookshelf class="w-5 h-5 mr-2" /> | ||
Browse All Scores | ||
</a> | ||
<a href="/manage" class="btn btn-sm variant-ghost-surface w-full justify-start"> | ||
<IconFileMusic class="w-5 h-5 mr-2" /> | ||
Manage My Scores | ||
</a> | ||
</nav> | ||
{/if} | ||
|
||
<style> | ||
.invert { | ||
filter: invert(1); | ||
} | ||
</style> |
Oops, something went wrong.