diff --git a/index.css b/index.css index 17913cb4..ed81773f 100644 --- a/index.css +++ b/index.css @@ -2036,7 +2036,7 @@ div.textual span, fill: none; } -div#notes { +#notes { display: none; position: fixed; width: 28vw; @@ -2046,22 +2046,47 @@ div#notes { border: 1px solid #5e4fa2; background: rgba(255, 250, 228, 0.7); box-shadow: 2px 2px 5px -3px #3a2804; - white-space: pre-line; } -div#notesHeader { +@media screen and (max-width: 600px) { + #notes { + width: 50vw; + } +} + +#notesHeader { font-weight: bold; font-size: 1.3em; - padding: 0 0 4px 14px; + padding: 16px 0 4px 12px; border-bottom: 1px solid #5e4fa2; } -div#notesBody { - padding: 0 1em; +#notesBody { + padding: 14px 12px; max-height: 80vh; overflow: auto; } +#notesBody > iframe { + pointer-events: none; + user-select: none; +} + +#notesBody p { + margin: 4px; +} + +#notesLegend { + height: 87%; + outline: 0; + overflow-y: auto; + padding: 0.6em; + font-family: Copperplate, monospace; + background-color: #fff; + border: 1px solid #dedede; + color: #000; +} + svg.button { position: relative; background-color: transparent; @@ -2318,45 +2343,6 @@ svg.button { stroke-width: 0; } -.pell { - border: 1px solid hsla(0, 0%, 4%, 0.1); -} - -.pell, -.pell-content { - box-sizing: border-box; -} - -.pell-content { - height: 14em; - outline: 0; - overflow-y: auto; - padding: 0.6em; - font-family: Copperplate, monospace; - background-color: #fff; - border: 1px solid #dedede; -} - -.pell-actionbar { - background-color: #fff; - border: 1px solid #dedede; - border-bottom: 0; -} - -.pell-button { - background-color: transparent; - border: none; - cursor: pointer; - height: 30px; - outline: 0; - width: 30px; - vertical-align: bottom; -} - -.pell-button-selected { - background-color: #f0f0f0; -} - #debug { font-size: 1px; opacity: 0.8; diff --git a/index.html b/index.html index 698bfb94..517d69dd 100644 --- a/index.html +++ b/index.html @@ -2911,22 +2911,20 @@ -
")},quote:{icon:"“ ”",title:"Quote",result:()=>o("formatBlock","
")},olist:{icon:"#",title:"Ordered List",result:()=>o("insertOrderedList")},ulist:{icon:"•",title:"Unordered List",result:()=>o("insertUnorderedList")},code:{icon:"</>",title:"Code",result:()=>o("formatBlock","")},line:{icon:"―",title:"Horizontal Line",result:()=>o("insertHorizontalRule")},link:{icon:"🔗",title:"Link",result:()=>navigator.clipboard.readText().then(e=>o("createLink",e))},image:{icon:"📷",title:"Image",result:()=>{navigator.clipboard.readText().then(e=>o("insertImage",e)),o("enableObjectResizing")}}},r={actionbar:"pell-actionbar",button:"pell-button",content:"pell-content",selected:"pell-button-selected"};return{exec:o,init:i=>{const a=i.actions?i.actions.map(e=>"string"==typeof e?l[e]:l[e.name]?{...l[e.name],...e}:e):Object.keys(l).map(e=>l[e]),s={...r,...i.classes},c=i.defaultParagraphSeparator||"div",u=n("div");u.className=s.actionbar,t(i.element,u);const d=i.element.content=n("div");return d.contentEditable=!0,d.className=s.content,d.oninput=(({target:{firstChild:e}})=>{e&&3===e.nodeType?o("formatBlock",`<${c}>`):"
"===d.innerHTML&&(d.innerHTML=""),i.onChange(d.innerHTML)}),d.onkeydown=(e=>{"Enter"===e.key&&"blockquote"===(e=>document.queryCommandValue(e))("formatBlock")&&setTimeout(()=>o("formatBlock",`<${c}>`),0)}),t(i.element,d),a.forEach(i=>{const o=n("button");if(o.className=s.button,o.innerHTML=i.icon,o.title=i.title,o.setAttribute("type","button"),o.onclick=(()=>i.result()&&d.focus()),i.state){const t=()=>o.classList[i.state()?"add":"remove"](s.selected);e(d,"keyup",t),e(d,"mouseup",t),e(o,"click",t)}t(u,o)}),i.styleWithCSS&&o("styleWithCSS"),o("defaultParagraphSeparator",c),i.element}}}); \ No newline at end of file diff --git a/main.js b/main.js index 72c726ab..713b5021 100644 --- a/main.js +++ b/main.js @@ -2,7 +2,7 @@ // https://github.com/Azgaar/Fantasy-Map-Generator "use strict"; -const version = "1.73"; // generator version +const version = "1.731"; // generator version document.title += " v" + version; // switches to disable/enable logging features @@ -434,9 +434,10 @@ function showWelcomeMessage() { const discord = link("https://discordapp.com/invite/X7E84HU", "Discord server"); const patreon = link("https://www.patreon.com/azgaar", "Patreon"); - alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version ${version}. + alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version ${version}. This version is compatible with ${changelog}, loaded .map files will be auto-updated. -Latest changes: +
Latest changes: +
- Advanced notes editor
- Zones editor: filter by type
- Color picker: new hatchings
- New style presets: Cyberpunk and Atlas
@@ -444,7 +445,6 @@ function showWelcomeMessage() {- 4 new textures
- Province capture logic rework
- Button to release all provinces
-- Limit military units by biome, state, culture and religion
Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.
diff --git a/modules/markers-generator.js b/modules/markers-generator.js index 1aefca5e..50acf34d 100644 --- a/modules/markers-generator.js +++ b/modules/markers-generator.js @@ -519,7 +519,7 @@ window.Markers = (function () { const dungeonSeed = `${seed}${cell}`; const name = "Dungeon"; - const legend = `Undiscovered dungeon. See One page dungeon`; + const legend = `Undiscovered dungeon. See One page dungeon`; notes.push({id, name, legend}); quantity--; } diff --git a/modules/ui/general.js b/modules/ui/general.js index 5823afec..b7a6255d 100644 --- a/modules/ui/general.js +++ b/modules/ui/general.js @@ -70,7 +70,8 @@ function mouseMove() { const point = d3.mouse(this); const i = findCell(point[0], point[1]); // pack cell id if (i === undefined) return; - showNotes(d3.event, i); + + showNotes(d3.event); const g = findGridCell(point[0], point[1]); // grid cell id if (tooltip.dataset.main) showMainTip(); else showMapTooltip(point, d3.event, i, g); @@ -78,7 +79,7 @@ function mouseMove() { } // show note box on hover (if any) -function showNotes(e, i) { +function showNotes(e) { if (notesEditor.offsetParent) return; let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id; if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id; diff --git a/modules/ui/notes-editor.js b/modules/ui/notes-editor.js index 3ad4b979..872437fa 100644 --- a/modules/ui/notes-editor.js +++ b/modules/ui/notes-editor.js @@ -1,25 +1,22 @@ "use strict"; function editNotes(id, name) { + // elements + const notesLegend = document.getElementById("notesLegend"); + const notesName = document.getElementById("notesName"); + const notesSelect = document.getElementById("notesSelect"); + const notesPin = document.getElementById("notesPin"); + // update list of objects - const select = document.getElementById("notesSelect"); - select.options.length = 0; + notesSelect.options.length = 0; for (const note of notes) { - select.options.add(new Option(note.id, note.id)); + notesSelect.options.add(new Option(note.id, note.id)); } - // initiate pell (html editor) - const notesText = document.getElementById("notesText"); - notesText.innerHTML = ""; - const editor = Pell.init({ - element: notesText, - onChange: html => { - const note = notes.find(note => note.id === select.value); - if (!note) return; - note.legend = html; - showNote(note); - } - }); + // update pin notes icon + const notesArePinned = options.pinNotes; + if (notesArePinned) notesPin.classList.add("pressed"); + else notesPin.classList.remove("pressed"); // select an object if (notes.length || id) { @@ -29,136 +26,161 @@ function editNotes(id, name) { if (!name) name = id; note = {id, name, legend: ""}; notes.push(note); - select.options.add(new Option(id, id)); + notesSelect.options.add(new Option(id, id)); } - select.value = id; + + notesSelect.value = id; notesName.value = note.name; - editor.content.innerHTML = note.legend; - showNote(note); + notesLegend.innerHTML = note.legend; + initEditor(); + updateNotesBox(note); } else { - editor.content.innerHTML = "There are no added notes. Click on element (e.g. label) and add a free text note"; - document.getElementById("notesName").value = ""; + // if notes array is empty + notesName.value = ""; + notesLegend.innerHTML = "No notes added. Click on an element (e.g. label or marker) and add a free text note"; } - // open a dialog $("#notesEditor").dialog({ title: "Notes Editor", - minWidth: "40em", - width: "50vw", - position: {my: "center", at: "center", of: "svg"} + width: "70vw", + height: window.innerHeight * 0.75, + position: {my: "center", at: "center", of: "svg"}, + close: removeEditor }); + $("[aria-describedby='notesEditor']").css("top", "10vh"); if (modules.editNotes) return; modules.editNotes = true; // add listeners - document.getElementById("notesSelect").addEventListener("change", changeObject); + document.getElementById("notesSelect").addEventListener("change", changeElement); document.getElementById("notesName").addEventListener("input", changeName); - document.getElementById("notesPin").addEventListener("click", () => (options.pinNotes = !options.pinNotes)); - document.getElementById("notesSpeak").addEventListener("click", () => speak(editor.content.innerHTML)); + document.getElementById("notesLegend").addEventListener("blur", updateLegend); + document.getElementById("notesPin").addEventListener("click", toggleNotesPin); document.getElementById("notesFocus").addEventListener("click", validateHighlightElement); document.getElementById("notesDownload").addEventListener("click", downloadLegends); document.getElementById("notesUpload").addEventListener("click", () => legendsToLoad.click()); document.getElementById("legendsToLoad").addEventListener("change", function () { uploadFile(this, uploadLegends); }); - document.getElementById("notesClearStyle").addEventListener("click", clearStyle); document.getElementById("notesRemove").addEventListener("click", triggerNotesRemove); - function showNote(note) { - document.getElementById("notes").style.display = "block"; + async function initEditor() { + if (!window.tinymce) { + const url = "https://cdn.tiny.cloud/1/4i6a79ymt2y0cagke174jp3meoi28vyecrch12e5puyw3p9a/tinymce/5/tinymce.min.js"; + try { + await import(url); + } catch (error) { + // error may be caused by failed request being cached, try again with random hash + try { + const hash = Math.random().toString(36).substring(2, 15); + await import(`${url}#${hash}`); + } catch (error) { + console.error(error); + } + } + } + + if (window.tinymce) { + tinymce.init({ + selector: "#notesLegend", + height: "90%", + menubar: false, + plugins: `autolink lists link charmap print formatpainter casechange code fullscreen image link media table paste hr checklist wordcount`, + toolbar: `code | undo redo | bold italic strikethrough | forecolor backcolor | formatpainter removeformat | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media table | fontselect fontsizeselect | blockquote hr casechange checklist charmap | print fullscreen`, + media_alt_source: false, + media_poster: false, + setup: editor => { + editor.on("Change", updateLegend); + } + }); + } + } + + function updateLegend() { + const note = notes.find(note => note.id === notesSelect.value); + if (!note) return tip("Note element is not found", true, "error", 4000); + + const isTinyEditorActive = window.tinymce?.activeEditor; + note.legend = isTinyEditorActive ? tinymce.activeEditor.getContent() : notesLegend.innerHTML; + updateNotesBox(note); + } + + function updateNotesBox(note) { document.getElementById("notesHeader").innerHTML = note.name; document.getElementById("notesBody").innerHTML = note.legend; } - function changeObject() { + function changeElement() { const note = notes.find(note => note.id === this.value); - if (!note) return; + if (!note) return tip("Note element is not found", true, "error", 4000); + notesName.value = note.name; - editor.content.innerHTML = note.legend; + notesLegend.innerHTML = note.legend; + updateNotesBox(note); + + if (window.tinymce) tinymce.activeEditor.setContent(note.legend); } function changeName() { - const id = document.getElementById("notesSelect").value; - const note = notes.find(note => note.id === id); - if (!note) return; + const note = notes.find(note => note.id === notesSelect.value); + if (!note) return tip("Note element is not found", true, "error", 4000); + note.name = this.value; - showNote(note); } function validateHighlightElement() { - const select = document.getElementById("notesSelect"); - const element = document.getElementById(select.value); + const element = document.getElementById(notesSelect.value); + if (element) return highlightElement(element, 3); - // if element is not found - if (element === null) { - alertMessage.innerHTML = "Related element is not found. Would you like to remove the note?"; - $("#alert").dialog({ - resizable: false, - title: "Element not found", - buttons: { - Remove: function () { - $(this).dialog("close"); - removeLegend(); - }, - Keep: function () { - $(this).dialog("close"); - } - } - }); - return; - } - - highlightElement(element, 3); // if element is found + confirmationDialog({ + title: "Element not found", + message: "Note element is not found. Would you like to remove the note?", + confirm: "Remove", + cancel: "Keep", + onConfirm: removeLegend + }); } function downloadLegends() { - const data = JSON.stringify(notes); + const notesData = JSON.stringify(notes); const name = getFileName("Notes") + ".txt"; - downloadFile(data, name); + downloadFile(notesData, name); } function uploadLegends(dataLoaded) { - if (!dataLoaded) { - tip("Cannot load the file. Please check the data format", false, "error"); - return; - } + if (!dataLoaded) return tip("Cannot load the file. Please check the data format", false, "error"); notes = JSON.parse(dataLoaded); - document.getElementById("notesSelect").options.length = 0; + notesSelect.options.length = 0; editNotes(notes[0].id, notes[0].name); } - function clearStyle() { - editor.content.innerHTML = editor.content.textContent; - } - function triggerNotesRemove() { - alertMessage.innerHTML = "Are you sure you want to remove the selected note?"; - $("#alert").dialog({ - resizable: false, + confirmationDialog({ title: "Remove note", - buttons: { - Remove: function () { - $(this).dialog("close"); - removeLegend(); - }, - Keep: function () { - $(this).dialog("close"); - } - } + message: "Are you sure you want to remove the selected note? There is no way to undo this action", + confirm: "Remove", + onConfirm: removeLegend }); } function removeLegend() { - const select = document.getElementById("notesSelect"); - const index = notes.findIndex(n => n.id === select.value); + const index = notes.findIndex(n => n.id === notesSelect.value); notes.splice(index, 1); - select.options.length = 0; + notesSelect.options.length = 0; if (!notes.length) { $("#notesEditor").dialog("close"); return; } - notesText.innerHTML = ""; editNotes(notes[0].id, notes[0].name); } + + function toggleNotesPin() { + options.pinNotes = !options.pinNotes; + this.classList.toggle("pressed"); + } + + function removeEditor() { + if (window.tinymce) tinymce.remove(); + } }