Skip to content

Commit

Permalink
Merge pull request #76 from 21CSM/42-score-metadata
Browse files Browse the repository at this point in the history
42 score metadata
  • Loading branch information
21CSM authored Oct 10, 2024
2 parents 004c4a8 + 40af257 commit f161250
Show file tree
Hide file tree
Showing 23 changed files with 1,065 additions and 121 deletions.
2 changes: 1 addition & 1 deletion src/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover" data-theme="hamlindigo">
<div style="display: contents">%sveltekit.body%</div>
<div style="display: contents" class="h-full overflow-hidden">%sveltekit.body%</div>
</body>
</html>
10 changes: 10 additions & 0 deletions src/app.postcss
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,13 @@
@tailwind components;
@tailwind utilities;
@tailwind variants;

html, body { @apply h-full overflow-hidden; }

.app-container {
@apply h-full overflow-hidden;
}

.content-container {
@apply h-full overflow-y-auto;
}
55 changes: 55 additions & 0 deletions src/lib/assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion src/lib/components/auth/LoginForm.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<script lang="ts">
import { auth } from '$lib/firebase';
import sheetMusicImage from '$lib/assets/medieval-sheet-music.png';
import logo from '$lib/assets/logo.svg';
let error: string = '';
</script>

<div class="min-h-screen flex items-center justify-center relative bg-gray-50">
<div class="background-image" style="background-image: url({sheetMusicImage});"></div>

<div class="max-w-md w-full bg-white shadow-lg rounded-lg p-6 space-y-4 border border-gray-200 z-10">
<h1 class="text-3xl font-serif font-semibold text-center text-gray-800">Ars Antiqua Online</h1>
<img src={logo} alt="Ars Antiqua Online">
<p class="text-center text-gray-600">
Explore the rich history of medieval music.
</p>
Expand Down
30 changes: 30 additions & 0 deletions src/lib/components/score/ScoreCard.svelte
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>
25 changes: 25 additions & 0 deletions src/lib/components/score/ScoreSearch.svelte
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>
12 changes: 12 additions & 0 deletions src/lib/components/score/TagBadge.svelte
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>
33 changes: 33 additions & 0 deletions src/lib/components/score/TagFilter.svelte
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>
11 changes: 11 additions & 0 deletions src/lib/components/shell/AuthenticatedApp.svelte
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>
217 changes: 217 additions & 0 deletions src/lib/components/shell/Header.svelte
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>
Loading

0 comments on commit f161250

Please sign in to comment.