Showcase
15
README.md
|
|
@ -1 +1,14 @@
|
||||||
Photo-Wall
|
# Photo-Wall
|
||||||
|
|
||||||
|
This project was started to provide an easy to use and free mockup tool for photographers.
|
||||||
|
You can use this tool to show photographs in different frames either at your own home or at a clients.
|
||||||
|
This includes the possibility of showing the exact dimensions of the picture in the chosen space.
|
||||||
|
#### Demo: [jpmatz.de](https://www.jpmatz.de/demo)
|
||||||
|
#### Desktop-App: [jpmatz.de](https://www.jpmatz.de)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Technical aspekt
|
||||||
|
|
||||||
|
To accomplish this goal a custom purely JS based render engine was created.
|
||||||
|
Since the entire application lives in the front-end a static webspace like a AWS-Bucket could be used for hosting, if one wanted to.
|
||||||
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 632 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 246 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
["1.jpg", "2.jpeg", "3.jpeg", "4.jpeg", "5.jpeg"]
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
var backgroundPath = "";
|
||||||
|
var backImg = new Image();
|
||||||
|
var framePaths = [];
|
||||||
|
|
||||||
|
backImg.src = backgroundPath;
|
||||||
|
|
||||||
|
var canvas = document.getElementById("canvas");
|
||||||
|
var ctx = canvas.getContext("2d");
|
||||||
|
var canvasOffset = $("#canvas").offset();
|
||||||
|
|
||||||
|
var offsetX = canvasOffset.left;
|
||||||
|
var offsetY = canvasOffset.top;
|
||||||
|
var canvasWidth = canvas.width;
|
||||||
|
var canvasHeight = canvas.height;
|
||||||
|
|
||||||
|
var isDragging = false;
|
||||||
|
var isScaling = false;
|
||||||
|
|
||||||
|
var pictures = []; // the array with picture objects
|
||||||
|
var focusedID = 0; // id of focused object
|
||||||
|
var zoomLevel = 1;
|
||||||
|
var canMouseX;
|
||||||
|
var canMouseY;
|
||||||
|
var scale = 1;
|
||||||
|
|
||||||
|
var scaleRefPoints = [];
|
||||||
|
var pixelPerMeter = 0;
|
||||||
|
var scaleRefLenght = 1;
|
||||||
|
var showMeasurements = false;
|
||||||
|
|
||||||
|
// render "engine"
|
||||||
|
function draw() {
|
||||||
|
prepareCanvas()
|
||||||
|
// draw all picture objects
|
||||||
|
pictures.forEach(function (item) {
|
||||||
|
try {
|
||||||
|
item.calcPassp();
|
||||||
|
drawPassp(item);
|
||||||
|
drawImage(item);
|
||||||
|
drawFrame(item);
|
||||||
|
drawMeasurements(item)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
drawRuler();
|
||||||
|
|
||||||
|
//residual from preparecanvas function
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawX(x, y) {
|
||||||
|
ctx.moveTo(x - 5, y - 5);
|
||||||
|
ctx.lineTo(x + 5, y + 5);
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.moveTo(x + 5, y - 5);
|
||||||
|
ctx.lineTo(x - 5, y + 5);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareCanvas() {
|
||||||
|
// scales the image to fit on the canvas
|
||||||
|
scale = canvasWidth / backImg.width;
|
||||||
|
|
||||||
|
if (backImg.height > backImg.width) {
|
||||||
|
scale = canvasHeight / backImg.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset for background to be centered
|
||||||
|
let offsetx = (canvasWidth - backImg.width * scale) / 2;
|
||||||
|
let offsety = (canvasHeight - backImg.height * scale) / 2;
|
||||||
|
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(canMouseX, canMouseY);
|
||||||
|
ctx.scale(zoomLevel, zoomLevel);
|
||||||
|
ctx.translate(-(canMouseX), -(canMouseY));
|
||||||
|
ctx.drawImage(backImg, offsetx, offsety, backImg.width * scale, backImg.height * scale);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawPassp(item) {
|
||||||
|
// draw passpatous
|
||||||
|
let img = item.img;
|
||||||
|
ctx.fillStyle = item.passpColor;
|
||||||
|
if (item.frameRotate) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(item.x + item.passpImg.width * item.passpScale / 2, item.y + item.passpImg.height * item.passpScale / 2); // change origin
|
||||||
|
ctx.rotate(Math.PI / 2);
|
||||||
|
ctx.translate(-(item.x + item.passpImg.width * item.passpScale / 2), -(item.y + item.passpImg.height * item.passpScale / 2)); // change origin
|
||||||
|
|
||||||
|
ctx.fillRect(
|
||||||
|
item.x - (item.passpImg.height * item.passpScale - (img.height * item.scale)) / 2 + item.passpOffset,
|
||||||
|
item.y + (item.passpImg.width * item.passpScale - (img.width * item.scale)) / 2 + item.passpOffset,
|
||||||
|
(item.passpImg.width * item.passpScale) - item.passpOffset * 2,
|
||||||
|
(item.passpImg.height * item.passpScale) - item.passpOffset * 2);
|
||||||
|
|
||||||
|
ctx.rotate(-Math.PI / 2);
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctx.fillRect(
|
||||||
|
item.x - (item.passpImg.width * item.passpScale - (img.width * item.scale)) / 2 + item.passpOffset,
|
||||||
|
item.y - (item.passpImg.height * item.passpScale - (img.height * item.scale)) / 2 + item.passpOffset,
|
||||||
|
(item.passpImg.width * item.passpScale) - item.passpOffset * 2,
|
||||||
|
(item.passpImg.height * item.passpScale) - item.passpOffset * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawImage(item) {
|
||||||
|
// draw image
|
||||||
|
let img = item.img;
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(item.x + item.img.width * item.scale / 2, item.y + item.img.height * item.scale / 2); // change origin
|
||||||
|
ctx.rotate(item.imgRotate);
|
||||||
|
ctx.translate(-(item.x + item.img.width * item.scale / 2), -(item.y + item.img.height * item.scale / 2)); // change origin
|
||||||
|
|
||||||
|
ctx.drawImage(img, item.x, item.y, img.width * item.scale, img.height * item.scale);
|
||||||
|
|
||||||
|
ctx.rotate(item.imgRotate);
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawFrame(item) {
|
||||||
|
// draw frame
|
||||||
|
// if frame should be rotated
|
||||||
|
let img = item.img;
|
||||||
|
if (item.frameRotate) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(item.x + item.passpImg.width * item.passpScale / 2, item.y + item.passpImg.height * item.passpScale / 2); // change origin
|
||||||
|
ctx.rotate(Math.PI / 2);
|
||||||
|
ctx.translate(-(item.x + item.passpImg.width * item.passpScale / 2), -(item.y + item.passpImg.height * item.passpScale / 2)); // change origin
|
||||||
|
|
||||||
|
ctx.drawImage(
|
||||||
|
item.passpImg,
|
||||||
|
item.x - (item.passpImg.height * item.passpScale - (img.height * item.scale)) / 2,
|
||||||
|
item.y + (item.passpImg.width * item.passpScale - (img.width * item.scale)) / 2,
|
||||||
|
item.passpImg.width * item.passpScale,
|
||||||
|
item.passpImg.height * item.passpScale);
|
||||||
|
|
||||||
|
ctx.rotate(-Math.PI / 2);
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctx.drawImage(
|
||||||
|
item.passpImg,
|
||||||
|
item.x - (item.passpImg.width * item.passpScale - (img.width * item.scale)) / 2,
|
||||||
|
item.y - (item.passpImg.height * item.passpScale - (img.height * item.scale)) / 2,
|
||||||
|
item.passpImg.width * item.passpScale,
|
||||||
|
item.passpImg.height * item.passpScale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawMeasurements(item) {
|
||||||
|
|
||||||
|
if (showMeasurements) {
|
||||||
|
// if on side
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
if (Math.round((item.imgRotate / (Math.PI / 2)) % 2) == 0) {
|
||||||
|
ctx.fillText(Number((item.img.width * item.scale) / pixelPerMeter).toFixed(2),
|
||||||
|
item.x + (item.img.width * item.scale) / 2 - 10,
|
||||||
|
item.y + (item.img.height * item.scale) + 15);
|
||||||
|
ctx.fillText(Number((item.img.height * item.scale) / pixelPerMeter).toFixed(2),
|
||||||
|
item.x - 25,
|
||||||
|
item.y + (item.img.height * item.scale) / 2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctx.fillText(Number((item.img.height * item.scale) / pixelPerMeter).toFixed(2),
|
||||||
|
item.x + (item.img.width * item.scale) / 2,
|
||||||
|
item.y + (item.img.height * item.scale) / 2 + (item.img.width * item.scale) / 2 + 10);
|
||||||
|
ctx.fillText(Number((item.img.width * item.scale) / pixelPerMeter).toFixed(2),
|
||||||
|
item.x + (item.getWidth() - item.getHeight()) / 2 - 25,
|
||||||
|
item.y + item.getHeight() / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawRuler() {
|
||||||
|
// draw ruler
|
||||||
|
if (isScaling) {
|
||||||
|
ctx.beginPath();
|
||||||
|
drawX(scaleRefPoints[0][0], scaleRefPoints[0][1]);
|
||||||
|
ctx.moveTo(scaleRefPoints[0][0], scaleRefPoints[0][1]);
|
||||||
|
if (scaleRefPoints[1] != null) {
|
||||||
|
ctx.lineTo(scaleRefPoints[1][0], scaleRefPoints[1][1]);
|
||||||
|
drawX(scaleRefPoints[1][0], scaleRefPoints[1][1])
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(canMouseX, canMouseY);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,292 @@
|
||||||
|
|
||||||
|
// drawing loop
|
||||||
|
setInterval(draw, 10);
|
||||||
|
setInterval(showDimInHeader, 200);
|
||||||
|
function updateSrc() {
|
||||||
|
backImg.src = backgroundPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHighestId(pics) {
|
||||||
|
let highest = -1;
|
||||||
|
pics.forEach(function (item) {
|
||||||
|
if (item.id > highest) {
|
||||||
|
highest = item.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return highest;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshControlls() {
|
||||||
|
pictures.forEach(function (item) {
|
||||||
|
let id = item.id;
|
||||||
|
scalePic(id, item.scale * 100);
|
||||||
|
scalePassp(id, item.passp);
|
||||||
|
scalePasspOffset(id, item.passpOffset);
|
||||||
|
if (showMeasurements) {
|
||||||
|
document.getElementById("measurementToggle" + id).checked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPicture() {
|
||||||
|
imgPath = document.getElementById("addPicture").files[0].path;
|
||||||
|
let id = getHighestId(pictures) + 1;
|
||||||
|
let pic = new Picture(imgPath, id);
|
||||||
|
addPicControll(imgPath, id);
|
||||||
|
pictures[id] = pic;
|
||||||
|
focusThis(id);
|
||||||
|
refreshControlls();
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeBackground() {
|
||||||
|
backImg.src = document.getElementById("changeBackground").files[0].path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scalePic(id, value) {
|
||||||
|
pictures[id].scale = value / 100;
|
||||||
|
document.getElementById("scale" + id).value = value;
|
||||||
|
document.getElementById("scaleInput" + id).value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scalePassp(id, value) {
|
||||||
|
pictures[id].passp = Number(value);
|
||||||
|
document.getElementById("passp" + id).value = value;
|
||||||
|
document.getElementById("passpInput" + id).value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scalePasspOffset(id, value) {
|
||||||
|
pictures[id].passpOffset = Number(value);
|
||||||
|
document.getElementById("passpOffset" + id).value = value;
|
||||||
|
document.getElementById("passpInputOffset" + id).value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusThis(id) {
|
||||||
|
focusedID = id;
|
||||||
|
$("#controllWrapper"+id).addClass('border-light').siblings().removeClass('border-light');
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFocus(id) {
|
||||||
|
$("#controllWrapper"+id).removeClass('border-light');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDimInHeader() {
|
||||||
|
pictures.forEach(function (item) {
|
||||||
|
let id = item.id;
|
||||||
|
let std = "Picture " + id;
|
||||||
|
if (showMeasurements) {
|
||||||
|
document.getElementById("measurementToggle" + id).checked = true;
|
||||||
|
|
||||||
|
let value = "";
|
||||||
|
let width = Number(item.getWidth() / pixelPerMeter).toFixed(2);
|
||||||
|
let height = Number(item.getHeight() / pixelPerMeter).toFixed(2);
|
||||||
|
if (!pictures[id].isRotated()) {
|
||||||
|
value = ' ' + width + 'm x ' + height + 'm';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value = ' ' + height + 'm x ' + width + 'm';
|
||||||
|
}
|
||||||
|
std += '<small>' + value + '</small>';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("picTitle" + id).innerHTML = std;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// adds the controll interface for pictures
|
||||||
|
function addPicControll(src, id) {
|
||||||
|
let frameListString = '<option selected="">frameless</option>';
|
||||||
|
// add the frame choices
|
||||||
|
framePaths.forEach(function (item) {
|
||||||
|
let i = item.split(".")[0];
|
||||||
|
frameListString += `<option value=${item}>${i}</option>`;
|
||||||
|
})
|
||||||
|
|
||||||
|
let frame = `
|
||||||
|
<div class="form-group" >
|
||||||
|
<select class="custom-select" onchange="updateFrame(${id}, this.value)">
|
||||||
|
${frameListString}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
let string = `
|
||||||
|
<div class="card text-white bg-secondary mb-3 picControll" id="controllWrapper${id}" onClick="focusThis(${id});" style="max-width: 20rem;">
|
||||||
|
<div class="card-header customPictures" id="cardHeader${id}">
|
||||||
|
<button class="btn btn-link collapseHeader" type="button" data-toggle="collapse" data-target="#collapse${id}" aria-expanded="true" aria-controls="collapse${id}">
|
||||||
|
<div class="picTitle" id="picTitle${id}">Picture ${id}</div>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="close btn" data-dismiss="alert" onClick="removeElement(${id})">×</button></div>
|
||||||
|
|
||||||
|
<div id="collapse${id}" class="collapse show" aria-labelledby="cardHeader${id}" data-parent="#accordionExample">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="slidecontainer">
|
||||||
|
|
||||||
|
<img src="${src}"></img >
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
Scale:
|
||||||
|
<div>
|
||||||
|
<input type="range" min="0" max="200" value="100" class="custom-range" id="scale${id}" onchange="scalePic(${id}, this.value)" oninput="scalePic(${id}, this.value)">
|
||||||
|
<input type="number" min="0" max="200" value="100" class="sliderAddInput form-control form-control-sm" placeholder="1.0" id="scaleInput${id}" oninput="scalePic(${id}, this.value)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${frame}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
Passpatous:
|
||||||
|
<div>
|
||||||
|
<input type="range" min="0" max="100" value="20" class="custom-range" id="passp${id}" onchange="scalePassp(${id}, this.value)" oninput="scalePassp(${id}, this.value)">
|
||||||
|
<input type="number" min="0" max="100" value="20" class="sliderAddInput form-control form-control-sm" placeholder="1.0" id="passpInput${id}" oninput="scalePassp(${id}, this.value)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
Passpatous Offset:
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="range" min="0" max="50" value="3" class="custom-range" id="passpOffset${id}" onchange="scalePasspOffset(${id}, this.value)" oninput="scalePasspOffset(${id}, this.value)">
|
||||||
|
<input type="number" min="0" max="50" value="3" class="sliderAddInput form-control form-control-sm" placeholder="1.0" id="passpInputOffset${id}" oninput="scalePasspOffset(${id}, this.value)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="custom-control custom-switch cstm-btn">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" id="rotateImg${id}" onclick="rotateImg(${id})"><i class="fas fa-redo fa-xs"></i></button>
|
||||||
|
<label for="rotateImg${id}"> rotate image</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="custom-control custom-switch cstm-btn">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" id="rotateFrame${id}" onclick="rotateFrame(${id})"><i class="fas fa-redo fa-xs"></i></button>
|
||||||
|
<label for="rotateFrame${id}">rotate frame</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="measurementToggle${id}" onchange="toggleShowMeasurements()">
|
||||||
|
<label class="custom-control-label" for="measurementToggle${id}">show measurements</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
let el = document.getElementById('wrapper');
|
||||||
|
el.innerHTML += string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeElement(elementId) {
|
||||||
|
// Removes an element from the document
|
||||||
|
var element = document.getElementById("controllWrapper" + elementId);
|
||||||
|
element.parentNode.removeChild(element);
|
||||||
|
delete pictures[elementId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// called on change, sets the frame of the calling object
|
||||||
|
function updateFrame(id, value) {
|
||||||
|
let backgroundBase = path.join(__dirname, "./frames/");
|
||||||
|
|
||||||
|
pictures[id].passpImg.src = backgroundBase + value;
|
||||||
|
pictures[id].frame = backgroundBase + value;
|
||||||
|
pictures[id].refreshSrc();
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotateFrame(id) {
|
||||||
|
pictures[id].frameRotate = !pictures[id].frameRotate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotateImg(id) {
|
||||||
|
pictures[id].imgRotate += Math.PI / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setZoomLevel(x) {
|
||||||
|
zoomLevel = x * scale;
|
||||||
|
//reset zoom re-center
|
||||||
|
if (x == 1) {
|
||||||
|
canMouseX = canvasWidth / 2;
|
||||||
|
canMouseY = canvasHeight / 2;
|
||||||
|
zoomLevel = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('setScale', function (e) {
|
||||||
|
canvas.style.cursor = "crosshair";
|
||||||
|
|
||||||
|
canvas.addEventListener('mousedown', startScale);
|
||||||
|
|
||||||
|
function startScale() {
|
||||||
|
canvas.removeEventListener('mousedown', startScale);
|
||||||
|
|
||||||
|
scaleRefPoints.push([canMouseX, canMouseY]);
|
||||||
|
isScaling = true;
|
||||||
|
canvas.addEventListener('mouseup', endScale);
|
||||||
|
};
|
||||||
|
|
||||||
|
function endScale() {
|
||||||
|
canvas.removeEventListener('mouseup', endScale);
|
||||||
|
scaleRefPoints.push([canMouseX, canMouseY]);
|
||||||
|
|
||||||
|
canvas.style.cursor = "default";
|
||||||
|
prompt({
|
||||||
|
title: 'Scale',
|
||||||
|
label: 'Distance measured in meters',
|
||||||
|
value: '1',
|
||||||
|
inputAttrs: {
|
||||||
|
type: 'number'
|
||||||
|
},
|
||||||
|
type: 'input'
|
||||||
|
})
|
||||||
|
.then((r) => {
|
||||||
|
if (r === null) {
|
||||||
|
isScaling = false;
|
||||||
|
scaleRefPoints = [];
|
||||||
|
console.log('user cancelled');
|
||||||
|
} else {
|
||||||
|
//TODO check if input number and truncate to number if not
|
||||||
|
isScaling = false;
|
||||||
|
scaleRefLenght = r;
|
||||||
|
pixelPerMeter = calcPythDist(scaleRefPoints[0], scaleRefPoints[1]) / scaleRefLenght;
|
||||||
|
console.log(pixelPerMeter);
|
||||||
|
scaleRefPoints = [];
|
||||||
|
showMeasurements = true;
|
||||||
|
showDimInHeader();
|
||||||
|
console.log('result', r);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function calcPythDist(point1, point2) {
|
||||||
|
let a = point2[0] - point1[0];
|
||||||
|
let b = point2[1] - point1[1];
|
||||||
|
return Math.sqrt((a * a) + (b * b))
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizeCanvas() {
|
||||||
|
canvas.width = window.innerWidth * 0.8;
|
||||||
|
canvas.height = window.innerHeight * 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('saveCanvas', function (e, path) {
|
||||||
|
let canvas = document.getElementById("canvas");
|
||||||
|
// Get the DataUrl from the Canvas
|
||||||
|
const url = canvas.toDataURL('image/jpg', 0.8);
|
||||||
|
|
||||||
|
// remove Base64 stuff from the Image
|
||||||
|
const base64Data = url.replace(/^data:image\/png;base64,/, "");
|
||||||
|
fs.writeFile(path, base64Data, 'base64', function (err) {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleShowMeasurements() {
|
||||||
|
showMeasurements = !showMeasurements;
|
||||||
|
console.log(showMeasurements);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
|
||||||
|
$("#canvas").mousedown(function (e) { handleMouseDown(e); });
|
||||||
|
$("#canvas").mousemove(function (e) { handleMouseMove(e); });
|
||||||
|
$("#canvas").mouseup(function (e) { handleMouseUp(e); });
|
||||||
|
$("#canvas").mouseout(function (e) { handleMouseOut(e); });
|
||||||
|
|
||||||
|
function openNav() { document.getElementById("mySidenav").style.width = "350px"; }
|
||||||
|
function closeNav() { document.getElementById("mySidenav").style.width = "0"; }
|
||||||
|
function openNavR() { document.getElementById("mySidenavR").style.width = "350px"; }
|
||||||
|
function closeNavR() { document.getElementById("mySidenavR").style.width = "0"; }
|
||||||
|
|
||||||
|
|
||||||
|
window.addEventListener('resize', calcOffset);
|
||||||
|
|
||||||
|
function calcOffset(){
|
||||||
|
//resizeCanvas();
|
||||||
|
canvas = document.getElementById("canvas");
|
||||||
|
ctx = canvas.getContext("2d");
|
||||||
|
canvasOffset = $("#canvas").offset();
|
||||||
|
offsetX = canvasOffset.left;
|
||||||
|
offsetY = canvasOffset.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).keydown(function checkKey(e) {
|
||||||
|
|
||||||
|
e = e || window.event;
|
||||||
|
|
||||||
|
if (e.keyCode == '38') {
|
||||||
|
// up arrow
|
||||||
|
pictures[focusedID].y = pictures[focusedID].y - 1;
|
||||||
|
}
|
||||||
|
else if (e.keyCode == '40') {
|
||||||
|
// down arrow
|
||||||
|
pictures[focusedID].y = pictures[focusedID].y + 1;
|
||||||
|
}
|
||||||
|
else if (e.keyCode == '37') {
|
||||||
|
// left arrow
|
||||||
|
pictures[focusedID].x = pictures[focusedID].x - 1;
|
||||||
|
}
|
||||||
|
else if (e.keyCode == '39') {
|
||||||
|
// right arrow
|
||||||
|
pictures[focusedID].x = pictures[focusedID].x + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function handleMouseDown(e) {
|
||||||
|
switch (e.which) {
|
||||||
|
case 3: setZoomLevel(2); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
canMouseX = parseInt(e.clientX - offsetX);
|
||||||
|
canMouseY = parseInt(e.clientY - offsetY);
|
||||||
|
// set the drag flag
|
||||||
|
isDragging = true;
|
||||||
|
|
||||||
|
// focuse on clicked image
|
||||||
|
let clickedID = -1;
|
||||||
|
if (e.which == 1) {
|
||||||
|
pictures.forEach(function (item) {
|
||||||
|
if (item.x <= canMouseX && item.y <= canMouseY) {
|
||||||
|
if (item.img.width * item.scale + item.x >= canMouseX && item.img.height * item.scale + item.y >= canMouseY) {
|
||||||
|
clickedID = item.id;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(clickedID != -1){
|
||||||
|
focusThis(clickedID);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
removeFocus(focusedID);
|
||||||
|
}
|
||||||
|
|
||||||
|
focusedID = clickedID;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseUp(e) {
|
||||||
|
switch (e.which) {
|
||||||
|
case 3: setZoomLevel(1); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
canMouseX = parseInt(e.clientX - offsetX);
|
||||||
|
canMouseY = parseInt(e.clientY - offsetY);
|
||||||
|
// clear the drag flag
|
||||||
|
isDragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseOut(e) {
|
||||||
|
canMouseX = parseInt(e.clientX - offsetX);
|
||||||
|
canMouseY = parseInt(e.clientY - offsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseMove(e) {
|
||||||
|
canMouseX = parseInt(e.clientX - offsetX);
|
||||||
|
canMouseY = parseInt(e.clientY - offsetY);
|
||||||
|
|
||||||
|
// if the drag flag is set, clear the canvas and draw the image
|
||||||
|
if (isDragging && zoomLevel == 1 && !isScaling) {
|
||||||
|
let pic = pictures[focusedID];
|
||||||
|
if(focusedID != -1){
|
||||||
|
pictures[focusedID].x = canMouseX - pic.img.width * pic.scale / 2;
|
||||||
|
pictures[focusedID].y = canMouseY - pic.img.height * pic.scale / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMousePos(e){
|
||||||
|
canMouseX = parseInt(e.clientX - offsetX);
|
||||||
|
canMouseY = parseInt(e.clientY - offsetY);
|
||||||
|
return [canMouseX, canMouseY]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 368 KiB |
|
After Width: | Height: | Size: 453 KiB |
|
After Width: | Height: | Size: 371 KiB |
|
After Width: | Height: | Size: 347 KiB |
|
After Width: | Height: | Size: 704 KiB |
|
After Width: | Height: | Size: 767 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
[ "2_1.png", "3_1.png", "4_1.png", "5_1.png", "6_1.png"]
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
const electron = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
const {app, BrowserWindow, Menu, dialog} = electron;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto Updater
|
||||||
|
*
|
||||||
|
* Uncomment the following code below and install `electron-updater` to
|
||||||
|
* support auto updating. Code Signing with a valid certificate is required.
|
||||||
|
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating
|
||||||
|
*/
|
||||||
|
|
||||||
|
process.env.NODE_ENV = 'production'
|
||||||
|
|
||||||
|
const { autoUpdater } = require("electron-updater");
|
||||||
|
|
||||||
|
const server = 'http://update.jpmatz.de'
|
||||||
|
const feed = `${server}/update/win/latest`
|
||||||
|
let update = true;
|
||||||
|
autoUpdater.setFeedURL(feed)
|
||||||
|
|
||||||
|
const ipcMain = electron.ipcMain
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
autoUpdater.checkForUpdates()
|
||||||
|
}, 60000)
|
||||||
|
|
||||||
|
autoUpdater.logger = require("electron-log");
|
||||||
|
autoUpdater.logger.transports.file.level = "info";
|
||||||
|
|
||||||
|
autoUpdater.on('update-downloaded', () => {
|
||||||
|
if(update){
|
||||||
|
dialog.showMessageBox({
|
||||||
|
type: 'info',
|
||||||
|
title: 'Found Updates',
|
||||||
|
message: 'Found updates, do you want to update now?',
|
||||||
|
buttons: ['Sure', 'Nope']
|
||||||
|
}, (buttonIndex) => {
|
||||||
|
if (buttonIndex === 0) {
|
||||||
|
const isSilent = true;
|
||||||
|
const isForceRunAfter = true;
|
||||||
|
autoUpdater.quitAndInstall(isSilent, isForceRunAfter);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
update = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
autoUpdater.on('error', message => {
|
||||||
|
console.error('There was a problem updating the application')
|
||||||
|
console.error(message)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
let mainWindow;
|
||||||
|
// when app is ready
|
||||||
|
app.on('ready', function(){
|
||||||
|
autoUpdater.checkForUpdates()
|
||||||
|
var dns = require('dns');
|
||||||
|
|
||||||
|
dns.resolve4('query.jpmatz.de', function (err, addresses) {
|
||||||
|
if (err) app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width: 800,
|
||||||
|
height: 600,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
//preload: path.join(__dirname, 'preload.js')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// load html
|
||||||
|
mainWindow.loadFile(path.join(__dirname, 'mainWindow.html'));
|
||||||
|
mainWindow.maximize();
|
||||||
|
|
||||||
|
|
||||||
|
//mainWindow.webContents.openDevTools();
|
||||||
|
//mainWindow.show();
|
||||||
|
// close all windows
|
||||||
|
mainWindow.on('closed', function(){
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// build Menue from Template
|
||||||
|
const mainMenu = Menu.buildFromTemplate(mainMenueTemplate);
|
||||||
|
// insert menu
|
||||||
|
Menu.setApplicationMenu(mainMenu);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// create menue template
|
||||||
|
|
||||||
|
const mainMenueTemplate = [
|
||||||
|
{
|
||||||
|
label: 'File',
|
||||||
|
submenu:[{
|
||||||
|
label: 'save',
|
||||||
|
accelerator: process.platform== 'darwin' ? 'Command+S' : 'Ctrl+S',
|
||||||
|
click(){
|
||||||
|
let path = dialog.showSaveDialog({
|
||||||
|
filters: [
|
||||||
|
{ name: 'png', extensions: ['png'] }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
mainWindow.webContents.send('saveCanvas', path);
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
accelerator: process.platform== 'darwin' ? 'Command+Q' : 'Ctrl+Q',
|
||||||
|
click(){
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Scale',
|
||||||
|
submenu:[{
|
||||||
|
label: 'set scale',
|
||||||
|
click(){
|
||||||
|
mainWindow.webContents.send('setScale');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// if mac add empty object to menue
|
||||||
|
|
||||||
|
if(process.platform == 'darwin'){
|
||||||
|
mainMenueTemplate.unshift({});
|
||||||
|
}
|
||||||
|
|
||||||
|
// add dev tools if debug
|
||||||
|
|
||||||
|
if(process.env.node_env != 'production'){
|
||||||
|
mainMenueTemplate.push({
|
||||||
|
label: 'DevTools',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'toogle',
|
||||||
|
accelerator: process.platform == 'darwin' ? 'Command+I' : 'Ctrl+I',
|
||||||
|
click(item, focusedWindow){
|
||||||
|
focusedWindow.toggleDevTools();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<!-- Bootstrap Optional JavaScript -->
|
||||||
|
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
|
||||||
|
<!-- Latest compiled and minified CSS -->
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
|
||||||
|
|
||||||
|
<!-- jQuery library -->
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Popper JS -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Latest compiled JavaScript -->
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<link href="./static/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
|
||||||
|
<link href="https://bootswatch.com/_assets/css/custom.min.css" rel="stylesheet" id="bootstrap-css2">
|
||||||
|
<link href="https://bootswatch.com/4/slate/bootstrap.css" rel="stylesheet" id="bootstrap-css3">
|
||||||
|
<link rel="stylesheet" href="./static/all.css">
|
||||||
|
<link rel="stylesheet" type="text/css" media="all" href="./static/custom.css" /> <!-- reset css -->
|
||||||
|
<script src="./static/all.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="mySidenav" class="sidenav">
|
||||||
|
<a href="javascript:void(0)" class="closebtn" onclick="closeNav()">×</a>
|
||||||
|
<div id="wrapperBackground">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="form-group changeBackground">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<div class="custom-file">
|
||||||
|
|
||||||
|
<input type="file" class="custom-file-input" id="changeBackground"
|
||||||
|
oninput="changeBackground(); this.value = null;">
|
||||||
|
<label class="custom-file-label" for="changeBackground">Add background</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="mySidenavR" class="sidenavR">
|
||||||
|
<a href="javascript:void(0)" class="closebtn" onclick="closeNavR()">×</a>
|
||||||
|
<div class="accordion" id="accordionExample">
|
||||||
|
<div id="wrapper">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group addPicture">
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<div class="custom-file">
|
||||||
|
|
||||||
|
<input type="file" class="custom-file-input" id="addPicture"
|
||||||
|
oninput="addPicture(); this.value = null;">
|
||||||
|
<label class="custom-file-label" for="addPicture">Add picture</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 text-center">
|
||||||
|
<div class="row ">
|
||||||
|
<div class="col col-md-1">
|
||||||
|
<span style="font-size:30px;cursor:pointer" onclick="openNav()">☰</span>
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-10 justify-content-md-center">
|
||||||
|
<canvas id="canvas" width=1500 height=950 onload="resizeCanvas()"></canvas>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col col-md-1 justify-content-md-right">
|
||||||
|
<span style="font-size:30px;cursor:pointer" onclick="openNavR()">☰</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="./preload.js"></script>
|
||||||
|
<script src="./canvas.js"></script>
|
||||||
|
<script src="./picture.js"></script>
|
||||||
|
<script src="./events.js"></script>
|
||||||
|
<script src="./controller.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
function Picture(src, id) {
|
||||||
|
this.id = id;
|
||||||
|
this.src = src;
|
||||||
|
this.x = 110;
|
||||||
|
this.y = 110;
|
||||||
|
|
||||||
|
this.img = new Image();
|
||||||
|
this.img.src = this.src;
|
||||||
|
this.imgRotate = Math.PI * 2;
|
||||||
|
|
||||||
|
this.frame = "";
|
||||||
|
this.passp = 20;
|
||||||
|
this.passpImg = new Image();
|
||||||
|
this.passpImg.src = this.frame;
|
||||||
|
this.frameRotate = false;
|
||||||
|
this.passpScale = 1;
|
||||||
|
this.passpColor = "#FFFFFF";
|
||||||
|
this.passpOffset = 3;
|
||||||
|
|
||||||
|
this.calcPassp = function () {
|
||||||
|
|
||||||
|
// check if image was rotated even number of times
|
||||||
|
let imgRotate1 = Math.round((this.imgRotate / (Math.PI / 2)) % 2) == 0;
|
||||||
|
|
||||||
|
let overlapping = this.frameRotate ^ imgRotate1;
|
||||||
|
|
||||||
|
if (overlapping) {
|
||||||
|
// scale the frame x percent bigger than the image
|
||||||
|
this.passpScale = ((this.img.width * this.scale) * (1 + this.passp / 100)) / this.passpImg.width;
|
||||||
|
|
||||||
|
// make sure image isn't bigger than frame in any dimension
|
||||||
|
if (this.img.height * this.scale > this.passpImg.height * this.passpScale) {
|
||||||
|
this.passpScale = ((this.img.height * this.scale) * (1 + this.passp / 100)) / this.passpImg.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if frame or image is rotated switch width and height
|
||||||
|
else {
|
||||||
|
// scale the frame x percent bigger than the image
|
||||||
|
this.passpScale = ((this.img.height * this.scale) * (1 + this.passp / 100)) / this.passpImg.width;
|
||||||
|
|
||||||
|
// make sure image isn't bigger than frame in any dimension
|
||||||
|
if (this.img.width * this.scale > this.passpImg.height * this.passpScale) {
|
||||||
|
this.passpScale = ((this.img.width * this.scale) * (1 + this.passp / 100)) / this.passpImg.height;
|
||||||
|
}
|
||||||
|
if (this.img.height * this.scale > this.passpImg.width * this.passpScale) {
|
||||||
|
this.passpScale = ((this.img.height * this.scale) * (1 + this.passp / 100)) / this.passpImg.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.passpScale === Infinity || this.passpScale === NaN) {
|
||||||
|
this.passpScale = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refreshSrc = function () {
|
||||||
|
this.img = new Image();
|
||||||
|
this.img.src = this.src;
|
||||||
|
|
||||||
|
this.passpImg = new Image();
|
||||||
|
this.passpImg.src = this.frame;
|
||||||
|
}
|
||||||
|
this.scale = 1;
|
||||||
|
this.calcScale = function () {
|
||||||
|
let value = ((canvasWidth * 30) / 100) / this.img.width;
|
||||||
|
this.scale = value.toFixed(2);
|
||||||
|
document.getElementById("scale" + id).value = Number(value * 100).toFixed(2);
|
||||||
|
document.getElementById("scaleInput" + id).value = Number(value * 100).toFixed(2);
|
||||||
|
}
|
||||||
|
this.img.addEventListener('load', this.calcScale.bind(this), false);
|
||||||
|
|
||||||
|
this.getWidth = function () {
|
||||||
|
return this.img.width * this.scale;
|
||||||
|
}
|
||||||
|
this.getHeight = function () {
|
||||||
|
return this.img.height * this.scale;
|
||||||
|
}
|
||||||
|
this.isRotated = function(){
|
||||||
|
return Math.round((this.imgRotate / (Math.PI / 2)) % 2) == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
loadBackgrounds();
|
||||||
|
loadFrames();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBackground(item) {
|
||||||
|
backgroundPath = "./backgrounds/"
|
||||||
|
updateSrc();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadBackgrounds() {
|
||||||
|
let backgroundBase = "./backgrounds"
|
||||||
|
let raw = './files.json'
|
||||||
|
let paths
|
||||||
|
$.getJSON(raw, function(data) {
|
||||||
|
paths = JSON.parse(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
setBackground(paths[0]);
|
||||||
|
|
||||||
|
paths.forEach(function (item) {
|
||||||
|
let path = backgroundBase + "/" + item;
|
||||||
|
let text = "Background " + item.split(".")[0];
|
||||||
|
|
||||||
|
let string = `
|
||||||
|
<div class="card text-white bg-dark mb-3 backgroundsNav" style="max-width: 18rem;" onClick="setBackground('${item}'); $(this).addClass('border-light').siblings().removeClass('border-light');" style="max-width: 20rem;">
|
||||||
|
<div class="card-header customPictures"> ${text} </div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="slidecontainer">
|
||||||
|
<img src="${path}"></img >
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
let el = document.getElementById('wrapperBackground');
|
||||||
|
el.innerHTML += string;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFrames() {
|
||||||
|
let backgroundBase = "./frames";
|
||||||
|
let raw = backgroundBase + '/files.json';
|
||||||
|
let paths
|
||||||
|
|
||||||
|
$.getJSON(raw, function(data) {
|
||||||
|
paths = JSON.parse(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
framePaths = Array.from(paths);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -0,0 +1,272 @@
|
||||||
|
// Superhero 4.3.1
|
||||||
|
// Bootswatch
|
||||||
|
|
||||||
|
|
||||||
|
// Variables ===================================================================
|
||||||
|
|
||||||
|
$web-font-path: "https://fonts.googleapis.com/css?family=Lato:300,400,700" !default;
|
||||||
|
@import url($web-font-path);
|
||||||
|
|
||||||
|
// Navbar ======================================================================
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons =====================================================================
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
@each $color, $value in $theme-colors {
|
||||||
|
&-#{$color} {
|
||||||
|
@if $enable-gradients {
|
||||||
|
background: $value linear-gradient(180deg, mix($white, $value, 15%), $value) repeat-x;
|
||||||
|
} @else {
|
||||||
|
background-color: $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typography ==================================================================
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-header {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blockquote-footer {
|
||||||
|
color: $body-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tables ======================================================================
|
||||||
|
|
||||||
|
.table {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
|
||||||
|
.thead-dark th {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not(.btn) {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: $text-muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-dark {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-primary {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-secondary {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: $secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-light {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: $light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-dark {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: $dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-success {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: $success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-info {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: $info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-danger {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: $danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-warning {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: $warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-active {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: $table-active-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-hover {
|
||||||
|
|
||||||
|
.table-primary:hover {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: darken($primary, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-secondary:hover {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: darken($secondary, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-light:hover {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: darken($light, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-dark:hover {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: darken($dark, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-success:hover {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: darken($success, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-info:hover {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: darken($info, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-danger:hover {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: darken($danger, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-warning:hover {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: darken($warning, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-active:hover {
|
||||||
|
&, > th, > td {
|
||||||
|
background-color: $table-active-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forms =======================================================================
|
||||||
|
|
||||||
|
label,
|
||||||
|
.radio label,
|
||||||
|
.checkbox label,
|
||||||
|
.help-block {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navs ========================================================================
|
||||||
|
|
||||||
|
.nav-tabs,
|
||||||
|
.nav-pills {
|
||||||
|
.nav-link,
|
||||||
|
.nav-link:hover {
|
||||||
|
color: $body-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link.disabled {
|
||||||
|
color: $nav-link-disabled-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link:hover,
|
||||||
|
.page-link:focus {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicators ==================================================================
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
border: none;
|
||||||
|
color: $white;
|
||||||
|
|
||||||
|
a,
|
||||||
|
.alert-link {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $color, $value in $theme-colors {
|
||||||
|
&-#{$color} {
|
||||||
|
@if $enable-gradients {
|
||||||
|
background: $value linear-gradient(180deg, mix($white, $value, 15%), $value) repeat-x;
|
||||||
|
} @else {
|
||||||
|
background-color: $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
&-warning,
|
||||||
|
&-info {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress bars ===============================================================
|
||||||
|
|
||||||
|
// Containers ==================================================================
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
&-header,
|
||||||
|
&-footer {
|
||||||
|
background-color: $table-hover-bg;
|
||||||
|
|
||||||
|
.close {
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
// Superhero 4.3.1
|
||||||
|
// Bootswatch
|
||||||
|
|
||||||
|
//
|
||||||
|
// Color system
|
||||||
|
//
|
||||||
|
|
||||||
|
$white: #fff !default;
|
||||||
|
$gray-100: #EBEBEB !default;
|
||||||
|
$gray-200: #4E5D6C !default;
|
||||||
|
$gray-300: #dee2e6 !default;
|
||||||
|
$gray-400: #ced4da !default;
|
||||||
|
$gray-500: #adb5bd !default;
|
||||||
|
$gray-600: #868e96 !default;
|
||||||
|
$gray-700: #495057 !default;
|
||||||
|
$gray-800: #343a40 !default;
|
||||||
|
$gray-900: #212529 !default;
|
||||||
|
$black: #000 !default;
|
||||||
|
|
||||||
|
$blue: #DF691A !default;
|
||||||
|
$indigo: #6610f2 !default;
|
||||||
|
$purple: #6f42c1 !default;
|
||||||
|
$pink: #e83e8c !default;
|
||||||
|
$red: #d9534f !default;
|
||||||
|
$orange: #f0ad4e !default;
|
||||||
|
$yellow: #f0ad4e !default;
|
||||||
|
$green: #5cb85c !default;
|
||||||
|
$teal: #20c997 !default;
|
||||||
|
$cyan: #5bc0de !default;
|
||||||
|
|
||||||
|
$primary: $blue !default;
|
||||||
|
$secondary: $gray-200 !default;
|
||||||
|
$success: $green !default;
|
||||||
|
$info: $cyan !default;
|
||||||
|
$warning: $yellow !default;
|
||||||
|
$danger: $red !default;
|
||||||
|
$light: lighten($gray-200, 35%) !default;
|
||||||
|
$dark: $gray-200 !default;
|
||||||
|
|
||||||
|
$yiq-contrasted-threshold: 185 !default;
|
||||||
|
|
||||||
|
// Body
|
||||||
|
|
||||||
|
$body-bg: #2B3E50 !default;
|
||||||
|
$body-color: $gray-100 !default;
|
||||||
|
|
||||||
|
// Components
|
||||||
|
|
||||||
|
$border-radius: 0 !default;
|
||||||
|
$border-radius-lg: 0 !default;
|
||||||
|
$border-radius-sm: 0 !default;
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
$font-family-sans-serif: "Lato", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
|
||||||
|
|
||||||
|
$text-muted: rgba(255,255,255,.4) !default;
|
||||||
|
|
||||||
|
// Tables
|
||||||
|
|
||||||
|
$table-accent-bg: rgba($white,.05) !default;
|
||||||
|
$table-hover-bg: rgba($white,.075) !default;
|
||||||
|
|
||||||
|
$table-border-color: rgba($black,.15) !default;
|
||||||
|
|
||||||
|
$table-head-bg: $light !default;
|
||||||
|
|
||||||
|
$table-dark-bg: $light !default;
|
||||||
|
$table-dark-border-color: $gray-200 !default;
|
||||||
|
$table-dark-color: $body-bg !default;
|
||||||
|
|
||||||
|
// Forms
|
||||||
|
|
||||||
|
$input-border-color: transparent !default;
|
||||||
|
|
||||||
|
$input-group-addon-color: $body-color !default;
|
||||||
|
|
||||||
|
$custom-file-button-color: $white !default;
|
||||||
|
$custom-file-border-color: $gray-200 !default;
|
||||||
|
|
||||||
|
// Dropdowns
|
||||||
|
|
||||||
|
$dropdown-bg: $gray-200 !default;
|
||||||
|
$dropdown-divider-bg: rgba($black,.15) !default;
|
||||||
|
|
||||||
|
$dropdown-link-color: $body-color !default;
|
||||||
|
$dropdown-link-hover-color: $dropdown-link-color !default;
|
||||||
|
$dropdown-link-hover-bg: $table-hover-bg !default;
|
||||||
|
|
||||||
|
// Navs
|
||||||
|
|
||||||
|
$nav-link-disabled-color: rgba(255,255,255,.4) !default;
|
||||||
|
|
||||||
|
$nav-tabs-border-color: $gray-200 !default;
|
||||||
|
$nav-tabs-link-active-color: $body-color !default;
|
||||||
|
$nav-tabs-link-active-border-color: $gray-200 !default;
|
||||||
|
|
||||||
|
// Navbar
|
||||||
|
|
||||||
|
$navbar-padding-y: 0.25rem !default;
|
||||||
|
|
||||||
|
$navbar-dark-color: rgba($white,.75) !default;
|
||||||
|
$navbar-dark-hover-color: $white !default;
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
|
||||||
|
$pagination-color: $white !default;
|
||||||
|
$pagination-bg: $gray-200 !default;
|
||||||
|
$pagination-border-color: transparent !default;
|
||||||
|
|
||||||
|
$pagination-hover-color: $white !default;
|
||||||
|
$pagination-hover-bg: $nav-link-disabled-color !default;
|
||||||
|
$pagination-hover-border-color: $pagination-border-color !default;
|
||||||
|
|
||||||
|
$pagination-disabled-color: $nav-link-disabled-color !default;
|
||||||
|
$pagination-disabled-bg: $pagination-bg !default;
|
||||||
|
$pagination-disabled-border-color: $pagination-border-color !default;
|
||||||
|
|
||||||
|
// Modals
|
||||||
|
|
||||||
|
$modal-content-bg: $gray-200 !default;
|
||||||
|
|
||||||
|
$modal-header-border-color: rgba(0,0,0,.2) !default;
|
||||||
|
|
||||||
|
// Cards
|
||||||
|
|
||||||
|
$card-cap-bg: $table-hover-bg !default;
|
||||||
|
$card-bg: $gray-200 !default;
|
||||||
|
|
||||||
|
// Popovers
|
||||||
|
|
||||||
|
$popover-bg: $gray-200 !default;
|
||||||
|
|
||||||
|
$popover-header-bg: $table-hover-bg !default;
|
||||||
|
|
||||||
|
// List group
|
||||||
|
|
||||||
|
$list-group-bg: $gray-200 !default;
|
||||||
|
$list-group-border-color: transparent !default;
|
||||||
|
|
||||||
|
$list-group-hover-bg: $nav-link-disabled-color !default;
|
||||||
|
|
||||||
|
$list-group-disabled-color: $nav-link-disabled-color !default;
|
||||||
|
|
||||||
|
$list-group-action-color: $white !default;
|
||||||
|
$list-group-action-hover-color: $white !default;
|
||||||
|
|
||||||
|
// Breadcrumbs
|
||||||
|
|
||||||
|
$breadcrumb-divider-color: $body-color !default;
|
||||||
|
$breadcrumb-active-color: $body-color !default;
|
||||||
|
|
||||||
|
// Code
|
||||||
|
|
||||||
|
$pre-color: inherit !default;
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
body{
|
||||||
|
background-color: #272B30;
|
||||||
|
padding-top:0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas{
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.backgroundsNav img{
|
||||||
|
width:16rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changeBackground{
|
||||||
|
margin-left: 20px;
|
||||||
|
width: 18rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card{
|
||||||
|
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
.sidenav{
|
||||||
|
background-color:#111;
|
||||||
|
height:100%;
|
||||||
|
left:0;
|
||||||
|
overflow-x:hidden;
|
||||||
|
padding-top:60px;
|
||||||
|
position:fixed;
|
||||||
|
top:0;
|
||||||
|
transition:.5s;
|
||||||
|
width:0;
|
||||||
|
z-index:1;
|
||||||
|
}
|
||||||
|
.sidenavR{
|
||||||
|
background-color:#111;
|
||||||
|
height:100%;
|
||||||
|
overflow-x:hidden;
|
||||||
|
padding-top:60px;
|
||||||
|
position:fixed;
|
||||||
|
right:0;
|
||||||
|
top:0;
|
||||||
|
transition:.5s;
|
||||||
|
width:0;
|
||||||
|
z-index:1;
|
||||||
|
}
|
||||||
|
.sidenav a,
|
||||||
|
.sidenavR a{
|
||||||
|
color:#818181;
|
||||||
|
display:block;
|
||||||
|
font-size:25px;
|
||||||
|
padding:8px 8px 8px 8px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 50px;
|
||||||
|
text-decoration:none;
|
||||||
|
transition:.3s;
|
||||||
|
overflow-x:hidden;
|
||||||
|
}
|
||||||
|
.sidenav a:hover,
|
||||||
|
.offcanvas a:focus,
|
||||||
|
.sidenavR a:hover,
|
||||||
|
.offcanvas a:focus{
|
||||||
|
color:#f1f1f1;
|
||||||
|
border: transparent;
|
||||||
|
}
|
||||||
|
.sidenav .closebtn,
|
||||||
|
.sidenavR .closebtn{
|
||||||
|
font-size:36px;
|
||||||
|
margin-left:50px;
|
||||||
|
position:absolute;
|
||||||
|
right:25px;
|
||||||
|
top:0;
|
||||||
|
|
||||||
|
border: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and max-height 450px {
|
||||||
|
.sidenav,
|
||||||
|
.sidenavR{
|
||||||
|
padding-top:15px;
|
||||||
|
}
|
||||||
|
.sidenav a,
|
||||||
|
.sidenavR a{
|
||||||
|
font-size:18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidenavR img {
|
||||||
|
width: 200px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
.slidecontainer {
|
||||||
|
width: 100%; /* Width of the outside container */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The slider itself */
|
||||||
|
.slider {
|
||||||
|
-webkit-appearance: none; /* Override default CSS styles */
|
||||||
|
appearance: none;
|
||||||
|
width: 100%; /* Full-width */
|
||||||
|
height: 25px; /* Specified height */
|
||||||
|
background: #d3d3d3; /* Grey background */
|
||||||
|
outline: none; /* Remove outline */
|
||||||
|
opacity: 0.7; /* Set transparency (for mouse-over effects on hover) */
|
||||||
|
-webkit-transition: .2s; /* 0.2 seconds transition on hover */
|
||||||
|
transition: opacity .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mouse-over effects */
|
||||||
|
.slider:hover {
|
||||||
|
opacity: 1; /* Fully shown on mouse-over */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */
|
||||||
|
.slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none; /* Override default look */
|
||||||
|
appearance: none;
|
||||||
|
width: 25px; /* Set a specific slider handle width */
|
||||||
|
height: 25px; /* Slider handle height */
|
||||||
|
background: #111; /* Green background */
|
||||||
|
|
||||||
|
cursor: pointer; /* Cursor on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.addPicture{
|
||||||
|
margin: 0 20px 0 20px;
|
||||||
|
width: 20rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card .text-white .bg-secondary:focus{
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
.close:hover{
|
||||||
|
color:red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cstm-btn{
|
||||||
|
padding-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picTitle{
|
||||||
|
max-width: 180px;
|
||||||
|
float: left;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapseHeader{
|
||||||
|
padding: 0 !important;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sliderAddInput{
|
||||||
|
max-width: 25% !important;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-range{
|
||||||
|
max-width: 70% !important;
|
||||||
|
margin-right: 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |