233 lines
7.7 KiB
HTML
233 lines
7.7 KiB
HTML
{{ define "content" }}
|
|
|
|
<main role="main" class="" x-data="editor()" x-init="setup_canvas();">
|
|
<div class="row">
|
|
<div class="col">
|
|
<canvas id="c" x-bind:style="canvas_style">
|
|
</canvas>
|
|
<img :src="img_data">
|
|
</div>
|
|
<div class="col">
|
|
<div id="tools-top" x-show="!toolbar">
|
|
<button type="button" @click="add_some_text()" class="btn btn-primary">Add text</button>
|
|
<!-- <button type="button" @click="crop()" class="btn btn-primary">Crop</button> -->
|
|
<button type="button" @click="apply()" class="btn btn-primary">Apply</button>
|
|
<button type="button" @click="cancel()" class="btn btn-primary">Cancel</button>
|
|
</div>
|
|
<div id="tools-delete" x-show="toolbar == 'text'">
|
|
<button type="button" @click="delete_selected();" class="btn btn-primary" style="">delete</button>
|
|
</div>
|
|
<div id="tools-crop" x-show="toolbar == 'crop'">
|
|
<button type="button" @click="apply_crop();" class="btn btn-primary" style="">crop</button>
|
|
<button type="button" @click="cancel_crop();" class="btn btn-primary" style="">cancel</button>
|
|
</div>
|
|
<div id="tools-colour" x-show="toolbar == 'text'">
|
|
<table>
|
|
<tr>
|
|
<th>foreground</th>
|
|
<template x-for="colour in colours">
|
|
<td>
|
|
<button type="button" @click="set_colour(colour, 'fg')" class="btn btn-primary" :style="'background-color: '+colour"> </button>
|
|
</td>
|
|
</template>
|
|
<td>
|
|
<button type="button" @click="set_colour('#fff0', 'fg')" class="btn btn-primary" style="">-</button>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th>background</th>
|
|
<template x-for="colour in colours">
|
|
<td>
|
|
<button type="button" @click="set_colour(colour, 'bg')" class="btn btn-primary" :style="'background-color: '+colour"> </button>
|
|
</td>
|
|
</template>
|
|
<td>
|
|
<button type="button" @click="set_colour('#fff0', 'bg')" class="btn btn-primary" style="">-</button>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr>
|
|
<th>outline</th>
|
|
<template x-for="colour in colours">
|
|
<td>
|
|
<button type="button" @click="set_colour(colour, 'stroke')" class="btn btn-primary" :style="'background-color: '+colour"> </button>
|
|
</td>
|
|
</template>
|
|
<td>
|
|
<button type="button" @click="set_colour('#fff0', 'stroke')" class="btn btn-primary" style="">-</button>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</main>
|
|
|
|
{{ end }}
|
|
|
|
{{ define "js" }}
|
|
|
|
<script>
|
|
// for some reason, canvas does not work correctly if the object
|
|
// is managed by alpine - see https://github.com/fabricjs/fabric.js/issues/7485
|
|
var canvas = null;
|
|
|
|
function editor() {
|
|
return {
|
|
img_data: "", scaleFactor: 0.5,
|
|
toolbar: null,
|
|
crop_state: {},
|
|
colours: [ 'red', 'blue', 'green', 'white', 'yellow', 'black', 'purple'],
|
|
|
|
canvas_style: "",
|
|
// "position: absolute; width: 100%; height: 100%; left: 0px; top: 0px; touch-action: none; -webkit-user-select: none;",
|
|
setup_canvas() {
|
|
// seriously javascript? just imagine, in 2021....
|
|
var url = new URL(window.location);
|
|
var id = url.searchParams.get("id");
|
|
var self = this;
|
|
canvas = new fabric.Canvas('c');
|
|
|
|
canvas.on('selection:cleared', function(options) {
|
|
self.toolbar = null;
|
|
});
|
|
|
|
fabric.Image.fromURL('/rest/image/'+id, function(oImg) {
|
|
self.scaleFactor = scalefactor(oImg.width, oImg.height);
|
|
canvas.setDimensions({width: oImg.width, height: oImg.height});
|
|
oImg.selectable = false;
|
|
canvas.add(oImg);
|
|
canvas.setHeight(canvas.getHeight() * (self.scaleFactor));
|
|
canvas.setWidth(canvas.getWidth() * (self.scaleFactor));
|
|
canvas.setZoom(self.scaleFactor);
|
|
});
|
|
},
|
|
export_image() {
|
|
this.img_data = canvas.toDataURL({multiplier: 1/this.scaleFactor});
|
|
},
|
|
add_some_text() {
|
|
var text = new fabric.Textbox('double click to change', { left: 20, top: 20, width: 300, fontSize: 40 });
|
|
canvas.add(text);
|
|
canvas.setActiveObject(text);
|
|
this.toolbar = 'text';
|
|
var self = this;
|
|
text.on('selected', function(options) {
|
|
self.toolbar = 'text';
|
|
});
|
|
},
|
|
delete_selected() {
|
|
selected = canvas.getActiveObjects();
|
|
selected.forEach(el => {
|
|
canvas.discardActiveObject(el);
|
|
canvas.remove(el);
|
|
});
|
|
},
|
|
set_colour(colour, type) {
|
|
selected = canvas.getActiveObjects();
|
|
console.log();
|
|
selected.forEach(el => {
|
|
if (type === 'fg') {
|
|
el.set('fill', colour);
|
|
}
|
|
if (type === 'bg') {
|
|
el.set('textBackgroundColor', colour);
|
|
}
|
|
if (type === 'stroke') {
|
|
el.set('stroke', colour);
|
|
}
|
|
|
|
});
|
|
canvas.renderAll();
|
|
},
|
|
|
|
// crop mode - XXX not yet implemented
|
|
crop() {
|
|
this.toolbar = 'crop';
|
|
this.crop_state = {};
|
|
canvas.selection = false; // disable drag drop selection so we can see the crop rect
|
|
let self = this;
|
|
this.crop_state.rectangle = new fabric.Rect({
|
|
fill: 'transparent',
|
|
stroke: '#ccc',
|
|
strokeDashArray: [2, 2],
|
|
visible: false
|
|
});
|
|
console.log(this.crop_state.rectangle);
|
|
var container = document.getElementById('c').getBoundingClientRect();
|
|
canvas.add(this.crop_state.rectangle);
|
|
canvas.on("mouse:down", function(event) {
|
|
if(1) {
|
|
console.log('wow mouse is down', event.e);
|
|
self.crop_state.rectangle.width = 2;
|
|
self.crop_state.rectangle.height = 2;
|
|
self.crop_state.rectangle.left = event.e.offsetX / self.scaleFactor;
|
|
self.crop_state.rectangle.top = event.e.offsetY / self.scaleFactor;
|
|
self.crop_state.rectangle.visible = true;
|
|
self.crop_state.mouseDown = event.e;
|
|
canvas.bringToFront(self.crop_state.rectangle);
|
|
}
|
|
});
|
|
// draw the rectangle as the mouse is moved after a down click
|
|
canvas.on("mouse:move", function(event) {
|
|
if(self.crop_state.mouseDown && 1) {
|
|
|
|
self.crop_state.rectangle.width = event.e.offsetX / self.scaleFactor - self.crop_state.rectangle.left;
|
|
self.crop_state.rectangle.height = event.e.offsetY / self.scaleFactor - self.crop_state.rectangle.top;
|
|
canvas.renderAll();
|
|
|
|
}
|
|
});
|
|
// when mouse click is released, end cropping mode
|
|
canvas.on("mouse:up", function() {
|
|
console.log('MOUSE UP');
|
|
self.crop_state.mouseDown = null;
|
|
});
|
|
},
|
|
apply_crop() {
|
|
console.log(this.crop_state.rectangle.width);
|
|
},
|
|
apply() {
|
|
|
|
image_data = canvas.toDataURL({
|
|
format: 'png',
|
|
multiplier: 1.0/this.scaleFactor});
|
|
let formData = new FormData();
|
|
formData.append('image', image_data);
|
|
var url = new URL(window.location);
|
|
var id = url.searchParams.get("id");
|
|
fetch('/rest/upload/'+id+'/markup', {method: 'POST', body: formData})
|
|
.then(response => response.json()) // convert to json
|
|
.then(json => {
|
|
console.log(json);
|
|
window.location = '/uploads.html';
|
|
})
|
|
},
|
|
cancel() {
|
|
window.location = '/uploads.html';
|
|
},
|
|
}
|
|
}
|
|
|
|
function scalefactor(width, height) {
|
|
max_width = window.innerWidth * 3/5;
|
|
max_height = window.innerHeight * 5/6;
|
|
|
|
if (width <= max_width && height <= max_height) {
|
|
return 1.0;
|
|
}
|
|
|
|
factor = max_width/width;
|
|
if (height*factor <= max_height) {
|
|
return factor;
|
|
}
|
|
|
|
return 1/ (height/max_height);
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
{{ end }} |