355 lines
12 KiB
Svelte
355 lines
12 KiB
Svelte
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
- file, You can obtain one at https://mozilla.org/MPL/2.0/. -->
|
|
|
|
<script lang="ts">
|
|
import { createForm } from 'felte';
|
|
import EntryKind from '../../dashboard/utils/EntryKind.svelte';
|
|
import {
|
|
FEELINGS,
|
|
TITLED_ENTRIES,
|
|
type AlbumEntry,
|
|
type IdlessEntry,
|
|
type KnownFeeling,
|
|
type SongEntry
|
|
} from '$lib/entry';
|
|
import { FontAwesomeIcon } from '@fortawesome/svelte-fontawesome';
|
|
import { faSpotify, faYoutube } from '@fortawesome/free-brands-svg-icons';
|
|
import { faChevronDown, faLink, faPlus, faXmark } from '@fortawesome/free-solid-svg-icons';
|
|
import FeelingPill from '../../dashboard/utils/FeelingPill.svelte';
|
|
import { addEntry, uploadAsset } from '$lib/api';
|
|
import { credentials, session_key } from '$lib/stores';
|
|
import FeelingsChooser from '$lib/components/FeelingsChooser.svelte';
|
|
|
|
credentials.subscribe(
|
|
(v) => v == null && setTimeout(() => (window.location.pathname = '/auth/login'), 200)
|
|
);
|
|
|
|
let kind: EntryKind | null;
|
|
const { form, errors } = createForm({
|
|
onSubmit: async (values) => {
|
|
let feelings = Object.keys(values)
|
|
.filter((v) => v.startsWith('feeling__'))
|
|
.map((v) => v.replaceAll('feeling__', '')) as KnownFeeling[];
|
|
|
|
let base;
|
|
if (values.kind === 'song' || values.kind === 'album') {
|
|
base = {
|
|
kind: values.kind,
|
|
artist: values.artist,
|
|
title: values.musicTitle,
|
|
link: [values.spotify, values.yt, values.otherProvider].filter(
|
|
(v) => v != null && v.length > 0
|
|
),
|
|
// FIXME: Infer Universal IDs (Spotify URL, etc)
|
|
id: []
|
|
};
|
|
} else if (values.kind === 'environment') {
|
|
base = {
|
|
kind: values.kind,
|
|
location:
|
|
values.location != null && values.location.length > 0 ? values.location : undefined
|
|
};
|
|
} else if (values.kind === 'date') {
|
|
base = {
|
|
kind: values.kind,
|
|
referencedDate: values.date
|
|
};
|
|
} else {
|
|
base = {
|
|
kind: values.kind
|
|
};
|
|
}
|
|
|
|
let asset_id;
|
|
if (values.asset != null && typeof values.asset === 'object') {
|
|
asset_id = await uploadAsset($session_key!, values.asset);
|
|
}
|
|
|
|
let entry: IdlessEntry = {
|
|
base,
|
|
creationDate: new Date().toISOString(),
|
|
assets: [asset_id].filter((v) => v != null) as string[],
|
|
feelings,
|
|
title: TITLED_ENTRIES.includes(values.kind) ? values.title : undefined,
|
|
description: values.description
|
|
};
|
|
|
|
await addEntry($credentials!, entry);
|
|
window.location.pathname = '/dashboard';
|
|
},
|
|
validate: (values) => {
|
|
let errors = {};
|
|
|
|
if (values.kind == null || values.kind.length === 0) {
|
|
errors['kind'] = 'Must choose an entry kind';
|
|
return errors;
|
|
}
|
|
|
|
if (values.kind === 'song' || values.kind === 'album') {
|
|
if (values.artist == null || values.artist.length === 0) {
|
|
errors['artist'] = 'Must not be empty';
|
|
}
|
|
|
|
if (values.musicTitle == null || values.musicTitle.length === 0) {
|
|
errors['musicTitle'] = 'Must not be empty';
|
|
}
|
|
|
|
if (
|
|
values.spotify.length === 0 &&
|
|
values.yt.length === 0 &&
|
|
values.otherProvider.length === 0 &&
|
|
(values.asset == null || typeof values.asset !== 'object')
|
|
) {
|
|
errors['links'] = 'You must add at least one link or upload an audio asset';
|
|
}
|
|
} else if (values.kind === 'date') {
|
|
if (values.date == null || values.date.length === 0) {
|
|
errors['date'] = 'Must choose a date';
|
|
}
|
|
} else if (values.kind === 'feeling') {
|
|
if (Object.keys(values).filter((v) => v.startsWith('feeling__')).length === 0) {
|
|
errors['feelings'] = 'Must choose at least one feeling';
|
|
}
|
|
} else {
|
|
if (values.title == null || values.title.length === 0) {
|
|
errors['title'] = 'Must not be empty';
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<div class="mt-3.5 flex justify-center">
|
|
<div class="flex w-[60%] flex-col">
|
|
<h1 class="pb-3.5 text-2xl">Add an entry</h1>
|
|
<form use:form>
|
|
<div class="mb-5">
|
|
<label for="add-entry__kind" class="mb-2 block text-sm font-medium text-gray-900">
|
|
Entry kind
|
|
</label>
|
|
<select
|
|
bind:value={kind}
|
|
id="add-entry__kind"
|
|
name="kind"
|
|
class="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-violet-500 focus:ring-violet-500"
|
|
>
|
|
<option value="" selected>Choose an entry kind</option>
|
|
<option value="song">Song</option>
|
|
<option value="album">Album</option>
|
|
<option value="event">Event</option>
|
|
<option value="memory">Memory</option>
|
|
<option value="feeling">Feeling</option>
|
|
<option value="environment">Environment</option>
|
|
<option value="date">Date</option>
|
|
</select>
|
|
{#if $errors.kind != null}
|
|
<p class="mt-2 text-sm text-red-600">
|
|
<span class="font-medium">{$errors.kind[0]}</span>
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
{#if TITLED_ENTRIES.includes(kind)}
|
|
<div class="mb-5">
|
|
<label for="add-entry__title" class="mb-2 block text-sm font-medium text-gray-900">
|
|
Title
|
|
</label>
|
|
<input
|
|
id="add-entry__title"
|
|
type="text"
|
|
name="title"
|
|
placeholder="At the sunflower field with my friends"
|
|
class="text-greay-900 block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm focus:border-violet-500 focus:ring-violet-500"
|
|
/>
|
|
{#if $errors.title != null}
|
|
<p class="mt-2 text-sm text-red-600">
|
|
<span class="font-medium">{$errors.title[0]}</span>
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
{#if ['song', 'album'].includes(kind)}
|
|
<div class="mb-5 flex flex-col gap-5 md:flex-row">
|
|
<div class="w-full">
|
|
<label for="add-entry__artist" class="mb-2 block text-sm font-medium text-gray-900">
|
|
Artist name
|
|
</label>
|
|
<input
|
|
id="add-entry__artist"
|
|
type="text"
|
|
name="artist"
|
|
placeholder="Claude Debussy"
|
|
class="text-greay-900 block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm focus:border-violet-500 focus:ring-violet-500"
|
|
/>
|
|
{#if $errors.artist != null}
|
|
<p class="mt-2 text-sm text-red-600">
|
|
<span class="font-medium">{$errors.artist[0]}</span>
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
<div class="w-full">
|
|
<label
|
|
for="add-entry__music-title"
|
|
class="mb-2 block text-sm font-medium text-gray-900"
|
|
>
|
|
<span class="capitalize">{kind}</span>
|
|
title
|
|
</label>
|
|
<input
|
|
id="add-entry__music-title"
|
|
type="text"
|
|
name="musicTitle"
|
|
placeholder="Clair de Lune"
|
|
class="text-greay-900 block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm focus:border-violet-500 focus:ring-violet-500"
|
|
/>
|
|
{#if $errors.musicTitle != null}
|
|
<p class="mt-2 text-sm text-red-600">
|
|
<span class="font-medium">{$errors.musicTitle[0]}</span>
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
<div class="mb-5">
|
|
<label for="add-entry__spotify" class="mb-2 block text-sm font-medium text-gray-900">
|
|
Spotify link
|
|
</label>
|
|
<div class="flex">
|
|
<span
|
|
class="rounded-e-0 inline-flex items-center rounded-s-md border border-e-0 border-gray-300 bg-gray-200 px-2.5 text-sm text-gray-900"
|
|
>
|
|
<FontAwesomeIcon size="lg" icon={faSpotify} />
|
|
</span>
|
|
<input
|
|
type="text"
|
|
id="add-entry__spotify"
|
|
name="spotify"
|
|
class="block w-full min-w-0 flex-1 rounded-none rounded-e-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-violet-500 focus:ring-violet-500"
|
|
placeholder={kind === 'song'
|
|
? 'https://open.spotify.com/track/...'
|
|
: 'https://open.spotify.com/album/...'}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="mb-5">
|
|
<label for="add-entry__yt" class="mb-2 block text-sm font-medium text-gray-900">
|
|
YouTube link
|
|
</label>
|
|
<div class="flex">
|
|
<span
|
|
class="rounded-e-0 inline-flex items-center rounded-s-md border border-e-0 border-gray-300 bg-gray-200 px-2.5 text-sm text-gray-900"
|
|
>
|
|
<FontAwesomeIcon size="lg" icon={faYoutube} />
|
|
</span>
|
|
<input
|
|
type="text"
|
|
id="add-entry__yt"
|
|
name="yt"
|
|
class="block w-full min-w-0 flex-1 rounded-none rounded-e-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-violet-500 focus:ring-violet-500"
|
|
placeholder="https://www.youtube.com/watch..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="mb-5">
|
|
<label for="add-entry__other" class="mb-2 block text-sm font-medium text-gray-900">
|
|
Link to other provider
|
|
</label>
|
|
<div class="flex">
|
|
<span
|
|
class="rounded-e-0 inline-flex items-center rounded-s-md border border-e-0 border-gray-300 bg-gray-200 px-2.5 text-sm text-gray-900"
|
|
>
|
|
<FontAwesomeIcon size="lg" icon={faLink} />
|
|
</span>
|
|
<input
|
|
type="text"
|
|
name="otherProvider"
|
|
id="add-entry__other"
|
|
class="block w-full min-w-0 flex-1 rounded-none rounded-e-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-violet-500 focus:ring-violet-500"
|
|
placeholder="https://www.music.tld/play/..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
{#if $errors.links != null}
|
|
<p class="mb-3.5 mt-2.5 text-sm text-red-600">
|
|
<span class="font-medium">{$errors.links[0]}</span>
|
|
</p>
|
|
{/if}
|
|
{:else if kind === 'environment'}
|
|
<div class="mb-5 w-full">
|
|
<label for="add-entry__location" class="mb-2 block text-sm font-medium text-gray-900">
|
|
Location
|
|
</label>
|
|
<input
|
|
id="add-entry__location"
|
|
type="text"
|
|
name="location"
|
|
placeholder="South of Almond Park"
|
|
class="text-greay-900 block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm focus:border-violet-500 focus:ring-violet-500"
|
|
/>
|
|
</div>
|
|
{:else if kind === 'date'}
|
|
<div class="mb-5 w-full">
|
|
<label for="add-entry__date" class="mb-2 block text-sm font-medium text-gray-900">
|
|
Referenced date
|
|
</label>
|
|
<input
|
|
id="add-entry__date"
|
|
type="date"
|
|
name="date"
|
|
class="text-greay-900 block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm focus:border-violet-500 focus:ring-violet-500"
|
|
/>
|
|
{#if $errors.date != null}
|
|
<p class="mt-2 text-sm text-red-600">
|
|
<span class="font-medium">{$errors.date[0]}</span>
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
{#if kind != null && kind.length > 0}
|
|
<div class="mb-5">
|
|
<label for="add-entry__description" class="mb-2 block text-sm font-medium text-gray-900">
|
|
Description
|
|
</label>
|
|
<textarea
|
|
name="description"
|
|
id="add-entry__description"
|
|
rows="7"
|
|
class="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-violet-500 focus:ring-violet-500"
|
|
placeholder="Write your thoughts here..."
|
|
></textarea>
|
|
</div>
|
|
|
|
<div class="mb-5">
|
|
<label for="add-entry__assets" class="mb-2 block text-sm font-medium text-gray-900">
|
|
Linked assets (max 5MB)
|
|
</label>
|
|
<input
|
|
name="asset"
|
|
id="add-entry__assets"
|
|
class="block w-full rounded-lg border border-gray-300 bg-gray-50 text-sm text-gray-900 file:me-4 file:border-0 file:border-gray-300 file:bg-gray-200 file:px-4 file:py-2.5 hover:cursor-pointer hover:file:bg-gray-300 focus:border-violet-500 focus:ring-violet-500"
|
|
type="file"
|
|
/>
|
|
</div>
|
|
|
|
<div class="mb-5">
|
|
<FeelingsChooser required={kind === 'feeling'} />
|
|
{#if $errors.feelings != null}
|
|
<p class="mt-1.5 text-sm text-red-600">
|
|
<span class="font-medium">{$errors.feelings[0]}</span>
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
|
|
<button
|
|
class="focust:outline-none mt-2 rounded-lg bg-violet-700 px-5 py-2.5 text-center font-medium text-white hover:bg-violet-800 focus:ring-4 focus:ring-violet-300"
|
|
type="submit"
|
|
>
|
|
Add new entry
|
|
</button>
|
|
{/if}
|
|
</form>
|
|
</div>
|
|
</div>
|