commit 7366fb89724b1fbbf4a12d17aea8520f7b12cb13 Author: kam (from the studio) Date: Wed Nov 24 15:44:47 2021 +0100 relative positioning diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..a1bd10a Binary files /dev/null and b/.DS_Store differ diff --git a/assets/map_description_H.jpg b/assets/map_description_H.jpg new file mode 100644 index 0000000..25a0328 Binary files /dev/null and b/assets/map_description_H.jpg differ diff --git a/assets/map_description_V.jpg b/assets/map_description_V.jpg new file mode 100644 index 0000000..e8c1806 Binary files /dev/null and b/assets/map_description_V.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..d5548d2 --- /dev/null +++ b/index.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + Collecting Labels + + +
+
+ +
+
+
+
+ + + + + diff --git a/labels.js b/labels.js new file mode 100644 index 0000000..9eaeac3 --- /dev/null +++ b/labels.js @@ -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); + }); + }); +} diff --git a/labels.json b/labels.json new file mode 100644 index 0000000..c594648 --- /dev/null +++ b/labels.json @@ -0,0 +1 @@ +{"labels":[{"position":{"x":372,"y":211},"size":{"width":65,"height":52},"index":0,"text":"hello","timestamp":1637677092049,"userID":7333518059},{"position":{"x":487,"y":186},"size":{"width":98,"height":223},"index":0,"text":"place for food and drinks, but also laptops","timestamp":1637677147973,"userID":4737334710},{"position":{"x":830,"y":237},"size":{"width":78,"height":68},"index":1,"text":"i ate silver noodles here","timestamp":1637677150312,"userID":7333518059},{"position":{"x":780,"y":176},"size":{"width":90,"height":88},"index":0,"text":"yes","timestamp":1637677148724,"userID":5500151117},{"position":{"x":987,"y":180},"size":{"width":77,"height":82},"index":1,"text":"yes","timestamp":1637677155873,"userID":5500151117},{"position":{"x":877,"y":308},"size":{"width":92,"height":90},"index":2,"text":"i drank some fine wine here","timestamp":1637677160365,"userID":7333518059},{"position":{"x":964,"y":603},"size":{"width":131,"height":51},"index":0,"text":"Here I like to drink my morning coffie. sip sip sip in the morning glory","timestamp":1637677162844,"userID":5359759454},{"position":{"x":397,"y":190},"size":{"width":47,"height":46},"index":2,"text":"yes","timestamp":1637677163041,"userID":5500151117},{"position":{"x":455,"y":172},"size":{"width":51,"height":63},"index":1,"text":"documents","timestamp":1637677177678,"userID":5359759454},{"position":{"x":600,"y":288},"size":{"width":59,"height":61},"index":1,"text":"where I usually sit; comfortable","timestamp":1637677177935,"userID":4737334710},{"position":{"x":410,"y":288},"size":{"width":60,"height":62},"index":2,"text":"hello, Supi <3","timestamp":1637677192260,"userID":4737334710},{"position":{"x":362,"y":186},"size":{"width":129,"height":103},"index":3,"text":"i ate some really good onigiri here","timestamp":1637677194735,"userID":7333518059},{"position":{"x":454,"y":164},"size":{"width":65,"height":257},"index":3,"text":"yes","timestamp":1637677196365,"userID":5500151117},{"position":{"x":524,"y":389},"size":{"width":44,"height":30},"index":2,"text":"light source","timestamp":1637677204585,"userID":5359759454},{"position":{"x":497,"y":430},"size":{"width":73,"height":60},"index":3,"text":"there should be a chair here","timestamp":1637677205205,"userID":4737334710},{"position":{"x":367,"y":307},"size":{"width":103,"height":81},"index":4,"text":"i drank a glass of water here","timestamp":1637677210086,"userID":7333518059},{"position":{"x":455,"y":246},"size":{"width":35,"height":46},"index":3,"text":"Laptop","timestamp":1637677215083,"userID":5359759454},{"position":{"x":610,"y":580},"size":{"width":183,"height":83},"index":5,"text":"sometimes here there's some wind blowing","timestamp":1637677223929,"userID":7333518059},{"position":{"x":466,"y":599},"size":{"width":104,"height":117},"index":4,"text":"way to the kitchen","timestamp":1637677223650,"userID":4737334710},{"position":{"x":668,"y":640},"size":{"width":158,"height":71},"index":6,"text":"digital wind","timestamp":1637677228131,"userID":7333518059},{"position":{"x":1053,"y":380},"size":{"width":80,"height":178},"index":4,"text":"light source","timestamp":1637677228104,"userID":5359759454},{"position":{"x":700,"y":598},"size":{"width":69,"height":55},"index":4,"text":"yes","timestamp":1637677237000,"userID":5500151117},{"position":{"x":364,"y":622},"size":{"width":64,"height":33},"index":5,"text":"yes","timestamp":1637677244002,"userID":5500151117},{"position":{"x":974,"y":560},"size":{"width":133,"height":100},"index":5,"text":"gateway to breathing either fresh air or some weed","timestamp":1637677249143,"userID":4737334710},{"position":{"x":357,"y":598},"size":{"width":77,"height":66},"index":5,"text":"this is my archive and I really love it","timestamp":1637677256980,"userID":5359759454},{"position":{"x":748,"y":679},"size":{"width":195,"height":68},"index":7,"text":"kind of an electric approach to the politics of air circulation","timestamp":1637677261745,"userID":7333518059},{"position":{"x":809,"y":186},"size":{"width":73,"height":67},"index":6,"text":"where my backpack lays","timestamp":1637677266375,"userID":4737334710},{"position":{"x":871,"y":293},"size":{"width":29,"height":65},"index":6,"text":"yes","timestamp":1637677278331,"userID":5500151117},{"position":{"x":973,"y":666},"size":{"width":166,"height":71},"index":8,"text":"and a barrier here","timestamp":1637677282909,"userID":7333518059},{"position":{"x":800,"y":602},"size":{"width":165,"height":52},"index":7,"text":"do you ever watch TV?","timestamp":1637677291804,"userID":4737334710},{"position":{"x":758,"y":207},"size":{"width":267,"height":139},"index":0,"text":"hello from emma","timestamp":1637677318303,"userID":817257294},{"position":{"x":1002,"y":189},"size":{"width":66,"height":178},"index":6,"text":"light grey, soft fabric, many evening spent on this couch","timestamp":1637677320080,"userID":5359759454},{"position":{"x":797,"y":212},"size":{"width":297,"height":177},"index":9,"text":"social area","timestamp":1637677332480,"userID":7333518059},{"position":{"x":1041,"y":429},"size":{"width":76,"height":144},"index":7,"text":"shadows","timestamp":1637677333874,"userID":5359759454},{"position":{"x":979,"y":314},"size":{"width":88,"height":55},"index":8,"text":"this area is reserved to my friends","timestamp":1637677355022,"userID":5359759454},{"position":{"x":887,"y":314},"size":{"width":33,"height":33},"index":9,"text":"dark grey","timestamp":1637677364142,"userID":5359759454},{"position":{"x":987,"y":179},"size":{"width":76,"height":83},"index":7,"text":"weeknights","timestamp":1637677366233,"userID":5500151117},{"position":{"x":626,"y":653},"size":{"width":139,"height":83},"index":1,"text":"how many kilos this can hold up?","timestamp":1637677395737,"userID":817257294},{"position":{"x":634,"y":619},"size":{"width":131,"height":48},"index":10,"text":"ich kann mich gar nicht an das hier erinnern","timestamp":1637677395866,"userID":5359759454},{"position":{"x":396,"y":189},"size":{"width":47,"height":48},"index":8,"text":"always","timestamp":1637677407512,"userID":5500151117},{"position":{"x":1033,"y":536},"size":{"width":130,"height":217},"index":2,"text":"space towards the outside","timestamp":1637677410213,"userID":817257294},{"position":{"x":822,"y":674},"size":{"width":114,"height":69},"index":3,"text":"no communication with others area","timestamp":1637677419164,"userID":817257294},{"position":{"x":1083,"y":208},"size":{"width":40,"height":151},"index":11,"text":"heating source","timestamp":1637677423499,"userID":5359759454},{"position":{"x":454,"y":163},"size":{"width":64,"height":260},"index":9,"text":"always","timestamp":1637677425431,"userID":5500151117},{"position":{"x":364,"y":623},"size":{"width":62,"height":29},"index":10,"text":"task-based","timestamp":1637677441969,"userID":5500151117},{"position":{"x":997,"y":592},"size":{"width":97,"height":86},"index":12,"text":"door to the outside world","timestamp":1637677442313,"userID":5359759454},{"position":{"x":1106,"y":193},"size":{"width":68,"height":461},"index":10,"text":"there are some venetian curtains. they are really difficult to use. they are crazy. super sensible. they have an agency of their own. it is frustrating you cannot really rely on them you cannot make plan for your environment because they decide for you. aaaaa annoing ","timestamp":1637677446917,"userID":7333518059},{"position":{"x":699,"y":596},"size":{"width":70,"height":60},"index":11,"text":"task-based","timestamp":1637677452189,"userID":5500151117},{"position":{"x":347,"y":654},"size":{"width":121,"height":76},"index":11,"text":"there is a nice poster here eheheh","timestamp":1637677459563,"userID":7333518059},{"position":{"x":872,"y":178},"size":{"width":112,"height":84},"index":12,"text":"no","timestamp":1637677462196,"userID":5500151117},{"position":{"x":359,"y":172},"size":{"width":40,"height":35},"index":13,"text":"dusty corner","timestamp":1637677463311,"userID":5359759454},{"position":{"x":987,"y":263},"size":{"width":77,"height":100},"index":13,"text":"no","timestamp":1637677467680,"userID":5500151117},{"position":{"x":375,"y":210},"size":{"width":223,"height":170},"index":4,"text":"social area","timestamp":1637677468767,"userID":817257294},{"position":{"x":796,"y":185},"size":{"width":270,"height":189},"index":5,"text":"social area","timestamp":1637677469854,"userID":817257294},{"position":{"x":406,"y":486},"size":{"width":74,"height":53},"index":12,"text":"perceived space here is really different","timestamp":1637677475878,"userID":7333518059},{"position":{"x":369,"y":599},"size":{"width":175,"height":144},"index":6,"text":"what should i do in here?","timestamp":1637677480310,"userID":817257294},{"position":{"x":584,"y":189},"size":{"width":49,"height":48},"index":14,"text":"no","timestamp":1637677479675,"userID":5500151117},{"position":{"x":586,"y":292},"size":{"width":46,"height":48},"index":15,"text":"no","timestamp":1637677485937,"userID":5500151117},{"position":{"x":397,"y":291},"size":{"width":48,"height":47},"index":16,"text":"no","timestamp":1637677491073,"userID":5500151117},{"position":{"x":475,"y":658},"size":{"width":76,"height":85},"index":13,"text":"the entrance of a place is a statement","timestamp":1637677493037,"userID":7333518059},{"position":{"x":672,"y":458},"size":{"width":309,"height":93},"index":7,"text":"area where you can lie down and stare at the ceiling","timestamp":1637677505314,"userID":817257294},{"position":{"x":779,"y":175},"size":{"width":90,"height":88},"index":17,"text":"always","timestamp":1637677507883,"userID":5500151117},{"position":{"x":420,"y":410},"size":{"width":111,"height":57},"index":8,"text":"food","timestamp":1637677517837,"userID":817257294},{"position":{"x":872,"y":293},"size":{"width":26,"height":66},"index":18,"text":"always","timestamp":1637677520846,"userID":5500151117},{"position":{"x":904,"y":344},"size":{"width":50,"height":61},"index":9,"text":"food","timestamp":1637677522873,"userID":817257294},{"position":{"x":1039,"y":229},"size":{"width":65,"height":167},"index":10,"text":"confortable zone","timestamp":1637677531973,"userID":817257294},{"position":{"x":856,"y":447},"size":{"width":200,"height":179},"index":14,"text":"this space is perceived really different. in the map is big and empty but actually it feels more warm ","timestamp":1637677534206,"userID":7333518059},{"position":{"x":462,"y":287},"size":{"width":89,"height":149},"index":15,"text":"what's under this table???????","timestamp":1637677545595,"userID":7333518059},{"position":{"x":986,"y":364},"size":{"width":390,"height":448},"index":0,"text":"hi","timestamp":1637681065599,"userID":2944965634},{"position":{"x":877,"y":282},"size":{"width":45,"height":38},"index":1,"text":"kjbkhdvacöoisdjc-jbwcluzqfwcij","timestamp":1637681075013,"userID":2944965634},{"position":{"x":923,"y":318},"size":{"width":46,"height":48},"index":2,"text":"lkxvb","timestamp":1637681108261,"userID":2944965634},{"position":{"x":395,"y":169},"size":{"width":554,"height":349},"index":0,"text":"hellooooo","timestamp":1637682059598,"userID":492584205}]} \ No newline at end of file diff --git a/panels.js b/panels.js new file mode 100644 index 0000000..7153dee --- /dev/null +++ b/panels.js @@ -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"); + }); +}); diff --git a/picture.js b/picture.js new file mode 100644 index 0000000..4fe68be --- /dev/null +++ b/picture.js @@ -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"); +}); diff --git a/session.js b/session.js new file mode 100644 index 0000000..8b85997 --- /dev/null +++ b/session.js @@ -0,0 +1 @@ +const userID = Math.round(Math.random() * 10000000000); diff --git a/storedLabels.js b/storedLabels.js new file mode 100644 index 0000000..e69de29 diff --git a/style.css b/style.css new file mode 100644 index 0000000..ea52269 --- /dev/null +++ b/style.css @@ -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; +} diff --git a/text-export.js b/text-export.js new file mode 100644 index 0000000..4ae4f0f --- /dev/null +++ b/text-export.js @@ -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); + } +}