fix: various audioviewer bugs & improvements

- reflect editable state of regions
- editing regions actually works
- note is hidden in NOTE
- add logging
- select annotation by (not double) clicking
feat/type-attributes
Tomáš Mládek 2023-03-05 21:01:31 +01:00
parent 2e62854fda
commit 77a3a61063
1 changed files with 103 additions and 63 deletions

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { debounce, throttle } from "lodash"; import { debounce, throttle } from "lodash";
import { onMount } from "svelte"; import { onMount } from "svelte";
import type { IValue } from "upend/types"; import type { IEntry, IValue } from "upend/types";
import type WaveSurfer from "wavesurfer.js"; import type WaveSurfer from "wavesurfer.js";
import type { Region, RegionParams } from "wavesurfer.js/src/plugin/regions"; import type { Region, RegionParams } from "wavesurfer.js/src/plugin/regions";
import { import {
@ -14,6 +14,7 @@
import { TimeFragment } from "../../../util/fragments/time"; import { TimeFragment } from "../../../util/fragments/time";
import Icon from "../../utils/Icon.svelte"; import Icon from "../../utils/Icon.svelte";
import Selector from "../../utils/Selector.svelte"; import Selector from "../../utils/Selector.svelte";
import UpObject from "../../display/UpObject.svelte";
import Spinner from "../../utils/Spinner.svelte"; import Spinner from "../../utils/Spinner.svelte";
import { i18n } from "../../../i18n"; import { i18n } from "../../../i18n";
@ -41,10 +42,6 @@
type UpRegion = Region & { data: IValue }; type UpRegion = Region & { data: IValue };
let currentAnnotation: UpRegion | undefined; let currentAnnotation: UpRegion | undefined;
$: currentAnnotationIndex =
Array.from(regions)
.sort((a, b) => a.start - b.start)
.findIndex((r) => r.id === currentAnnotation.id) + 1;
async function loadAnnotations() { async function loadAnnotations() {
const entity = await fetchEntity(address); const entity = await fetchEntity(address);
@ -60,8 +57,11 @@
wavesurfer.addRegion({ wavesurfer.addRegion({
id: `ws-region-${e.entity}`, id: `ws-region-${e.entity}`,
color: annotation.get("COLOR") || DEFAULT_ANNOTATION_COLOR, color: annotation.get("COLOR") || DEFAULT_ANNOTATION_COLOR,
attributes: { "upend-id": annotation.address }, attributes: {
data: (annotation.attr["LBL"] || [])[0]?.value, "upend-address": annotation.address,
label: annotation.get("LBL"),
},
data: (annotation.attr["NOTE"] || [])[0]?.value,
...fragment, ...fragment,
} as RegionParams); } as RegionParams);
} }
@ -69,70 +69,72 @@
}); });
} }
async function createAnnotation(region: Region) {
const [_, uuid] = await putEntry({
entity: {
t: "Uuid",
},
});
// incorrect types, `update()` does take `attributes`
region.update({ attributes: { "upend-id": uuid } } as any);
await putEntry([
{
entity: uuid,
attribute: "ANNOTATES",
value: {
t: "Address",
c: address,
},
},
{
entity: uuid,
attribute: "W3C_FRAGMENT_SELECTOR",
value: {
t: "String",
c: new TimeFragment(region.start, region.end).toString(),
},
},
]);
}
$: if (wavesurfer) { $: if (wavesurfer) {
if (editable) { if (editable) {
wavesurfer.enableDragSelection({ color: DEFAULT_ANNOTATION_COLOR }); wavesurfer.enableDragSelection({ color: DEFAULT_ANNOTATION_COLOR });
} else { } else {
wavesurfer.disableDragSelection(); wavesurfer.disableDragSelection();
} }
regions.forEach((region) => Object.values(wavesurfer.regions.list).forEach((region) => {
region.update({ drag: detail, resize: detail }) region.update({ drag: editable, resize: editable });
); });
} }
async function updateAnnotation(region: Region) { async function updateAnnotation(region: Region) {
const entity = region.attributes["upend-id"]; let entity = region.attributes["upend-address"];
// Newly created
if (!entity) {
let [_, newEntity] = await putEntry({
entity: {
t: "Uuid",
},
});
entity = newEntity;
const nextAnnotationIndex = Object.values(wavesurfer.regions.list).length;
const label = `Annotation #${nextAnnotationIndex}`;
region.update({
attributes: {
"upend-address": entity,
label,
},
} as any); // incorrect types, `update()` does take `attributes`
}
await putEntityAttribute(entity, "LBL", {
t: "String",
c: region.attributes["label"],
});
await putEntityAttribute(entity, "ANNOTATES", {
t: "Address",
c: address,
});
await putEntityAttribute(entity, "W3C_FRAGMENT_SELECTOR", { await putEntityAttribute(entity, "W3C_FRAGMENT_SELECTOR", {
t: "String", t: "String",
c: new TimeFragment(region.start, region.end).toString(), c: new TimeFragment(region.start, region.end).toString(),
}); });
await putEntityAttribute(entity, "LBL", region.data as IValue);
await putEntityAttribute(entity, "COLOR", { await putEntityAttribute(entity, "COLOR", {
t: "String", t: "String",
c: region.color, c: region.color,
}); });
if (Object.values(region.data).length) {
await putEntityAttribute(entity, "NOTE", region.data as IValue);
}
} }
const updateAnnotationDebounced = debounce(updateAnnotation, 250); const updateAnnotationDebounced = debounce(updateAnnotation, 250);
async function deleteAnnotation(region: Region) { async function deleteAnnotation(region: Region) {
// TODO - what if there's no id? if (region.attributes["upend-address"]) {
await deleteEntry(region.attributes["upend-id"]); await deleteEntry(region.attributes["upend-address"]);
}
} }
const regions = new Set<Region>();
onMount(async () => { onMount(async () => {
const WaveSurfer = await import("wavesurfer.js"); const WaveSurfer = await import("wavesurfer.js");
const TimelinePlugin = await import("wavesurfer.js/src/plugin/timeline"); const TimelinePlugin = await import("wavesurfer.js/src/plugin/timeline");
@ -163,43 +165,69 @@
}); });
wavesurfer.on("ready", () => { wavesurfer.on("ready", () => {
console.debug("wavesurfer ready");
loaded = true; loaded = true;
loadAnnotations(); loadAnnotations();
}); });
wavesurfer.on("region-created", (region: UpRegion) => { wavesurfer.on("region-created", async (region: UpRegion) => {
regions.add(region); console.debug("wavesurfer region-created", region);
if (!region.attributes["upend-id"]) {
createAnnotation(region); // Updating here, because if `drag` and `resize` are passed during adding,
currentAnnotation = region; // updating no longer works.
region.update({ drag: editable, resize: editable });
// If the region was created from the UI
if (!region.attributes["upend-address"]) {
await updateAnnotation(region);
// currentAnnotation = region;
} }
}); });
wavesurfer.on("region-updated", (region: UpRegion) => { wavesurfer.on("region-updated", (region: UpRegion) => {
// console.debug("wavesurfer region-updated", region);
currentAnnotation = region; currentAnnotation = region;
}); });
wavesurfer.on("region-update-end", (region: UpRegion) => { wavesurfer.on("region-update-end", (region: UpRegion) => {
console.debug("wavesurfer region-update-end", region);
updateAnnotation(region); updateAnnotation(region);
currentAnnotation = region; currentAnnotation = region;
}); });
wavesurfer.on("region-removed", (region: UpRegion) => { wavesurfer.on("region-removed", (region: UpRegion) => {
console.debug("wavesurfer region-removed", region);
currentAnnotation = null;
deleteAnnotation(region); deleteAnnotation(region);
regions.delete(region);
}); });
wavesurfer.on("region-in", (region: UpRegion) => { // wavesurfer.on("region-in", (region: UpRegion) => {
// console.debug("wavesurfer region-in", region);
// currentAnnotation = region;
// });
// wavesurfer.on("region-out", (region: UpRegion) => {
// console.debug("wavesurfer region-out", region);
// if (currentAnnotation.id === region.id) {
// currentAnnotation = undefined;
// }
// });
wavesurfer.on("region-click", (region: UpRegion, _ev: MouseEvent) => {
console.debug("wavesurfer region-click", region);
currentAnnotation = region; currentAnnotation = region;
}); });
wavesurfer.on("region-out", (region: UpRegion) => {
if (currentAnnotation.id === region.id) {
currentAnnotation = undefined;
}
});
wavesurfer.on("region-dblclick", (region: UpRegion, _ev: MouseEvent) => { wavesurfer.on("region-dblclick", (region: UpRegion, _ev: MouseEvent) => {
console.debug("wavesurfer region-dblclick", region);
currentAnnotation = region; currentAnnotation = region;
setTimeout(() => wavesurfer.setCurrentTime(region.start)); setTimeout(() => wavesurfer.setCurrentTime(region.start));
}); });
@ -231,7 +259,7 @@
}); });
</script> </script>
<div class="audio"> <div class="audio" class:editable>
{#if !loaded} {#if !loaded}
<Spinner centered /> <Spinner centered />
{/if} {/if}
@ -254,7 +282,15 @@
<div class="wavesurfer" bind:this={containerEl} /> <div class="wavesurfer" bind:this={containerEl} />
{#if currentAnnotation} {#if currentAnnotation}
<section class="annotationEditor labelborder"> <section class="annotationEditor labelborder">
<header><h3>Annotation #{currentAnnotationIndex}</h3></header> <header>
<h3>{$i18n.t("Annotation")}</h3>
</header>
{#if currentAnnotation.attributes["upend-address"]}
<UpObject
link
address={currentAnnotation.attributes["upend-address"]}
/>
{/if}
<div class="baseControls"> <div class="baseControls">
<div class="regionControls"> <div class="regionControls">
<div class="start"> <div class="start">
@ -310,7 +346,7 @@
{#key currentAnnotation} {#key currentAnnotation}
<Selector <Selector
type="value" type="value"
valueTypes={["String"]} valueTypes={["String", "Address"]}
value={currentAnnotation.data} value={currentAnnotation.data}
disabled={!editable} disabled={!editable}
on:input={(ev) => { on:input={(ev) => {
@ -379,6 +415,10 @@
display: none; display: none;
} }
:global(.audio:not(.editable) .wavesurfer-handle) {
display: none;
}
:global(.wavesurfer-handle) { :global(.wavesurfer-handle) {
background: var(--foreground-lightest) !important; background: var(--foreground-lightest) !important;
} }