|
|
@@ -10,7 +10,19 @@
|
|
|
import { transfer, transferKind } from "./lib/pads";
|
|
|
import SequencePanel from "./lib/SequencePanel.svelte";
|
|
|
import SlotGrid, { type Cell } from "./lib/SlotGrid.svelte";
|
|
|
- import { app, exportFolder, exportZip, listen, loadProject, message, newProject, refresh, run, ui, type Tab } from "./lib/store.svelte";
|
|
|
+ import {
|
|
|
+ app,
|
|
|
+ exportFolder,
|
|
|
+ exportZip,
|
|
|
+ listen,
|
|
|
+ loadProject,
|
|
|
+ message,
|
|
|
+ newProject,
|
|
|
+ refresh,
|
|
|
+ run,
|
|
|
+ ui,
|
|
|
+ type Tab,
|
|
|
+ } from "./lib/store.svelte";
|
|
|
import { BANKS, PER_BANK, bars, gridSlots, tuneText } from "./lib/slots";
|
|
|
|
|
|
onMount(() => {
|
|
|
@@ -35,7 +47,11 @@
|
|
|
|
|
|
// Export would fail on a missing sample file, so it is not offered at all.
|
|
|
const exportBlocked = $derived((st?.missingCount ?? 0) > 0);
|
|
|
- const exportBlockedWhy = $derived(exportBlocked ? "Export is disabled while sample files are missing. Replace them, or put the files back and load the project again." : undefined);
|
|
|
+ const exportBlockedWhy = $derived(
|
|
|
+ exportBlocked
|
|
|
+ ? "Export is disabled while sample files are missing. Replace them, or put the files back and load the project again."
|
|
|
+ : undefined,
|
|
|
+ );
|
|
|
|
|
|
const padCounts = $derived(perBank((s) => (pads[s]?.file ?? "") !== ""));
|
|
|
const seqCounts = $derived(perBank((s) => seqBySlot.has(s)));
|
|
|
@@ -46,7 +62,9 @@
|
|
|
const p = pads[slot];
|
|
|
const badges = p.missing
|
|
|
? ["missing"]
|
|
|
- : [tuneText(p.coarse, p.fine), p.muteGroup ? "M" + p.muteGroup : "", p.hasNotes ? "notes" : ""].filter(Boolean);
|
|
|
+ : [tuneText(p.coarse, p.fine), p.muteGroup ? "M" + p.muteGroup : "", p.hasNotes ? "notes" : ""].filter(
|
|
|
+ Boolean,
|
|
|
+ );
|
|
|
return {
|
|
|
slot,
|
|
|
label: p.label,
|
|
|
@@ -119,13 +137,24 @@
|
|
|
<h1 class="visually-hidden">MPCkit</h1>
|
|
|
<div class="name">
|
|
|
<label class="eyebrow" for="project-name">Project name</label>
|
|
|
- <input id="project-name" type="text" value={st?.name ?? ""} spellcheck="false" autocomplete="off" onchange={rename} />
|
|
|
+ <input
|
|
|
+ id="project-name"
|
|
|
+ type="text"
|
|
|
+ value={st?.name ?? ""}
|
|
|
+ spellcheck="false"
|
|
|
+ autocomplete="off"
|
|
|
+ onchange={rename}
|
|
|
+ />
|
|
|
</div>
|
|
|
<div class="actions">
|
|
|
<button type="button" class="btn" onclick={newProject}>New</button>
|
|
|
<button type="button" class="btn" onclick={loadProject}>Load</button>
|
|
|
- <button type="button" class="btn" disabled={exportBlocked} title={exportBlockedWhy} onclick={exportZip}>Export zip</button>
|
|
|
- <button type="button" class="btn primary" disabled={exportBlocked} title={exportBlockedWhy} onclick={exportFolder}>Export to folder</button>
|
|
|
+ <button type="button" class="btn" disabled={exportBlocked} title={exportBlockedWhy} onclick={exportZip}
|
|
|
+ >Export zip</button
|
|
|
+ >
|
|
|
+ <button type="button" class="btn primary" disabled={exportBlocked} title={exportBlockedWhy} onclick={exportFolder}
|
|
|
+ >Export to folder</button
|
|
|
+ >
|
|
|
</div>
|
|
|
<button type="button" class="about-link" onclick={() => (ui.aboutOpen = true)}>About</button>
|
|
|
</header>
|
|
|
@@ -138,8 +167,26 @@
|
|
|
{/if}
|
|
|
|
|
|
<div class="tabs" role="tablist" aria-label="Views">
|
|
|
- <button id="tab-pads" type="button" role="tab" aria-selected={ui.tab === "pads"} aria-controls="view-pads" tabindex={ui.tab === "pads" ? 0 : -1} onclick={() => pickTab("pads")} onkeydown={tabKey}>Pads</button>
|
|
|
- <button id="tab-sequences" type="button" role="tab" aria-selected={ui.tab === "sequences"} aria-controls="view-sequences" tabindex={ui.tab === "sequences" ? 0 : -1} onclick={() => pickTab("sequences")} onkeydown={tabKey}>Sequences</button>
|
|
|
+ <button
|
|
|
+ id="tab-pads"
|
|
|
+ type="button"
|
|
|
+ role="tab"
|
|
|
+ aria-selected={ui.tab === "pads"}
|
|
|
+ aria-controls="view-pads"
|
|
|
+ tabindex={ui.tab === "pads" ? 0 : -1}
|
|
|
+ onclick={() => pickTab("pads")}
|
|
|
+ onkeydown={tabKey}>Pads</button
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ id="tab-sequences"
|
|
|
+ type="button"
|
|
|
+ role="tab"
|
|
|
+ aria-selected={ui.tab === "sequences"}
|
|
|
+ aria-controls="view-sequences"
|
|
|
+ tabindex={ui.tab === "sequences" ? 0 : -1}
|
|
|
+ onclick={() => pickTab("sequences")}
|
|
|
+ onkeydown={tabKey}>Sequences</button
|
|
|
+ >
|
|
|
</div>
|
|
|
|
|
|
{#if st}
|
|
|
@@ -161,7 +208,10 @@
|
|
|
ondragbank={(b) => (ui.padBank = b)}
|
|
|
/>
|
|
|
<SlotGrid cells={padCells} selected={ui.padSlot} onpick={pickPad} dropTarget ondroppad={dropPad} />
|
|
|
- <p class="hint grid-hint">Click a pad to select and hear it. Drag a pad to move it, drag onto a filled pad to swap, hold Alt while dragging to copy. Drag over a bank letter to reach another bank. Drop a WAV file on any pad to assign it.</p>
|
|
|
+ <p class="hint grid-hint">
|
|
|
+ Click a pad to select and hear it. Drag a pad to move it, drag onto a filled pad to swap, hold Alt while
|
|
|
+ dragging to copy. Drag over a bank letter to reach another bank. Drop a WAV file on any pad to assign it.
|
|
|
+ </p>
|
|
|
</section>
|
|
|
<PadPanel pad={pads[ui.padSlot]} limits={st.limits} />
|
|
|
</div>
|
|
|
@@ -182,7 +232,9 @@
|
|
|
}}
|
|
|
/>
|
|
|
<SlotGrid cells={seqCells} selected={ui.seqSlot} onpick={(s) => (ui.seqSlot = s)} />
|
|
|
- <p class="hint grid-hint">Each pad is one sequence slot, {st.limits.maxSequences} in total. Click an empty slot to add a sequence there.</p>
|
|
|
+ <p class="hint grid-hint">
|
|
|
+ Each pad is one sequence slot, {st.limits.maxSequences} in total. Click an empty slot to add a sequence there.
|
|
|
+ </p>
|
|
|
</section>
|
|
|
<SequencePanel slot={ui.seqSlot} seq={seqBySlot.get(ui.seqSlot)} limits={st.limits} />
|
|
|
</div>
|
|
|
@@ -192,7 +244,9 @@
|
|
|
<span>{st.sampleCount} of {st.limits.maxSamples} samples</span>
|
|
|
<span>{st.sequenceCount} of {st.limits.maxSequences} sequences</span>
|
|
|
<span>WAV files only</span>
|
|
|
- {#if st.missingCount}<span class="missing">{st.missingCount} sample {st.missingCount === 1 ? "file" : "files"} missing: export is disabled</span>{/if}
|
|
|
+ {#if st.missingCount}<span class="missing"
|
|
|
+ >{st.missingCount} sample {st.missingCount === 1 ? "file" : "files"} missing: export is disabled</span
|
|
|
+ >{/if}
|
|
|
{#if st.dirty}<span class="dirty">Unsaved changes: export to keep them</span>{/if}
|
|
|
{#if app.notice}<span class="notice" title={app.notice}>{app.notice}</span>{/if}
|
|
|
</footer>
|
|
|
@@ -204,7 +258,7 @@
|
|
|
<AboutModal onclose={() => (ui.aboutOpen = false)} />
|
|
|
{/if}
|
|
|
{#if ui.picker && st}
|
|
|
- <PadPicker picker={ui.picker} pads={pads} onclose={() => (ui.picker = null)} />
|
|
|
+ <PadPicker picker={ui.picker} {pads} onclose={() => (ui.picker = null)} />
|
|
|
{/if}
|
|
|
{#if ui.confirm}
|
|
|
<ConfirmModal c={ui.confirm} />
|