relative positioning
commit
7366fb8972
Binary file not shown.
After Width: | Height: | Size: 227 KiB |
Binary file not shown.
After Width: | Height: | Size: 224 KiB |
@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<script src="session.js" defer></script>
|
||||||
|
<script src="labels.js" defer></script>
|
||||||
|
<script src="picture.js" defer></script>
|
||||||
|
<script src="panels.js" defer></script>
|
||||||
|
<script src="text-export.js" defer></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<title>Collecting Labels</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main id="container">
|
||||||
|
<figure class="background-container" id="background-container">
|
||||||
|
<img id="background-image" draggable="false" src="assets/map_description_H.jpg" />
|
||||||
|
<div id="target"></div>
|
||||||
|
</figure>
|
||||||
|
</main>
|
||||||
|
<div id="editor"></div>
|
||||||
|
<nav>
|
||||||
|
<button id="show-transcription">Export</button>
|
||||||
|
<button id="show-info">?</button>
|
||||||
|
<button id="show-image">IMG</button>
|
||||||
|
<button id="load-labels">Load</button>
|
||||||
|
</nav>
|
||||||
|
<aside class="info" id="info-panel">
|
||||||
|
<button class="close">X</button>
|
||||||
|
<h1 class="title">Concrete 🎏 Label</h1>
|
||||||
|
<p>
|
||||||
|
What do we need to know: - text labels-contents - order - position - size - who
|
||||||
|
wrote it ? (random id? name? nothing at all?) - timestamp ? -
|
||||||
|
</p>
|
||||||
|
</aside>
|
||||||
|
<aside class="transcription" id="transcription-panel">
|
||||||
|
<button class="close">X</button>
|
||||||
|
<h1 class="title">Label Transcription</h1>
|
||||||
|
<ol class="labels-contents"></ol>
|
||||||
|
<button id="export-text">Export</button>
|
||||||
|
</aside>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,369 @@
|
|||||||
|
// Get the container to use as a canvas
|
||||||
|
const container = document.getElementById("container");
|
||||||
|
const editor = document.getElementById("editor");
|
||||||
|
const backgroundImage = document.getElementById("background-image");
|
||||||
|
const imageTarget = document.getElementById("target");
|
||||||
|
const backgroundContainer = document.getElementById("background-container");
|
||||||
|
|
||||||
|
let backgroundWidth;
|
||||||
|
let backgroundHeight;
|
||||||
|
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
targetSize();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("resize", (e) => {
|
||||||
|
targetSize();
|
||||||
|
});
|
||||||
|
|
||||||
|
function targetSize() {
|
||||||
|
backgroundWidth = backgroundImage.width;
|
||||||
|
backgroundHeight = backgroundImage.height;
|
||||||
|
|
||||||
|
// cringe
|
||||||
|
imageTarget.style.width = backgroundWidth + "px";
|
||||||
|
imageTarget.style.height = backgroundHeight + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of labels
|
||||||
|
let labels = [];
|
||||||
|
let labelsObj = [];
|
||||||
|
|
||||||
|
let closing = false;
|
||||||
|
|
||||||
|
// Start is where the mouse is pressed, End is where the mouse is released
|
||||||
|
let startX;
|
||||||
|
let startY;
|
||||||
|
let endX;
|
||||||
|
let endY;
|
||||||
|
|
||||||
|
// Minimum size for the label to be created
|
||||||
|
let minimumSizeX = 5;
|
||||||
|
let minimumSizeY = 5;
|
||||||
|
|
||||||
|
// Boolean for showing the editor during drawing
|
||||||
|
let showEditor = false;
|
||||||
|
let editorX = 0;
|
||||||
|
let editorY = 0;
|
||||||
|
|
||||||
|
function percentagePosition(e, target) {
|
||||||
|
let targetRect = target.getBoundingClientRect();
|
||||||
|
let x = e.clientX - targetRect.left;
|
||||||
|
let y = e.clientY - targetRect.top;
|
||||||
|
|
||||||
|
return { x: (x / backgroundWidth) * 100, y: (y / backgroundHeight) * 100 };
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("mousedown", (e) => {
|
||||||
|
let percentage = percentagePosition(e, imageTarget);
|
||||||
|
|
||||||
|
if (e.target.tagName !== "BUTTON" && e.target.tagName !== "TEXTAREA") {
|
||||||
|
startX = percentage.x;
|
||||||
|
startY = percentage.y;
|
||||||
|
|
||||||
|
editorX = e.x;
|
||||||
|
editorY = e.y;
|
||||||
|
|
||||||
|
// activate the editor
|
||||||
|
showEditor = true;
|
||||||
|
editor.classList.add("show-editor");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store the coordinates and trigger the function
|
||||||
|
window.addEventListener("mouseup", (e) => {
|
||||||
|
if (e.target.tagName !== "BUTTON" && e.target.tagName !== "TEXTAREA") {
|
||||||
|
let percentage = percentagePosition(e, imageTarget);
|
||||||
|
|
||||||
|
endX = percentage.x;
|
||||||
|
endY = percentage.y;
|
||||||
|
|
||||||
|
// disable the editor
|
||||||
|
showEditor = false;
|
||||||
|
editor.classList.remove("show-editor");
|
||||||
|
editor.style.width = 0;
|
||||||
|
editor.style.height = 0;
|
||||||
|
|
||||||
|
// draw label
|
||||||
|
drawLabel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Edit the editor box using transform instead of left / top in order to be more efficient
|
||||||
|
// (but still with width and height ehm idk if this affects the performance a lot)
|
||||||
|
// (and it is something we must care of because this event is called like every frame that the mouse is dragged)
|
||||||
|
|
||||||
|
window.addEventListener("mousemove", (e) => {
|
||||||
|
if (showEditor) {
|
||||||
|
let minX = Math.min(editorX, e.x);
|
||||||
|
let minY = Math.min(editorY, e.y);
|
||||||
|
|
||||||
|
let maxX = Math.max(editorX, e.x);
|
||||||
|
let maxY = Math.max(editorY, e.y);
|
||||||
|
|
||||||
|
let width = maxX - minX;
|
||||||
|
let height = maxY - minY;
|
||||||
|
|
||||||
|
// Apply a different class when the sizes pass the minimum size
|
||||||
|
// (i don't know if is good made like this)
|
||||||
|
if (width > minimumSizeX && height > minimumSizeY) {
|
||||||
|
editor.classList.add("can-draw");
|
||||||
|
} else {
|
||||||
|
editor.classList.remove("can-draw");
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.style.transform = `translate(${minX}px, ${minY}px)`;
|
||||||
|
editor.style.width = `${maxX - minX}px`;
|
||||||
|
editor.style.height = `${maxY - minY}px`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store the coordinates and trigger the function
|
||||||
|
// container.addEventListener("mousedown", (e) => {
|
||||||
|
// // Avoid inserting a new label if the user is clicking on a close button)
|
||||||
|
// if (e.target.tagName !== "BUTTON" && e.target.tagName !== "TEXTAREA") {
|
||||||
|
// startX = e.x;
|
||||||
|
// startY = e.y;
|
||||||
|
|
||||||
|
// // activate the editor
|
||||||
|
// showEditor = true;
|
||||||
|
// editor.classList.add("show-editor");
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// container.addEventListener("mouseup", (e) => {
|
||||||
|
// if (e.target.tagName !== "BUTTON" && e.target.tagName !== "TEXTAREA") {
|
||||||
|
// endX = e.x;
|
||||||
|
// endY = e.y;
|
||||||
|
|
||||||
|
// // disable the editor
|
||||||
|
// showEditor = false;
|
||||||
|
// editor.classList.remove("show-editor");
|
||||||
|
// editor.style.width = 0;
|
||||||
|
// editor.style.height = 0;
|
||||||
|
|
||||||
|
// // draw label
|
||||||
|
// drawLabel();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Edit the editor box using transform instead of left / top in order to be more efficient
|
||||||
|
// (but still with width and height ehm idk if this affects the performance a lot)
|
||||||
|
// (and it is something we must care of because this event is called like every frame that the mouse is dragged)
|
||||||
|
// container.addEventListener("mousemove", (e) => {
|
||||||
|
// if (showEditor) {
|
||||||
|
// let minX = Math.min(startX, e.x);
|
||||||
|
// let minY = Math.min(startY, e.y);
|
||||||
|
|
||||||
|
// let maxX = Math.max(startX, e.x);
|
||||||
|
// let maxY = Math.max(startY, e.y);
|
||||||
|
|
||||||
|
// let width = maxX - minX;
|
||||||
|
// let height = maxY - minY;
|
||||||
|
|
||||||
|
// // Apply a different class when the sizes pass the minimum size
|
||||||
|
// // (i don't know if is good made like this)
|
||||||
|
// if (width > minimumSizeX && height > minimumSizeY) {
|
||||||
|
// editor.classList.add("can-draw");
|
||||||
|
// } else {
|
||||||
|
// editor.classList.remove("can-draw");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// editor.style.transform = `translate(${minX}px, ${minY}px)`;
|
||||||
|
// editor.style.width = `${maxX - minX}px`;
|
||||||
|
// editor.style.height = `${maxY - minY}px`;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Check the mouse direction and create the Label
|
||||||
|
// The origin points of the label (because is positioned with top left) are always the lowest x and y values
|
||||||
|
// The width and height are the greater x and y values (because width and height cannot be negative)
|
||||||
|
|
||||||
|
function drawLabel() {
|
||||||
|
let minX = Math.min(startX, endX);
|
||||||
|
let minY = Math.min(startY, endY);
|
||||||
|
|
||||||
|
let maxX = Math.max(startX, endX);
|
||||||
|
let maxY = Math.max(startY, endY);
|
||||||
|
|
||||||
|
let width = maxX - minX;
|
||||||
|
let height = maxY - minY;
|
||||||
|
|
||||||
|
if (width > minimumSizeX && height > minimumSizeY) {
|
||||||
|
// Create a label and push it into the array of labels
|
||||||
|
let temporaryLabel = createLabel(minX, minY, width, height, labels.length);
|
||||||
|
temporaryLabel.classList.add("temporary");
|
||||||
|
|
||||||
|
let form = document.createElement("form");
|
||||||
|
let input = document.createElement("textarea");
|
||||||
|
input.placeholder = "Describe this area";
|
||||||
|
|
||||||
|
let insert = document.createElement("button");
|
||||||
|
insert.innerHTML = "Insert";
|
||||||
|
|
||||||
|
let cancel = document.createElement("button");
|
||||||
|
cancel.innerHTML = "x";
|
||||||
|
|
||||||
|
form.appendChild(input);
|
||||||
|
form.appendChild(insert);
|
||||||
|
form.appendChild(cancel);
|
||||||
|
|
||||||
|
temporaryLabel.appendChild(form);
|
||||||
|
|
||||||
|
imageTarget.appendChild(temporaryLabel);
|
||||||
|
|
||||||
|
new Promise(function (resolve, reject) {
|
||||||
|
// then if the user click insert and there is a value in the input-- > resolve the promise and return the text input to create the label,
|
||||||
|
// if the user click cancel-- > reject the promise and don't create the label
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
|
||||||
|
// Insert button
|
||||||
|
insert.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (input.value) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancel button
|
||||||
|
cancel.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Create the label
|
||||||
|
let label = createLabel(minX, minY, width, height, labels.length);
|
||||||
|
|
||||||
|
// Add the text input to the label
|
||||||
|
let text = document.createElement("p");
|
||||||
|
text.classList.add("label--text");
|
||||||
|
text.innerHTML = input.value;
|
||||||
|
label.appendChild(text);
|
||||||
|
|
||||||
|
let labelObj = {
|
||||||
|
position: {
|
||||||
|
x: minX,
|
||||||
|
y: minY,
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
},
|
||||||
|
index: labels.length,
|
||||||
|
text: input.value,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
userID: userID,
|
||||||
|
};
|
||||||
|
|
||||||
|
uploadLabel(labelObj);
|
||||||
|
labelsObj.push(labelObj);
|
||||||
|
labels.push(label);
|
||||||
|
|
||||||
|
imageTarget.appendChild(label);
|
||||||
|
createLabelTranscription(label);
|
||||||
|
})
|
||||||
|
.catch((e) => {})
|
||||||
|
.then(() => {
|
||||||
|
imageTarget.removeChild(temporaryLabel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the label element
|
||||||
|
function createLabel(x, y, width, height, index) {
|
||||||
|
let label = document.createElement("div");
|
||||||
|
label.classList.add("label");
|
||||||
|
label.style.left = `${x}%`;
|
||||||
|
label.style.top = `${y}%`;
|
||||||
|
label.style.width = `${width}%`;
|
||||||
|
label.style.height = `${height}%`;
|
||||||
|
|
||||||
|
// data attribute index maybe we will need it later maybe not
|
||||||
|
// with the index number of the label
|
||||||
|
label.setAttribute("data-index", index);
|
||||||
|
|
||||||
|
// Insert the number in the label
|
||||||
|
let labelNumber = document.createElement("p");
|
||||||
|
labelNumber.classList.add("label--number");
|
||||||
|
labelNumber.innerHTML = index + 1;
|
||||||
|
label.appendChild(labelNumber);
|
||||||
|
|
||||||
|
// Add a button for deleting the label
|
||||||
|
// TODO: reactive numbering oh no
|
||||||
|
let close = document.createElement("button");
|
||||||
|
close.classList.add("label--close");
|
||||||
|
close.innerHTML = "x";
|
||||||
|
close.addEventListener("click", (e) => {
|
||||||
|
label.remove();
|
||||||
|
});
|
||||||
|
label.appendChild(close);
|
||||||
|
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transcriptionPanel = document.getElementById("transcription-panel");
|
||||||
|
const transcriptionList = transcriptionPanel.querySelector("ol");
|
||||||
|
|
||||||
|
function createLabelTranscription(label) {
|
||||||
|
let transcription = document.createElement("li");
|
||||||
|
transcription.innerHTML = label.querySelector(".label--text").innerHTML;
|
||||||
|
|
||||||
|
transcriptionList.appendChild(transcription);
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadLabel(obj) {
|
||||||
|
fetch("https://hub.xpub.nl/soupboat/collecting-labels/", {
|
||||||
|
method: "POST", // or 'PUT'
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(obj),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
console.log("Success:", data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const load = document.getElementById("load-labels");
|
||||||
|
load.addEventListener("click", loadLabels);
|
||||||
|
|
||||||
|
function loadLabels() {
|
||||||
|
load.removeEventListener("click", loadLabels);
|
||||||
|
fetch("./labels.json")
|
||||||
|
.then((response) => {
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
labelsObj = [labelsObj, ...labels];
|
||||||
|
data.labels.forEach((label, index) => {
|
||||||
|
let labelElement = createLabel(
|
||||||
|
label.position.x,
|
||||||
|
label.position.y,
|
||||||
|
label.size.width,
|
||||||
|
label.size.height,
|
||||||
|
index
|
||||||
|
);
|
||||||
|
|
||||||
|
// THIS IS TEMPORARY
|
||||||
|
labelElement.style.backgroundColor = `hsla(${Math.floor(
|
||||||
|
(label.userID / 10000000000) * 255
|
||||||
|
)}, 100%, 75%, 0.2)`;
|
||||||
|
|
||||||
|
// Add the text input to the label
|
||||||
|
let text = document.createElement("p");
|
||||||
|
text.classList.add("label--text");
|
||||||
|
text.innerHTML = label.text;
|
||||||
|
labelElement.appendChild(text);
|
||||||
|
|
||||||
|
labelElement.classList.add("loaded");
|
||||||
|
|
||||||
|
imageTarget.appendChild(labelElement);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,20 @@
|
|||||||
|
const showInfo = document.getElementById("show-info");
|
||||||
|
const infoPanel = document.getElementById("info-panel");
|
||||||
|
|
||||||
|
showInfo.addEventListener("click", (e) => {
|
||||||
|
infoPanel.classList.add("active");
|
||||||
|
infoPanel.querySelector(".close").addEventListener("click", (e) => {
|
||||||
|
infoPanel.classList.remove("active");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const showTranscription = document.getElementById("show-transcription");
|
||||||
|
// declared previously in labels.js
|
||||||
|
// const transcriptionPanel = document.getElementById("transcription-panel");
|
||||||
|
|
||||||
|
showTranscription.addEventListener("click", (e) => {
|
||||||
|
transcriptionPanel.classList.add("active");
|
||||||
|
transcriptionPanel.querySelector(".close").addEventListener("click", (e) => {
|
||||||
|
transcriptionPanel.classList.remove("active");
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,25 @@
|
|||||||
|
// let fileName = "";
|
||||||
|
|
||||||
|
// window.addEventListener("load", function () {
|
||||||
|
// let input = document.querySelector('input[type="file"]');
|
||||||
|
// input.addEventListener("change", function () {
|
||||||
|
// if (this.files && this.files[0]) {
|
||||||
|
// fileName = this.files[0].name;
|
||||||
|
// let img = document.querySelector("img");
|
||||||
|
// img.onload = () => {
|
||||||
|
// img.classList.add("visible");
|
||||||
|
// input.classList.add("hidden");
|
||||||
|
// URL.revokeObjectURL(img.src); // no longer needed, free memory
|
||||||
|
// };
|
||||||
|
|
||||||
|
// img.src = URL.createObjectURL(this.files[0]); // set src to blob url
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
const imageButton = document.getElementById("show-image");
|
||||||
|
|
||||||
|
imageButton.addEventListener("click", (e) => {
|
||||||
|
let img = document.querySelector("img");
|
||||||
|
img.classList.toggle("hidden");
|
||||||
|
});
|
@ -0,0 +1 @@
|
|||||||
|
const userID = Math.round(Math.random() * 10000000000);
|
@ -0,0 +1,322 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-form {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 90vw;
|
||||||
|
height: 90vh;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor {
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
border: 1px solid tomato;
|
||||||
|
opacity: 0.5;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 150;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor.can-draw {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor.show-editor {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(250, 99, 72, 0.2);
|
||||||
|
/* border: 1px solid currentColor; */
|
||||||
|
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.2);
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.temporary {
|
||||||
|
background: none;
|
||||||
|
/* border: 1px dashed tomato; */
|
||||||
|
box-shadow: none;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.temporary form {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.temporary textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 1ch;
|
||||||
|
border: none;
|
||||||
|
background-color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.temporary textarea:focus {
|
||||||
|
outline: 1px dashed tomato;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.temporary button {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.temporary button + button {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.temporary .label--number,
|
||||||
|
.label.temporary .label--close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label--number {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 4px;
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label--close {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
padding: 0 4px;
|
||||||
|
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
background: none;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.label--text {
|
||||||
|
margin: 1ch 0;
|
||||||
|
padding: 0 1ch;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-input {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 200;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
background-color: rgba(255, 99, 71, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-input.visible {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal input {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
border-bottom: 1px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal input:focus {
|
||||||
|
outline: none;
|
||||||
|
background-color: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-input button {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cancel {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#target {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-container img {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
|
object-fit: contain;
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-container img.visible {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info,
|
||||||
|
.transcription {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 50;
|
||||||
|
|
||||||
|
padding: 24px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
width: 25%;
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
|
background-color: #111;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
transform: translateX(100%);
|
||||||
|
transition: transform 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transcription.active,
|
||||||
|
.info.active {
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: transform 0.6s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transcription .title,
|
||||||
|
.info .title {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transcription ol {
|
||||||
|
padding: 0;
|
||||||
|
list-style-position: inside;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#show-info,
|
||||||
|
#show-transcription,
|
||||||
|
.close,
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 0 4px;
|
||||||
|
|
||||||
|
border: 1px solid currentColor;
|
||||||
|
|
||||||
|
color: tomato;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#show-transcription:hover,
|
||||||
|
#show-info:hover {
|
||||||
|
border: 1px solid tomato;
|
||||||
|
background-color: tomato;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
right: 24px;
|
||||||
|
top: 32px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#export-text:hover,
|
||||||
|
.close:hover {
|
||||||
|
border: 1px solid white;
|
||||||
|
background-color: white;
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
#export-text {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 50;
|
||||||
|
|
||||||
|
padding: 24px;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav > * {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
const exportText = document.getElementById("export-text");
|
||||||
|
|
||||||
|
exportText.addEventListener("click", (e) => {
|
||||||
|
let text = document.querySelector("ol").innerText;
|
||||||
|
let title = fileName.slice(0, fileName.indexOf(".")) || "export";
|
||||||
|
title += ".txt";
|
||||||
|
download(text, title, "text/plain;charset=utf-8");
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/13405129/javascript-create-and-save-file
|
||||||
|
// Thank you Kanchu!
|
||||||
|
// Function to download data to a file
|
||||||
|
function download(data, filename, type) {
|
||||||
|
var file = new Blob([data], { type: type });
|
||||||
|
if (window.navigator.msSaveOrOpenBlob)
|
||||||
|
// IE10+
|
||||||
|
window.navigator.msSaveOrOpenBlob(file, filename);
|
||||||
|
else {
|
||||||
|
// Others
|
||||||
|
var a = document.createElement("a"),
|
||||||
|
url = URL.createObjectURL(file);
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
setTimeout(function () {
|
||||||
|
document.body.removeChild(a);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue