Files
diy-startpage/startpage.html
T

2507 lines
84 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<style>
/*
* GENERAL STATE HELPERS
*/
.hidden {
visibility: hidden;
display: none;
}
/*
* CURSOR STUFF
body {
cursor: url(""), auto;
}
.grabbable:hover {
cursor: url(""), grab;
}
.removable:hover {
cursor: url(""), crosshair;
}
*/
.movable {
position: fixed;
}
.container {
z-index: 998;
display: flex;
align-items: center;
justify-content: center;
overflow: auto;
}
.section {
margin-top: 0rem;
}
.settingsContainer {
background-color: rgba(173, 165, 165, 0.8);
z-index: 999;
user-select: none;
border: solid black 2px;
border-radius: 10px;
padding: 10px;
display: block;
top: 2rem;
align-self: flex-end;
height: 25rem;
overflow: scroll;
}
/*
* SETTINGS TABS
*/
#tabList {
display: block;
border-bottom: solid 2px black;
padding-bottom: 10px;
margin-bottom: 10px;
}
.tab {
margin-right: 2rem;
user-select: none;
text-wrap: nowrap;
}
.tab:hover {
cursor: pointer;
text-decoration: underline;
}
.tab.active {
font-weight: bold;
text-decoration: underline;
}
#ioTab {
flex-shrink: 1;
}
/*
* SETTINGS PAGES
*/
form,
#instructionsForm {
display: none;
flex-direction: column;
}
.formTitle {
margin-bottom: 1rem;
text-decoration: underline;
font-style: italic;
align-self: center;
}
label {
align-self: start;
}
input {
align-self: flex-end;
width: 100%;
}
input[type="checkbox"],
input[type="radio"] {
width: auto;
}
.expandableMenuToggle {
cursor: pointer;
padding 18px;
width: 100%;
}
.expanded, .expandableMenuToggle:hover {
background-color: rgba(173, 165, 165, 0.8);
}
.expandableMenu {
display: none;
padding: 0 18px;
border-left: 2px solid black;
}
#instructionsTab {
}
#bookmarkForm {
}
#linkCustomizationForm {
}
#containerCustomizationForm {
}
#resetForm {
}
#editToggle {
z-index: 999;
position: fixed;
font-size: 0.75rem;
}
#editToggle:hover {
cursor: pointer;
}
.hiddenButton {
display: inline;
visibility: visible;
font-style: italic;
}
.hiddenButton.hidden {
visibility: hidden;
display: none;
}
.hiddenButton:hover {
cursor: pointer;
}
#imageRemovalWarning {
color: red;
size: 3rem;
font-weight: bold;
user-select: none;
z-index: 1000;
display: inline;
align-self: center;
background-color: black;
border: solid red 0.25rem;
padding: 0.25rem;
}
</style>
</head>
<body>
<div style="display: flex; flex-direction: column">
<div id="imageRemovalWarning" class="hidden">
IMAGE REMOVAL MODE ACTIVE!!
</div>
<a id="editToggle" onClick="toggleEditMode()" style="align-self: flex-end"
>edit page</a
>
<br />
<div id="settingsContainer" class="movable settingsContainer hidden">
<div id="tabList">
<span class="tab" id="containerTab">containers / layers</span>
<span class="tab" id="imageAndWallpaperTab">images + wallpaper</span>
<span class="tab" id="audioTab">audio</span>
<span class="tab" id="globalSettingsTab">global settings</span>
<br />
<span class="tab" id="ioTab">import / export</span>
<span class="tab" id="resetTab">reset data</span>
<span class="tab" id="instructionsTab">instructions</span>
</div>
<!-- note to self:
- i should rename these so they don't end in "Form",
- rather something like "Page". to do that though i need
- to change the logic for switching tabs that has the string
- modification hardcoded
-->
<div id="instructionsForm">
<p id="helpText">
you are now editing the page!<br /><br />
this is the settings box. there are various tabs containing various
customization options for your startpage experience. you can create
and categorize bookmarks, customize the bookmark box to your taste,
change the wallpaper, and add + maniuplate any image you want!<br /><br />
<b>[ <i>let it be known</i> ]</b><br />
<i
>your changes will only be saved when you stop editing the page.
to cancel your live edits, refresh the page without hitting
save.</i
><br /><br />
<b>[ <i>bookmark tips</i> ]</b><br />
to group bookmarks together, just enter them with the same section.
bookmarks without a section will stay at the top. you can remove
individual bookmarks or whole sections, and sections can be
rearranged. there's also a button in this menu to full reset.<br /><br />
<b>[ <i>customization tips</i> ]</b><br />
everything on the screen can be dragged around and resized. left
click and drag to move, right click and drag to resize!<br /><br />
you can add as many images to the background and resize them as you
wish, but they can only be added via URL. i have provided a quick
link to a place to upload your own images if you have specific
things in mind, but you can also make a beautiful collage of random
photographs you find without using any disk space.<br /><br />
<b>[ <i>changing font</i> ]</b><br />
you can use any font that is available on
<a href="https://fonts.google.com/" target="blank">google fonts</a>.
FYI, if you have multiple fonts selected only the first one will be
loaded, and it will be the regular version (if there are variants of
the font)<br /><br />
to load a font:<br />
1.) on google fonts, click on a font you like, press the "get font"
button, and then press the "get embed code" button. <br />2.) under
the "web" tab (you are already probably on it), copy the stuff
under: "embed code in the &lt;head&gt; of your html" (it should be a
few lines starting with &lt;link&gt;)<br />3.) paste contents into
the "change font" input, found on the "container" tab of this
floating menu.<br /><br /><b>[ <i>back-up themes!</i> ]</b><br />
you can export your entire current setup to import again later (save
ur moodboards or share with friends). this is primarily useful
because everything for this page is stored as a cookie. so, if you
reset your browser history or cache, your startpage will be lost!!
unless you have it backed up 8]
<br /><br />
clicking 'export data' will fill your clipboard with your currently
saved data (make sure you've saved your changes before exporting),
simply paste the contents into a text file for safekeeping. to load
a saved configuration, just paste the exported text and hit
import.<br /><br />
if you somehow lose your bookmark box off-screen, use the "reset box
customization" options to get it back. you will lose your other
customizations tho :[<br /><br />
</p>
</div>
<form id="containerForm" onsubmit="return false">
<div id="containers">
<p class="formTitle">layers:</p>
</div>
<div class="formTitle">add new layer</div>
<input
id="newContainerNameInput"
placeholder="new layer name"
type="text"
/>
<button id="newContainerCreateButton" onclick="createNewContainer()">
create new layer
</button>
</form>
<form id="imageAndWallpaperForm" onsubmit="return false">
<div class="formTitle">edit images</div>
<div>
<label> add a floating image to the page: </label><br />
<input type="text" name="url" placeholder="paste image URL" />
<span
><a
href="https://imgur.com/upload"
target="_blank"
style="color: white"
>imgur upload</a
></span
>
</div>
<button onclick="addFloatingImage()">add floating image</button><br />
<div>
<label for="imageRatioToggle">free transform mode</label>
<input type="checkbox" id="imageRatioToggle" />
</div>
<div>
<label for="imageRemovalToggle">image removal mode</label>
<input type="checkbox" id="imageRemovalToggle" />
</div>
<br />
<div class="formTitle">edit wallpaper</div>
<div>
<label> image as wallpaper: </label><br />
<input id="wallpaperUrl" placeholder="paste image URL" />
<button id="submitUrl" onclick="changeWallpaper()">
set image
</button>
<label for="wallpaperRepeatToggle"> tile? </label>
<input id="wallpaperRepeatToggle" type="checkbox" />
</div>
<div>
<label> solid color as wallpaper: </label><br />
<input type="color" id="wallpaperColorPicker" />
</div>
</form>
<form id="ioForm" onsubmit="return false">
<label
>use the button below to copy your current configuration to your
clipboard<br/ >(you should save this somewhere)</label
>
<button onclick="exportData()">export data</button><br />
<input id="importDataInput" />
<button
onclick="importData()"
placeholder="paste exported data here to load"
>
import data
</button>
</form>
<form id="audioForm" onsubmit="return false">
<label
>paste a direct URL to an audio file (ending in .mp3 or .wav or
whatever) to load:</label
>
<input id="audioLinkInput" />
<button onclick="setAudioLink()">set audio</button>
<audio id="audio" controls>
<source id="audioSource" src="" type="audio/mp3" />
</audio>
<label for="autoplayAudioToggle">auto-play when page loads? </label>
<input id="autoplayAudioToggle" type="checkbox" checked />
<label
>(note: this will require white-listing this site in your browser.
you can whitelist local HTML files! the URL would be something like
"file:///C:/path/to/saved/file")</label
>
</form>
<form id="globalSettingsForm" onsubmit="return false">
<label>enter source for normal cursor: </label>
<input id="pointerCursorInput" placeholder="paste link here" />
<button onclick="setDefaultCursor()">set normal cursor</button>
<label>enter source for grabbing cursor: </label>
<input id="grabCursorInput" placeholder="paste link here" />
<button onclick="setGrabCursor()">set grabbing cursor</button>
</form>
<form id="resetForm" onsubmit="return false">
<div class="formTitle">use these buttons to reset your page</div>
<button onclick="wipeBookmarks()">wipe links</button>
<button onclick="setDefault()">set to default gunny links</button
><br />
<button onclick="wipeImages()">wipe floating images</button><br />
<button onclick="wipeWallpaper()">reset wallpaper</button><br />
<button onclick="wipeContainerSettings()">
reset box customizations</button
><br />
<button onclick="resetCursors()">reset cursors</button>
<button onclick="resetFont()">reset font</button>
</form>
</div>
</div>
</body>
<script>
// cookie holder for coordinates + size of settings menu
let settingsMenuData = {};
// cookie holder for coordinates + size of containers
let containerDataMap = new Map(); // key: element.id | values: {x:, y:, width:, height:, containerSettings}
// cookie holder for container settings
// let containerSettings = {};
// default values for easy resetting
let defaultTextContainerSettings = {
backgroundRgba: "rgba(255,255,255,.9)",
backgroundAlpha: 100,
borderWidth: "5",
borderRadius: "0",
borderColor: "grey",
sectionFontName: "",
sectionFontCode: "",
sectionColor: "",
sectionSize: "16",
sectionItalic: false,
sectionBold: false,
linkColor: "",
linkSize: "16",
enableDate: true,
enableClock: true,
/*
*
* IMPLEMENT CLOCK TOGGLE LOGIC
*/
clockColor: "",
clockSize: "16",
clockBold: false,
clockItalic: false,
disableDivider: false,
dividerColor: "",
centerAlign: false,
shadowX: "0",
shadowY: "0",
shadowBlur: "0",
shadowRgba: "rgba(255,255,255,.90)",
};
/* let defaultImageContainerSettings = {
backgroundRgba: "",
backgroundAlpha: 100,
borderWidth: "0",
borderRadius: "0",
borderColor: "grey",
sectionFontName: "",
sectionFontCode: "",
sectionColor: "",
sectionSize: "16",
sectionItalic: false,
sectionBold: false,
linkColor: "",
linkSize: "16",
/*
*
* IMPLEMENT THIS
* clockToggle
*
clockColor: "",
clockSize: "16",
clockBold: false,
clockItalic: false,
disableDivider: false,
dividerColor: "",
centerAlign: false,
shadowX: "0",
shadowY: "0",
shadowBlur: "0",
shadowRgba: "rgba(255,255,255,.90)",
};*/
// cookie holder for images
// let imageMap = new Map(); // key: img.id | value: {id:, url:}
let wallpaper = "";
// local states
let editing = false;
let moving = false;
let resizing = false;
let removing = 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 numberOfContainers = 0;
let cursors = {};
class Container {
id;
name;
x;
y;
height;
width;
imageUrl;
containerSettings;
sections;
constructor(
name,
x,
y,
height,
width,
imageUrl,
containerSettings,
sections
) {
/**
* TODO
* check if id is already used
* ensure there are no brackets in the name
*/
this.name = name;
this.id = name.replace(" ", "-");
// if initializing saved container content,
if (x !== undefined && y !== undefined) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.imageUrl = imageUrl;
this.containerSettings = containerSettings;
this.sections = sections;
this.loadSavedContainer();
}
// new text container content,
else if (imageUrl == undefined) {
this.initializeNewTextContainer();
}
// or new image container content
else {
// sanitize image input?
this.imageUrl = imageUrl;
this.initializeNewImageContainer();
}
// apply bookmarks
this.loadBookmarks();
// then create container options UI in settings menu
this.insertContainerSettingsListing();
this.applyContainerSettings();
// establish necessary event listeners
this.addContainerEventListeners()
this.addSettingsMenuEventListeners();
// and save
// done!
numberOfContainers++;
}
/**
* creates a basic container shell and lets
* loadBookmarks() and applyContainerSettings() do the rest
*/
loadSavedContainer() {
document.body.insertAdjacentHTML(
"beforeend",
`
<div class="movable container" id=${this.id}>
<div style="margin: auto">
<div id=${this.id + "-clock"}>
<span id=${this.id + "-date"}></span>
<br />
<span id=${this.id + "-time"}></span>
</div>
<hr id=${this.id + "-divider"} />
<div id=${this.id + "-sections"} ></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 + "-time").innerText =
timeFormat.format(new Date());
// set time on interval to continue to update
setInterval(() => {
document.getElementById(this.id + "-time").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());
}
/**
* creates a default image container (NEED TO IMPLEMENT DEFAULT IMAGE CONTAINER SETTINGS)
*/
initializeNewImageContainer() {
// use default settings
this.containerSettings = defaultImageContainerSettings;
// insert default container HTML
document.body.insertAdjacentHTML(
"beforeend",
`
<span>
<img
class="movable userImage"
id="${this.id}"
onmousedown="bringImageToTop(this)"
style="z-index: ${numberOfContainers};"
src="${this.imageUrl}"
draggable=false
/>
</span>
`
);
// save position fields
let newContainer = document.getElementById(this.id);
this.x = newContainer.offsetLeft;
this.y = newContainer.offsetTop;
this.height = newContainer.offsetHeight;
this.width = newContainer.offsetWidth;
}
/**
* creates a default text container
*/
initializeNewTextContainer() {
// use default settings
this.containerSettings = defaultTextContainerSettings;
// set no sections
this.sections = {};
// insert default container HTML
document.body.insertAdjacentHTML(
"beforeend",
`
<div class="movable container" id=${this.id}>
<div style="margin: auto">
<div id=${this.id + "-clock"}>
<span id=${this.id + "-date"}></span>
<br />
<span id=${this.id + "-time"}></span>
</div>
<hr id=${this.id + "-divider"} />
<div id=${this.id + "-sections"} ></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 + "-time").innerText =
timeFormat.format(new Date());
// set time on interval to continue to update
setInterval(() => {
document.getElementById(this.id + "-time").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 position fields
let newContainer = document.getElementById(this.id);
this.x = newContainer.offsetLeft;
this.y = newContainer.offsetTop;
this.height = newContainer.offsetHeight;
this.width = newContainer.offsetWidth;
}
/**
* creates container options menu in page settings menu
*/
/**
* TODO:
* --> remove "images" from wallpaper tab
* ----> furthermore, condense all global settings into one tab
* ----- including wallpaper, audio, cursor
* add toggles for each settings menu
* rename settings form div ids (see "note to self")
*/
insertContainerSettingsListing() {
document.getElementById("containers").insertAdjacentHTML(
"beforeend",
`
<div class="containerListing" id=${this.id + "-container-listing"}>
<p class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">+ ${this.name}</p>
<div class="expandableMenu" id=${this.id + "-settings-menu"}>
<p class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">+ ${this.id}: bookmarks</h1>
<div class="expandableMenu" id=${this.id + "-bookmark-menu"} >
<label> enter url: </label>
<input id=${this.id + "-url-input"} type="text" name="url" />
<label> enter label: </label>
<input id=${this.id + "-label-input"} type="text" name="label" />
<label> enter section (optional): </label>
<input id=${this.id + "-section-input"} type="text" name="section" />
<br /><br />
<button onclick="addLink(${this.id})">add link</button>
</div>
<p class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">+ ${this.id}: customize background color</p>
<div class="expandableMenu">
<label>set background color: </label>
<input id=${this.id + "-settings-bg-color"} type="color" />
<label>set background opacity (%): </label>
<input id=${this.id + "-settings-bg-alpha"} />
</div>
<p class="expandableMenuToggle" onclick="toggleExpandableMenu(this)">+ ${this.id}: text settings</p>
<div class="expandableMenu">
<label>change font (see instructions for more info):</label>
<input id=${this.id + "-settings-font-input"} />
<button id=${this.id + "-settings-set-font-button"}>set font</button>
<label>current font:</label>
<span id=${this.id + "-settings-font-name"}></span>
<br /><br />
<label>align text:</label>
<br />
<label for=${this.id + "-settings-left-align"}>left</label>
<input id=${this.id + "-settings-left-align"} type="radio" name="align" />
<label for=${this.id + "-settings-center-align"}>center</label>
<input id=${this.id + "-settings-center-align"} type="radio" name="align" />
</div>
<div class="formTitle">customize sections</div>
<div>
<label>set section size (px): </label>
<input id=${this.id + "-settings-section-size"} />
</div>
<div>
<label>set section color: </label>
<input id=${this.id + "-settings-section-color"} type="color" />
</div>
<div>
<label for="sectionBoldToggle">bold sections?</label>
<input id=${this.id + "-settings-section-bold"} type="checkbox" />
</div>
<div>
<label for="sectionItalicToggle">italic sections?</label>
<input id=${this.id + "-settings-section-italic"} type="checkbox" />
</div>
<br />
<div class="formTitle">customize links</div>
<div>
<label>set link size (px): </label>
<input id=${this.id + "-settings-link-size"} />
</div>
<div>
<label>set link color: </label>
<input id=${this.id + "-settings-link-color"} type="color" />
</div>
<br />
<div class="formTitle">change border + shape</div>
<div>
<label>set border color: </label>
<input id=${this.id + "-settings-border-color"} type="color" />
</div>
<div>
<label>set border width (px): </label>
<input id=${
this.id + "-settings-border-width"
} placeholder="0 for none" />
</div>
<div>
<label>set container roundedness (px): </label>
<input id=${
this.id + "-settings-border-radius"
} placeholder="0 for square" />
</div>
<br />
<div class="formTitle">change date + clock</div>
<div>
<label>show date?</label>
<input id=${this.id + "-settings-date-toggle"} />
</div>
<div>
<label>show clock?</label>
<input id=${this.id + "-settings-clock-toggle"} />
</div>
<div>
<label>set date + time color: </label>
<input id=${this.id + "-settings-clock-color"} type="color" />
</div>
<div>
<label>set date + time font size (px): </label>
<input id=${this.id + "-settings-clock-size"} />
</div>
<div>
<label for=${this.id + "-settings-clock-bold"}>bold?</label>
<input id=${this.id + "-settings-clock-bold"} type="checkbox" />
</div>
<div>
<label for=${this.id + "-settings-clock-italic"}>italic?</label>
<input id=${
this.id + "-settings-clock-italic"
} type="checkbox" />
</div>
<br />
<div class="formTitle">change divider</div>
<div>
<label for=${
this.id + "-settings-divider-toggle"
}>disable divider: </label>
<input id=${
this.id + "-settings-divider-toggle"
} type="checkbox" />
</div>
<div>
<label>set divider color: </label>
<input id=${this.id + "-settings-divider-color"} type="color" />
</div>
<br />
<div class="formTitle">change shadow / glow</div>
<div>
shadow settings:<br />
<label>x-offset (px): </label><br />
<input id=${
this.id + "-settings-shadow-x"
} placeholder="all 0 for none" /><br />
<label>y-offset (px): </label><br />
<input id=${
this.id + "-settings-shadow-y"
} placeholder="all 0 for none" /><br />
<label>blur radius / fuzziness (px): </label><br />
<input id=${
this.id + "-settings-shadow-blur"
} placeholder="all 0 for none" /><br />
<label>shadow color: </label><br />
<input id=${
this.id + "-settings-shadow-color"
} type="color" /><br />
<label>shadow opacity:</label>
<input id=${this.id + "-settings-shadow-alpha"} />
</div>
</div>
</div>
</div>
`
);
}
/*
* applies saved cosmetic customizations to container
*/
applyContainerSettings() {
/** set options relevant to both image and text containers */
/** POSITION */
document.getElementById(this.id).style.top = this.y;
document.getElementById(this.id).style.left = this.x;
/** SIZE */
const containerSettings = this.containerSettings;
document.getElementById(this.id).style.width = this.width + "px";
// this.width - 2 * parseInt(containerSettings.borderWidth);
document.getElementById(this.id).style.height = this.height + "px";
// this.height - 2 * parseInt(containerSettings.borderWidth);
/** SHADOW / GLOW */
document.getElementById(this.id).style.boxShadow =
containerSettings.shadowX +
"px " +
containerSettings.shadowY +
"px " +
containerSettings.shadowBlur +
"px " +
containerSettings.shadowRgba;
// fill in settings ui
document.getElementById(this.id + "-settings-shadow-x").value =
containerSettings.shadowX;
document.getElementById(this.id + "-settings-shadow-y").value =
containerSettings.shadowY;
document.getElementById(this.id + "-settings-shadow-blur").value =
containerSettings.shadowBlur;
document.getElementById(this.id + "-settings-shadow-color").value =
rgbToHex(
containerSettings.shadowRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")
.splice(0, 3)
);
document.getElementById(this.id + "-settings-shadow-alpha").value =
containerSettings.shadowRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")[3] * 100;
/** BORDER */
document.getElementById(this.id).style.borderStyle = "solid";
document.getElementById(this.id).style.borderWidth =
containerSettings.borderWidth + "px";
document.getElementById(this.id).style.borderRadius =
containerSettings.borderRadius + "px";
document.getElementById(this.id).style.borderColor =
containerSettings.borderColor;
// fill-in settings ui
document.getElementById(this.id + "-settings-border-color").value =
containerSettings.borderColor;
document.getElementById(this.id + "-settings-border-width").value =
containerSettings.borderWidth;
document.getElementById(this.id + "-settings-border-radius").value =
containerSettings.borderRadius;
/** if this is an image container, set the IMAGE SOURCE and then we are done */
if (this.imageUrl != undefined) {
document.getElementById(this.id).src = this.imageUrl;
return;
}
/** otherwise, this is a text-based container and there are more options to set: */
/** FONT */
if (containerSettings.sectionFontName == "") {
document.getElementById(this.id + "-settings-font-name").innerHTML =
"system default";
} else {
document.getElementById(this.id + "-settings-font-name").innerHTML =
containerSettings.sectionFontName;
}
if (containerSettings.sectionFontCode != "") {
document.head.insertAdjacentHTML(
"beforeend",
containerSettings.sectionFontCode
);
// fill in settings ui
document.getElementById(this.id).style.fontFamily =
containerSettings.sectionFontName;
}
/** TEXT ALIGNMENT */
if (containerSettings.centerAlign) {
document.getElementById(this.id).style.textAlign = "center";
} else {
document.getElementById(this.id).style.textAlign = "left";
}
// fill-in settings ui
document.getElementById(this.id + "-settings-left-align").checked =
!containerSettings.centerAlign;
document.getElementById(this.id + "-settings-center-align").checked =
containerSettings.centerAlign;
/** SECTION CUSTOMIZATION */
// (weight, italic, colors, etc. are all set upon render in loadBookmarks())
// fill-in settings ui
// (FONT NEEDS WORK)
// document.getElementById(this.id + "-settings-section-font-name").value =
// containerSettings.sectionFontName;
document.getElementById(this.id + "-settings-section-color").value =
containerSettings.sectionColor;
document.getElementById(this.id + "-settings-section-size").value =
containerSettings.sectionSize;
document.getElementById(this.id + "-settings-section-bold").checked =
containerSettings.sectionBold;
document.getElementById(this.id + "-settings-section-italic").checked =
containerSettings.sectionItalic;
document.getElementById(this.id + "-settings-link-color").value =
containerSettings.linkColor;
document.getElementById(this.id + "-settings-link-size").value =
containerSettings.linkSize;
/** BACKGROUND COLOR */
document.getElementById(this.id).style.backgroundColor =
containerSettings.backgroundRgba;
// fill-in settings ui
document.getElementById(this.id + "-settings-bg-color").value =
rgbToHex(
containerSettings.backgroundRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")
.splice(0, 3)
);
document.getElementById(this.id + "-settings-bg-alpha").value =
containerSettings.backgroundRgba
.replace("rgba(", "")
.slice(0, -1)
.split(",")[3] * 100;
/** CLOCK */
// TODO EITHER SHOW THE CLOCK OR DONT
if (containerSettings.enableClock) {
}
if (containerSettings.enableDate) {
}
document.getElementById(this.id + "-clock").style.color =
containerSettings.clockColor;
document.getElementById(this.id + "-clock").style.fontSize =
containerSettings.clockSize + "px";
if (containerSettings.clockBold) {
document.getElementById(this.id + "-clock").style.fontWeight = "bold";
}
if (containerSettings.clockItalic) {
document.getElementById(this.id + "-clock").style.fontStyle =
"italic";
}
// fill-in settings ui
document.getElementById(this.id + "-settings-date-toggle").checked =
containerSettings.enableDate;
document.getElementById(this.id + "-settings-clock-toggle").checked =
containerSettings.enableClock;
document.getElementById(this.id + "-settings-clock-color").value =
containerSettings.clockColor;
document.getElementById(this.id + "-settings-clock-size").value =
containerSettings.clockSize;
document.getElementById(this.id + "-settings-clock-bold").checked =
containerSettings.clockBold;
document.getElementById(this.id + "-settings-clock-italic").checked =
containerSettings.clockItalic;
/** DIVIDER */
let divider = document.getElementById(this.id + "-divider");
if (containerSettings.disableDivider) {
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 + "-clock").style.marginBottom =
"18px";
}
} else {
divider.style.color = containerSettings.dividerColor;
divider.style.backgroundColor = containerSettings.dividerColor;
divider.style.borderColor = containerSettings.dividerColor;
}
// fill-in settings ui
document.getElementById(this.id + "-settings-divider-toggle").checked =
containerSettings.disableDivider;
document.getElementById(this.id + "-settings-divider-color").value =
containerSettings.dividerColor;
}
/**
* applies event listeners for the container
*/
addContainerEventListeners() {
let container = document.getElementById(this.id);
// make element movable + resizable
container.addEventListener(
"mousedown",
mouseDownMovableElement,
true
);
// prevent context menu when resizing
container.addEventListener("contextmenu", (e) => {
e.preventDefault();
});
// stop resizing element if cursor leaves page
document.addEventListener("mouseleave", (mouseLeave) => {
if (resizing) {
resizing = false;
document.removeEventListener("mousemove", resizeElement);
storeElementData();
}
});
}
/**
* applies event listeners on container options inputs in settings menu
*/
addSettingsMenuEventListeners() {
// container 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);
// 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);
// 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
// TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// ADD CLOCK and DATE TOGGLE HERE
document
.getElementById(this.id + "-settings-clock-color")
.addEventListener("input", changeClockColor, false);
document
.getElementById(this.id + "-settings-clock-size")
.addEventListener("input", changeClockSize, false);
document
.getElementById(this.id + "-settings-clock-bold")
.addEventListener("change", toggleClockBold, false);
document
.getElementById(this.id + "-settings-clock-italic")
.addEventListener("change", toggleClockItalic, false);
// container divider setting listeners
document
.getElementById(this.id + "-settings-divider-toggle")
.addEventListener("change", disableDivider, false);
document
.getElementById(this.id + "-settings-divider-color")
.addEventListener("input", changeDividerColor, false);
// 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);
}
/**
* [re]loads saved bookmark sections + links for container
*/
loadBookmarks() {
let containerSettings = this.containerSettings;
let linkContainer = document.getElementById(this.id + "-sections");
linkContainer.innerHTML = "";
if (Object.keys(this.sections).length == 0) {
return;
}
let sectionData = Object.values(this.sections);
// render the sections
let weight = containerSettings.sectionBold ? "bold" : "normal";
let italic = containerSettings.sectionItalic ? "italic" : "normal";
for (let i = 0; i < sectionData.length; i++) {
linkContainer.insertAdjacentHTML(
"beforeend",
`
${i == 0 ? "" : `<br />`}
<div id="${this.id}-section-${i}">
<span
class="hiddenButton ${editing ? "" : "hidden"}"
onClick="deleteSection(this)"
>
[X]
</span>
<span
class="section"
style="color: ${containerSettings.sectionColor};
font-size: ${containerSettings.sectionSize};
font-weight: ${weight};
font-style: ${italic}"
>
${sectionData[i].label}
</span>
<span
class="hiddenButton ${editing ? "" : "hidden"}"
onClick="reorderSection(this, 'up')"
>
[up]
</span>
<span
class="hiddenButton ${editing ? "" : "hidden"}"
onClick="reorderSection(this, 'down')"
>
[down]
</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}" >
<span
class="hiddenButton bookmark ${editing ? "" : "hidden"}"
onClick="deleteLink(this)"
>
[X]
</span>
<a
class="${this.id}-link"
href="${sectionData[s].links[l].url}"
>
${sectionData[s].links[l].label}
</a>
</div>
`
);
}
}
// set link colors
let linkElements = document.getElementsByClassName(this.id + "-link");
for (let i = 0; i < linkElements.length; i++) {
linkElements[i].style.color = containerSettings.linkColor;
linkElements[i].style.fontSize = containerSettings.linkSize + "px";
}
// ensure smooth ux when rerendering
if (containerSettings.disableDivider) {
document.getElementById("clock").style.marginBottom = "18px";
}
}
}
/*************************
* constructor / init :p *
*************************/
(() => {
window.onbeforeunload = unloadPage;
document.body.addEventListener("keydown", (e) => {
if (e.key == "e" && !editing) {
toggleEditMode();
}
});
/** set up cursors **/
cursors = JSON.parse(localStorage.getItem("cursors")) || {};
if (cursors.default != undefined) {
// apply new default cursor to entire html document
document.getElementsByTagName("html")[0].style.cursor =
'url("' + cursors.default + '"), auto';
}
/** set wallpaper */
wallpaper = JSON.parse(localStorage.getItem("wallpaper")) || "";
if (wallpaper.startsWith("#")) {
document.body.style.background = wallpaper;
document.getElementById("wallpaperColorPicker").value = wallpaper;
} else {
document.body.style.backgroundImage = "url('" + wallpaper + "')";
}
repeatWallpaper =
JSON.parse(localStorage.getItem("repeatWallpaper")) || false;
if (repeatWallpaper) {
document.body.style.backgroundSize = "contain";
document.getElementById("wallpaperRepeatToggle").checked = true;
} else {
document.body.style.backgroundSize = "cover";
}
/* init audio + auto-play */
audioLink = JSON.parse(localStorage.getItem("audioLink")) || "";
document.getElementById("audioSource").src = audioLink;
autoplayAudio =
JSON.parse(localStorage.getItem("autoplayAudio")) || false;
document.getElementById("autoplayAudioToggle").checked = autoplayAudio;
if (audioLink != "" && autoplayAudio) {
document
.getElementById("audio")
.play()
.catch((error) => {
alert(
"auto-playing audio won't work unless you whitelist this page in your browser!"
);
});
}
/** (FROM ADDSETTINGSEVENTLISTENERS) local state setting input listeners */
document
.getElementById("imageRatioToggle")
.addEventListener("change", toggleImageRatio, false);
document
.getElementById("imageRemovalToggle")
.addEventListener("change", toggleImageRemoval, false);
document
.getElementById("autoplayAudioToggle")
.addEventListener("change", toggleAutoplayAudio, false);
// page wallpaper
document
.getElementById("wallpaperColorPicker")
.addEventListener("input", changeWallpaper, false);
document
.getElementById("wallpaperRepeatToggle")
.addEventListener("change", toggleWallpaperRepeat, false);
/**
OLD INIT
/** load image data cookies *
let imageValues =
JSON.parse(localStorage.getItem("imageMapValues")) || [];
for (let i = 0; i < imageValues.length; i++) {
imageMap.set(imageValues[i].id, imageValues[i]);
}
/** set event listeners for settings menu *
addSettingsEventListeners();
/** load settings menu data */
settingsMenuData = JSON.parse(
localStorage.getItem("settingsMenuData")
) || {
x: "",
y: "",
width: "",
height: "",
};
setupContainerSettingsMenu();
/** load container data */
let containerMapValues =
JSON.parse(localStorage.getItem("containerMapValues")) || [];
for (let i = 0; i < containerMapValues.length; i++) {
// containerDataMap.set(containerMapValues[i].id, containerMapValues[i]);
if (containerMapValues[i].id != "settingsContainer") {
containerDataMap.set(containerMapValues[i].id, new Container(
containerMapValues[i].id,
containerMapValues[i].x,
containerMapValues[i].y,
containerMapValues[i].height,
containerMapValues[i].width,
containerMapValues[i].imageUrl,
containerMapValues[i].containerSettings,
containerMapValues[i].sections
));
}
}
numberOfContainers = containerDataMap.length;
/** load last active settings tab user was on */
activeTabId =
JSON.parse(localStorage.getItem("activeTabId")) || "instructionsTab";
document.getElementById(
activeTabId.replace("Tab", "Form")
).style.display = "flex";
document.getElementById(activeTabId).classList.add("active");
/** tab-switching listeners */
let tabs = document.getElementsByClassName("tab");
for (let i = 0; i < tabs.length; i++) {
tabs[i].addEventListener("click", changeActiveTab, false);
}
})();
/**************************
* INITIALIZATION HELPERS *
**************************/
function setupContainerSettingsMenu() {
let settingsContainer = document.getElementById("settingsContainer");
/* set at the last saved location */
settingsContainer.style.top = settingsMenuData.y;
settingsContainer.style.left = settingsMenuData.x;
settingsContainer.style.width = settingsMenuData.width + "px";
settingsContainer.style.height = settingsMenuData.height + "px";
/* add event listeners */
// moving / resizing
settingsContainer.addEventListener(
"mousedown",
mouseDownMovableElement,
true
);
// prevent context menu when resizing
settingsContainer.addEventListener("contextmenu", (e) => {
e.preventDefault();
});
// stop resizing element if cursor leaves page
document.addEventListener("mouseleave", (mouseLeave) => {
if (resizing) {
resizing = false;
document.removeEventListener("mousemove", resizeElement);
storeElementData();
}
});
}
/** inserts saved images in their saved order (postioning done upon return)
function initializeSavedImages() {
let numImages = 1;
// render each image in the map!
imageMap.forEach((value, test, map) => {
let data = containerDataMap.get(value.id);
document.body.insertAdjacentHTML(
"beforeend",
`
<img
class="movable userImage"
id="${value.id}"
onmousedown="bringImageToTop(this)"
style="z-index: ${numImages}; width: ${data.width}; height: ${data.height};"
src="${value.url}"
draggable=false
/>
`
);
numImages++;
});
}*/
/************************
* 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 wallpaper info
localStorage.setItem("wallpaper", JSON.stringify(wallpaper));
localStorage.setItem(
"repeatWallpaper",
JSON.stringify(repeatWallpaper)
);
// save doodad + gadget cookies
localStorage.setItem("audioLink", JSON.stringify(audioLink));
localStorage.setItem("autoplayAudio", JSON.stringify(autoplayAudio));
localStorage.setItem("cursors", JSON.stringify(cursors));
// save active settings tab for next session
localStorage.setItem("activeTabId", JSON.stringify(activeTabId));
document.getElementById("editToggle").innerHTML = "edit page";
}
}
function toggleExpandableMenu(toggleButton) {
toggleButton.classList.toggle("expanded");
let toggleButtonLabel = toggleButton.innerText;
let menuContent = toggleButton.nextElementSibling;
console.log(toggleButtonLabel);
if (menuContent.style.display === "block") {
menuContent.style.display = "none";
toggleButton.innerHTML = toggleButtonLabel.replace("-", "+");
} else {
menuContent.style.display = "block";
toggleButton.innerHTML = toggleButtonLabel.replace("+", "-");
}
}
function toggleImageRatio() {
keepImageRatio = !document.getElementById("imageRatioToggle").checked;
}
function toggleImageRemoval() {
removing = document.getElementById("imageRemovalToggle").checked;
let images = document.getElementsByClassName("userImage");
if (removing) {
for (let i = 0; i < images.length; i++) {
// change cursor on images to crosshair
images[i].classList.remove("grabbable");
images[i].classList.add("removable");
images[i].addEventListener(
"mousedown",
mouseDownRemovableElement,
false
);
document
.getElementById("imageRemovalWarning")
.classList.remove("hidden");
}
} else {
for (let i = 0; i < images.length; i++) {
// remove crosshair cursor from images
images[i].classList.remove("removable");
images[i].classList.add("grabbable");
images[i].removeEventListener(
"mousedown",
mouseDownRemovableElement,
false
);
document
.getElementById("imageRemovalWarning")
.classList.add("hidden");
}
}
}
/** handles the revealing/concealing of elements part of toggling edit mode */
function toggleEditingElements(show) {
const editButton = document.getElementById("editToggle");
const settingsContainer = document.getElementById("settingsContainer");
const hiddenButtons = document.getElementsByClassName("hiddenButton");
/** when editing-mode is enabled, */
if (show) {
editButton.innerHTML = "save + stop editing";
// reveal settings container
settingsContainer.classList.remove("hidden");
for (let i = 0; i < hiddenButtons.length; i++) {
hiddenButtons[i].classList.remove("hidden");
}
// change cursor on movable elements to indicate grabbable
let movableElements = document.getElementsByClassName("movable");
for (let i = 0; i < movableElements.length; i++) {
movableElements[i].classList.add("grabbable");
}
} else {
// remove grabbable cursor
let movableElements = document.getElementsByClassName("movable");
for (let i = 0; i < movableElements.length; i++) {
movableElements[i].classList.remove("grabbable");
}
// disable image removal mode active
if (removing) {
removing = false;
document.getElementById("imageRemovalToggle").checked = false;
let images = document.getElementsByClassName("userImage");
for (let i = 0; i < images.length; i++) {
images[i].classList.remove("removable");
}
document
.getElementById("imageRemovalWarning")
.classList.add("hidden");
}
// hide the settings container
settingsContainer.classList.add("hidden");
for (let i = 0; i < hiddenButtons.length; i++) {
hiddenButtons[i].classList.add("hidden");
}
}
}
function toggleWallpaperRepeat(checkbox) {
repeatWallpaper = this.checked;
if (repeatWallpaper) {
document.body.style.backgroundSize = "contain";
} else {
document.body.style.backgroundSize = "cover";
}
}
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", "Form")
).style.display = "none";
// set new tab
activeTabId = event.target.id;
document.getElementById(
activeTabId.replace("Tab", "Form")
).style.display = "flex";
document.getElementById(activeTabId).classList.add("active");
}
/********************************************
* DOCUMENT MANAGEMENT / MODIFICATION STUFF *
********************************************/
function createNewContainer() {
let containerName = document.getElementById(
"newContainerNameInput"
).value;
document.getElementById("newContainerNameInput").value = "";
// generate generic unnamed name
if (containerName == "") {
containerName =
"container-" + (containerDataMap.keys().length + 1).toString();
}
containerDataMap.set(
containerName,
new Container(
containerName,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined
)
);
}
/** 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") {
console.log("saving settings cont");
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;
}
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) {
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.id.startsWith("img-") && keepImageRatio) {
// growing
if (mouseOffsetY > 0) {
let newWidth = currentWidth * (1 + mouseOffsetY / 100);
let newHeight = currentHeight * (1 + mouseOffsetY / 100);
// determine offset in px (to keep image center while resizing)
let offsetX = (currentWidth - newWidth) / 2.0;
let offsetY = (currentHeight - newHeight) / 2.0;
changingElement.style.width = newWidth + "px";
changingElement.style.height = newHeight + "px";
changingElement.style.left =
changingElement.offsetLeft + offsetX + "px";
changingElement.style.top =
changingElement.offsetTop + offsetY + "px";
}
// shrinking
else if (mouseOffsetY < 0) {
let newWidth = currentWidth * (1 + mouseOffsetY / 100);
let newHeight = currentHeight * (1 + mouseOffsetY / 100);
// determine offset in px (to keep image center while resizing)
let offsetX = (currentWidth - newWidth) / 2.0;
let offsetY = (currentHeight - newHeight) / 2.0;
changingElement.style.width = newWidth + "px";
changingElement.style.height = newHeight + "px";
changingElement.style.left =
changingElement.offsetLeft + offsetX + "px";
changingElement.style.top =
changingElement.offsetTop + offsetY + "px";
}
}
// if resizing container / image freely
else {
let newHeight = currentHeight - mouseOffsetY;
let newWidth = currentWidth - mouseOffsetX;
changingElement.style.height = newHeight + "px";
changingElement.style.width = newWidth + "px";
}
}
function mouseDownMovableElement(mouseDown) {
/** MOVEMENT / RESIZING HANDLERS */
if (!editing || removing) {
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';
}
function setGrabCursor() {
cursors.grab = document.getElementById("grabCursorInput").value;
// apply new grab cursor to grabbable elements...
}
/**********************
* BOOK MARK HANDLERS *
**********************/
function addLink(containerElement) {
let containerId = containerElement.id.split("-")[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.startsWith("http")) {
url = "https://" + url; // append HTTPS to link if needed
}
if (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 tempIndex = sectionLabels.indexOf(section);
// existing section
if (tempIndex != -1) {
let tempSectionLinks = Object.values(container.sections)[tempIndex].links;
tempSectionLinks.push({
label: label,
url: url
});
}
// creating new section
else {
let tempSectionLinks = [];
tempSectionLinks.push({
label: label,
url: url
});
container.sections[sectionData.length] = {}
container.sections[sectionData.length].links = tempSectionLinks;
container.sections[sectionData.length].label = section;
}
// reset inputs + render
document.getElementById(containerId + "-url-input").value = "";
document.getElementById(containerId + "-label-input").value = "";
container.loadBookmarks();
}
function reorderSection(buttonPressed, direction) {
let temp = buttonPressed.parentElement.id.split("-");
let sectionId = parseInt(temp[temp.length - 1]);
let container = containerDataMap.get(temp[0]);
let copy = container.sections[sectionId];
if (direction == "up" && sectionId != 0) {
container.sections[sectionId] = container.sections[sectionId - 1];
container.sections[sectionId - 1] = copy;
} else if (direction == "down" && (sectionId + 1) != Object.keys(container.sections).length) {
console.log(sectionId);
console.log(Object.keys(container.sections).length);
container.sections[sectionId] = container.sections[sectionId + 1];
container.sections[sectionId + 1] = copy;
}
container.loadBookmarks();
}
function deleteSection(buttonPressed) {
let temp = buttonPressed.parentElement.id.split("-");
let sectionId = parseInt(temp[temp.length - 1]);
let container = containerDataMap.get(temp[0]);
// go through sections, starting with one to delete
let numSections = Object.keys(container.sections).length;
for (let i = sectionId; 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();
}
function deleteLink(buttonPressed) {
// remove the link from local array
const linkIndex = buttonPressed.parentElement.id.replace("link-", "");
links.splice(linkIndex, 1);
// determine whether to remove link"s section (if no more links)
let removeSection = true;
const linkSection = buttonPressed.parentElement.parentElement.id;
for (let i = 0; i < links.length; i++) {
if (links[i].section == linkSection) {
removeSection = false;
break;
}
}
// remove section (if needed) + save
if (removeSection) {
sections.splice(sections.indexOf(linkSection), 1);
}
loadSections();
}
/******************
* IMAGE HANDLERS *
******************/
function addFloatingImage() {
// get image url from form
const form = document.getElementById("imageAndWallpaperForm");
const formData = new FormData(form);
let url = formData.get("url");
if (url == "") {
return;
}
// save image info to local arrays (saved for real upon stopping edit)
let keys = imageMap.keys();
let numImages = 1;
for (const key of keys) {
if (key.slice(-1) > numImages) {
console.log(key);
numImages = parseInt(key.slice(-1));
}
}
numImages++;
imageMap.set("img-" + numImages, { id: "img-" + numImages, url: url });
// render image on page
let body = document.body;
body.insertAdjacentHTML(
"beforeend",
`
<img
class="movable userImage"
id="img-${numImages}"
onmousedown="bringImageToTop(this)"
style="z-index: ${numImages};"
src="${url}"
draggable=false />
`
);
changingElement = document.getElementById("img-" + numImages);
// make image movable + resizable
changingElement.addEventListener(
"mousedown",
mouseDownMovableElement,
true
);
// prevent right click from opening context menu when resizing
changingElement.addEventListener("contextmenu", (e) => {
e.preventDefault();
});
// save image size data (don"t use parentheses when calling storeElementData so it's callback)
changingElement.onload = storeElementData;
form.reset();
}
function bringImageToTop(imageTouched) {
// when an image gets clicked or dragged, bring it to top!
if (editing) {
// copy map values to temp list
let tempValuesList = [];
let orderHelper = [];
imageMap.forEach((value, key) => {
tempValuesList.push(value);
orderHelper.push(value.id);
});
// put clicked image at end of temp array
let imageIndex = orderHelper.indexOf(imageTouched.id);
tempValuesList.splice(imageIndex, 1);
tempValuesList.push(imageMap.get(imageTouched.id));
// set new z-indexes and repopulate map based on temp array
imageMap = new Map();
for (let i = 0; i < tempValuesList.length; i++) {
let element = document.getElementById(tempValuesList[i].id);
element.style.zIndex = i.toString();
imageMap.set(tempValuesList[i].id, tempValuesList[i]);
}
}
}
function mouseDownRemovableElement(mouseDown) {
if (!removing) {
return;
}
let imageToRemove = mouseDown.currentTarget.id;
containerDataMap.delete(imageToRemove);
imageMap.delete(imageToRemove);
document.getElementById(imageToRemove).remove();
}
/************************************
* CONTAINER CUSTOMIZATION HANDLERS *
************************************/
function changeContainerBackground(colorChange) {
let containerId = colorChange.currentTarget.id.split("-")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.backgroundRgba = hexToRgba(
document.getElementById(container.id + "-settings-bg-color").value,
document.getElementById(container.id + "-settings-bg-alpha").value
);
document.getElementById(containerId).style.backgroundColor =
container.containerSettings.backgroundRgba;
}
function changeLinkColor(colorChange) {
let containerId = colorChange.currentTarget.id.split("-")[0];
let container = containerDataMap.get(containerId);
container.containerSettings.linkColor = colorChange.target.value;
let linkElements = document.getElementsByClassName("link");
for (let i = 0; i < linkElements.length; i++) {
linkElements[i].style.color = containerSettings.linkColor;
}
}
function changeLinkSize(sizeChange) {
containerSettings.linkSize =
sizeChange.target.value == "" ? "0" : sizeChange.target.value;
let linkElements = document.getElementsByClassName("link");
for (let i = 0; i < linkElements.length; i++) {
linkElements[i].style.fontSize = containerSettings.linkSize + "px";
}
}
function changeSectionColor(colorChange) {
containerSettings.sectionColor = colorChange.target.value;
let sectionElements = document.getElementsByClassName("section");
for (let i = 0; i < sectionElements.length; i++) {
sectionElements[i].style.color = containerSettings.sectionColor;
}
}
function changeSectionSize(sizeChange) {
containerSettings.sectionSize =
sizeChange.target.value == "" ? "0" : sizeChange.target.value;
let sectionElements = document.getElementsByClassName("section");
for (let i = 0; i < sectionElements.length; i++) {
sectionElements[i].style.fontSize =
containerSettings.sectionSize + "px";
}
}
function toggleSectionBold(checkbox) {
containerSettings.sectionBold = this.checked;
loadSections();
}
function toggleSectionItalic(checkbox) {
containerSettings.sectionItalic = this.checked;
loadSections();
}
// BORDER
function changeContainerBorderWidth(borderChange) {
containerSettings.borderWidth = borderChange.target.value;
document.getElementById("mainContainer").style.borderWidth =
containerSettings.borderWidth + "px";
}
function changeContainerBorderRadius(radiusChange) {
containerSettings.borderRadius = radiusChange.target.value;
document.getElementById("mainContainer").style.borderRadius =
containerSettings.borderRadius + "px";
}
function changeContainerBorderColor(colorChange) {
containerSettings.borderColor = colorChange.target.value;
document.getElementById("mainContainer").style.borderColor =
containerSettings.borderColor;
}
// CLOCK
function changeClockColor(colorChange) {
containerSettings.clockColor = colorChange.target.value;
document.getElementById("clock").style.color =
containerSettings.clockColor;
}
function changeClockSize(sizeChange) {
containerSettings.clockSize =
sizeChange.target.value == "" ? "0" : sizeChange.target.value;
document.getElementById("clock").style.fontSize =
containerSettings.clockSize + "px";
}
function toggleClockBold(checkbox) {
containerSettings.clockBold = this.checked;
if (this.checked) {
document.getElementById("clock").style.fontWeight = "bold";
} else {
document.getElementById("clock").style.fontWeight = "normal";
}
}
function toggleClockBold(checkbox) {
containerSettings.clockBold = this.checked;
let weight = this.checked ? "bold" : "normal";
document.getElementById("clock").style.fontWeight = weight;
}
function toggleClockItalic(checkbox) {
containerSettings.clockItalic = this.checked;
let italic = this.checked ? "italic" : "normal";
document.getElementById("clock").style.fontStyle = italic;
}
// DIVIDER
function changeDividerColor(colorChange) {
containerSettings.dividerColor = colorChange.target.value;
let divider = document.getElementById("containerDivider");
// set multiple places for compatibility
divider.style.color = containerSettings.dividerColor;
divider.style.backgroundColor = containerSettings.dividerColor;
divider.style.borderColor = containerSettings.dividerColor;
}
function disableDivider(checkbox) {
containerSettings.disableDivider = this.checked;
document.getElementById("containerDivider").hidden = this.checked;
if (this.checked && links.length > 0) {
document.getElementById("clock").style.marginBottom = "18px";
} else {
document.getElementById("clock").style.marginBottom = "0px";
}
}
// TEXT
function changeContainerAlignment() {
if (document.getElementById("setLeftAlign").checked) {
containerSettings.centerAlign = false;
document.getElementById("mainContainer").style.textAlign = "left";
} else if (document.getElementById("setCenterAlign").checked) {
containerSettings.centerAlign = true;
document.getElementById("mainContainer").style.textAlign = "center";
}
}
function changeFont() {
let input = document.getElementById("fontInput").value;
if (input == "") {
return;
}
// wipe current font tags
let oldTags = document.getElementsByTagName("link");
for (let i = 0; i < oldTags.length; i++) {
oldTags[0].remove();
}
// parse input
fontCode = input;
fontName = fontCode.slice(fontCode.indexOf("family=") + 7);
fontName = fontName.slice(0, fontName.indexOf("&") + 1);
fontName = fontName.slice(0, fontName.indexOf(":")).replace("+", " ");
// set new font
document.head.insertAdjacentHTML("beforeend", fontCode);
document.body.style.fontFamily = fontName;
document.getElementById("currentFont").innerHTML = fontName;
}
// SHADOW
function changeContainerShadow() {
containerSettings.shadowX =
document.getElementById("shadowX").value == ""
? 0
: document.getElementById("shadowX").value;
containerSettings.shadowY =
document.getElementById("shadowY").value == ""
? 0
: document.getElementById("shadowY").value;
containerSettings.shadowBlur =
document.getElementById("shadowBlur").value == ""
? 0
: document.getElementById("shadowBlur").value;
containerSettings.shadowRgba = hexToRgba(
document.getElementById("shadowColorPicker").value,
document.getElementById("shadowAlpha").value
);
let shadow =
containerSettings.shadowX +
"px " +
containerSettings.shadowY +
"px " +
containerSettings.shadowBlur +
"px " +
containerSettings.shadowRgba;
// set shadow
document.getElementById("mainContainer").style.boxShadow = shadow;
}
/************************************
* RESET / IMPORT & EXPORT HANDLERS *
************************************/
function wipeBookmarks() {
// clear bookmark arrays
links = [];
sections = [];
localStorage.setItem("links", JSON.stringify(links));
localStorage.setItem("sections", JSON.stringify(sections));
if (containerSettings.disableDivider) {
document.getElementById("clock").style.marginBottom = "0px";
}
// refresh container
loadSections();
}
function wipeWallpaper() {
document.body.style.backgroundImage = "";
document.body.style.background = "";
wallpaper = "";
}
function wipeImages() {
imageMap = new Map();
// wipe images from screen
imageElements = document.getElementsByClassName("userImage");
for (let i = imageElements.length - 1; i >= 0; i--) {
imageElements[i].remove();
}
// remove image position + sizes from element data map
let tempMap = new Map();
tempMap.set(
"settingsContainer",
containerDataMap.get("settingsContainer")
);
tempMap.set("mainContainer", containerDataMap.get("mainContainer"));
containerDataMap = tempMap;
}
function wipeContainerSettings() {
containerSettings = defaultTextContainerSettings;
containerDataMap.get("mainContainer").width = "auto";
containerDataMap.get("mainContainer").height = "auto";
containerDataMap.get("mainContainer").x = "10px";
containerDataMap.get("mainContainer").y = "10px";
document.getElementById("mainContainer").style.width = "auto";
document.getElementById("mainContainer").style.height = "auto";
document.getElementById("mainContainer").style.top = "10px";
document.getElementById("mainContainer").style.left = "10px";
applyContainerSettings();
loadSections();
}
function setDefault() {
links = [
{
url: "https://www.youtube.com",
label: "youtube",
section: "video + picture",
},
{
url: "https://www.twitch.com",
label: "twitch",
section: "video + picture",
},
{
url: "https://www.instagram.com",
label: "insta",
section: "video + picture",
},
{
url: "https://www.archive.org",
label: "archive",
section: "video + picture",
},
{
url: "https://www.soundcloud.com",
label: "soundcloud",
section: "music",
},
{
url: "https://www.bandcamp.com",
label: "bandcamp",
section: "music",
},
{
url: "https://www.splice.com",
label: "splice",
section: "music",
},
{
url: "https://www.usevia.app",
label: "via",
section: "utils",
},
{
url: "https://www.distrokid.com/home",
label: "distrokid",
section: "utils",
},
];
sections = ["video + picture", "music", "utils"];
localStorage.setItem("links", JSON.stringify(links));
localStorage.setItem("sections", JSON.stringify(sections));
loadSections();
}
function resetFont() {
let oldTags = document.getElementsByTagName("link");
for (let i = 0; i < oldTags.length; i++) {
oldTags[0].remove();
}
document.body.style.fontFamily = "";
fontCode = "";
fontName = "";
}
function resetCursors() {
cursors = {};
document.getElementsByTagName("html")[0].style.cursor = "auto";
}
async function exportData() {
try {
window.focus();
await navigator.clipboard.writeText(JSON.stringify(localStorage));
} catch (error) {
console.error(error.message);
}
alert(
"export to clipboard successful. paste contents to file and keep safe!"
);
}
function importData(buttonClick) {
let data = document.getElementById("importDataInput").value;
if (data == "") {
return;
}
try {
data = JSON.parse(document.getElementById("importDataInput").value);
} catch (error) {
alert("please only enter data that was provided by the export button!");
}
for (const [key, value] of Object.entries(data)) {
localStorage.setItem(key, value);
}
alert(
"data import successful. " +
"please refresh the page!" +
"\n(you can ignore unsaved changes warning, the imported data has already been saved)"
);
justImported = true;
}
/*****************
* COLOR HELPERS *
*****************/
function hexToRgba(hex, alpha) {
const sanitizedHex = hex.replace(/^#/, "");
const parsedHex = parseInt(sanitizedHex, 16);
return (
"rgba(" +
((parsedHex >> 16) & 255) +
"," +
((parsedHex >> 8) & 255) +
"," +
(parsedHex & 255) +
"," +
alpha / 100 +
")"
);
}
function rgbToHex(rgb) {
const toHex = (c) => {
const hex = c.toString(16);
return hex.length === 1 ? "0" + hex : hex;
};
return (
"#" +
toHex(parseInt(rgb[0])) +
toHex(parseInt(rgb[1])) +
toHex(parseInt(rgb[2]))
);
}
</script>
</html>