Initial Release
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Stickerboard - pancakz</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="description" content="bakonpancakz's Stickerboard!">
|
||||
<link rel="stylesheet" href="/assets/index.css">
|
||||
<link rel="icon" href="/assets/favicon.png">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="layout-wrapper">
|
||||
|
||||
<div class="layout-canvas">
|
||||
<div class="section-canvas">
|
||||
<p class="chalk-highlight">loading stickers</p>
|
||||
<img draggable="false" id="canvas" src="/assets/stickerboard.webp" alt="Latest Stickerboard" style="opacity: 0;" onload="this.style.opacity=1">
|
||||
<img draggable="false" id="preview">
|
||||
</div>
|
||||
<div class="section-make-row">
|
||||
<p class="chalk-primary">An AI will be monitoring your behaviour today ◑﹏◐</p>
|
||||
</div>
|
||||
<div class="section-make-row">
|
||||
<a target="_blank" href="https://piapro.net/intl/en.html">
|
||||
<img alt="Mikufan Button" src="/assets/button_mikufan.png">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout-create">
|
||||
<button id="pane-swap">Create Sticker</button>
|
||||
|
||||
<div class="layout-pane pane-stickers">
|
||||
{{ range . }}
|
||||
{{ if .Visible}}
|
||||
<div class="section-post" data-offsetx="{{ .OffsetX }}" data-offsety="{{ .OffsetY }}" data-scale="{{ .ImageScale }}" data-height="{{ .ImageHeight }}" data-width="{{ .ImageWidth}}">
|
||||
{{ if .UserName }}
|
||||
<a class="text-header" href="{{ .UserURL }}" title="Visit '{{ .UserURL }}'" target="_blank">{{ .UserName }}</a>
|
||||
{{ else }}
|
||||
<p class="text-header">Anonymous</p>
|
||||
{{ end }}
|
||||
<p class="text-hint">{{ .Created.Format "Jan 02 2006 - 3:04 PM" }}</p>
|
||||
<p class="text-description">{{ .Message }}</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="layout-pane pane-uploader" hidden>
|
||||
<div class="form-section">
|
||||
<p class="text-header">Sticker</p>
|
||||
<p class="text-description">
|
||||
Select the image you wish to share.
|
||||
It must be in either <u>GIF</u>, <u>JPEG</u>, or <u>PNG</u> format,
|
||||
up to 16 Megabytes in size, and up to 2048 pixels in dimensions.
|
||||
</p>
|
||||
<input id="form-file" type="file" accept=".gif,.jpg,.jpeg,.png,.webp" hidden>
|
||||
<button onclick="document.querySelector('#form-file').click()">
|
||||
Select File
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<p class="text-header">Placement</p>
|
||||
<p class="text-description">
|
||||
You can position your sticker by either clicking anywhere
|
||||
on the <u id="canvas" style="cursor: help;">canvas</u>
|
||||
or by using the inputs boxes below.
|
||||
</p>
|
||||
<div class="form-unique-placement">
|
||||
<p>X:</p>
|
||||
<input id="form-x" type="number" value="0" disabled>
|
||||
<p>Y:</p>
|
||||
<input id="form-y" type="number" value="0" disabled>
|
||||
<p>Scale:</p>
|
||||
<input id="form-s" type="number" min="1" max="100" value="100" disabled>
|
||||
</div>
|
||||
<script>
|
||||
(() => {
|
||||
// Sometimes people don't know, yknow?
|
||||
const u = document.querySelector("u#canvas")
|
||||
const c = document.querySelector("img#canvas")
|
||||
u.onmouseleave = () => c.style.borderColor = 'var(--color-primary)'
|
||||
u.onmouseover = () => c.style.borderColor = 'var(--color-highlight)'
|
||||
})()
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<p class="text-header">Profile</p>
|
||||
<p class="text-description">
|
||||
Leave these boxes blank to remain anonymous. You can
|
||||
include a URL to your personal homepage or social media
|
||||
if you like.
|
||||
</p>
|
||||
<input id="form-username" type="text" autocomplete="off" placeholder="Username: bakonpancakz">
|
||||
<input id="form-userurl" type="text" autocomplete="off" placeholder="URL: https://panca.kz/">
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<p class="text-header">Comment</p>
|
||||
<p class="text-description">
|
||||
Leave a comment for other users to see.
|
||||
Inappropriate comments will be censored.
|
||||
</p>
|
||||
<textarea id="form-comment" maxlength="1000" placeholder="Hello World!"></textarea>
|
||||
<script>
|
||||
(() => {
|
||||
// Good for the soul :3
|
||||
const c = document.querySelector("textarea#form-comment")
|
||||
const x = [
|
||||
"blah blah blah",
|
||||
"sometimes i dream about cheese",
|
||||
"weiner",
|
||||
"mikudayo",
|
||||
]
|
||||
c.placeholder = x[Math.floor(Math.random() * x.length)]
|
||||
})()
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<p id="form-error">...</p>
|
||||
</div>
|
||||
|
||||
<div class="form-section" style="margin-bottom: 0;">
|
||||
<button id="form-submit">Post</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-make-row">
|
||||
<a class="chalk-highlight" href="https://panca.kz/">Homepage</a>
|
||||
<span class="chalk-secondary">•</span>
|
||||
<a class="chalk-highlight" href="https://panca.kz/goto/stickerboard">Source</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
<script src="/assets/index.js"></script>
|
||||
|
||||
</html>
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,301 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--color-background: black;
|
||||
--color-text: #f0f0f0;
|
||||
--color-gray: #a3a3a3;
|
||||
--color-primary: #86cecb;
|
||||
--color-secondary: #128888;
|
||||
--color-highlight: #e12885;
|
||||
--canvas-width: 854px;
|
||||
--canvas-height: 480px;
|
||||
}
|
||||
|
||||
div::-webkit-scrollbar {
|
||||
border: 1px solid var(--color-secondary);
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
div::-webkit-scrollbar-thumb {
|
||||
border: 1px solid var(--color-secondary);
|
||||
background-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-background);
|
||||
margin: 64px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
textarea,
|
||||
button,
|
||||
input,
|
||||
p,
|
||||
a {
|
||||
color: var(--color-text);
|
||||
font-family: monospace;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.chalk-primary {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.chalk-secondary {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.chalk-highlight {
|
||||
color: var(--color-highlight);
|
||||
}
|
||||
|
||||
.chalk-gray {
|
||||
color: var(--color-gray);
|
||||
}
|
||||
|
||||
.text-header {
|
||||
font-size: x-large;
|
||||
font-weight: bold;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.text-hint {
|
||||
color: var(--color-gray);
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.text-description {
|
||||
color: var(--color-secondary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.text-description u {
|
||||
color: var(--color-highlight);
|
||||
}
|
||||
|
||||
/* Site Layout - Canvas */
|
||||
div.layout-wrapper {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
div.layout-canvas {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
div.section-canvas {
|
||||
min-width: var(--canvas-width);
|
||||
max-width: var(--canvas-width);
|
||||
min-height: var(--canvas-height);
|
||||
max-height: var(--canvas-height);
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border: 1px solid var(--color-primary);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.section-canvas img#preview {
|
||||
border: 1px solid var(--color-highlight);
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
div.section-canvas>* {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
div.section-make-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Site Layout - Panes */
|
||||
div.layout-create {
|
||||
min-height: var(--canvas-height);
|
||||
max-height: var(--canvas-height);
|
||||
min-width: 300px;
|
||||
max-width: 300px;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
button#pane-swap {
|
||||
border: 1px solid var(--color-primary);
|
||||
box-sizing: border-box;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 32px;
|
||||
max-height: 32px;
|
||||
}
|
||||
|
||||
button#pane-swap:active,
|
||||
button#pane-swap:focus-visible {
|
||||
background-color: var(--color-primary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.layout-pane {
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: calc(var(--canvas-height) - 30px);
|
||||
max-height: calc(var(--canvas-height) - 30px);
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
div.form-section {
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/* Site Layout - Pane Posts */
|
||||
div.section-post:not(:last-child) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
div.section-post {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
div.section-post>a[href] {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div.section-post>a[href]:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.section-post>a[href=""] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
/* Site Layout - Pane Upload */
|
||||
div.form-section>button {
|
||||
border: 1px solid var(--color-primary);
|
||||
box-sizing: border-box;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 24px;
|
||||
max-height: 24px;
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
div.form-section>button:active,
|
||||
div.form-section>button:focus-visible {
|
||||
background-color: var(--color-secondary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.form-section>input {
|
||||
padding: 0 4px;
|
||||
border: 1px solid var(--color-primary);
|
||||
box-sizing: border-box;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 24px;
|
||||
max-height: 24px;
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
div.form-section>input:focus-visible {
|
||||
border-color: var(--color-primary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.form-section>input::placeholder {
|
||||
color: var(--color-gray);
|
||||
}
|
||||
|
||||
div.form-section>textarea {
|
||||
padding: 4px;
|
||||
border: 1px solid var(--color-secondary);
|
||||
box-sizing: border-box;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 72px;
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
div.form-section>textarea:focus-visible {
|
||||
border-color: var(--color-primary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.form-section>textarea::placeholder {
|
||||
color: var(--color-gray);
|
||||
}
|
||||
|
||||
div.form-unique-placement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
div.form-unique-placement>p {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
div.form-unique-placement>input {
|
||||
padding: 0 4px;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
border: 1px solid var(--color-secondary);
|
||||
box-sizing: border-box;
|
||||
background: none;
|
||||
min-height: 24px;
|
||||
max-height: 24px;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.form-unique-placement>input:disabled {
|
||||
color: var(--color-gray);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
div.form-unique-placement>input:not(:disabled):focus-visible,
|
||||
div.form-unique-placement>input:not(:disabled):active {
|
||||
border-color: var(--color-highlight);
|
||||
}
|
||||
|
||||
div.form-section>p#form-error {
|
||||
color: var(--color-highlight);
|
||||
text-align: center;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
div.form-section>button#form-submit {
|
||||
min-height: 32px;
|
||||
max-height: 32px;
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
//@ts-check
|
||||
|
||||
// ██
|
||||
// ██ ██ ██
|
||||
// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
// ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
// ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
// ██ ██
|
||||
// ██
|
||||
|
||||
(() => {
|
||||
const blank_pixel = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
|
||||
|
||||
/** @param {any} s */
|
||||
const $ = s => document.querySelector(s)
|
||||
|
||||
/** @param {string} s */
|
||||
const safe_url = s => {
|
||||
try {
|
||||
new URL(s)
|
||||
return true
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {Blob} f */
|
||||
const file_url = f => new Promise((o, r) => {
|
||||
const reader = new FileReader()
|
||||
reader.onerror = e => r(e)
|
||||
reader.onload = () => o(reader.result)
|
||||
reader.readAsDataURL(f)
|
||||
})
|
||||
|
||||
const
|
||||
/** @type {HTMLImageElement?} */ elemCanvas = $("#canvas"),
|
||||
/** @type {HTMLImageElement?} */ elemPreview = $("#preview"),
|
||||
/** @type {HTMLInputElement?} */ formFile = $("#form-file"),
|
||||
/** @type {HTMLInputElement?} */ formX = $("#form-x"),
|
||||
/** @type {HTMLInputElement?} */ formY = $("#form-y"),
|
||||
/** @type {HTMLInputElement?} */ formS = $("#form-s"),
|
||||
/** @type {HTMLInputElement?} */ formUserName = $("#form-username"),
|
||||
/** @type {HTMLInputElement?} */ formUserURL = $("#form-userurl"),
|
||||
/** @type {HTMLTextAreaElement?} */ formComment = $("#form-comment"),
|
||||
/** @type {HTMLParagraphElement?} */ formError = $("#form-error"),
|
||||
/** @type {HTMLButtonElement?} */ formSubmit = $("#form-submit"),
|
||||
/** @type {HTMLDivElement?} */ paneStickers = $(".pane-stickers"),
|
||||
/** @type {HTMLDivElement?} */ paneUploader = $(".pane-uploader"),
|
||||
/** @type {HTMLButtonElement?} */ buttonSwap = $("#pane-swap")
|
||||
|
||||
let CANVAS_WIDTH = 854, CANVAS_HEIGHT = 480, CANVAS_STICKER_MAX_HEIGHT = 240, CANVAS_STICKER_MAX_HEIGHT_START = 160
|
||||
let scale = 1, ox = 0, oy = 0, iw = 0, ih = 0, click = false, busy = false, preview = false
|
||||
|
||||
// Pane Swapping
|
||||
if (buttonSwap) buttonSwap.onclick = (() => {
|
||||
let viewUploader = false
|
||||
return () => {
|
||||
if (!paneStickers || !paneUploader) return
|
||||
if (viewUploader) {
|
||||
buttonSwap.textContent = "Create Sticker"
|
||||
paneStickers.removeAttribute("hidden")
|
||||
paneUploader.setAttribute("hidden", "true")
|
||||
canvas_clear()
|
||||
} else {
|
||||
buttonSwap.textContent = "Cancel"
|
||||
paneStickers.setAttribute("hidden", "true")
|
||||
paneUploader.removeAttribute("hidden")
|
||||
}
|
||||
viewUploader = !viewUploader
|
||||
}
|
||||
})()
|
||||
|
||||
// Sticker Preview
|
||||
function preview_update() {
|
||||
if (!preview || !formS || !formY || !formX || !elemCanvas || !elemPreview) return
|
||||
|
||||
let max = Math.floor((CANVAS_STICKER_MAX_HEIGHT / ih) * 100)
|
||||
if (max > 100) max = 100
|
||||
formS.max = String(max)
|
||||
formS.removeAttribute("disabled")
|
||||
|
||||
formX.min = String(0 - Math.round(iw * scale))
|
||||
formX.max = String(elemCanvas.offsetWidth)
|
||||
formX.removeAttribute("disabled")
|
||||
|
||||
formY.min = String(0 - Math.round(ih * scale))
|
||||
formY.max = String(elemCanvas.offsetHeight)
|
||||
formY.removeAttribute("disabled")
|
||||
|
||||
elemPreview.style.opacity = "1"
|
||||
elemPreview.style.height = `${Math.round(ih * scale)}px`
|
||||
elemPreview.style.width = `${Math.round(iw * scale)}px`
|
||||
elemPreview.style.bottom = `${oy}px`
|
||||
elemPreview.style.left = `${ox}px`
|
||||
}
|
||||
if (formX) formX.oninput = () => {
|
||||
const e = formX, v = e.valueAsNumber
|
||||
if (isNaN(v)) return
|
||||
if (v < parseInt(e.min)) e.value = e.min
|
||||
if (v > parseInt(e.max)) e.value = e.max
|
||||
ox = e.valueAsNumber
|
||||
preview_update()
|
||||
}
|
||||
if (formY) formY.oninput = () => {
|
||||
const e = formY, v = e.valueAsNumber
|
||||
if (isNaN(v)) return
|
||||
if (v < parseInt(e.min)) e.value = e.min
|
||||
if (v > parseInt(e.max)) e.value = e.max
|
||||
oy = e.valueAsNumber
|
||||
preview_update()
|
||||
}
|
||||
if (formS) formS.oninput = () => {
|
||||
const e = formS, v = e.valueAsNumber
|
||||
if (isNaN(v)) return
|
||||
if (v < parseInt(e.min)) e.value = e.min
|
||||
if (v > parseInt(e.max)) e.value = e.max
|
||||
scale = e.valueAsNumber / 100
|
||||
preview_update()
|
||||
}
|
||||
|
||||
if (formFile) formFile.onchange = async () => {
|
||||
if (!elemPreview || !formS || !formX || !formY) return
|
||||
const image = formFile.files?.item(0)
|
||||
if (!image) {
|
||||
elemPreview.style.opacity = "0"
|
||||
elemPreview.src = blank_pixel
|
||||
return
|
||||
}
|
||||
elemPreview.src = await file_url(image)
|
||||
elemPreview.onload = () => {
|
||||
iw = elemPreview.naturalWidth
|
||||
ih = elemPreview.naturalHeight
|
||||
if (ih > CANVAS_STICKER_MAX_HEIGHT_START) {
|
||||
scale = CANVAS_STICKER_MAX_HEIGHT_START / ih
|
||||
}
|
||||
ox = Math.round((CANVAS_WIDTH / 2) - ((iw * scale) / 2))
|
||||
oy = Math.round((CANVAS_HEIGHT / 2) - ((ih * scale / 2)))
|
||||
formS.valueAsNumber = Math.round(scale * 100)
|
||||
formX.valueAsNumber = ox
|
||||
formY.valueAsNumber = oy
|
||||
preview = true
|
||||
preview_update()
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {MouseEvent} ev */
|
||||
function preview_move(ev) {
|
||||
if (!preview || !formX || !formY || !elemPreview) return
|
||||
formX.valueAsNumber = ev.offsetX - Math.round((iw * scale) / 2)
|
||||
formY.valueAsNumber = CANVAS_HEIGHT - ev.offsetY - Math.round((ih * scale) / 2)
|
||||
if (formX.oninput) formX.oninput(ev)
|
||||
if (formY.oninput) formY.oninput(ev)
|
||||
}
|
||||
if (elemCanvas) {
|
||||
elemCanvas.onclick = preview_move
|
||||
document.onmouseup = () => { click = false }
|
||||
elemCanvas.onmousedown = () => { click = true }
|
||||
elemCanvas.onmousemove = e => { click && preview_move(e) }
|
||||
}
|
||||
|
||||
// Sticker Forms
|
||||
function canvas_clear() {
|
||||
if (
|
||||
!formX || !formY || !formS || !formComment || !formUserName ||
|
||||
!formUserURL || !formFile || !elemPreview || !elemPreview || !formError
|
||||
) return
|
||||
for (const elem of [formX, formY, formS]) {
|
||||
elem.setAttribute("disabled", "true")
|
||||
elem.value = "0"
|
||||
}
|
||||
formS.value = "100"
|
||||
formComment.value = ""
|
||||
formUserName.value = ""
|
||||
formUserURL.value = ""
|
||||
formFile.value = ""
|
||||
elemPreview.style.opacity = "0"
|
||||
elemPreview.src = blank_pixel
|
||||
formError.textContent = "..."
|
||||
preview = false
|
||||
}
|
||||
if (formSubmit) formSubmit.onclick = async () => {
|
||||
if (
|
||||
busy || !formX || !formY || !formS || !elemCanvas || !formError ||
|
||||
!formFile || !formUserURL || !formUserName || !formComment
|
||||
) return
|
||||
busy = true
|
||||
try {
|
||||
// Sanity Checks
|
||||
// Comment, X, Y, and Scale already accounted for
|
||||
const image = formFile.files?.item(0)
|
||||
if (!image) {
|
||||
throw "Missing Sticker"
|
||||
}
|
||||
if (formUserURL.value && !safe_url(formUserURL.value)) {
|
||||
throw "Invalid URL"
|
||||
}
|
||||
|
||||
// Generate Form Body
|
||||
const form = new FormData()
|
||||
form.append("sticker", image, "sticker")
|
||||
form.append("data", JSON.stringify({
|
||||
offset_x: formX.valueAsNumber,
|
||||
offset_y: formY.valueAsNumber,
|
||||
image_scale: formS.valueAsNumber,
|
||||
user_url: formUserURL.value,
|
||||
user_name: formUserName.value,
|
||||
message: formComment.value,
|
||||
}))
|
||||
|
||||
// Send Image
|
||||
formError.textContent = "Uploading, please wait..."
|
||||
const resp = await fetch("/stickers", { method: "POST", body: form })
|
||||
if (resp.status !== 201) {
|
||||
throw `${resp.status}: ${await resp.text() || resp.statusText}`
|
||||
}
|
||||
formError.textContent = "Done! Refreshing..."
|
||||
window.location.reload()
|
||||
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
formError.textContent = String(err)
|
||||
}
|
||||
busy = false
|
||||
}
|
||||
|
||||
// Sticker Hovering
|
||||
document.querySelectorAll(".section-post").forEach(elem => {
|
||||
const x = parseInt(elem.getAttribute("data-offsetx") || "0")
|
||||
const y = parseInt(elem.getAttribute("data-offsety") || "0")
|
||||
const s = parseFloat(elem.getAttribute("data-scale") || "0")
|
||||
const h = parseInt(elem.getAttribute("data-height") || "0")
|
||||
const w = parseInt(elem.getAttribute("data-width") || "0")
|
||||
elem.addEventListener("mouseover", () => {
|
||||
if (elemPreview) {
|
||||
elemPreview.style.opacity = "1"
|
||||
elemPreview.style.height = `${Math.round(h * s)}px`
|
||||
elemPreview.style.width = `${Math.round(w * s)}px`
|
||||
elemPreview.style.bottom = `${y}px`
|
||||
elemPreview.style.left = `${x}px`
|
||||
}
|
||||
})
|
||||
elem.addEventListener("mouseout", () => {
|
||||
if (elemPreview) {
|
||||
elemPreview.style.opacity = "0"
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
})()
|
||||
Reference in New Issue
Block a user