Browse Source

supporting older browsers -- fallback for no BPG and babel build

main
forest 3 months ago
parent
commit
77a15612b1
15 changed files with 4342 additions and 50 deletions
  1. +2
    -1
      .gitignore
  2. +15
    -0
      README.md
  3. +32
    -19
      encoding.go
  4. +11
    -0
      frontend-build/babel.config.js
  5. +2828
    -0
      frontend-build/package-lock.json
  6. +24
    -0
      frontend-build/package.json
  7. +4
    -0
      index.css
  8. +1
    -1
      index.gotemplate.html
  9. +3
    -3
      main.go
  10. +1307
    -0
      static/app.babel.js
  11. +1
    -0
      static/app.babel.js.map
  12. +7
    -3
      static/app.css
  13. +106
    -22
      static/app.js
  14. BIN
      static/blue-wall-tile-1000.jpg
  15. +1
    -1
      static/bpgdec8.js

+ 2
- 1
.gitignore View File

@ -1,2 +1,3 @@
graffiti-bolt.db
user-content/*
user-content/*
node_modules

+ 15
- 0
README.md View File

@ -0,0 +1,15 @@
## graffiti-app
### development
First start up the frontend build in watch-mode in one terminal:
```
cd frontend-build
npm run-script watch
```
Second, run the go server:
```
go run .
```

+ 32
- 19
encoding.go View File

@ -196,9 +196,9 @@ func handleImageEncodingOrDieTrying(response http.ResponseWriter, file *os.File,
resizedImage2, _, _, err := createBPGOrDieTrying(createBPGOrDieTryingOptions{
Input: resizedImage,
MaxDimension: 420,
BPGQuantizerParam: 30,
BPGQuantizerParam: 37,
BPGEncoderTryhardParam: 9,
JPEGQuality: 40,
JPEGQuality: 30,
Suffix: "small",
})
if err == nil {
@ -243,18 +243,21 @@ func enqueueBPGEncode(sourceFilename, bpgFilename, quantizerParam, tryhardParam
func runBPGEncodeQueueConsumer() {
recoverFromPanic := func() {
if err := recover(); err != nil {
fmt.Print("\n\n!!!!!\n")
log.Printf("runBPGEncodeQueueConsumer panic: %+v\n", err)
debug.PrintStack()
fmt.Print("\n!!!!!\n\n")
}
}
bpgEncodeQueueConsumer := func() {
defer func() {
if err := recover(); err != nil {
fmt.Print("\n\n!!!!!\n")
log.Printf("runBPGEncodeQueueConsumer panic: %+v\n", err)
debug.PrintStack()
fmt.Print("\n!!!!!\n\n")
}
}()
defer recoverFromPanic()
for {
var consumedKey []byte = nil
err := db.View(func(tx *bolt.Tx) error {
defer recoverFromPanic()
bucket := tx.Bucket([]byte("bpg_encode_queue"))
queueDepth := bucket.Stats().KeyN
if queueDepth == 0 {
@ -291,16 +294,26 @@ func runBPGEncodeQueueConsumer() {
return nil
})
if consumedKey != nil {
cleanupErr := db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte("bpg_encode_queue"))
if debugLog {
log.Printf("BPGEncodeQueueConsumer: removing key %x from queue\n", consumedKey)
}
return bucket.Delete(consumedKey)
})
if cleanupErr != nil {
log.Printf("BPGEncodeQueueConsumer: failed to remove key %x from queue: %s\n", consumedKey, err)
myConsumedKey := make([]byte, len(consumedKey))
for i, b := range consumedKey {
myConsumedKey[i] = b
}
(func(myConsumedKey []byte) {
cleanupErr := db.Update(func(tx *bolt.Tx) error {
defer recoverFromPanic()
bucket := tx.Bucket([]byte("bpg_encode_queue"))
// if consumedKey == nil {
// return new Error()
// }
if debugLog {
log.Printf("BPGEncodeQueueConsumer: removing key %x from queue\n", myConsumedKey)
}
return bucket.Delete(myConsumedKey)
})
if cleanupErr != nil {
log.Printf("BPGEncodeQueueConsumer: failed to remove key %x from queue: %s\n", consumedKey, err)
}
})(myConsumedKey)
}
if err != nil {


+ 11
- 0
frontend-build/babel.config.js View File

@ -0,0 +1,11 @@
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'entry',
debug: true
}
]
]
};

+ 2828
- 0
frontend-build/package-lock.json
File diff suppressed because it is too large
View File


+ 24
- 0
frontend-build/package.json View File

@ -0,0 +1,24 @@
{
"name": "graffiti-frontend-build",
"version": "0.0.0",
"description": "",
"main": "n/a",
"scripts": {
"//": "--source-type script",
"build": "babel --minified ../static/app.js --out-file ../static/app.babel.js --source-maps",
"watch": "babel --watch ../static/app.js --out-file ../static/app.babel.js --source-maps",
"babel": "babel --help"
},
"browserslist": "> 0.25%, not dead",
"repository": {
"type": "git",
"url": "https://git.sequentialread.com/forest/graffiti-app.git"
},
"author": "",
"license": "GPL-3.0-or-later",
"dependencies": {
"@babel/cli": "^7.14.5",
"@babel/core": "^7.14.6",
"@babel/preset-env": "^7.14.7"
}
}

+ 4
- 0
index.css View File

@ -5,6 +5,10 @@ body {
font-family: 'Ubuntu','Roboto','Open Sans',sans-serif;
}
.display-none {
display: none;
}
.fullscreen-flying {
position: fixed;
top:0;


+ 1
- 1
index.gotemplate.html View File

@ -9,7 +9,7 @@
<script>
window.graffitiAppState = {{ .AppStateJSON }};
</script>
<script src="static/app.js"></script>
<script src="static/app.babel.js"></script>
<link rel="preload" href="static/bpgdec8.wasm" as="fetch" type="application/wasm">
<script src="static/bpgdec8.js"></script>


+ 3
- 3
main.go View File

@ -49,7 +49,7 @@ type Post struct {
Type PostType `json:"type,omitempty"`
TextContent string `json:"textContent,omitempty"`
TransparentImage bool `json:"transparentImage,omitempty"`
ThumbnailBase64 string `json:"thumbnailBase64,omitempty"`
ThumbnailJPEGBase64 string `json:"thumbnailJPEGBase64,omitempty"`
Filename string `json:"filename,omitempty"`
ContentType string `json:"contentType,omitempty"`
MillisecondsSinceUnixEpoch int64 `json:"millisecondsSinceUnixEpoch,omitempty"`
@ -431,10 +431,10 @@ func main() {
// handleImageEncodingOrDieTrying will respond with an http error if it fails
// -- so we dont handle errors, we continue *only* if there are no errors.
thumbnailBase64, err := handleImageEncodingOrDieTrying(response, dstFile, contentType, post.Filename, uploadId)
thumbnailJPEGBase64, err := handleImageEncodingOrDieTrying(response, dstFile, contentType, post.Filename, uploadId)
if err == nil {
post.ThumbnailBase64 = thumbnailBase64
post.ThumbnailJPEGBase64 = thumbnailJPEGBase64
err := createPost(&post)
if err != nil {
log.Printf("/upload 500: createPost(): %+v\n", err)


+ 1307
- 0
static/app.babel.js
File diff suppressed because it is too large
View File


+ 1
- 0
static/app.babel.js.map
File diff suppressed because it is too large
View File


+ 7
- 3
static/app.css View File

@ -80,9 +80,7 @@ textarea {
}
.display-none {
display: none;
}
.centered-flex {
display: flex;
@ -168,6 +166,12 @@ textarea {
width: 8em;
}
.jpeg-background {
background-image: url('blue-wall-tile-1000.jpg');
background-size: 2000px 2000px;
background-repeat: repeat;
}
.material-style-button {
position: absolute;
right: 1rem;


+ 106
- 22
static/app.js View File

@ -70,8 +70,16 @@
let edgePanning = false;
let wallContainer;
let postsContainer;
let supportsBPG;
window.addEventListener('DOMContentLoaded', () => {
window.addEventListener('DOMContentLoaded', detectBPGSupport);
window.onBPGSupportDetected = (supported) => {
if(supportsBPG !== undefined || supported == undefined) {
return;
}
supportsBPG = supported;
window.localStorage['supportsBPG'] = String(supportsBPG);
loader = document.getElementById("progress-container");
loader.style.display = 'none';
@ -148,13 +156,13 @@
}
} else {
if(deltaTime != 0) {
realVelocityX = (viewX-lastViewX)/deltaTime;
realVelocityY = (viewY-lastViewY)/deltaTime;
const realVelocityX = (viewX-lastViewX)/deltaTime;
const realVelocityY = (viewY-lastViewY)/deltaTime;
lastViewX = viewX;
lastViewY = viewY;
velocityX = lerp(velocityX, realVelocityX, absorbVelocityRate*deltaTime);
velocityY = lerp(velocityY, realVelocityY, absorbVelocityRate*deltaTime);
}
velocityX = lerp(velocityX, realVelocityX, absorbVelocityRate*deltaTime);
velocityY = lerp(velocityY, realVelocityY, absorbVelocityRate*deltaTime);
}
let wallX = -viewX;
@ -260,6 +268,7 @@
if(isPolaroid) {
const svg = textPostSVGContainer.querySelector('svg');
if(svg) {
const elementHeightPx = Number(imagePreviewCanvas.style.height.replace("px", ""));
const svgWidth = svg.getBoundingClientRect().width;
const widthFactor = svgWidth/textPostDefaultMaxWidth;
textPostPreviewContainer.style.top = `${Math.round(elementHeightPx+polaroidTextVerticalFudge*widthFactor*polaroidBorderThickness)}px`;
@ -314,7 +323,7 @@
imageFile = e.target.files[0];
createImagePreview(contentPreviewArea, e.target.files[0]).then((canvas) => {
imagePreviewCanvas = canvas;
elementHeightPx = Number(canvas.style.height.replace("px", ""));
const 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.")
}
@ -453,11 +462,46 @@
const centerOfViewX = Math.round(viewX+window.innerWidth*0.5);
const centerOfViewY = Math.round(viewY+window.innerHeight*0.5);
xhr("GET", `posts?x=${centerOfViewX}&y=${centerOfViewY}&cacheBust=${new Date().getTime()}`, null, onPostsLoaded)
});
};
function detectBPGSupport() {
window.onBPGSupportDetected(false);
return
if (window.localStorage['supportsBPG'] !== undefined) {
window.onBPGSupportDetected(Boolean(window.localStorage['supportsBPG']));
return
}
try {
setTimeout(() => window.onBPGSupportDetected(false), 1500);
// see static/test.png for the source of this image.
const testBPGImageBase64 = "QlBH+3AAICAAA5JCUAOSQlBECcF1gRIAAAABRAHBdYESAAABJgmusLkxB6WjYAAAASYBr1i28siTPC6H7on+4sC2h4bAl2edvY7Djuh2ZwC8S9WS+Y2qtILgTWZX/g4H4h8zG3PPOQWOs4ZscvkyuvyG3aMqtKbtKdCfCFJbNQ==";
const result = window.createBPGElement(document.body, null, "display-none", 32, 32, {base64: testBPGImageBase64, contentType:"image/bpg"});
result.promise.then(
() => {
const context = result.canvas.getContext("2d");
const upperLeft = context.getImageData(0,0,1,1).data;
const upperRight = context.getImageData(31,0,1,1).data;
const lowerRight = context.getImageData(31,31,1,1).data;
const lowerLeft = context.getImageData(0,31,1,1).data;
const upperLeftIsRed = upperLeft[0] > 200 && upperLeft[1] < 50 && upperLeft[2] < 50;
const upperRightIsGreen = upperRight[0] < 50 && upperRight[1] > 200 && upperRight[2] < 50;
const lowerRightIsWhite = lowerRight[0] > 200 && lowerRight[1] > 200 && lowerRight[2] > 200;
const lowerLeftIsBlue = lowerLeft[0] < 50 && lowerLeft[1] < 50 && lowerLeft[2] > 200;
window.onBPGSupportDetected(upperLeftIsRed && upperRightIsGreen && lowerRightIsWhite && lowerLeftIsBlue);
},
() => {
window.onBPGSupportDetected(false);
}
)
} catch (err) {
window.onBPGSupportDetected(false);
}
}
function onPostsLoaded(statusCode, postsJSON) {
if(statusCode == 200) {
posts = JSON.parse(postsJSON)
const posts = JSON.parse(postsJSON)
posts.forEach(post => createPost(post, false))
} else {
throw new Error("error attempting to load posts: " + postsJSON)
@ -617,7 +661,6 @@
}
function createPost(metadata, isPreview) {
const isTextPost = (!isPreview && metadata.URL == "") || (isPreview && !imageFile);
const anchorPoint = createElement(postsContainer, "div", {
class: "position-absolute",
@ -631,32 +674,63 @@
if( isTextPost ) {
text = createText(postContainer, metadata.textContent, textPostMaxLines, metadata.randomSeed);
} else {
let canvas;
let thumbnailJPEGBase64 = metadata.thumbnailJPEGBase64;
if(thumbnailJPEGBase64 && !thumbnailJPEGBase64.startsWith("/9j")) {
thumbnailJPEGBase64 = `${jpegHeaderBase64}${thumbnailJPEGBase64}`;
}
let imageElement;
if(isPreview) {
canvas = imagePreviewCanvas;
} else {
imageElement = imagePreviewCanvas;
} else if(supportsBPG) {
const result = window.createBPGElement(
postContainer,
`user-content/${metadata.filename}-small.bpg`,
"image-load-blur",
`${metadata.width}px`,
`${metadata.height}px`,
backgroundImageThumbnailBPGBase64
`${metadata.height}px`,
{base64: thumbnailJPEGBase64, contentType:"image/jpeg"},
);
canvas = result.canvas;
imageElement = result.canvas;
result.promise.then(() => {
setTimeout(() => canvas.classList.add("resolve-0"), 10);
setTimeout(() => canvas.classList.add("resolve"), 510);
setTimeout(() => imageElement.classList.add("resolve-0"), 10);
setTimeout(() => imageElement.classList.add("resolve"), 510);
});
} else {
imageElement = createElement(postContainer, "div", {style:{width: `${metadata.width}px`, height: `${metadata.height}px`}});
const thumbnail = createElement(imageElement, "img", {
class: "image-load-blur",
style:{
width: `${metadata.width}px`,
height: `${metadata.height}px`,
position: 'absolute',
},
src: `data:image/jpeg;base64,${thumbnailJPEGBase64}`
});
const mainImg = createElement(imageElement, "img", {
class: "image-load-blur",
style: {
width: `${metadata.width}px`,
height: `${metadata.height}px`,
position: 'absolute',
},
src: `user-content/${metadata.filename}-small.jpeg`,
onload: () => {
imageElement.removeChild(thumbnail);
thumbnail.remove();
setTimeout(() => mainImg.classList.add("resolve-0"), 10);
setTimeout(() => mainImg.classList.add("resolve"), 510);
}
});
}
if(metadata.transparentImage) {
postContainer.appendChild(canvas);
postContainer.appendChild(imageElement);
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`);
const polaroid = createPolaroid(postContainer, imageElement, `polaroid-border-${randomBorder}.webp`);
if(metadata.textContent) {
@ -734,13 +808,23 @@
}
function createTiledBackground(parent) {
if(!supportsBPG) {
createElement(parent, "div", {
class: "jpeg-background",
style: {
width: `${backgroundImageSize[0]*backgroundScale*2}px`,
height: `${backgroundImageSize[1]*backgroundScale*2}px`,
}
});
return
}
window.createBPGElement(
parent,
"static/blue-wall-tile-1000.bpg",
"position-absolute image-load-blur pointer-events-none",
`${backgroundImageSize[0]*backgroundScale}px`,
`${backgroundImageSize[1]*backgroundScale}px`,
backgroundImageThumbnailBPGBase64
{base64: backgroundImageThumbnailBPGBase64, contentType: "image/bpg"}
).promise.then((originalTile) => {
setTimeout(() => originalTile.classList.add("resolve-0"), 10);
setTimeout(() => originalTile.classList.add("resolve"), 510);
@ -853,8 +937,8 @@
if(!dragging) {
return;
}
x = e.touches?.[0]?.clientX || e.clientX;
y = e.touches?.[0]?.clientY || e.clientY;
const x = e.touches?.[0]?.clientX || e.clientX;
const y = e.touches?.[0]?.clientY || e.clientY;
onDragged(x-lastDragX, y-lastDragY, x,y);
lastDragX = x;
lastDragY = y;


BIN
static/blue-wall-tile-1000.jpg View File

Before After
Width: 1000  |  Height: 656  |  Size: 34 KiB

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


Loading…
Cancel
Save