Files
diy-startpage/startpage.html
T

2999 lines
111 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>startpage</title>
<style>
/*
* GENERAL STATE HELPERS
*/
.hidden {
visibility: hidden;
display: none;
}
/*
* CURSOR STUFF
body {
cursor: url(""), auto;
}
.removable:hover {
cursor: url(""), crosshair;
}
.grabbable:hover {
cursor: grab;
}
*/
body {
height: 100vh;
overflow: hidden;
}
.movable {
position: fixed;
user-select: none;
}
.container {
z-index: 998;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
overflow: auto;
}
.containerHeader {
display: flex;
flex-direction: column;
width: 100%;
margin: auto;
}
.section {
margin-top: 0rem;
}
.settingsContainer {
background-color: rgba(173, 165, 165, 0.9);
z-index: 999;
border: solid black 2px;
border-radius: 10px;
display: block;
top: 2rem;
align-self: flex-end;
height: 25rem;
width: 40rem;
overflow: scroll;
}
/*
* SETTINGS TABS
*/
#tabList {
display: flex;
justify-content: space-between;
position: sticky;
top: 0;
background-color: rgba(173, 165, 165, 1);
padding: 5px 5px 5px 5px;
border-bottom: solid 2px black;
}
.tab {
margin-right: 2rem;
text-wrap: nowrap;
color: black;
}
.tab:hover {
cursor: pointer;
text-decoration: underline;
}
.tab.active {
font-weight: bold;
text-decoration: underline;
}
.tabContent {
display: none;
flex-direction: column;
padding-left: 1.5rem;
padding-right: 1.5rem;
}
a.visited {
color: black;
}
#containers {
padding-left: .25rem;
}
/*
* SETTINGS PAGES
*/
label {
align-self: start;
}
input {
align-self: flex-end;
}
input[type="checkbox"],
input[type="radio"] {
width: auto;
}
.expandableMenuToggle {
display: flex;
flex-direction: row;
justify-content: space-between;
cursor: pointer;
margin: 0 0 0 0;
padding: .5rem 0rem .5rem .5rem;
}
.expandableMenuToggle.active, .expandableMenuToggle:hover {
background-color: rgba(213, 205, 205, 1);
}
.expandedMenuIndicator {
margin: 0 1rem 0 0;
font-size: 2rem;
}
.expandableMenu {
display: none;
padding: 1rem 0 0 1rem;
margin-bottom: 1rem;
border-left: 4px solid rgba(213, 205, 205);
}
.menuHeader {
text-align: center;
width: 100%;
font-style: italic;
font-weight: bold;
padding: 1rem 0;
margin: 0;
}
.containerListing {
border: 1px solid black;
margin-bottom: .5rem;
}
.manageButtons {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 1rem 1rem;
}
.bookmarkListingContainer {
display: flex;
flex-direction: column;
}
.sectionListingContainer {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: .5rem;
}
.linkListingContainer {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: .5rem;
}
.containerOptionListing {
display: flex;
justify-content: space-between;
}
#noContainerWarning {
font-style: italic;
}
input {
width: 4rem;
}
input[type="text"] {
width: 4rem;
}
input[type="color"] {
width: 4rem;
}
button {
border: none;
border-radius: 50px;
padding: .25rem .5rem .25rem .5rem;
}
.bookmarkListingButtons {
}
.bookmarkButton {
}
.bookmarkButton:hover {
cursor: pointer;
background-color: rgba(173, 165, 165, 0.8);
}
.bookmarkDeleteButton {
}
.deleteButton:hover {
background-color: rgba(173, 165, 165, 0.8);
color: red;
}
hr {
width: 75%;
}
#editToggle {
z-index: 999;
position: fixed;
font-size: 0.75rem;
}
#editToggle:hover {
cursor: pointer;
}
</style>
</head>
<body>
<div style="display: flex; flex-direction: column">
<a id="editToggle" onClick="toggleEditMode()" style="align-self: flex-end"
>edit page
</a>
</div>
<div id="settingsContainer" class="movable settingsContainer hidden">
<div id="tabList">
<div>
<a class="tab" id="containerTab">layers</a>
<a class="tab" id="globalSettingsTab" style="margin: 0 2rem 0 0;">global settings</a>
</div>
<div style="text-align: right;">
<a class="tab" id="helpTab" style="margin: 0;">help</a>
<a class="tab" id="ioTab" style="margin: 0 0 0 2rem;">import / export</a>
</div>
</div>
<div>
<div id="helpPage" class="tabContent">
<p>
you are now editing the page!<br /><br />
this is the settings box - there are various tabs containing various
customization options for your startpage experience.<br /><br />
this box is adjustable, just like every layer you add to the page.
you can drag it around the page, and dragging with right click will
adjust the size of the layer.<br /><br />
on the "layers" tab, you can add two types of layers to the page:
</p>
<ol>
<li>
<b>bookmark layer:</b><br />a customizable box that can serve as a clock,
tell you the date, and hold bookmarks all at once. it can also just be
one of those things, or any combination.<br /><br />to group a bookmarks
together within a layer, enter the respective links with the same "section".
uncategorized (no section specified) bookmarks will stay at the top of their layer.
</li><br />
<li>
<b>image layer:</b><br />paste any URL that points directly at an image or GIF
to add it to the page.<br /><br />use "free transform" in the "global
settings" tab to change the image resizing style.
</li>
</ol>
<p>
<b>your changes will be saved to the page when you exit editing mode.</b><br /><br />
to go back to the last saved state while still editing the page (or to revert/undo
live changes), refresh the page without disabling / exiting "editing" mode.
<br /><br />
that concludes the basics. there's some more advanced information below:
</p>
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>tips and tricks</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu">
<p>
there are some keyboard shortcuts for your convenience:<br />
"e" = enables editing mode<br />
esc = disables editing mode (and saves page)<br />
<!-- "1"-"4" = jump between settings box tabs<br /><br /> -->
</p>
<hr />
<p>
<u>precise adjustments:</u>
if you click on a slider to focus it, and then keep your mouse hovered over it,
you can use the arrow keys to make minute adjustments.
</p>
<hr />
<p>
<u>floating text</u>:<br />
for floating text anywhere on the page, remove the border and set the
background opacity of a layer to zero.<br /> then, add new sections to the
layer without any link information (sections can be added on their own).
customize the layer's "section" text to your liking and place the text
wherever you want.
</p>
</div>
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>setting fonts</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu">
<p>
you can use any font that is available on
<a href="https://fonts.google.com/" target="blank">google fonts</a>.
<br /><br />
beware, if you have multiple font families selected (it keeps track of
all of the fonts you press "get font" on), only the first one will be
loaded. also, only regular versions of fonts are supported.<br /><br />
to load a font:<br />
1.) on google fonts, open a font you like, and press the "get font"
button. then, press the "get embed code" button.<br /><br />
2.) there will be a section on the right with various tabs ("web",
"android", "ios", ..) under the "web" tab (you are already probably
on it), click the "copy code" button under the option titled:
"embed code in the &lt;head&gt; of your html" (it should be a
few lines starting with "&lt;link ...", right at the very top)<br /><br />
3.) paste copied contents into the "change font" input, found on the desired
layer's "text options"
</p>
</div>
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>sharing / backing pages up</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu">
<p>
you can export your entire current page's setup to your clipboard in the
"import / export" tab. open a text file and paste the contents to store
your theme for later or for sharing with others.<br /><br />
to load in a theme, simply paste the output of the export button into the
"import" input, and refresh the page.
</p>
</div>
</div>
<div id="containerPage" class="tabContent">
<div style="display:flex;flex-direction:column;align-items:center;">
<h2 class="menuHeader">add bookmark layer</h2>
<input
id="newTextContainerNameInput"
placeholder="(optional) layer name"
style="width: 100%"
type="text"
/>
<button id="newTextContainerCreateButton" onclick="createNewTextContainer(this)">
create new layer
</button>
</div>
<div style="display:flex;flex-direction:column;align-items:center;">
<h2 class="menuHeader">add image to page</h2>
<input
id="newImageContainerNameInput"
placeholder="(optional) image name"
style="width: 100%;"
type="text"
/>
<input
id="newImageContainerUrlInput"
placeholder="direct URL to image"
style="width: 100%;"
type="text"
/>
<button id="newImageContainerCreateButton" onclick="createNewImageContainer(this)">
place image
</button>
<a href="https://imgur.com/upload" target="_blank" style="color: black; font-size: .75rem;">imgur upload for convenience</a>
</div>
<div>
<h2 class="menuHeader">layers</h2>
<div style="display: flex; flex-direction: row; justify-content: space-around; margin-bottom: 1rem;">
<button onclick="collapseLayerListings()">collapse all listings</button>
<button onclick="expandLayerListings()">expand all listings</button>
</div>
<div id="containers"></div>
</div>
</div>
<div id="ioPage" class="tabContent">
<p>export current setup to clipboard (see help tab for more information):</p>
<div>
<button style="height: 3rem; width: 100%; margin-bottom: 2.5rem;" onclick="exportData()">export data</button>
</div>
<hr />
<div style="display: flex; flex-direction: column; margin-top: 2.5rem; gap: 1rem;">
<input id="importDataInput" style="width: 90%; align-self: center" placeholder="paste data to import here"/>
<button style="width: 100%; height: 3rem;" onclick="importData()" >
import data
</button>
</div>
</div>
<div id="globalSettingsPage" class="tabContent">
<p class="menuHeader">image manipulation</p>
<div class="containerOptionListing" style="margin-bottom: 2rem;">
<label for="imageRatioToggle">image free transform mode</label>
<input type="checkbox" id="imageRatioToggle" />
</div>
<p class="menuHeader">change page wallpaper</p>
<div class="containerOptionListing">
<label for="wallpaperUrl">set image as wallpaper: </label>
<div style="display: flex; flex-direction: column; align-items: end;">
<input id="wallpaperUrl" placeholder="paste image URL" style="width: 10rem" />
<br />
<button id="submitUrl" onclick="changeWallpaper()">
set image
</button>
</div>
</div>
<div class="containerOptionListing">
<label for="wallpaperRepeatToggle"> tile wallpaper? </label>
<input id="wallpaperRepeatToggle" type="checkbox" />
</div>
<div class="containerOptionListing" style="margin-bottom: 2rem;">
<label for="wallpaperColorPicker">or, set wallpaper as solid color: </label>
<input type="color" id="wallpaperColorPicker" />
</div>
<p class="menuHeader">change cursors</p>
<p>for reference: <a href="https://www.totallyfreecursors.com/">cursors</a></p>
<div class="containerOptionListing">
<label for="pointerCursorInput">change normal cursor:</label>
<div style="display: flex; flex-direction: column; align-items: end;">
<input id="pointerCursorInput" placeholder="paste link to image URL" />
<button onclick="setDefaultCursor()">set normal cursor</button>
</div>
</div>
<div class="containerOptionListing">
<label for="grabCursorInput">change grab cursor:</label>
<div style="display: flex; flex-direction: column; align-items: end;">
<input id="grabCursorInput" placeholder="paste link to image URL" />
<button onclick="setGrabCursor()">set grabbing cursor</button>
</div>
</div>
<div class="containerOptionListing" style="margin-bottom: 2rem;">
<label for="linkCursorInput">change link cursor:</label>
<div style="display: flex; flex-direction: column; align-items: end;">
<input id="linkCursorInput" placeholder="paste link to cursor image URL here" />
<button onclick="setLinkHoverCursor()">set link hover cursor</button>
</div>
</div>
<p class="menuHeader">audio</p>
<div class="containerOptionListing">
<label for=audioLinkInput">paste a direct URL to an audio file:</label>
<div style="display: flex; flex-direction: column; align-items: end;">
<input id="audioLinkInput" />
<button onclick="setAudioLink()">set audio</button>
</div>
</div>
<div class="containerOptionListing">
<label for="autoplayAudioToggle">auto-play when page loads? </label>
<input id="autoplayAudioToggle" type="checkbox" checked />
</div>
<audio id="audio" controls style="width: 100%">
<source id="audioSource" src="" type="audio/mp3" />
</audio>
<p>
(note: this will require either white-listing this site in your browser
or generally enabling "allow auto-play audio" in your browser's settings.
</p>
</div>
</div>
</body>
<script>
// cookie holder for coordinates + size of settings menu
let settingsMenuData = {};
// cookie holders for container data
let containerDataMap = new Map();
let numberTotalContainers = 0;
let zIndexMap = new Map();
// default values for easy resetting
let defaultTextContainerSettings = {
backgroundRgba: "rgba(255,255,255,.9)",
backgroundAlpha: 100,
borderWidth: "5",
borderRadius: "0",
borderColor: "grey",
fontName: "",
fontCode: "",
sectionColor: "",
sectionSize: "16",
sectionItalic: false,
sectionBold: false,
linkColor: "",
linkSize: "16",
enableDate: true,
enableClock: true,
headerColor: "",
headerSize: "16",
headerBold: false,
headerItalic: false,
enableDivider: true,
dividerColor: "",
textAlign: "center",
shadowX: "0",
shadowY: "0",
shadowBlur: "0",
shadowRgba: "rgba(255,255,255,.90)",
zIndex: ""
};
let defaultImageContainerSettings = {
borderWidth: "0",
borderRadius: "0",
borderColor: "grey",
shadowX: "0",
shadowY: "0",
shadowBlur: "0",
shadowRgba: "rgba(255,255,255,.90)",
zIndex: ""
};
let wallpaper = "";
// local states
let editing = false;
let moving = false;
let resizing = false;
let changingElement;
let lastMouseX = 0;
let lastMouseY = 0;
let justImported = false;
let activeTabId = "";
let keepImageRatio = true;
let repeatWallpaper = false;
let audioLink = "";
let autoplayAudio = true;
let numberOfTextContainers = 0;
let numberOfImageContainers = 0;
let cursors = {};
class Container {
id;
name;
x;
y;
height;
width;
imageUrl;
containerSettings;
sections;
clockIntervalId;
constructor(
id,
name,
x,
y,
height,
width,
imageUrl,
containerSettings,
sections
) {
/* check if id is already used
if (containerDataMap.has(name.replace(" ", "-").toLowerCase())) {
alert("that name is already used, please use another");
return;
}*/
// ensure there are no brackets in the name
if (name.indexOf("<") != -1 || name.indexOf(">") != -1) {
alert("no brackets in the name please");
return;
}
let loadingFromSave = (id != undefined);
let isImage = (imageUrl != undefined);
// if constructing image container
if (isImage) {
numberOfImageContainers++;
if (loadingFromSave) {
this.name = name;
this.id = id;
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.containerSettings = containerSettings;
this.imageUrl = imageUrl;
}
else {
numberTotalContainers++;
if (name == "") this.name = "image " + numberOfImageContainers;
else this.name = name;
this.id = findLowestAvailableId();
this.imageUrl = imageUrl;
// deep copy default settings
this.containerSettings = JSON.parse(JSON.stringify(defaultImageContainerSettings));
zIndexMap.set(String(numberOfImageContainers + numberOfTextContainers), this.id);
this.containerSettings.zIndex = numberOfImageContainers + numberOfTextContainers;
}
this.initializeImageContainer();
}
// if constructing text container
else {
numberOfTextContainers++;
if (loadingFromSave) {
this.name = name;
this.id = id;
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.containerSettings = containerSettings;
this.sections = sections;
}
else {
numberTotalContainers++;
if (name == "") this.name = "bookmark layer " + numberOfTextContainers;
else this.name = name;
this.id = findLowestAvailableId();
this.sections = {};
// deep copy default settings
this.containerSettings = JSON.parse(JSON.stringify(defaultTextContainerSettings));
zIndexMap.set(String(numberOfTextContainers + numberOfImageContainers), this.id);
this.containerSettings.zIndex = numberOfImageContainers + numberOfTextContainers;
}
this.initializeTextContainer();
this.loadBookmarks();
}
this.applySettings();
this.addContainerEventListeners()
// and save
containerDataMap.set(this.id, this);
}
/**
* creates image element and lets
* loadBookmarks() and applySettings() do the rest
*/
initializeImageContainer() {
document.body.insertAdjacentHTML(
"beforeend",
`
<img
class="movable userImage"
id="${this.id}"
style="z-index: ${numberOfImageContainers + numberOfTextContainers};"
src="${this.imageUrl}"
draggable=false
/>
`
);
}
/**
* creates shell HTML to hold bookmarks and lets
* loadBookmarks() and applySettings() do the rest
*/
initializeTextContainer() {
// insert default container HTML
document.body.insertAdjacentHTML(
"beforeend",
`
<div class="movable container" id=${this.id}>
<div>
<div id=${this.id + "-header"} class="containerHeader">
<div id=${this.id + "-date"}></div>
<div id=${this.id + "-clock"}></div>
</div>
<hr id=${this.id + "-divider"} />
<div id=${this.id + "-bookmarks"} ></div>
<div>
</div>
`
);
/** set up the time */
const timeFormat = new Intl.DateTimeFormat([], {
timeZone: "America/New_York",
hour12: false,
hour: "numeric",
minute: "numeric",
second: "numeric",
});
// set time immediately so it shows upon load
document.getElementById(this.id + "-clock").innerText =
timeFormat.format(new Date());
// set time on interval to continue to update
this.clockIntervalId = setInterval(() => {
document.getElementById(this.id + "-clock").innerText =
timeFormat.format(new Date());
}, 1000);
/** set up date */
const dateFormat = new Intl.DateTimeFormat([], {
weekday: "long",
day: "numeric",
month: "long",
});
document.getElementById(this.id + "-date").innerText =
dateFormat.format(new Date());
// save data if not loading existing container
if (this.x == undefined && this.y == undefined) {
let newContainer = document.getElementById(this.id);
this.x = newContainer.offsetLeft;
this.y = newContainer.offsetTop;
this.height = newContainer.offsetHeight;
this.width = newContainer.offsetWidth;
}
}
/**
* creates container options menu in page settings menu
*/
/**
* TODO:
* -> remove "images" from wallpaper tab
* -> furthermore, condense all global settings into one tab
* - including wallpaper, audio, cursor
* rename settings form div ids (see "note to self")
*/
loadBookmarkListings() {
if (Object.keys(this.sections).length == 0) {
let bookmarkListings = document.getElementById(this.id + "-bookmark-menu--listings");
bookmarkListings.innerHTML = `<p style="padding: 0 0 .25rem 0; font-style: italic;">(there are no bookmarks to manage...)</p>`;
return;
}
let sectionData = Object.values(this.sections);
let containerSettings = this.containerSettings;
let bookmarkListings = document.getElementById(this.id + "-bookmark-menu--listings");
bookmarkListings.innerHTML = "";
// [in]render the section listings
for (let i = 0; i < sectionData.length; i++) {
bookmarkListings.insertAdjacentHTML(
"beforeend",
`
${i == 0 ? "" : `<br />`}
<div id="${this.id}-section-listing--${i}" class="sectionListingContainer">
<span class=${this.id + "-section"}>
${sectionData[i].label == "" ? "<u>uncategorized links</u>: " : "<u>section</u>: " + sectionData[i].label}
</span>
<div class="bookmarkListingButtons">
${(i > 1) ? `
<button onClick="reorderSection(this, 'up')" class="bookmarkButton">
up
</button>
` : `` }
${(i > 0) && (i < sectionData.length - 1) ? `
<button onClick="reorderSection(this, 'down')" class="bookmarkButton">
down
</button>
` : `` }
<button onClick="deleteSection(this)" class="deleteButton">delete section</button>
</div>
</div>
`
);
}
// [re]render the link listings
for (let s = sectionData.length - 1; s >= 0; s--) {
for (let l = sectionData[s].links.length - 1; l >= 0; l--) {
let targetSection = document.getElementById(
this.id + "-section-listing--" + s
);
targetSection.insertAdjacentHTML(
"afterend",
`
<div id="${this.id}-section-listing--${s}--${l}" class="linkListingContainer">
<span class="${this.id}-link">
${sectionData[s].links[l].label}
</span>
<div class="bookmarkListingButtons">
${l > 0 ? `
<button onClick="reorderLink(this, 'up')" class="bookmarkButton">
up
</button>
` : ``
}
${l < sectionData[s].links.length - 1 ? `
<button onClick="reorderLink(this, 'down')" class="bookmarkButton">
down
</button>
` : ``
}
<button onClick="deleteLink(this)" class="deleteButton">delete link</button>
</div>
</div>
`
);
}
}
}
createTextContainerMenuListing() {
let zindex = parseInt(this.containerSettings.zIndex);
let upButtons = `
<div>
<button id="` + this.id + `--move-up-button"} onclick="reorderContainer(this, 'up')">move up</button>
<button id="` + this.id + `--move-top-button"} onclick="reorderContainer(this, 'top')">summon to top</button>
</div>
`;
let downButtons = `
<div ${zindex == 1 ? `` : `style="margin-top: .25rem;"`}>
<button id="` + this.id + `--move-down-button"} onclick="reorderContainer(this, 'down')">move down</button>
<button id="` + this.id + `--move-bottom-button"} onclick="reorderContainer(this, 'bottom')">banish to bottom</button>
</div>
`;
document.getElementById("containers").insertAdjacentHTML(
"afterbegin",
`
<div class="containerListing" id=${this.id + "-container-listing"}>
<div class="expandableMenuToggle active" onclick="toggleExpandableMenu(this)">
<p id=${this.id + "--listing-header"}>[${this.containerSettings.zIndex}]: ${this.name}</p>
<p class="expandedMenuIndicator">-</p>
</div>
<div class="expandableMenu" id=${this.id + "-settings-menu"} style="display: block; margin: 0;">
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>${this.name}: bookmarks</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu" id=${this.id + "-bookmark-menu"} >
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>manage bookmarks / sections</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu">
<div id=${this.id + "-bookmark-menu--listings"} class="bookmarkListingContainer">
</div>
</div>
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>add new bookmarks / sections</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu" id=${this.id + "-bookmark-menu--add-new"} >
<input
id=${this.id + "-url-input"}
type="text" name="url"
placeholder="enter URL for bookmark"
style="width: 90%;"
/>
<br />
<input
id=${this.id + "-label-input"}
type="text" name="label"
placeholder="enter label for bookmark"
style="width: 90%"
/>
<br />
<input
id=${this.id + "-section-input"}
type="text" name="section"
placeholder="optional: enter a new or existing section name"
style="width: 90%"
/>
<br />
<button id=${this.id + "-add-link-button"} onclick="addLink(this)">add it</button>
</div>
</div>
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>${this.name}: customize</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu">
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>header options</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu">
<p class="menuHeader">date + time options</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-date-toggle"}>show date?</label>
<input id=${this.id + "-settings-date-toggle"} type="checkbox" />
</div>
<div class="containerOptionListing">
<label for=${this.id + "-settings-clock-toggle"}>show clock?</label>
<input id=${this.id + "-settings-clock-toggle"} type="checkbox" />
</div>
<br />
<div class="containerOptionListing">
<label for=${this.id + "-settings-clock-color"}>set header text color: </label>
<input id=${this.id + "-settings-clock-color"} type="color" />
</div>
<br />
<div class="containerOptionListing">
<label>set header font size (px): </label>
<input id=${this.id + "-settings-clock-size"} />
</div>
<div class="containerOptionListing">
<label for=${this.id + "-settings-clock-bold"}>bold?</label>
<input id=${this.id + "-settings-clock-bold"} type="checkbox" />
</div>
<div class="containerOptionListing">
<label for=${this.id + "-settings-clock-italic"}>italic?</label>
<input id=${this.id + "-settings-clock-italic"} type="checkbox" />
</div>
<p class="menuHeader">divider options</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-divider-toggle"}>display divider?</label>
<input id=${this.id + "-settings-divider-toggle"} type="checkbox" />
</div>
<div class="containerOptionListing">
<label>divider color: </label>
<input id=${this.id + "-settings-divider-color"} type="color" />
</div>
</div>
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>text options</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu">
<p class="menuHeader">section text options</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-section-size"}>set section size (px): </label>
<input id=${this.id + "-settings-section-size"} />
</div>
<div class="containerOptionListing">
<label for=${this.id + "-settings-section-color"}>set section color: </label>
<input id=${this.id + "-settings-section-color"} type="color" />
</div>
<div class="containerOptionListing">
<label for="sectionBoldToggle">bold?</label>
<input id=${this.id + "-settings-section-bold"} type="checkbox" />
</div>
<div class="containerOptionListing">
<label for="sectionItalicToggle">italic?</label>
<input id=${this.id + "-settings-section-italic"} type="checkbox" />
</div>
<p class="menuHeader">bookmark text options</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-link-size"}>set link size (px): </label>
<input id=${this.id + "-settings-link-size"} />
</div>
<div class="containerOptionListing">
<label for=${this.id + "-settings-link-color"}>set link color: </label>
<input id=${this.id + "-settings-link-color"} type="color" />
</div>
<p class="menuHeader">layer-wide options</p>
<div class="containerOptionListing">
<div>
<p style="margin: 0;">
change font:<br />(see help tab for more info)<br />
</p>
</div>
<div style="display: flex; flex-direction: column; align-items: end;">
<input id=${this.id + "-settings-font-input"} />
<br />
<button id=${this.id + "-settings-set-font-button"}>set font</button>
<br />
<b>current font:</b>
<span id=${this.id + "-settings-font-name"}></span>
</div>
</div>
<br />
<div class="containerOptionListing" >
<p style="margin-top: 0;">align text:</p>
<div style="display: flex; flex-direction: column; align-items: end;">
<div>
<label for=${this.id + "-settings-left-align"}>left</label>
<input id=${this.id + "-settings-left-align"} type="radio" name="align" />
</div>
<div>
<label for=${this.id + "-settings-center-align"}>center</label>
<input id=${this.id + "-settings-center-align"} type="radio" name="align" />
</div>
<div>
<label for=${this.id + "-settings-right-align"}>right</label>
<input id=${this.id + "-settings-right-align"} type="radio" name="align" />
</div>
</div>
</div>
</div>
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>color + shape options</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu">
<p class="menuHeader">background color</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-bg-color"}>set background color: </label>
<input id=${this.id + "-settings-bg-color"} type="color" />
</div>
<div class="containerOptionListing">
<label for=${this.id + "-settings-bg-alpha"}>set background opacity (%): </label>
<input id=${this.id + "-settings-bg-alpha"} type="range" min="0" max="100" />
</div>
<p class="menuHeader">layer shape</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-border-radius"}>roundness: </label>
<input id=${this.id + "-settings-border-radius"} type="range" max="175" />
</div>
<p class="menuHeader">border</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-border-color"}>set border color: </label>
<input id=${this.id + "-settings-border-color"} type="color" />
</div>
<div class="containerOptionListing">
<label for=${this.id + "-settings-border-width"}>set border width: </label>
<input id=${this.id + "-settings-border-width"} type="range" min="0" max="100" />
</div>
<p class="menuHeader">shadow / glow</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-shadow-color"}>shadow color: </label>
<input id=${this.id + "-settings-shadow-color"} type="color" />
</div>
<div class="containerOptionListing">
<label>shadow opacity:</label>
<input id=${this.id + "-settings-shadow-alpha"} type="range" min="0" max="100" />
</div>
<br />
<div class="containerOptionListing">
<label for=${this.id + "-settings-shadow-x"}>shift shadow left/right: </label>
<input id=${this.id + "-settings-shadow-x"} type="range" min="-1000" max="1000" />
</div>
<div class="containerOptionListing">
<label for=${this.id + "-settings-shadow-y"}>shift shadow up/down: </label>
<input id=${this.id + "-settings-shadow-y"} type="range" min="-1000" max="1000" />
</div>
<br />
<div class="containerOptionListing">
<label for=${this.id + "-settings-shadow-blur"}>shadow sharpness/fuzziness: </label>
<input id=${this.id + "-settings-shadow-blur"} type="range" min="0" max="200" />
</div>
</div>
</div>
</div>
<div class="manageButtons">
<div id=${this.id + "--manage-buttons"} >
${zindex == numberTotalContainers ? `` : upButtons}
${zindex == 1 ? `` : downButtons}
</div>
<div>
<button id=${this.id + "--delete-button"} class="deleteButton" onclick="deleteContainer(this)">delete layer</button>
</div>
</div>
`
);
/** fill in inputs with current values */
let containerSettings = this.containerSettings;
// SHADOW / GLOW
document.getElementById(this.id + "-settings-shadow-x").value =
containerSettings.shadowX;
document.getElementById(this.id + "-settings-shadow-y").value =
containerSettings.shadowY;
document.getElementById(this.id + "-settings-shadow-blur").value =
containerSettings.shadowBlur;
document.getElementById(this.id + "-settings-shadow-color").value =
rgbToHex(
containerSettings.shadowRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")
.splice(0, 3)
);
document.getElementById(this.id + "-settings-shadow-alpha").value =
containerSettings.shadowRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")[3] * 100;
// BORDER
document.getElementById(this.id + "-settings-border-color").value =
containerSettings.borderColor;
document.getElementById(this.id + "-settings-border-width").value =
containerSettings.borderWidth;
document.getElementById(this.id + "-settings-border-radius").value =
containerSettings.borderRadius;
// FONT
if (containerSettings.fontName == "") {
document.getElementById(this.id + "-settings-font-name").innerHTML =
"system default";
} else {
document.getElementById(this.id + "-settings-font-name").innerHTML =
containerSettings.fontName;
}
// TEXT ALIGNMENT
document.getElementById(this.id + "-settings-left-align").checked =
!containerSettings.textAlign == "left";
document.getElementById(this.id + "-settings-center-align").checked =
containerSettings.textAlign == "center";
document.getElementById(this.id + "-settings-right-align").checked =
containerSettings.textAlign == "right";
// BOOKMARK OPTIONS
document.getElementById(this.id + "-settings-section-color").value =
containerSettings.sectionColor;
document.getElementById(this.id + "-settings-section-size").value =
containerSettings.sectionSize;
document.getElementById(this.id + "-settings-section-bold").checked =
containerSettings.sectionBold;
document.getElementById(this.id + "-settings-section-italic").checked =
containerSettings.sectionItalic;
document.getElementById(this.id + "-settings-link-color").value =
containerSettings.linkColor;
document.getElementById(this.id + "-settings-link-size").value =
containerSettings.linkSize;
// BACKGROUND COLOR
document.getElementById(this.id + "-settings-bg-color").value =
rgbToHex(
containerSettings.backgroundRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")
.splice(0, 3)
);
document.getElementById(this.id + "-settings-bg-alpha").value =
containerSettings.backgroundRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")[3] * 100;
// HEADER OPTIONS
document.getElementById(this.id + "-settings-date-toggle").checked =
containerSettings.enableDate;
document.getElementById(this.id + "-settings-clock-toggle").checked =
containerSettings.enableClock;
document.getElementById(this.id + "-settings-clock-color").value =
containerSettings.headerColor;
document.getElementById(this.id + "-settings-clock-size").value =
containerSettings.headerSize;
document.getElementById(this.id + "-settings-clock-bold").checked =
containerSettings.headerBold;
document.getElementById(this.id + "-settings-clock-italic").checked =
containerSettings.headerItalic;
// DIVIDER
document.getElementById(this.id + "-settings-divider-toggle").checked =
containerSettings.enableDivider;
document.getElementById(this.id + "-settings-divider-color").value =
containerSettings.dividerColor;
}
createImageContainerMenuListing() {
let zindex = this.containerSettings.zIndex;
let upButtons = `
<div>
<button id="` + this.id + `--move-up-button"} onclick="reorderContainer(this, 'up')">move up</button>
<button id="` + this.id + `--move-top-button"} onclick="reorderContainer(this, 'top')">summon to top</button>
</div>
`;
let downButtons = `
<div ${zindex == 1 ? `` : `style="margin-top: .25rem;"`}>
<button id="` + this.id + `--move-down-button"} onclick="reorderContainer(this, 'down')">move down</button>
<button id="` + this.id + `--move-bottom-button"} onclick="reorderContainer(this, 'bottom')">banish to bottom</button>
</div>
`;
document.getElementById("containers").insertAdjacentHTML(
"afterbegin",
`
<div class="containerListing" id=${this.id + "-container-listing"}>
<div class="expandableMenuToggle active" onclick="toggleExpandableMenu(this)">
<p id=${this.id + "--listing-header"}>[${this.containerSettings.zIndex}]: ${this.name}</p>
<p class="expandedMenuIndicator">-</p>
</div>
<div class="expandableMenu" style="display: block; margin: 0;" id=${this.id + "-settings-menu"}>
<p class="menuHeader">image shape</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-border-radius"}>roundness: </label>
<input id=${this.id + "-settings-border-radius"} type="range" max="175" />
</div>
<p class="menuHeader">border</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-border-color"}>set border color: </label>
<input id=${this.id + "-settings-border-color"} type="color" />
</div>
<div class="containerOptionListing">
<label for=${this.id + "-settings-border-width"}>set border width: </label>
<input id=${this.id + "-settings-border-width"} type="range" min="0" max="100" />
</div>
<p class="menuHeader">shadow / glow</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-shadow-color"}>shadow color: </label>
<input id=${this.id + "-settings-shadow-color"} type="color" />
</div>
<div class="containerOptionListing">
<label>shadow opacity:</label>
<input id=${this.id + "-settings-shadow-alpha"} type="range" min="0" max="100" />
</div>
<br />
<div class="containerOptionListing">
<label for=${this.id + "-settings-shadow-x"}>shift shadow left/right: </label>
<input id=${this.id + "-settings-shadow-x"} type="range" min="-1000" max="1000" />
</div>
<div class="containerOptionListing">
<label for=${this.id + "-settings-shadow-y"}>shift shadow up/down: </label>
<input id=${this.id + "-settings-shadow-y"} type="range" min="-1000" max="1000" />
</div>
<br />
<div class="containerOptionListing">
<label for=${this.id + "-settings-shadow-blur"}>shadow sharpness/fuzziness: </label>
<input id=${this.id + "-settings-shadow-blur"} type="range" min="0" max="200" />
</div>
</div>
<div class="manageButtons">
<div id=${this.id + "--manage-buttons"}>
${zindex == numberTotalContainers ? `` : upButtons}
${zindex == 1 ? `` : downButtons}
</div>
<div>
<button id=${this.id + "--delete-button"} onclick="deleteContainer(this)">delete layer</button>
</div>
</div>
</div>
`
);
/** fill in inputs with current values */
let containerSettings = this.containerSettings;
// SHADOW / GLOW
document.getElementById(this.id + "-settings-shadow-x").value =
containerSettings.shadowX;
document.getElementById(this.id + "-settings-shadow-y").value =
containerSettings.shadowY;
document.getElementById(this.id + "-settings-shadow-blur").value =
containerSettings.shadowBlur;
document.getElementById(this.id + "-settings-shadow-color").value =
rgbToHex(
containerSettings.shadowRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")
.splice(0, 3)
);
document.getElementById(this.id + "-settings-shadow-alpha").value =
containerSettings.shadowRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")[3] * 100;
// BORDER
document.getElementById(this.id + "-settings-border-color").value =
containerSettings.borderColor;
document.getElementById(this.id + "-settings-border-width").value =
containerSettings.borderWidth;
document.getElementById(this.id + "-settings-border-radius").value =
containerSettings.borderRadius;
}
/*
* applies saved cosmetic customizations to container
*/
applySettings() {
/** set options relevant to both image and text containers */
/** POSITION */
document.getElementById(this.id).style.top = this.y;
document.getElementById(this.id).style.left = this.x;
document.getElementById(this.id).style.zIndex = this.containerSettings.zIndex;
/** SIZE */
let containerSettings = this.containerSettings;
document.getElementById(this.id).style.width = this.width + "px";
// this.width - 2 * parseInt(containerSettings.borderWidth);
document.getElementById(this.id).style.height = this.height + "px";
// this.height - 2 * parseInt(containerSettings.borderWidth);
/** SHADOW / GLOW */
document.getElementById(this.id).style.boxShadow =
containerSettings.shadowX +
"px " +
containerSettings.shadowY +
"px " +
containerSettings.shadowBlur +
"px " +
containerSettings.shadowRgba;
/** BORDER */
document.getElementById(this.id).style.borderStyle = "solid";
document.getElementById(this.id).style.borderWidth =
containerSettings.borderWidth + "px";
document.getElementById(this.id).style.borderRadius =
containerSettings.borderRadius + "px";
document.getElementById(this.id).style.borderColor =
containerSettings.borderColor;
/** if this is an image container, all relevant settings have been applied */
if (this.imageUrl != undefined) {
return;
}
/** FONT */
if (containerSettings.fontCode != "") {
document.head.insertAdjacentHTML(
"beforeend",
containerSettings.fontCode
);
document.getElementById(this.id).style.fontFamily =
containerSettings.fontName;
}
/** TEXT ALIGNMENT */
if (containerSettings.textAlign == "center") {
document.getElementById(this.id).style.textAlign = "center";
} else if (containerSettings.textAlign == "left") {
document.getElementById(this.id).style.textAlign = "left";
} else if (containerSettings.textAlign == "right") {
document.getElementById(this.id).style.textAlign = "right";
}
/** BOOKMARK CUSTOMIZATION */
// apply link customization
let linkElements = document.getElementsByClassName(this.id + "-link");
for (let i = 0; i < linkElements.length; i++) {
linkElements[i].style.color = containerSettings.linkColor;
linkElements[i].style.fontSize = containerSettings.linkSize + "px";
}
// apply section customization
let sectionElements = document.getElementsByClassName(this.id + "-section");
for (let i = 0; i < sectionElements.length; i++) {
sectionElements[i].style.color = containerSettings.sectionColor;
sectionElements[i].style.fontSize = containerSettings.sectionSize + "px";
sectionElements[i].style.fontWeight = containerSettings.sectionBold ? "bold" : "normal";
sectionElements[i].style.fontStyle = containerSettings.sectionItalic ? "italic" : "normal";
}
/** BACKGROUND COLOR */
document.getElementById(this.id).style.backgroundColor =
containerSettings.backgroundRgba;
/** HEADER */
// show header elements
document.getElementById(this.id + "-date").style.display =
containerSettings.enableDate ? "inline" : "none";
document.getElementById(this.id + "-clock").style.display =
containerSettings.enableClock ? "inline" : "none";
// apply header customizations
document.getElementById(this.id + "-header").style.color =
containerSettings.headerColor;
document.getElementById(this.id + "-header").style.fontSize =
containerSettings.headerSize + "px";
document.getElementById(this.id + "-header").style.fontWeight =
containerSettings.headerBold ? "bold" : "normal";
document.getElementById(this.id + "-header").style.fontStyle =
containerSettings.headerItalic ? "italic" : "normal";
/** DIVIDER */
let divider = document.getElementById(this.id + "-divider");
if (!containerSettings.enableDivider) {
document.getElementById(this.id + "-divider").hidden = true;
// if there are links in this container, add room under the divider
if (Object.keys(this.sections).length > 0) {
document.getElementById(this.id + "-header").style.marginBottom =
"18px";
}
} else {
divider.style.color = containerSettings.dividerColor;
divider.style.backgroundColor = containerSettings.dividerColor;
divider.style.borderColor = containerSettings.dividerColor;
}
}
/**
* applies event listeners for the container
*/
addContainerEventListeners() {
let container = document.getElementById(this.id);
// make element movable + resizable
container.addEventListener(
"mousedown",
mouseDownMovableElement,
true
);
// prevent context menu when resizing
container.addEventListener("contextmenu", (e) => {
e.preventDefault();
});
// stop resizing element if cursor leaves page
document.addEventListener("mouseleave", (mouseLeave) => {
if (resizing) {
resizing = false;
document.removeEventListener("mousemove", resizeElement);
storeElementData();
}
});
}
/**
* applies event listeners on container options inputs in settings menu
*/
addSettingsMenuEventListeners() {
// container border setting listeners
document
.getElementById(this.id + "-settings-border-color")
.addEventListener("input", changeContainerBorderColor, false);
document
.getElementById(this.id + "-settings-border-width")
.addEventListener("input", changeContainerBorderWidth, false);
document
.getElementById(this.id + "-settings-border-radius")
.addEventListener("input", changeContainerBorderRadius, false); // container color setting listeners
// container shadow setting listeners
document
.getElementById(this.id + "-settings-shadow-x")
.addEventListener("input", changeContainerShadow, false);
document
.getElementById(this.id + "-settings-shadow-y")
.addEventListener("input", changeContainerShadow, false);
document
.getElementById(this.id + "-settings-shadow-blur")
.addEventListener("input", changeContainerShadow, false);
document
.getElementById(this.id + "-settings-shadow-color")
.addEventListener("input", changeContainerShadow, false);
document
.getElementById(this.id + "-settings-shadow-alpha")
.addEventListener("input", changeContainerShadow, false);
/** if this is an image container, all relevant listeners have been applied */
if (this.imageUrl != undefined) {
return;
}
// conatiner background color setting listeners
document
.getElementById(this.id + "-settings-bg-color")
.addEventListener("input", changeContainerBackground, false);
document
.getElementById(this.id + "-settings-bg-alpha")
.addEventListener("input", changeContainerBackground, false);
// container text setting listeners
document
.getElementById(this.id + "-settings-set-font-button")
.addEventListener("click", changeFont, false);
document
.getElementById(this.id + "-settings-left-align")
.addEventListener("change", changeContainerAlignment, false);
document
.getElementById(this.id + "-settings-center-align")
.addEventListener("change", changeContainerAlignment, false);
document
.getElementById(this.id + "-settings-right-align")
.addEventListener("change", changeContainerAlignment, false);
// bookmark section setting listeners
document
.getElementById(this.id + "-settings-section-color")
.addEventListener("input", changeSectionColor, false);
document
.getElementById(this.id + "-settings-section-size")
.addEventListener("input", changeSectionSize, false);
document
.getElementById(this.id + "-settings-section-bold")
.addEventListener("change", toggleSectionBold, false);
document
.getElementById(this.id + "-settings-section-italic")
.addEventListener("change", toggleSectionItalic, false);
// bookmark setting listeners
document
.getElementById(this.id + "-settings-link-color")
.addEventListener("input", changeLinkColor, false);
document
.getElementById(this.id + "-settings-link-size")
.addEventListener("input", changeLinkSize, false);
// clock setting listeners
document
.getElementById(this.id + "-settings-date-toggle")
.addEventListener("input", toggleDate, false);
document
.getElementById(this.id + "-settings-clock-toggle")
.addEventListener("input", toggleClock, false);
document
.getElementById(this.id + "-settings-clock-color")
.addEventListener("input", changeHeaderColor, false);
document
.getElementById(this.id + "-settings-clock-size")
.addEventListener("input", changeHeaderSize, false);
document
.getElementById(this.id + "-settings-clock-bold")
.addEventListener("change", toggleHeaderBold, false);
document
.getElementById(this.id + "-settings-clock-italic")
.addEventListener("change", toggleHeaderItalic, false);
// container divider setting listeners
document
.getElementById(this.id + "-settings-divider-toggle")
.addEventListener("change", toggleDivider, false);
document
.getElementById(this.id + "-settings-divider-color")
.addEventListener("input", changeDividerColor, false);
}
/**
* [re]renders saved bookmark sections + links for container
*/
loadBookmarks() {
if (Object.keys(this.sections).length == 0) {
let containerBookmarks = document.getElementById(this.id + "-bookmarks");
containerBookmarks.innerHTML = "";
return;
}
let sectionData = Object.values(this.sections);
let containerSettings = this.containerSettings;
let containerBookmarks = document.getElementById(this.id + "-bookmarks");
containerBookmarks.innerHTML = "";
for (let i = 0; i < sectionData.length; i++) {
containerBookmarks.insertAdjacentHTML(
"beforeend",
`
${i == 0 ? "" : `<br />`}
<div id="${this.id}-section-${i}">
<span class=${this.id + "-section"}>
${sectionData[i].label}
</span>
</div>
`
);
}
// and then render the links
for (let s = 0; s < sectionData.length; s++) {
for (let l = 0; l < sectionData[s].links.length; l++) {
let targetSection = document.getElementById(
this.id + "-section-" + s
);
targetSection.insertAdjacentHTML(
"beforeend",
`
<div id="${this.id}-section-${s}-link-${l}" >
<a
class="${this.id}-link"
href="${sectionData[s].links[l].url}"
>
${sectionData[s].links[l].label}
</a>
</div>
`
);
}
}
// ensure smooth ux when rerendering
if (!containerSettings.enableDivider) {
document.getElementById(this.id + "-header").style.marginBottom = "18px";
}
}
}
/*************************
* constructor / init :p *
*************************/
(() => {
window.onbeforeunload = unloadPage;
/** add keybinds */
document.body.addEventListener("keydown", (e) => {
if (e.key == "e" && !editing) {
toggleEditMode();
}
if (e.key == "Escape" && editing) {
toggleEditMode();
}
/*
if (e.key == "1" && editing) {
changeActiveTab({target: { id: "containerTab" }});
}
if (e.key == "2" && editing) {
changeActiveTab({target: { id: "globalSettingsTab" }});
}
if (e.key == "3" && editing) {
changeActiveTab({target: { id: "helpTab" }});
}
if (e.key == "4" && editing) {
changeActiveTab({target: { id: "ioTab" }});
} */
});
/** set up cursors */
cursors = JSON.parse(localStorage.getItem("cursors")) || {};
if (cursors.default != undefined) {
// apply new default cursor to entire html document
document.getElementsByTagName("html")[0].style.cursor =
'url("' + cursors.default + '"), auto';
}
/** set wallpaper */
wallpaper = JSON.parse(localStorage.getItem("wallpaper")) || "";
if (wallpaper.startsWith("#")) {
document.body.style.background = wallpaper;
document.getElementById("wallpaperColorPicker").value = wallpaper;
} else {
document.body.style.backgroundImage = "url('" + wallpaper + "')";
}
repeatWallpaper =
JSON.parse(localStorage.getItem("repeatWallpaper")) || false;
if (repeatWallpaper) {
document.body.style.backgroundSize = "contain";
document.getElementById("wallpaperRepeatToggle").checked = true;
} else {
document.body.style.backgroundSize = "cover";
}
/* init audio + auto-play */
audioLink = JSON.parse(localStorage.getItem("audioLink")) || "";
document.getElementById("audioSource").src = audioLink;
autoplayAudio =
JSON.parse(localStorage.getItem("autoplayAudio")) || false;
document.getElementById("autoplayAudioToggle").checked = autoplayAudio;
if (audioLink != "" && autoplayAudio) {
document
.getElementById("audio")
.play()
.catch((error) => {
alert(
"auto-playing audio won't work unless you whitelist this page in your browser!"
);
});
}
/** (FROM ADDSETTINGSEVENTLISTENERS) local state setting input listeners */
document
.getElementById("imageRatioToggle")
.addEventListener("change", toggleImageRatio, false);
document
.getElementById("autoplayAudioToggle")
.addEventListener("change", toggleAutoplayAudio, false);
// page wallpaper
document
.getElementById("wallpaperColorPicker")
.addEventListener("input", changeWallpaper, false);
document
.getElementById("wallpaperRepeatToggle")
.addEventListener("change", toggleWallpaperRepeat, false);
/** load container data */
let zIndexMapData = JSON.parse(localStorage.getItem("zIndexMapData")) || [];
let zIndexMapKeys = Object.keys(zIndexMapData) || [];
for (let i = 0; i < zIndexMapKeys.length; i++) {
zIndexMap.set(
zIndexMapKeys[i],
zIndexMapData[zIndexMapKeys[i]]
);
}
let containerMapValues =
JSON.parse(localStorage.getItem("containerMapValues")) || [];
numberTotalContainers = containerMapValues.length;
for (let i = 0; i < numberTotalContainers; i++) {
containerDataMap.set(containerMapValues[i].id, new Container(
containerMapValues[i].id,
containerMapValues[i].name,
containerMapValues[i].x,
containerMapValues[i].y,
containerMapValues[i].height,
containerMapValues[i].width,
containerMapValues[i].imageUrl,
containerMapValues[i].containerSettings,
containerMapValues[i].sections
));
}
/** set up + load settings menu data */
settingsMenuData = JSON.parse(
localStorage.getItem("settingsMenuData")
) || {
x: "",
y: "",
width: "",
height: "",
};
setupSettingsContainer();
// load last active settings tab user was on
activeTabId =
JSON.parse(localStorage.getItem("activeTabId")) || "helpTab";
document.getElementById(
activeTabId.replace("Tab", "Page")
).style.display = "flex";
document.getElementById(activeTabId).classList.add("active");
// add tab-switching listeners
let tabs = document.getElementsByClassName("tab");
for (let i = 0; i < tabs.length; i++) {
tabs[i].addEventListener("click", changeActiveTab, false);
}
})();
/**************************
* INITIALIZATION HELPERS *
**************************/
function setupSettingsContainer() {
let settingsContainer = document.getElementById("settingsContainer");
if (numberTotalContainers == 0) {
document.getElementById("containers").insertAdjacentHTML(
"beforeend",
`<p id="noContainerWarning">(you have no layers right now)</p>`
);
}
// render listings of existing containers in menu, in order of z-index
for (let i = 1; i <= numberTotalContainers; i++) {
let currentContainer = containerDataMap.get(zIndexMap.get(String(i)));
if (currentContainer.imageUrl == undefined) {
currentContainer.createTextContainerMenuListing();
currentContainer.loadBookmarkListings();
}
else {
currentContainer.createImageContainerMenuListing();
}
currentContainer.addSettingsMenuEventListeners();
}
/* place settings container at the last saved location */
settingsContainer.style.top = settingsMenuData.y;
settingsContainer.style.left = settingsMenuData.x;
settingsContainer.style.width = settingsMenuData.width + "px";
settingsContainer.style.height = settingsMenuData.height + "px";
/* add event listeners */
// moving / resizing
settingsContainer.addEventListener(
"mousedown",
mouseDownMovableElement,
true
);
// prevent context menu when resizing
settingsContainer.addEventListener("contextmenu", (e) => {
e.preventDefault();
});
// stop resizing element if cursor leaves page
document.addEventListener("mouseleave", (mouseLeave) => {
if (resizing) {
resizing = false;
document.removeEventListener("mousemove", resizeElement);
storeElementData();
}
});
}
/************************
* LOCAL STATE MANAGERS *
************************/
/** primary event handler for toggling page editing */
function toggleEditMode() {
// enable editing mode (reveal hidden elements)
if (editing == false) {
editing = true;
toggleEditingElements(true);
}
// disable editing mode + save element data
else {
editing = false;
toggleEditingElements(false);
document.getElementById("editToggle").innerHTML = "saving";
// if data was just imported directly to localStorage, don't save current element states (would overwrite import)
if (justImported) {
justImported = false;
return;
}
// save state of settings menu
localStorage.setItem(
"settingsMenuData",
JSON.stringify(settingsMenuData)
);
// save states of all containers
let valueArray = [];
containerDataMap.values().forEach((value) => {
valueArray.push(value);
});
localStorage.setItem("containerMapValues", JSON.stringify(valueArray));
/* save z-indexes */
let temp = {};
zIndexMap.keys().forEach((key) => {
temp[key] = zIndexMap.get(key);
});
localStorage.setItem("zIndexMapData", JSON.stringify(temp));
// save wallpaper info
localStorage.setItem("wallpaper", JSON.stringify(wallpaper));
localStorage.setItem(
"repeatWallpaper",
JSON.stringify(repeatWallpaper)
);
// save doodad + gadget cookies
localStorage.setItem("audioLink", JSON.stringify(audioLink));
localStorage.setItem("autoplayAudio", JSON.stringify(autoplayAudio));
localStorage.setItem("cursors", JSON.stringify(cursors));
// save active settings tab for next session
localStorage.setItem("activeTabId", JSON.stringify(activeTabId));
document.getElementById("editToggle").innerHTML = "edit page";
}
}
function toggleExpandableMenu(toggleButton) {
toggleButton.classList.toggle("active");
let menuContent = toggleButton.nextElementSibling;
if (menuContent.style.display === "block") {
menuContent.style.display = "none";
toggleButton.children[1].innerHTML = "+";
} else {
menuContent.style.display = "block";
toggleButton.children[1].innerHTML = "-";
}
}
function collapseLayerListings() {
let listings = document.getElementsByClassName("containerListing");
for (let i = 0; i < listings.length; i++) {
// first child of a container listing is always the menu toggle
if (listings[i].children[0].classList.contains("active")) {
toggleExpandableMenu(listings[i].children[0]);
}
}
}
function expandLayerListings() {
let listings = document.getElementsByClassName("containerListing");
for (let i = 0; i < listings.length; i++) {
// first child of a container listing is always the menu toggle
if (!listings[i].children[0].classList.contains("active")) {
toggleExpandableMenu(listings[i].children[0]);
}
}
}
function toggleImageRatio() {
keepImageRatio = !document.getElementById("imageRatioToggle").checked;
}
/** handles the revealing/concealing of elements part of toggling edit mode */
function toggleEditingElements(show) {
const editButton = document.getElementById("editToggle");
const settingsContainer = document.getElementById("settingsContainer");
/** when editing-mode is enabled, */
if (show) {
editButton.innerHTML = "save + stop editing";
// reveal settings container
settingsContainer.classList.remove("hidden");
// change cursor on movable elements to indicate grabbable
let movableElements = document.getElementsByClassName("movable");
for (let i = 0; i < movableElements.length; i++) {
// mmovableElements[i].classList.add("grabbable");
movableElements[i].style.cursor = cursors.grab ? 'url("' + cursors.grab + '"), grab' : "grab";
}
} else {
// remove grabbable cursor
let movableElements = document.getElementsByClassName("movable");
for (let i = 0; i < movableElements.length; i++) {
movableElements[i].style.cursor = "";
}
// hide the settings container
settingsContainer.classList.add("hidden");
}
}
function toggleWallpaperRepeat(checkbox) {
repeatWallpaper = this.checked;
if (repeatWallpaper) {
document.body.style.backgroundSize = "contain";
document.body.style.backgroundRepeat = "repeat";
} else {
document.body.style.backgroundSize = "cover";
document.body.style.backgroundRepeat = "no-repeat";
}
}
function unloadPage() {
if (editing) {
return "you are currently editing and changes are unsaved. stay?";
}
}
function changeActiveTab(event) {
// remove 'active' state from current tab
document.getElementById(activeTabId).classList.remove("active");
document.getElementById(
activeTabId.replace("Tab", "Page")
).style.display = "none";
// set new tab
activeTabId = event.target.id;
document.getElementById(
activeTabId.replace("Tab", "Page")
).style.display = "flex";
document.getElementById(activeTabId).classList.add("active");
}
/********************************************
* DOCUMENT MANAGEMENT / MODIFICATION STUFF *
********************************************/
function createNewTextContainer() {
let containerName = document.getElementById(
"newTextContainerNameInput"
).value;
document.getElementById("newTextContainerNameInput").value = "";
let container = new Container(
undefined,
containerName,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined
);
container.createTextContainerMenuListing();
container.loadBookmarkListings();
container.addSettingsMenuEventListeners();
updateContainerListingOrder();
}
function createNewImageContainer() {
let containerName = document.getElementById(
"newImageContainerNameInput"
).value;
let imageUrl = document.getElementById(
"newImageContainerUrlInput"
).value;
document.getElementById("newImageContainerNameInput").value = "";
document.getElementById("newImageContainerUrlInput").value = "";
let container = new Container(
undefined,
containerName,
undefined,
undefined,
undefined,
undefined,
imageUrl,
undefined,
undefined
);
container.createImageContainerMenuListing();
container.addSettingsMenuEventListeners();
updateContainerListingOrder();
}
/**
* Used in the case of a container being reordered, deleted, or a new container being made.
* Updates all container listing order numbers + recalculates whether they should have up/down buttons
*/
function updateContainerListingOrder() {
// either show "you have no containers" or don't
if (numberTotalContainers > 0 && document.getElementById("noContainerWarning") != undefined) {
document.getElementById("noContainerWarning").remove();
}
else if (numberTotalContainers == 0 && document.getElementById("noContainerWarning") == undefined) {
document.getElementById("containers").insertAdjacentHTML(
"beforeend",
`<p id="noContainerWarning">(you have no layers right now)</p>`
);
return;
}
// go through all container listings and make only the necessary updates
for (let i = 1; i <= numberTotalContainers; i++) {
let currentContainer = containerDataMap.get(zIndexMap.get(String(i)));
let containerListingHeader = document.getElementById(currentContainer.id + "--listing-header");
let containerManageButtons = document.getElementById(currentContainer.id + "--manage-buttons");
// update layer number
containerListingHeader.innerText =
"[" + currentContainer.containerSettings.zIndex + "] " + currentContainer.name;
// remove / insert the necessary up/down buttons
containerManageButtons.innerHTML = "";
if (currentContainer.containerSettings.zIndex != numberTotalContainers) {
containerManageButtons.insertAdjacentHTML(
"beforeend",
`
<button id="` + currentContainer.id + `--move-top-button"} onclick="reorderContainer(this, 'top')">summon to top</button>
<button id="` + currentContainer.id + `--move-up-button"} onclick="reorderContainer(this, 'up')">move up</button>
`
);
}
if (currentContainer.containerSettings.zIndex != 1) {
containerManageButtons.insertAdjacentHTML(
"beforeend",
`
<button id="` + currentContainer.id + `--move-down-button"} onclick="reorderContainer(this, 'down')">move down</button>
<button id="` + currentContainer.id + `--move-bottom-button"} onclick="reorderContainer(this, 'bottom')">banish to bottom</button>
`
);
}
}
}
function reorderContainer(buttonPressed, direction) {
/** first, deal with reordering container listings */
let containerId = buttonPressed.id.split("--")[0];
let movingContainer = containerDataMap.get(containerId);
let currentIndex = parseInt(movingContainer.containerSettings.zIndex);
let destinationIndex;
// clone + remove the relocating listing
let clickedListingCopy = document.getElementById(containerId + "-container-listing").cloneNode(true);
document.getElementById(containerId + "-container-listing").remove();
if (direction == "top") {
}
else if (direction == "bottom") {
}
/** then, deal with reordering the actual containers */
// for up/down, simply swap container element z-indexes + save to map
if (direction == "up" || direction == "down") {
// determine destination + move listing
if (direction == "up") {
destinationIndex = String(currentIndex + 1);
document.getElementById(zIndexMap.get(destinationIndex) + "-container-listing").insertAdjacentElement("beforebegin", clickedListingCopy);
}
else if (direction == "down") {
destinationIndex = String(currentIndex - 1);
document.getElementById(zIndexMap.get(destinationIndex) + "-container-listing").insertAdjacentElement("afterend", clickedListingCopy);
}
// swap z-index of element in destination index with current
document.getElementById(containerId).style.zIndex = destinationIndex;
document.getElementById(zIndexMap.get(destinationIndex)).style.zIndex = currentIndex;
// save new indexes to container data
movingContainer.containerSettings.zIndex = parseInt(destinationIndex);
containerDataMap.get(zIndexMap.get(destinationIndex)).containerSettings.zIndex = currentIndex;
// save changes to map
zIndexMap.set(String(currentIndex), zIndexMap.get(destinationIndex));
zIndexMap.set(destinationIndex, movingContainer.id);
// reapply listeners to menu that got remade
containerDataMap.get(zIndexMap.get(destinationIndex)).addSettingsMenuEventListeners();
}
// for big jumps, we don't just swap z-indexes
else if (direction == "top") {
// moved container will be at end, so we need to ripple all zIndexes between Current and end down 1
for (let i = currentIndex; i < numberTotalContainers; i++) {
console.log("setting: " + i + ", " + zIndexMap.get(String(i+1)));
zIndexMap.set(String(i), zIndexMap.get(String(i+1)))
document.getElementById(zIndexMap.get(String(i))).style.zIndex--;
containerDataMap.get(zIndexMap.get(String(i))).containerSettings.zIndex--;
}
// then actually move the container and menu listing element
document.getElementById("containers").insertAdjacentElement("afterbegin", clickedListingCopy);
document.getElementById(containerId).style.zIndex = numberTotalContainers;
// and save
containerDataMap.get(containerId).containerSettings.zIndex = numberTotalContainers;
zIndexMap.set(String(numberTotalContainers), containerId);
}
else if (direction == "bottom") {
// moved container is now at bottom, so we need to ripple all other zIndexes up 1
for (let i = currentIndex; i > 1; i--) {
console.log("setting: " + i + ", " + zIndexMap.get(String(i+1)));
zIndexMap.set(String(i), zIndexMap.get(String(i - 1)))
document.getElementById(zIndexMap.get(String(i))).style.zIndex++;
containerDataMap.get(zIndexMap.get(String(i))).containerSettings.zIndex++;
}
document.getElementById("containers").insertAdjacentElement("beforeend", clickedListingCopy);
document.getElementById(containerId).style.zIndex = 1;
// and save
containerDataMap.get(containerId).containerSettings.zIndex = 1;
zIndexMap.set("1", containerId);
}
// since menu for moving container was removed+readded, we need to reapply listeners
movingContainer.addSettingsMenuEventListeners();
// and refresh the listings based on the new saved data
updateContainerListingOrder();
}
function deleteContainer(buttonPressed) {
let containerId = buttonPressed.id.split("--")[0];
let container = containerDataMap.get(containerId);
if (container.imageUrl == undefined) {
numberOfTextContainers--;
clearInterval(container.clockIntervalId);
}
else {
numberOfImageContainers--;
}
// delete from zindex map and ripple zIndexes down
for (let i = container.containerSettings.zIndex; i < numberTotalContainers; i++) {
zIndexMap.set(String(i), zIndexMap.get(String(i+1)));
containerDataMap.get(zIndexMap.get(String(i+1))).containerSettings.zIndex--;
}
zIndexMap.delete(String(numberTotalContainers));
// remove data associated with container
containerDataMap.delete(containerId);
numberTotalContainers--;
document.getElementById(containerId).remove();
document.getElementById(containerId + "-container-listing").remove();
updateContainerListingOrder();
}
/**
* uses map to determine the lowest available ID index for given type, to
* prevent accidentally using the same ID number for unnamed containers twice
*/
function findLowestAvailableId() {
let id = "";
for(let index = 1; index < 200; index ++) {
id = "container-" + index;
if (!containerDataMap.has(id)) {
console.log(id);
return id;
}
}
return -1;
}
/** upon click release on movable elements, store position data for when saving to localStorage when disabling edit mode */
function storeElementData() {
let borderWidth = changingElement.style.borderWidth.slice(0, -2) * 2; // slice to remove "px"
let element = document.getElementById(changingElement.id);
if (changingElement.id == "settingsContainer") {
settingsMenuData.x = element.style.left;
settingsMenuData.y = element.style.top;
settingsMenuData.width = element.offsetWidth;
settingsMenuData.height = element.offsetHeight;
}
else {
let container = containerDataMap.get(element.id);
container.x = element.style.left;
container.y = element.style.top;
container.width = element.offsetWidth - borderWidth;
container.height = element.offsetHeight - borderWidth;
}
}
function setAudioLink() {
let link = document.getElementById("audioLinkInput").value;
if (link == "") {
return;
}
audioLink = link;
document.getElementById("audioLinkInput").value = "";
document.getElementById("audioSource").src = audioLink;
document.getElementById("audio").load();
}
function toggleAutoplayAudio(checkbox) {
autoplayAudio = this.checked;
}
function changeWallpaper(event) {
// if image set
if (event == undefined) {
// get background url
let url = document.getElementById("wallpaperUrl").value;
if (url == "") {
alert("must add image url to add background");
return;
}
document.body.style.backgroundImage = "url('" + url + "')";
document.getElementById("wallpaperUrl").value = "";
wallpaper = url;
}
// if solid color set
else {
document.body.style.background = event.target.value;
wallpaper = event.target.value;
}
}
function moveElement(mouseMove) {
if (!moving) {
console.log("RETURN: " + mouseMove.target.tagName);
return;
}
// calculate offset from last mouse position to current position (1 frame at a time)
let mouseOffsetX = lastMouseX - mouseMove.clientX;
let mouseOffsetY = lastMouseY - mouseMove.clientY;
// save coords of mousepos for use above ^ next frame
lastMouseX = mouseMove.clientX;
lastMouseY = mouseMove.clientY;
// move to element same distance as mouse offset
changingElement.style.top =
changingElement.offsetTop - mouseOffsetY + "px";
changingElement.style.left =
changingElement.offsetLeft - mouseOffsetX + "px";
}
function resizeElement(mouseMove) {
if (!resizing) {
return;
}
// calculate offset from last mouse position to current position (1 frame at a time)
let mouseOffsetX = lastMouseX - mouseMove.clientX;
let mouseOffsetY = lastMouseY - mouseMove.clientY;
// save coords of mousepos for use above ^ next frame
lastMouseX = mouseMove.clientX;
lastMouseY = mouseMove.clientY;
let borderWidth =
window.getComputedStyle(changingElement).borderWidth.slice(0, -2) * 2 +
window.getComputedStyle(changingElement).padding.slice(0, -2) * 2; // remove "px"
let currentWidth = changingElement.offsetWidth - borderWidth;
let currentHeight = changingElement.offsetHeight - borderWidth;
// if proportional image resizing enabled
if ((changingElement.tagName == "IMG") && keepImageRatio) {
// growing
if (mouseOffsetY > 0) {
let newWidth = currentWidth * (1 + mouseOffsetY / 100);
let newHeight = currentHeight * (1 + mouseOffsetY / 100);
// determine offset in px (to keep image center while resizing)
let offsetX = (currentWidth - newWidth) / 2.0;
let offsetY = (currentHeight - newHeight) / 2.0;
changingElement.style.width = newWidth + "px";
changingElement.style.height = newHeight + "px";
changingElement.style.left =
changingElement.offsetLeft + offsetX + "px";
changingElement.style.top =
changingElement.offsetTop + offsetY + "px";
}
// shrinking
else if (mouseOffsetY < 0) {
let newWidth = currentWidth * (1 + mouseOffsetY / 100);
let newHeight = currentHeight * (1 + mouseOffsetY / 100);
// determine offset in px (to keep image center while resizing)
let offsetX = (currentWidth - newWidth) / 2.0;
let offsetY = (currentHeight - newHeight) / 2.0;
changingElement.style.width = newWidth + "px";
changingElement.style.height = newHeight + "px";
changingElement.style.left =
changingElement.offsetLeft + offsetX + "px";
changingElement.style.top =
changingElement.offsetTop + offsetY + "px";
}
}
// if resizing container / image freely
else {
let newHeight = currentHeight - mouseOffsetY;
let newWidth = currentWidth - mouseOffsetX;
changingElement.style.height = newHeight + "px";
changingElement.style.width = newWidth + "px";
}
}
function mouseDownMovableElement(mouseDown) {
/** MOVEMENT / RESIZING HANDLERS */
if (!editing ||
mouseDown.target.tagName == "BUTTON" ||
mouseDown.target.tagName == "INPUT" ||
mouseDown.target.tagName == "LABEL" ||
mouseDown.target.parentElement.classList.contains("expandableMenuToggle") ||
mouseDown.target.classList.contains("expandableMenuToggle")
) {
return;
}
changingElement = document.getElementById(mouseDown.currentTarget.id);
// save mouse position prior to drag event
lastMouseX = mouseDown.clientX;
lastMouseY = mouseDown.clientY;
// left click drag initiates movement, release on mouse up
if (mouseDown.button == 0) {
moving = true;
document.addEventListener("mousemove", moveElement);
document.addEventListener("mouseup", (mouseUp) => {
if (moving && mouseUp.button == 0) {
moving = false;
document.removeEventListener("mousemove", moveElement);
storeElementData();
}
});
}
// right click drag initiates movement, release on mouse up
if (mouseDown.button == 2) {
resizing = true;
document.addEventListener("mousemove", resizeElement);
document.addEventListener("mouseup", (mouseUp) => {
if (resizing && mouseUp.button == 2) {
resizing = false;
document.removeEventListener("mousemove", resizeElement);
storeElementData();
}
});
}
}
function setDefaultCursor() {
cursors.default = document.getElementById("pointerCursorInput").value;
// apply new default cursor to entire html document
document.getElementsByTagName("html")[0].style.cursor =
'url("' + cursors.default + '"), auto';
document.getElementById("pointerCursorInput").value = "";
}
function setGrabCursor() {
cursors.grab = document.getElementById("grabCursorInput").value;
// apply new grab cursor to grabbable elements...
let movableElements = document.getElementsByClassName("movable");
for (let i = 0; i < movableElements.length; i++) {
console.log(movableElements[i]);
movableElements[i].style.cursor = cursors.grab ? 'url("' + cursors.grab + '"), grab' : "grab";
}
document.getElementById("grabCursorInput").value = "";
}
function setLinkHoverCursor() {
cursors.link = document.getElementById("linkCursorInput").value;
// apply new grab cursor to grabbable elements...
let links = document.getElementsByTagName("a");
for (let i = 0; i < links.length; i++) {
links[i].style.cursor = cursors.link ? 'url("' + cursors.link + '"), pointer' : "pointer";
}
document.getElementById("linkCursorInput").value = "";
}
/**********************
* BOOK MARK HANDLERS *
**********************/
function addLink(containerElement) {
let containerId = containerElement.id.split("-add-link-")[0];
let container = containerDataMap.get(containerId);
// collect data from inputs
let url = document.getElementById(containerId + "-url-input").value;
let label = document.getElementById(containerId + "-label-input").value;
let section = document.getElementById(containerId + "-section-input").value;
// sanitize input
if (url.length > 0 && !url.startsWith("http")) {
url = "https://" + url; // append HTTPS to link if needed
}
if (section == "" && (label == "" || url == "https://")) {
alert("you must specify url and label to add a bookmark");
return;
}
// store new link in container
let sectionData = Object.values(container.sections);
let sectionLabels = [];
for (let i = 0; i < sectionData.length; i++) {
sectionLabels.push(sectionData[i].label);
}
let sectionIndex = sectionLabels.indexOf(section);
// add link to existing section
if (sectionIndex != -1) {
let tempSectionLinks = Object.values(container.sections)[sectionIndex].links;
tempSectionLinks.push({
label: label,
url: url
});
}
// or create new section + add link
else {
let newSectionLinks = [];
newSectionLinks.push({
label: label,
url: url
});
// ensure that uncategorized links stay at top of container
if (section == "" && sectionData.length > 0) {
let reorderedSections = {};
reorderedSections[0] = {};
reorderedSections[0].links = newSectionLinks;
reorderedSections[0].label = section;
// move existing sections down
for (let i = 0; i < sectionData.length; i++) {
reorderedSections[i+1] = sectionData[i];
}
container.sections = reorderedSections;
}
else {
container.sections[sectionData.length] = {};
container.sections[sectionData.length].links = newSectionLinks;
container.sections[sectionData.length].label = section;
}
}
// reset inputs + render
document.getElementById(containerId + "-url-input").value = "";
document.getElementById(containerId + "-label-input").value = "";
container.loadBookmarks();
container.loadBookmarkListings();
}
function reorderSection(buttonPressed, direction) {
let temp = buttonPressed.parentElement.parentElement.id.split("--");
let container = containerDataMap.get(temp[0].split("-section-listing")[0]);
const sectionIndex = parseInt(temp[temp.length - 1]);
let copy = container.sections[sectionIndex];
if (direction == "up" && sectionIndex != 0) {
container.sections[sectionIndex] = container.sections[sectionIndex - 1];
container.sections[sectionIndex - 1] = copy;
} else if (direction == "down" && (sectionIndex + 1) != Object.keys(container.sections).length) {
container.sections[sectionIndex] = container.sections[sectionIndex + 1];
container.sections[sectionIndex + 1] = copy;
}
container.loadBookmarks();
container.loadBookmarkListings();
}
function deleteSection(buttonPressed) {
let temp = buttonPressed.parentElement.parentElement.id.split("--");
let container = containerDataMap.get(temp[0].split("-section-listing")[0]);
const sectionIndex = parseInt(temp[temp.length - 1]);
// delete section
let numSections = Object.keys(container.sections).length;
for (let i = sectionIndex; i < numSections; i++) {
container.sections[i] = container.sections[i+1];
}
// trim last section index that is no longer needed
delete container.sections[numSections - 1];
// refresh screen
container.loadBookmarks();
container.loadBookmarkListings();
}
function deleteLink(buttonPressed) {
let temp = buttonPressed.parentElement.parentElement.id.split("--");
let container = containerDataMap.get(temp[0].split("-section-listing")[0]);
const sectionIndex = parseInt(temp[temp.length - 2]);
const linkIndex = parseInt(temp[temp.length - 1]);
// delete link
container.sections[sectionIndex].links.splice(linkIndex, 1);
// refresh screen
container.loadBookmarks();
container.loadBookmarkListings();
}
function reorderLink(buttonPressed, direction) {
let temp = buttonPressed.parentElement.parentElement.id.split("--");
let container = containerDataMap.get(temp[0].split("-section-listing")[0]);
const sectionIndex = parseInt(temp[temp.length - 2]);
const linkIndex = parseInt(temp[temp.length - 1]);
let links = container.sections[sectionIndex].links;
// cut out section and re-insert into array
let link = container.sections[sectionIndex].links[linkIndex];
container.sections[sectionIndex].links.splice(linkIndex, 1);
if (direction == "up" && linkIndex != 0) {
links.splice(linkIndex - 1, 0, link);
} else if (direction == "down" && linkIndex != links.length) {
links.splice(linkIndex + 1, 0, link);
}
// refresh screen
container.loadBookmarks();
container.loadBookmarkListings();
}
/************************************
* CONTAINER CUSTOMIZATION HANDLERS *
************************************/
function changeContainerBackground(colorChange) {
let containerId = colorChange.currentTarget.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.backgroundRgba = hexToRgba(
document.getElementById(containerId + "-settings-bg-color").value,
document.getElementById(containerId + "-settings-bg-alpha").value
);
document.getElementById(containerId).style.backgroundColor =
container.containerSettings.backgroundRgba;
}
function changeLinkColor(colorChange) {
let containerId = colorChange.currentTarget.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.linkColor = colorChange.target.value;
let linkElements = document.getElementsByClassName(container.id + "-link");
for (let i = 0; i < linkElements.length; i++) {
linkElements[i].style.color = container.containerSettings.linkColor;
}
}
function changeLinkSize(sizeChange) {
let containerId = sizeChange.currentTarget.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.linkSize =
sizeChange.target.value == "" ? "0" : sizeChange.target.value;
let linkElements = document.getElementsByClassName(container.id + "-link");
for (let i = 0; i < linkElements.length; i++) {
linkElements[i].style.fontSize = container.containerSettings.linkSize + "px";
}
}
function changeSectionColor(colorChange) {
let containerId = colorChange.currentTarget.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.sectionColor = colorChange.target.value;
let sectionElements = document.getElementsByClassName(container.id + "-section");
for (let i = 0; i < sectionElements.length; i++) {
sectionElements[i].style.color = container.containerSettings.sectionColor;
}
}
function changeSectionSize(sizeChange) {
let containerId = sizeChange.currentTarget.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.sectionSize =
sizeChange.target.value == "" ? "0" : sizeChange.target.value;
let sectionElements = document.getElementsByClassName(container.id + "-section");
for (let i = 0; i < sectionElements.length; i++) {
sectionElements[i].style.fontSize = container.containerSettings.sectionSize + "px";
}
}
function toggleSectionBold(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
let sectionElements = document.getElementsByClassName(containerId + "-section");
for (let i = 0; i < sectionElements.length; i++) {
sectionElements[i].style.fontWeight = checkboxChanged.target.checked ? "bold" : "normal";
}
container.containerSettings.sectionBold = checkboxChanged.target.checked;
}
function toggleSectionItalic(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
let sectionElements = document.getElementsByClassName(containerId + "-section");
for (let i = 0; i < sectionElements.length; i++) {
sectionElements[i].style.fontStyle = checkboxChanged.target.checked ? "italic" : "normal";
}
container.containerSettings.sectionItalic = checkboxChanged.target.checked;
}
// BORDER
function changeContainerBorderWidth(borderChange) {
let containerId = borderChange.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.borderWidth = borderChange.target.value;
document.getElementById(containerId).style.borderWidth =
container.containerSettings.borderWidth + "px";
}
function changeContainerBorderRadius(radiusChange) {
let containerId = radiusChange.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.borderRadius = radiusChange.target.value;
document.getElementById(containerId).style.borderRadius =
container.containerSettings.borderRadius + "px";
}
function changeContainerBorderColor(colorChange) {
let containerId = colorChange.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.borderColor = colorChange.target.value;
document.getElementById(containerId).style.borderColor =
container.containerSettings.borderColor;
}
// CLOCK
function toggleDate(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.enableDate = checkboxChanged.target.checked;
document.getElementById(containerId + "-date").style.display =
checkboxChanged.target.checked ? "inline" : "none";
}
function toggleClock(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.enableClock = checkboxChanged.target.checked;
document.getElementById(containerId + "-clock").style.display =
checkboxChanged.target.checked ? "inline" : "none";
}
function changeHeaderColor(colorChange) {
let containerId = colorChange.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.headerColor = colorChange.target.value;
document.getElementById(containerId + "-header").style.color =
container.containerSettings.headerColor;
}
function changeHeaderSize(sizeChange) {
let containerId = sizeChange.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.headerSize =
sizeChange.target.value == "" ? "0" : sizeChange.target.value;
document.getElementById(containerId + "-header").style.fontSize =
container.containerSettings.headerSize + "px";
}
function toggleHeaderBold(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.headerBold = checkboxChanged.target.checked;
document.getElementById(containerId + "-header").style.fontWeight =
checkboxChanged.target.checked ? "bold" : "normal";
}
function toggleHeaderItalic(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.headerItalic = checkboxChanged.target.checked;
document.getElementById(containerId + "-header").style.fontStyle =
checkboxChanged.target.checked ? "italic" : "normal";
}
// DIVIDER
function changeDividerColor(colorChange) {
let containerId = colorChange.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.dividerColor = colorChange.target.value;
let divider = document.getElementById(containerId + "-divider");
// set multiple places for compatibility
divider.style.color = container.containerSettings.dividerColor;
divider.style.backgroundColor = container.containerSettings.dividerColor;
divider.style.borderColor = container.containerSettings.dividerColor;
}
function toggleDivider(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.enableDivider = checkboxChanged.target.checked;
document.getElementById(containerId + "-divider").style.display =
checkboxChanged.target.checked ? "block" : "none";
if (checkboxChanged.target.checked && Object.keys(container.sections).length > 0) {
document.getElementById(containerId + "-clock").style.marginBottom = "18px";
} else {
document.getElementById(containerId + "-clock").style.marginBottom = "0px";
}
}
// TEXT
function changeContainerAlignment(alignmentChanged) {
let containerId = alignmentChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
if (document.getElementById(containerId + "-settings-left-align").checked) {
container.containerSettings.textAlign = "left";
document.getElementById(containerId).style.textAlign = "left";
} else if (document.getElementById(containerId + "-settings-center-align").checked) {
container.containerSettings.textAlign = "center";
document.getElementById(containerId).style.textAlign = "center";
} else if (document.getElementById(containerId + "-settings-right-align").checked) {
container.containerSettings.textAlign = "right";
document.getElementById(containerId).style.textAlign = "right";
}
}
function changeFont(buttonClicked) {
let containerId = buttonClicked.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
let input = document.getElementById(containerId + "-settings-font-input").value;
if (input == "") {
return;
}
// parse input to extract name of font
fontCode = input;
fontName = fontCode.slice(fontCode.indexOf("family=") + 7);
if (fontName.indexOf("&") != -1) {
fontName = fontName.slice(0, fontName.indexOf("&") + 1);
}
fontName = fontName.slice(0, fontName.indexOf(":")).replaceAll("+", " ");
// set font on container
document.head.insertAdjacentHTML("beforeend", fontCode);
document.getElementById(containerId).style.fontFamily = fontName;
document.getElementById(containerId + "-settings-font-name").innerHTML = fontName;
container.containerSettings.fontCode = fontCode;
container.containerSettings.fontName = fontName;
}
// SHADOW
function changeContainerShadow(valueChanged) {
let containerId = valueChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.shadowX =
document.getElementById(containerId + "-settings-shadow-x").value == ""
? 0
: document.getElementById(containerId + "-settings-shadow-x").value;
container.containerSettings.shadowY =
document.getElementById(containerId + "-settings-shadow-y").value == ""
? 0
: document.getElementById(containerId + "-settings-shadow-y").value;
container.containerSettings.shadowBlur =
document.getElementById(containerId + "-settings-shadow-blur").value == ""
? 0
: document.getElementById(containerId + "-settings-shadow-blur").value;
container.containerSettings.shadowRgba = hexToRgba(
document.getElementById(containerId + "-settings-shadow-color").value,
document.getElementById(containerId + "-settings-shadow-alpha").value
);
let shadow =
container.containerSettings.shadowX +
"px " +
container.containerSettings.shadowY +
"px " +
container.containerSettings.shadowBlur +
"px " +
container.containerSettings.shadowRgba;
// set shadow
document.getElementById(containerId).style.boxShadow = shadow;
}
/************************************
* RESET / IMPORT & EXPORT HANDLERS *
************************************/
async function exportData() {
try {
window.focus();
await navigator.clipboard.writeText(JSON.stringify(localStorage));
} catch (error) {
console.error(error.message);
}
alert(
"export to clipboard successful. paste contents to file and keep safe!"
);
}
function importData(buttonClick) {
let data = document.getElementById("importDataInput").value;
if (data == "") {
return;
}
try {
data = JSON.parse(document.getElementById("importDataInput").value);
} catch (error) {
alert("please only enter data that was provided by the export button!");
}
for (const [key, value] of Object.entries(data)) {
localStorage.setItem(key, value);
}
alert(
"data import successful. " +
"please refresh the page!" +
"\n(you can ignore unsaved changes warning, the imported data has already been saved)"
);
justImported = true;
}
/*****************
* COLOR HELPERS *
*****************/
function hexToRgba(hex, alpha) {
const sanitizedHex = hex.replace(/^#/, "");
const parsedHex = parseInt(sanitizedHex, 16);
return (
"rgba(" +
((parsedHex >> 16) & 255) +
"," +
((parsedHex >> 8) & 255) +
"," +
(parsedHex & 255) +
"," +
alpha / 100 +
")"
);
}
function rgbToHex(rgb) {
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? "0" + hex : hex;
};
return (
"#" +
toHex(parseInt(rgb[0])) +
toHex(parseInt(rgb[1])) +
toHex(parseInt(rgb[2]))
);
}
</script>
</html>