Files
tinypin/client/app.js
2021-01-29 14:23:37 -06:00

293 lines
7.3 KiB
JavaScript

Reef.debug(true);
// force a re-render
app.addSetter("render", (data) => {
appComponent.render();
});
app.addSetter("loader.show", (data) => {
data.loading++;
});
app.addSetter("loader.hide", (data) => {
data.loading--;
});
app.addSetter("load.boards", async (data) => {
store.do("loader.show");
let res = await fetch("/api/boards");
data.boards = await res.json();
data.initialized = true;
store.do("loader.hide");
});
// handle update events
window.addEventListener("broadcast", async (e) => {
let data = store.data;
if ( e.detail.updateBoard ){
console.log("updating board");
let boardId = e.detail.updateBoard;
store.do("load.boards");
// if we are currently viewing this board, reload the pins
if ( data.board && boardId && boardId == data.board.id ){
store.do("load.board", true);
}
} else if ( e.detail.deleteBoard ) {
console.log("deleting board");
let boardId = e.detail.deleteBoard;
// reload the boards
store.do("load.boards");
// we're currently looking at this board... alert and error
if ( data.board && boardId == data.board.id ){
window.alert("this board has been deleted on another device");
window.location.hash = "#";
}
}
});
app.addSetter('load.board', async (data, force) => {
store.do("loader.show");
if ( data.hash.board ){
if ( force || !data.board || data.board.id != data.hash.board ){
let res = await fetch("/api/boards/" + data.hash.board);
data.board = await res.json();
}
}
store.do("loader.hide");
});
app.addSetter('load.user', async (data) => {
store.do("loader.show");
let res = await fetch("/api/whoami");
data.user = await res.json();
window.uid = data.user.id;
dispatchSocketConnect();
store.do("loader.hide");
});
app.addSetter("hash.update", (data) => {
data.hash = parseQueryString(window.location.hash.substr(1));
if ( data.hash.board ){
store.do('load.board');
} else {
data.board = null;
data.pinZoomModal.active = false;
data.addPinModal.active = false;
data.aboutModal.active = false;
}
});
function dispatchSocketConnect(){
window.dispatchEvent(new CustomEvent("socket-connect"));
}
let store = new Reef.Store({
data: {
hash: {
board: null
},
initialized: false,
menuOpen: false,
loading: 0,
user: null,
showHiddenBoards: window.localStorage.showHiddenBoards == "true" || false,
boards: [],
board: null,
addPinModal: {
pinId: null,
active: false,
boardId: "",
newBoardName: null,
imageUrl: "",
previewImageUrl: null,
siteUrl: "",
description: "",
saveInProgress: false
},
pinZoomModal: {
active: false,
pin: null,
fullDescriptionOpen: false
},
aboutModal: {
active: false
},
editBoardModal: {
active: false,
name: "",
hidden: 0
},
editPinModal: {
active: false,
pin: null,
newBoardName: null,
saveInProgress: false
}
},
getters: app.getGetters(),
setters: app.getSetters()
});
app.freeze();
// init the app component
const appComponent = new Reef("#app", {
store: store,
template: (data) => {
return /*html*/`
<div id="navbar"></div>
<section class="section">
<div class="container" id="brickwall-container">
<div id="brickwall" class="brickwall"></div>
</div>
</section>
<div id="addPinModal"></div>
<div id="pinZoomModal"></div>
<div id="editBoardModal"></div>
<div id="aboutModal"></div>
<div id="editPinModal"></div>
`
//<div id="loader" class="button is-text ${data.loading ? 'is-loading' : ''}"></div>
}
});
// attach all the child components
for (const [name, f] of Object.entries(app.getComponents())) {
let c = f(store);
if ( !c ){
throw(new Error(`component ${name} did not return a Reef component`));
} else {
appComponent.attach(c);
}
}
document.addEventListener('click', (el) => {
// we always want to close the menu on click. if we clicked an item,
// that will still trigger below
let burger = el.target.closest('.navbar-burger');
if ( !burger && store.data.menuOpen ){
store.do('navbar.closeMenu');
}
let target = el.target.closest('[data-onclick]');
if (target) {
let action = target.getAttribute('data-onclick');
if (action) {
try{
store.do(action, target);
} catch (err){
console.error(`Error invoking ${action}:`, err);
}
}
} else {
let targetx = el.target.closest('[data-onclick-x]'); // onclick-x attempts to invoke a function on window instead of a setter. this is useful to bypass re-rendering
if ( targetx ){
let actionx = targetx.getAttribute('data-onclick-x');
if ( actionx ){
window[actionx](targetx);
}
}
}
});
// focusout bubbles while 'blur' does not.
document.addEventListener('focusout', (el) => {
let target = el.target.closest('[data-onblur]');
if ( target ){
let method = target.getAttribute('data-onblur');
if ( method ) {
store.do(method, target);
}
}
});
document.addEventListener('keyup', (el) => {
if ( store.data.pinZoomModal.active ){
if ( el.key == "Escape" ){
store.do('pinZoomModal.close');
} else if ( el.key == "ArrowLeft" ){
store.do('pinZoomModal.moveLeft');
} else if ( el.key == "ArrowRight" ){
store.do('pinZoomModal.moveRight');
}
}
if ( store.data.addPinModal.active ){
if ( el.key == "Escape" ){
store.do('addPinModal.close');
}
}
if ( store.data.aboutModal.active ){
if ( el.key == "Escape" ){
store.do('aboutModal.close');
}
}
});
window.addEventListener("hashchange", () => {
store.do("hash.update");
});
window.addEventListener('resize', (evt) => {
store.do("render");
});
Reef.databind(appComponent);
store.do('load.user');
store.do('load.boards');
store.do('hash.update');
appComponent.render();
// refresh on load.
window.lastVisibilityChange = new Date().getTime();
document.addEventListener("visibilitychange", async () => {
let now = new Date().getTime();
// only run if we haven't run in the last second.. prevent double updates
if ( document.visibilityState === 'visible' && (now - window.lastVisibilityChange) > 1000) {
window.lastVisibilityChange = now;
let connected = false;
if ( dispatchSocketConnect ){ // maybe we stripped out web sockets
connected = await dispatchSocketConnect();
}
if ( !connected ){
store.do("load.boards");
store.do("load.board");
}
}
});