Ruben Beltran del Rio 2026-01-22 12:59:10 +01:00
parent c4bf944f93
commit 87d65c599e
No known key found for this signature in database
4 changed files with 16528 additions and 2130 deletions

View File

@ -1,4 +1,7 @@
* { margin: 0; padding: 0 } * {
margin: 0;
padding: 0;
}
body { body {
overflow: hidden; overflow: hidden;
@ -16,7 +19,7 @@ iframe {
/* The bed of the Machine */ /* The bed of the Machine */
.machine { .machine {
background: url('/images/Metal Panel.svg'); background: url("/images/Metal Panel.svg");
background-repeat: repeat-x; background-repeat: repeat-x;
bottom: 0; bottom: 0;
height: 4rem; height: 4rem;
@ -29,10 +32,12 @@ iframe {
label { label {
background-color: #202123; background-color: #202123;
text-shadow: 1px -1px 0px #42464dFF, -1px 1px 0px #ffffff1A; text-shadow:
1px -1px 0px #42464dff,
-1px 1px 0px #ffffff1a;
color: #eeeeee; color: #eeeeee;
display: block; display: block;
font: 0.625rem 'Arial Rounded MT Bold'; font: 0.625rem "Arial Rounded MT Bold";
padding: 0.0625rem 0.3125rem; padding: 0.0625rem 0.3125rem;
text-transform: uppercase; text-transform: uppercase;
transform: rotate(-5deg); transform: rotate(-5deg);
@ -47,7 +52,7 @@ label {
top: 0; top: 0;
.grater-bed { .grater-bed {
background: url('/images/Grater Bed.svg'); background: url("/images/Grater Bed.svg");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: bottom; background-position: bottom;
position: absolute; position: absolute;
@ -56,9 +61,9 @@ label {
width: 8.5rem; width: 8.5rem;
&::before { &::before {
background: url('/images/Sausage.svg'); background: url("/images/Sausage.svg");
background-repeat: no-repeat; background-repeat: no-repeat;
content: ' '; content: " ";
display: block; display: block;
height: 3em; height: 3em;
opacity: 0; opacity: 0;
@ -72,7 +77,7 @@ label {
} }
.grater-blade { .grater-blade {
background: url('/images/Grater Blade.svg'); background: url("/images/Grater Blade.svg");
height: 0.625rem; height: 0.625rem;
left: var(--graterTravel); left: var(--graterTravel);
position: absolute; position: absolute;
@ -88,7 +93,7 @@ label {
width: 6.125rem; width: 6.125rem;
.stem { .stem {
background: url('/images/Lever Handle.svg'); background: url("/images/Lever Handle.svg");
height: 3.4375rem; height: 3.4375rem;
position: absolute; position: absolute;
width: 3.0625rem; width: 3.0625rem;
@ -107,9 +112,9 @@ label {
} }
&::before { &::before {
background: url('/images/Lever Back.svg'); background: url("/images/Lever Back.svg");
bottom: -0.5rem; bottom: -0.5rem;
content: ''; content: "";
height: 1.375rem; height: 1.375rem;
left: 2.25rem; left: 2.25rem;
position: absolute; position: absolute;
@ -117,9 +122,9 @@ label {
} }
&::after { &::after {
background: url('/images/Lever Front.svg'); background: url("/images/Lever Front.svg");
bottom: -0.5rem; bottom: -0.5rem;
content: ''; content: "";
height: 1.0625rem; height: 1.0625rem;
left: 2.40625rem; left: 2.40625rem;
position: absolute; position: absolute;
@ -149,22 +154,22 @@ label {
} }
.ready-to-grate { .ready-to-grate {
background: url('/images/Red Unlit Light.svg'); background: url("/images/Red Unlit Light.svg");
} }
.ready-to-load { .ready-to-load {
background: url('/images/Green Unlit Light.svg'); background: url("/images/Green Unlit Light.svg");
} }
[data-current-state="readyToGrate"] .ready-to-grate { [data-current-state="readyToGrate"] .ready-to-grate {
background: url('/images/Red Lit Light.svg'); background: url("/images/Red Lit Light.svg");
} }
[data-current-state="readyToLoad"] .ready-to-load { [data-current-state="readyToLoad"] .ready-to-load {
background: url('/images/Green Lit Light.svg'); background: url("/images/Green Lit Light.svg");
} }
/* The Link Loader Wheel and Chain Mechanism */ /* The Link Loader Wheel and Chain Mechanism */
.chain { .chain {
background: url('/images/Chain Link.svg'); background: url("/images/Chain Link.svg");
background-position-y: var(--chainOffset); background-position-y: var(--chainOffset);
background-repeat: repeat-y; background-repeat: repeat-y;
height: 100vh; height: 100vh;
@ -184,7 +189,7 @@ label {
width: 6rem; width: 6rem;
.body { .body {
background: url('/images/Wheel.svg'); background: url("/images/Wheel.svg");
height: 6rem; height: 6rem;
left: 0; left: 0;
position: absolute; position: absolute;
@ -212,27 +217,28 @@ label {
/* Make a Sausage */ /* Make a Sausage */
.sausage { .sausage {
background: url('/images/Sausage.svg'); align-items: center;
background: url("/images/Sausage.svg");
background-position: top; background-position: top;
background-repeat: no-repeat; background-repeat: no-repeat;
color: #fff; color: #fff;
display: block; display: flex;
font: 0.625rem 'Arial Rounded MT Bold'; font: 0.625rem "Arial Rounded MT Bold";
height: calc(3rem - var(--grateLevel)); height: calc(3rem - var(--grateLevel));
justify-content: center;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
top: var(--grateLevel); top: var(--grateLevel);
left: 0; left: 0;
text-align: center; text-align: center;
text-overflow: ellipsis;
text-transform: uppercase; text-transform: uppercase;
width: 8.125rem; width: 8.125rem;
white-space: nowrap;
z-index: 0; z-index: 0;
display: flex;
align-items: center;
justify-content: center;
} }
/* Util */ /* Util */
.no-select { .no-select {
user-select: none user-select: none;
} }

17135
example.html

File diff suppressed because one or more lines are too long

View File

@ -1,23 +1,22 @@
<!DOCTYPE HTML> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="author" content="Rubén Beltrán del Río"> <meta name="author" content="Rubén Beltrán del Río" />
<meta name="description" content="{{ description }}"> <meta name="description" content="{{ description }}" />
<meta name="fediverse:creator" content="@ruben@friendship.quest" /> <meta name="fediverse:creator" content="@ruben@friendship.quest" />
<title>Internet Machine.</title> <title>Internet Machine.</title>
<link rel="stylesheet" type="text/css" href="/css/style.css"> <link rel="stylesheet" type="text/css" href="/css/style.css" />
<link rel="author" href="humans.txt"> <link rel="author" href="humans.txt" />
<script type="module" src="/js/machine.js"></script> <script type="module" src="/js/machine.js"></script>
</head> </head>
<body> <body>
<main class="web-content"> <main class="web-content"></main>
</main>
<nav class="machine"> <nav class="machine">
<section class="link-grater"> <section class="link-grater">
<article class="grater-bed"> <article class="grater-bed">
@ -42,7 +41,7 @@
</article> </article>
</article> </article>
<label>URL Loader</label> <label>URL Loader</label>
</section </section>
</nav> </nav>
</body> </body>
</html> </html>

View File

@ -5,90 +5,147 @@ const kDimensions = {
sausageHeight: 48, sausageHeight: 48,
}; };
const kState = { const kState = {
waiting: 'waiting', waiting: "waiting",
readyToGrate: 'readyToGrate', readyToGrate: "readyToGrate",
readyToLoad: 'readyToLoad' readyToLoad: "readyToLoad",
}; };
const kAction = { const kAction = {
completeLoading: Symbol('completeLoading'), completeLoading: Symbol("completeLoading"),
startPullingLever: Symbol('startPullingLever'), startPullingLever: Symbol("startPullingLever"),
stopPullingLever: Symbol('stopPullingLever'), stopPullingLever: Symbol("stopPullingLever"),
pullLever: Symbol('pullLever'), pullLever: Symbol("pullLever"),
startTurningWheel: Symbol('startTurningWheel'), startTurningWheel: Symbol("startTurningWheel"),
stopTurningWheel: Symbol('stopTurningWheel'), stopTurningWheel: Symbol("stopTurningWheel"),
turnWheel: Symbol('turnWheel'), turnWheel: Symbol("turnWheel"),
setIsWaitingForFrame: Symbol('setIsWaitingForFrame'), setIsWaitingForFrame: Symbol("setIsWaitingForFrame"),
createSausage: Symbol('createSausage'), createSausage: Symbol("createSausage"),
completeGrating: Symbol('completeGrating'), completeGrating: Symbol("completeGrating"),
}; };
const kRotationDirection = { const kRotationDirection = {
clockwise: Symbol('clockwise'), clockwise: Symbol("clockwise"),
counterClockwise: Symbol('counterClockwise'), counterClockwise: Symbol("counterClockwise"),
}; };
const kPullDirection = { const kPullDirection = {
right: Symbol('right'), right: Symbol("right"),
left: Symbol('left'), left: Symbol("left"),
}; };
// Configuration /////////////////////////////////////////////////////////////// // Configuration ///////////////////////////////////////////////////////////////
const configuration = { const configuration = {
wheelStrength: 1, wheelStrength: 1,
maxLoad: 100, maxLoad: 100,
leverTravel: 80.72, leverTravel: 80.72,
graterTravel: 69, graterTravel: 69,
chainSpeed: 0.5, chainSpeed: 0.5,
grateStrength: 0.01, grateStrength: 0.01,
defaultUrl: '/example.html' defaultUrl: "/example.html",
}; };
// App State /////////////////////////////////////////////////////////////////// // App State ///////////////////////////////////////////////////////////////////
/** /**
* The main app state * The main app state
*/ */
let appState = null; let appState = null;
/** /**
* Functions to handle side effects * Functions to handle side effects: Creation or removal of HTML elements,
* actions that occur as a result of a change in state.
*/
const middleware = [
/**
* Makes text unselectable while we move the lever or wheel.
*/ */
const middleware = [
function disableSelect(_, action) { function disableSelect(_, action) {
if (action === kAction.startTurningWheel || action === kAction.startPullingLever) { if (
document.querySelector('.machine')?.classList.add('no-select'); action === kAction.startTurningWheel ||
action === kAction.startPullingLever
) {
document.querySelector(".machine")?.classList.add("no-select");
} }
}, },
/**
* Makes text selectable when we're done moving the lever or wheel.
*/
function enableSelect(_, action) { function enableSelect(_, action) {
if (action === kAction.stopTurningWheel || action === kAction.stopPullingLever) { if (
document.querySelector('.machine')?.classList.remove('no-select'); action === kAction.stopTurningWheel ||
action === kAction.stopPullingLever
) {
document.querySelector(".machine")?.classList.remove("no-select");
} }
}, },
/**
* Cleans up the old iFrames when we load the new one, and triggers a complete.
*/
function completeLoading(state, action) { function completeLoading(state, action) {
if (action === kAction.turnWheel && state.currentState === kState.readyToLoad && parseFloat(state.load) >= configuration.maxLoad) { if (
action === kAction.turnWheel &&
state.currentState === kState.readyToLoad &&
parseFloat(state.load) >= configuration.maxLoad
) {
const iframes = $iframeContainer.querySelectorAll("iframe");
for (const iframe of iframes) {
if (iframe !== appState.currentIframe) {
$iframeContainer.removeChild(iframe);
}
}
send(kAction.completeLoading); send(kAction.completeLoading);
} }
}, },
/**
* Sets the individual load state to the current iframe.
*/
function updateIframe(state, action) { function updateIframe(state, action) {
if (action === kAction.turnWheel && state.currentState === kState.readyToLoad && parseFloat(state.load) <= configuration.maxLoad) { if (
state.currentIframe?.style.setProperty('--loadProgress', `${100 - state.load}vh`); action === kAction.turnWheel &&
state.currentState === kState.readyToLoad &&
parseFloat(state.load) <= configuration.maxLoad
) {
state.currentIframe?.style.setProperty(
"--loadProgress",
`${100 - state.load}vh`,
);
} }
}, },
/**
* Cleans up the sausage and loads the iframe once the grating is complete.
*/
function completeGrating(state, action) { function completeGrating(state, action) {
if (action === kAction.pullLever && state.currentState === kState.readyToGrate && parseFloat(state.grateLevel) >= kDimensions.sausageHeight) { if (
action === kAction.pullLever &&
state.currentState === kState.readyToGrate &&
parseFloat(state.grateLevel) >= kDimensions.sausageHeight
) {
removeSausages(); removeSausages();
const $iframe = loadIframe(state.currentUrl, 0); const $iframe = loadIframe(state.currentUrl, 0);
send(kAction.completeGrating, { iframe: $iframe }); send(kAction.completeGrating, { iframe: $iframe });
} }
}, },
/**
* Creates the sausage element if we are at the right state.
*/
function createSausageIfWaiting(state, action, data) { function createSausageIfWaiting(state, action, data) {
if (action === kAction.createSausage && state.currentState === kState.waiting) { if (
action === kAction.createSausage &&
state.currentState === kState.waiting
) {
createSausage(data.href, data.label); createSausage(data.href, data.label);
} }
}, },
function log(state, action, data) { ];
//console.log(state.load);
}
];
const $stateContainer = document.querySelector('.machine'); /**
* We store the state as CSS variables and data attributes in an element
* declared as the $stateContainer. UI updates as a result of this. We
* try to avoid manipulating the elements with JS, except for creating and
* removing elements.
*/
const $stateContainer = document.querySelector(".machine");
const updateState = function updateState(state) { const updateState = function updateState(state) {
for (const [key, value] of Object.entries(state)) { for (const [key, value] of Object.entries(state)) {
$stateContainer.style.setProperty(`--${key}`, value); $stateContainer.style.setProperty(`--${key}`, value);
@ -97,6 +154,9 @@ const updateState = function updateState(state) {
appState = state; appState = state;
}; };
/**
* We modify a copy of the state and return it.
*/
const reduce = function reduce(stateReference, action, data) { const reduce = function reduce(stateReference, action, data) {
let state = Object.assign({}, stateReference); let state = Object.assign({}, stateReference);
@ -105,10 +165,18 @@ const reduce = function reduce(stateReference, action, data) {
state.wheelRotation = `${data.angle}rad`; state.wheelRotation = `${data.angle}rad`;
if (state.currentState === kState.readyToLoad) { if (state.currentState === kState.readyToLoad) {
const sign = data.direction === kRotationDirection.clockwise ? 1 : -1; const sign = data.direction === kRotationDirection.clockwise ? 1 : -1;
const load = Math.max(Math.min(state.load - sign * data.magnitude * configuration.wheelStrength, configuration.maxLoad), 0); const load = Math.max(
Math.min(
state.load - sign * data.magnitude * configuration.wheelStrength,
configuration.maxLoad,
),
0,
);
state.load = load; state.load = load;
if (load < configuration.maxLoad && load > 0) { if (load < configuration.maxLoad && load > 0) {
const chainOffset = parseFloat(state.chainOffset) + sign * data.magnitude * configuration.chainSpeed; const chainOffset =
parseFloat(state.chainOffset) +
sign * data.magnitude * configuration.chainSpeed;
state.chainOffset = `${chainOffset}%`; state.chainOffset = `${chainOffset}%`;
} }
} }
@ -116,18 +184,21 @@ const reduce = function reduce(stateReference, action, data) {
case kAction.pullLever: case kAction.pullLever:
const diff = Math.abs(state.leverTravel - data.magnitude); const diff = Math.abs(state.leverTravel - data.magnitude);
state.leverTravel = data.magnitude; state.leverTravel = data.magnitude;
const rotation = data.magnitude * configuration.leverTravel / 100; const rotation = (data.magnitude * configuration.leverTravel) / 100;
state.leverRotation = `${rotation}deg` state.leverRotation = `${rotation}deg`;
const gratingTravel = data.magnitude * configuration.graterTravel / 100; const gratingTravel = (data.magnitude * configuration.graterTravel) / 100;
state.graterTravel = `${gratingTravel}px` state.graterTravel = `${gratingTravel}px`;
if (state.currentState === kState.readyToGrate) { if (state.currentState === kState.readyToGrate) {
const grateLevel = Math.min(parseFloat(state.grateLevel) + diff * configuration.grateStrength, kDimensions.sausageHeight); const grateLevel = Math.min(
parseFloat(state.grateLevel) + diff * configuration.grateStrength,
kDimensions.sausageHeight,
);
state.grateLevel = `${grateLevel}px`; state.grateLevel = `${grateLevel}px`;
} }
break; break;
case kAction.createSausage: case kAction.createSausage:
state.currentUrl = data.href; state.currentUrl = data.href;
state.grateLevel = '0%'; state.grateLevel = "0%";
state.currentState = kState.readyToGrate; state.currentState = kState.readyToGrate;
break; break;
case kAction.startTurningWheel: case kAction.startTurningWheel:
@ -144,12 +215,13 @@ const reduce = function reduce(stateReference, action, data) {
break; break;
case kAction.completeLoading: case kAction.completeLoading:
state.currentState = kState.waiting; state.currentState = kState.waiting;
state.currentIframe = null;
state.load = 0; state.load = 0;
break; break;
case kAction.completeGrating: case kAction.completeGrating:
state.currentState = kState.readyToLoad; state.currentState = kState.readyToLoad;
state.currentIframe = data.iframe; state.currentIframe = data.iframe;
state.grateLevel = '0%'; state.grateLevel = "0%";
state.load = 0; state.load = 0;
break; break;
} }
@ -162,102 +234,119 @@ const reduce = function reduce(stateReference, action, data) {
* It receives an action and its payload. * It receives an action and its payload.
* It queues the middleware to run after this. * It queues the middleware to run after this.
*/ */
const send = function send(action, data) { const send = function send(action, data) {
const state = Object.assign({}, appState); const state = Object.assign({}, appState);
for (const m of middleware) { for (const m of middleware) {
setTimeout(() => m(state, action, data), 0); setTimeout(() => m(state, action, data), 0);
} }
const newState = reduce(state, action, data); const newState = reduce(state, action, data);
updateState(newState); updateState(newState);
}; };
// Initializzation ///////////////////////////////////////////////////////////// // Initializzation /////////////////////////////////////////////////////////////
const initialize = function initialize() { /**
* Initializes state and events.
*/
const initialize = function initialize() {
const state = { const state = {
currentState: kState.waiting, currentState: kState.waiting,
isPullingLever: false, isPullingLever: false,
isTurningWheel: false, isTurningWheel: false,
leverTravel: 0, leverTravel: 0,
grateLevel: 0, grateLevel: 0,
leverRotation: '0deg', leverRotation: "0deg",
graterTravel: '0rem', graterTravel: "0rem",
wheelRotation: '0deg', wheelRotation: "0deg",
chainOffset: '0%', chainOffset: "0%",
load: 0, load: 0,
}; };
updateState(state); updateState(state);
// Reset interaction with widgets if we lift a mouse button. // Reset interaction with widgets if we lift a mouse button.
document.addEventListener('mouseup', () => { document.addEventListener("mouseup", () => {
send(kAction.stopTurningWheel) send(kAction.stopTurningWheel);
send(kAction.stopPullingLever) send(kAction.stopPullingLever);
}); });
// The lever handle initiates interactions with the lever. // The lever handle initiates interactions with the lever.
document.querySelector('.lever .handle')?.addEventListener('mousedown', () => { document
send(kAction.startPullingLever) .querySelector(".lever .handle")
?.addEventListener("mousedown", () => {
send(kAction.startPullingLever);
}); });
// The wheel handle initiates interactions with the wheel. // The wheel handle initiates interactions with the wheel.
document.querySelector('.wheel .handle')?.addEventListener('mousedown', () => { document
send(kAction.startTurningWheel) .querySelector(".wheel .handle")
?.addEventListener("mousedown", () => {
send(kAction.startTurningWheel);
}); });
// Rotation is calculated relatively to the whole wheel. // Rotation is calculated relatively to the whole wheel.
// And travel based no the full arc of the lever // And travel based no the full arc of the lever
const $wheel = document.querySelector('.wheel'); const $wheel = document.querySelector(".wheel");
const $lever = document.querySelector('.lever'); const $lever = document.querySelector(".lever");
document.addEventListener('mousemove', (event) => { document.addEventListener("mousemove", (event) => {
if (appState.isTurningWheel && !appState.waitingForFrame) { if (appState.isTurningWheel && !appState.waitingForFrame) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
const elementPosition = $wheel.getBoundingClientRect(); const elementPosition = $wheel.getBoundingClientRect();
const x = event.clientX - elementPosition.left; const x = event.clientX - elementPosition.left;
const y = event.clientY - elementPosition.top; const y = event.clientY - elementPosition.top;
const angle = angleFromCenter(x, y, kDimensions.wheelSize, kDimensions.wheelSize) const angle = angleFromCenter(
x,
y,
kDimensions.wheelSize,
kDimensions.wheelSize,
);
const oldAngle = parseFloat(appState.wheelRotation); const oldAngle = parseFloat(appState.wheelRotation);
const { direction, magnitude } = rotationDirection(oldAngle, angle); const { direction, magnitude } = rotationDirection(oldAngle, angle);
send(kAction.turnWheel, { angle, direction, magnitude }); send(kAction.turnWheel, { angle, direction, magnitude });
send(kAction.setIsWaitingForFrame, { state: false }) send(kAction.setIsWaitingForFrame, { state: false });
}); });
send(kAction.setIsWaitingForFrame, { state: true }) send(kAction.setIsWaitingForFrame, { state: true });
} }
if (appState.isPullingLever && !appState.waitingForFrame) { if (appState.isPullingLever && !appState.waitingForFrame) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
const elementPosition = $lever.getBoundingClientRect(); const elementPosition = $lever.getBoundingClientRect();
const x = event.clientX - elementPosition.left; const x = event.clientX - elementPosition.left;
const magnitude = Math.max(Math.min(x * 100 / kDimensions.leverWidth, 100), 0); const magnitude = Math.max(
const direction = magnitude >= appState.leverTravel ? kPullDirection.right : kPullDirection.left; Math.min((x * 100) / kDimensions.leverWidth, 100),
0,
);
const direction =
magnitude >= appState.leverTravel
? kPullDirection.right
: kPullDirection.left;
send(kAction.pullLever, { magnitude, direction }); send(kAction.pullLever, { magnitude, direction });
send(kAction.setIsWaitingForFrame, { state: false }) send(kAction.setIsWaitingForFrame, { state: false });
}); });
send(kAction.setIsWaitingForFrame, { state: true }) send(kAction.setIsWaitingForFrame, { state: true });
} }
}); });
// The URL Grater is the drop area. // The URL Grater is the drop area.
const $grater = document.querySelector('.grater-bed'); const $grater = document.querySelector(".grater-bed");
$grater.addEventListener('dragenter', (event) => { $grater.addEventListener("dragenter", (event) => {
event.preventDefault(); event.preventDefault();
if (appState.currentState === kState.waiting) { if (appState.currentState === kState.waiting) {
$grater.classList.add('drag-over'); $grater.classList.add("drag-over");
} }
}); });
$grater.addEventListener('dragover', (event) => event.preventDefault()); $grater.addEventListener("dragover", (event) => event.preventDefault());
$grater.addEventListener('dragleave', (event) => { $grater.addEventListener("dragleave", (event) => {
if (!$grater.contains(event.relatedTarget)) { if (!$grater.contains(event.relatedTarget)) {
$grater.classList.remove('drag-over'); $grater.classList.remove("drag-over");
} }
}); });
$grater.addEventListener('drop', (event) => { $grater.addEventListener("drop", (event) => {
event.preventDefault(); event.preventDefault();
if (appState.currentState !== kState.waiting) { if (appState.currentState !== kState.waiting) {
return; return;
} }
const href = event.dataTransfer.getData('text/uri-list'); const href = event.dataTransfer.getData("text/uri-list");
const label = event.dataTransfer.getData('text/plain'); const label = event.dataTransfer.getData("text/plain");
$grater.classList.remove('drag-over'); $grater.classList.remove("drag-over");
send(kAction.createSausage, { href, label }); send(kAction.createSausage, { href, label });
}); });
@ -265,23 +354,23 @@ const reduce = function reduce(stateReference, action, data) {
const urlFromQuery = params.get("url"); const urlFromQuery = params.get("url");
const url = urlFromQuery ?? configuration.defaultUrl; const url = urlFromQuery ?? configuration.defaultUrl;
loadIframe(url, 100); loadIframe(url, 100);
}; };
// Utility Functions /////////////////////////////////////////////////////////// // Utility Functions ///////////////////////////////////////////////////////////
/** /**
* Calculates the angle of a point (x,y) relative to the center of a square * Calculates the angle of a point (x,y) relative to the center of a square
* with dimensions (w, h). The result is in radians. * with dimensions (w, h). The result is in radians.
*/ */
const angleFromCenter = function angleFromCenter(x, y, w, h) { const angleFromCenter = function angleFromCenter(x, y, w, h) {
const centerX = w / 2; const centerX = w / 2;
const centerY = h / 2; const centerY = h / 2;
const dx = x - centerX; const dx = x - centerX;
const dy = y - centerY; const dy = y - centerY;
return Math.atan2(dy, dx); return Math.atan2(dy, dx);
}; };
/** /**
* Given two angles, calculates the direction of the rotation. * Given two angles, calculates the direction of the rotation.
*/ */
const rotationDirection = function rotationDirection(angle1, angle2) { const rotationDirection = function rotationDirection(angle1, angle2) {
@ -291,46 +380,49 @@ const rotationDirection = function rotationDirection(angle1, angle2) {
if (diff < -Math.PI) diff += 2 * Math.PI; if (diff < -Math.PI) diff += 2 * Math.PI;
const magnitude = Math.abs(diff); const magnitude = Math.abs(diff);
const direction = diff > 0 ? kRotationDirection.counterClockwise : kRotationDirection.clockwise; const direction =
diff > 0
? kRotationDirection.counterClockwise
: kRotationDirection.clockwise;
return { return {
direction, direction,
magnitude magnitude,
}; };
}; };
/** /**
* Loads an iframe and appends it into the body. * Loads an iframe and appends it into the body.
*/ */
const $iframeContainer = document.querySelector('.web-content'); const $iframeContainer = document.querySelector(".web-content");
const loadIframe = function loadIframe(url, loadProgress) { const loadIframe = function loadIframe(url, loadProgress) {
const $iframe = document.createElement('iframe'); const $iframe = document.createElement("iframe");
$iframe.src = url; $iframe.src = url;
$iframe.style.setProperty('--loadProgress', `${100 - loadProgress}vh`); $iframe.style.setProperty("--loadProgress", `${100 - loadProgress}vh`);
$iframeContainer.appendChild($iframe); $iframeContainer.appendChild($iframe);
$iframe.addEventListener('load', () => { $iframe.addEventListener("load", () => {
const iframeDocument = $iframe.contentDocument; const iframeDocument = $iframe.contentDocument;
iframeDocument.querySelectorAll('a').forEach(($link) => { iframeDocument.querySelectorAll("a").forEach(($link) => {
$link.addEventListener('click', (event) => event.preventDefault()); $link.addEventListener("click", (event) => event.preventDefault());
$link.draggable = true; $link.draggable = true;
$link.addEventListener('dragstart', (event) => { $link.addEventListener("dragstart", (event) => {
event.dataTransfer.setData('text/uri-list', $link.href); event.dataTransfer.setData("text/uri-list", $link.href);
event.dataTransfer.setData('text/plain', $link.textContent); event.dataTransfer.setData("text/plain", $link.textContent.trim());
const ghost = $link.cloneNode(true); const ghost = $link.cloneNode(true);
ghost.style.background = 'url(\'/images/Sausage.svg\')'; ghost.style.background = "url('/images/Sausage.svg')";
ghost.style.backgroundRepeat = 'no-repeat'; ghost.style.backgroundRepeat = "no-repeat";
ghost.style.cursor = 'grabbing'; ghost.style.cursor = "grabbing";
ghost.style.display = 'block'; ghost.style.display = "block";
ghost.style.height = '3rem'; ghost.style.height = "3rem";
ghost.style.width = '8.125rem'; ghost.style.width = "8.125rem";
ghost.style.color = '#fff'; ghost.style.color = "#fff";
ghost.style.textAlign = 'center'; ghost.style.textAlign = "center";
ghost.style.fontSize = '0.625rem'; ghost.style.fontSize = "0.625rem";
ghost.style.fontFamily = 'sans-serif'; ghost.style.fontFamily = "sans-serif";
ghost.style.textTransform = 'uppercase'; ghost.style.textTransform = "uppercase";
ghost.style.paddingTop = '1rem'; ghost.style.paddingTop = "1rem";
document.body.appendChild(ghost); document.body.appendChild(ghost);
event.dataTransfer.setDragImage(ghost, 10, 10); event.dataTransfer.setDragImage(ghost, 10, 10);
@ -341,21 +433,27 @@ const loadIframe = function loadIframe(url, loadProgress) {
return $iframe; return $iframe;
}; };
/**
* Creates a sausage link for grating.
*/
const createSausage = function createSausage(href, label) { const createSausage = function createSausage(href, label) {
const $sausage = document.createElement('article'); const $sausage = document.createElement("article");
$sausage.classList.add('sausage'); $sausage.classList.add("sausage");
$sausage.dataset.href = href; $sausage.dataset.href = href;
$sausage.innerText = label; $sausage.innerText = label;
const $graterBed = document.querySelector('.grater-bed'); const $graterBed = document.querySelector(".grater-bed");
$graterBed?.appendChild($sausage); $graterBed?.appendChild($sausage);
}; };
/**
* Removes a completely grated sausage link.
*/
const removeSausages = function removeSausages() { const removeSausages = function removeSausages() {
const $graterBed = document.querySelector('.grater-bed'); const $graterBed = document.querySelector(".grater-bed");
const $existingSausages = $graterBed.querySelectorAll('.sausage'); const $existingSausages = $graterBed.querySelectorAll(".sausage");
for (const $existingSausage of $existingSausages) { for (const $existingSausage of $existingSausages) {
$graterBed?.removeChild($existingSausage); $graterBed?.removeChild($existingSausage);
} }
} };
initialize(); initialize();