autoformat + npm update
This commit is contained in:
parent
66852229bd
commit
c104a70dbe
3 changed files with 15262 additions and 5901 deletions
20772
app/package-lock.json
generated
20772
app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -7,6 +7,7 @@
|
||||||
"build": "vue-cli-service build"
|
"build": "vue-cli-service build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@vue/cli": "^4.5.12",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"fetch-progress": "^1.3.0",
|
"fetch-progress": "^1.3.0",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="svg-content">
|
<div class="svg-content">
|
||||||
<div :class="['loading-screen', {loaded: loadedPercent === 100}]">
|
<div :class="['loading-screen', { loaded: loadedPercent === 100 }]">
|
||||||
<div :style="{width: `${loadedPercent}%`}" class="loading-bar"></div>
|
<div :style="{ width: `${loadedPercent}%` }" class="loading-bar"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content" ref="root">
|
<div class="content" ref="root">
|
||||||
<div class="video-scrolls">
|
<div class="video-scrolls">
|
||||||
<VideoScroll v-for="scroll in scrolls" :definition="scroll"/>
|
<VideoScroll v-for="scroll in scrolls" :definition="scroll" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AudioArea v-for="audio in audioAreas" :definition="audio" :bbox="bbox"/>
|
<AudioArea v-for="audio in audioAreas" :definition="audio" :bbox="bbox" />
|
||||||
<div class="dev devpanel">
|
<div class="dev devpanel">
|
||||||
<div>
|
<div>
|
||||||
<span>Current viewport position:</span>
|
<span>Current viewport position:</span>
|
||||||
|
@ -16,13 +16,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>Current cursor position:</span>
|
<span>Current cursor position:</span>
|
||||||
<span>{{ Math.round(mousePosition.x) }}x{{ Math.round(mousePosition.y) }}</span>
|
<span
|
||||||
|
>{{ Math.round(mousePosition.x) }}x{{
|
||||||
|
Math.round(mousePosition.y)
|
||||||
|
}}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>Zoom level:</span><span>{{ (Math.round(bbox.z * 1000) / 1000) }}</span>
|
<span>Zoom level:</span
|
||||||
|
><span>{{ Math.round(bbox.z * 1000) / 1000 }}</span>
|
||||||
</div>
|
</div>
|
||||||
<label>
|
<label>
|
||||||
<input v-model="showInternal" type="checkbox">
|
<input v-model="showInternal" type="checkbox" />
|
||||||
<label>Show internal elements</label>
|
<label>Show internal elements</label>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,46 +35,49 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {defineComponent, onMounted, reactive, ref} from "vue";
|
import { defineComponent, onMounted, reactive, ref } from "vue";
|
||||||
import createPanZoom, {PanZoom} from "panzoom";
|
import createPanZoom, { PanZoom } from "panzoom";
|
||||||
import VideoScroll, {VideoScrollDef, VideoScrollDirection} from "@/components/VideoScroll.vue";
|
import VideoScroll, {
|
||||||
import AudioArea, {AudioAreaDef} from "@/components/AudioArea.vue";
|
VideoScrollDef,
|
||||||
|
VideoScrollDirection,
|
||||||
|
} from "@/components/VideoScroll.vue";
|
||||||
|
import AudioArea, { AudioAreaDef } from "@/components/AudioArea.vue";
|
||||||
import Stats from "stats.js";
|
import Stats from "stats.js";
|
||||||
import {rotate} from "@/utils";
|
import { rotate } from "@/utils";
|
||||||
import fetchProgress from "fetch-progress";
|
import fetchProgress from "fetch-progress";
|
||||||
|
|
||||||
|
|
||||||
export interface BoundingBox {
|
export interface BoundingBox {
|
||||||
x: number,
|
x: number;
|
||||||
y: number,
|
y: number;
|
||||||
w: number,
|
w: number;
|
||||||
h: number,
|
h: number;
|
||||||
z: number
|
z: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "SVGContent",
|
name: "SVGContent",
|
||||||
components: {AudioArea, VideoScroll},
|
components: { AudioArea, VideoScroll },
|
||||||
props: {
|
props: {
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showInternal: false
|
showInternal: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
showInternal(value) {
|
showInternal(value) {
|
||||||
Array.from(this.root!.getElementsByClassName("internal")).forEach((el) => {
|
Array.from(this.root!.getElementsByClassName("internal")).forEach(
|
||||||
(el as SVGElement).style.visibility = value ? "visible" : "hidden";
|
(el) => {
|
||||||
});
|
(el as SVGElement).style.visibility = value ? "visible" : "hidden";
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props, {emit}) {
|
setup(props, { emit }) {
|
||||||
const root = ref<HTMLDivElement | null>(null);
|
const root = ref<HTMLDivElement | null>(null);
|
||||||
const loadedPercent = ref(0);
|
const loadedPercent = ref(0);
|
||||||
const panzoom = ref<null | PanZoom>(null);
|
const panzoom = ref<null | PanZoom>(null);
|
||||||
|
@ -82,34 +90,41 @@ export default defineComponent({
|
||||||
y: ref(0),
|
y: ref(0),
|
||||||
w: ref(0),
|
w: ref(0),
|
||||||
h: ref(0),
|
h: ref(0),
|
||||||
z: ref(1)
|
z: ref(1),
|
||||||
});
|
});
|
||||||
const mousePosition = reactive({
|
const mousePosition = reactive({
|
||||||
x: ref(0),
|
x: ref(0),
|
||||||
y: ref(0)
|
y: ref(0),
|
||||||
});
|
});
|
||||||
const panning = ref(false);
|
const panning = ref(false);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const element = root.value as unknown as HTMLDivElement;
|
const element = (root.value as unknown) as HTMLDivElement;
|
||||||
console.info("[SVG] Initializing.");
|
console.info("[SVG] Initializing.");
|
||||||
|
|
||||||
// Fetch & load SVG
|
// Fetch & load SVG
|
||||||
console.info(`[SVG] Fetching "${props.url}..."`);
|
console.info(`[SVG] Fetching "${props.url}..."`);
|
||||||
const fetchResult = await fetch(props.url).then(
|
const fetchResult = await fetch(props.url).then(
|
||||||
fetchProgress({
|
fetchProgress({
|
||||||
onProgress(progress) {
|
onProgress(progress) {
|
||||||
loadedPercent.value = (progress as any).percentage;
|
loadedPercent.value = (progress as any).percentage;
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const svgParsed = new DOMParser().parseFromString(await fetchResult.text(), "image/svg+xml") as Document;
|
const svgParsed = new DOMParser().parseFromString(
|
||||||
|
await fetchResult.text(),
|
||||||
|
"image/svg+xml"
|
||||||
|
) as Document;
|
||||||
console.debug("[SVG] Loaded.");
|
console.debug("[SVG] Loaded.");
|
||||||
loadedPercent.value = 100;
|
loadedPercent.value = 100;
|
||||||
const svg = element.appendChild(svgParsed.firstElementChild as Element) as any;
|
const svg = element.appendChild(
|
||||||
|
svgParsed.firstElementChild as Element
|
||||||
|
) as any;
|
||||||
|
|
||||||
// Set document background
|
// Set document background
|
||||||
const pageColor = svg.getElementById("base")?.attributes.getNamedItem("pagecolor");
|
const pageColor = svg
|
||||||
|
.getElementById("base")
|
||||||
|
?.attributes.getNamedItem("pagecolor");
|
||||||
if (pageColor) {
|
if (pageColor) {
|
||||||
console.debug(`[SVG] Found pageColor attribute: ${pageColor.value}`);
|
console.debug(`[SVG] Found pageColor attribute: ${pageColor.value}`);
|
||||||
emit("setBackground", pageColor.value);
|
emit("setBackground", pageColor.value);
|
||||||
|
@ -138,49 +153,57 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
panzoom.value = pz;
|
panzoom.value = pz;
|
||||||
|
|
||||||
// Calculate SVG-unit bounding box, update transform
|
// Calculate SVG-unit bounding box, update transform
|
||||||
pz.on("transform", function (_) {
|
pz.on("transform", function (_) {
|
||||||
const transform = pz.getTransform();
|
const transform = pz.getTransform();
|
||||||
const currentRatio = svg.clientWidth * transform.scale / svg.viewBox.baseVal.width;
|
const currentRatio =
|
||||||
|
(svg.clientWidth * transform.scale) / svg.viewBox.baseVal.width;
|
||||||
|
|
||||||
bbox.x = transform.x / currentRatio * -1;
|
bbox.x = (transform.x / currentRatio) * -1;
|
||||||
bbox.y = transform.y / currentRatio * -1;
|
bbox.y = (transform.y / currentRatio) * -1;
|
||||||
bbox.w = window.innerWidth / currentRatio;
|
bbox.w = window.innerWidth / currentRatio;
|
||||||
bbox.h = window.innerHeight / currentRatio;
|
bbox.h = window.innerHeight / currentRatio;
|
||||||
bbox.z = transform.scale;
|
bbox.z = transform.scale;
|
||||||
|
|
||||||
window.location.hash =
|
window.location.hash = `${Math.round(bbox.x + bbox.w / 2)},${Math.round(
|
||||||
`${Math.round(bbox.x + bbox.w / 2)},${Math.round(bbox.y + bbox.h / 2)},${Math.round(transform.scale * 1000) / 1000}z`;
|
bbox.y + bbox.h / 2
|
||||||
|
)},${Math.round(transform.scale * 1000) / 1000}z`;
|
||||||
});
|
});
|
||||||
|
|
||||||
function panToElement(target: SVGRectElement, smooth: boolean) {
|
function panToElement(target: SVGRectElement, smooth: boolean) {
|
||||||
console.debug(`[SVG] Panning to element: #${target.id}`);
|
console.debug(`[SVG] Panning to element: #${target.id}`);
|
||||||
const transform = pz.getTransform();
|
const transform = pz.getTransform();
|
||||||
const currentRatio = svg.clientWidth * transform.scale / svg.viewBox.baseVal.width;
|
const currentRatio =
|
||||||
|
(svg.clientWidth * transform.scale) / svg.viewBox.baseVal.width;
|
||||||
const ratio = svg.clientWidth / svg.viewBox.baseVal.width;
|
const ratio = svg.clientWidth / svg.viewBox.baseVal.width;
|
||||||
const targetScale = window.innerWidth / (target.width.baseVal.value * ratio);
|
const targetScale =
|
||||||
|
window.innerWidth / (target.width.baseVal.value * ratio);
|
||||||
|
|
||||||
const svgTargetX = (target.x.baseVal.value + target.width.baseVal.value / 2) * currentRatio;
|
const svgTargetX =
|
||||||
const svgTargetY = (target.y.baseVal.value + target.height.baseVal.value / 2) * currentRatio;
|
(target.x.baseVal.value + target.width.baseVal.value / 2) *
|
||||||
|
currentRatio;
|
||||||
|
const svgTargetY =
|
||||||
|
(target.y.baseVal.value + target.height.baseVal.value / 2) *
|
||||||
|
currentRatio;
|
||||||
|
|
||||||
if (smooth) {
|
if (smooth) {
|
||||||
panning.value = true;
|
panning.value = true;
|
||||||
|
|
||||||
pz.smoothMoveTo(
|
pz.smoothMoveTo(
|
||||||
svgTargetX * -1 + window.innerWidth / 2,
|
svgTargetX * -1 + window.innerWidth / 2,
|
||||||
svgTargetY * -1 + window.innerHeight / 2,
|
svgTargetY * -1 + window.innerHeight / 2
|
||||||
);
|
);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const finalTransform = pz.getTransform();
|
const finalTransform = pz.getTransform();
|
||||||
pz.smoothZoomAbs(
|
pz.smoothZoomAbs(
|
||||||
(svgTargetX + finalTransform.x),
|
svgTargetX + finalTransform.x,
|
||||||
(svgTargetY + finalTransform.y),
|
svgTargetY + finalTransform.y,
|
||||||
targetScale
|
targetScale
|
||||||
);
|
);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
panning.value = false;
|
panning.value = false;
|
||||||
|
@ -188,10 +211,14 @@ export default defineComponent({
|
||||||
}, 400 * 4);
|
}, 400 * 4);
|
||||||
} else {
|
} else {
|
||||||
pz.moveTo(
|
pz.moveTo(
|
||||||
svgTargetX * -1 + window.innerWidth / 2,
|
svgTargetX * -1 + window.innerWidth / 2,
|
||||||
svgTargetY * -1 + window.innerHeight / 2,
|
svgTargetY * -1 + window.innerHeight / 2
|
||||||
|
);
|
||||||
|
pz.zoomAbs(
|
||||||
|
window.innerWidth / 2,
|
||||||
|
window.innerHeight / 2,
|
||||||
|
targetScale
|
||||||
);
|
);
|
||||||
pz.zoomAbs(window.innerWidth / 2, window.innerHeight / 2, targetScale);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,18 +238,25 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pan to start element or location in hash
|
// Pan to start element or location in hash
|
||||||
const locationMatch = window.location.href.match(/#([\-0-9.]+),([\-0-9.]+),([0-9.]+)z/);
|
const locationMatch = window.location.href.match(
|
||||||
|
/#([\-0-9.]+),([\-0-9.]+),([0-9.]+)z/
|
||||||
|
);
|
||||||
if (locationMatch) {
|
if (locationMatch) {
|
||||||
console.debug(`[SVGCONTENT] Got a location match: ${locationMatch}`);
|
console.debug(`[SVGCONTENT] Got a location match: ${locationMatch}`);
|
||||||
const [_, x, y, z] = locationMatch;
|
const [_, x, y, z] = locationMatch;
|
||||||
|
|
||||||
const transform = pz.getTransform();
|
const transform = pz.getTransform();
|
||||||
const currentRatio = svg.clientWidth * transform.scale / svg.viewBox.baseVal.width;
|
const currentRatio =
|
||||||
|
(svg.clientWidth * transform.scale) / svg.viewBox.baseVal.width;
|
||||||
pz.moveTo(
|
pz.moveTo(
|
||||||
(parseFloat(x) * currentRatio * -1 + window.innerWidth / 2),
|
parseFloat(x) * currentRatio * -1 + window.innerWidth / 2,
|
||||||
(parseFloat(y) * currentRatio * -1 + window.innerHeight / 2)
|
parseFloat(y) * currentRatio * -1 + window.innerHeight / 2
|
||||||
|
);
|
||||||
|
pz.zoomAbs(
|
||||||
|
window.innerWidth / 2,
|
||||||
|
window.innerHeight / 2,
|
||||||
|
parseFloat(z)
|
||||||
);
|
);
|
||||||
pz.zoomAbs(window.innerWidth / 2, window.innerHeight / 2, parseFloat(z));
|
|
||||||
} else if (start) {
|
} else if (start) {
|
||||||
console.debug(`[SVGCONTENT] Panning to start anchor.`);
|
console.debug(`[SVGCONTENT] Panning to start anchor.`);
|
||||||
panToElement(start, false);
|
panToElement(start, false);
|
||||||
|
@ -235,8 +269,10 @@ export default defineComponent({
|
||||||
|
|
||||||
// Links
|
// Links
|
||||||
console.debug("[SVG] Processing hyperlinks.");
|
console.debug("[SVG] Processing hyperlinks.");
|
||||||
const {anchor, hyper} = processHyperlinks(svg);
|
const { anchor, hyper } = processHyperlinks(svg);
|
||||||
console.info(`[SVG] Found ${anchor.length} anchor links and ${hyper.length} hyperlinks.`);
|
console.info(
|
||||||
|
`[SVG] Found ${anchor.length} anchor links and ${hyper.length} hyperlinks.`
|
||||||
|
);
|
||||||
anchor.forEach(([anchorId, element]) => {
|
anchor.forEach(([anchorId, element]) => {
|
||||||
const anchor = anchors.value.find((a) => a.id == anchorId);
|
const anchor = anchors.value.find((a) => a.id == anchorId);
|
||||||
if (!anchor) {
|
if (!anchor) {
|
||||||
|
@ -265,9 +301,11 @@ export default defineComponent({
|
||||||
stats = new Stats();
|
stats = new Stats();
|
||||||
document.body.appendChild(stats.dom);
|
document.body.appendChild(stats.dom);
|
||||||
|
|
||||||
Array.from(document.body.getElementsByClassName("dev")).forEach((el) => {
|
Array.from(document.body.getElementsByClassName("dev")).forEach(
|
||||||
(el as HTMLElement).style.display = "block";
|
(el) => {
|
||||||
});
|
(el as HTMLElement).style.display = "block";
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animations: FPS Counter, Edge scrolling
|
// Animations: FPS Counter, Edge scrolling
|
||||||
|
@ -275,7 +313,8 @@ export default defineComponent({
|
||||||
window.addEventListener("mousemove", (ev) => {
|
window.addEventListener("mousemove", (ev) => {
|
||||||
mouse = ev;
|
mouse = ev;
|
||||||
const transform = pz.getTransform();
|
const transform = pz.getTransform();
|
||||||
const currentRatio = svg.clientWidth * transform.scale / svg.viewBox.baseVal.width;
|
const currentRatio =
|
||||||
|
(svg.clientWidth * transform.scale) / svg.viewBox.baseVal.width;
|
||||||
mousePosition.x = (mouse.clientX - transform.x) / currentRatio;
|
mousePosition.x = (mouse.clientX - transform.x) / currentRatio;
|
||||||
mousePosition.y = (mouse.clientY - transform.y) / currentRatio;
|
mousePosition.y = (mouse.clientY - transform.y) / currentRatio;
|
||||||
});
|
});
|
||||||
|
@ -286,8 +325,8 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edge scrolling
|
// Edge scrolling
|
||||||
const MOVE_EDGE_X = window.innerWidth * .25;
|
const MOVE_EDGE_X = window.innerWidth * 0.25;
|
||||||
const MOVE_EDGE_Y = window.innerHeight * .25;
|
const MOVE_EDGE_Y = window.innerHeight * 0.25;
|
||||||
const MAX_SPEED = 20;
|
const MAX_SPEED = 20;
|
||||||
|
|
||||||
if (mouse && !panning.value && document.fullscreenElement) {
|
if (mouse && !panning.value && document.fullscreenElement) {
|
||||||
|
@ -295,20 +334,32 @@ export default defineComponent({
|
||||||
let verticalShift: number;
|
let verticalShift: number;
|
||||||
|
|
||||||
const transform = pz.getTransform();
|
const transform = pz.getTransform();
|
||||||
if (mouse.clientX < MOVE_EDGE_X || mouse.clientX > window.innerWidth - MOVE_EDGE_X) {
|
if (
|
||||||
|
mouse.clientX < MOVE_EDGE_X ||
|
||||||
|
mouse.clientX > window.innerWidth - MOVE_EDGE_X
|
||||||
|
) {
|
||||||
const horizontalEdgeDistance =
|
const horizontalEdgeDistance =
|
||||||
(mouse.clientX < window.innerWidth / 2) ? mouse.clientX : (mouse.clientX - window.innerWidth);
|
mouse.clientX < window.innerWidth / 2
|
||||||
const horizontalRatio = (MOVE_EDGE_X - Math.abs(horizontalEdgeDistance)) / MOVE_EDGE_X;
|
? mouse.clientX
|
||||||
|
: mouse.clientX - window.innerWidth;
|
||||||
|
const horizontalRatio =
|
||||||
|
(MOVE_EDGE_X - Math.abs(horizontalEdgeDistance)) / MOVE_EDGE_X;
|
||||||
const direction = mouse.clientX < MOVE_EDGE_X ? 1 : -1;
|
const direction = mouse.clientX < MOVE_EDGE_X ? 1 : -1;
|
||||||
horizontalShift = horizontalRatio * direction * MAX_SPEED;
|
horizontalShift = horizontalRatio * direction * MAX_SPEED;
|
||||||
} else {
|
} else {
|
||||||
horizontalShift = 0;
|
horizontalShift = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mouse.clientY < MOVE_EDGE_Y || mouse.clientY > window.innerHeight - MOVE_EDGE_Y) {
|
if (
|
||||||
|
mouse.clientY < MOVE_EDGE_Y ||
|
||||||
|
mouse.clientY > window.innerHeight - MOVE_EDGE_Y
|
||||||
|
) {
|
||||||
const verticalEdgeDistance =
|
const verticalEdgeDistance =
|
||||||
(mouse.clientY < window.innerHeight / 2) ? mouse.clientY : (mouse.clientY - window.innerHeight);
|
mouse.clientY < window.innerHeight / 2
|
||||||
const verticalRatio = (MOVE_EDGE_Y - Math.abs(verticalEdgeDistance)) / MOVE_EDGE_Y;
|
? mouse.clientY
|
||||||
|
: mouse.clientY - window.innerHeight;
|
||||||
|
const verticalRatio =
|
||||||
|
(MOVE_EDGE_Y - Math.abs(verticalEdgeDistance)) / MOVE_EDGE_Y;
|
||||||
const direction = mouse.clientY < MOVE_EDGE_Y ? 1 : -1;
|
const direction = mouse.clientY < MOVE_EDGE_Y ? 1 : -1;
|
||||||
verticalShift = verticalRatio * direction * MAX_SPEED;
|
verticalShift = verticalRatio * direction * MAX_SPEED;
|
||||||
} else {
|
} else {
|
||||||
|
@ -316,7 +367,10 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (horizontalShift || verticalShift) {
|
if (horizontalShift || verticalShift) {
|
||||||
pz.moveTo(transform!.x + horizontalShift, transform!.y + verticalShift);
|
pz.moveTo(
|
||||||
|
transform!.x + horizontalShift,
|
||||||
|
transform!.y + verticalShift
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +384,6 @@ export default defineComponent({
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
root,
|
root,
|
||||||
loadedPercent,
|
loadedPercent,
|
||||||
|
@ -340,7 +393,7 @@ export default defineComponent({
|
||||||
scrolls,
|
scrolls,
|
||||||
audioAreas,
|
audioAreas,
|
||||||
bbox,
|
bbox,
|
||||||
mousePosition
|
mousePosition,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -348,12 +401,12 @@ export default defineComponent({
|
||||||
function processAnchors(document: XMLDocument): SVGRectElement[] {
|
function processAnchors(document: XMLDocument): SVGRectElement[] {
|
||||||
const result: SVGRectElement[] = [];
|
const result: SVGRectElement[] = [];
|
||||||
Array.from(document.getElementsByTagName("rect"))
|
Array.from(document.getElementsByTagName("rect"))
|
||||||
.filter((el) => el.id.startsWith("anchor"))
|
.filter((el) => el.id.startsWith("anchor"))
|
||||||
.forEach((anchor) => {
|
.forEach((anchor) => {
|
||||||
console.debug(`[SVG/ANCHORS] Found anchor #${anchor.id}.`);
|
console.debug(`[SVG/ANCHORS] Found anchor #${anchor.id}.`);
|
||||||
anchor.classList.add("internal");
|
anchor.classList.add("internal");
|
||||||
result.push(anchor);
|
result.push(anchor);
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,92 +414,124 @@ async function processScrolls(svg: XMLDocument): Promise<VideoScrollDef[]> {
|
||||||
const ratio = (svg as any).clientWidth / (svg as any).viewBox.baseVal.width;
|
const ratio = (svg as any).clientWidth / (svg as any).viewBox.baseVal.width;
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
Array.from(svg.getElementsByTagName("image"))
|
Array.from(svg.getElementsByTagName("image"))
|
||||||
.filter((el) => Array.from(el.children).some((el) => el.tagName == "desc"))
|
.filter((el) =>
|
||||||
.map(async (el) => {
|
Array.from(el.children).some((el) => el.tagName == "desc")
|
||||||
const descNode = Array.from(el.children).find((el) => el.tagName == "desc");
|
)
|
||||||
console.debug(`[SVG/VIDEOSCROLLS] Found video scroll #${el.id}: ${descNode?.textContent}`);
|
.map(async (el) => {
|
||||||
const [directionString, filesURL] = descNode!.textContent!.split("\n");
|
const descNode = Array.from(el.children).find(
|
||||||
|
(el) => el.tagName == "desc"
|
||||||
|
);
|
||||||
|
console.debug(
|
||||||
|
`[SVG/VIDEOSCROLLS] Found video scroll #${el.id}: ${descNode?.textContent}`
|
||||||
|
);
|
||||||
|
const [directionString, filesURL] = descNode!.textContent!.split("\n");
|
||||||
|
|
||||||
const directions: VideoScrollDirection[] = directionString.split(" ").map((direction) => {
|
const directions: VideoScrollDirection[] = directionString
|
||||||
if (!Object.values(VideoScrollDirection).includes(direction as VideoScrollDirection)) {
|
.split(" ")
|
||||||
throw new Error(`Unknown direction string: "${direction}"`);
|
.map((direction) => {
|
||||||
}
|
if (
|
||||||
return direction as VideoScrollDirection;
|
!Object.values(VideoScrollDirection).includes(
|
||||||
});
|
direction as VideoScrollDirection
|
||||||
|
)
|
||||||
|
) {
|
||||||
console.debug(`[SVG/VIDEOSCROLLS] Fetching ${filesURL}...`);
|
throw new Error(`Unknown direction string: "${direction}"`);
|
||||||
const fileFetch = await fetch(`content/${filesURL}`);
|
|
||||||
const preURL = fileFetch.url.replace(/\/files.lst$/, "");
|
|
||||||
const files = (await fileFetch.text()).split("\n").filter(Boolean).map((str) => `${preURL}/${str}`);
|
|
||||||
|
|
||||||
let x = el.x.baseVal.value;
|
|
||||||
let y = el.y.baseVal.value;
|
|
||||||
let w = el.width.baseVal.value;
|
|
||||||
let h = el.height.baseVal.value;
|
|
||||||
let angle = 0;
|
|
||||||
|
|
||||||
const transform = el.attributes.getNamedItem("transform");
|
|
||||||
const rotateResult = /rotate\((-?[0-9.]+)\)/.exec(transform?.value || "");
|
|
||||||
if (rotateResult) {
|
|
||||||
angle = parseFloat(rotateResult[1]);
|
|
||||||
const [ncx, ncy] = rotate(x + w / 2, y + h / 2, 0, 0, angle);
|
|
||||||
x = ncx - w / 2;
|
|
||||||
y = ncy - h / 2;
|
|
||||||
}
|
}
|
||||||
|
return direction as VideoScrollDirection;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
console.debug(`[SVG/VIDEOSCROLLS] Fetching ${filesURL}...`);
|
||||||
top: y * ratio,
|
const fileFetch = await fetch(`content/${filesURL}`);
|
||||||
left: x * ratio,
|
const preURL = fileFetch.url.replace(/\/files.lst$/, "");
|
||||||
angle,
|
const files = (await fileFetch.text())
|
||||||
width: w * ratio,
|
.split("\n")
|
||||||
height: h * ratio,
|
.filter(Boolean)
|
||||||
directions,
|
.map((str) => `${preURL}/${str}`);
|
||||||
files
|
|
||||||
};
|
let x = el.x.baseVal.value;
|
||||||
})
|
let y = el.y.baseVal.value;
|
||||||
|
let w = el.width.baseVal.value;
|
||||||
|
let h = el.height.baseVal.value;
|
||||||
|
let angle = 0;
|
||||||
|
|
||||||
|
const transform = el.attributes.getNamedItem("transform");
|
||||||
|
const rotateResult = /rotate\((-?[0-9.]+)\)/.exec(
|
||||||
|
transform?.value || ""
|
||||||
|
);
|
||||||
|
if (rotateResult) {
|
||||||
|
angle = parseFloat(rotateResult[1]);
|
||||||
|
const [ncx, ncy] = rotate(x + w / 2, y + h / 2, 0, 0, angle);
|
||||||
|
x = ncx - w / 2;
|
||||||
|
y = ncy - h / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: y * ratio,
|
||||||
|
left: x * ratio,
|
||||||
|
angle,
|
||||||
|
width: w * ratio,
|
||||||
|
height: h * ratio,
|
||||||
|
directions,
|
||||||
|
files,
|
||||||
|
};
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function processAudio(svg: XMLDocument): AudioAreaDef[] {
|
function processAudio(svg: XMLDocument): AudioAreaDef[] {
|
||||||
const circles: (SVGCircleElement | SVGEllipseElement)[] = Array.from(svg.getElementsByTagName("circle"));
|
const circles: (SVGCircleElement | SVGEllipseElement)[] = Array.from(
|
||||||
const ellipses: (SVGCircleElement | SVGEllipseElement)[] = Array.from(svg.getElementsByTagName("ellipse"));
|
svg.getElementsByTagName("circle")
|
||||||
return circles.concat(ellipses)
|
);
|
||||||
.filter((el) => Array.from(el.children).some((el) => el.tagName == "desc"))
|
const ellipses: (SVGCircleElement | SVGEllipseElement)[] = Array.from(
|
||||||
.map((el) => {
|
svg.getElementsByTagName("ellipse")
|
||||||
const descNode = Array.from(el.children).find((el) => el.tagName == "desc");
|
);
|
||||||
console.debug(`[SVG/AUDIOAREAS] Found audio area #${el.id}: ${descNode?.textContent}`);
|
return circles
|
||||||
const audioSrc = descNode!.textContent!.trim();
|
.concat(ellipses)
|
||||||
|
.filter((el) => Array.from(el.children).some((el) => el.tagName == "desc"))
|
||||||
|
.map((el) => {
|
||||||
|
const descNode = Array.from(el.children).find(
|
||||||
|
(el) => el.tagName == "desc"
|
||||||
|
);
|
||||||
|
console.debug(
|
||||||
|
`[SVG/AUDIOAREAS] Found audio area #${el.id}: ${descNode?.textContent}`
|
||||||
|
);
|
||||||
|
const audioSrc = descNode!.textContent!.trim();
|
||||||
|
|
||||||
const radius = el.hasAttribute("r") ?
|
const radius = el.hasAttribute("r")
|
||||||
(el as SVGCircleElement).r.baseVal.value :
|
? (el as SVGCircleElement).r.baseVal.value
|
||||||
((el as SVGEllipseElement).rx.baseVal.value + (el as SVGEllipseElement).ry.baseVal.value) / 2;
|
: ((el as SVGEllipseElement).rx.baseVal.value +
|
||||||
|
(el as SVGEllipseElement).ry.baseVal.value) /
|
||||||
|
2;
|
||||||
|
|
||||||
el.classList.add("internal");
|
el.classList.add("internal");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cx: el.cx.baseVal.value,
|
cx: el.cx.baseVal.value,
|
||||||
cy: el.cy.baseVal.value,
|
cy: el.cy.baseVal.value,
|
||||||
radius,
|
radius,
|
||||||
src: `content/${audioSrc}`,
|
src: `content/${audioSrc}`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function processHyperlinks(svg: XMLDocument): { anchor: [string, SVGAElement][], hyper: SVGAElement[] } {
|
function processHyperlinks(
|
||||||
|
svg: XMLDocument
|
||||||
|
): { anchor: [string, SVGAElement][]; hyper: SVGAElement[] } {
|
||||||
const anchor: [string, SVGAElement][] = [];
|
const anchor: [string, SVGAElement][] = [];
|
||||||
const hyper: SVGAElement[] = [];
|
const hyper: SVGAElement[] = [];
|
||||||
Array.from(svg.getElementsByTagName("a")).forEach((el) => {
|
Array.from(svg.getElementsByTagName("a")).forEach((el) => {
|
||||||
if (el.getAttribute("xlink:href")?.startsWith("anchor")) {
|
if (el.getAttribute("xlink:href")?.startsWith("anchor")) {
|
||||||
anchor.push([el.getAttribute("xlink:href") as string, el as unknown as SVGAElement]);
|
anchor.push([
|
||||||
|
el.getAttribute("xlink:href") as string,
|
||||||
|
(el as unknown) as SVGAElement,
|
||||||
|
]);
|
||||||
el.setAttribute("xlink:href", "#");
|
el.setAttribute("xlink:href", "#");
|
||||||
} else {
|
} else {
|
||||||
el.setAttribute("target", "_blank");
|
el.setAttribute("target", "_blank");
|
||||||
hyper.push(el as unknown as SVGAElement);
|
hyper.push((el as unknown) as SVGAElement);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {anchor, hyper};
|
return { anchor, hyper };
|
||||||
}
|
}
|
||||||
|
|
||||||
function processStart(svg: XMLDocument): SVGRectElement | null {
|
function processStart(svg: XMLDocument): SVGRectElement | null {
|
||||||
|
@ -454,9 +539,8 @@ function processStart(svg: XMLDocument): SVGRectElement | null {
|
||||||
if (start) {
|
if (start) {
|
||||||
start.classList.add("internal");
|
start.classList.add("internal");
|
||||||
}
|
}
|
||||||
return start as (SVGRectElement | null);
|
return start as SVGRectElement | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
@ -518,6 +602,6 @@ function processStart(svg: XMLDocument): SVGRectElement | null {
|
||||||
}
|
}
|
||||||
|
|
||||||
.devpanel div span {
|
.devpanel div span {
|
||||||
margin: 0 .5em;
|
margin: 0 0.5em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue