Prettify
parent
c4bf944f93
commit
87d65c599e
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18133
example.html
18133
example.html
File diff suppressed because one or more lines are too long
19
index.html
19
index.html
|
|
@ -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>
|
||||||
|
|
|
||||||
554
js/machine.js
554
js/machine.js
|
|
@ -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 = [
|
*/
|
||||||
function disableSelect(_, action) {
|
const middleware = [
|
||||||
if (action === kAction.startTurningWheel || action === kAction.startPullingLever) {
|
/**
|
||||||
document.querySelector('.machine')?.classList.add('no-select');
|
* Makes text unselectable while we move the lever or wheel.
|
||||||
}
|
*/
|
||||||
},
|
function disableSelect(_, action) {
|
||||||
function enableSelect(_, action) {
|
if (
|
||||||
if (action === kAction.stopTurningWheel || action === kAction.stopPullingLever) {
|
action === kAction.startTurningWheel ||
|
||||||
document.querySelector('.machine')?.classList.remove('no-select');
|
action === kAction.startPullingLever
|
||||||
}
|
) {
|
||||||
},
|
document.querySelector(".machine")?.classList.add("no-select");
|
||||||
function completeLoading(state, action) {
|
|
||||||
if (action === kAction.turnWheel && state.currentState === kState.readyToLoad && parseFloat(state.load) >= configuration.maxLoad) {
|
|
||||||
send(kAction.completeLoading);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function updateIframe(state, action) {
|
|
||||||
if (action === kAction.turnWheel && state.currentState === kState.readyToLoad && parseFloat(state.load) <= configuration.maxLoad) {
|
|
||||||
state.currentIframe?.style.setProperty('--loadProgress', `${100 - state.load}vh`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function completeGrating(state, action) {
|
|
||||||
if (action === kAction.pullLever && state.currentState === kState.readyToGrate && parseFloat(state.grateLevel) >= kDimensions.sausageHeight) {
|
|
||||||
removeSausages();
|
|
||||||
const $iframe = loadIframe(state.currentUrl, 0);
|
|
||||||
send(kAction.completeGrating, { iframe: $iframe });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function createSausageIfWaiting(state, action, data) {
|
|
||||||
if (action === kAction.createSausage && state.currentState === kState.waiting) {
|
|
||||||
createSausage(data.href, data.label);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function log(state, action, data) {
|
|
||||||
//console.log(state.load);
|
|
||||||
}
|
}
|
||||||
];
|
},
|
||||||
|
|
||||||
const $stateContainer = document.querySelector('.machine');
|
/**
|
||||||
|
* Makes text selectable when we're done moving the lever or wheel.
|
||||||
|
*/
|
||||||
|
function enableSelect(_, action) {
|
||||||
|
if (
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the individual load state to the current iframe.
|
||||||
|
*/
|
||||||
|
function updateIframe(state, action) {
|
||||||
|
if (
|
||||||
|
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) {
|
||||||
|
if (
|
||||||
|
action === kAction.pullLever &&
|
||||||
|
state.currentState === kState.readyToGrate &&
|
||||||
|
parseFloat(state.grateLevel) >= kDimensions.sausageHeight
|
||||||
|
) {
|
||||||
|
removeSausages();
|
||||||
|
const $iframe = loadIframe(state.currentUrl, 0);
|
||||||
|
send(kAction.completeGrating, { iframe: $iframe });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the sausage element if we are at the right state.
|
||||||
|
*/
|
||||||
|
function createSausageIfWaiting(state, action, data) {
|
||||||
|
if (
|
||||||
|
action === kAction.createSausage &&
|
||||||
|
state.currentState === kState.waiting
|
||||||
|
) {
|
||||||
|
createSausage(data.href, data.label);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
}
|
}
|
||||||
|
|
@ -158,132 +230,149 @@ const reduce = function reduce(stateReference, action, data) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main mechanism for view changes to trigger updates.
|
* The main mechanism for view changes to trigger updates.
|
||||||
* 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 state = {
|
*/
|
||||||
currentState: kState.waiting,
|
const initialize = function initialize() {
|
||||||
isPullingLever: false,
|
const state = {
|
||||||
isTurningWheel: false,
|
currentState: kState.waiting,
|
||||||
leverTravel: 0,
|
isPullingLever: false,
|
||||||
grateLevel: 0,
|
isTurningWheel: false,
|
||||||
leverRotation: '0deg',
|
leverTravel: 0,
|
||||||
graterTravel: '0rem',
|
grateLevel: 0,
|
||||||
wheelRotation: '0deg',
|
leverRotation: "0deg",
|
||||||
chainOffset: '0%',
|
graterTravel: "0rem",
|
||||||
load: 0,
|
wheelRotation: "0deg",
|
||||||
};
|
chainOffset: "0%",
|
||||||
updateState(state);
|
load: 0,
|
||||||
|
|
||||||
// Reset interaction with widgets if we lift a mouse button.
|
|
||||||
document.addEventListener('mouseup', () => {
|
|
||||||
send(kAction.stopTurningWheel)
|
|
||||||
send(kAction.stopPullingLever)
|
|
||||||
});
|
|
||||||
|
|
||||||
// The lever handle initiates interactions with the lever.
|
|
||||||
document.querySelector('.lever .handle')?.addEventListener('mousedown', () => {
|
|
||||||
send(kAction.startPullingLever)
|
|
||||||
});
|
|
||||||
|
|
||||||
// The wheel handle initiates interactions with the wheel.
|
|
||||||
document.querySelector('.wheel .handle')?.addEventListener('mousedown', () => {
|
|
||||||
send(kAction.startTurningWheel)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rotation is calculated relatively to the whole wheel.
|
|
||||||
// And travel based no the full arc of the lever
|
|
||||||
const $wheel = document.querySelector('.wheel');
|
|
||||||
const $lever = document.querySelector('.lever');
|
|
||||||
document.addEventListener('mousemove', (event) => {
|
|
||||||
if (appState.isTurningWheel && !appState.waitingForFrame) {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
const elementPosition = $wheel.getBoundingClientRect();
|
|
||||||
const x = event.clientX - elementPosition.left;
|
|
||||||
const y = event.clientY - elementPosition.top;
|
|
||||||
const angle = angleFromCenter(x, y, kDimensions.wheelSize, kDimensions.wheelSize)
|
|
||||||
const oldAngle = parseFloat(appState.wheelRotation);
|
|
||||||
const { direction, magnitude } = rotationDirection(oldAngle, angle);
|
|
||||||
send(kAction.turnWheel, { angle, direction, magnitude });
|
|
||||||
send(kAction.setIsWaitingForFrame, { state: false })
|
|
||||||
});
|
|
||||||
send(kAction.setIsWaitingForFrame, { state: true })
|
|
||||||
}
|
|
||||||
if (appState.isPullingLever && !appState.waitingForFrame) {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
const elementPosition = $lever.getBoundingClientRect();
|
|
||||||
const x = event.clientX - elementPosition.left;
|
|
||||||
const magnitude = Math.max(Math.min(x * 100 / kDimensions.leverWidth, 100), 0);
|
|
||||||
const direction = magnitude >= appState.leverTravel ? kPullDirection.right : kPullDirection.left;
|
|
||||||
send(kAction.pullLever, { magnitude, direction });
|
|
||||||
send(kAction.setIsWaitingForFrame, { state: false })
|
|
||||||
});
|
|
||||||
send(kAction.setIsWaitingForFrame, { state: true })
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// The URL Grater is the drop area.
|
|
||||||
const $grater = document.querySelector('.grater-bed');
|
|
||||||
$grater.addEventListener('dragenter', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (appState.currentState === kState.waiting) {
|
|
||||||
$grater.classList.add('drag-over');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$grater.addEventListener('dragover', (event) => event.preventDefault());
|
|
||||||
$grater.addEventListener('dragleave', (event) => {
|
|
||||||
if (!$grater.contains(event.relatedTarget)) {
|
|
||||||
$grater.classList.remove('drag-over');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$grater.addEventListener('drop', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (appState.currentState !== kState.waiting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const href = event.dataTransfer.getData('text/uri-list');
|
|
||||||
const label = event.dataTransfer.getData('text/plain');
|
|
||||||
$grater.classList.remove('drag-over');
|
|
||||||
send(kAction.createSausage, { href, label });
|
|
||||||
});
|
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
const urlFromQuery = params.get("url");
|
|
||||||
const url = urlFromQuery ?? configuration.defaultUrl;
|
|
||||||
loadIframe(url, 100);
|
|
||||||
};
|
};
|
||||||
|
updateState(state);
|
||||||
|
|
||||||
|
// Reset interaction with widgets if we lift a mouse button.
|
||||||
|
document.addEventListener("mouseup", () => {
|
||||||
|
send(kAction.stopTurningWheel);
|
||||||
|
send(kAction.stopPullingLever);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The lever handle initiates interactions with the lever.
|
||||||
|
document
|
||||||
|
.querySelector(".lever .handle")
|
||||||
|
?.addEventListener("mousedown", () => {
|
||||||
|
send(kAction.startPullingLever);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The wheel handle initiates interactions with the wheel.
|
||||||
|
document
|
||||||
|
.querySelector(".wheel .handle")
|
||||||
|
?.addEventListener("mousedown", () => {
|
||||||
|
send(kAction.startTurningWheel);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Rotation is calculated relatively to the whole wheel.
|
||||||
|
// And travel based no the full arc of the lever
|
||||||
|
const $wheel = document.querySelector(".wheel");
|
||||||
|
const $lever = document.querySelector(".lever");
|
||||||
|
document.addEventListener("mousemove", (event) => {
|
||||||
|
if (appState.isTurningWheel && !appState.waitingForFrame) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const elementPosition = $wheel.getBoundingClientRect();
|
||||||
|
const x = event.clientX - elementPosition.left;
|
||||||
|
const y = event.clientY - elementPosition.top;
|
||||||
|
const angle = angleFromCenter(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
kDimensions.wheelSize,
|
||||||
|
kDimensions.wheelSize,
|
||||||
|
);
|
||||||
|
const oldAngle = parseFloat(appState.wheelRotation);
|
||||||
|
const { direction, magnitude } = rotationDirection(oldAngle, angle);
|
||||||
|
send(kAction.turnWheel, { angle, direction, magnitude });
|
||||||
|
send(kAction.setIsWaitingForFrame, { state: false });
|
||||||
|
});
|
||||||
|
send(kAction.setIsWaitingForFrame, { state: true });
|
||||||
|
}
|
||||||
|
if (appState.isPullingLever && !appState.waitingForFrame) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const elementPosition = $lever.getBoundingClientRect();
|
||||||
|
const x = event.clientX - elementPosition.left;
|
||||||
|
const magnitude = Math.max(
|
||||||
|
Math.min((x * 100) / kDimensions.leverWidth, 100),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const direction =
|
||||||
|
magnitude >= appState.leverTravel
|
||||||
|
? kPullDirection.right
|
||||||
|
: kPullDirection.left;
|
||||||
|
send(kAction.pullLever, { magnitude, direction });
|
||||||
|
send(kAction.setIsWaitingForFrame, { state: false });
|
||||||
|
});
|
||||||
|
send(kAction.setIsWaitingForFrame, { state: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// The URL Grater is the drop area.
|
||||||
|
const $grater = document.querySelector(".grater-bed");
|
||||||
|
$grater.addEventListener("dragenter", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (appState.currentState === kState.waiting) {
|
||||||
|
$grater.classList.add("drag-over");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$grater.addEventListener("dragover", (event) => event.preventDefault());
|
||||||
|
$grater.addEventListener("dragleave", (event) => {
|
||||||
|
if (!$grater.contains(event.relatedTarget)) {
|
||||||
|
$grater.classList.remove("drag-over");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$grater.addEventListener("drop", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (appState.currentState !== kState.waiting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const href = event.dataTransfer.getData("text/uri-list");
|
||||||
|
const label = event.dataTransfer.getData("text/plain");
|
||||||
|
$grater.classList.remove("drag-over");
|
||||||
|
send(kAction.createSausage, { href, label });
|
||||||
|
});
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const urlFromQuery = params.get("url");
|
||||||
|
const url = urlFromQuery ?? configuration.defaultUrl;
|
||||||
|
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) {
|
||||||
let diff = angle2 - angle1;
|
let diff = angle2 - angle1;
|
||||||
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue