Timeline styling
This commit is contained in:
parent
f8a506bb77
commit
1590fa5615
3 changed files with 147 additions and 20 deletions
111
src/Viz.svelte
111
src/Viz.svelte
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import parsed_transits from "/data/parsed_transits.json";
|
import parsed_transits from "/data/parsed_transits.json";
|
||||||
import { draw, slide, fade } from "svelte/transition";
|
import { draw, slide, fade, scale } from "svelte/transition";
|
||||||
|
import typewriter from "./lib/typewriter";
|
||||||
import { fps } from "@sveu/browser";
|
import { fps } from "@sveu/browser";
|
||||||
import LibrariesSvg from "./LibrariesSvg.svelte";
|
import LibrariesSvg from "./LibrariesSvg.svelte";
|
||||||
|
|
||||||
|
@ -261,30 +262,51 @@
|
||||||
|
|
||||||
<!-- {#if currentScene?.rendered_elements.indexOf("timeline") >= 0} -->
|
<!-- {#if currentScene?.rendered_elements.indexOf("timeline") >= 0} -->
|
||||||
<div id="timeline">
|
<div id="timeline">
|
||||||
{#if $current_item}
|
|
||||||
<div id="current">
|
|
||||||
<h2>{$current_item.title}</h2>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
<div id="events">
|
<div id="events">
|
||||||
{#each $events as e}
|
{#each $events as e}
|
||||||
<div class="entry" in:slide={{ duration: 200 }}>
|
<div class="entry" in:slide={{ duration: 200 }}>
|
||||||
<!-- {m['Title (Complete)']} -->
|
<!-- {m['Title (Complete)']} -->
|
||||||
<span class="date"
|
<span class="date" in:typewriter={{ delay: 1000, speed: 10 }}>
|
||||||
>{e.date.toLocaleDateString("en-UK", {
|
<h3>
|
||||||
weekday: "long",
|
{e.date.toLocaleDateString("en-UK", {
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "long",
|
month: "long",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
})}</span
|
})}
|
||||||
>
|
</h3>
|
||||||
<div class="text">
|
<span class="weekday">
|
||||||
<span class="title">{e.title}</span>
|
{e.date.toLocaleDateString("en-UK", {
|
||||||
<span class="description">{e.description}</span>
|
weekday: "long",
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<span class="time">
|
||||||
|
{e.date.toLocaleTimeString("en-UK", {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<div class="divider">
|
||||||
|
<div
|
||||||
|
class="dot"
|
||||||
|
in:scale={{ delay: 500, duration: 600 }}
|
||||||
|
></div>
|
||||||
|
<div class="connector"><!-- transition in css: --></div>
|
||||||
|
</div>
|
||||||
|
<div class="text" in:typewriter={{ delay: 1500, speed: 10 }}>
|
||||||
|
<h3>{e.title}</h3>
|
||||||
|
<p>{e.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{#if $current_item}
|
||||||
|
<div id="current">
|
||||||
|
<h2>{$current_item.title}</h2>
|
||||||
|
<p>{[$current_item.Date, $current_item.Publisher, $current_item.Place].filter((e) => e).join(', ')}</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<!-- {/if} -->
|
<!-- {/if} -->
|
||||||
|
|
||||||
|
@ -303,6 +325,58 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
|
|
||||||
|
#events{
|
||||||
|
position: relative;
|
||||||
|
// max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
.date {
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
--divider-width: 15px;
|
||||||
|
--connector-width: 2px;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
transform: translateY(calc(20px));
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
height: var(--divider-width);
|
||||||
|
width: var(--divider-width);
|
||||||
|
background-color: rgb(79 70 229);
|
||||||
|
border-radius: calc(var(--divider-width) / 2);
|
||||||
|
// border: solid 5px black;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
.connector {
|
||||||
|
flex-grow: 1;
|
||||||
|
border-left: solid var(--connector-width) rgb(203 213 225);
|
||||||
|
transition: flex-grow 1s ease-in-out;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-left: calc(
|
||||||
|
(var(--divider-width) - var(--connector-width)) / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:last-child .divider .connector {
|
||||||
|
flex-grow: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
h3 {
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#fps {
|
#fps {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -355,7 +429,4 @@
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
#events {
|
|
||||||
max-height: 50px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -22,6 +22,7 @@ export type Item = {
|
||||||
_original: Object
|
_original: Object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function importItem(orig: Object, locations: Map<string, Location>): Item {
|
export function importItem(orig: Object, locations: Map<string, Location>): Item {
|
||||||
// Barcode
|
// Barcode
|
||||||
// Request Date
|
// Request Date
|
||||||
|
|
55
src/lib/typewriter.js
Normal file
55
src/lib/typewriter.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// Written by Weiming Wu
|
||||||
|
// https://weimingwu.medium.com/a-better-typewriter-transition-in-svelte-528e9610ec2e
|
||||||
|
|
||||||
|
export default function typewriter(node, { delay = 0, speed = 50 }) {
|
||||||
|
const textNodes = getAllTextNodes(node);
|
||||||
|
if (!textNodes.length) {
|
||||||
|
throw new Error(`This transition only works on elements with text nodes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalLength = 0;
|
||||||
|
const ranges = textNodes.map(textNode => {
|
||||||
|
const range = [totalLength, totalLength + textNode.textContent.length];
|
||||||
|
totalLength += textNode.textContent.length;
|
||||||
|
const text = textNode.textContent;
|
||||||
|
textNode.textContent = '';
|
||||||
|
return { textNode, range, text };
|
||||||
|
});
|
||||||
|
|
||||||
|
let currentRangeIndex = 0;
|
||||||
|
function getCurrentRange(i) {
|
||||||
|
while (ranges[currentRangeIndex].range[1] < i && currentRangeIndex < ranges.length) {
|
||||||
|
const { textNode, text } = ranges[currentRangeIndex];
|
||||||
|
textNode.textContent = text; // finish typing up the last node
|
||||||
|
currentRangeIndex++;
|
||||||
|
}
|
||||||
|
return ranges[currentRangeIndex];
|
||||||
|
}
|
||||||
|
const duration = totalLength * speed;
|
||||||
|
|
||||||
|
return {
|
||||||
|
delay,
|
||||||
|
duration,
|
||||||
|
tick: t => {
|
||||||
|
const progress = ~~(totalLength * t);
|
||||||
|
const { textNode, range, text } = getCurrentRange(progress);
|
||||||
|
const [start, end] = range;
|
||||||
|
const textLength = ((progress - start) / (end - start)) * text.length;
|
||||||
|
textNode.textContent = text.slice(0, textLength);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getAllTextNodes(node) {
|
||||||
|
if (node.nodeType === 3) {
|
||||||
|
return [node];
|
||||||
|
} else if (node.hasChildNodes()) {
|
||||||
|
let list = [];
|
||||||
|
for (let child of node.childNodes) {
|
||||||
|
getAllTextNodes(child).forEach(textNode => list.push(textNode));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
Loading…
Reference in a new issue