135 lines
5.3 KiB
JavaScript
135 lines
5.3 KiB
JavaScript
// PayToLaugh front end.
|
|
//
|
|
// NOTE ON THE TOKEN BELOW: it is public by construction. A static page cannot
|
|
// keep a secret, so the endpoint behind it is built to be safe in public
|
|
// hands. It reads and writes small records only and cannot spend money.
|
|
// Script and video generation are deliberately NOT reachable from here.
|
|
//
|
|
// NOTE ON "LOGIN": an email address identifies you, it does not authenticate
|
|
// you. Anyone who knows an address can see that account. Temporary, by
|
|
// agreement, until real auth lands.
|
|
|
|
var API = "https://api.corben.world/functions/da0474ea-4e56-405c-9f0d-35c3e4037a69/invoke"
|
|
+ "?token=97cd624cd2bd6a74eb692a940e71851f";
|
|
|
|
var PHOTO_KINDS = ["head shot", "profile", "full body", "sitting",
|
|
"playing", "sleeping", "action", "other"];
|
|
// A plate is built from a head-on shot; these are the ones worth having.
|
|
var REFERENCE_KINDS = ["head shot", "profile", "full body"];
|
|
|
|
async function api(action, payload) {
|
|
var body = Object.assign({ action: action }, payload || {});
|
|
var res;
|
|
try {
|
|
res = await fetch(API, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body)
|
|
});
|
|
} catch (e) {
|
|
return { ok: false, error: "Could not reach the server. Check your connection and try again." };
|
|
}
|
|
var outer;
|
|
try { outer = await res.json(); }
|
|
catch (e) { return { ok: false, error: "The server sent something unreadable." }; }
|
|
return outer && outer.result ? outer.result : outer;
|
|
}
|
|
|
|
var Session = {
|
|
key: "ptl_email",
|
|
nameKey: "ptl_name",
|
|
get: function () { try { return localStorage.getItem(this.key); } catch (e) { return null; } },
|
|
name: function () { try { return localStorage.getItem(this.nameKey) || ""; } catch (e) { return ""; } },
|
|
set: function (email, name) {
|
|
try {
|
|
localStorage.setItem(this.key, email);
|
|
if (name) localStorage.setItem(this.nameKey, name);
|
|
} catch (e) {}
|
|
},
|
|
clear: function () {
|
|
try { localStorage.removeItem(this.key); localStorage.removeItem(this.nameKey); } catch (e) {}
|
|
}
|
|
};
|
|
|
|
function esc(s) {
|
|
return String(s == null ? "" : s).replace(/[&<>\"']/g, function (c) {
|
|
return { "&": "&", "<": "<", ">": ">", '\"': """, "'": "'" }[c];
|
|
});
|
|
}
|
|
|
|
function navBar(current) {
|
|
var email = Session.get();
|
|
var links = [["/", "Home", "home"], ["/pets.html", "My pets", "pets"],
|
|
["/create.html", "New video", "create"], ["/videos.html", "My videos", "videos"]];
|
|
var html = '<nav><div class="inner">'
|
|
+ '<a class="brand" href="/">Pay<span>To</span>Laugh</a>'
|
|
+ links.map(function (l) {
|
|
return '<a class="link' + (l[2] === current ? ' on' : '') + '" href="' + l[0] + '">' + l[1] + '</a>';
|
|
}).join("")
|
|
+ '<div class="who">'
|
|
+ (email
|
|
? esc(email) + ' <button type="button" id="ptl-signout">Sign out</button>'
|
|
: '<a class="link" href="/register.html">Sign in</a>')
|
|
+ '</div></div></nav>';
|
|
document.body.insertAdjacentHTML("afterbegin", html);
|
|
var b = document.getElementById("ptl-signout");
|
|
if (b) b.onclick = function () { Session.clear(); location.href = "/register.html"; };
|
|
}
|
|
|
|
function requireSession() {
|
|
var e = Session.get();
|
|
if (!e) { location.href = "/register.html?next=" + encodeURIComponent(location.pathname); return null; }
|
|
return e;
|
|
}
|
|
|
|
function showMsg(id, text, kind) {
|
|
var el = document.getElementById(id);
|
|
if (!el) return;
|
|
el.className = "msg " + (kind || "err");
|
|
el.textContent = text;
|
|
}
|
|
function hideMsg(id) {
|
|
var el = document.getElementById(id);
|
|
if (el) el.className = "msg";
|
|
}
|
|
|
|
// Mirrors the server's arithmetic exactly: $0.40 per 8s clip plus $0.17 for
|
|
// script and plate, at a 5x markup. If one changes, change both.
|
|
function priceFor(segments) {
|
|
var cost = segments * 0.40 + 0.17;
|
|
return { cost: cost, price: Math.round(cost * 5 * 100) / 100, seconds: segments * 8 };
|
|
}
|
|
|
|
// Shrink in the browser before upload. Phone photos run to 5-10MB, which is
|
|
// slow to send and far more detail than a reference plate needs; 1600px is
|
|
// ample. Returns base64 without the data: prefix.
|
|
function resizeImage(file, maxPx, quality) {
|
|
maxPx = maxPx || 1600;
|
|
quality = quality || 0.85;
|
|
return new Promise(function (resolve, reject) {
|
|
var reader = new FileReader();
|
|
reader.onerror = function () { reject(new Error("Could not read that file.")); };
|
|
reader.onload = function () {
|
|
var img = new Image();
|
|
img.onerror = function () { reject(new Error("That file is not an image we can read.")); };
|
|
img.onload = function () {
|
|
var w = img.naturalWidth, h = img.naturalHeight;
|
|
if (!w || !h) { reject(new Error("That image looks empty.")); return; }
|
|
var scale = Math.min(1, maxPx / Math.max(w, h));
|
|
var cw = Math.round(w * scale), ch = Math.round(h * scale);
|
|
var c = document.createElement("canvas");
|
|
c.width = cw; c.height = ch;
|
|
var g = c.getContext("2d");
|
|
g.fillStyle = "#ffffff";
|
|
g.fillRect(0, 0, cw, ch); // flatten transparency for JPEG
|
|
g.drawImage(img, 0, 0, cw, ch);
|
|
var url;
|
|
try { url = c.toDataURL("image/jpeg", quality); }
|
|
catch (e) { reject(new Error("That image could not be processed.")); return; }
|
|
resolve({ b64: url.split(",")[1], width: cw, height: ch });
|
|
};
|
|
img.src = reader.result;
|
|
};
|
|
reader.readAsDataURL(file);
|
|
});
|
|
}
|