|
|
@ -9,6 +9,7 @@ const easelElement = document.getElementById('easel'); |
|
|
|
|
|
|
|
const dZoom = 0.001; |
|
|
|
const dBrushSize = 0.5; |
|
|
|
const dOpacity = 0.001; |
|
|
|
const initialWidth = 800; |
|
|
|
const initialHeight = 600; |
|
|
|
const maxBrushSize = 500; |
|
|
@ -92,7 +93,8 @@ function makeButtonElement({icon, name, func, key}) { |
|
|
|
button.key = key; |
|
|
|
button.icon = icon; |
|
|
|
button.element = document.createElement('div'); |
|
|
|
button.element.className = 'button'; |
|
|
|
button.element.classList.add('button'); |
|
|
|
button.element.classList.add('bar-button'); |
|
|
|
|
|
|
|
button.element.addEventListener('click', func); |
|
|
|
|
|
|
@ -393,7 +395,7 @@ function makeCanvas({height=600, width=800, background=false}) { // {{{ |
|
|
|
const dx = x2 - x1; |
|
|
|
const dy = y2 - y1; |
|
|
|
const distance = Math.sqrt(dx * dx + dy * dy); |
|
|
|
const steps = Math.ceil(distance / (size / 2)); |
|
|
|
const steps = Math.ceil(distance / (size / 3)); |
|
|
|
|
|
|
|
for (let i = 0; i <= steps; i++) { |
|
|
|
const x = Math.round(x1 + (dx * i) / steps); |
|
|
@ -472,11 +474,11 @@ function makeCanvas({height=600, width=800, background=false}) { // {{{ |
|
|
|
canvas.ctx.putImageData(new ImageData(data, canvas.width, canvas.height), 0, 0); |
|
|
|
} |
|
|
|
|
|
|
|
canvas.toDataUrl = function() { |
|
|
|
const dataURL = canvas.toDataURL(); |
|
|
|
const dimensions = `${canvas.width}x${canvas.height}`; |
|
|
|
return {dataURL, dimensions}; |
|
|
|
} |
|
|
|
// canvas.toDataUrl = function() {
|
|
|
|
// const dataURL = canvas.toDataURL();
|
|
|
|
// const dimensions = `${canvas.width}x${canvas.height}`;
|
|
|
|
// return {dataURL, dimensions};
|
|
|
|
// }
|
|
|
|
|
|
|
|
canvas.fromDataUrl = function(dataURL, dimensions) { |
|
|
|
const img = new Image(); |
|
|
@ -496,6 +498,10 @@ function makeCanvas({height=600, width=800, background=false}) { // {{{ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
canvas.add = function(canvas2) { |
|
|
|
canvas.ctx.drawImage(canvas2, 0, 0); |
|
|
|
} |
|
|
|
|
|
|
|
canvas.setWidth(width); |
|
|
|
canvas.setHeight(height); |
|
|
|
|
|
|
@ -512,11 +518,74 @@ function makeLayer({height=600, width=800, background=undefined}) { // {{{ |
|
|
|
|
|
|
|
layer.controllerElement = document.createElement('div'); |
|
|
|
layer.controllerElement.className = 'layer-controller'; |
|
|
|
layer.controllerElement.innerHTML = '<i class="fa-solid fa-circle-check"></i>'; |
|
|
|
|
|
|
|
layer.controllerElement.addEventListener('click', () => { |
|
|
|
layer.previewElement = document.createElement('img'); |
|
|
|
layer.previewElement.className = 'layer-preview'; |
|
|
|
layer.previewElement.src = layer.canvas.toDataURL(); |
|
|
|
layer.previewElement.addEventListener('click', () => { |
|
|
|
layers.setActive(layer); |
|
|
|
}); |
|
|
|
layer.controllerElement.appendChild(layer.previewElement); |
|
|
|
|
|
|
|
if (!layer.background) { |
|
|
|
|
|
|
|
layer.moveButtons = document.createElement('div'); |
|
|
|
layer.moveButtons.classList.add('button'); |
|
|
|
layer.moveButtons.classList.add('layer-move-buttons'); |
|
|
|
layer.moveButtons.className = 'layer-move-buttons'; |
|
|
|
|
|
|
|
layer.moveUpButton = document.createElement('div'); |
|
|
|
layer.moveUpButton.classList.add('button'); |
|
|
|
layer.moveUpButton.classList.add('layer-move-button'); |
|
|
|
layer.moveUpButton.innerHTML = '<i class="fa-solid fa-arrow-up"></i>'; |
|
|
|
layer.moveUpButton.addEventListener('click', () => { |
|
|
|
layers.moveUp(layer); |
|
|
|
}); |
|
|
|
|
|
|
|
layer.moveButtons.appendChild(layer.moveUpButton); |
|
|
|
|
|
|
|
layer.moveDownButton = document.createElement('div'); |
|
|
|
layer.moveDownButton.classList.add('button'); |
|
|
|
layer.moveDownButton.classList.add('layer-move-button'); |
|
|
|
layer.moveDownButton.innerHTML = '<i class="fa-solid fa-arrow-down"></i>'; |
|
|
|
layer.moveDownButton.addEventListener('click', () => { |
|
|
|
layers.moveDown(layer); |
|
|
|
}); |
|
|
|
|
|
|
|
layer.moveButtons.appendChild(layer.moveDownButton); |
|
|
|
|
|
|
|
layer.controllerElement.appendChild(layer.moveButtons); |
|
|
|
|
|
|
|
layer.mergeButtons = document.createElement('div'); |
|
|
|
layer.mergeButtons.classList.add('button'); |
|
|
|
layer.mergeButtons.classList.add('layer-merge-buttons'); |
|
|
|
layer.mergeButtons.className = 'layer-merge-buttons'; |
|
|
|
|
|
|
|
layer.mergeUpButton = document.createElement('div'); |
|
|
|
layer.mergeUpButton.classList.add('button'); |
|
|
|
layer.mergeUpButton.classList.add('layer-merge-button'); |
|
|
|
layer.mergeUpButton.innerHTML = '<i class="fa-solid fa-angles-up"></i>'; |
|
|
|
layer.mergeUpButton.addEventListener('click', () => { |
|
|
|
layers.mergeUp(layer); |
|
|
|
}); |
|
|
|
|
|
|
|
layer.mergeButtons.appendChild(layer.mergeUpButton); |
|
|
|
|
|
|
|
layer.controllerElement.appendChild(layer.mergeButtons); |
|
|
|
|
|
|
|
layer.deleteButton = document.createElement('div'); |
|
|
|
layer.deleteButton.classList.add('button'); |
|
|
|
layer.deleteButton.classList.add('layer-delete-button'); |
|
|
|
layer.deleteButton.innerHTML = '<i class="fa-solid fa-trash-can"></i>'; |
|
|
|
layer.deleteButton.addEventListener('click', () => { |
|
|
|
layers.delete(layer); |
|
|
|
}); |
|
|
|
|
|
|
|
layer.controllerElement.appendChild(layer.deleteButton); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
layer.activate = function() { |
|
|
|
layer.active = true; |
|
|
@ -528,6 +597,16 @@ function makeLayer({height=600, width=800, background=undefined}) { // {{{ |
|
|
|
layer.controllerElement.classList.remove('active'); |
|
|
|
} |
|
|
|
|
|
|
|
layer.refreshPreview = function() { |
|
|
|
layer.previewElement.src = layer.canvas.toDataURL(); |
|
|
|
} |
|
|
|
|
|
|
|
layer.changeOpacity = function(opacity) { |
|
|
|
console.log({opacity}); |
|
|
|
layer.opacity = opacity; |
|
|
|
layer.canvas.style.opacity = opacity |
|
|
|
} |
|
|
|
|
|
|
|
return layer; |
|
|
|
} // }}}
|
|
|
|
|
|
|
@ -537,7 +616,8 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)' |
|
|
|
layers.width = width; |
|
|
|
|
|
|
|
layers.addButton = document.createElement('div'); |
|
|
|
layers.addButton.className = 'layer-add-button'; |
|
|
|
layers.addButton.classList.add('button'); |
|
|
|
layers.addButton.classList.add('layer-add-button'); |
|
|
|
layers.addButton.innerHTML = '<i class="fa-solid fa-plus"></i>'; |
|
|
|
layers.addButton.addEventListener('click', () => { |
|
|
|
layers.add(); |
|
|
@ -598,9 +678,16 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)' |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
layers.refreshPreviews = function() { |
|
|
|
layers.forEach(layer => { |
|
|
|
layer.refreshPreview(); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
layers.refresh = function() { |
|
|
|
layers.refreshControllers(); |
|
|
|
layers.refreshLayers(); |
|
|
|
layers.refreshPreviews(); |
|
|
|
} |
|
|
|
|
|
|
|
layers.add = function() { |
|
|
@ -609,7 +696,7 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)' |
|
|
|
width: layers.width, |
|
|
|
}); |
|
|
|
layers.push(layer); |
|
|
|
layer.activate(); |
|
|
|
layers.setActive(layer); |
|
|
|
layers.refresh(); |
|
|
|
} |
|
|
|
|
|
|
@ -630,7 +717,7 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)' |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
layers.moveUp = function(layer) { |
|
|
|
layers.moveDown = function(layer) { |
|
|
|
if (layer.background) return; |
|
|
|
if (layers.indexOf(layer) === layers.length - 1) return; |
|
|
|
const index = layers.indexOf(layer); |
|
|
@ -640,7 +727,7 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)' |
|
|
|
layers.refresh(); |
|
|
|
} |
|
|
|
|
|
|
|
layers.moveDown = function(layer) { |
|
|
|
layers.moveUp = function(layer) { |
|
|
|
if (layer.background) return; |
|
|
|
if (layers.indexOf(layer) === 1) return; |
|
|
|
const index = layers.indexOf(layer); |
|
|
@ -650,6 +737,51 @@ function makeLayers({height=600, width=800, backgroundColor='rgb(255, 255, 255)' |
|
|
|
layers.refresh(); |
|
|
|
} |
|
|
|
|
|
|
|
layers.mergeUp = function(layer) { |
|
|
|
if (layer.background) return; |
|
|
|
const index = layers.indexOf(layer); |
|
|
|
const belowLayer = layers[index - 1]; |
|
|
|
if (belowLayer.background) return; |
|
|
|
belowLayer.canvas.add(layer.canvas); |
|
|
|
layers.delete(layer); |
|
|
|
layers.setActive(belowLayer); |
|
|
|
} |
|
|
|
|
|
|
|
layers.mergeDown = function(layer) { |
|
|
|
if (layer.background) return; |
|
|
|
const index = layers.indexOf(layer); |
|
|
|
if (index === layers.length - 1) return; |
|
|
|
const aboveLayer = layers[index + 1]; |
|
|
|
aboveLayer.canvas.add(layer.canvas); |
|
|
|
layers.delete(layer); |
|
|
|
layers.setActive(aboveLayer); |
|
|
|
} |
|
|
|
|
|
|
|
layers.mergeAll = function() { |
|
|
|
const backgroundLayer = layers[0]; |
|
|
|
layers.forEach(layer => { |
|
|
|
if (layer !== backgroundLayer) { |
|
|
|
backgroundLayer.canvas.add(layer.canvas); |
|
|
|
} |
|
|
|
}); |
|
|
|
layers.deleteAll(); |
|
|
|
} |
|
|
|
|
|
|
|
layers.tempMergeAll = function() { |
|
|
|
const backgroundLayerCopy = makeLayer({ |
|
|
|
height: layers.height, |
|
|
|
width: layers.width, |
|
|
|
background: true, |
|
|
|
}); |
|
|
|
// backgroundLayerCopy.canvas.fill(backgroundColor);
|
|
|
|
layers.forEach(layer => { |
|
|
|
if (!layer.background) { |
|
|
|
backgroundLayerCopy.canvas.add(layer.canvas); |
|
|
|
} |
|
|
|
}); |
|
|
|
return backgroundLayerCopy; |
|
|
|
} |
|
|
|
|
|
|
|
layers.setActive = function(layer) { |
|
|
|
layers.forEach(layer => layer.deactivate()); |
|
|
|
layer.activate(); |
|
|
@ -838,10 +970,10 @@ commands.add({ // export {{{ |
|
|
|
key: 'e', |
|
|
|
icon: '<i class="fa-solid fa-floppy-disk"></i>', |
|
|
|
func: function exportCanvas() { |
|
|
|
const canvas = layers.getActive().canvas; |
|
|
|
const mergedCanvas = layers.tempMergeAll().canvas; |
|
|
|
const link = document.createElement('a'); |
|
|
|
link.download = 'canvas.png'; |
|
|
|
link.href = canvas.toDataURL(); |
|
|
|
link.href = mergedCanvas.toDataURL(); |
|
|
|
link.click(); |
|
|
|
} |
|
|
|
}); // }}}
|
|
|
@ -851,22 +983,24 @@ commands.add({ // import {{{ |
|
|
|
key: 'i', |
|
|
|
icon: '<i class="fa-regular fa-folder-open"></i>', |
|
|
|
func: function importCanvas() { |
|
|
|
const canvas = layers.getActive().canvas; |
|
|
|
const ctx = canvas.ctx; |
|
|
|
const input = document.createElement('input'); |
|
|
|
input.type = 'file'; |
|
|
|
input.accept = 'image/*'; |
|
|
|
input.onchange = (e) => { |
|
|
|
input.onchange = function(e) { |
|
|
|
const file = e.target.files[0]; |
|
|
|
const reader = new FileReader(); |
|
|
|
reader.onload = (e) => { |
|
|
|
reader.onload = function(e) { |
|
|
|
const dataURL = e.target.result; |
|
|
|
const img = new Image(); |
|
|
|
img.onload = () => { |
|
|
|
canvas.width = img.width; |
|
|
|
canvas.height = img.height; |
|
|
|
ctx.drawImage(img, 0, 0); |
|
|
|
img.src = dataURL; |
|
|
|
img.onload = function() { |
|
|
|
layers.add(); |
|
|
|
const canvas = layers.getActive().canvas; |
|
|
|
canvas.fromDataUrl(dataURL, `${img.width}x${img.height}`); |
|
|
|
layers.setWidth(img.width); |
|
|
|
layers.setHeight(img.height); |
|
|
|
layers.refresh(); |
|
|
|
} |
|
|
|
img.src = e.target.result; |
|
|
|
} |
|
|
|
reader.readAsDataURL(file); |
|
|
|
} |
|
|
@ -899,7 +1033,7 @@ commands.add({ // change-shape {{{ |
|
|
|
} |
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
commands.add({ |
|
|
|
commands.add({ //reset {{{
|
|
|
|
name: 'reset', |
|
|
|
key: 'r', |
|
|
|
icon: '<i class="fa-solid fa-home"></i>', |
|
|
@ -912,6 +1046,8 @@ commands.add({ |
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// TOOLS {{{
|
|
|
|
|
|
|
|
// FACTORY {{{
|
|
|
@ -1023,6 +1159,9 @@ tools.add({ // brush {{{ |
|
|
|
canvasStartX = canvasEndX; |
|
|
|
canvasStartY = canvasEndY; |
|
|
|
}, |
|
|
|
mouseUp: function(e) { |
|
|
|
layers.getActive().refreshPreview(); |
|
|
|
}, |
|
|
|
mouseLeave: function(e) { |
|
|
|
brushPreview.hide(); |
|
|
|
} |
|
|
@ -1041,6 +1180,9 @@ tools.add({ // content-move {{{ |
|
|
|
canvas.clearCanvas(); |
|
|
|
canvas.restoreCanvas(dX, dY); |
|
|
|
}, |
|
|
|
mouseUp: function(e) { |
|
|
|
layers.getActive().refreshPreview(); |
|
|
|
}, |
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
tools.add({ // move {{{
|
|
|
@ -1055,6 +1197,7 @@ tools.add({ // move {{{ |
|
|
|
easelElement.style.left = dX + 'px'; |
|
|
|
easelElement.style.top = dY + 'px'; |
|
|
|
}, |
|
|
|
cursor: 'fontawesome/png/arrows-alt-solid.png', |
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
tools.add({ // zoom {{{
|
|
|
@ -1077,6 +1220,9 @@ tools.add({ // bucket-fill {{{ |
|
|
|
mouseDown: function(e) { |
|
|
|
const canvas = layers.getActive().canvas; |
|
|
|
canvas.floodFill(canvasStartX, canvasStartY, brushColor.toRgbaArray()); |
|
|
|
}, |
|
|
|
mouseUp: function(e) { |
|
|
|
layers.getActive().refreshPreview(); |
|
|
|
} |
|
|
|
}); // }}}
|
|
|
|
|
|
|
@ -1123,6 +1269,9 @@ tools.add({ // resize {{{ |
|
|
|
layers.resize(newWidth, newHeight); |
|
|
|
startX = endX; |
|
|
|
startY = endY; |
|
|
|
}, |
|
|
|
mouseUp: function(e) { |
|
|
|
layers.refreshPreviews(); |
|
|
|
} |
|
|
|
}); // }}}
|
|
|
|
|
|
|
@ -1160,13 +1309,27 @@ tools.add({ // color-mix {{{ |
|
|
|
} |
|
|
|
}); // }}}
|
|
|
|
|
|
|
|
// tools.add({ // opacity {{{
|
|
|
|
// name: 'opacity',
|
|
|
|
// key: 'o',
|
|
|
|
// icon: '<i class="fa-solid fa-ghost"></i>',
|
|
|
|
// mouseDrag: function(e) {
|
|
|
|
// layer = layers.getActive();
|
|
|
|
// var opacity = layer.opacity += dX * dOpacity;
|
|
|
|
// if (opacity < 0) opacity = 0;
|
|
|
|
// if (opacity > 1) opacity = 1;
|
|
|
|
// layer.changeOpacity(opacity);
|
|
|
|
// startX = endX;
|
|
|
|
// },
|
|
|
|
// }); // }}}
|
|
|
|
|
|
|
|
// }}}
|
|
|
|
|
|
|
|
// PUCKS {{{
|
|
|
|
|
|
|
|
// FACTORY {{{
|
|
|
|
|
|
|
|
function makePuck({puckColor, key, editable=true}) { |
|
|
|
function makePuck({puckColor, key}) { |
|
|
|
if (!puckColor) throw new Error('No puck color provided'); |
|
|
|
|
|
|
|
|
|
|
@ -1175,16 +1338,6 @@ function makePuck({puckColor, key, editable=true}) { |
|
|
|
puck.element.style.backgroundColor = puckColor; |
|
|
|
puck.element.className = 'puck'; |
|
|
|
|
|
|
|
if (editable) { |
|
|
|
const deleteHandle = document.createElement('div'); |
|
|
|
deleteHandle.className = 'delete-handle'; |
|
|
|
deleteHandle.innerHTML = '<i class="fa-solid fa-trash-can"></i>'; |
|
|
|
puck.element.appendChild(deleteHandle); |
|
|
|
deleteHandle.addEventListener('click', () => { |
|
|
|
puck.element.remove(); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
if (key) { |
|
|
|
puck.key = key; |
|
|
|
const keyHint = document.createElement('div'); |
|
|
@ -1234,8 +1387,8 @@ function makePuck({puckColor, key, editable=true}) { |
|
|
|
function makePucks() { |
|
|
|
const pucks = []; |
|
|
|
|
|
|
|
pucks.add = function({puckColor, key, editable}) { |
|
|
|
const puck = makePuck({puckColor, key, editable}); |
|
|
|
pucks.add = function({puckColor, key}) { |
|
|
|
const puck = makePuck({puckColor, key}); |
|
|
|
pucks.push(puck); |
|
|
|
} |
|
|
|
|
|
|
@ -1249,50 +1402,68 @@ const pucks = makePucks(); |
|
|
|
|
|
|
|
pucks.add({ // black
|
|
|
|
puckColor: 'rgb(0, 0, 0)', |
|
|
|
key: '1', |
|
|
|
editable: false, |
|
|
|
}); |
|
|
|
|
|
|
|
pucks.add({ // white
|
|
|
|
puckColor: 'rgb(255, 255, 255)', |
|
|
|
key: '2', |
|
|
|
editable: false, |
|
|
|
}); |
|
|
|
|
|
|
|
pucks.add({ // Cadmium Yellow
|
|
|
|
puckColor: 'rgb(254, 236, 0)', |
|
|
|
}); |
|
|
|
pucks.add({ // Hansa Yellow
|
|
|
|
puckColor: 'rgb(252, 211, 0)', |
|
|
|
}); |
|
|
|
pucks.add({ // Cadmium Orange
|
|
|
|
puckColor: 'rgb(255, 105, 0)', |
|
|
|
}); |
|
|
|
pucks.add({ // Cadmium Red
|
|
|
|
puckColor: 'rgb(255, 39, 2)', |
|
|
|
}); |
|
|
|
pucks.add({ // Quinacridone Magenta
|
|
|
|
puckColor: 'rgb(128, 2, 46)', |
|
|
|
}); |
|
|
|
pucks.add({ // Cobalt Violet
|
|
|
|
puckColor: 'rgb(78, 0, 66)', |
|
|
|
}); |
|
|
|
pucks.add({ // Ultramarine Blue
|
|
|
|
puckColor: 'rgb(25, 0, 89)', |
|
|
|
}); |
|
|
|
pucks.add({ // Cobalt Blue
|
|
|
|
puckColor: 'rgb(0, 33, 133)', |
|
|
|
}); |
|
|
|
pucks.add({ // Phthalo Blue
|
|
|
|
puckColor: 'rgb(13, 27, 68)', |
|
|
|
}); |
|
|
|
pucks.add({ // Phthalo Green
|
|
|
|
puckColor: 'rgb(0, 60, 50)', |
|
|
|
}); |
|
|
|
pucks.add({ // Permanent Green
|
|
|
|
puckColor: 'rgb(7, 109, 22)', |
|
|
|
}); |
|
|
|
pucks.add({ // Sap Green
|
|
|
|
puckColor: 'rgb(107, 148, 4)', |
|
|
|
}); |
|
|
|
pucks.add({ // Burnt Sienna
|
|
|
|
puckColor: 'rgb(123, 72, 0)', |
|
|
|
}); |
|
|
|
pucks.add({ // red
|
|
|
|
puckColor: 'rgb(255, 0, 0)', |
|
|
|
key: '3', |
|
|
|
editable: false, |
|
|
|
}); |
|
|
|
|
|
|
|
pucks.add({ // green
|
|
|
|
puckColor: 'rgb(0, 255, 0)', |
|
|
|
}); |
|
|
|
pucks.add({ // blue
|
|
|
|
puckColor: 'rgb(0, 0, 255)', |
|
|
|
key: '5', |
|
|
|
editable: false, |
|
|
|
}); |
|
|
|
|
|
|
|
pucks.add({ // yellow
|
|
|
|
puckColor: 'rgb(255, 255, 0)', |
|
|
|
key: '6', |
|
|
|
editable: false, |
|
|
|
}); |
|
|
|
|
|
|
|
pucks.add({ // cyan
|
|
|
|
puckColor: 'rgb(0, 255, 255)', |
|
|
|
key: '7', |
|
|
|
editable: false, |
|
|
|
}); |
|
|
|
|
|
|
|
pucks.add({ // yellow
|
|
|
|
puckColor: 'rgb(255, 255, 0)', |
|
|
|
}); |
|
|
|
pucks.add({ // magenta
|
|
|
|
puckColor: 'rgb(255, 0, 255)', |
|
|
|
key: '8', |
|
|
|
editable: false, |
|
|
|
}); |
|
|
|
|
|
|
|
pucks.add({ // green
|
|
|
|
puckColor: 'rgb(0, 255, 0)', |
|
|
|
key: '4', |
|
|
|
editable: false, |
|
|
|
}); |
|
|
|
|
|
|
|
// }}}
|
|
|
|