Browse Source

placing posts on the background and fix text size/positioning

main
forest 3 months ago
parent
commit
8ca2eab871
4 changed files with 216 additions and 57 deletions
  1. +16
    -4
      main.go
  2. +8
    -2
      static/app.css
  3. +191
    -50
      static/app.js
  4. +1
    -1
      static/bpgdec8.js

+ 16
- 4
main.go View File

@ -34,19 +34,31 @@ const (
type Post struct {
Id string
X float64
Y float64
Angle float64
X int
Y int
Angle int
Width int
Height int
Collider *PostCollider
AuthorId string
AuthorDisplayName string
RandomSeed int
Type PostType
TextContent string
TransparentImage bool
URL string
ThumbnailBase64 string
Filename string
ContentType string
}
type PostCollider struct {
R int
X1 int
Y1 int
X2 int
Y2 int
}
type IndexTemplateData struct {
Title string
AppStateJSON string


+ 8
- 2
static/app.css View File

@ -243,8 +243,6 @@ textarea {
width: 420px;
}
.text-post-svg-container {}
.text-post-randomize-button-container {
position: relative;
width: 100%;
@ -318,4 +316,12 @@ textarea {
width:100%;
height:100%;
z-index: 1;
}
.posts {
position: fixed;
width:100%;
height:100%;
z-index: 2;
}

+ 191
- 50
static/app.js View File

@ -6,13 +6,13 @@
const xmlXlinkXMLNS = 'http://www.w3.org/1999/xlink';
const textPostMaxLines = 7;
const polaroidTextMaxLines = 2;
const polaroidTextVerticalFudge = -50;
const polaroidTextVerticalFudge = -25;
const polaroidRandomizeButtonFudge = [50,100];
const textPostMaxWidth = 420;
const textPostDefaultMaxWidth = 420;
const textPostLineSpacing = 1.3;
const textPostFontSize = "22px";
const textPostFontSizePixels = 22;
const textPostMaxWidthEstimationFudgeFactor = 1;
const textPostPadding = [30,40];
const textPostPadding = [30,35];
const textPostRandomTranslate = [0,0];
const textPostRandomBezier = [10,30];
const textLineRandomTranslate = [22,5];
@ -37,7 +37,7 @@
let textPostPreviewContainer;
let imagePostIsTransparent;
let imagePreviewCanvas;
let textPostRandomSeed = Math.random();
let newPostRandomSeed = Math.random();
let textPostFontIndex = 0;
let lastMaxLines = 7;
let lastDefaultTextValue = "...";
@ -45,7 +45,8 @@
let lastDragY = 0;
let viewX = 0;
let viewY = 0;
let lastDragDistance
let wallContainer;
let postsContainer;
window.addEventListener('DOMContentLoaded', () => {
@ -56,9 +57,16 @@
let postElement;
let lastTextPostValue;
const wallContainer = createElement(document.body, "div", {
wallContainer = createElement(document.body, "div", {
class: "wall",
draggable: true,
style: {
width: `${Math.round(backgroundImageSize[0]*backgroundScale*2)}px`,
height: `${Math.round(backgroundImageSize[1]*backgroundScale*2)}px`,
}
});
postsContainer = createElement(document.body, "div", {
class: "posts pointer-events-none",
});
createTiledBackground(wallContainer);
@ -83,8 +91,8 @@
}
x = e.touches?.[0]?.clientX || e.clientX;
y = e.touches?.[0]?.clientY || e.clientY;
viewX += x-lastDragX;
viewY += y-lastDragY;
viewX -= x-lastDragX;
viewY -= y-lastDragY;
lastDragX = x;
lastDragY = y;
};
@ -97,13 +105,14 @@
function update(timestamp) {
let wallX = viewX;
let wallY = viewY;
let wallX = -viewX;
let wallY = -viewY;
while(wallX > 0) { wallX -= backgroundImageSize[0]*backgroundScale; }
while(wallY > 0) { wallY -= backgroundImageSize[1]*backgroundScale; }
wallX = wallX % Math.round(backgroundImageSize[0]*backgroundScale);
wallY = wallY % Math.round(backgroundImageSize[1]*backgroundScale);
wallContainer.style.transform = `translate(${wallX}px, ${wallY}px)`;
postsContainer.style.transform = `translate(${Math.round(-viewX)}px, ${Math.round(-viewY)}px)`;
window.requestAnimationFrame(update);
}
@ -164,16 +173,21 @@
lastDefaultTextValue = defaultTextValue;
clearElementContents(textPostPreviewContainer);
const isPolaroid = imagePreviewCanvas && !imagePostIsTransparent;
const maxWidth = isPolaroid ? Number(imagePreviewCanvas.style.width.replace("px", "")) : textPostDefaultMaxWidth;
if(maxWidth == NaN) {
throw new Error("reRenderTextPostPreview(): imagePreviewCanvas must have a defined width in pixels.")
}
const textPostSVGContainer = createElement(textPostPreviewContainer, "div", {
class: "text-post-svg-container",
class: "vertical align-center",
style: {
width: `${420}px`, // TODO make this dynamic
width: `${maxWidth}px`,
height: `${Math.round(315*(maxLines/textPostMaxLines))}px`,
}
});
validateForm();
createTextPost(textPostSVGContainer, textInput.value || defaultTextValue || "", maxLines, textPostRandomSeed);
createText(textPostSVGContainer, textInput.value || defaultTextValue || "", maxLines, newPostRandomSeed, maxWidth);
};
const createRandomizeButton = (parent, maxLines) => {
@ -186,10 +200,10 @@
class: "material-style-button text-post-randomize-button centered-flex",
onclick: () => {
textPostFontIndex ++;
textPostRandomSeed = Math.round(Math.random()*2147483647);
newPostRandomSeed = Math.round(Math.random()*2147483647);
reRenderTextPostPreview(lastMaxLines, lastDefaultTextValue);
if(polaroid) {
const randomBorder = mulberry32(textPostRandomSeed)() > 0.5 ? '1' : '2';
const randomBorder = mulberry32(newPostRandomSeed)() > 0.5 ? '1' : '2';
polaroid.style.borderImageSource = `url('static/polaroid-border-${randomBorder}.webp')`;
}
@ -220,8 +234,12 @@
const onImageSelected = (e) => {
clearElementContents(contentPreviewArea);
createImagePreview(contentPreviewArea, e.target.files[0]).then(({canvas, elementWidthPx, elementHeightPx}) => {
createImagePreview(contentPreviewArea, e.target.files[0]).then((canvas) => {
imagePreviewCanvas = canvas;
elementHeightPx = Number(canvas.style.height.replace("px", ""));
if(elementHeightPx == NaN) {
throw new Error("onImageSelected(): Invalid argument canvas: element must have a defined size in pixels.")
}
imagePostIsTransparent = canvasIsTransparent(canvas);
if(imagePostIsTransparent) {
@ -241,20 +259,20 @@
} else {
// opaque images (photos) get the polaroid border with theh text overlayed on the bottom of the border
const randomBorder = mulberry32(textPostRandomSeed)() > 0.5 ? '1' : '2';
const polaroid = createPolaroid(contentPreviewArea, canvas, elementWidthPx, elementHeightPx, `polaroid-border-${randomBorder}.webp`);
const randomBorder = mulberry32(newPostRandomSeed)() > 0.5 ? '1' : '2';
const polaroid = createPolaroid(contentPreviewArea, canvas, `polaroid-border-${randomBorder}.webp`);
textPostPreviewContainer = createElement(
createElement(polaroid, "div", {class: "position-absolute"}),
"div",
{
style: {
position: "relative",
top: `${Math.round(elementHeightPx+polaroidTextVerticalFudge*polaroidBorderThickness)}px`
}
}
createElement(polaroid, "div", {class: "position-absolute "}),
"div", { style: { position: "relative" } }
);
reRenderTextPostPreview(polaroidTextMaxLines, "");
const svg = textPostPreviewContainer.querySelector('svg');
if(svg) {
const svgWidth = svg.getClientRects()[0].width;
const widthFactor = svgWidth/textPostDefaultMaxWidth;
textPostPreviewContainer.style.top = `${Math.round(elementHeightPx+polaroidTextVerticalFudge*widthFactor*polaroidBorderThickness)}px`;
}
const randomizeButton = createRandomizeButton(polaroid);
@ -345,6 +363,8 @@
postIcon.style.display = "block";
postElement.onclick = openNewPost;
}, 500);
enterPostPositioningMode(textInput.value);
// xhr("POST", "upload-meta", {}, () => {
// imagePreviewCanvas.toBlob((jpegBlob) => {
@ -373,9 +393,110 @@
});
function enterPostPositioningMode(textContent) {
postsContainer.draggable = false;
const width = Number(imagePreviewCanvas.style.width.replace("px", ""));
const height = Number(imagePreviewCanvas.style.height.replace("px", ""));
if(width == NaN || height == NaN) {
throw new Error("enterPostPositioningMode(): imagePreviewCanvas must have a defined size in pixels.");
}
console.log(`viewY: ${viewY}, height: ${height}, window.innerHeight: ${window.innerHeight}, `);
console.log(`Math.round(viewY+(height*-0.5)+(window.innerHeight*0.5)): ${Math.round(viewY+(height*-0.5)+(window.innerHeight*0.5))}, `);
const postContainer = createPost({
X: Math.round(viewX+(width*-0.5)+(window.innerWidth*0.5)),
Y: Math.round(viewY+(height*-0.5)+(window.innerHeight*0.5)),
Width: width,
Height: height,
Angle: 0,
RandomSeed: newPostRandomSeed,
TextContent: textContent,
TransparentImage: imagePostIsTransparent,
}, true);
}
function createPost(metadata, isPreview) {
const postContainer = createElement(postsContainer, "div", {
class: "position-absolute vertical align-center",
style: { transform: `translate(${metadata.X}px, ${metadata.Y}px)` }
});
let text;
if( (!isPreview && metadata.URL == "") || (isPreview && !imagePreviewCanvas) ) {
text = createText(postContainer, metadata.TextContent, textPostMaxLines, metadata.RandomSeed);
} else {
let canvas;
if(isPreview) {
canvas = imagePreviewCanvas;
} else {
const result = window.createBPGElement(
postContainer,
`user-content/${metadata.Filename}-small.bpg`,
"image-load-blur",
`${metadata.Width}px`,
`${metadata.Height}px`,
backgroundImageThumbnailBase64
);
canvas = result.canvas;
result.promise.then(() => {
setTimeout(() => canvas.classList.add("resolve-0"), 10);
setTimeout(() => canvas.classList.add("resolve"), 510);
});
}
if(metadata.TransparentImage) {
postContainer.appendChild(canvas);
if(metadata.TextContent) {
text = createText(postContainer, metadata.TextContent, textPostMaxLines, metadata.RandomSeed, textPostDefaultMaxWidth);
}
} else {
const randomBorder = mulberry32(metadata.RandomSeed)() > 0.5 ? '1' : '2';
const polaroid = createPolaroid(postContainer, canvas, `polaroid-border-${randomBorder}.webp`);
if(metadata.TextContent) {
const textContainer = createElement(
createElement(polaroid, "div", {class: "position-absolute "}),
"div", {
style: {
position: "relative",
}
}
);
const svgContainer = createElement(textContainer, "div", {
class: "vertical align-center",
style: {
width: `${metadata.Width}px`,
height: `${Math.round(315*(polaroidTextMaxLines/textPostMaxLines))}px`
}
});
text = createText(svgContainer, metadata.TextContent, polaroidTextMaxLines, metadata.RandomSeed, metadata.Width);
const svg = svgContainer.querySelector('svg');
if(svg) {
const svgWidth = svg.getClientRects()[0].width;
const widthFactor = svgWidth/textPostDefaultMaxWidth;
textContainer.style.top = `${Math.round(metadata.Height+polaroidTextVerticalFudge*widthFactor*polaroidBorderThickness)}px`;
}
}
}
}
if(text) {
}
return postContainer;
}
function createPolaroid(parent, canvas, border) {
function createPolaroid(parent, canvas, elementWidthPx, elementHeightPx, border) {
const elementWidthPx = Number(canvas.style.width.replace("px", ""));
const elementHeightPx = Number(canvas.style.height.replace("px", ""));
if(elementWidthPx == NaN || elementHeightPx == NaN) {
throw new Error("createPolaroid(): Invalid argument canvas: element must have a defined size in pixels.");
}
const polaroidContainer = createElement(parent, "div", {
class: "polaroid-container",
@ -423,7 +544,7 @@
`${backgroundImageSize[0]*backgroundScale}px`,
`${backgroundImageSize[1]*backgroundScale}px`,
backgroundImageThumbnailBase64
).then((originalTile) => {
).promise.then((originalTile) => {
setTimeout(() => originalTile.classList.add("resolve-0"), 10);
setTimeout(() => originalTile.classList.add("resolve"), 510);
@ -498,7 +619,7 @@
document.body.removeChild(img);
resolve({canvas, elementWidthPx: canvasWidth, elementHeightPx: canvasHeight});
resolve(canvas);
}
});
});
@ -520,12 +641,12 @@
return numberOfOpaquePixels < 7;
}
function createMediaPost(parent, randomSeed) {
}
function createTextPost(parent, text, maxLines, randomSeed) {
function createText(parent, text, maxLines, randomSeed, maxWidth, textSizeFactor) {
maxWidth = maxWidth || textPostDefaultMaxWidth;
const widthFactor = maxWidth / textPostDefaultMaxWidth;
textSizeFactor = textSizeFactor || 1;
// max 32 bit int = 2,147,483,647
const prng = mulberry32(randomSeed);
const randNorm = () => (prng()-0.5)*2;
@ -547,27 +668,27 @@
distanceFromTeal = distanceFromTeal > 1 ? 1 : distanceFromTeal;
const color = hsvToRGBString(prng(), prng()*0.5+0.3, prng()*0.2+0.2 * (1-distanceFromTeal));
let textStyle = {fontFamily, fontWeight, fontSize: textPostFontSize};
let textStyle = {fontFamily, fontWeight, fontSize: `${textPostFontSizePixels*textSizeFactor}px`};
let lineHeight = predictTextSize("TestjGg", textStyle)[1];
const translateStart = textPostRandomTranslate.map(v => randNorm()*v);
const translateEnd = textPostRandomTranslate.map(v => randNorm()*v);
const bezierStart = textPostRandomBezier.map(v => randNorm()*v);
const bezierEnd = textPostRandomBezier.map(v => randNorm()*v);
const bezierStart = textPostRandomBezier.map(v => randNorm()*v*widthFactor);
const bezierEnd = textPostRandomBezier.map(v => randNorm()*v*widthFactor);
const lineCurves = [...Array(maxLines)].map((_, i) => {
const lineY = textPostPadding[1]+((i+1)*lineHeight);
const lineY = textPostPadding[1]*widthFactor+((i+1)*lineHeight);
const lineTranslateStart = textLineRandomTranslate.map(v => randNorm()*v);
const lineTranslateEnd = textLineRandomTranslate.map(v => randNorm()*v);
const lineBezierStart = textLineRandomBezier.map(v => randNorm()*v);
const lineBezierEnd = textLineRandomBezier.map(v => randNorm()*v);
const lineBezierStart = textLineRandomBezier.map(v => randNorm()*v*widthFactor);
const lineBezierEnd = textLineRandomBezier.map(v => randNorm()*v*widthFactor);
const start = [
textPostPadding[0]+translateStart[0]+lineTranslateStart[0],
lineY+translateStart[1]+lineTranslateStart[1]
lineY+(translateStart[1]+lineTranslateStart[1])*widthFactor
];
const end = [
(textPostMaxWidth-textPostPadding[0])+translateEnd[0]+lineTranslateEnd[0],
lineY+translateEnd[1]+lineTranslateEnd[1]
(maxWidth-textPostPadding[0])+translateEnd[0]+lineTranslateEnd[0],
lineY+(translateEnd[1]+lineTranslateEnd[1])*widthFactor
];
const midpoint = [(start[0]+end[0])*0.5, (start[1]+end[1])*0.5];
@ -575,12 +696,13 @@
return {
startPoint: `${start[0]} ${start[1]}`,
endPoint: `${end[0]} ${end[1]}`,
startHandle: `${midpoint[0]+bezierStart[0]+lineBezierStart[0]} ${midpoint[1]+bezierStart[1]+lineBezierStart[1]}`,
endHandle: `${midpoint[0]+bezierEnd[0]+lineBezierEnd[0]} ${midpoint[1]+bezierEnd[1]+lineBezierEnd[1]}`,
startHandle: `${midpoint[0]+bezierStart[0]+lineBezierStart[0]} ${midpoint[1]+(bezierStart[1]+lineBezierStart[1])}`,
endHandle: `${midpoint[0]+bezierEnd[0]+lineBezierEnd[0]} ${midpoint[1]+(bezierEnd[1]+lineBezierEnd[1])}`,
estimatedWidth: (end[0]-start[0]),
}
});
let widestLineWidth = 0;
let lines = text.split("\n").flatMap(line => {
const words = line.split(" ");
const linesToReturn = [];
@ -589,8 +711,12 @@
const textSize = predictTextSize([...lineBuffer, word].join(" "), textStyle);
const curveWidth = lineCurves[linesToReturn.length % lineCurves.length];
const addingCausesWrap = textSize[0]*textPostMaxWidthEstimationFudgeFactor > curveWidth.estimatedWidth;
if(!addingCausesWrap && textSize[0] > widestLineWidth) {
widestLineWidth = textSize[0];
}
//console.log(`est: ${textSize[0]}, limit: ${lineCurves[linesToReturn.length].estimatedWidth}, wrap: ${addingCausesWrap}, value: ${[...lineBuffer, word].join(" ")}`)
if(lineBuffer.length == 0 && addingCausesWrap) {
widestLineWidth = curveWidth.estimatedWidth;
linesToReturn.push([word]);
} else if(addingCausesWrap) {
linesToReturn.push(lineBuffer.join(" ")+"\n");
@ -605,6 +731,22 @@
return linesToReturn;
});
let textPostWidth = (widestLineWidth*textPostMaxWidthEstimationFudgeFactor)+textPostPadding[0]*2;
textPostWidth = textPostWidth > maxWidth ? maxWidth : textPostWidth;
const widthUtilizationFactor = textPostWidth/maxWidth;
const textPostHeight = ((lines.length > maxLines ? maxLines : lines.length) * lineHeight)+textPostPadding[1]*widthUtilizationFactor+textPostPadding[1];
const textPostVerticalPaddingClip = textPostPadding[1] - textPostPadding[1]*widthUtilizationFactor;
// if this text could be more than 30% wider without wrapping, start over with bigger font size.
if( textSizeFactor == 1 && widthUtilizationFactor < 0.7) {
textSizeFactor = 0.95/widthUtilizationFactor;
textSizeFactor = textSizeFactor > 2 ? 2 : textSizeFactor;
return createText(parent, text, maxLines, randomSeed, maxWidth, textSizeFactor)
}
// trim lines array to maxLines
if(lines.length > maxLines) {
lines = lines.slice(0, maxLines)
@ -614,14 +756,13 @@
lineHeight *= textPostLineSpacing;
const textPostHeight = ((lines.length > maxLines ? maxLines : lines.length) * lineHeight)+textPostPadding[1]*2;
const textPreview = createElementNS(parent, svgXMLNS, "svg", {
"xmlns": [xmlnsXMLNS, svgXMLNS],
"xml:space": [xmlSpaceXMLNS, 'preserve'],
"version": "1.1",
"viewBox": `0 0 ${textPostMaxWidth} ${textPostHeight}`,
"width": `${textPostMaxWidth}`,
"viewBox": `0 ${textPostVerticalPaddingClip} ${textPostWidth} ${textPostHeight}`,
"width": `${textPostWidth}`,
"height": `${textPostHeight}`,
});
@ -646,7 +787,7 @@
{
"xlink:href": [xmlXlinkXMLNS, `#text-post-preview-path-${i}`],
"href": `#text-post-preview-path-${i}`,
"style": {fontFamily, fontWeight, fontSize: textPostFontSize},
"style": {fontFamily, fontWeight, fontSize: `${textPostFontSizePixels*textSizeFactor}px`},
"fill": color
},
line,


+ 1
- 1
static/bpgdec8.js
File diff suppressed because it is too large
View File


Loading…
Cancel
Save