kcl-digital-humanities-garden/assets/js/scripts.js

413 lines
12 KiB
JavaScript
Raw Normal View History

2021-03-25 02:18:10 +00:00
// Header interaction
const headerEle = document.querySelector('#header')
let headerElePosStore = 0
const debounce = (fn) => {
let frame
return (...params) => {
if (frame) {
window.cancelAnimationFrame(frame)
}
frame = window.requestAnimationFrame(() => {
fn(...params)
})
}
}
const storeScroll = (event) => {
const bodyRect = document.body.getBoundingClientRect()
if (bodyRect.top < headerElePosStore &&
-headerEle.offsetHeight > bodyRect.top) {
headerEle.classList.add('header-inactive')
} else {
headerEle.classList.remove('header-inactive')
}
headerElePosStore = bodyRect.top
}
document.addEventListener('scroll', debounce(storeScroll), { passive: true })
2021-03-12 01:58:04 +00:00
// Link preview
let opacityTimeout
let contentTimeout
const transitionDurationMs = 100
const tooltipWrapper = document.getElementById('tooltip-wrapper')
const tooltipContent = document.getElementById('tooltip-content')
2021-03-28 16:30:27 +00:00
const tooltipSource = document.getElementById('tooltip-source')
2021-03-12 01:58:04 +00:00
2021-03-25 02:18:10 +00:00
const hideTooltip = () => {
opacityTimeout = setTimeout(() => {
2021-03-12 01:58:04 +00:00
tooltipWrapper.style.opacity = 0
2021-03-25 02:18:10 +00:00
contentTimeout = setTimeout(() => {
2021-03-12 01:58:04 +00:00
tooltipContent.innerHTML = ''
tooltipWrapper.style.display = 'none'
}, transitionDurationMs + 1)
}, transitionDurationMs)
}
2021-03-25 02:18:10 +00:00
const showTooltip = (event) => {
2021-03-12 01:58:04 +00:00
const elem = event.target
const elemProps = elem.getClientRects()[elem.getClientRects().length - 1]
const top = window.pageYOffset || document.documentElement.scrollTop
2021-05-30 19:17:00 +00:00
if (event.target.host === window.location.host && event.target.getAttribute('href').charAt(0) !== '#') {
2021-03-15 22:35:31 +00:00
window.fetch(event.target.href)
.then(response => response.text())
.then(data => {
const parser = new window.DOMParser()
const doc = parser.parseFromString(data, 'text/html')
let tooltipContentHtml = ''
tooltipContentHtml += '<div class="b">' + doc.querySelector('h1').innerHTML + '</div>'
tooltipContentHtml += doc.querySelector('.note-contents').innerHTML
tooltipContent.innerHTML = tooltipContentHtml
2021-03-28 16:30:27 +00:00
const pathIndex = event.target.href.split('/')
tooltipSource.innerHTML = `/${pathIndex[pathIndex.length - 1]}`
2021-03-15 22:35:31 +00:00
tooltipWrapper.style.display = 'block'
2021-03-25 02:18:10 +00:00
setTimeout(() => {
2021-03-15 22:35:31 +00:00
tooltipWrapper.style.opacity = 1
}, 1)
})
2021-03-12 01:58:04 +00:00
tooltipWrapper.style.left = elemProps.left - (tooltipWrapper.offsetWidth / 2) + (elemProps.width / 2) + 'px'
if ((window.innerHeight - elemProps.top) < (tooltipWrapper.offsetHeight)) {
tooltipWrapper.style.top = elemProps.top + top - tooltipWrapper.offsetHeight - 10 + 'px'
} else if ((window.innerHeight - elemProps.top) > (tooltipWrapper.offsetHeight)) {
tooltipWrapper.style.top = elemProps.top + top + 35 + 'px'
}
if ((elemProps.left + (elemProps.width / 2)) < (tooltipWrapper.offsetWidth / 2)) {
tooltipWrapper.style.left = '10px'
} else if ((document.body.clientWidth - elemProps.left - (elemProps.width / 2)) < (tooltipWrapper.offsetWidth / 2)) {
tooltipWrapper.style.left = document.body.clientWidth - tooltipWrapper.offsetWidth - 20 + 'px'
}
}
}
2021-03-25 02:18:10 +00:00
const setupListeners = (linkElement) => {
2021-05-21 18:34:56 +00:00
linkElement.addEventListener('mouseleave', () => {
2021-03-12 01:58:04 +00:00
hideTooltip()
})
2021-05-21 18:34:56 +00:00
tooltipWrapper.addEventListener('mouseleave', () => {
2021-03-12 01:58:04 +00:00
hideTooltip()
})
2021-03-25 02:18:10 +00:00
linkElement.addEventListener('mouseenter', event => {
2021-03-12 01:58:04 +00:00
clearTimeout(opacityTimeout)
clearTimeout(contentTimeout)
showTooltip(event)
})
2021-05-21 18:34:56 +00:00
tooltipWrapper.addEventListener('mouseenter', () => {
2021-03-12 01:58:04 +00:00
clearTimeout(opacityTimeout)
clearTimeout(contentTimeout)
})
}
2021-03-15 19:49:03 +00:00
document.querySelectorAll('#notes-entry-container a').forEach(setupListeners)
2021-03-12 03:07:34 +00:00
2021-03-28 23:11:31 +00:00
// Random note index
if (typeof window.graphDataIndex !== 'undefined') {
const indexNodes = window.graphDataIndex.nodes
const randomNode = indexNodes[Math.floor(Math.random() * indexNodes.length)]
2021-04-03 22:32:00 +00:00
let counter = 0
setInterval(() => {
counter += 1
document.querySelector('.loading').innerHTML += '.'
2021-04-18 15:22:31 +00:00
if (counter === 5) {
2021-04-03 22:32:00 +00:00
document.querySelector('.loading').innerHTML = 'Loading a note'
counter = 0
}
}, 500)
setInterval(() => {
const randomNodeLoader = indexNodes[Math.floor(Math.random() * indexNodes.length)]
2021-04-18 15:22:31 +00:00
const randomNodeTemplate = randomNodeLoader.path
2021-04-03 22:32:00 +00:00
document.querySelector('.rand-notes').innerHTML += randomNodeTemplate
2021-04-18 15:22:31 +00:00
if (counter === 4) {
2021-04-03 22:32:00 +00:00
document.querySelector('.rand-notes').innerHTML = ''
}
2021-05-02 19:23:23 +00:00
}, 50)
2021-04-03 22:32:00 +00:00
setTimeout(() => {
window.location = randomNode.path
}, 1500)
2021-03-28 23:11:31 +00:00
}
2021-03-12 03:07:34 +00:00
// Notes graph
2021-05-09 15:54:11 +00:00
const d3 = window.d3
2021-03-16 00:06:04 +00:00
if (typeof window.graphData !== 'undefined') {
2021-03-12 03:07:34 +00:00
const MINIMAL_NODE_SIZE = 10
2021-04-25 18:30:58 +00:00
const MAX_NODE_SIZE = 16
2021-03-12 03:07:34 +00:00
const STROKE = 1
2021-03-15 22:22:28 +00:00
const FONT_SIZE = 12
2021-03-12 03:07:34 +00:00
const TICKS = 200
2021-04-25 18:30:58 +00:00
const FONT_BASELINE = 42
2021-03-12 03:07:34 +00:00
const MAX_LABEL_LENGTH = 50
2021-03-16 00:06:04 +00:00
const nodesData = window.graphData.nodes
const linksData = window.graphData.edges
2021-03-12 03:07:34 +00:00
const nodeSize = {}
const updateNodeSize = () => {
nodesData.forEach((el) => {
let weight =
2021-04-25 18:30:58 +00:00
8 *
2021-03-12 03:07:34 +00:00
Math.sqrt(
2021-04-25 18:30:58 +00:00
linksData.filter((l) => l.source.id === el.id || l.target.id === el.id)
2021-03-12 03:07:34 +00:00
.length + 1
)
if (weight < MINIMAL_NODE_SIZE) {
weight = MINIMAL_NODE_SIZE
} else if (weight > MAX_NODE_SIZE) {
weight = MAX_NODE_SIZE
}
nodeSize[el.id] = weight
})
}
const onClick = (d) => {
window.location = d3.select(d.target).data()[0].path
}
2021-03-25 02:18:10 +00:00
const onMouseover = (d) => {
2021-03-12 03:07:34 +00:00
const relatedNodesSet = new Set()
2021-03-15 22:22:28 +00:00
const destinationID = d3.select(d.target).data()[0].id
2021-03-12 03:07:34 +00:00
linksData
2021-03-15 22:22:28 +00:00
.filter((n) => n.target.id === destinationID || n.source.id === destinationID)
2021-03-12 03:07:34 +00:00
.forEach((n) => {
relatedNodesSet.add(n.target.id)
relatedNodesSet.add(n.source.id)
})
node.attr('class', (nodeD) => {
2021-03-15 22:22:28 +00:00
if (nodeD.id !== destinationID && !relatedNodesSet.has(nodeD.id)) {
2021-03-12 03:07:34 +00:00
return 'inactive'
}
2021-05-21 18:34:56 +00:00
return 'active'
2021-03-12 03:07:34 +00:00
})
link.attr('class', (linkD) => {
2021-03-15 22:22:28 +00:00
if (linkD.source.id !== destinationID && linkD.target.id !== destinationID) {
2021-03-12 03:07:34 +00:00
return 'inactive'
}
2021-03-25 02:18:10 +00:00
return 'active'
2021-03-12 03:07:34 +00:00
})
text.attr('class', (textD) => {
2021-03-15 22:22:28 +00:00
if (textD.id !== destinationID && !relatedNodesSet.has(textD.id)) {
2021-03-12 03:07:34 +00:00
return 'inactive'
}
2021-05-21 18:34:56 +00:00
return 'active'
2021-03-12 03:07:34 +00:00
})
}
2021-03-25 02:18:10 +00:00
const onMouseout = (d) => {
2021-03-12 03:07:34 +00:00
node.attr('class', '')
link.attr('class', '')
text.attr('class', '')
link.attr('stroke-width', STROKE)
}
const graphWrapper = document.getElementById('graph-wrapper')
const element = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
element.setAttribute('width', graphWrapper.getBoundingClientRect().width)
2021-05-18 01:35:39 +00:00
element.setAttribute('height', graphWrapper.getBoundingClientRect().height)
2021-03-25 02:18:10 +00:00
element.classList.add('grab', 'grabbing')
2021-03-12 03:07:34 +00:00
graphWrapper.appendChild(element)
const reportWindowSize = () => {
element.setAttribute('width', window.innerWidth)
element.setAttribute('height', window.innerHeight)
}
window.onresize = reportWindowSize
2021-04-18 20:25:46 +00:00
const svg = d3.select('#graph-wrapper svg')
2021-03-12 03:07:34 +00:00
const width = Number(svg.attr('width'))
const height = Number(svg.attr('height'))
const simulation = d3
.forceSimulation(nodesData)
.force('forceX', d3.forceX().x(width / 2))
.force('forceY', d3.forceY().y(height / 2))
.force('charge', d3.forceManyBody())
.force(
'link',
d3
.forceLink(linksData)
.id((d) => d.id)
2021-03-15 22:22:28 +00:00
.distance(100)
2021-03-12 03:07:34 +00:00
)
.force('center', d3.forceCenter(width / 2, height / 2))
2021-04-18 15:29:36 +00:00
.force('collision', d3.forceCollide().radius(90))
2021-03-12 03:07:34 +00:00
.stop()
const g = svg.append('g')
let link = g.append('g').attr('class', 'links').selectAll('.link')
let node = g.append('g').attr('class', 'nodes').selectAll('.node')
2021-04-19 00:34:38 +00:00
let status = g.append('g').attr('class', 'status').selectAll('.status')
2021-03-12 03:07:34 +00:00
let text = g.append('g').attr('class', 'text').selectAll('.text')
const resize = (event) => {
if (event) {
const scale = event.transform
2021-04-03 22:32:00 +00:00
if (!activeNode.empty()) {
const newX = parseFloat(activeNode.attr('cx')) - scale.x
const newY = parseFloat(activeNode.attr('cy')) - scale.y
g.attr('transform', 'translate(' + (width / 2 - newX) + ',' + (height / 2 - newY) + ')')
} else {
g.attr('transform', scale)
}
2021-03-12 03:07:34 +00:00
}
}
const ticked = () => {
node.attr('cx', (d) => d.x).attr('cy', (d) => d.y)
2021-04-25 18:30:58 +00:00
status.attr('x', (d) => d.x - 2).attr('y', (d) => d.y)
2021-03-12 03:07:34 +00:00
text
.attr('x', (d) => d.x)
2021-04-03 22:32:00 +00:00
.attr('y', (d) => d.y - (FONT_BASELINE - nodeSize[d.id]))
2021-03-12 03:07:34 +00:00
link
2021-03-15 22:22:28 +00:00
.attr('d', (d) => {
const dx = d.target.x - d.source.x
const dy = d.target.y - d.source.y
const dr = Math.sqrt(dx * dx + dy * dy)
return 'M' +
d.source.x + ',' +
d.source.y + 'A' +
dr + ',' + dr + ' 0 0,1 ' +
d.target.x + ',' +
d.target.y
})
2021-03-12 03:07:34 +00:00
}
const restart = () => {
updateNodeSize()
2021-04-19 00:34:38 +00:00
status = status.data(nodesData, (d) => d.id).enter().append('text')
status
.text((d) => d.status)
.attr('font-size', '18px')
.attr('text-anchor', 'middle')
2021-04-26 00:38:08 +00:00
.attr('dominant-baseline', 'central')
2021-04-19 00:34:38 +00:00
.merge(status)
node = node.data(nodesData, (d) => d.id).enter().append('circle')
node
2021-03-12 03:07:34 +00:00
.attr('r', (d) => {
return nodeSize[d.id]
})
.on('click', onClick)
.on('mouseover', onMouseover)
.on('mouseout', onMouseout)
.merge(node)
2021-04-19 00:34:38 +00:00
link = link
.data(linksData, (d) => `${d.source.id}-${d.target.id}`)
.enter()
.append('path')
.attr('stroke-width', STROKE)
.merge(link)
2021-03-12 03:07:34 +00:00
2021-04-19 00:34:38 +00:00
text = text.data(nodesData, (d) => d.label).enter().append('text')
2021-03-12 03:07:34 +00:00
text = text
.text((d) => shorten(d.label.replace(/_*/g, ''), MAX_LABEL_LENGTH))
.attr('font-size', `${FONT_SIZE}px`)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'central')
.on('click', onClick)
.on('mouseover', onMouseover)
.on('mouseout', onMouseout)
.merge(text)
node.attr('active', (d) => isCurrentPath(d.path) ? true : null)
2021-05-30 19:17:00 +00:00
node.attr('long-form', (d) => d.type === 'long-form' ? true : null)
2021-03-12 03:07:34 +00:00
text.attr('active', (d) => isCurrentPath(d.path) ? true : null)
simulation.nodes(nodesData)
simulation.force('link').links(linksData)
simulation.alpha(1).restart()
simulation.stop()
for (let i = 0; i < TICKS; i++) {
simulation.tick()
}
ticked()
}
2021-03-25 02:18:10 +00:00
const zoomHandler = d3.zoom().scaleExtent([1, 1]).on('zoom', resize)
2021-03-12 03:07:34 +00:00
zoomHandler(svg)
restart()
2021-04-03 22:32:00 +00:00
const activeNode = svg.select('circle[active]')
if (!activeNode.empty()) {
const centerNode = (xx, yy) => {
g.attr('transform', 'translate(' + (width / 2 - xx) + ',' + (height / 2 - yy) + ')')
}
centerNode(activeNode.attr('cx'), activeNode.attr('cy'))
}
2021-03-12 03:07:34 +00:00
function isCurrentPath (notePath) {
2021-04-03 22:32:00 +00:00
return window.location.pathname === notePath
2021-03-12 03:07:34 +00:00
}
function shorten (str, maxLen, separator = ' ') {
if (str.length <= maxLen) return str
return str.substr(0, str.lastIndexOf(separator, maxLen)) + '...'
}
}
2021-04-04 00:24:54 +00:00
// Note expander
const noteExpander = document.querySelector('#note-expand')
2021-04-11 18:54:57 +00:00
const noteContainer = document.querySelector('#note-container')
2021-04-04 00:24:54 +00:00
if (noteExpander) {
2021-04-11 18:54:57 +00:00
if (window.localStorage.getItem('noteExpanded') === 'true') {
noteExpander.classList.add('rotate-180')
noteContainer.classList.add('w-two-thirds')
}
2021-04-04 00:24:54 +00:00
noteExpander.addEventListener('click', (event) => {
2021-04-11 18:54:57 +00:00
noteContainer.classList.toggle('w-two-thirds')
2021-04-04 00:24:54 +00:00
event.target.classList.toggle('rotate-180')
2021-04-11 18:54:57 +00:00
if (window.localStorage.getItem('noteExpanded') === 'true') {
window.localStorage.setItem('noteExpanded', 'false')
} else {
window.localStorage.setItem('noteExpanded', 'true')
}
2021-04-04 00:24:54 +00:00
})
}
2021-04-18 17:33:57 +00:00
// Weather
const capitalize = str => `${str.charAt(0).toUpperCase()}${str.slice(1)}`
const conditionsContainer = document.getElementById('gardenConditions')
2021-04-20 01:07:34 +00:00
const openWeatherUrl = 'https://garden-weather-api.vercel.app/weather/toronto'
2021-04-18 17:33:57 +00:00
2021-05-09 15:54:11 +00:00
async function fetchWeather () {
const response = await window.fetch(openWeatherUrl)
const weatherData = await response.json()
2021-05-09 15:49:58 +00:00
conditionsContainer.innerHTML = `${capitalize(weatherData.weather[0].description)}, ${Math.round(weatherData.main.temp)}°C`
conditionsContainer.classList.remove('dn')
}
2021-04-18 17:33:57 +00:00
if (conditionsContainer) {
2021-05-09 15:49:58 +00:00
fetchWeather().catch(e => {
2021-05-09 15:54:11 +00:00
console.log('There has been a problem with getting the weather ' + e.message)
})
2021-04-18 17:33:57 +00:00
}