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