3465 lines
127 KiB
HTML
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 <head> of your html" (it should be a
|
|
few lines starting with "<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>
|