Files
diy-startpage/startpage.html
T

3465 lines
127 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>startpage</title>
<style>
/*
* GENERAL STATE HELPERS
*/
.hidden {
visibility: hidden;
display: none;
}
.invertedX {
transform: scale(-1, 1);
}
.invertedY {
transform: scale(1, -1);
}
.doubleInverted {
transform: scale(-1, -1);
}
/*
* 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;
overflow: auto;
}
.youtubeEmbed {
border-style: solid;
width: 100%;
height: 100%;
position: absolute;
display: inline;
}
.youtubeEmbedCover {
border-style: solid;
width: 100%;
height: 100%;
background-color: rgba(250,250,250,.5);
position: absolute;
}
.hyperlinkedImage {
cursor: inherit;
}
.section {
margin-top: 0rem;
}
.settingsContainer {
/** background-color: rgba(173, 165, 165, 0.9); */
background-color: rgba(133, 135, 135, 0.95);
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(133, 135, 135, 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;
}
#newTextContainerNameInput, #newImageContainerNameInput, #newImageContainerUrlInput {
margin-bottom: .25rem;
}
#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;
}
.newBookmarkForm {
display: flex;
flex-direction: column;
align-items: center;
gap: .25rem;
}
.bookmarkListingContainer {
display: flex;
flex-direction: column;
}
.renameAndClone {
display: flex;
flex-direction: row;
justify-content: space-between;
margin: .5rem .5rem 1rem 0;
}
.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;
margin: .25rem 0;
}
#noContainerWarning {
font-style: italic;
}
input {
width: 6rem;
}
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 configuration panel - use the tabs above to find various
customization options for your startpage experience.<br /><br />
this panel is adjustable, as every "layer" you add to the page will be as well.
click + drag to move, right-click + drag to resize.<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/or hold bookmarks.<br /><br />to group multiple bookmarks
together, enter links with the same "section".
uncategorized (no "section" specified) bookmarks will stay at the top of their layer.
</li><br />
<li>
<b>media layer:</b><br />paste any URL that either points directly to an image/GIF
or to a youtube video to add it to the page. images/GIFs can act as bookmarks themselves.
<br /><br />use "free transform" in the "global settings" tab to toggle the resizing
mode on media containers.
</li>
</ol>
<p>
<b><u>your changes will be saved to the page when you exit editing mode!</u></b><br /><br />
to undo current changes / revert to the last saved state, force refresh the page while still
editing the page.
<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>
click on a slider (e.g. border width) to focus it, keep your mouse hovered over it,
and use the arrow keys to make minute changes.
</p>
<hr />
<p>
<u>floating text</u>:<br />
create a bookmark layer, remove the border, and set the background opacity to zero.
<br /> then, add new sections to the layer without entering any label/link information.
customize the layer's section text to your liking and place 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 />
NOTE: if you have multiple font families in your cart (it tracks all
of the fonts you press "get font" on), only the top 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 up (import/export tab)</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu">
<p>
you can export your current page's entire setup to your clipboard in the
"import / export" tab. save the contents somewhere on your computer to store
your theme for later or for sharing with others.<br /><br />
to load in a theme, simply paste exported data into the "import" input,
and refresh the page (ignore the warning that saved data will be lost).
</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 layer
</button>
</div>
<hr style="margin-top: 2rem" />
<div style="display:flex;flex-direction:column;align-items:center;">
<h2 class="menuHeader">add media layer</h2>
<input
id="newImageContainerNameInput"
placeholder="(optional) layer name"
style="width: 100%;"
type="text"
/>
<input
id="newImageContainerUrlInput"
placeholder="image / youtube URL"
style="width: 100%;"
type="text"
/>
<button id="newImageContainerCreateButton" onclick="createNewImageContainer(this)">
create layer
</button>
<a href="https://imgur.com/upload" target="_blank" style="color: black; font-size: .75rem;">imgur upload for convenience</a>
</div>
<hr style="margin-top: 2rem" />
<div>
<h2 class="menuHeader">layer list</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">
<div style="margin-top: 2.5rem">
<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;align-items:center;">
<input id="importDataInput" style="width: 90%; align-self: center" placeholder="paste theme data to import here"/>
<button style="width: 100%; height: 3rem;" onclick="importData()" >
import data
</button>
<span style="font-size: .8rem">(see help tab for more information)</span>
</div>
</div>
<div id="globalSettingsPage" class="tabContent">
<h2 class="menuHeader">media manipulation</h2>
<div class="containerOptionListing" style="margin-bottom: 2rem;">
<label for="imageRatioToggle">toggle free transform on images/videos</label>
<input type="checkbox" id="imageRatioToggle" />
</div>
<hr />
<h2 class="menuHeader">change wallpaper</h2>
<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>
<hr />
<h2 class="menuHeader">change cursors</h2>
<p>
NOTE: images larger than 32x32 may not work (based on browser)<br />
<a target="_blank" href="https://www.totallyfreecursors.com/">
site with a bunch of cursors for reference
</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" style="width: 80%" placeholder="paste 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" style="width: 80%" placeholder="paste 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" style="width: 80%" placeholder="paste image URL" />
<button onclick="setLinkHoverCursor()">set link hover cursor</button>
</div>
</div>
<hr />
<h2 class="menuHeader">audio</h2>
<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" style="width: 80%" placeholder="paste file URL" />
<button onclick="setAudioLink()">set audio</button>
</div>
</div>
<audio id="audio" controls style="width: 100%">
<source id="audioSource" src="" type="audio/mp3" />
</audio>
<div class="containerOptionListing">
<label for="autoplayAudioToggle">auto-play when page loads? </label>
<input id="autoplayAudioToggle" type="checkbox" checked />
</div>
<p>
NOTE: autoplay requires either whitelisting this site 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: "",
invertX: false,
invertY: false,
autoplay: false
};
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 numberOfMediaContainers = 0;
let cursors = {};
class Container {
id;
name;
x;
y;
height;
width;
mediaUrl;
settings;
sections;
clockIntervalId;
youtubeId;
imageHyperlink;
constructor(
id,
name,
x,
y,
height,
width,
mediaUrl,
settings,
sections,
youtubeId,
imageHyperlink
) {
/* 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 isMedia = (mediaUrl != undefined);
if (isMedia) {
numberOfMediaContainers++;
if (loadingFromSave) {
this.name = name;
this.id = id;
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.settings = settings;
this.mediaUrl = mediaUrl;
this.youtubeId = youtubeId;
this.imageHyperlink = imageHyperlink;
}
else {
numberTotalContainers++;
if (mediaUrl.includes("youtu.be")) {
this.youtubeId = mediaUrl.slice(mediaUrl.indexOf(".be/")+4, mediaUrl.length);
}
else if (mediaUrl.includes("youtube.com")) {
// normal youtube.com link
this.youtubeId = mediaUrl.slice(mediaUrl.indexOf("v=")+2, mediaUrl.length);
if (this.youtubeId.includes("&")) {
this.youtubeId = this.youtubeId.slice(0, this.youtubeId.indexOf("&"));
}
}
if (name == "") {
if (this.videoId != undefined) {
this.name = "video " + numberOfMediaContainers;
}
else {
this.name = "image " + numberOfMediaContainers;
}
}
else {
this.name = name;
}
this.id = findLowestAvailableId();
this.mediaUrl = mediaUrl;
// deep copy default settings
this.settings = JSON.parse(JSON.stringify(defaultImageContainerSettings));
zIndexMap.set(String(numberOfMediaContainers + numberOfTextContainers), this.id);
this.settings.zIndex = numberOfMediaContainers + numberOfTextContainers;
}
if (this.youtubeId != undefined) {
this.initializeYoutubeContainer();
}
else {
this.initializeImageContainer(loadingFromSave);
}
}
else {
numberOfTextContainers++;
if (loadingFromSave) {
this.name = name;
this.id = id;
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.settings = settings;
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.settings = JSON.parse(JSON.stringify(defaultTextContainerSettings));
zIndexMap.set(String(numberOfTextContainers + numberOfMediaContainers), this.id);
this.settings.zIndex = numberOfMediaContainers + numberOfTextContainers;
}
this.initializeTextContainer();
this.loadBookmarks();
}
this.applySettings();
this.addContainerEventListeners();
// and save
containerDataMap.set(this.id, this);
}
/**
* creates image element, applySettings() will do the rest
*/
initializeImageContainer(loadingFromSave) {
document.body.insertAdjacentHTML(
"beforeend",
`
${loadingFromSave && this.imageHyperlink ?
`<a id="` + this.id + `--hyperlink" href="` + this.imageHyperlink + `">`
: ``}
<img
class="movable ${this.imageHyperlink ? "hyperlinkedImage" : ""}"
id="${this.id}"
style="z-index: ${numberOfMediaContainers + numberOfTextContainers};"
src="${this.mediaUrl}"
draggable=false
></img>
${loadingFromSave ? `</a>` : ``}
`
);
}
initializeYoutubeContainer() {
let embedLink = "https://www.youtube.com/embed/" + this.youtubeId;
if (this.settings.autoplay) {
embedLink += "?autoplay=1";
}
document.body.insertAdjacentHTML(
"beforeend",
`
<div
id=${this.id}
class="movable media"
style="width:640px;height:480px"
>
<div class="youtubeEmbedCover" style="z-index: ${900 + numberOfMediaContainers + numberOfTextContainers}; ${editing ? 'display: block"' : 'display:none;"'}></div>
<iframe
class="youtubeEmbed"
id=${this.id + "--embed-frame"}
style="z-index: ${numberOfMediaContainers + numberOfTextContainers}; "
src="${embedLink}"
/>
</div>
`
);
}
/**
* creates text container element, 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;
}
}
/**
* inserts and populates the settings inputs for the given text container
*/
createTextContainerMenuListing() {
let zindex = parseInt(this.settings.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.settings.zIndex}]: ${this.name}</p>
<p class="expandedMenuIndicator">-</p>
</div>
<div class="expandableMenu" id=${this.id + "-settings-menu"} style="display: block; margin: 0;">
<div class="renameAndClone">
<div>
<input id=${this.id + "--rename-input"} placeholder="new name here" />
<button id=${this.id + "--rename-button"} onclick="renameContainer(this)">rename layer</button>
</div>
<button id=${this.id + "--clone-button"} onclick="cloneContainer(this)">clone layer</button>
</div>
<hr />
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>bookmarks</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu" id=${this.id + "-bookmark-menu"} >
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>add new</p>
<p class="expandedMenuIndicator">+</p>
</div>
<div class="expandableMenu" id=${this.id + "-bookmark-menu--add-new"} >
<div class="newBookmarkForm">
<input
id=${this.id + "-url-input"}
type="text" name="url"
placeholder="enter URL for bookmark"
style="width: 100%;"
/>
<input
id=${this.id + "-label-input"}
type="text" name="label"
placeholder="enter label for bookmark"
style="width: 100%"
/>
<input
id=${this.id + "-section-input"}
type="text" name="section"
placeholder="optional: enter a new or existing section name"
style="width: 100%"
/>
<button
id=${this.id + "-add-link-button"}
onclick="addLink(this)"
style="width: 50%">
add it
</button>
</div>
</div>
<div class="menuHeader">current bookmarks</div>
<div id=${this.id + "-bookmark-menu--listings"} class="bookmarkListingContainer">
</div>
</div>
<div class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>shaping / color</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 class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">
<p>date / clock / divider</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>general text / font 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 /><span style="font-size: .8rem">(see help tab for more info)</span><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>
<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 settings = this.settings;
// SHADOW / GLOW
document.getElementById(this.id + "-settings-shadow-x").value =
settings.shadowX;
document.getElementById(this.id + "-settings-shadow-y").value =
settings.shadowY;
document.getElementById(this.id + "-settings-shadow-blur").value =
settings.shadowBlur;
document.getElementById(this.id + "-settings-shadow-color").value =
rgbToHex(
settings.shadowRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")
.splice(0, 3)
);
document.getElementById(this.id + "-settings-shadow-alpha").value =
settings.shadowRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")[3] * 100;
// BORDER
document.getElementById(this.id + "-settings-border-color").value =
settings.borderColor;
document.getElementById(this.id + "-settings-border-width").value =
settings.borderWidth;
document.getElementById(this.id + "-settings-border-radius").value =
settings.borderRadius;
// FONT
if (settings.fontName == "") {
document.getElementById(this.id + "-settings-font-name").innerHTML =
"browser default";
} else {
document.getElementById(this.id + "-settings-font-name").innerHTML =
settings.fontName;
}
// TEXT ALIGNMENT
document.getElementById(this.id + "-settings-left-align").checked =
!settings.textAlign == "left";
document.getElementById(this.id + "-settings-center-align").checked =
settings.textAlign == "center";
document.getElementById(this.id + "-settings-right-align").checked =
settings.textAlign == "right";
// BOOKMARK OPTIONS
document.getElementById(this.id + "-settings-section-color").value =
settings.sectionColor;
document.getElementById(this.id + "-settings-section-size").value =
settings.sectionSize;
document.getElementById(this.id + "-settings-section-bold").checked =
settings.sectionBold;
document.getElementById(this.id + "-settings-section-italic").checked =
settings.sectionItalic;
document.getElementById(this.id + "-settings-link-color").value =
settings.linkColor;
document.getElementById(this.id + "-settings-link-size").value =
settings.linkSize;
// BACKGROUND COLOR
document.getElementById(this.id + "-settings-bg-color").value =
rgbToHex(
settings.backgroundRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")
.splice(0, 3)
);
document.getElementById(this.id + "-settings-bg-alpha").value =
settings.backgroundRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")[3] * 100;
// HEADER OPTIONS
document.getElementById(this.id + "-settings-date-toggle").checked =
settings.enableDate;
document.getElementById(this.id + "-settings-clock-toggle").checked =
settings.enableClock;
document.getElementById(this.id + "-settings-clock-color").value =
settings.headerColor;
document.getElementById(this.id + "-settings-clock-size").value =
settings.headerSize;
document.getElementById(this.id + "-settings-clock-bold").checked =
settings.headerBold;
document.getElementById(this.id + "-settings-clock-italic").checked =
settings.headerItalic;
// DIVIDER
document.getElementById(this.id + "-settings-divider-toggle").checked =
settings.enableDivider;
document.getElementById(this.id + "-settings-divider-color").value =
settings.dividerColor;
}
/**
* [re]renders bookmarks shown in the settings menu for a given text container
*/
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 settings = this.settings;
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>
`
);
}
}
}
createMediaContainerMenuListing() {
let zindex = this.settings.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>
`;
let youtubeEmbedOptions = `
<p class="menuHeader">youtube autoplay</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-youtube-autoplay"}>autoplay on load?</label>
<input id=${this.id + "-settings-youtube-autoplay"} type="checkbox" onclick="toggleYoutubeAutoplay(this)" />
</div>
`;
let imageHyperlinkOptions = `
<p class="menuHeader">set image hyperlink</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-image-hyperlink-input"}>hyperlink image</label>
<div style="display: flex; flex-direction: column; align-items: end;">
<input
id=${this.id + "-settings-image-hyperlink-input"}
placeholder="URL to apply to image"
style="width: 100%;"
type="text"
/>
<button id=${this.id + "-settings-image-hyperlink-set-button"} onclick="setImageHyperlink(this)">
set hyperlink
</button>`;
if (this.imageHyperlink) {
imageHyperlinkOptions += `
<button id=${this.id + "-settings-image-hyperlink-remove-button"} onclick="removeImageHyperlink(this)">
remove hyperlink
</button>`;
}
imageHyperlinkOptions += `
</div>
</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.settings.zIndex}]: ${this.name}</p>
<p class="expandedMenuIndicator">-</p>
</div>
<div class="expandableMenu" style="display: block; margin: 0;" id=${this.id + "-settings-menu"}>
<div class="renameAndClone">
<div>
<input id=${this.id + "--rename-input"} placeholder="new name here" />
<button id=${this.id + "--rename-button"} onclick="renameContainer(this)">rename layer</button>
</div>
<button id=${this.id + "--clone-button"} onclick="cloneContainer(this)">clone layer</button>
</div>
<hr />
${this.youtubeId ? `` : imageHyperlinkOptions}
<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>
<p class="menuHeader">mirroring</p>
<div class="containerOptionListing">
<label for=${this.id + "-settings-mirror-x"}>mirror horizontally</label>
<input id=${this.id + "-settings-mirror-x"} type="checkbox" />
</div>
<div class="containerOptionListing">
<label for=${this.id + "-settings-mirror-y"}>mirror vertically</label>
<input id=${this.id + "-settings-mirror-y"} type="checkbox" />
</div>
${this.youtubeId ? youtubeEmbedOptions : ``}
</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 settings = this.settings;
// SHADOW / GLOW
document.getElementById(this.id + "-settings-shadow-x").value =
settings.shadowX;
document.getElementById(this.id + "-settings-shadow-y").value =
settings.shadowY;
document.getElementById(this.id + "-settings-shadow-blur").value =
settings.shadowBlur;
document.getElementById(this.id + "-settings-shadow-color").value =
rgbToHex(
settings.shadowRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")
.splice(0, 3)
);
document.getElementById(this.id + "-settings-shadow-alpha").value =
settings.shadowRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")[3] * 100;
// BORDER
document.getElementById(this.id + "-settings-border-color").value =
settings.borderColor;
document.getElementById(this.id + "-settings-border-width").value =
settings.borderWidth;
document.getElementById(this.id + "-settings-border-radius").value =
settings.borderRadius;
// MIRRORING
document.getElementById(this.id + "-settings-mirror-x").checked =
settings.invertX;
document.getElementById(this.id + "-settings-mirror-y").checked =
settings.invertY;
}
/**
* applies event listeners on container options inputs in settings menu (both text+media containers)
*/
addSettingsMenuEventListeners() {
/** apply listeners for both text+media containers */
// 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 media container, settings diverge here */
if (this.mediaUrl != undefined) {
document
.getElementById(this.id + "-settings-mirror-x")
.addEventListener("change", toggleMirrorX, false);
document
.getElementById(this.id + "-settings-mirror-y")
.addEventListener("change", toggleMirrorY, false);
return;
}
/** text-only settings */
// container 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);
}
/*
* applies saved cosmetic customizations to container upon init (both text+media containers)
*/
applySettings() {
// make sure old themes will still load
this.ensureBackwardsCompatibility();
/** apply customizations 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.settings.zIndex;
/** SIZE */
let settings = this.settings;
document.getElementById(this.id).style.width = this.width + "px";
// this.width - 2 * parseInt(settings.borderWidth);
document.getElementById(this.id).style.height = this.height + "px";
// this.height - 2 * parseInt(settings.borderWidth);
/** SHADOW / GLOW */
document.getElementById(this.id).style.boxShadow =
settings.shadowX +
"px " +
settings.shadowY +
"px " +
settings.shadowBlur +
"px " +
settings.shadowRgba;
/** if this is a media container, settings diverge here */
/** BORDER */
if (this.mediaUrl?.includes("youtube.com") || this.mediaUrl?.includes("youtu.be")) {
// apply border to iframe and cover element rather than containing div
document.getElementById(this.id).children[0].style.borderColor =
settings.borderColor;
document.getElementById(this.id).children[1].style.borderColor =
settings.borderColor;
document.getElementById(this.id).children[0].style.borderWidth =
settings.borderWidth + "px";
document.getElementById(this.id).children[1].style.borderWidth =
settings.borderWidth + "px";
document.getElementById(this.id).children[0].style.borderRadius =
settings.borderRadius + "px";
document.getElementById(this.id).children[1].style.borderRadius =
settings.borderRadius + "px";
}
else {
document.getElementById(this.id).style.borderRadius =
settings.borderRadius + "px";
document.getElementById(this.id).style.borderStyle = "solid";
document.getElementById(this.id).style.borderWidth =
settings.borderWidth + "px";
document.getElementById(this.id).style.borderColor =
settings.borderColor;
}
/** MEDIA MIRRORING */
if (this.mediaUrl != undefined) {
let invertX = settings.invertX;
let invertY = settings.invertY;
if (invertY && invertX) {
document.getElementById(this.id).classList.add("doubleInverted");
}
else if (invertX) {
document.getElementById(this.id).classList.add("invertedX");
}
else if (invertY) {
document.getElementById(this.id).classList.toggle("invertedY");
}
return;
}
/** apply text-container-only customizations */
/** FONT */
if (settings.fontCode != "") {
document.head.insertAdjacentHTML(
"beforeend",
settings.fontCode
);
document.getElementById(this.id).style.fontFamily =
settings.fontName;
}
/** TEXT ALIGNMENT */
if (settings.textAlign == "center") {
document.getElementById(this.id).style.textAlign = "center";
} else if (settings.textAlign == "left") {
document.getElementById(this.id).style.textAlign = "left";
} else if (settings.textAlign == "right") {
document.getElementById(this.id).style.textAlign = "right";
}
/** BOOKMARK CUSTOMIZATION HAPPENS IN loadBookmarks() FOR EASIER,
LESS INTENSE RE-RENDERING WHEN BOOKMARK CUSTOMIZATIONS CHANGE */
/** BACKGROUND COLOR */
document.getElementById(this.id).style.backgroundColor =
settings.backgroundRgba;
/** HEADER */
// show header elements
document.getElementById(this.id + "-date").style.display =
settings.enableDate ? "inline" : "none";
document.getElementById(this.id + "-clock").style.display =
settings.enableClock ? "inline" : "none";
// apply header customizations
document.getElementById(this.id + "-header").style.color =
settings.headerColor;
document.getElementById(this.id + "-header").style.fontSize =
settings.headerSize + "px";
document.getElementById(this.id + "-header").style.fontWeight =
settings.headerBold ? "bold" : "normal";
document.getElementById(this.id + "-header").style.fontStyle =
settings.headerItalic ? "italic" : "normal";
/** DIVIDER */
let divider = document.getElementById(this.id + "-divider");
if (!settings.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 = settings.dividerColor;
divider.style.backgroundColor = settings.dividerColor;
divider.style.borderColor = settings.dividerColor;
}
}
/**
* applies event listeners (moving/resizing) for the container
*/
addContainerEventListeners() {
let container = document.getElementById(this.id);
console.log("adding event listeners for " + 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();
}
});
}
/**
* [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 settings = this.settings;
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 (!settings.enableDivider) {
document.getElementById(this.id + "-header").style.marginBottom = "18px";
}
// apply link customization
let linkElements = document.getElementsByClassName(this.id + "-link");
for (let i = 0; i < linkElements.length; i++) {
linkElements[i].style.color = settings.linkColor;
linkElements[i].style.fontSize = settings.linkSize + "px";
}
// apply section customization
let sectionElements = document.getElementsByClassName(this.id + "-section");
for (let i = 0; i < sectionElements.length; i++) {
sectionElements[i].style.color = settings.sectionColor;
sectionElements[i].style.fontSize = settings.sectionSize + "px";
sectionElements[i].style.fontWeight = settings.sectionBold ? "bold" : "normal";
sectionElements[i].style.fontStyle = settings.sectionItalic ? "italic" : "normal";
}
}
/**
* if a container is imported and missing a setting, add it and set to default
*/
ensureBackwardsCompatibility() {
let currentSettings;
if (this.mediaUrl != undefined) {
currentSettings = Object.keys(defaultImageContainerSettings);
}
else {
currentSettings = Object.keys(defaultTextContainerSettings);
}
for (let i = 0; i < currentSettings.length; i++) {
if (this.settings[currentSettings[i]] == undefined) {
console.log("found missing option: ");
console.log(currentSettings[i]);
if (this.mediaUrl != undefined) {
this.settings[currentSettings[i]] =
defaultImageContainerSettings[currentSettings[i]];
}
else {
this.settings[currentSettings[i]] =
defaultTextContainerSettings[currentSettings[i]];
}
}
}
}
}
/******************************
* page 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 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";
document.body.style.backgroundPosition = "center";
}
/* 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!"
);
});
}
// 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 ? // ensure backwards compatibility after field name changed
containerMapValues[i].imageUrl : containerMapValues[i].mediaUrl,
containerMapValues[i].settings,
containerMapValues[i].sections,
containerMapValues[i].youtubeId,
containerMapValues[i].imageHyperlink
));
}
/** 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);
}
/** set up cursors */
cursors = JSON.parse(localStorage.getItem("cursors")) || {};
if (cursors != {}) {
// apply new default cursor to entire html document
document.getElementsByTagName("html")[0].style.cursor =
cursors.link ? 'url("' + cursors.default + '"), auto' : "";
// apply link hover cursor
let links = document.getElementsByTagName("a");
for (let i = 0; i < links.length; i++) {
links[i].style.cursor = cursors.link ? 'url("' + cursors.link + '"), pointer' : "pointer";
}
}
})();
/**************************
* 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.mediaUrl == undefined) {
currentContainer.createTextContainerMenuListing();
currentContainer.loadBookmarkListings();
}
else {
currentContainer.createMediaContainerMenuListing();
}
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
);
// local state inputs
document.getElementById("imageRatioToggle").checked = !keepImageRatio;
document
.getElementById("imageRatioToggle")
.addEventListener("change", toggleImageRatio, false);
document
.getElementById("autoplayAudioToggle")
.addEventListener("change", toggleAutoplayAudio, false);
// 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";
}
}
/** 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";
}
// take hyperlinked images out of anchor tags to enable moving/resizing
let hyperlinkedImages = document.getElementsByClassName("hyperlinkedImage");
let parent, clone;
for (let i = 0; i < hyperlinkedImages.length; i++) {
parent = hyperlinkedImages[i].parentElement;
clone = hyperlinkedImages[i].cloneNode();
hyperlinkedImages[i].remove();
parent.parentNode.insertBefore(clone, parent);
parent.remove();
containerDataMap.get(clone.id).addContainerEventListeners();
}
// cover any youtube iframes to enable moving/resizing
let embeddedVideos = document.getElementsByClassName("youtubeEmbedCover");
for (let i = 0; i < embeddedVideos.length; i++) {
embeddedVideos[i].style.display = "block";
}
} else {
// remove grabbable cursor
let movableElements = document.getElementsByClassName("movable");
for (let i = 0; i < movableElements.length; i++) {
movableElements[i].style.cursor = "";
}
// activate links on hyperlinked images
let hyperlinkedImages = document.getElementsByClassName("hyperlinkedImage");
let link, clone;
for (let i = 0; i < hyperlinkedImages.length; i++) {
containerId = hyperlinkedImages[i].id;
link = containerDataMap.get(containerId).imageHyperlink;
clone = hyperlinkedImages[i].cloneNode();
hyperlinkedImages[i].insertAdjacentHTML("afterend",
`<a id="` + containerId + `--hyperlink" href="` + link + `"></a>`
);
hyperlinkedImages[i].remove();
document.getElementById(containerId + "--hyperlink").appendChild(clone);
if (cursors.link != undefined) {
document.getElementById(containerId + "--hyperlink").style.cursor = cursors.link ? 'url("' + cursors.link + '"), pointer' : "";
}
}
// remove any youtube iframe covers
let embeddedVideos = document.getElementsByClassName("youtubeEmbedCover");
for (let i = 0; i < embeddedVideos.length; i++) {
embeddedVideos[i].style.display = "none";
}
// hide the settings container
settingsContainer.classList.add("hidden");
}
}
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;
}
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.backgroundPosition = "center";
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,
undefined,
undefined
);
container.createTextContainerMenuListing();
container.loadBookmarkListings();
container.addSettingsMenuEventListeners();
updateContainerListingOrder();
}
function createNewImageContainer() {
let containerName = document.getElementById(
"newImageContainerNameInput"
).value;
let mediaUrl = document.getElementById(
"newImageContainerUrlInput"
).value;
document.getElementById("newImageContainerNameInput").value = "";
document.getElementById("newImageContainerUrlInput").value = "";
let container = new Container(
undefined,
containerName,
undefined,
undefined,
undefined,
undefined,
mediaUrl,
undefined,
undefined,
undefined,
undefined
);
container.createMediaContainerMenuListing();
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.settings.zIndex + "] " + currentContainer.name;
// remove / insert the necessary up/down buttons
containerManageButtons.innerHTML = "";
if (currentContainer.settings.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.settings.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.settings.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.settings.zIndex = parseInt(destinationIndex);
containerDataMap.get(zIndexMap.get(destinationIndex)).settings.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))).settings.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).settings.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))).settings.zIndex++;
}
document.getElementById("containers").insertAdjacentElement("beforeend", clickedListingCopy);
document.getElementById(containerId).style.zIndex = 1;
// and save
containerDataMap.get(containerId).settings.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 renameContainer(buttonPressed) {
let containerId = buttonPressed.id.split("--")[0];
let container = containerDataMap.get(containerId);
let newName = document.getElementById(containerId + "--rename-input").value;
container.name = newName;
document.getElementById(containerId + "--rename-input").value = "";
document.getElementById(containerId + "--listing-header").innerText =
"[" + container.settings.zIndex + "]: " + newName;
}
function cloneContainer(buttonPressed) {
let containerId = buttonPressed.id.split("--")[0];
let container = containerDataMap.get(containerId);
let newSettings = JSON.parse(JSON.stringify(container.settings));
newSettings.zIndex = numberTotalContainers + 1;
let newX = parseInt(container.x.slice(0, -2)) + 10;
let newY = parseInt(container.y.slice(0, -2)) + 10;
let clone = new Container(
findLowestAvailableId(),
container.name + " clone",
String(newX + "px"),
String(newY + "px"),
container.height,
container.width,
container.mediaUrl,
newSettings,
container.sections,
container.youtubeId,
container.imageHyperlink
);
zIndexMap.set(numberTotalContainers + 1, clone.id);
if (clone.mediaUrl == undefined) {
clone.createTextContainerMenuListing();
}
else {
clone.createMediaContainerMenuListing();
}
clone.addSettingsMenuEventListeners();
numberTotalContainers++;
updateContainerListingOrder();
}
function deleteContainer(buttonPressed) {
let containerId = buttonPressed.id.split("--")[0];
let container = containerDataMap.get(containerId);
if (container.mediaUrl == undefined) {
numberOfTextContainers--;
clearInterval(container.clockIntervalId);
}
else {
numberOfMediaContainers--;
}
// delete from zindex map and ripple zIndexes down
for (let i = container.settings.zIndex; i < numberTotalContainers; i++) {
zIndexMap.set(String(i), zIndexMap.get(String(i+1)));
containerDataMap.get(zIndexMap.get(String(i+1))).settings.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" || changingElement.classList.contains("media")) && 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 cursor on links...
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.settings.backgroundRgba = hexToRgba(
document.getElementById(containerId + "-settings-bg-color").value,
document.getElementById(containerId + "-settings-bg-alpha").value
);
document.getElementById(containerId).style.backgroundColor =
container.settings.backgroundRgba;
}
function changeLinkColor(colorChange) {
let containerId = colorChange.currentTarget.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.linkColor = colorChange.target.value;
let linkElements = document.getElementsByClassName(container.id + "-link");
for (let i = 0; i < linkElements.length; i++) {
linkElements[i].style.color = container.settings.linkColor;
}
}
function changeLinkSize(sizeChange) {
let containerId = sizeChange.currentTarget.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.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.settings.linkSize + "px";
}
}
function changeSectionColor(colorChange) {
let containerId = colorChange.currentTarget.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.sectionColor = colorChange.target.value;
let sectionElements = document.getElementsByClassName(container.id + "-section");
for (let i = 0; i < sectionElements.length; i++) {
sectionElements[i].style.color = container.settings.sectionColor;
}
}
function changeSectionSize(sizeChange) {
let containerId = sizeChange.currentTarget.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.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.settings.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.settings.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.settings.sectionItalic = checkboxChanged.target.checked;
}
// BORDER
function changeContainerBorderWidth(borderChange) {
let containerId = borderChange.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.borderWidth = borderChange.target.value;
if (container.mediaUrl?.includes("youtube.com") || container.mediaUrl?.includes("youtu.be")) {
document.getElementById(containerId).children[0].style.borderWidth =
container.settings.borderWidth + "px";
document.getElementById(containerId).children[1].style.borderWidth =
container.settings.borderWidth + "px";
}
else {
document.getElementById(containerId).style.borderWidth =
container.settings.borderWidth + "px";
}
}
function changeContainerBorderRadius(radiusChange) {
let containerId = radiusChange.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.borderRadius = radiusChange.target.value;
if (container.mediaUrl?.includes("youtube.com") || container.mediaUrl?.includes("youtu.be")) {
document.getElementById(containerId).children[0].style.borderRadius =
container.settings.borderRadius + "px";
document.getElementById(containerId).children[1].style.borderRadius =
container.settings.borderRadius + "px";
}
else {
document.getElementById(containerId).style.borderRadius =
container.settings.borderRadius + "px";
}
}
function changeContainerBorderColor(colorChange) {
let containerId = colorChange.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.borderColor = colorChange.target.value;
if (container.mediaUrl?.includes("youtube.com") || container.mediaUrl?.includes("youtu.be")) {
document.getElementById(containerId).children[0].style.borderColor =
container.settings.borderColor;
document.getElementById(containerId).children[1].style.borderColor =
container.settings.borderColor;
}
else {
document.getElementById(containerId).style.borderColor =
container.settings.borderColor;
}
}
// CLOCK
function toggleDate(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.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.settings.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.settings.headerColor = colorChange.target.value;
document.getElementById(containerId + "-header").style.color =
container.settings.headerColor;
}
function changeHeaderSize(sizeChange) {
let containerId = sizeChange.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.headerSize =
sizeChange.target.value == "" ? "0" : sizeChange.target.value;
document.getElementById(containerId + "-header").style.fontSize =
container.settings.headerSize + "px";
}
function toggleHeaderBold(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.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.settings.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.settings.dividerColor = colorChange.target.value;
let divider = document.getElementById(containerId + "-divider");
// set multiple places for compatibility
divider.style.color = container.settings.dividerColor;
divider.style.backgroundColor = container.settings.dividerColor;
divider.style.borderColor = container.settings.dividerColor;
}
function toggleDivider(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.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";
}
}
// GENERAL TEXT SETTINGS
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.settings.textAlign = "left";
document.getElementById(containerId).style.textAlign = "left";
} else if (document.getElementById(containerId + "-settings-center-align").checked) {
container.settings.textAlign = "center";
document.getElementById(containerId).style.textAlign = "center";
} else if (document.getElementById(containerId + "-settings-right-align").checked) {
container.settings.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.settings.fontCode = fontCode;
container.settings.fontName = fontName;
}
// SHADOW
function changeContainerShadow(valueChanged) {
let containerId = valueChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.shadowX =
document.getElementById(containerId + "-settings-shadow-x").value == ""
? 0
: document.getElementById(containerId + "-settings-shadow-x").value;
container.settings.shadowY =
document.getElementById(containerId + "-settings-shadow-y").value == ""
? 0
: document.getElementById(containerId + "-settings-shadow-y").value;
container.settings.shadowBlur =
document.getElementById(containerId + "-settings-shadow-blur").value == ""
? 0
: document.getElementById(containerId + "-settings-shadow-blur").value;
container.settings.shadowRgba = hexToRgba(
document.getElementById(containerId + "-settings-shadow-color").value,
document.getElementById(containerId + "-settings-shadow-alpha").value
);
let shadow =
container.settings.shadowX +
"px " +
container.settings.shadowY +
"px " +
container.settings.shadowBlur +
"px " +
container.settings.shadowRgba;
// set shadow
document.getElementById(containerId).style.boxShadow = shadow;
}
// IMAGE INVERSION
function toggleMirrorX(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
let invertX = checkboxChanged.target.checked;
container.settings.invertX = invertX;
if (invertX && container.settings.invertY) {
document.getElementById(containerId).classList.remove("invertedY");
document.getElementById(containerId).classList.add("doubleInverted");
}
else if (!invertX && document.getElementById(containerId).classList.contains("doubleInverted")) {
document.getElementById(containerId).classList.remove("doubleInverted");
document.getElementById(containerId).classList.add("invertedY");
}
else {
document.getElementById(containerId).classList.toggle("invertedX");
}
}
function toggleMirrorY(checkboxChanged) {
let containerId = checkboxChanged.target.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
let invertY = checkboxChanged.target.checked;
container.settings.invertY = invertY;
if (invertY && container.settings.invertX) {
document.getElementById(containerId).classList.remove("invertedX");
document.getElementById(containerId).classList.add("doubleInverted");
}
else if (!invertY && document.getElementById(containerId).classList.contains("doubleInverted")) {
document.getElementById(containerId).classList.remove("doubleInverted");
document.getElementById(containerId).classList.add("invertedX");
}
else {
document.getElementById(containerId).classList.toggle("invertedY");
}
}
function toggleYoutubeAutoplay(checkbox) {
let containerId = checkbox.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.settings.autoplay = checkbox.checked;
}
// IMAGE HYPERLINKS
function setImageHyperlink(button) {
let containerId = button.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
let input = document.getElementById(containerId + "-settings-image-hyperlink-input").value;
if (input == "") {
return;
}
document.getElementById(containerId + "-settings-image-hyperlink-input").value = "";
if (container.imageHyperlink == undefined) {
document.getElementById(containerId + "-settings-image-hyperlink-set-button")
.insertAdjacentHTML("afterend", `
<button id=${containerId + "-settings-image-hyperlink-remove-button"} onclick="removeImageHyperlink(this)">remove hyperlink</button>`);
}
container.imageHyperlink = input;
document.getElementById(containerId).classList.add("hyperlinkedImage");
}
function removeImageHyperlink(button) {
let containerId = button.id.split("-settings")[0];
let container = containerDataMap.get(containerId);
container.imageHyperlink = undefined;
document.getElementById(containerId).classList.remove("hyperlinkedImage");
document.getElementById(containerId + "-settings-image-hyperlink-remove-button").remove();
}
/************************************
* 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 loaded + stored)"
);
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>