diff --git a/birb.js b/birb.js index d12d343..f299455 100644 --- a/birb.js +++ b/birb.js @@ -563,6 +563,23 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI super("", () => {}); } } + + class StickyNote { + /** + * @param {string} id + * @param {string} [site] + * @param {string} [content] + * @param {number} [top] + * @param {number} [left] + */ + constructor(id, site="", content="", top=0, left=0) { + this.id = id; + this.site = site; + this.content = content; + this.top = top; + this.left = left; + } + } const menuItems = [ new MenuItem(`Pet ${birdBirb()}`, pet), @@ -579,6 +596,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI new DebugMenuItem("Disable Debug", () => { debugMode = false; }), + new MenuItem("Add Sticky Note", newStickyNote), new Separator(), new MenuItem("Settings", () => switchMenuItems(settingsItems), false), ]; @@ -657,6 +675,8 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI let unlockedSpecies = [DEFAULT_BIRD]; let visible = true; let lastPetTimestamp = 0; + /** @type {StickyNote[]} */ + let stickyNotes = []; /** * @returns {boolean} Whether the script is running in a userscript extension context @@ -689,6 +709,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI userSettings = saveData.settings ?? {}; unlockedSpecies = saveData.unlockedSpecies ?? [DEFAULT_BIRD]; currentSpecies = saveData.currentSpecies ?? DEFAULT_BIRD; + stickyNotes = []; + if (saveData.stickyNotes) { + for (let note of saveData.stickyNotes) { + if (note.id) { + stickyNotes.push(new StickyNote(note.id, note.site, note.content, note.top, note.left)); + } + } + } + log(stickyNotes.length + " sticky notes loaded"); switchSpecies(currentSpecies); } @@ -698,6 +727,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI currentSpecies: currentSpecies, settings: userSettings }; + if (stickyNotes.length > 0) { + saveData.stickyNotes = stickyNotes.map(note => ({ + id: note.id, + site: note.site, + content: note.content, + top: note.top, + left: note.left + })); + } if (isUserScript()) { log("Saving data to UserScript storage"); // @ts-ignore @@ -739,41 +777,84 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return settings().birbMode ? "Birb" : "Bird"; } - function makeStickyNote(top = 500, left = 500) { + function newStickyNote() { + const id = Date.now().toString(); + const site = window.location.href; + const stickyNote = new StickyNote(id, site, ""); + const element = renderStickyNote(stickyNote); + centerElement(element); + stickyNote.top = parseInt(element.style.top, 10); + stickyNote.left = parseInt(element.style.left, 10); + stickyNotes.push(stickyNote); + save(); + } + + /** + * @param {StickyNote} stickyNote + * @returns {HTMLElement} + */ + function renderStickyNote(stickyNote) { let html = `
Sticky Note
x
- +
` - const stickyNote = makeElement("birb-window"); - stickyNote.classList.add("birb-sticky-note"); - stickyNote.innerHTML = html; + const noteElement = makeElement("birb-window"); + noteElement.classList.add("birb-sticky-note"); + noteElement.innerHTML = html; - stickyNote.style.top = `${top}px`; - stickyNote.style.left = `${left}px`; - document.body.appendChild(stickyNote); + noteElement.style.top = `${stickyNote.top}px`; + noteElement.style.left = `${stickyNote.left}px`; + document.body.appendChild(noteElement); - makeDraggable(stickyNote.querySelector(".birb-window-header")); + makeDraggable(noteElement.querySelector(".birb-window-header"), true, (top, left) => { + stickyNote.top = top; + stickyNote.left = left; + save(); + }); - const closeButton = stickyNote.querySelector(".birb-window-close"); + const closeButton = noteElement.querySelector(".birb-window-close"); if (closeButton) { makeClosable(() => { - confirm("Are you sure you want to delete this sticky note?") && stickyNote.remove(); + if (confirm("Are you sure you want to delete this sticky note?")) { + deleteStickyNote(stickyNote); + noteElement.remove(); + } }, closeButton); } - centerElement(stickyNote); + const textarea = noteElement.querySelector(".birb-sticky-note-input"); + if (textarea && textarea instanceof HTMLTextAreaElement) { + let saveTimeout; + // Save after debounce + textarea.addEventListener("input", () => { + stickyNote.content = textarea.value; + clearTimeout(saveTimeout); + saveTimeout = setTimeout(() => { + save(); + }, 500); + }); + } // On window resize window.addEventListener("resize", () => { - const modTop = `${top - Math.min(window.innerHeight - stickyNote.offsetHeight, top)}px`; - const modLeft = `${left - Math.min(window.innerWidth - stickyNote.offsetWidth, left)}px`; - stickyNote.style.transform = `translate(-${modLeft}, -${modTop})`; + const modTop = `${stickyNote.top - Math.min(window.innerHeight - noteElement.offsetHeight, stickyNote.top)}px`; + const modLeft = `${stickyNote.left - Math.min(window.innerWidth - noteElement.offsetWidth, stickyNote.left)}px`; + noteElement.style.transform = `translate(-${modLeft}, -${modTop})`; }); + return noteElement; + } + + /** + * @param {StickyNote} stickyNote + */ + function deleteStickyNote(stickyNote) { + stickyNotes = stickyNotes.filter(note => note.id !== stickyNote.id); + save(); } function init() { @@ -828,7 +909,12 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } }); - makeStickyNote(); + // Render all sticky notes + for (let stickyNote of stickyNotes) { + if (stickyNote.site === window.location.href.split("?")[0]) { + renderStickyNote(stickyNote); + } + } setInterval(update, 1000 / 60); } @@ -1354,8 +1440,9 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI /** * @param {HTMLElement|null} element The element to detect drag events on * @param {boolean} [parent] Whether to move the parent element when the child is dragged + * @param {(top: number, left: number) => void} [callback] Callback for when element is moved */ - function makeDraggable(element, parent = true) { + function makeDraggable(element, parent = true, callback = () => {}) { if (!element) { return; } @@ -1384,11 +1471,19 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI e.preventDefault(); }); - document.addEventListener("mouseup", () => { + document.addEventListener("mouseup", (e) => { + if (isMouseDown) { + callback(elementToMove.offsetTop, elementToMove.offsetLeft); + e.preventDefault(); + } isMouseDown = false; }); - document.addEventListener("touchend", () => { + document.addEventListener("touchend", (e) => { + if (isMouseDown) { + callback(elementToMove.offsetTop, elementToMove.offsetLeft); + e.preventDefault(); + } isMouseDown = false; }); diff --git a/dist/birb.js b/dist/birb.js index efc85b0..c9bd5c7 100644 --- a/dist/birb.js +++ b/dist/birb.js @@ -881,6 +881,23 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI super("", () => {}); } } + + class StickyNote { + /** + * @param {string} id + * @param {string} [site] + * @param {string} [content] + * @param {number} [top] + * @param {number} [left] + */ + constructor(id, site="", content="", top=0, left=0) { + this.id = id; + this.site = site; + this.content = content; + this.top = top; + this.left = left; + } + } const menuItems = [ new MenuItem(`Pet ${birdBirb()}`, pet), @@ -897,6 +914,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI new DebugMenuItem("Disable Debug", () => { debugMode = false; }), + new MenuItem("Add Sticky Note", newStickyNote), new Separator(), new MenuItem("Settings", () => switchMenuItems(settingsItems), false), ]; @@ -975,6 +993,8 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI let unlockedSpecies = [DEFAULT_BIRD]; let visible = true; let lastPetTimestamp = 0; + /** @type {StickyNote[]} */ + let stickyNotes = []; /** * @returns {boolean} Whether the script is running in a userscript extension context @@ -1007,6 +1027,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI userSettings = saveData.settings ?? {}; unlockedSpecies = saveData.unlockedSpecies ?? [DEFAULT_BIRD]; currentSpecies = saveData.currentSpecies ?? DEFAULT_BIRD; + stickyNotes = []; + if (saveData.stickyNotes) { + for (let note of saveData.stickyNotes) { + if (note.id) { + stickyNotes.push(new StickyNote(note.id, note.site, note.content, note.top, note.left)); + } + } + } + log(stickyNotes.length + " sticky notes loaded"); switchSpecies(currentSpecies); } @@ -1016,6 +1045,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI currentSpecies: currentSpecies, settings: userSettings }; + if (stickyNotes.length > 0) { + saveData.stickyNotes = stickyNotes.map(note => ({ + id: note.id, + site: note.site, + content: note.content, + top: note.top, + left: note.left + })); + } if (isUserScript()) { log("Saving data to UserScript storage"); // @ts-ignore @@ -1057,41 +1095,84 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return settings().birbMode ? "Birb" : "Bird"; } - function makeStickyNote(top = 500, left = 500) { + function newStickyNote() { + const id = Date.now().toString(); + const site = window.location.href; + const stickyNote = new StickyNote(id, site, ""); + const element = renderStickyNote(stickyNote); + centerElement(element); + stickyNote.top = parseInt(element.style.top, 10); + stickyNote.left = parseInt(element.style.left, 10); + stickyNotes.push(stickyNote); + save(); + } + + /** + * @param {StickyNote} stickyNote + * @returns {HTMLElement} + */ + function renderStickyNote(stickyNote) { let html = `
Sticky Note
x
- +
` - const stickyNote = makeElement("birb-window"); - stickyNote.classList.add("birb-sticky-note"); - stickyNote.innerHTML = html; + const noteElement = makeElement("birb-window"); + noteElement.classList.add("birb-sticky-note"); + noteElement.innerHTML = html; - stickyNote.style.top = `${top}px`; - stickyNote.style.left = `${left}px`; - document.body.appendChild(stickyNote); + noteElement.style.top = `${stickyNote.top}px`; + noteElement.style.left = `${stickyNote.left}px`; + document.body.appendChild(noteElement); - makeDraggable(stickyNote.querySelector(".birb-window-header")); + makeDraggable(noteElement.querySelector(".birb-window-header"), true, (top, left) => { + stickyNote.top = top; + stickyNote.left = left; + save(); + }); - const closeButton = stickyNote.querySelector(".birb-window-close"); + const closeButton = noteElement.querySelector(".birb-window-close"); if (closeButton) { makeClosable(() => { - confirm("Are you sure you want to delete this sticky note?") && stickyNote.remove(); + if (confirm("Are you sure you want to delete this sticky note?")) { + deleteStickyNote(stickyNote); + noteElement.remove(); + } }, closeButton); } - centerElement(stickyNote); + const textarea = noteElement.querySelector(".birb-sticky-note-input"); + if (textarea && textarea instanceof HTMLTextAreaElement) { + let saveTimeout; + // Save after debounce + textarea.addEventListener("input", () => { + stickyNote.content = textarea.value; + clearTimeout(saveTimeout); + saveTimeout = setTimeout(() => { + save(); + }, 500); + }); + } // On window resize window.addEventListener("resize", () => { - const modTop = `${top - Math.min(window.innerHeight - stickyNote.offsetHeight, top)}px`; - const modLeft = `${left - Math.min(window.innerWidth - stickyNote.offsetWidth, left)}px`; - stickyNote.style.transform = `translate(-${modLeft}, -${modTop})`; + const modTop = `${stickyNote.top - Math.min(window.innerHeight - noteElement.offsetHeight, stickyNote.top)}px`; + const modLeft = `${stickyNote.left - Math.min(window.innerWidth - noteElement.offsetWidth, stickyNote.left)}px`; + noteElement.style.transform = `translate(-${modLeft}, -${modTop})`; }); + return noteElement; + } + + /** + * @param {StickyNote} stickyNote + */ + function deleteStickyNote(stickyNote) { + stickyNotes = stickyNotes.filter(note => note.id !== stickyNote.id); + save(); } function init() { @@ -1146,7 +1227,12 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } }); - makeStickyNote(); + // Render all sticky notes + for (let stickyNote of stickyNotes) { + if (stickyNote.site === window.location.href.split("?")[0]) { + renderStickyNote(stickyNote); + } + } setInterval(update, 1000 / 60); } @@ -1672,8 +1758,9 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI /** * @param {HTMLElement|null} element The element to detect drag events on * @param {boolean} [parent] Whether to move the parent element when the child is dragged + * @param {(top: number, left: number) => void} [callback] Callback for when element is moved */ - function makeDraggable(element, parent = true) { + function makeDraggable(element, parent = true, callback = () => {}) { if (!element) { return; } @@ -1702,11 +1789,19 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI e.preventDefault(); }); - document.addEventListener("mouseup", () => { + document.addEventListener("mouseup", (e) => { + if (isMouseDown) { + callback(elementToMove.offsetTop, elementToMove.offsetLeft); + e.preventDefault(); + } isMouseDown = false; }); - document.addEventListener("touchend", () => { + document.addEventListener("touchend", (e) => { + if (isMouseDown) { + callback(elementToMove.offsetTop, elementToMove.offsetLeft); + e.preventDefault(); + } isMouseDown = false; }); diff --git a/dist/birb.user.js b/dist/birb.user.js index 1237ac0..7db94cb 100644 --- a/dist/birb.user.js +++ b/dist/birb.user.js @@ -894,6 +894,23 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI super("", () => {}); } } + + class StickyNote { + /** + * @param {string} id + * @param {string} [site] + * @param {string} [content] + * @param {number} [top] + * @param {number} [left] + */ + constructor(id, site="", content="", top=0, left=0) { + this.id = id; + this.site = site; + this.content = content; + this.top = top; + this.left = left; + } + } const menuItems = [ new MenuItem(`Pet ${birdBirb()}`, pet), @@ -910,6 +927,7 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI new DebugMenuItem("Disable Debug", () => { debugMode = false; }), + new MenuItem("Add Sticky Note", newStickyNote), new Separator(), new MenuItem("Settings", () => switchMenuItems(settingsItems), false), ]; @@ -988,6 +1006,8 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI let unlockedSpecies = [DEFAULT_BIRD]; let visible = true; let lastPetTimestamp = 0; + /** @type {StickyNote[]} */ + let stickyNotes = []; /** * @returns {boolean} Whether the script is running in a userscript extension context @@ -1020,6 +1040,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI userSettings = saveData.settings ?? {}; unlockedSpecies = saveData.unlockedSpecies ?? [DEFAULT_BIRD]; currentSpecies = saveData.currentSpecies ?? DEFAULT_BIRD; + stickyNotes = []; + if (saveData.stickyNotes) { + for (let note of saveData.stickyNotes) { + if (note.id) { + stickyNotes.push(new StickyNote(note.id, note.site, note.content, note.top, note.left)); + } + } + } + log(stickyNotes.length + " sticky notes loaded"); switchSpecies(currentSpecies); } @@ -1029,6 +1058,15 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI currentSpecies: currentSpecies, settings: userSettings }; + if (stickyNotes.length > 0) { + saveData.stickyNotes = stickyNotes.map(note => ({ + id: note.id, + site: note.site, + content: note.content, + top: note.top, + left: note.left + })); + } if (isUserScript()) { log("Saving data to UserScript storage"); // @ts-ignore @@ -1070,41 +1108,84 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI return settings().birbMode ? "Birb" : "Bird"; } - function makeStickyNote(top = 500, left = 500) { + function newStickyNote() { + const id = Date.now().toString(); + const site = window.location.href; + const stickyNote = new StickyNote(id, site, ""); + const element = renderStickyNote(stickyNote); + centerElement(element); + stickyNote.top = parseInt(element.style.top, 10); + stickyNote.left = parseInt(element.style.left, 10); + stickyNotes.push(stickyNote); + save(); + } + + /** + * @param {StickyNote} stickyNote + * @returns {HTMLElement} + */ + function renderStickyNote(stickyNote) { let html = `
Sticky Note
x
- +
` - const stickyNote = makeElement("birb-window"); - stickyNote.classList.add("birb-sticky-note"); - stickyNote.innerHTML = html; + const noteElement = makeElement("birb-window"); + noteElement.classList.add("birb-sticky-note"); + noteElement.innerHTML = html; - stickyNote.style.top = `${top}px`; - stickyNote.style.left = `${left}px`; - document.body.appendChild(stickyNote); + noteElement.style.top = `${stickyNote.top}px`; + noteElement.style.left = `${stickyNote.left}px`; + document.body.appendChild(noteElement); - makeDraggable(stickyNote.querySelector(".birb-window-header")); + makeDraggable(noteElement.querySelector(".birb-window-header"), true, (top, left) => { + stickyNote.top = top; + stickyNote.left = left; + save(); + }); - const closeButton = stickyNote.querySelector(".birb-window-close"); + const closeButton = noteElement.querySelector(".birb-window-close"); if (closeButton) { makeClosable(() => { - confirm("Are you sure you want to delete this sticky note?") && stickyNote.remove(); + if (confirm("Are you sure you want to delete this sticky note?")) { + deleteStickyNote(stickyNote); + noteElement.remove(); + } }, closeButton); } - centerElement(stickyNote); + const textarea = noteElement.querySelector(".birb-sticky-note-input"); + if (textarea && textarea instanceof HTMLTextAreaElement) { + let saveTimeout; + // Save after debounce + textarea.addEventListener("input", () => { + stickyNote.content = textarea.value; + clearTimeout(saveTimeout); + saveTimeout = setTimeout(() => { + save(); + }, 500); + }); + } // On window resize window.addEventListener("resize", () => { - const modTop = `${top - Math.min(window.innerHeight - stickyNote.offsetHeight, top)}px`; - const modLeft = `${left - Math.min(window.innerWidth - stickyNote.offsetWidth, left)}px`; - stickyNote.style.transform = `translate(-${modLeft}, -${modTop})`; + const modTop = `${stickyNote.top - Math.min(window.innerHeight - noteElement.offsetHeight, stickyNote.top)}px`; + const modLeft = `${stickyNote.left - Math.min(window.innerWidth - noteElement.offsetWidth, stickyNote.left)}px`; + noteElement.style.transform = `translate(-${modLeft}, -${modTop})`; }); + return noteElement; + } + + /** + * @param {StickyNote} stickyNote + */ + function deleteStickyNote(stickyNote) { + stickyNotes = stickyNotes.filter(note => note.id !== stickyNote.id); + save(); } function init() { @@ -1159,7 +1240,12 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI } }); - makeStickyNote(); + // Render all sticky notes + for (let stickyNote of stickyNotes) { + if (stickyNote.site === window.location.href.split("?")[0]) { + renderStickyNote(stickyNote); + } + } setInterval(update, 1000 / 60); } @@ -1685,8 +1771,9 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI /** * @param {HTMLElement|null} element The element to detect drag events on * @param {boolean} [parent] Whether to move the parent element when the child is dragged + * @param {(top: number, left: number) => void} [callback] Callback for when element is moved */ - function makeDraggable(element, parent = true) { + function makeDraggable(element, parent = true, callback = () => {}) { if (!element) { return; } @@ -1715,11 +1802,19 @@ Promise.all([loadSpriteSheetPixels(SPRITE_SHEET), loadSpriteSheetPixels(DECORATI e.preventDefault(); }); - document.addEventListener("mouseup", () => { + document.addEventListener("mouseup", (e) => { + if (isMouseDown) { + callback(elementToMove.offsetTop, elementToMove.offsetLeft); + e.preventDefault(); + } isMouseDown = false; }); - document.addEventListener("touchend", () => { + document.addEventListener("touchend", (e) => { + if (isMouseDown) { + callback(elementToMove.offsetTop, elementToMove.offsetLeft); + e.preventDefault(); + } isMouseDown = false; });