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
This commit is contained in:
parent
2e62854fda
commit
77a3a61063
1 changed files with 103 additions and 63 deletions
|
@ -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,69 +69,71 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue