This commit is contained in:
Mal 2023-01-13 00:25:05 +01:00
commit 2dfb26bf13
6 changed files with 713 additions and 0 deletions

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="107.10338mm"
height="135.48544mm"
viewBox="0 0 107.10338 135.48544"
version="1.1"
id="svg615"
xml:space="preserve"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="icon-delete.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview617"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.52850974"
inkscape:cx="256.38127"
inkscape:cy="317.87493"
inkscape:window-width="1845"
inkscape:window-height="1011"
inkscape:window-x="75"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs
id="defs612" /><g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-38.8519,-50.146291)"><circle
style="fill:#800000;stroke-width:0.264999;stroke-linecap:round;stroke-dasharray:1.06, 0.264999"
id="path800"
cx="124.01968"
cy="63.871456"
r="13.725165" /><rect
style="fill:#800000;stroke-width:0.264999;stroke-linecap:round;stroke-dasharray:1.06, 0.264999"
id="rect908"
width="39.083843"
height="49.209461"
x="106.87144"
y="81.037872"
ry="7.3926311" /><rect
style="fill:#800000;stroke-width:0.265002;stroke-linecap:round;stroke-dasharray:1.06001, 0.265002"
id="rect910"
width="13.525886"
height="45.075115"
x="134.86862"
y="-21.938469"
ry="6.7643981"
transform="matrix(0.71669611,0.69738561,-0.6927963,0.72113334,0,0)" /><rect
style="fill:#800000;stroke-width:0.265003;stroke-linecap:round;stroke-dasharray:1.06001, 0.265003"
id="rect910-6"
width="13.527364"
height="66.433311"
x="106.91904"
y="121.48386"
ry="5.8429613"
transform="matrix(0.99979748,-0.0201246,0.01359535,0.99990758,0,0)"
inkscape:transform-center-x="5.1449419"
inkscape:transform-center-y="-1.0140819e-05" /><rect
style="fill:#800000;stroke-width:0.265003;stroke-linecap:round;stroke-dasharray:1.06001, 0.265003"
id="rect910-6-7"
width="13.527364"
height="66.433311"
x="123.62118"
y="121.54776"
ry="5.8429613"
transform="matrix(0.99979748,-0.0201246,0.01359535,0.99990758,0,0)"
inkscape:transform-center-x="5.1449419"
inkscape:transform-center-y="-1.0140819e-05" /><rect
style="fill:#800000;stroke-width:0.264999;stroke-linecap:round;stroke-dasharray:1.06, 0.264999"
id="rect966"
width="42.142654"
height="3.842999"
x="46.099419"
y="180.48439"
ry="1.8306717" /><rect
style="fill:#800000;stroke-width:0.265088;stroke-linecap:round;stroke-dasharray:1.06035, 0.265088"
id="rect966-5"
width="62.254524"
height="5.0046005"
x="126.77824"
y="-28.271225"
ry="2.3840184"
transform="matrix(0.1231071,0.99239339,-0.98724039,0.15923696,0,0)" /><rect
style="fill:#800000;stroke-width:0.265088;stroke-linecap:round;stroke-dasharray:1.06035, 0.265088"
id="rect966-5-3"
width="62.254524"
height="5.0046005"
x="105.78143"
y="105.33174"
ry="2.3840184"
transform="matrix(-0.1231071,0.99239339,0.98724039,0.15923696,0,0)" /><rect
style="fill:#800000;stroke-width:0.264999;stroke-linecap:round;stroke-dasharray:1.06, 0.264999"
id="rect1022"
width="24.812437"
height="24.812437"
x="133.02756"
y="38.251972"
ry="2.3840184"
transform="rotate(45)" /></g></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="17.671124mm"
height="17.67112mm"
viewBox="0 0 17.671124 17.67112"
version="1.1"
id="svg436"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="icon-play.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview438"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.74742565"
inkscape:cx="162.55798"
inkscape:cy="48.165326"
inkscape:window-width="1845"
inkscape:window-height="1011"
inkscape:window-x="75"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs433" />
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-62.317001,-135.59352)">
<path
id="rect234-3"
style="fill:#00ff00;stroke-width:0.264999;stroke-linecap:round;stroke-dasharray:1.06, 0.264999"
d="m 62.317001,135.59352 17.671123,8.83556 -17.671122,8.83556 z"
sodipodi:nodetypes="cccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="17.671124mm"
height="17.671124mm"
viewBox="0 0 17.671124 17.671124"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="icon-stop.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="2.9897026"
inkscape:cx="-96.497893"
inkscape:cy="53.182548"
inkscape:window-width="1845"
inkscape:window-height="1011"
inkscape:window-x="75"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2" />
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-68.072159,-77.569153)">
<rect
style="fill:#ff0000;stroke-width:0.264999;stroke-linecap:round;stroke-dasharray:1.06, 0.264999"
id="rect234"
width="17.671124"
height="17.671124"
x="68.072159"
y="77.569153" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

163
public/index.html Normal file
View File

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>LED Cube</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="style.css">
<script defer type="text/javascript" src="index.js"></script>
</head>
<body>
<div id="cube-window">
<div id="cube">
<!--
<div class="layer">
<div class="row">
<div class="led led-off"></div>
<div class="led led-previous"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="layer-submenu">
<div class="submenu-line"></div>
<div class="layer-buttons">
<div class="layer-button layer-button-on" title="Alle an"></div>
<div class="layer-button layer-button-off" title="Alle aus"></div>
</div>
</div>
</div>
<div class="layer">
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
</div>
<div class="layer">
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
</div>
<div class="layer">
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
<div class="row">
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
<div class="led"></div>
</div>
</div>
-->
</div>
</div>
<div id="animation-window">
<div id="frame-menu">
<button class="icon-button icon-button-play" id="button-play-frames" title="Animation abspielen"></button>
<button id="button-stop-frames" class="icon-button icon-button-stop" title="Animation stoppen"></button>
<div id="frame-bar">
<div id="frame-display">Frame: <span id="current-frame">1</span>/<span id="frame-number">1</span></div>
<input id="frame-slider" type="range" step="1" min="1" max="1" value="1">
</div>
<input type="number" class="suffix suffix-ms" min="1" step="1" max="65355" value="100" title="Frame-Dauer (ms)">
<button id="button-add-frame" title="Frame hinzufügen">+</button>
<button id="button-delete-frame" class="icon-button icon-button-delete" title="Frame löschen"></button>
</div>
</div>
</body>
</html>

131
public/index.js Normal file
View File

@ -0,0 +1,131 @@
const Frame = function (duration) {
this.layers = [];
this.duration = duration;
}
const FrameFactory = function (width, depth, height) {
this.width = width;
this.depth = depth;
this.height = height;
this.getFrame = function (duration) {
const frame = new Frame(duration);
for (let z = 0; z < this.height; z++) {
const layer = [];
for (let led = 0; led < this.width * this.depth; led++) {
layer.push(false);
}
frame.layers.push(layer);
}
return frame;
}
}
const Cube = function (width, depth, height) {
this.width = width;
this.depth = depth;
this.height = height;
this.html = document.getElementById('cube');
this.frames = [];
this.currentFrame = 0;
this.setup = function () {
this._setupHtml();
}
this.setCurrentFrame = function (frameIndex) {
this.currentFrame = frameIndex;
this._loadFrame(frameIndex);
}
this.switchLed = function (layerIndex, ledIndex) {
console.log(layerIndex);
this.frames[this.currentFrame].layers[layerIndex][ledIndex] = !this.frames[this.currentFrame].layers[layerIndex][ledIndex];
}
this.setLayer = function (layerIndex, state) {
for (let index = 0; index < this.width * this.depth; index++) {
this.frames[this.currentFrame].layers[layerIndex][index] = state;
}
for (const row of this.html.children[this.height - layerIndex - 1].children) {
for (const led of row.children) {
this._setLed(led, state);
}
}
}
this._loadFrame = function (frameIndex) {
for (let layer = 0; layer < this.frames[frameIndex].layers.length; layer++) {
for (let row = 0; row < this.depth; row++) {
for (let led = 0; led < this.width; led++) {
const state = this.frames[frameIndex].layers[layer][row * this.depth + led];
this._setLed(this.html.children[layer].children[row].children[led], state);
}
}
}
}
this._setupHtml = function () {
for (let z = 0; z < this.height; z++) {
const layerElement = document.createElement('div');
layerElement.classList.add('layer');
for (let y = 0; y < this.depth; y++) {
const row = document.createElement('div');
row.classList.add('row');
for (let x = 0; x < this.width; x++) {
const led = document.createElement('div');
led.classList.add('led', 'led-off');
led.addEventListener(
'click',
(event) => {
const index = y * this.depth + x;
const layer = this.height - z - 1;
this.switchLed(layer, index);
this._setLed(event.target, this.frames[this.currentFrame].layers[layer][index]);
}
);
row.appendChild(led);
}
layerElement.appendChild(row);
}
this.html.appendChild(layerElement);
}
}
this._setLed = function (ledHtml, state) {
if (state) {
ledHtml.classList.remove('led-off');
} else {
ledHtml.classList.add('led-off');
}
}
}
const WIDTH = 4;
const DEPTH = 4;
const HEIGHT = 4;
const cube = new Cube(WIDTH, DEPTH, HEIGHT);
cube.setup();
console.log(cube.html.children);
const frameFactory = new FrameFactory(WIDTH, DEPTH, HEIGHT);
cube.frames.push(frameFactory.getFrame(100));
cube.setCurrentFrame(0);
cube.setLayer(2, true);

208
public/style.css Normal file
View File

@ -0,0 +1,208 @@
* {
box-sizing: border-box;
}
html {
min-height: 100%;
}
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
background-color: black;
color: white;
font-family: sans-serif;
}
input {
border: 2px solid white;
background-color: transparent;
color: white;
padding: 5px;
border-radius: 10px;
font-size: 16px;
outline: none;
}
input[type=number] {
max-width: 100px;
}
button {
background-color: #164cff;
border: none;
border-radius: 10px;
padding: 0 20px;
color: white;
font-weight: bold;
font-size: 16px;
cursor: pointer;
height: 32px;
}
.suffix-ms::after {
content: "ms";
}
.icon-button {
background-color: transparent;
height: 32px;
background-position: center center;
background-repeat: no-repeat;
background-size: auto 32px;
border-radius: 0;
}
.icon-button:hover {
background-color: transparent;
}
.icon-button-play {
background-image: url("graphics/icon-play.svg");
}
.icon-button-stop {
background-image: url("graphics/icon-stop.svg");
}
.icon-button-delete {
background-image: url("graphics/icon-delete.svg");
}
button:hover {
background-color: #3765ff;
}
#cube-window {
display: flex;
justify-content: center;
align-items: center;
background-color: black;
}
#animation-window {
display: flex;
justify-content: center;
align-items: center;
}
#frame-menu {
border: 2px solid white;
border-radius: 20px;
padding: 20px;
display: flex;
align-items: center;
}
#frame-menu > * {
margin: 0 10px;
}
#frame-display {
text-align: center;
}
#frame-slider {
width: 250px;
}
#cube {
display: block;
padding-bottom: 80px;
}
.layer {
position: relative;
transform: scale(1.0, 0.4) rotate(-45deg);
margin-bottom: -40%;
border: 2px solid transparent;
border-radius: 20px;
background-color: black;
}
.layer:hover {
border: 2px solid white;
border-radius: 20px;
}
.layer-submenu {
position: absolute;
left: 67%;
bottom: -74px;
display: flex;
align-items: center;
transform: rotate(45deg) scale(1.0, 2.1);
visibility: hidden;
padding-left: 58px;
}
.submenu-line {
border: 1px solid white;
width: 80px;
}
.layer:hover .layer-submenu {
visibility: visible;
}
.layer-buttons {
display: flex;
border: solid 2px white;
padding: 10px;
border-radius: 10px;
}
.layer-button {
border-radius: 50%;
border: 2px solid white;
width: 32px;
height: 32px;
margin: 5px;
cursor: pointer;
}
.layer-button-on {
background-color: red;
}
.layer-button-off {
background-color: black;
}
.led {
border-radius: 50%;
background-color: red;
width: 32px;
height: 48px;
margin: 8px 16px;
transform: rotate(45deg);
cursor: pointer;
box-shadow: 0 0 10px red;
}
.led-off {
opacity: 0.25;
box-shadow: none;
}
.led-previous {
opacity: 0.5;
}
.led-red {
background-color: red;
}
.led-blue {
background-color: #3333ff;
}
.led-green {
background-color: #00ff00;
}
.row {
display: flex;
}