Skip to content
Snippets Groups Projects
Verified Commit 5667264b authored by Miniontoby's avatar Miniontoby :writing_hand_tone1:
Browse files

Update musicsheet to add type selection.

Also now included musicsheet in LiveView.
parent c623e212
No related branches found
No related tags found
No related merge requests found
Showing
with 183 additions and 58 deletions
......@@ -64,6 +64,7 @@ model MusicSheet {
id Int @id @default(autoincrement())
name String
userType String @default("ALL")
fileType String @default("ChordPro")
file String?
song Song @relation(fields: [songId], references: [id])
songId Int
......
......@@ -57,7 +57,7 @@ export default function injectSocketIO(server) {
currentSongId: 0,
currentCoupletId: 0,
}
} else if (current_connections[identifier].playlist === "{}") {
} else { //if (current_connections[identifier].playlist === "{}") {
current_connections[identifier].playlist = data?.playlist ?? "{}";
if (current_connections[identifier].playlist !== "{}")
io.to(identifier).emit('syncPlaylist', {
......
......@@ -43,6 +43,13 @@
.btn-danger {
@apply w-full text-white bg-red-400 hover:bg-red-500 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800;
}
.chord-sheet .row:has(.lyrics) {
@apply hover:bg-gray-200 hover:dark:bg-gray-700;
}
.chord-sheet .row.active:has(.lyrics) {
@apply bg-gray-500 hover:bg-gray-200 hover:dark:bg-gray-700;
}
}
@media (prefers-color-scheme: light) {
......@@ -60,4 +67,17 @@
[data-theme="dark"] {
color-scheme: light;
}
}
\ No newline at end of file
}
.comment { background-color: #00008b; color: #97bdd8; }
.annotation { color: #ff6bb8; }
.chord { color: #93f293; }
.chord:not(:last-child) { padding-right: 10px; }
.paragraph { margin-bottom: 1em; }
.row { display: flex; }
.row.active { background-color: #ff0fff; }
.chord:after { content: '\200b'; }
.lyrics:after { content: '\200b'; }
......@@ -45,3 +45,14 @@ export const navs = [
ifLoggedOut: true,
},
];
///////////////////////////////
// Please don't edit this... //
///////////////////////////////
export const fileOptions = [
{ label: 'ChordPro', value: 'ChordPro', disabled: false },
{ label: 'ChordsOverWords', value: 'ChordsOverWords', disabled: false },
{ label: 'UltimateGuitar', value: 'UltimateGuitar', disabled: false },
];
......@@ -128,6 +128,10 @@
"placeholder": "Enter Sheet Name",
"label": "Music Sheet Name"
},
"file_type": {
"placeholder": "Enter Sheet File Type",
"label": "Music Sheet File Type"
},
"content": {
"placeholder": "Enter Sheet Content",
"label": "Music Sheet Content"
......
......@@ -128,6 +128,10 @@
"placeholder": "Sheet Naam invoeren",
"label": "Muziekblad Name"
},
"file_type": {
"placeholder": "Sheet Bestandstype invoeren",
"label": "Muziekblad Bestandstype"
},
"content": {
"placeholder": "Sheet Inhoud invoeren",
"label": "Muziekblad Inhoud"
......
......@@ -10,8 +10,9 @@ export async function getAll() {
select: {
id: true,
name: true,
userType: true,
fileType: true,
file: true,
userType: true,
song: true,
}
})
......@@ -31,8 +32,9 @@ export async function getAllBySong(songId: number) {
select: {
id: true,
name: true,
userType: true,
fileType: true,
file: true,
userType: true,
song: true,
}
})
......@@ -52,8 +54,9 @@ export async function findById(id: number) {
select: {
id: true,
name: true,
userType: true,
fileType: true,
file: true,
userType: true,
song: true,
}
})
......@@ -67,11 +70,12 @@ export async function findById(id: number) {
* @param file
* @returns { object } created musicsheet
*/
export async function create(songId: number, name: string, file: string|undefined = undefined) {
export async function create(songId: number, name: string, fileType: string, file: string|undefined = undefined) {
return await db.musicSheet.create({
data: {
songId,
name,
fileType,
file,
}
})
......@@ -85,13 +89,15 @@ export async function create(songId: number, name: string, file: string|undefine
* @param file
* @returns { object } edited musicsheet
*/
export async function edit(id: number, name: string, file: string|undefined = undefined) {
export async function edit(id: number, name: string, fileType: string, file: string|undefined = undefined) {
return await db.musicSheet.update({
where: {
id,
},
data: {
name,
fileType,
file,
}
})
}
......
......@@ -53,7 +53,16 @@ export async function findById(id: number) {
id: true,
name: true,
date: true,
songs: true,
songs: {
select: {
id: true,
name: true,
artist: true,
lyrics: true,
url: true,
sheets: true,
},
},
team: {
select: {
id: true,
......
import * as Playlist from '$lib/server/models/Playlist';
import * as Song from '$lib/server/models/Song';
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageServerLoad} */
......
......@@ -2,6 +2,7 @@
import { io } from '$lib/webSocketConnection.js';
import { Song } from '$lib/Song';
import ChordSheetJS from 'chordsheetjs';
// import Fa from 'svelte-fa'
// import { faUser } from '@fortawesome/free-solid-svg-icons'
import { writable, derived } from 'svelte/store';
......@@ -15,12 +16,33 @@
const state = writable({ code: 0 }); // 0 = loading, 1 = success, 2 = error
const currentUsers = writable(0);
const parsers = {
'ChordsOverWords': ChordSheetJS.ChordsOverWordsParser,
'UltimateGuitar': ChordSheetJS.UltimateGuitarParser,
'ChordPro': ChordSheetJS.ChordProParser,
}
const playlist = writable(data.playlist);
const currentSongId = writable(0);
const songObject = derived(currentSongId, ($currentSongId) => {
if ($playlist?.songs && $currentSongId < $playlist.songs.length) {
const song = $playlist?.songs[$currentSongId];
if (song) {
if (song?.sheets && song?.sheets?.length !== 0) {
const musicsheet = song.sheets[0];
const parser = new (parsers[musicsheet.fileType ?? 'ChordPro'] ?? parsers['ChordPro'])();
const songSheet = parser.parse(musicsheet?.file);
const formatter = new ChordSheetJS.HtmlDivFormatter();
const output = formatter.format(songSheet);
if (typeof document !== 'undefined'){
const el = document.createElement('div');
el.innerHTML = output;
const possibleSongText = [...el.querySelectorAll('.paragraph')].map(a=>[...a.querySelectorAll('.row:has(.lyrics)')].map(b=>[...b.querySelectorAll('.lyrics')].map(c=>c.innerText).join('')).filter(d=>d!=='').join('\n')).filter(e=>e!=='').join('\n\n');
// console.log(possibleSongText);
}
}
const songObj = new Song(song.lyrics, $currentSongId);
if (!songObj.title) songObj.title = song.name;
let index = 0;
......@@ -34,11 +56,39 @@
return null;
});
const currentCoupletId = writable(0);
const musicSheetObject = derived(currentSongId, ($currentSongId) => {
if ($playlist?.songs && $currentSongId < $playlist.songs.length) {
const song = $playlist?.songs[$currentSongId];
if (song?.sheets && song?.sheets?.length !== 0) {
const musicsheet = song.sheets[0];
const parser = new (parsers[musicsheet.fileType ?? 'ChordPro'] ?? parsers['ChordPro'])();
const songSheet = parser.parse(musicsheet?.file);
const formatter = new ChordSheetJS.HtmlDivFormatter();
setTimeout(function() {
if (typeof document !== 'undefined') {
document.querySelectorAll('.chord-sheet > .paragraph > .row:has(.column > .lyrics)').forEach(function(element, index) {
element.addEventListener('click', function() {
setCurrentCouplet(index);
document.querySelectorAll('.chord-sheet > .paragraph > .row.active').forEach((e) => e.classList.remove('active'))
element.classList.add('active');
});
if (index === $currentCoupletId) element.classList.add('active');
});
}
}, 100);
return formatter.format(songSheet);
}
}
return null;
});
onMount(function() {
currentCoupletId.subscribe(function () {
setTimeout(function() {
document.querySelector('#songViewer > .couplet > .songtext.active')?.scrollIntoView({ block: 'center' });
document.querySelector('.active')?.scrollIntoView({ block: 'center' });
}, 100);
});
// if ($state.code === 0 && io.connected) io.emit('ready', { playlistId, playlist: JSON.stringify($playlist), joinCode: code });
......@@ -150,7 +200,9 @@
</div>
<div id='songViewer'>
{#if $songObject}
{#if $musicSheetObject}
{@html $musicSheetObject}
{:else if $songObject}
<h1>{$songObject.title} ({$currentSongId + 1} / {$playlist?.songs?.length ?? 0})</h1>
{#each $songObject.couplets as couplet, i}
<div class="couplet">
......@@ -164,6 +216,9 @@
{/if}
</div>
<style>
.songtext {
max-inline-size: 100%;
}
.active {
background-color: green;
}
......
<script>
import Roles from '$lib/roles';
import { Song } from '$lib/Song';
import ChordSheetJS from 'chordsheetjs';
import ChordSheetJS from 'chordsheetjs';
import { goto } from '$app/navigation';
import { resolveRoute } from '$app/paths';
import { slide } from 'svelte/transition';
......@@ -13,56 +13,29 @@
export let data;
$: authUser = data.authUser;
$: song = data.song;
$: songObject = song && new Song(song.lyrics, song.id);
const musicsheet = data.musicsheet;
const parsers = {
"ChordsOverWords": ChordSheetJS.ChordsOverWordsParser,
"UltimateGuitar": ChordSheetJS.UltimateGuitarParser,
"ChordPro": ChordSheetJS.ChordProParser,
'ChordsOverWords': ChordSheetJS.ChordsOverWordsParser,
'UltimateGuitar': ChordSheetJS.UltimateGuitarParser,
'ChordPro': ChordSheetJS.ChordProParser,
}
const parser = new parsers['ChordPro']();
const parser = new (parsers[musicsheet.fileType ?? 'ChordPro'] ?? parsers['ChordPro'])();
const songSheet = parser.parse(musicsheet?.file);
const formatter = new ChordSheetJS.TextFormatter();
const formatter = new ChordSheetJS.HtmlDivFormatter();
/** @type {import('svelte/store').Writable<string>} */
getContext('title').set('musicsheets.info');
</script>
<div transition:slide>
<h1>{$_('page.musicsheets.info.title')}</h1>
{#if (authUser.roleId <= Roles.Leader)}
{#if (authUser.roleId <= Roles.User)}
<button class="btn-action" on:click={() => goto(resolveRoute('/musicsheets/[song]/[id]/edit', { song: String(song.id), id: String(musicsheet.id) }), { replaceState: false })}>{$_('page.musicsheets.info.edit.button')}</button>
{/if}
<p><strong>{$_('fields.musicsheet.name.label')}:</strong> {musicsheet.name}</p>
<br><hr /><br>
<h3>{$_('fields.musicsheet.content.label')}:</h3><br>
<div class="song_container" hidden>
{#each songObject.couplets as couplet, i}
<div class="couplet">
{#each couplet as text, j}<pre class="songtext">{text}</pre>{#if j < couplet.length - 1}<br>{/if}{/each}
</div>
{#if i < songObject.couplets.length - 1}<br>{/if}
{/each}
</div>
<br>
<pre class="bg-green-800">
{formatter.format(songSheet)}
</pre>
</div>
<style>
.song_container {
width: max-content;
background-color: grey;
}
.couplet {
background-color: green;
padding: 10px;
}
.songtext {
width: 100%;
max-inline-size: 100%;
background-color: blue;
padding: 0px 5px;
}
</style>
\ No newline at end of file
{@html formatter.format(songSheet)}
</div>
\ No newline at end of file
......@@ -20,16 +20,17 @@ export const actions = {
const data = await request.formData();
const formData = parseAndCheckFormData(data, [
{ name: 'name', required: true, type: 'string' },
{ name: 'fileType', required: true, type: 'string' },
{ name: 'file', required: true, type: 'string' },
]);
if (!formData.success) return formData.data;
const { name, file } = formData.data;
const { name, fileType, file } = formData.data;
const origMusicsheet = await MusicSheet.findById(Number(params.id));
if (!origMusicsheet)
return fail(404);
const musicsheet = await MusicSheet.edit(origMusicsheet.id, name, file);
const musicsheet = await MusicSheet.edit(origMusicsheet.id, name, fileType, file);
return { success: true, musicsheet };
}
}
<script>
import TextField from '$lib/components/inputs/TextField.svelte';
import SelectField from '$lib/components/inputs/SelectField.svelte';
import Error from '$lib/components/alerts/Error.svelte';
import Notice from '$lib/components/alerts/Notice.svelte';
import Success from '$lib/components/alerts/Success.svelte';
import { fileOptions } from '$lib/config';
import { enhance, applyAction } from '$app/forms';
import { goto } from '$app/navigation';
......@@ -18,6 +20,7 @@
$: form = $page.form;
/** @type {string} */ $: name = form?.name ?? musicsheet?.name ?? '';
/** @type {string} */ $: fileType = form?.fileType ?? musicsheet?.fileType ?? '';
/** @type {string} */ $: file = form?.file ?? musicsheet?.file ?? '';
let working = false;
......@@ -69,6 +72,16 @@
bind:value={name}
disabled={working}
/>
<SelectField
id="fileType"
placeholder={$_('fields.musicsheet.file_type.placeholder')}
label={true}
labelText={$_('fields.musicsheet.file_type.label')}
required={true}
options={fileOptions}
bind:value={fileType}
disabled={working}
/>
<TextField
id="file"
type="textarea"
......
......@@ -9,10 +9,11 @@ export const actions = {
const data = await request.formData();
const formData = parseAndCheckFormData(data, [
{ name: 'name', required: true, type: 'string' },
{ name: 'content', required: true, type: 'string' },
{ name: 'fileType', required: true, type: 'string' },
{ name: 'file', required: true, type: 'string' },
]);
if (!formData.success) return formData.data;
const { name, content } = formData.data;
const { name, fileType, file } = formData.data;
const song = await Song.findById(Number(params.song));
if (!song)
......@@ -21,7 +22,7 @@ export const actions = {
//if (await MusicSheet.getByName(name))
// return fail(400, { unique: true });
const musicsheet = await MusicSheet.create(song.id, name, content);
const musicsheet = await MusicSheet.create(song.id, name, fileType, file);
return { success: true, musicsheet, ...formData.data };
}
}
<script>
import TextField from '$lib/components/inputs/TextField.svelte';
import SelectField from '$lib/components/inputs/SelectField.svelte';
import Error from '$lib/components/alerts/Error.svelte';
import Notice from '$lib/components/alerts/Notice.svelte';
import Success from '$lib/components/alerts/Success.svelte';
import { fileOptions } from '$lib/config';
import { enhance, applyAction } from '$app/forms';
import { goto } from '$app/navigation';
......@@ -18,7 +20,8 @@
$: form = $page.form;
/** @type {string} */ $: name = form?.name ?? '';
/** @type {string} */ $: content = form?.content ?? '';
/** @type {string} */ $: fileType = form?.fileType ?? '';
/** @type {string} */ $: file = form?.file ?? '';
let working = false;
......@@ -69,14 +72,24 @@
bind:value={name}
disabled={working}
/>
<SelectField
id="fileType"
placeholder={$_('fields.musicsheet.file_type.placeholder')}
label={true}
labelText={$_('fields.musicsheet.file_type.label')}
required={true}
options={fileOptions}
bind:value={fileType}
disabled={working}
/>
<TextField
id="content"
id="file"
type="textarea"
placeholder={$_('fields.musicsheet.content.placeholder')}
label={true}
labelText={$_('fields.musicsheet.content.label')}
required={true}
bind:value={content}
bind:value={file}
disabled={working}
/>
<button class="btn-success" disabled={working}>{$_('actions.create')}</button>
......
......@@ -21,16 +21,17 @@ export const actions = {
const formData = parseAndCheckFormData(data, [
{ name: 'songId', required: true, type: 'number' },
{ name: 'name', required: true, type: 'string' },
{ name: 'content', required: true, type: 'string' },
{ name: 'fileType', required: true, type: 'string' },
{ name: 'file', required: true, type: 'string' },
]);
if (!formData.success) return formData.data;
const { songId, name, content } = formData.data;
const { songId, name, fileType, file } = formData.data;
const song = await Song.findById(songId);
if (!song)
return fail(404);
const musicsheet = await MusicSheet.create(song.id, name, content);
const musicsheet = await MusicSheet.create(song.id, name, fileType, file);
return { success: true, musicsheet, ...formData.data };
}
}
......@@ -4,6 +4,7 @@
import Error from '$lib/components/alerts/Error.svelte';
import Notice from '$lib/components/alerts/Notice.svelte';
import Success from '$lib/components/alerts/Success.svelte';
import { fileOptions } from '$lib/config';
import { enhance, applyAction } from '$app/forms';
import { goto } from '$app/navigation';
......@@ -20,6 +21,7 @@
/** @type {number} */ $: songId = form?.songId ?? -1;
/** @type {string} */ $: name = form?.name ?? '';
/** @type {string} */ $: fileType = form?.fileType ?? '';
/** @type {string} */ $: file = form?.file ?? '';
/**
......@@ -89,8 +91,18 @@
bind:value={name}
disabled={working}
/>
<SelectField
id="fileType"
placeholder={$_('fields.musicsheet.file_type.placeholder')}
label={true}
labelText={$_('fields.musicsheet.file_type.label')}
required={true}
options={fileOptions}
bind:value={fileType}
disabled={working}
/>
<TextField
id="content"
id="file"
type="textarea"
placeholder={$_('fields.musicsheet.content.placeholder')}
label={true}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment