Initial commit. \n 1. Screen capture plugin. \n 2. PNG annotation plugin
This commit is contained in:
126
DEPRECATED/jQuery_pluggable_annotate_png.html
Normal file
126
DEPRECATED/jQuery_pluggable_annotate_png.html
Normal file
@@ -0,0 +1,126 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Image Annotation Control</title>
|
||||
<style>
|
||||
.image-annotator {
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.canvas-container {
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#imageCanvas {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
.toolbar {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.toolbar button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="image-annotator">
|
||||
<input type="file" id="imageUpload" accept="image/png">
|
||||
<div class="canvas-container">
|
||||
<canvas id="imageCanvas"></canvas>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<button id="addArrow">Add Arrow</button>
|
||||
<button id="addTextbox">Add Textbox</button>
|
||||
<button id="eraseElement">Erase Element</button>
|
||||
</div>
|
||||
<button id="saveButton">Save</button>
|
||||
</div>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.5.0/fabric.min.js"></script>
|
||||
<script>
|
||||
(function($) {
|
||||
$.fn.imageAnnotator = function(options) {
|
||||
return this.each(function() {
|
||||
const $container = $(this);
|
||||
const $imageUpload = $container.find('#imageUpload');
|
||||
const $canvas = $container.find('#imageCanvas');
|
||||
const $addArrow = $container.find('#addArrow');
|
||||
const $addTextbox = $container.find('#addTextbox');
|
||||
const $eraseElement = $container.find('#eraseElement');
|
||||
const $saveButton = $container.find('#saveButton');
|
||||
|
||||
let canvas = new fabric.Canvas('imageCanvas', {
|
||||
width: 800,
|
||||
height: 600
|
||||
});
|
||||
|
||||
$imageUpload.on('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function(f) {
|
||||
fabric.Image.fromURL(f.target.result, function(img) {
|
||||
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), {
|
||||
scaleX: canvas.width / img.width,
|
||||
scaleY: canvas.height / img.height
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$addArrow.on('click', function() {
|
||||
const arrow = new fabric.Path('M 0 0 L 200 100', {
|
||||
fill: 'red',
|
||||
stroke: 'red',
|
||||
strokeWidth: 2,
|
||||
left: 100,
|
||||
top: 100
|
||||
});
|
||||
canvas.add(arrow);
|
||||
});
|
||||
|
||||
$addTextbox.on('click', function() {
|
||||
const textbox = new fabric.Textbox('Enter text', {
|
||||
left: 100,
|
||||
top: 100,
|
||||
width: 150,
|
||||
fontSize: 20
|
||||
});
|
||||
canvas.add(textbox);
|
||||
});
|
||||
|
||||
$eraseElement.on('click', function() {
|
||||
const activeObject = canvas.getActiveObject();
|
||||
if (activeObject) {
|
||||
canvas.remove(activeObject);
|
||||
}
|
||||
});
|
||||
|
||||
$saveButton.on('click', function() {
|
||||
if (confirm("Please ensure there is no sensitive information or PII in your annotations. Do you want to proceed?")) {
|
||||
const dataURL = canvas.toDataURL({
|
||||
format: 'png',
|
||||
quality: 1
|
||||
});
|
||||
console.log(dataURL); // In a real application, you would send this to your server
|
||||
alert("Image saved as Base64 string. Check the console for the output.");
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}(jQuery));
|
||||
|
||||
// Initialize the plugin
|
||||
$(document).ready(function() {
|
||||
$('.image-annotator').imageAnnotator();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
121
DEPRECATED/plugin_capture_screen_with_modals.js
Normal file
121
DEPRECATED/plugin_capture_screen_with_modals.js
Normal file
@@ -0,0 +1,121 @@
|
||||
(function($) {
|
||||
$.fn.screenshotExporter = function(options) {
|
||||
var settings = $.extend({
|
||||
filename: 'screenshot.png',
|
||||
excludeSelector: null
|
||||
}, options);
|
||||
|
||||
return this.each(function() {
|
||||
var $button = $(this);
|
||||
|
||||
$button.on('click', function() {
|
||||
takeScreenshot();
|
||||
});
|
||||
|
||||
function takeScreenshot() {
|
||||
// Hide the button temporarily
|
||||
$button.hide();
|
||||
|
||||
// Find the maximum z-index in the document
|
||||
var maxZIndex = Math.max(
|
||||
...Array.from(document.querySelectorAll('body *'))
|
||||
.map(a => parseFloat(window.getComputedStyle(a).zIndex))
|
||||
.filter(a => !isNaN(a))
|
||||
);
|
||||
|
||||
// Create a wrapper div
|
||||
var $wrapper = $('<div>')
|
||||
.css({
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: maxZIndex + 1,
|
||||
pointerEvents: 'none'
|
||||
})
|
||||
.appendTo('body');
|
||||
|
||||
// Move all fixed and absolute positioned elements into the wrapper
|
||||
$('body *').each(function() {
|
||||
var $el = $(this);
|
||||
var position = $el.css('position');
|
||||
if (position === 'fixed' || position === 'absolute') {
|
||||
var offset = $el.offset();
|
||||
$el.data('original-position', {
|
||||
parent: $el.parent(),
|
||||
nextSibling: $el.next(),
|
||||
position: position,
|
||||
top: $el.css('top'),
|
||||
left: $el.css('left'),
|
||||
zIndex: $el.css('z-index')
|
||||
});
|
||||
$el.appendTo($wrapper)
|
||||
.css({
|
||||
position: 'absolute',
|
||||
top: offset.top,
|
||||
left: offset.left,
|
||||
zIndex: position === 'fixed' ? (parseFloat($el.css('z-index')) || 0) + maxZIndex : $el.css('z-index')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Use html2canvas to capture the screenshot
|
||||
html2canvas(document.body, {
|
||||
ignoreElements: function(element) {
|
||||
// Exclude the button and any elements matching the excludeSelector
|
||||
return element === $button[0] ||
|
||||
(settings.excludeSelector && $(element).is(settings.excludeSelector));
|
||||
},
|
||||
windowWidth: document.documentElement.scrollWidth,
|
||||
windowHeight: document.documentElement.scrollHeight
|
||||
}).then(function(canvas) {
|
||||
// Convert canvas to blob
|
||||
canvas.toBlob(function(blob) {
|
||||
// Create a temporary URL for the blob
|
||||
var url = URL.createObjectURL(blob);
|
||||
|
||||
// Create a temporary anchor element
|
||||
var a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = settings.filename;
|
||||
|
||||
// Append the anchor to the body, click it, and remove it
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
|
||||
// Revoke the blob URL
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
// Restore the original positions of moved elements
|
||||
$wrapper.children().each(function() {
|
||||
var $el = $(this);
|
||||
var originalPosition = $el.data('original-position');
|
||||
if (originalPosition) {
|
||||
if (originalPosition.nextSibling.length) {
|
||||
$el.insertBefore(originalPosition.nextSibling);
|
||||
} else {
|
||||
$el.appendTo(originalPosition.parent);
|
||||
}
|
||||
$el.css({
|
||||
position: originalPosition.position,
|
||||
top: originalPosition.top,
|
||||
left: originalPosition.left,
|
||||
zIndex: originalPosition.zIndex
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the wrapper
|
||||
$wrapper.remove();
|
||||
|
||||
// Show the button again
|
||||
$button.show();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}(jQuery));
|
||||
183
DEPRECATED/plugin_display_png_v1a.js
Normal file
183
DEPRECATED/plugin_display_png_v1a.js
Normal file
@@ -0,0 +1,183 @@
|
||||
|
||||
|
||||
|
||||
var isEraseMode = false;
|
||||
var activeSelection = null;
|
||||
|
||||
function hookupPageDisplayPng() {
|
||||
const canvas = new fabric.Canvas('canvas');
|
||||
const symbolSelect = document.getElementById('symbolSelect');
|
||||
const colorPicker = document.getElementById('colorPicker');
|
||||
const symbols = ['Orange Arrow.png'];
|
||||
|
||||
// Populate symbol select
|
||||
symbols.forEach(symbol => {
|
||||
const option = document.createElement('option');
|
||||
option.value = symbol;
|
||||
option.textContent = symbol.replace('.png', '');
|
||||
symbolSelect.appendChild(option);
|
||||
});
|
||||
|
||||
// Image upload
|
||||
document.getElementById('imageUpload').addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(f) {
|
||||
fabric.Image.fromURL(f.target.result, function(img) {
|
||||
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), {
|
||||
scaleX: canvas.width / img.width,
|
||||
scaleY: canvas.height / img.height
|
||||
});
|
||||
});
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
// Add arrow
|
||||
document.getElementById('addArrow').addEventListener('click', function() {
|
||||
const arrow = new fabric.Path('M 0 0 L 200 100', {
|
||||
fill: colorPicker.value,
|
||||
stroke: colorPicker.value,
|
||||
strokeWidth: 2,
|
||||
left: 100,
|
||||
top: 100
|
||||
});
|
||||
canvas.add(arrow);
|
||||
});
|
||||
|
||||
// Add textbox
|
||||
document.getElementById('addTextbox').addEventListener('click', function() {
|
||||
const textbox = new fabric.Textbox('Type here', {
|
||||
left: 100,
|
||||
top: 100,
|
||||
width: 150,
|
||||
fontSize: 20,
|
||||
fill: colorPicker.value
|
||||
});
|
||||
canvas.add(textbox);
|
||||
});
|
||||
|
||||
// Add symbol
|
||||
symbolSelect.addEventListener('change', function() {
|
||||
if (this.value) {
|
||||
fabric.Image.fromURL(`symbols/${this.value}`, function(img) {
|
||||
img.set({
|
||||
left: 100,
|
||||
top: 100,
|
||||
scaleX: 0.5,
|
||||
scaleY: 0.5
|
||||
});
|
||||
canvas.add(img);
|
||||
});
|
||||
this.value = ''; // Reset select
|
||||
}
|
||||
});
|
||||
|
||||
// Color change for selected object
|
||||
colorPicker.addEventListener('change', function() {
|
||||
const activeObject = canvas.getActiveObject();
|
||||
if (activeObject) {
|
||||
if (activeObject.type === 'textbox') {
|
||||
activeObject.set('fill', this.value);
|
||||
} else {
|
||||
activeObject.set('fill', this.value);
|
||||
activeObject.set('stroke', this.value);
|
||||
}
|
||||
canvas.renderAll();
|
||||
}
|
||||
});
|
||||
|
||||
// Erase mode
|
||||
document.getElementById('eraseMode').addEventListener('click', function() {
|
||||
isEraseMode = !isEraseMode;
|
||||
this.textContent = isEraseMode ? 'Exit Erase Mode' : 'Erase Mode';
|
||||
|
||||
if (isEraseMode) {
|
||||
// Delete any pre-selected elements when entering erase mode
|
||||
deleteSelectedObjects(canvas);
|
||||
|
||||
// Disable selection and drawing
|
||||
canvas.selection = false;
|
||||
canvas.isDrawingMode = false;
|
||||
|
||||
// Change cursor to indicate erase mode
|
||||
canvas.defaultCursor = 'crosshair';
|
||||
canvas.hoverCursor = 'crosshair';
|
||||
} else {
|
||||
// Re-enable selection when exiting erase mode
|
||||
canvas.selection = true;
|
||||
|
||||
// Reset cursors
|
||||
canvas.defaultCursor = 'default';
|
||||
canvas.hoverCursor = 'move';
|
||||
}
|
||||
});
|
||||
|
||||
// Object selection in erase mode
|
||||
canvas.on('selection:created', function(options) {
|
||||
if (isEraseMode) {
|
||||
deleteSelectedObjects(canvas);
|
||||
}
|
||||
});
|
||||
|
||||
canvas.on('mouse:down', function(options) {
|
||||
if (isEraseMode && options.target) {
|
||||
canvas.remove(options.target);
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent dragging in erase mode
|
||||
canvas.on('object:moving', function(options) {
|
||||
if (isEraseMode) {
|
||||
options.target.setCoords();
|
||||
canvas.remove(options.target);
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
});
|
||||
|
||||
// Save image
|
||||
document.getElementById('saveImage').addEventListener('click', function() {
|
||||
if (confirm("Please ensure there is no sensitive information or PII in your annotations. Do you want to proceed with saving?")) {
|
||||
const dataURL = canvas.toDataURL({
|
||||
format: 'png',
|
||||
quality: 1
|
||||
});
|
||||
console.log("Base64 string:", dataURL);
|
||||
// Here you would typically send this dataURL to your server or database
|
||||
alert("Image saved as Base64 string. Check the console for the output.");
|
||||
}
|
||||
});
|
||||
/*
|
||||
// Object selection
|
||||
canvas.on('selection:created', function(options) {
|
||||
if (isEraseMode && options.target) {
|
||||
canvas.remove(options.target);
|
||||
} else if (options.target) {
|
||||
if (options.target.type === 'textbox') {
|
||||
colorPicker.value = options.target.fill;
|
||||
} else {
|
||||
colorPicker.value = options.target.stroke || options.target.fill;
|
||||
}
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
function deleteSelectedObjects(canvas) {
|
||||
// const canvas = new fabric.Canvas('canvas');
|
||||
const activeObject = canvas.getActiveObject();
|
||||
if (activeObject) {
|
||||
if (activeObject.type === 'activeSelection') {
|
||||
// If it's a multi-selection, remove all selected objects
|
||||
activeObject.forEachObject(function(obj) {
|
||||
canvas.remove(obj);
|
||||
});
|
||||
canvas.discardActiveObject();
|
||||
} else {
|
||||
// If it's a single object, just remove it
|
||||
canvas.remove(activeObject);
|
||||
}
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
}
|
||||
268
DEPRECATED/plugin_display_png_v2a.js
Normal file
268
DEPRECATED/plugin_display_png_v2a.js
Normal file
@@ -0,0 +1,268 @@
|
||||
|
||||
(function($) {
|
||||
$.fn.annotatorPNG = function(options) {
|
||||
var settings = $.extend({
|
||||
heightCanvas: "600px",
|
||||
widthCanvas: "800px",
|
||||
}, options);
|
||||
|
||||
var flagToolbox = "annotator-png-toolbox";
|
||||
var flagContainer = "annotator-png-container";
|
||||
var flagContainerAnnotation = "annotator-png-container-annotation";
|
||||
var flagUploadImage = "annotator-png-upload-image";
|
||||
var flagAddArrow = "annotator-png-add-arrow";
|
||||
var flagAddTextbox = "annotator-png-add-textbox";
|
||||
var flagAddSymbol = "annotator-png-add-symbol";
|
||||
var flagColourPicker = "annotator-png-colour-picker";
|
||||
var flagEraseMode = "annotator-png-erase-mode";
|
||||
var flagSaveImage = "annotator-png-save-image";
|
||||
var flagCanvasAnnotation = "annotator-png-canvas-annotation";
|
||||
var keyFabric = "fabric";
|
||||
var keyIsEraseMode = "isEraseMode";
|
||||
|
||||
function getCanvas() {
|
||||
let $annotatorPNG = $(document).find("." + flagAnnotatorPNG);
|
||||
let $canvasAnnotation = $annotatorPNG.find("canvas." + flagCanvasAnnotation);
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
console.log("canvas: ", canvas);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function deleteSelectedObjects() {
|
||||
let canvas = getCanvas();
|
||||
const activeObject = canvas.getActiveObject();
|
||||
if (activeObject) {
|
||||
console.log("active object type:", activeObject.type);
|
||||
if (activeObject.type === 'activeSelection') {
|
||||
// If it's a multi-selection, remove all selected objects
|
||||
activeObject.forEachObject(function(obj) {
|
||||
canvas.remove(obj);
|
||||
});
|
||||
canvas.discardActiveObject();
|
||||
} else {
|
||||
// If it's a single object, just remove it
|
||||
canvas.remove(activeObject);
|
||||
}
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
}
|
||||
|
||||
return this.each(function() {
|
||||
var $annotatorPNG = $(this);
|
||||
$annotatorPNG.data(keyIsEraseMode, false);
|
||||
|
||||
const symbols = ['Orange Arrow.png'];
|
||||
|
||||
// Make elements
|
||||
var $toolbox = $('<div class="' + flagToolbox + ' ' + flagContainer + '"></div>');
|
||||
|
||||
var $inputUploadImage = $('<input type="file" class="' + flagUploadImage + '" accept="image/*">');
|
||||
$toolbox.append($inputUploadImage);
|
||||
|
||||
var $buttonAddArrow = $('<button class="' + flagAddArrow + '">Add Arrow</button>');
|
||||
$toolbox.append($buttonAddArrow);
|
||||
|
||||
var $buttonAddTextbox = $('<button class="' + flagAddTextbox + '">Add Textbox</button>');
|
||||
$toolbox.append($buttonAddTextbox);
|
||||
|
||||
var $selectAddSymbol = $('<select class="' + flagAddSymbol + '"></select>');
|
||||
$selectAddSymbol.append($("<option>", {
|
||||
value: "",
|
||||
text: "Select Symbol",
|
||||
}));
|
||||
symbols.forEach(symbol => {
|
||||
$selectAddSymbol.append($("<option>", {
|
||||
value: symbol,
|
||||
text: symbol.replace('.png', ''),
|
||||
}));
|
||||
});
|
||||
$toolbox.append($selectAddSymbol);
|
||||
|
||||
var $inputColourPicker = $('<input type="color" class="' + flagColourPicker + '">');
|
||||
$toolbox.append($inputColourPicker);
|
||||
|
||||
var $buttonEraseMode = $('<button class="' + flagEraseMode + '">Erase Mode</button>');
|
||||
$toolbox.append($buttonEraseMode);
|
||||
|
||||
var $buttonSaveImage = $('<button class="' + flagSaveImage + '">Save Image</button>');
|
||||
$toolbox.append($buttonSaveImage);
|
||||
$annotatorPNG.append($toolbox);
|
||||
|
||||
var $containerAnnotation = $('<div class="' + flagContainerAnnotation + ' ' + flagContainer + '"></div>');
|
||||
var $canvasAnnotation = $('<canvas class="' + flagCanvasAnnotation + '" height="' + settings.heightCanvas + '" width="' + settings.widthCanvas + '"></canvas>');
|
||||
let canvas = new fabric.Canvas($canvasAnnotation[0]);
|
||||
canvas.selection = true;
|
||||
$canvasAnnotation.data(keyFabric, canvas);
|
||||
$containerAnnotation.append($canvasAnnotation);
|
||||
$annotatorPNG.append($containerAnnotation);
|
||||
|
||||
$canvasAnnotation.on('object:selected', function(e) {
|
||||
console.log('Object selected:', e.target);
|
||||
});
|
||||
|
||||
$canvasAnnotation.on('selection:cleared', function() {
|
||||
console.log('Selection cleared');
|
||||
});
|
||||
|
||||
// Add triggers
|
||||
$inputUploadImage.on("change", function(event) {
|
||||
console.log("File uploaded:", event.target.files[0]);
|
||||
const file = event.target.files[0];
|
||||
const reader = new FileReader();
|
||||
let canvas = getCanvas();
|
||||
reader.onload = function(eventReader) {
|
||||
fabric.Image.fromURL(eventReader.target.result, function(image) {
|
||||
canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas), {
|
||||
scaleX: canvas.width / image.width,
|
||||
scaleY: canvas.height / image.height,
|
||||
});
|
||||
});
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$buttonAddArrow.on("click", function() {
|
||||
console.log("Add Arrow clicked");
|
||||
let canvas = getCanvas();
|
||||
const arrow = new fabric.Path('M 0 0 L 200 100', {
|
||||
fill: $inputColourPicker.val(),
|
||||
stroke: $inputColourPicker.val(),
|
||||
strokeWidth: 2,
|
||||
left: 100,
|
||||
top: 100,
|
||||
selectable: true,
|
||||
});
|
||||
canvas.add(arrow);
|
||||
canvas.renderAll();
|
||||
});
|
||||
|
||||
$buttonAddTextbox.on("click", function() {
|
||||
console.log("Add Textbox clicked");
|
||||
let canvas = getCanvas();
|
||||
const textbox = new fabric.Textbox('Type here', {
|
||||
left: 100,
|
||||
top: 100,
|
||||
width: 150,
|
||||
fontSize: 20,
|
||||
fill: $inputColourPicker.val(),
|
||||
selectable: true,
|
||||
});
|
||||
canvas.add(textbox);
|
||||
canvas.renderAll();
|
||||
});
|
||||
|
||||
$selectAddSymbol.on("change", function() {
|
||||
console.log("Add Symbol changed:", this.value);
|
||||
if (this.value) {
|
||||
let canvas = getCanvas();
|
||||
fabric.Image.fromURL(`symbols/${this.value}`, function(image) {
|
||||
image.set({
|
||||
left: 100,
|
||||
top: 100,
|
||||
scaleX: 0.5,
|
||||
scaleY: 0.5,
|
||||
});
|
||||
canvas.add(image);
|
||||
});
|
||||
this.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
$inputColourPicker.on("change", function() {
|
||||
console.log("Colour Picker changed:", this.value);
|
||||
let canvas = getCanvas();
|
||||
const activeObject = canvas.getActiveObject();
|
||||
if (activeObject) {
|
||||
if (activeObject.type === 'textbox') {
|
||||
activeObject.set('fill', this.value);
|
||||
} else {
|
||||
activeObject.set('fill', this.value);
|
||||
activeObject.set('stroke', this.value);
|
||||
}
|
||||
canvas.renderAll();
|
||||
}
|
||||
});
|
||||
|
||||
$buttonEraseMode.on("click", function() {
|
||||
console.log("Erase Mode clicked");
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
isEraseMode = !isEraseMode;
|
||||
$annotatorPNG.data(keyIsEraseMode, isEraseMode);
|
||||
console.log("Erase Mode:", isEraseMode);
|
||||
this.textContent = isEraseMode ? 'Exit Erase Mode' : 'Erase Mode';
|
||||
|
||||
if (isEraseMode) {
|
||||
// Delete any pre-selected elements when entering erase mode
|
||||
deleteSelectedObjects();
|
||||
|
||||
// Disable selection and drawing
|
||||
canvas.selection = false;
|
||||
canvas.isDrawingMode = false;
|
||||
|
||||
// Change cursor to indicate erase mode
|
||||
canvas.defaultCursor = 'crosshair';
|
||||
canvas.hoverCursor = 'crosshair';
|
||||
} else {
|
||||
// Re-enable selection when exiting erase mode
|
||||
canvas.selection = true;
|
||||
|
||||
// Reset cursors
|
||||
canvas.defaultCursor = 'default';
|
||||
canvas.hoverCursor = 'move';
|
||||
}
|
||||
});
|
||||
|
||||
// Object selection in erase mode
|
||||
$canvasAnnotation.on('selection:created', function() {
|
||||
console.log("Selection created");
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
if (isEraseMode) {
|
||||
deleteSelectedObjects();
|
||||
}
|
||||
});
|
||||
|
||||
// Object click in erase mode
|
||||
$canvasAnnotation.on('mouse:down', function(event) {
|
||||
console.log("Canvas mouse down:", event);
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
if (isEraseMode && event.target) {
|
||||
let canvas = getCanvas();
|
||||
canvas.remove(event.target);
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent dragging in erase mode
|
||||
$canvasAnnotation.on('object:moving', function(event) {
|
||||
console.log("Canvas object moving:", event);
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
if (isEraseMode) {
|
||||
let canvas = getCanvas();
|
||||
event.target.setCoords();
|
||||
canvas.remove(event.target);
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
});
|
||||
|
||||
$buttonSaveImage.on("click", function() {
|
||||
let canvas = getCanvas();
|
||||
if (confirm("Please ensure there is no sensitive information or PII in your annotations. Do you want to proceed with saving?")) {
|
||||
const dataURL = canvas.toDataURL({
|
||||
format: 'png',
|
||||
quality: 1
|
||||
});
|
||||
|
||||
// Output
|
||||
console.log("Base64 string:", dataURL);
|
||||
|
||||
var a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = dataURL;
|
||||
a.download = settings.fileName;
|
||||
|
||||
alert("Image saved as Base64 string. Check the console and Downloads folder for the output.");
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
}(jQuery));
|
||||
72
DEPRECATED/plugin_tooltip.js
Normal file
72
DEPRECATED/plugin_tooltip.js
Normal file
@@ -0,0 +1,72 @@
|
||||
(function($) {
|
||||
$.fn.simpleTooltip = function(options) {
|
||||
// Default settings
|
||||
var settings = $.extend({
|
||||
color: '#000000',
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderColor: '#CCCCCC',
|
||||
position: 'top'
|
||||
}, options);
|
||||
|
||||
return this.each(function() {
|
||||
var $element = $(this);
|
||||
var tooltipText = $element.attr('data-tooltip');
|
||||
|
||||
// Create tooltip element
|
||||
var $tooltip = $('<div class="simple-tooltip">' + tooltipText + '</div>');
|
||||
|
||||
// Apply styles to tooltip
|
||||
$tooltip.css({
|
||||
'position': 'absolute',
|
||||
'display': 'none',
|
||||
'padding': '5px 10px',
|
||||
'border': '1px solid ' + settings.borderColor,
|
||||
'border-radius': '4px',
|
||||
'background-color': settings.backgroundColor,
|
||||
'color': settings.color,
|
||||
'font-size': '12px',
|
||||
'z-index': 1000
|
||||
});
|
||||
|
||||
// Add tooltip to body
|
||||
$('body').append($tooltip);
|
||||
|
||||
// Show tooltip on hover
|
||||
$element.on('mouseenter', function() {
|
||||
var elementOffset = $element.offset();
|
||||
var elementWidth = $element.outerWidth();
|
||||
var elementHeight = $element.outerHeight();
|
||||
var tooltipWidth = $tooltip.outerWidth();
|
||||
var tooltipHeight = $tooltip.outerHeight();
|
||||
|
||||
var left, top;
|
||||
|
||||
switch(settings.position) {
|
||||
case 'top':
|
||||
left = elementOffset.left + (elementWidth / 2) - (tooltipWidth / 2);
|
||||
top = elementOffset.top - tooltipHeight - 5;
|
||||
break;
|
||||
case 'bottom':
|
||||
left = elementOffset.left + (elementWidth / 2) - (tooltipWidth / 2);
|
||||
top = elementOffset.top + elementHeight + 5;
|
||||
break;
|
||||
case 'left':
|
||||
left = elementOffset.left - tooltipWidth - 5;
|
||||
top = elementOffset.top + (elementHeight / 2) - (tooltipHeight / 2);
|
||||
break;
|
||||
case 'right':
|
||||
left = elementOffset.left + elementWidth + 5;
|
||||
top = elementOffset.top + (elementHeight / 2) - (tooltipHeight / 2);
|
||||
break;
|
||||
}
|
||||
|
||||
$tooltip.css({left: left, top: top}).fadeIn(200);
|
||||
});
|
||||
|
||||
// Hide tooltip on mouse leave
|
||||
$element.on('mouseleave', function() {
|
||||
$tooltip.fadeOut(200);
|
||||
});
|
||||
});
|
||||
};
|
||||
}(jQuery));
|
||||
573
DEPRECATED/test_plugin_capture_screen_Mocha_async.js
Normal file
573
DEPRECATED/test_plugin_capture_screen_Mocha_async.js
Normal file
@@ -0,0 +1,573 @@
|
||||
// Note: This code assumes you have Mocha, Chai, and Sinon loaded in your test environment
|
||||
// along with jQuery and html2canvas. You'll also need to include your screen capture plugin.
|
||||
|
||||
describe('Screen Capture Plugin', function() {
|
||||
var $button, sandbox, originalBodyHTML;
|
||||
|
||||
beforeEach(function() {
|
||||
originalBodyHTML = document.body.innerHTML;
|
||||
// Create a test element and initialize the plugin
|
||||
$button = $('.' + flagCaptureScreen);
|
||||
$button.screenshotButton();
|
||||
// $button = $button.find('button');
|
||||
|
||||
// Create a sinon sandbox for mocking
|
||||
sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
// Clean up
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
after(function() {
|
||||
document.body.innerHTML = originalBodyHTML;
|
||||
});
|
||||
|
||||
// 1. Initialization and Setup
|
||||
describe('1. Initialization and Setup', function() {
|
||||
it('a. should initialize correctly on a given DOM element', function() {
|
||||
expect($button).to.exist;
|
||||
});
|
||||
|
||||
it('b. should create the button with correct text', function() {
|
||||
expect($button.text()).to.equal('Capture Screen');
|
||||
});
|
||||
|
||||
it('c. should make the button visible on the page', function() {
|
||||
expect($button.is(':visible')).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
// 2. Button Functionality
|
||||
describe('2. Button Functionality', function() {
|
||||
it('a. should trigger screenshot process on click', function(done) {
|
||||
// const canvasStub = sinon.stub(document.createElement('canvas'));
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
// await waitForClick($button);
|
||||
$button.click();
|
||||
setTimeout(() => {
|
||||
expect(html2canvas.called).to.be.true;
|
||||
expect(html2canvas.callCount).to.equal(1);
|
||||
html2canvas.restore();
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
it('b. should hide button during screenshot process',
|
||||
/*
|
||||
async function() {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
let resolve;
|
||||
const promise = new Promise((r) => resolve = r);
|
||||
function handleButtonClick() {
|
||||
resolve();
|
||||
$button.off('click', handleButtonClick);
|
||||
}
|
||||
$button.on("click", function() {
|
||||
handleButtonClick();
|
||||
});
|
||||
$button.click();
|
||||
expect($button.hasClass(flagIsHidden)).to.be.true;
|
||||
html2canvas.restore();
|
||||
}
|
||||
*/
|
||||
function(done) {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
$button.click();
|
||||
expect($button.hasClass(flagIsHidden)).to.be.true;
|
||||
html2canvas.restore();
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
it('c. should show button after screenshot is taken', function(done) {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
/*
|
||||
await waitForClick($button).then(() => {
|
||||
setTimeout(() => {
|
||||
expect($button.hasClass(flagIsHidden)).to.be.false;
|
||||
// html2canvas.restore();
|
||||
}, 100); // delay for screenshot and callback process
|
||||
})
|
||||
.catch((err) => {
|
||||
html2canvas.restore();
|
||||
throw err;
|
||||
});
|
||||
*/
|
||||
$button.click();
|
||||
setTimeout(() => {
|
||||
expect($button.hasClass(flagIsHidden)).to.be.false;
|
||||
html2canvas.restore();
|
||||
done();
|
||||
}, 1000); // delay for screenshot and callback process
|
||||
});
|
||||
});
|
||||
|
||||
// 3. Input Handling
|
||||
describe('3. Input Handling', function() {
|
||||
let $input, $textarea;
|
||||
|
||||
beforeEach(function() {
|
||||
$input = $('<input type="text" value="test">').appendTo('body');
|
||||
$textarea = $('<textarea>test</textarea>').appendTo('body');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
$input.remove();
|
||||
$textarea.remove();
|
||||
});
|
||||
|
||||
it('a. should clear input and textarea values during screenshot', function(done) {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
$button.click();
|
||||
expect($input.val()).to.equal('');
|
||||
expect($textarea.val()).to.equal('');
|
||||
html2canvas.restore();
|
||||
done();
|
||||
});
|
||||
|
||||
it('b. should restore input and textarea values after screenshot', function(done) {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
/*
|
||||
await waitForClick($button).then(() => {
|
||||
setTimeout(() => {
|
||||
expect($input.val()).to.equal('test');
|
||||
expect($textarea.val()).to.equal('test');
|
||||
}, 1000); // delay for screenshot and callback process
|
||||
})
|
||||
/*
|
||||
.catch((err) => {
|
||||
html2canvas.restore();
|
||||
throw err;
|
||||
})*
|
||||
;
|
||||
*/
|
||||
$button.click();
|
||||
setTimeout(() => {
|
||||
expect($input.val()).to.equal('test');
|
||||
expect($textarea.val()).to.equal('test');
|
||||
done();
|
||||
}, 1000);
|
||||
html2canvas.restore();
|
||||
});
|
||||
});
|
||||
|
||||
// 4. Modal and Floating Element Handling
|
||||
describe('4. Modal and Floating Element Handling', function() {
|
||||
let $modal;
|
||||
|
||||
beforeEach(function() {
|
||||
$modal = $('<div class="modal">Modal Content</div>').appendTo('body');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
$modal.remove();
|
||||
});
|
||||
|
||||
it('a. should capture visible modals', async function() {
|
||||
sinon.stub(window, 'html2canvas').callsFake(function(element) {
|
||||
expect($(element).find('.modal').length).to.equal(5);
|
||||
return Promise.resolve(document.createElement('canvas'));
|
||||
});
|
||||
await waitForClick($button);
|
||||
html2canvas.restore();
|
||||
});
|
||||
});
|
||||
|
||||
// 5. HTML2Canvas Integration
|
||||
describe('5. HTML2Canvas Integration', function() {
|
||||
it('a. should call html2canvas with correct parameters', async function() {
|
||||
const html2canvasStub = sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
await waitForClick($button);
|
||||
html2canvas.restore();
|
||||
expect(html2canvasStub.calledWith(document.body)).to.be.true;
|
||||
});
|
||||
|
||||
it('b. should handle html2canvas errors gracefully', async function() {
|
||||
sinon.stub(window, 'html2canvas').rejects(new Error('Test error'));
|
||||
sinon.stub(console, 'error');
|
||||
await waitForClick($button);
|
||||
html2canvas.restore();
|
||||
expect(console.error.called).to.be.true;
|
||||
console.error.restore();
|
||||
});
|
||||
});
|
||||
|
||||
// 6. Screenshot Generation
|
||||
describe('6. Screenshot Generation', function() {
|
||||
/* Base64 string used instead of Blob
|
||||
it('a. should create a blob from the canvas', async function() {
|
||||
const canvas = document.createElement('canvas');
|
||||
sinon.stub(window, 'html2canvas').resolves(canvas);
|
||||
sinon.stub(canvas, 'toBlob').callsArgWith(0, new Blob());
|
||||
await waitForClick($button);
|
||||
html2canvas.restore();
|
||||
expect(canvas.toBlob.called).to.be.true;
|
||||
canvas.toBlob.restore();
|
||||
});
|
||||
*/
|
||||
|
||||
it('b. should generate a Base64 PNG string when saving is confirmed', async function() {
|
||||
const consoleStub = sinon.stub(console, 'log');
|
||||
await waitForClick($button).then(() => {
|
||||
setTimeout(() => {
|
||||
expect(consoleStub.callCount).to.equal(2);
|
||||
const base64String = consoleStub.getCall(0).args[1];
|
||||
expect(base64String).to.be.a('string');
|
||||
expect(base64String).to.match(/^data:image\/png;base64,/);
|
||||
consoleStub.restore();
|
||||
}, 1000); // delay for screenshot and callback process
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 7. Download Functionality
|
||||
describe('7. Download Functionality', function() {
|
||||
it('a. should create a temporary anchor element for download', async function() {
|
||||
// Stub document.createElement
|
||||
createElementStub = sinon.stub(document, 'createElement').callsFake(function(tagName) {
|
||||
if (tagName === 'a') {
|
||||
return {
|
||||
style: {},
|
||||
href: '',
|
||||
download: '',
|
||||
click: sinon.spy()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Stub document.body.appendChild and removeChild
|
||||
appendChildStub = sinon.stub(document.body, 'appendChild');
|
||||
removeChildStub = sinon.stub(document.body, 'removeChild');
|
||||
|
||||
// Mock blob and URL
|
||||
blobMock = new Blob(['test'], { type: 'text/plain' });
|
||||
urlMock = 'blob:http://example.com/test';
|
||||
sinon.stub(URL, 'createObjectURL').returns(urlMock);
|
||||
sinon.stub(URL, 'revokeObjectURL');
|
||||
|
||||
await waitForClick($button).then(() => {
|
||||
setTimeout(() => {
|
||||
// Assertions
|
||||
expect(createElementStub.calledWith('a')).to.be.true;
|
||||
expect(appendChildStub.calledOnce).to.be.true;
|
||||
expect(removeChildStub.calledOnce).to.be.true;
|
||||
|
||||
const aElement = appendChildStub.getCall(0).args[0];
|
||||
expect(aElement.href).to.equal(urlMock);
|
||||
expect(aElement.download).to.equal('screenshot.png'); // Assuming settings.fileName is 'screenshot.png'
|
||||
}, 1000);
|
||||
});
|
||||
/*
|
||||
const appendChildStub = sandbox.stub(document.body, 'appendChild').callsFake((element) => {
|
||||
console.log("attempting to append element: ", element);
|
||||
if (element.tagName === 'IFRAME') {
|
||||
// Allow iframe to be appended normally
|
||||
document.body.appendChild.wrappedMethod.call(document.body, element);
|
||||
}
|
||||
});
|
||||
sandbox.stub(document.body, 'removeChild');
|
||||
sandbox.stub(URL, 'createObjectURL').returns('blob:test');
|
||||
sandbox.stub(URL, 'revokeObjectURL');
|
||||
|
||||
await waitForClick($button).then(() => {
|
||||
setTimeout(() => {
|
||||
expect(document.body.appendChild.calledOnce).to.be.true;
|
||||
expect(document.body.appendChild.args[0][0].tagName).to.equal('A');
|
||||
}, 1000); // delay for screenshot and callback process
|
||||
});
|
||||
*/
|
||||
});
|
||||
|
||||
it('b. should click the temporary anchor element programmatically', async function() {
|
||||
const fakeAnchor = {
|
||||
style: {},
|
||||
click: sinon.spy()
|
||||
};
|
||||
sandbox.stub(document, 'createElement').returns(fakeAnchor);
|
||||
|
||||
await waitForClick($button);
|
||||
expect(fakeAnchor.click.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('c. should remove the temporary anchor after download', async function() {
|
||||
sandbox.stub(document.body, 'removeChild');
|
||||
|
||||
await waitForClick($button);
|
||||
expect(document.body.removeChild.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
// 8. Cross-browser Compatibility
|
||||
describe('8. Cross-browser Compatibility', function() {
|
||||
it('a. should work in Chrome-like environments', function() {
|
||||
// Assuming we're running tests in a Chrome-like environment
|
||||
expect(() => $button.screenshotButton()).to.not.throw();
|
||||
});
|
||||
|
||||
// Additional browser-specific tests would go here
|
||||
// These might need to be run in different environments or with browser mocks
|
||||
});
|
||||
|
||||
// 9. Performance
|
||||
describe('9. Performance', function() {
|
||||
it('a. should capture and generate screenshot within acceptable time', async function() {
|
||||
this.timeout(5000); // Adjust timeout as needed
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
await waitForClick($button);
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
expect(duration).to.be.below(1000); // Adjust threshold as needed
|
||||
});
|
||||
|
||||
// Additional performance tests with different page complexities would go here
|
||||
});
|
||||
|
||||
// 10. Error Handling
|
||||
describe('10. Error Handling', function() {
|
||||
it('a. should log error when html2canvas is not loaded', async function() {
|
||||
const consoleErrorStub = sandbox.stub(console, 'error');
|
||||
const originalHtml2Canvas = window.html2canvas;
|
||||
delete window.html2canvas;
|
||||
|
||||
await waitForClick($button);
|
||||
|
||||
expect(consoleErrorStub.calledOnce).to.be.true;
|
||||
expect(consoleErrorStub.args[0][0]).to.include('html2canvas is not loaded');
|
||||
|
||||
// Restore html2canvas
|
||||
window.html2canvas = originalHtml2Canvas;
|
||||
});
|
||||
|
||||
it('a. should handle errors during capture process', async function() {
|
||||
sandbox.stub(window, 'html2canvas').rejects(new Error('Capture failed'));
|
||||
const consoleErrorStub = sandbox.stub(console, 'error');
|
||||
|
||||
await waitForClick($button);
|
||||
|
||||
expect(consoleErrorStub.calledOnce).to.be.true;
|
||||
expect(consoleErrorStub.args[0][0]).to.include('Capture failed');
|
||||
});
|
||||
});
|
||||
|
||||
// Configuration Options
|
||||
describe('11. Configuration Options', function() {
|
||||
it('11.1 should apply custom button text correctly', function() {
|
||||
const customText = 'Capture Screen';
|
||||
$button.screenshotButton({ buttonText: customText });
|
||||
expect($button.text()).to.equal(customText);
|
||||
});
|
||||
|
||||
it('11.2 should use custom filename for the downloaded image', async function() {
|
||||
const customFileName = 'custom-screenshot.png';
|
||||
$button.screenshotButton({ fileName: customFileName });
|
||||
|
||||
// Mock html2canvas and URL.createObjectURL
|
||||
global.html2canvas = sinon.stub().resolves({ toBlob: (callback) => callback(new Blob()) });
|
||||
global.URL.createObjectURL = sinon.stub().returns('blob:url');
|
||||
|
||||
// Mock anchor creation and click
|
||||
const anchorMock = {
|
||||
style: {},
|
||||
click: sinon.spy(),
|
||||
remove: sinon.spy()
|
||||
};
|
||||
sinon.stub(document, 'createElement').returns(anchorMock);
|
||||
|
||||
await waitForClick($button);
|
||||
|
||||
expect(anchorMock.download).to.equal(customFileName);
|
||||
document.createElement.restore();
|
||||
});
|
||||
|
||||
it('11.3 should respect custom modal selectors during capture', async function() {
|
||||
const customSelector = '.custom-modal';
|
||||
$button.screenshotButton({ modalsSelector: customSelector });
|
||||
|
||||
// Create a custom modal element
|
||||
$('<div class="custom-modal">Custom Modal</div>').appendTo('body');
|
||||
|
||||
// Mock html2canvas
|
||||
global.html2canvas = sinon.spy((element, options) => {
|
||||
expect(options.ignoreElements).to.be.a('function');
|
||||
const customModal = document.querySelector(customSelector);
|
||||
expect(options.ignoreElements(customModal)).to.be.false;
|
||||
done();
|
||||
return Promise.resolve({ toBlob: () => {} });
|
||||
});
|
||||
|
||||
await waitForClick($button);
|
||||
});
|
||||
});
|
||||
|
||||
// Accessibility
|
||||
describe('12. Accessibility', function() {
|
||||
it('12.1 should have appropriate ARIA attributes', function() {
|
||||
$button.screenshotButton(defaultOptions);
|
||||
expect($button.attr('role')).to.equal('button');
|
||||
expect($button.attr('aria-label')).to.equal(defaultOptions.buttonText);
|
||||
});
|
||||
|
||||
it('12.2 should be keyboard accessible', async function() {
|
||||
$button.screenshotButton(defaultOptions);
|
||||
$button.on('keydown', function(e) {
|
||||
if (e.which === 13) { // Enter key
|
||||
done();
|
||||
}
|
||||
});
|
||||
const event = new KeyboardEvent('keydown', { 'keyCode': 13 });
|
||||
$button[0].dispatchEvent(event);
|
||||
});
|
||||
});
|
||||
|
||||
// Security
|
||||
describe('13. Security', function() {
|
||||
it('13.1 should not capture sensitive information in inputs and textareas', async function() {
|
||||
$button.screenshotButton(defaultOptions);
|
||||
|
||||
// Create test input and textarea with sensitive information
|
||||
$('<input type="text" value="sensitive-info">').appendTo('body');
|
||||
$('<textarea>confidential-data</textarea>').appendTo('body');
|
||||
|
||||
// Mock html2canvas
|
||||
global.html2canvas = sinon.spy(() => {
|
||||
const inputs = document.querySelectorAll('input, textarea');
|
||||
inputs.forEach(input => {
|
||||
expect(input.value).to.be.empty;
|
||||
});
|
||||
done();
|
||||
return Promise.resolve({ toBlob: () => {} });
|
||||
});
|
||||
|
||||
await waitForClick($button);
|
||||
});
|
||||
|
||||
it('13.2 should restore input and textarea values after capture', async function() {
|
||||
$button.screenshotButton(defaultOptions);
|
||||
|
||||
const inputValue = 'sensitive-info';
|
||||
const textareaValue = 'confidential-data';
|
||||
|
||||
// Create test input and textarea with sensitive information
|
||||
$('<input type="text" value="' + inputValue + '">').appendTo('body');
|
||||
$('<textarea>' + textareaValue + '</textarea>').appendTo('body');
|
||||
|
||||
// Mock html2canvas and URL.createObjectURL
|
||||
global.html2canvas = sinon.stub().resolves({ toBlob: (callback) => callback(new Blob()) });
|
||||
global.URL.createObjectURL = sinon.stub().returns('blob:url');
|
||||
|
||||
await waitForClick($button);
|
||||
|
||||
setTimeout(() => {
|
||||
const inputs = document.querySelectorAll('input, textarea');
|
||||
inputs.forEach(input => {
|
||||
if (input.tagName === 'INPUT') {
|
||||
expect(input.value).to.equal(inputValue);
|
||||
} else if (input.tagName === 'TEXTAREA') {
|
||||
expect(input.value).to.equal(textareaValue);
|
||||
}
|
||||
});
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
// CSS Interaction
|
||||
describe('14. CSS Interaction', function() {
|
||||
it('14.1 should not break existing page styles', function() {
|
||||
const originalStyles = getComputedStyle(document.body);
|
||||
$button.screenshotButton();
|
||||
const newStyles = getComputedStyle(document.body);
|
||||
expect(originalStyles.cssText).to.equal(newStyles.cssText);
|
||||
});
|
||||
|
||||
it('14.2 should apply correct button styling', function() {
|
||||
$button.screenshotButton();
|
||||
const $button = $button.find('button');
|
||||
const buttonStyles = getComputedStyle($button[0]);
|
||||
expect(buttonStyles.display).to.not.equal('none');
|
||||
// Add more specific style checks as per your plugin's CSS
|
||||
});
|
||||
});
|
||||
|
||||
// jQuery Plugin Standards
|
||||
describe('15. jQuery Plugin Standards', function() {
|
||||
it('15.1 should return jQuery object for chaining', function() {
|
||||
const result = $button.screenshotButton();
|
||||
expect(result).to.equal($button);
|
||||
});
|
||||
|
||||
it('15.2 should initialize plugin on multiple elements', function() {
|
||||
$('body').append('<div class="test-class"></div><div class="test-class"></div>');
|
||||
$('.test-class').screenshotButton();
|
||||
expect($('.test-class').find('button').length).to.equal(2);
|
||||
});
|
||||
});
|
||||
|
||||
// Memory Management
|
||||
describe('16. Memory Management', function() {
|
||||
it('16.1 should not leak memory on repeated initialization and destruction', function() {
|
||||
const initialMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
for (let i = 0; i < 100; i++) {
|
||||
$button.screenshotButton();
|
||||
$button.empty();
|
||||
}
|
||||
const finalMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
expect(finalMemory - initialMemory).to.be.below(1000000); // Less than 1MB increase
|
||||
});
|
||||
|
||||
it('16.2 should remove all created DOM elements after use', function() {
|
||||
$button.screenshotButton();
|
||||
const initialChildCount = $button[0].childElementCount;
|
||||
$button.find('button').click();
|
||||
// Assuming the screenshot process is synchronous for this test
|
||||
expect($button[0].childElementCount).to.equal(initialChildCount);
|
||||
});
|
||||
});
|
||||
|
||||
// Responsiveness
|
||||
describe('17. Responsiveness', function() {
|
||||
it('17.1 should behave correctly on different screen sizes', function() {
|
||||
const viewports = [
|
||||
{width: 320, height: 568}, // iPhone 5
|
||||
{width: 1024, height: 768}, // iPad
|
||||
{width: 1920, height: 1080} // Full HD
|
||||
];
|
||||
|
||||
viewports.forEach(size => {
|
||||
$button.width(size.width).height(size.height);
|
||||
$button.screenshotButton();
|
||||
const $button = $button.find('button');
|
||||
expect($button.is(':visible')).to.be.true;
|
||||
expect($button.width()).to.be.at.most(size.width);
|
||||
});
|
||||
});
|
||||
|
||||
it('17.2 should capture accurate viewport in screenshot', async function() {
|
||||
$button.width(800).height(600);
|
||||
$button.html('<div id="content" style="width: 100%; height: 100%; background-color: red;"></div>');
|
||||
$button.screenshotButton();
|
||||
|
||||
const $button = $button.find('button');
|
||||
await waitForClick($button);
|
||||
|
||||
// Mock html2canvas to check if it's called with correct dimensions
|
||||
sinon.replace(window, 'html2canvas', sinon.fake.returns(Promise.resolve({
|
||||
toBlob: (callback) => callback(new Blob(['fakepngdata'], {type: 'image/png'}))
|
||||
})));
|
||||
|
||||
setTimeout(() => {
|
||||
expect(html2canvas.calledOnce).to.be.true;
|
||||
const args = html2canvas.firstCall.args[1];
|
||||
expect(args.width).to.equal(800);
|
||||
expect(args.height).to.equal(600);
|
||||
sinon.restore();
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
174
DEPRECATED/test_plugin_display_png_Jest.js
Normal file
174
DEPRECATED/test_plugin_display_png_Jest.js
Normal file
@@ -0,0 +1,174 @@
|
||||
// Mock dependencies
|
||||
jest.mock('fabric');
|
||||
|
||||
// Setup
|
||||
let container, canvas;
|
||||
|
||||
beforeEach(() => {
|
||||
// Set up our document body
|
||||
document.body.innerHTML = '<div class="image-annotator"></div>';
|
||||
container = document.querySelector('.image-annotator');
|
||||
|
||||
// Mock Fabric.Canvas
|
||||
canvas = new fabric.Canvas();
|
||||
fabric.Canvas.mockImplementation(() => canvas);
|
||||
|
||||
// Initialize our plugin
|
||||
$('.image-annotator').imageAnnotator();
|
||||
});
|
||||
|
||||
// Initialization and Setup
|
||||
test('1. Plugin initializes correctly', () => {
|
||||
expect(container.querySelector('canvas')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('2. Canvas is created with correct dimensions', () => {
|
||||
expect(canvas.setWidth).toHaveBeenCalledWith(800);
|
||||
expect(canvas.setHeight).toHaveBeenCalledWith(600);
|
||||
});
|
||||
|
||||
test('3. All UI elements are present after initialization', () => {
|
||||
expect(container.querySelector('#imageUpload')).not.toBeNull();
|
||||
expect(container.querySelector('#addArrow')).not.toBeNull();
|
||||
expect(container.querySelector('#addTextbox')).not.toBeNull();
|
||||
expect(container.querySelector('#eraseElement')).not.toBeNull();
|
||||
expect(container.querySelector('#saveButton')).not.toBeNull();
|
||||
});
|
||||
|
||||
// Image Upload and Display
|
||||
test('4. PNG file can be successfully uploaded', () => {
|
||||
const file = new File([''], 'test.png', { type: 'image/png' });
|
||||
const event = { target: { files: [file] } };
|
||||
|
||||
const fileReader = {
|
||||
readAsDataURL: jest.fn(),
|
||||
onload: null
|
||||
};
|
||||
window.FileReader = jest.fn(() => fileReader);
|
||||
|
||||
$('#imageUpload').trigger('change', event);
|
||||
|
||||
expect(fileReader.readAsDataURL).toHaveBeenCalledWith(file);
|
||||
});
|
||||
|
||||
test('5. Uploaded image is displayed correctly on the canvas', () => {
|
||||
// This test would be similar to test 4, but would also check that fabric.Image.fromURL is called
|
||||
// and that setBackgroundImage is called on the canvas
|
||||
});
|
||||
|
||||
test('6. Image is scaled appropriately to fit the canvas', () => {
|
||||
// Similar to test 5, but would check the scaling parameters passed to setBackgroundImage
|
||||
});
|
||||
|
||||
test('7. Invalid file types are handled', () => {
|
||||
const file = new File([''], 'test.jpg', { type: 'image/jpeg' });
|
||||
const event = { target: { files: [file] } };
|
||||
|
||||
console.error = jest.fn();
|
||||
|
||||
$('#imageUpload').trigger('change', event);
|
||||
|
||||
expect(console.error).toHaveBeenCalledWith('Please upload a PNG file.');
|
||||
});
|
||||
|
||||
// Arrow Addition
|
||||
test('9. Clicking "Add Arrow" creates a new arrow on the canvas', () => {
|
||||
$('#addArrow').click();
|
||||
|
||||
expect(fabric.Path).toHaveBeenCalled();
|
||||
expect(canvas.add).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Textbox Addition
|
||||
test('12. Clicking "Add Textbox" creates a new textbox on the canvas', () => {
|
||||
$('#addTextbox').click();
|
||||
|
||||
expect(fabric.Textbox).toHaveBeenCalled();
|
||||
expect(canvas.add).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Element Manipulation
|
||||
test('16. Arrows can be selected, moved, resized, and rotated', () => {
|
||||
const arrow = new fabric.Path();
|
||||
canvas.getActiveObject.mockReturnValue(arrow);
|
||||
|
||||
arrow.set = jest.fn();
|
||||
|
||||
canvas.trigger('object:modified', { target: arrow });
|
||||
|
||||
expect(arrow.set).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Erasing Elements
|
||||
test('20. Clicking "Erase Element" removes the currently selected element', () => {
|
||||
const object = new fabric.Object();
|
||||
canvas.getActiveObject.mockReturnValue(object);
|
||||
|
||||
$('#eraseElement').click();
|
||||
|
||||
expect(canvas.remove).toHaveBeenCalledWith(object);
|
||||
});
|
||||
|
||||
// Saving Functionality
|
||||
test('24. Clicking "Save" triggers the confirmation dialog', () => {
|
||||
window.confirm = jest.fn(() => true);
|
||||
|
||||
$('#saveButton').click();
|
||||
|
||||
expect(window.confirm).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('26. Confirming the dialog generates a Base64 PNG string', () => {
|
||||
window.confirm = jest.fn(() => true);
|
||||
canvas.toDataURL = jest.fn(() => 'data:image/png;base64,ABC123');
|
||||
|
||||
$('#saveButton').click();
|
||||
|
||||
expect(canvas.toDataURL).toHaveBeenCalledWith({ format: 'png', quality: 1 });
|
||||
});
|
||||
|
||||
// Edge Cases and Error Handling
|
||||
test('29. Plugin handles initialization on an invalid DOM element', () => {
|
||||
console.error = jest.fn();
|
||||
|
||||
$('.non-existent-element').imageAnnotator();
|
||||
|
||||
expect(console.error).toHaveBeenCalledWith('Invalid element for image annotator');
|
||||
});
|
||||
|
||||
// Performance
|
||||
test('34. Rendering performance with many elements', () => {
|
||||
const start = performance.now();
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
canvas.add(new fabric.Path());
|
||||
}
|
||||
|
||||
canvas.renderAll();
|
||||
|
||||
const end = performance.now();
|
||||
expect(end - start).toBeLessThan(1000); // Assuming 1 second is our performance threshold
|
||||
});
|
||||
|
||||
// Browser Compatibility
|
||||
// Note: This would typically be done with a tool like Selenium or Cypress for cross-browser testing
|
||||
|
||||
// Accessibility
|
||||
test('38. Keyboard navigation for adding elements', () => {
|
||||
const event = new KeyboardEvent('keydown', {'key': 'Enter'});
|
||||
$('#addArrow').focus();
|
||||
$('#addArrow')[0].dispatchEvent(event);
|
||||
|
||||
expect(fabric.Path).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Security
|
||||
test('41. Confirmation dialog appears consistently before saving', () => {
|
||||
window.confirm = jest.fn(() => true);
|
||||
|
||||
$('#saveButton').click();
|
||||
$('#saveButton').click();
|
||||
$('#saveButton').click();
|
||||
|
||||
expect(window.confirm).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
47
DEPRECATED/test_view_capture_screen.html
Normal file
47
DEPRECATED/test_view_capture_screen.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Screen Capture Test Page</title>
|
||||
<link rel="stylesheet" href="capture-screen.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="content-wrapper">
|
||||
<main>
|
||||
<h1>Screen Capture Test Page</h1>
|
||||
<p>This page is used to test the screen capture functionality.</p>
|
||||
<input type="text" id="inputTestText" value="Test input">
|
||||
<textarea id="textareaTest">Test textarea</textarea>
|
||||
<input type="number" id="inputTestNumber" value="2" />
|
||||
<input type="password" id="inputTestPassword" value="password123">
|
||||
<button class="capture-screen">Capture Screen</button>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div id="modalTest" class="modal">
|
||||
<div class="modal-content">
|
||||
<h2>Test Modal</h2>
|
||||
<p>This is a test modal.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mocha"></div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.3.2/mocha.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.3.4/chai.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/10.0.0/sinon.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.3.2/html2canvas.min.js"></script>
|
||||
<script src="plugin_capture_screen.js"></script>
|
||||
<script>
|
||||
mocha.setup('bdd');
|
||||
const expect = chai.expect;
|
||||
</script>
|
||||
<script src="test_plugin_capture_screen_Mocha.js"></script>
|
||||
<script>
|
||||
var flagCaptureScreen = "capture-screen";
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
110
DEPRECATED/view_capture_screen_v2a.html
Normal file
110
DEPRECATED/view_capture_screen_v2a.html
Normal file
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Screen Capture Control</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
||||
<link rel="stylesheet" href="capture-screen.css">
|
||||
<style>
|
||||
#captureButton {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#captureButton:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.sensitive-data {
|
||||
color: transparent;
|
||||
background-color: #ccc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Website Title</h1>
|
||||
</header>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<main>
|
||||
<h2>Main Content</h2>
|
||||
<p>This is where the main content of your page goes. You can add articles, blog posts, or any other primary content here.</p>
|
||||
<input type="text" value="Test 1" />
|
||||
<input type="number" value="2" />
|
||||
<textarea>Test 3</textarea>
|
||||
<button class="capture-screen">Capture Screen</button>
|
||||
|
||||
<!-- Notification Widget -->
|
||||
<div id="notification-widget" class="widget">
|
||||
<h3>Notifications</h3>
|
||||
<p>You have 3 new messages.</p>
|
||||
</div>
|
||||
<a href="#notification-widget" id="notification-toggle" class="widget-toggle">Toggle Notifications</a>
|
||||
|
||||
<!-- Chat Widget -->
|
||||
<div id="chat-widget" class="widget">
|
||||
<h3>Chat</h3>
|
||||
<p>Chat content goes here...</p>
|
||||
</div>
|
||||
<a href="#chat-widget" id="chat-toggle" class="widget-toggle">Toggle Chat</a>
|
||||
|
||||
<!-- Quick Settings Widget -->
|
||||
<div id="settings-widget" class="widget">
|
||||
<h3>Quick Settings</h3>
|
||||
<p>Adjust your settings here...</p>
|
||||
</div>
|
||||
<a href="#settings-widget" id="settings-toggle" class="widget-toggle">Toggle Settings</a>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.capture-screen').on('click', function() {
|
||||
// Hide the capture button
|
||||
$(this).hide();
|
||||
|
||||
// Mask sensitive data
|
||||
$('input, textarea').each(function() {
|
||||
$(this).addClass('sensitive-data');
|
||||
$(this).attr('data-original-value', $(this).val());
|
||||
$(this).val('********');
|
||||
});
|
||||
|
||||
// Capture the screen
|
||||
html2canvas(document.body).then(function(canvas) {
|
||||
// Convert canvas to PNG data URL
|
||||
var dataURL = canvas.toDataURL('image/png');
|
||||
|
||||
// Create a temporary link and trigger download
|
||||
var link = document.createElement('a');
|
||||
link.href = dataURL;
|
||||
link.download = 'screen_capture.png';
|
||||
link.click();
|
||||
|
||||
// Restore sensitive data
|
||||
$('input, textarea').each(function() {
|
||||
$(this).removeClass('sensitive-data');
|
||||
$(this).val($(this).attr('data-original-value'));
|
||||
$(this).removeAttr('data-original-value');
|
||||
});
|
||||
|
||||
// Show the capture button again
|
||||
$('.capture-screen').show();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
60
DEPRECATED/view_capture_screen_v2b.html
Normal file
60
DEPRECATED/view_capture_screen_v2b.html
Normal file
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Simple Layout Template</title>
|
||||
<link rel="stylesheet" href="capture-screen.css">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
|
||||
<script src="./plugin_capture_screen.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Website Title</h1>
|
||||
</header>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<main>
|
||||
<h2>Main Content</h2>
|
||||
<p>This is where the main content of your page goes. You can add articles, blog posts, or any other primary content here.</p>
|
||||
<input type="text" value="Test 1" />
|
||||
<input type="number" value="2" />
|
||||
<textarea>Test 3</textarea>
|
||||
<button class="capture-screen">Capture Screen</button>
|
||||
|
||||
<!-- Notification Widget -->
|
||||
<div id="notification-widget" class="widget">
|
||||
<h3>Notifications</h3>
|
||||
<p>You have 3 new messages.</p>
|
||||
</div>
|
||||
<a href="#notification-widget" id="notification-toggle" class="widget-toggle">Toggle Notifications</a>
|
||||
|
||||
<!-- Chat Widget -->
|
||||
<div id="chat-widget" class="widget">
|
||||
<h3>Chat</h3>
|
||||
<p>Chat content goes here...</p>
|
||||
</div>
|
||||
<a href="#chat-widget" id="chat-toggle" class="widget-toggle">Toggle Chat</a>
|
||||
|
||||
<!-- Quick Settings Widget -->
|
||||
<div id="settings-widget" class="widget">
|
||||
<h3>Quick Settings</h3>
|
||||
<p>Adjust your settings here...</p>
|
||||
</div>
|
||||
<a href="#settings-widget" id="settings-toggle" class="widget-toggle">Toggle Settings</a>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
var flagCaptureScreen = "capture-screen";
|
||||
$("." + flagCaptureScreen).screenshotButton({
|
||||
buttonText: "Capture Screen",
|
||||
fileName: "screenshot.png",
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
63
DEPRECATED/view_display_png_v1a.html
Normal file
63
DEPRECATED/view_display_png_v1a.html
Normal file
@@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Image Annotation Control</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="plugin_display_png_v1a.js"></script>
|
||||
<style>
|
||||
#annotationContainer {
|
||||
position: relative;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
border: 1px solid #ccc;
|
||||
margin: 20px auto;
|
||||
}
|
||||
#canvas {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
#toolbox {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="annotatorPNG">
|
||||
<div id="toolbox">
|
||||
<input type="file" id="imageUpload" accept="image/png">
|
||||
<button id="addArrow">Add Arrow</button>
|
||||
<button id="addTextbox">Add Textbox</button>
|
||||
<select id="symbolSelect">
|
||||
<option value="">Select Symbol</option>
|
||||
<!-- Options will be populated dynamically -->
|
||||
</select>
|
||||
<input type="color" id="colorPicker" value="#ff0000">
|
||||
<button id="eraseMode">Erase Mode</button>
|
||||
<button id="saveImage">Save Image</button>
|
||||
</div>
|
||||
<div id="annotationContainer">
|
||||
<canvas id="canvas" width="800" height="600"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
// $(document).ready(hookupPageDisplayPng);
|
||||
// change to flags - enables multiple annotatorPNGs per webpage
|
||||
var idButtonAddArrow = "#addArrow";
|
||||
var idButtonAddTextbox = "#addTextbox";
|
||||
var idButtonEraseMode = "#eraseMode";
|
||||
var idButtonSaveImage = "#saveImage";
|
||||
var idColorPicker = "#colorPicker";
|
||||
var idImageUpload = "#imageUpload";
|
||||
var idSelectSymbol = "#symbolSelect";
|
||||
|
||||
$(document).ready(function() {
|
||||
hookupPageDisplayPng();
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
36
DEPRECATED/view_display_png_v2a.html
Normal file
36
DEPRECATED/view_display_png_v2a.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Image Annotation Control</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="plugin_display_png.js"></script>
|
||||
<link rel="stylesheet" href="display-png.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="annotator-png"></div>
|
||||
</body>
|
||||
<script>
|
||||
var flagAnnotatorPNG = "annotator-png";
|
||||
var flagToolbox = "annotator-png-toolbox";
|
||||
var flagContainer = "annotator-png-container";
|
||||
var flagContainerAnnotation = "annotator-png-container-annotation";
|
||||
var flagUploadImage = "annotator-png-upload-image";
|
||||
var flagAddArrow = "annotator-png-add-arrow";
|
||||
var flagAddTextbox = "annotator-png-add-textbox";
|
||||
var flagAddSymbol = "annotator-png-add-symbol";
|
||||
var flagColourPicker = "annotator-png-colour-picker";
|
||||
var flagEraseMode = "annotator-png-erase-mode";
|
||||
var flagSaveImage = "annotator-png-save-image";
|
||||
var flagCanvasAnnotation = "annotator-png-canvas-annotation";
|
||||
var keyFabric = "fabric";
|
||||
var isEraseMode = false; // "isEraseMode";
|
||||
|
||||
$(document).ready(function() {
|
||||
$("." + flagAnnotatorPNG).annotatorPNG({
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
98
DEPRECATED/view_modals.html
Normal file
98
DEPRECATED/view_modals.html
Normal file
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Modal/Floating Element Examples</title>
|
||||
<style>
|
||||
/* Common styles */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Method 1: CSS Positioning */
|
||||
.modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.modal-content {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
#modal1:target {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Method 2: Dialog Element */
|
||||
dialog::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
dialog {
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Method 3: Floating Element */
|
||||
.floating-element {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Modal/Floating Element Examples</h1>
|
||||
|
||||
<!-- Method 1: CSS Positioning -->
|
||||
<h2>1. CSS Positioning</h2>
|
||||
<a href="#modal1" class="btn">Open Modal 1</a>
|
||||
<div id="modal1" class="modal-overlay">
|
||||
<div class="modal-content">
|
||||
<h2>Modal 1</h2>
|
||||
<p>This is a modal using CSS positioning.</p>
|
||||
<a href="#" class="btn">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Method 2: Dialog Element -->
|
||||
<h2>2. Dialog Element</h2>
|
||||
<button class="btn" onclick="document.getElementById('modal2').showModal()">Open Modal 2</button>
|
||||
<dialog id="modal2">
|
||||
<h2>Modal 2</h2>
|
||||
<p>This is a modal using the dialog element.</p>
|
||||
<button class="btn" onclick="document.getElementById('modal2').close()">Close</button>
|
||||
</dialog>
|
||||
|
||||
<!-- Method 3: Floating Element -->
|
||||
<h2>3. Floating Element</h2>
|
||||
<div class="floating-element">
|
||||
<h2>Floating Element</h2>
|
||||
<p>This is a floating element using fixed positioning.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
89
DEPRECATED/view_modals2.html
Normal file
89
DEPRECATED/view_modals2.html
Normal file
@@ -0,0 +1,89 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Floating Widgets Examples</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
.widget {
|
||||
position: fixed;
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
display: none;
|
||||
}
|
||||
.widget-toggle {
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#notification-widget {
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 250px;
|
||||
}
|
||||
#notification-toggle {
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
#chat-widget {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
}
|
||||
#chat-toggle {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
#settings-widget {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 200px;
|
||||
}
|
||||
#settings-toggle {
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
}
|
||||
.widget:target {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Floating Widgets Examples</h1>
|
||||
<p>Click the buttons to toggle the widgets.</p>
|
||||
|
||||
<!-- Notification Widget -->
|
||||
<div id="notification-widget" class="widget">
|
||||
<h3>Notifications</h3>
|
||||
<p>You have 3 new messages.</p>
|
||||
</div>
|
||||
<a href="#notification-widget" id="notification-toggle" class="widget-toggle">Toggle Notifications</a>
|
||||
|
||||
<!-- Chat Widget -->
|
||||
<div id="chat-widget" class="widget">
|
||||
<h3>Chat</h3>
|
||||
<p>Chat content goes here...</p>
|
||||
</div>
|
||||
<a href="#chat-widget" id="chat-toggle" class="widget-toggle">Toggle Chat</a>
|
||||
|
||||
<!-- Quick Settings Widget -->
|
||||
<div id="settings-widget" class="widget">
|
||||
<h3>Quick Settings</h3>
|
||||
<p>Adjust your settings here...</p>
|
||||
</div>
|
||||
<a href="#settings-widget" id="settings-toggle" class="widget-toggle">Toggle Settings</a>
|
||||
</body>
|
||||
</html>
|
||||
13
DEPRECATED/view_tooltip.html
Normal file
13
DEPRECATED/view_tooltip.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="./plugin_capture_screen.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('[data-tooltip]').simpleTooltip({
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#000000',
|
||||
position: 'bottom'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<button data-tooltip="This is a tooltip">Hover me</button>
|
||||
15
NOTES.txt
Normal file
15
NOTES.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
Tests:
|
||||
rename variables, methods
|
||||
|
||||
|
||||
Screen Capture:
|
||||
Add tooltip on click button, and check that tooltip included in png
|
||||
test scrollable elements - do objects end up in png?
|
||||
css styles - more required
|
||||
|
||||
button enter trigger
|
||||
|
||||
Annotate PNG:
|
||||
|
||||
72
submission - backup/README
Normal file
72
submission - backup/README
Normal file
@@ -0,0 +1,72 @@
|
||||
# Screen Capture and Image Annotation Project
|
||||
|
||||
This project provides two main functionalities:
|
||||
1. Screen Capture: A pluggable web control to capture the current browser viewport.
|
||||
2. Image Annotation: A tool to annotate PNG images with arrows, textboxes, and symbols.
|
||||
|
||||
## Features
|
||||
|
||||
### Screen Capture
|
||||
- Capture the current browser viewport as a PNG image.
|
||||
- Exclude sensitive information from inputs and textareas.
|
||||
- Capture modals and floating elements.
|
||||
- Save the captured image as a PNG file.
|
||||
|
||||
### Image Annotation
|
||||
- Upload and display PNG images.
|
||||
- Add arrows, textboxes, and symbols to the image.
|
||||
- Erase annotated elements.
|
||||
- Save the annotated image as a Base64 PNG string.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
```
|
||||
git clone https://github.com/your-username/screen-capture-annotation-project.git
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Screen Capture
|
||||
|
||||
Include the necessary files in your HTML:
|
||||
|
||||
```html
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
|
||||
<script src="plugin_capture_screen.js"></script>
|
||||
<link rel="stylesheet" href="capture-screen.css">
|
||||
```
|
||||
|
||||
Initialize the plugin on a button:
|
||||
|
||||
```javascript
|
||||
$('.capture-screen').screenshotButton();
|
||||
```
|
||||
|
||||
### Image Annotation
|
||||
|
||||
Include the necessary files in your HTML:
|
||||
|
||||
```html
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="plugin_display_png.js"></script>
|
||||
<link rel="stylesheet" href="display-png.css">
|
||||
```
|
||||
|
||||
Initialize the plugin on a container:
|
||||
|
||||
```javascript
|
||||
$('.annotator-png').annotatorPNG();
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To run the tests:
|
||||
|
||||
1. Open the test views in your browser.
|
||||
|
||||
|
||||
## Known bugs
|
||||
- Colour picker button must be clicked to set colour of active canvas object after selecting colour
|
||||
101
submission - backup/capture-screen.css
Normal file
101
submission - backup/capture-screen.css
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
header {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
main {
|
||||
flex: 3;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Widgets */
|
||||
.modal {
|
||||
position: fixed;
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
/*
|
||||
display: none;
|
||||
*/
|
||||
}
|
||||
.widget-toggle {
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#notification-widget {
|
||||
top: 100px;
|
||||
right: 20px;
|
||||
width: 250px;
|
||||
}
|
||||
#notification-toggle {
|
||||
top: 100px;
|
||||
right: 20px;
|
||||
}
|
||||
#chat-widget {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
}
|
||||
#chat-toggle {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
#settings-widget {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 200px;
|
||||
}
|
||||
#settings-toggle {
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
}
|
||||
.modal:target {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Testing */
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 80%;
|
||||
}
|
||||
15
submission - backup/display-png.css
Normal file
15
submission - backup/display-png.css
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
.annotator-png-container-annotation {
|
||||
position: relative;
|
||||
border: 1px solid #ccc;
|
||||
margin: 20px auto;
|
||||
}
|
||||
.annotator-png-canvas-annotation {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.annotator-png-toolbox {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
152
submission - backup/plugin_capture_screen.js
Normal file
152
submission - backup/plugin_capture_screen.js
Normal file
@@ -0,0 +1,152 @@
|
||||
|
||||
(function($) {
|
||||
$.fn.screenshotButton = function(options) {
|
||||
var settings = $.extend({
|
||||
buttonText: 'Capture Screen',
|
||||
fileName: 'screenshot.png',
|
||||
modalsSelector: '.modal, .popup, .overlay, .dialog, .tooltip, [class*="modal"], [class*="popup"], [class*="overlay"], [class*="dialog"], [class*="tooltip"], [style*="position: fixed"], [style*="position: absolute"], [style*="z-index"]',
|
||||
}, options);
|
||||
|
||||
function exportToBase64String(canvas) {
|
||||
const dataURL = canvas.toDataURL({
|
||||
format: 'png',
|
||||
quality: 1
|
||||
});
|
||||
console.log("Base64 string:", dataURL);
|
||||
return dataURL;
|
||||
// alert("Image saved as Base64 string. Check the console and Downloads folder for the output.");
|
||||
}
|
||||
function exportToBlob(canvas) {
|
||||
canvas.toBlob(function(blob) {
|
||||
var url = URL.createObjectURL(blob);
|
||||
console.log("blob: ", blob);
|
||||
return url;
|
||||
});
|
||||
}
|
||||
function downloadPNG(url) {
|
||||
var a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = settings.fileName;
|
||||
|
||||
console.log("adding button a: ", a);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
return this.each(function() {
|
||||
var $button = $(this);
|
||||
|
||||
$button.text(settings.buttonText);
|
||||
$button.removeClass(flagIsHidden);
|
||||
$button.attr('aria-label', settings.buttonText);
|
||||
|
||||
if (!$button.hasClass(flagInitialised)) {
|
||||
$button.addClass(flagInitialised);
|
||||
$button.on('click', function() {
|
||||
if (typeof html2canvas === 'undefined') {
|
||||
console.error('html2canvas is not loaded. Please include the library.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide button and sensitive information in inputs
|
||||
// $button.hide();
|
||||
$button.addClass(flagIsHidden);
|
||||
$('input').each(function() {
|
||||
$(this).attr("previousValue", $(this).val());
|
||||
$(this).val('');
|
||||
});
|
||||
$('textarea').each(function() {
|
||||
$(this).attr("previousValue", $(this).val());
|
||||
$(this).val('');
|
||||
});
|
||||
|
||||
// set display: block on element for all visible modals and floating elements
|
||||
// elements not detected by html2canvas with cascaded class-based display style
|
||||
$(settings.modalsSelector).each(function() {
|
||||
$(this).css('display', $(this).css('display'));
|
||||
});
|
||||
|
||||
html2canvas(document.body, {
|
||||
logging: true,
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
}).then(function(canvas) {
|
||||
let url = exportToBase64String(canvas);
|
||||
// exportToBlob(canvas);
|
||||
|
||||
downloadPNG(url);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
// Show button and sensitive information in inputs
|
||||
// $button.show();
|
||||
$button.removeClass(flagIsHidden);
|
||||
$('input').each(function() {
|
||||
$(this).val($(this).attr("previousValue"));
|
||||
});
|
||||
$('textarea').each(function() {
|
||||
$(this).val($(this).attr("previousValue"));
|
||||
});
|
||||
}).catch(function(e) {
|
||||
console.error(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}(jQuery));
|
||||
|
||||
function hookupTestModals() {
|
||||
let $buttonToggleModalChat = $(idButtonToggleModalChat);
|
||||
let $modalChat = $(idModalChat);
|
||||
let $buttonToggleModalNotifications = $(idButtonToggleModalNotifications);
|
||||
let $modalNotifications = $(idModalNotifications);
|
||||
let $buttonToggleModalSettings = $(idButtonToggleModalSettings);
|
||||
let $modalSettings = $(idModalSettings);
|
||||
|
||||
if (!$buttonToggleModalChat.hasClass(flagInitialised)) {
|
||||
$buttonToggleModalChat.addClass(flagInitialised);
|
||||
$buttonToggleModalChat.on('click', function() {
|
||||
if ($modalChat.hasClass(flagIsHidden)) {
|
||||
$modalChat.removeClass(flagIsHidden);
|
||||
} else {
|
||||
$modalChat.addClass(flagIsHidden);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!$modalChat.hasClass(flagInitialised)) {
|
||||
$modalChat.addClass(flagInitialised);
|
||||
$modalChat.addClass(flagIsHidden);
|
||||
}
|
||||
|
||||
if (!$buttonToggleModalNotifications.hasClass(flagInitialised)) {
|
||||
$buttonToggleModalNotifications.addClass(flagInitialised);
|
||||
$buttonToggleModalNotifications.on('click', function() {
|
||||
if ($modalNotifications.hasClass(flagIsHidden)) {
|
||||
$modalNotifications.removeClass(flagIsHidden);
|
||||
} else {
|
||||
$modalNotifications.addClass(flagIsHidden);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!$modalNotifications.hasClass(flagInitialised)) {
|
||||
$modalNotifications.addClass(flagInitialised);
|
||||
$modalNotifications.addClass(flagIsHidden);
|
||||
}
|
||||
|
||||
if (!$buttonToggleModalSettings.hasClass(flagInitialised)) {
|
||||
$buttonToggleModalSettings.addClass(flagInitialised);
|
||||
$buttonToggleModalSettings.on('click', function() {
|
||||
if ($modalSettings.hasClass(flagIsHidden)) {
|
||||
$modalSettings.removeClass(flagIsHidden);
|
||||
} else {
|
||||
$modalSettings.addClass(flagIsHidden);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!$modalSettings.hasClass(flagInitialised)) {
|
||||
$modalSettings.addClass(flagInitialised);
|
||||
$modalSettings.addClass(flagIsHidden);
|
||||
}
|
||||
}
|
||||
274
submission - backup/plugin_display_png,js
Normal file
274
submission - backup/plugin_display_png,js
Normal file
@@ -0,0 +1,274 @@
|
||||
|
||||
(function($) {
|
||||
$.fn.annotatorPNG = function(options) {
|
||||
var settings = $.extend({
|
||||
heightCanvas: "600px",
|
||||
widthCanvas: "800px",
|
||||
textButtonSave: "Save Image",
|
||||
}, options);
|
||||
|
||||
|
||||
function getCanvas() {
|
||||
let $annotatorPNG = $(document).find("." + flagAnnotatorPNG);
|
||||
let $canvasAnnotation = $($annotatorPNG.find("canvas." + flagCanvasAnnotation)[0]);
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function deleteSelectedObjects() {
|
||||
let canvas = getCanvas();
|
||||
const activeObject = canvas.getActiveObject();
|
||||
if (activeObject) {
|
||||
console.log("active object type:", activeObject.type);
|
||||
if (activeObject.type === 'activeSelection') {
|
||||
activeObject.forEachObject(function(obj) {
|
||||
canvas.remove(obj);
|
||||
});
|
||||
canvas.discardActiveObject();
|
||||
} else {
|
||||
canvas.remove(activeObject);
|
||||
}
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
}
|
||||
|
||||
return this.each(function() {
|
||||
let $annotatorPNG = $(this);
|
||||
let $toolbox = $annotatorPNG.find("." + flagToolbox);
|
||||
let $inputUploadImage = $toolbox.find("." + flagUploadImage);
|
||||
let $buttonAddArrow = $toolbox.find("." + flagAddArrow);
|
||||
let $buttonAddTextbox = $toolbox.find("." + flagAddTextbox);
|
||||
let $selectAddSymbol = $toolbox.find("." + flagAddSymbol);
|
||||
let $inputColourPicker = $toolbox.find("." + flagColourPicker);
|
||||
let $buttonEraseMode = $toolbox.find("." + flagEraseMode);
|
||||
let $buttonSaveImage = $toolbox.find("." + flagSaveImage);
|
||||
let $containerAnnotation = $annotatorPNG.find("." + flagContainerAnnotation);
|
||||
let $canvasAnnotation = $containerAnnotation.find("." + flagCanvasAnnotation);
|
||||
|
||||
// Default values
|
||||
$inputUploadImage.val('');
|
||||
|
||||
const symbols = ['Orange Arrow.png'];
|
||||
symbols.forEach(symbol => {
|
||||
$selectAddSymbol.append($("<option>", {
|
||||
value: symbol,
|
||||
text: symbol.replace('.png', ''),
|
||||
}));
|
||||
});
|
||||
|
||||
$inputColourPicker.val('#ff0000');
|
||||
|
||||
$buttonEraseMode.textContent = "Erase Mode";
|
||||
$annotatorPNG.data(keyIsEraseMode, false);
|
||||
|
||||
$buttonSaveImage.text(settings.textButtonSave);
|
||||
|
||||
$canvasAnnotation.css({
|
||||
height: settings.heightCanvas,
|
||||
width: settings.widthCanvas,
|
||||
});
|
||||
console.log("canvas: ", $canvasAnnotation[0]);
|
||||
var canvas = new fabric.Canvas($canvasAnnotation[0], {
|
||||
// containerClass: flagContainerAnnotation,
|
||||
});
|
||||
canvas.clear();
|
||||
canvas.selection = true;
|
||||
$canvasAnnotation.data(keyFabric, canvas);
|
||||
|
||||
// Triggers
|
||||
if (!$inputUploadImage.hasClass(flagInitialised)) {
|
||||
$inputUploadImage.addClass(flagInitialised);
|
||||
$inputUploadImage.on("change", function(event) {
|
||||
console.log("File uploaded:", event.target.files[0]);
|
||||
const file = event.target.files[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
if (!(file.type == 'image/png')) {
|
||||
alert("Please upload a PNG file.");
|
||||
throw new Error("Invalid file type.");
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
let canvas = getCanvas();
|
||||
reader.onload = function(eventReader) {
|
||||
fabric.Image.fromURL(eventReader.target.result, function(image) {
|
||||
canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas), {
|
||||
scaleX: settings.widthCanvas.replace('px', '') / image.width,
|
||||
scaleY: settings.heightCanvas.replace('px', '') / image.height,
|
||||
});
|
||||
});
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
if (!$buttonAddArrow.hasClass(flagInitialised)) {
|
||||
$buttonAddArrow.addClass(flagInitialised);
|
||||
$buttonAddArrow.on("click", function() {
|
||||
console.log("Add Arrow clicked");
|
||||
let canvas = getCanvas();
|
||||
const arrow = new fabric.Path('M 0 0 L 200 100', {
|
||||
fill: $inputColourPicker.val(),
|
||||
stroke: $inputColourPicker.val(),
|
||||
strokeWidth: 2,
|
||||
left: 100,
|
||||
top: 100,
|
||||
selectable: true,
|
||||
});
|
||||
canvas.add(arrow);
|
||||
canvas.renderAll();
|
||||
});
|
||||
}
|
||||
|
||||
if (!$buttonAddTextbox.hasClass(flagInitialised)) {
|
||||
$buttonAddTextbox.addClass(flagInitialised);
|
||||
$buttonAddTextbox.on("click", function() {
|
||||
console.log("Add Textbox clicked");
|
||||
let canvas = getCanvas();
|
||||
const textbox = new fabric.Textbox('Type here', {
|
||||
left: 100,
|
||||
top: 100,
|
||||
width: 150,
|
||||
fontSize: 20,
|
||||
fill: $inputColourPicker.val(),
|
||||
selectable: true,
|
||||
});
|
||||
canvas.add(textbox);
|
||||
canvas.renderAll();
|
||||
});
|
||||
}
|
||||
|
||||
if (!$selectAddSymbol.hasClass(flagInitialised)) {
|
||||
$selectAddSymbol.addClass(flagInitialised);
|
||||
$selectAddSymbol.on("change", function() {
|
||||
console.log("Add Symbol changed:", this.value);
|
||||
if (this.value) {
|
||||
let canvas = getCanvas();
|
||||
fabric.Image.fromURL(`symbols/${this.value}`, function(image) {
|
||||
image.set({
|
||||
left: 100,
|
||||
top: 100,
|
||||
scaleX: 0.5,
|
||||
scaleY: 0.5,
|
||||
});
|
||||
canvas.add(image);
|
||||
});
|
||||
this.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!$inputColourPicker.hasClass(flagInitialised)) {
|
||||
$inputColourPicker.addClass(flagInitialised);
|
||||
$inputColourPicker.on("change", function() {
|
||||
console.log("Colour Picker changed:", this.value);
|
||||
let canvas = getCanvas();
|
||||
canvas.renderAll();
|
||||
const activeObject = canvas.getActiveObject();
|
||||
if (activeObject) {
|
||||
if (activeObject.type === 'textbox') {
|
||||
activeObject.set('fill', this.value);
|
||||
} else {
|
||||
activeObject.set('fill', this.value);
|
||||
activeObject.set('stroke', this.value);
|
||||
}
|
||||
canvas.renderAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!$buttonEraseMode.hasClass(flagInitialised)) {
|
||||
$buttonEraseMode.addClass(flagInitialised);
|
||||
$buttonEraseMode.data(keyIsEraseMode, false);
|
||||
$buttonEraseMode.on("click", function() {
|
||||
console.log("Erase Mode clicked");
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
isEraseMode = !isEraseMode;
|
||||
$annotatorPNG.data(keyIsEraseMode, isEraseMode);
|
||||
console.log("Erase Mode:", isEraseMode);
|
||||
this.textContent = isEraseMode ? 'Exit Erase Mode' : 'Erase Mode';
|
||||
|
||||
if (isEraseMode) {
|
||||
deleteSelectedObjects();
|
||||
|
||||
canvas.selection = false;
|
||||
canvas.isDrawingMode = false;
|
||||
|
||||
canvas.defaultCursor = 'crosshair';
|
||||
canvas.hoverCursor = 'crosshair';
|
||||
} else {
|
||||
canvas.selection = true;
|
||||
canvas.defaultCursor = 'default';
|
||||
canvas.hoverCursor = 'move';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (!$canvasAnnotation.hasClass(flagInitialised)) {
|
||||
$canvasAnnotation.addClass(flagInitialised);
|
||||
// Object selection in erase mode
|
||||
$canvasAnnotation.on('selection:created', function() {
|
||||
console.log("Selection created");
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
if (isEraseMode) {
|
||||
deleteSelectedObjects();
|
||||
}
|
||||
});
|
||||
|
||||
// Object click in erase mode
|
||||
$canvasAnnotation.on('mouse:down', function(event) {
|
||||
console.log("Canvas mouse down:", event);
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
if (isEraseMode && event.target) {
|
||||
let canvas = getCanvas();
|
||||
canvas.remove(event.target);
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent dragging in erase mode
|
||||
$canvasAnnotation.on('object:moving', function(event) {
|
||||
console.log("Canvas object moving:", event);
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
if (isEraseMode) {
|
||||
let canvas = getCanvas();
|
||||
event.target.setCoords();
|
||||
canvas.remove(event.target);
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!$buttonSaveImage.hasClass(flagInitialised)) {
|
||||
$buttonSaveImage.addClass(flagInitialised);
|
||||
$buttonSaveImage.on("click", function() {
|
||||
let canvas = getCanvas();
|
||||
if (window.confirm("Please ensure there is no sensitive information or PII in your annotations. Do you want to proceed with saving?")) {
|
||||
const dataURL = canvas.toDataURL({
|
||||
format: 'png',
|
||||
quality: 1
|
||||
});
|
||||
|
||||
// Output
|
||||
console.log("Base64 string:", dataURL);
|
||||
|
||||
let a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = dataURL;
|
||||
a.download = settings.fileName;
|
||||
|
||||
/* Download image
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
*/
|
||||
|
||||
alert("Image saved as Base64 string. Check the console for the output.");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}(jQuery));
|
||||
BIN
submission - backup/screenshot (16).png
Normal file
BIN
submission - backup/screenshot (16).png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
13
submission - backup/shared.js
Normal file
13
submission - backup/shared.js
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
var flagInitialised = "initialised";
|
||||
var flagIsHidden = "is-hidden";
|
||||
var flagIsVisible = "is-visible";
|
||||
|
||||
async function waitForClick($element) {
|
||||
return new Promise((resolve) => {
|
||||
$element.on('click', function() {
|
||||
resolve();
|
||||
});
|
||||
$element.click();
|
||||
});
|
||||
}
|
||||
BIN
submission - backup/symbols/Orange Arrow.png
Normal file
BIN
submission - backup/symbols/Orange Arrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
527
submission - backup/test_plugin_capture_screen_Mocha.js
Normal file
527
submission - backup/test_plugin_capture_screen_Mocha.js
Normal file
@@ -0,0 +1,527 @@
|
||||
|
||||
describe('Screen Capture Plugin', function() {
|
||||
var $button, sandbox, originalBodyHTML;
|
||||
|
||||
beforeEach(function() {
|
||||
originalBodyHTML = document.body.innerHTML;
|
||||
|
||||
$button = $('.' + flagCaptureScreen);
|
||||
$button.screenshotButton(defaultOptions);
|
||||
|
||||
sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
$button.screenshotButton(defaultOptions);
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
after(function() {
|
||||
document.body.innerHTML = originalBodyHTML;
|
||||
});
|
||||
|
||||
describe('1. Initialization and Setup', function() {
|
||||
afterEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
it('a. Should initialize correctly on a given DOM element', function() {
|
||||
expect($button).to.exist;
|
||||
});
|
||||
|
||||
it('b. Should create the button with correct text', function() {
|
||||
expect($button.text()).to.equal('Capture Screen');
|
||||
});
|
||||
|
||||
it('c. Should make the button visible on the page', function() {
|
||||
expect($button.is(':visible')).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('2. Button Functionality', function() {
|
||||
afterEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
it('a. Should trigger screenshot process on click', function() {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect(html2canvas.called).to.be.true;
|
||||
expect(html2canvas.callCount).to.equal(1);
|
||||
html2canvas.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('b. Should hide button during screenshot process', function(done) {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
$button.click();
|
||||
expect($button.hasClass(flagIsHidden)).to.be.true;
|
||||
html2canvas.restore();
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
it('c. Should show button after screenshot is taken', function() {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect($button.hasClass(flagIsHidden)).to.be.false;
|
||||
html2canvas.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('3. Input Handling', function() {
|
||||
let $input, $textarea;
|
||||
|
||||
beforeEach(function() {
|
||||
$input = $('<input type="text" value="test">').appendTo('body');
|
||||
$textarea = $('<textarea>test</textarea>').appendTo('body');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
$input.remove();
|
||||
$textarea.remove();
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
it('a. Should clear input and textarea values during screenshot', function(done) {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
$button.click();
|
||||
expect($input.val()).to.equal('');
|
||||
expect($textarea.val()).to.equal('');
|
||||
html2canvas.restore();
|
||||
done();
|
||||
});
|
||||
|
||||
it('b. Should restore input and textarea values after screenshot', function() {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect($input.val()).to.equal('test');
|
||||
expect($textarea.val()).to.equal('test');
|
||||
html2canvas.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('4. Modal and Floating Element Handling', function() {
|
||||
let $modal;
|
||||
|
||||
beforeEach(function() {
|
||||
$modal = $('<div class="modal">Modal Content</div>').appendTo('body');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
$modal.remove();
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
|
||||
it('a. Should capture visible modals', function(done) {
|
||||
sinon.stub(window, 'html2canvas').callsFake(function(element) {
|
||||
expect($(element).find('.modal').length).to.equal(5);
|
||||
return Promise.resolve(document.createElement('canvas'));
|
||||
});
|
||||
$button.click();
|
||||
html2canvas.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('5. HTML2Canvas Integration', function() {
|
||||
let html2canvasStub, consoleErrorStub;
|
||||
|
||||
afterEach(function() {
|
||||
if (html2canvasStub && html2canvasStub.restore) {
|
||||
html2canvasStub.restore();
|
||||
}
|
||||
if (consoleErrorStub && consoleErrorStub.restore) {
|
||||
consoleErrorStub.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it('a. Should call html2canvas with correct parameters', function() {
|
||||
html2canvasStub = sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect(html2canvasStub.calledWith(document.body)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('b. Should handle html2canvas errors gracefully', function() {
|
||||
html2canvasStub = sinon.stub(window, 'html2canvas').rejects(new Error('Test error'));
|
||||
consoleErrorStub = sinon.stub(console, 'error');
|
||||
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect(consoleErrorStub.called).to.be.true;
|
||||
expect(consoleErrorStub.firstCall.args[0]).to.be.an('error');
|
||||
expect(consoleErrorStub.firstCall.args[0].message).to.equal('Test error');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('6. Screenshot Generation', function() {
|
||||
afterEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
/* Base64 string used instead of Blob
|
||||
it('a. Should create a blob from the canvas', function(done) {
|
||||
const canvas = document.createElement('canvas');
|
||||
sinon.stub(window, 'html2canvas').resolves(canvas);
|
||||
sinon.stub(canvas, 'toBlob').callsArgWith(0, new Blob());
|
||||
$button.click();
|
||||
html2canvas.restore();
|
||||
expect(canvas.toBlob.called).to.be.true;
|
||||
canvas.toBlob.restore();
|
||||
});
|
||||
*/
|
||||
|
||||
it('b. Should generate a Base64 PNG string when saving is confirmed', function(done) {
|
||||
const consoleStub = sinon.stub(console, 'log');
|
||||
$button.click();
|
||||
setTimeout(() => {
|
||||
expect(consoleStub.callCount).to.equal(2);
|
||||
const base64String = consoleStub.getCall(0).args[1];
|
||||
expect(base64String).to.be.a('string');
|
||||
expect(base64String).to.match(/^data:image\/png;base64,/);
|
||||
consoleStub.restore();
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('7. Download Functionality', function() {
|
||||
let createElementStub, appendChildStub, removeChildStub, html2canvasStub;
|
||||
let originalJQuery, jQueryStub, eachStub, originalCreateElement;
|
||||
|
||||
beforeEach(function() {
|
||||
originalJQuery = window.$;
|
||||
originalCreateElement = document.createElement;
|
||||
|
||||
jQueryStub = sinon.stub();
|
||||
eachStub = sinon.stub();
|
||||
jQueryStub.returns({
|
||||
each: eachStub,
|
||||
attr: sinon.stub(),
|
||||
val: sinon.stub(),
|
||||
addClass: sinon.stub(),
|
||||
removeClass: sinon.stub()
|
||||
});
|
||||
window.$ = jQueryStub;
|
||||
|
||||
html2canvasStub = sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
|
||||
createElementStub = sinon.stub(document, 'createElement').callsFake(function(tagName) {
|
||||
if (tagName === 'a') {
|
||||
return {
|
||||
style: {},
|
||||
href: '',
|
||||
download: '',
|
||||
click: sinon.spy(),
|
||||
textContent: ''
|
||||
};
|
||||
}
|
||||
return originalCreateElement.call(document, tagName);
|
||||
});
|
||||
appendChildStub = sinon.stub(document.body, 'appendChild');
|
||||
removeChildStub = sinon.stub(document.body, 'removeChild');
|
||||
|
||||
sinon.stub(URL, 'createObjectURL').returns('blob:http://example.com/test');
|
||||
sinon.stub(URL, 'revokeObjectURL');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sinon.restore();
|
||||
window.$ = originalJQuery;
|
||||
document.createElement = originalCreateElement;
|
||||
});
|
||||
|
||||
it('a. Should create a temporary anchor element for download', function() {
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect(html2canvasStub.calledWith(document.body)).to.be.true;
|
||||
expect(createElementStub.calledWith('a')).to.be.true;
|
||||
expect(appendChildStub.calledOnce).to.be.true;
|
||||
expect(removeChildStub.calledOnce).to.be.true;
|
||||
|
||||
const aElement = appendChildStub.getCall(0).args[0];
|
||||
expect(aElement.href).to.include('data:image/png;base64');
|
||||
expect(aElement.download).to.equal('screenshot.png');
|
||||
});
|
||||
});
|
||||
|
||||
it('b. Should click the temporary anchor element programmatically', function() {
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
console.log("createElementStub: ", createElementStub, createElementStub.calledOnce);
|
||||
expect(createElementStub.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('c. Should remove the temporary anchor after download', function(done) {
|
||||
$button.click();
|
||||
setTimeout(() => {
|
||||
expect(removeChildStub.calledOnce).to.be.true;
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('8. Cross-browser Compatibility', function() {
|
||||
it('a. Should work in Chrome-like environments', function() {
|
||||
// Assuming we're running tests in a Chrome-like environment
|
||||
expect(() => $button.screenshotButton()).to.not.throw();
|
||||
});
|
||||
|
||||
// Additional browser-specific tests would go here
|
||||
// These might need to be run in different environments or with browser mocks
|
||||
});
|
||||
|
||||
describe('9. Performance', function() {
|
||||
it('a. Should capture and generate screenshot within acceptable time', function(done) {
|
||||
this.timeout(5000);
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
$button.click();
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
expect(duration).to.be.below(1000);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('10. Error Handling', function() {
|
||||
var html2canvasStub, consoleErrorStub;
|
||||
|
||||
beforeEach(function() {
|
||||
consoleErrorStub = sandbox.stub(console, 'error');
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
if (html2canvasStub && html2canvasStub.restore) {
|
||||
html2canvasStub.restore();
|
||||
}
|
||||
if (consoleErrorStub && consoleErrorStub.restore) {
|
||||
consoleErrorStub.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it('a. Should log error when html2canvas is not loaded', function(done) {
|
||||
const originalHtml2Canvas = window.html2canvas;
|
||||
delete window.html2canvas;
|
||||
|
||||
$button.click();
|
||||
|
||||
expect(consoleErrorStub.calledOnce).to.be.true;
|
||||
expect(consoleErrorStub.args[0][0]).to.include('html2canvas is not loaded');
|
||||
|
||||
window.html2canvas = originalHtml2Canvas;
|
||||
done();
|
||||
});
|
||||
|
||||
it('b. Should handle errors during capture process', function() {
|
||||
let errorName = 'Capture failed';
|
||||
html2canvasStub = sinon.stub(window, 'html2canvas').rejects(new Error(errorName));
|
||||
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect(consoleErrorStub.called).to.be.true;
|
||||
expect(consoleErrorStub.firstCall.args[0]).to.be.an('error');
|
||||
expect(consoleErrorStub.firstCall.args[0].message).to.equal(errorName);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('11. Configuration Options', function() {
|
||||
beforeEach(function() {
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sinon.restore();
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
it('a. Should apply custom button text correctly', function() {
|
||||
const customText = 'Custom Capture Screen';
|
||||
$button.screenshotButton({ buttonText: customText });
|
||||
expect($button.text()).to.equal(customText);
|
||||
});
|
||||
|
||||
it('b. Should use custom filename for the downloaded image', function(done) {
|
||||
console.log("11.b");
|
||||
this.timeout(5000);
|
||||
const customFileName = 'custom-screenshot.png';
|
||||
$button.screenshotButton({ fileName: customFileName });
|
||||
const consoleStub = sinon.stub(console, 'log');
|
||||
|
||||
$button.click();
|
||||
setTimeout(() => {
|
||||
console.log("consoleStub: ", consoleStub);
|
||||
try {
|
||||
expect(consoleStub.callCount).to.equal(2);
|
||||
const call = consoleStub.getCall(1);
|
||||
console.log("call: ", call);
|
||||
const a = call.args[1];
|
||||
console.log("a: ", a);
|
||||
// expect(consoleLogStub.calledWith("adding button a: ", customFileName)).to.be.true;
|
||||
} catch (e) {
|
||||
console.log("error: ", e);
|
||||
}
|
||||
consoleStub.restore();
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('12. Accessibility', function() {
|
||||
it('a. Should have appropriate ARIA attributes', function() {
|
||||
expect($button.attr('role')).to.equal('button');
|
||||
expect($button.attr('aria-label')).to.equal(defaultOptions.buttonText);
|
||||
});
|
||||
|
||||
it('b. Should be keyboard accessible', function(done) {
|
||||
$button.on('keydown', function(e) {
|
||||
if (e.which === 13) { // Enter key
|
||||
done();
|
||||
}
|
||||
});
|
||||
const event = new KeyboardEvent('keydown', { 'keyCode': 13 });
|
||||
$button[0].dispatchEvent(event);
|
||||
});
|
||||
});
|
||||
|
||||
describe('13. Security', function() {
|
||||
const inputValue = 'sensitive-info';
|
||||
const textareaValue = 'confidential-data';
|
||||
const idInput = 'testInput1';
|
||||
const idTextarea = 'testInput2';
|
||||
|
||||
afterEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
it('a. Should not capture sensitive information in inputs and textareas', function(done) {
|
||||
$('<input id="' + idInput + '" type="text" value="' + inputValue + '">').appendTo('body');
|
||||
$('<textarea id="' + idTextarea + '">' + textareaValue + '</textarea>').appendTo('body');
|
||||
$button.click();
|
||||
const inputs = document.querySelectorAll('input, textarea');
|
||||
inputs.forEach(input => {
|
||||
expect(input.value).to.be.empty;
|
||||
});
|
||||
$('#' + idInput).remove();
|
||||
$('#' + idTextarea).remove();
|
||||
done();
|
||||
});
|
||||
|
||||
it('b. Should restore input and textarea values after capture', function() {
|
||||
|
||||
$('#' + idInput).remove();
|
||||
$('#' + idTextarea).remove();
|
||||
$('<input id="' + idInput + '" type="text" value="' + inputValue + '">').appendTo('body');
|
||||
$('<textarea id="' + idTextarea + '">' + textareaValue + '</textarea>').appendTo('body');
|
||||
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
let $input = $('#' + idInput);
|
||||
let $textarea = $('#' + idTextarea);
|
||||
expect($input.val()).to.equal(inputValue);
|
||||
expect($textarea.val()).to.equal(textareaValue);
|
||||
$input.remove();
|
||||
$textarea.remove();
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('14. CSS Interaction', function() {
|
||||
it('a. Should not break existing page styles', function() {
|
||||
const originalStyles = getComputedStyle(document.body);
|
||||
const newStyles = getComputedStyle(document.body);
|
||||
expect(originalStyles.cssText).to.equal(newStyles.cssText);
|
||||
});
|
||||
|
||||
it('b. Should apply correct button styling', function() {
|
||||
const buttonStyles = getComputedStyle($button[0]);
|
||||
expect(buttonStyles.display).to.not.equal('none');
|
||||
// Add more specific style checks as per your plugin's CSS
|
||||
});
|
||||
});
|
||||
|
||||
describe('15. jQuery Plugin Requirements', function() {
|
||||
it('a. Should return jQuery object for chaining', function() {
|
||||
const result = $button.screenshotButton();
|
||||
expect(result).to.equal($button);
|
||||
});
|
||||
|
||||
it('b. Should initialize plugin on multiple elements', function() {
|
||||
$('body').append('<div class="test-class"></div><div class="test-class"></div>');
|
||||
$('.test-class').screenshotButton();
|
||||
expect($('.test-class').length).to.equal(2);
|
||||
$('.test-class').remove();
|
||||
});
|
||||
});
|
||||
|
||||
describe('16. Memory Management', function() {
|
||||
it('a. Should not leak memory on repeated initialization and destruction', function() {
|
||||
const initialMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
for (let i = 0; i < 500; i++) {
|
||||
$button.screenshotButton();
|
||||
$button.empty();
|
||||
}
|
||||
const finalMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
expect(finalMemory - initialMemory).to.be.below(1000000); // 1 MB ish
|
||||
});
|
||||
|
||||
it('b. Should remove all created DOM elements after use', function() {
|
||||
$button.screenshotButton();
|
||||
const initialChildCount = $button[0].childElementCount;
|
||||
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect($button[0].childElementCount).to.equal(initialChildCount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('17. Responsiveness', function() {
|
||||
var html2canvasStub;
|
||||
|
||||
beforeEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
if (html2canvasStub && html2canvasStub.restore) {
|
||||
html2canvasStub.restore();
|
||||
}
|
||||
$button.width(100).height(50);
|
||||
});
|
||||
|
||||
it('a. Should behave correctly on different screen sizes', function() {
|
||||
const viewports = [
|
||||
{width: 320, height: 568}, // iPhone 5
|
||||
{width: 1024, height: 768}, // iPad
|
||||
{width: 1920, height: 1080} // Full HD
|
||||
];
|
||||
|
||||
viewports.forEach(size => {
|
||||
$button.width(size.width).height(size.height);
|
||||
$button.screenshotButton();
|
||||
expect($button.hasClass(flagIsHidden)).to.be.false;
|
||||
expect($button.width()).to.be.at.most(size.width);
|
||||
});
|
||||
});
|
||||
|
||||
it('', function() {});
|
||||
});
|
||||
});
|
||||
376
submission - backup/test_plugin_display_png_Mocha.js
Normal file
376
submission - backup/test_plugin_display_png_Mocha.js
Normal file
@@ -0,0 +1,376 @@
|
||||
|
||||
describe('Image Annotation Control', function() {
|
||||
var $annotatorPNG, $toolbox, $inputUploadImage, $buttonAddArrow, $buttonAddTextbox, $selectAddSymbol, $inputColourPicker, $buttonEraseMode, $buttonSaveImage, $containerAnnotation, $canvasAnnotation;
|
||||
|
||||
beforeEach(function() {
|
||||
$annotatorPNG = $(document).find("." + flagAnnotatorPNG);
|
||||
$annotatorPNG.annotatorPNG(annotatorSettings);
|
||||
$toolbox = $annotatorPNG.find("." + flagToolbox);
|
||||
$inputUploadImage = $toolbox.find("." + flagUploadImage);
|
||||
$buttonAddArrow = $toolbox.find("." + flagAddArrow);
|
||||
$buttonAddTextbox = $toolbox.find("." + flagAddTextbox);
|
||||
$selectAddSymbol = $toolbox.find("." + flagAddSymbol);
|
||||
$inputColourPicker = $toolbox.find("." + flagColourPicker);
|
||||
$buttonEraseMode = $toolbox.find("." + flagEraseMode);
|
||||
$buttonSaveImage = $toolbox.find("." + flagSaveImage);
|
||||
$containerAnnotation = $annotatorPNG.find("." + flagContainerAnnotation);
|
||||
$canvasAnnotation = $containerAnnotation.find("." + flagCanvasAnnotation);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
let canvas = $($containerAnnotation.children()[0].children[0]).clone();
|
||||
$containerAnnotation.empty();
|
||||
$containerAnnotation.append(canvas);
|
||||
});
|
||||
|
||||
describe('1. Initialization and Setup', function() {
|
||||
it('a. Should initialize correctly on a given DOM element', async function() {
|
||||
expect($annotatorPNG).to.exist;
|
||||
});
|
||||
|
||||
it('b. Should create canvas with correct dimensions', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
expect(canvas.width).to.equal(800);
|
||||
expect(canvas.height).to.equal(600);
|
||||
expect($canvasAnnotation.attr('width')).to.equal('800');
|
||||
expect($canvasAnnotation.attr('height')).to.equal('600');
|
||||
});
|
||||
|
||||
it('c. Should have all UI elements present with correct values after initialization', async function() {
|
||||
expect($annotatorPNG.find("." + flagToolbox)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagUploadImage)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagAddArrow)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagAddTextbox)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagAddSymbol)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagColourPicker)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagEraseMode)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagSaveImage)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagContainerAnnotation)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagCanvasAnnotation).length > 0).to.be.true;
|
||||
/*
|
||||
expect($toolbox).to.exist;
|
||||
expect($inputUploadImage).to.exist;
|
||||
expect($buttonAddArrow).to.exist;
|
||||
expect($buttonAddTextbox).to.exist;
|
||||
expect($selectAddSymbol).to.exist;
|
||||
expect($selectAddSymbol.find("option")).to.exist;
|
||||
expect($inputColourPicker).to.exist;
|
||||
expect($buttonEraseMode).to.exist;
|
||||
expect($buttonSaveImage).to.exist;
|
||||
expect($containerAnnotation).to.exist;
|
||||
expect($canvasAnnotation).to.exist;
|
||||
*/
|
||||
|
||||
expect($inputUploadImage.val()).to.equal('');
|
||||
expect($inputColourPicker.val()).to.equal('#ff0000');
|
||||
expect($buttonSaveImage.text().length > 0).to.be.true; // For unkown settings argument value
|
||||
expect($buttonSaveImage.text()).to.equal('Save Image');
|
||||
expect($canvasAnnotation.data(keyFabric)).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('2. Image Upload and Display', function() {
|
||||
it('a. Should successfully upload a PNG file', function(done) {
|
||||
const file = new File([''], 'screenshot (16).png', { type: 'image/png' });
|
||||
const fileInput = $inputUploadImage[0];
|
||||
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(file);
|
||||
fileInput.files = dataTransfer.files;
|
||||
|
||||
$(fileInput).trigger('change');
|
||||
|
||||
setTimeout(() => {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
if (canvas) console.log("canvas.backgroundImage: " + canvas.backgroundImage);
|
||||
expect(canvas.backgroundImage).to.exist;
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('b. Should display the uploaded image correctly on the canvas', function(done) {
|
||||
const file = new File([''], 'screenshot (16).png', { type: 'image/png' });
|
||||
const fileInput = $inputUploadImage[0];
|
||||
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(file);
|
||||
fileInput.files = dataTransfer.files;
|
||||
|
||||
$(fileInput).trigger('change');
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
let context = canvas.getContext('2d');
|
||||
let imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
expect(canvas.width).to.equal(800);
|
||||
expect(canvas.height).to.equal(600);
|
||||
expect(imageData.data.length).to.equal(800 * 600 * 4);
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Error during canvas initialisation: " + e);
|
||||
done(e);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('d. Should handle invalid file types', async function() {
|
||||
const file = new File([''], 'test.jpg', { type: 'image/jpeg' });
|
||||
const fileInput = $inputUploadImage[0];
|
||||
|
||||
const confirmStub = sinon.stub(window, 'alert').returns(true);
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(file);
|
||||
fileInput.files = dataTransfer.files;
|
||||
|
||||
expect(() => $(fileInput).trigger('change')).to.throw();
|
||||
confirmStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('3. Arrow Addition', function() {
|
||||
it('a. Should create a new arrow on the canvas when "Add Arrow" is clicked', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
console.log("canvas: " + canvas);
|
||||
await waitForClick($buttonAddArrow);
|
||||
console.log("canvas after: " + canvas);
|
||||
expect(canvas.getObjects()).to.have.lengthOf(1);
|
||||
expect(canvas.getObjects()[0].type).to.equal('path');
|
||||
});
|
||||
|
||||
it('b. Should create arrow with correct default properties', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddArrow);
|
||||
const arrow = canvas.getObjects()[0];
|
||||
expect(arrow.fill).to.equal('#ff0000');
|
||||
expect(arrow.stroke).to.equal('#ff0000');
|
||||
expect(arrow.strokeWidth).to.equal(2);
|
||||
});
|
||||
|
||||
it('c. Should allow multiple arrows to be added to the canvas', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddArrow);
|
||||
await waitForClick($buttonAddArrow);
|
||||
expect(canvas.getObjects()).to.have.lengthOf(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('4. Textbox Addition', function() {
|
||||
it('a. Should create a new textbox on the canvas when "Add Textbox" is clicked', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
expect(canvas.getObjects()).to.have.lengthOf(1);
|
||||
expect(canvas.getObjects()[0].type).to.equal('textbox');
|
||||
});
|
||||
|
||||
it('b. Should create textbox with correct default properties', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
const textbox = canvas.getObjects()[0];
|
||||
expect(textbox.fontSize).to.equal(20);
|
||||
expect(textbox.left).to.equal(100);
|
||||
expect(textbox.top).to.equal(100);
|
||||
});
|
||||
|
||||
it('c. Should allow text to be entered and edited in the textbox', async function() {
|
||||
this.timeout(10000);
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
const textbox = canvas.getObjects()[0];
|
||||
console.log("textbox: " + textbox);
|
||||
textbox.text = 'New Text';
|
||||
console.log("textbox: " + textbox);
|
||||
canvas.renderAll();
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
expect(textbox.text).to.equal('New Text');
|
||||
});
|
||||
|
||||
it('d. Should allow multiple textboxes to be added to the canvas', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
expect(canvas.getObjects()).to.have.lengthOf(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('5. Element Manipulation', function() {
|
||||
it('a. Should allow arrows to be selected, moved, resized, and rotated', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddArrow);
|
||||
const arrow = canvas.getObjects()[0];
|
||||
arrow.set({ left: 150, top: 150, scaleX: 2, angle: 45 });
|
||||
canvas.renderAll();
|
||||
expect(arrow.left).to.equal(150);
|
||||
expect(arrow.top).to.equal(150);
|
||||
expect(arrow.scaleX).to.equal(2);
|
||||
expect(arrow.angle).to.equal(45);
|
||||
});
|
||||
|
||||
it('b. Should allow textboxes to be selected, moved, resized, and rotated', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
const textbox = canvas.getObjects()[0];
|
||||
textbox.set({ left: 150, top: 150, scaleX: 2, angle: 45 });
|
||||
canvas.renderAll();
|
||||
expect(textbox.left).to.equal(150);
|
||||
expect(textbox.top).to.equal(150);
|
||||
expect(textbox.scaleX).to.equal(2);
|
||||
expect(textbox.angle).to.equal(45);
|
||||
});
|
||||
});
|
||||
|
||||
describe('6. Erasing Elements', function() {
|
||||
afterEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
$annotatorPNG.annotatorPNG(annotatorSettings);
|
||||
});
|
||||
|
||||
it('a. Should remove the currently selected arrow when "Erase Element" is clicked', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddArrow);
|
||||
canvas.setActiveObject(canvas.getObjects()[0]);
|
||||
await waitForClick($buttonEraseMode);
|
||||
expect(canvas.getObjects()).to.have.lengthOf(0);
|
||||
});
|
||||
|
||||
it('b. Should remove the currently selected textbox when "Erase Element" is clicked', function(done) {
|
||||
this.timeout(10000);
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
waitForClick($buttonAddTextbox).then(() => {
|
||||
canvas.setActiveObject(canvas.getObjects()[0]);
|
||||
canvas.renderAll();
|
||||
waitForClick($buttonEraseMode).then(() => {
|
||||
canvas.renderAll();
|
||||
expect(canvas.getObjects()).to.have.lengthOf(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('c. Should not affect other elements on the canvas when erasing', function(done) {
|
||||
this.timeout(10000);
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
waitForClick($buttonAddArrow).then(() => {
|
||||
console.log("canvas.getObjects()[0]: ", canvas.getObjects()[0]);
|
||||
waitForClick($buttonAddTextbox).then(() => {
|
||||
canvas.setActiveObject(canvas.getObjects()[0]);
|
||||
waitForClick($buttonEraseMode).then(() => {
|
||||
expect(canvas.getObjects()).to.have.lengthOf(1);
|
||||
console.log("canvas.getObjects()[0]: ", canvas.getObjects()[0]);
|
||||
expect(canvas.getObjects()[0].text).to.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('7. Saving Functionality', function() {
|
||||
it('a. Should trigger confirmation dialog when "Save" is clicked', async function() {
|
||||
const confirmStub = sinon.stub(window, 'confirm').returns(true);
|
||||
const alertStub = sinon.stub(window, 'alert').returns(true);
|
||||
await waitForClick($buttonSaveImage);
|
||||
expect(confirmStub.calledOnce).to.be.true;
|
||||
confirmStub.restore();
|
||||
alertStub.restore();
|
||||
});
|
||||
|
||||
it('b. Should prevent saving when confirmation dialog is canceled', async function() {
|
||||
const confirmStub = sinon.stub(window, 'confirm').returns(false);
|
||||
const consoleStub = sinon.stub(console, 'log');
|
||||
await waitForClick($buttonSaveImage);
|
||||
expect(consoleStub.called).to.be.false;
|
||||
confirmStub.restore();
|
||||
consoleStub.restore();
|
||||
});
|
||||
|
||||
it('c. Should generate a Base64 PNG string when saving is confirmed', async function() {
|
||||
sinon.stub(window, 'confirm').returns(true);
|
||||
const alertStub = sinon.stub(window, 'alert').returns(true);
|
||||
const consoleStub = sinon.stub(console, 'log');
|
||||
await waitForClick($buttonSaveImage);
|
||||
expect(consoleStub.calledOnce).to.be.true;
|
||||
const base64String = consoleStub.getCall(0).args[1];
|
||||
expect(base64String).to.be.a('string');
|
||||
expect(base64String).to.match(/^data:image\/png;base64,/);
|
||||
window.confirm.restore();
|
||||
consoleStub.restore();
|
||||
alertStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('8. Edge Cases and Error Handling', function() {
|
||||
it('a. Should handle initialization on invalid DOM element', async function() {
|
||||
expect(() => $('#non-existent-element').imageAnnotator()).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('9. Performance', function() {
|
||||
it('a. Should capture and generate screenshot within acceptable time', function(done) {
|
||||
this.timeout(5000);
|
||||
|
||||
const startTime = performance.now();
|
||||
const alertStub = sinon.stub(window, 'alert').returns(true);
|
||||
const confirmStub = sinon.stub(window, 'confirm').returns(true);
|
||||
|
||||
$buttonSaveImage.click();
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
expect(duration).to.be.below(1000);
|
||||
alertStub.restore();
|
||||
confirmStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('10. Accessibility', function() {
|
||||
it('a. Should have appropriate ARIA labels for all interactive elements', async function() {
|
||||
expect($inputUploadImage.attr('aria-label').length > 0).to.be.true;
|
||||
expect($buttonAddArrow.attr('aria-label').length > 0).to.be.true;
|
||||
expect($buttonAddTextbox.attr('aria-label').length > 0).to.be.true;
|
||||
expect($selectAddSymbol.attr('aria-label').length > 0).to.be.true;
|
||||
expect($inputColourPicker.attr('aria-label').length > 0).to.be.true;
|
||||
expect($buttonEraseMode.attr('aria-label').length > 0).to.be.true;
|
||||
expect($buttonSaveImage.attr('aria-label').length > 0).to.be.true;
|
||||
expect($canvasAnnotation.attr('aria-label').length > 0).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('11. jQuery Plugin Requirements', function() {
|
||||
it('a. Should return jQuery object for chaining', function() {
|
||||
const result = $annotatorPNG.annotatorPNG(annotatorSettings);
|
||||
expect(result).to.equal($annotatorPNG);
|
||||
});
|
||||
|
||||
it('b. Should initialize plugin on multiple elements', function() {
|
||||
$('body').append('<div class="test-class"></div><div class="test-class"></div>');
|
||||
$('.test-class').annotatorPNG(annotatorSettings);
|
||||
expect($('.test-class').length).to.equal(2);
|
||||
$('.test-class').remove();
|
||||
});
|
||||
});
|
||||
|
||||
describe('12. Memory Management', function() {
|
||||
it('a. Should not leak memory on repeated initialization and destruction', function() {
|
||||
const initialMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
for (let i = 0; i < 500; i++) {
|
||||
$annotatorPNG.annotatorPNG(annotatorSettings);
|
||||
$canvasAnnotation.data(keyFabric).dispose();
|
||||
}
|
||||
const finalMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
expect(finalMemory - initialMemory).to.be.below(1000000); // 1 MB ish
|
||||
});
|
||||
|
||||
it('b. Should remove all created DOM elements after use', function() {
|
||||
const initialChildCount = $annotatorPNG[0].childElementCount;
|
||||
const initialCanvasCount = $containerAnnotation[0].childElementCount;
|
||||
|
||||
$annotatorPNG.annotatorPNG(annotatorSettings);
|
||||
|
||||
expect($annotatorPNG[0].childElementCount).to.equal(initialChildCount);
|
||||
expect($containerAnnotation[0].childElementCount).to.equal(initialCanvasCount);
|
||||
});
|
||||
});
|
||||
});
|
||||
113
submission - backup/view_capture_screen.html
Normal file
113
submission - backup/view_capture_screen.html
Normal file
@@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Simple Layout Template</title>
|
||||
|
||||
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
|
||||
<script src="shared.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.3.2/mocha.min.css">
|
||||
<link rel="stylesheet" href="capture-screen.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Website Title</h1>
|
||||
</header>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<main>
|
||||
<h2>Main Content</h2>
|
||||
<p>This is where the main content of your page goes. You can add articles, blog posts, or any other primary content here.</p>
|
||||
<input type="text" value="Test 1" />
|
||||
<input type="number" value="2" />
|
||||
<textarea>Test 3</textarea>
|
||||
|
||||
<!-- Start of Screen Capture Plugin HTML -->
|
||||
<button class="capture-screen" role="button">Capture Screen</button>
|
||||
<!-- End of Screen Capture Plugin HTML -->
|
||||
|
||||
<div id="notification-widget" class="modal">
|
||||
<h3>Notifications</h3>
|
||||
<p>You have 3 new messages.</p>
|
||||
</div>
|
||||
<a href="#notification-widget" id="notification-toggle" class="widget-toggle">Toggle Notifications</a>
|
||||
|
||||
<div id="chat-widget" class="modal">
|
||||
<h3>Chat</h3>
|
||||
<p>Chat content goes here...</p>
|
||||
</div>
|
||||
<a href="#chat-widget" id="chat-toggle" class="widget-toggle">Toggle Chat</a>
|
||||
|
||||
<div id="settings-widget" class="modal">
|
||||
<h3>Quick Settings</h3>
|
||||
<p>Adjust your settings here...</p>
|
||||
</div>
|
||||
<a href="#settings-widget" id="settings-toggle" class="widget-toggle">Toggle Settings</a>
|
||||
|
||||
<div id="mocha"></div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div id="modalTest" class="modal">
|
||||
<div class="modal-content">
|
||||
<h2>Test Modal</h2>
|
||||
<p>This is a test modal.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.3.2/mocha.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.3.4/chai.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/10.0.0/sinon.min.js"></script>
|
||||
<div id="mocha"></div>
|
||||
|
||||
<script class="mocha-init">
|
||||
mocha.setup({
|
||||
ui: 'bdd',
|
||||
globals: [
|
||||
'flagInitialised',
|
||||
'defaultOptions', 'flagCaptureScreen',
|
||||
'idModalNotifications', 'idButtonToggleModalNotifications',
|
||||
'idModalChat', 'idButtonToggleModalChat',
|
||||
'idModalSettings', 'idButtonToggleModalSettings',
|
||||
'idModalTest'
|
||||
]
|
||||
});
|
||||
mocha.checkLeaks();
|
||||
const expect = chai.expect;
|
||||
</script>
|
||||
|
||||
<script src="plugin_capture_screen.js"></script>
|
||||
<script src="test_plugin_capture_screen_Mocha.js"></script>
|
||||
|
||||
<script class="mocha-exec">
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
<!-- Start of Screen Capture Plugin JavaScript -->
|
||||
<script>
|
||||
var defaultOptions = {
|
||||
buttonText: "Capture Screen",
|
||||
fileName: "screenshot.png",
|
||||
};
|
||||
var flagCaptureScreen = "capture-screen";
|
||||
var idModalNotifications = "#notification-widget";
|
||||
var idButtonToggleModalNotifications = "#notification-toggle";
|
||||
var idModalChat = "#chat-widget";
|
||||
var idButtonToggleModalChat = "#chat-toggle";
|
||||
var idModalSettings = "#settings-widget";
|
||||
var idButtonToggleModalSettings = "#settings-toggle";
|
||||
var idModalTest = "#modalTest";
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
$(".capture-screen").screenshotButton(defaultOptions);
|
||||
hookupTestModals();
|
||||
});
|
||||
</script>
|
||||
<!-- End of Screen Capture Plugin JavaScript -->
|
||||
</html>
|
||||
84
submission - backup/view_display_png.html
Normal file
84
submission - backup/view_display_png.html
Normal file
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Image Annotation Control</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.3.2/mocha.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.3.4/chai.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/10.0.1/sinon.min.js"></script>
|
||||
<script src="shared.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.3.2/mocha.min.css">
|
||||
<link rel="stylesheet" href="display-png.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Start of PNG Annotation HTML -->
|
||||
<div class="annotator-png">
|
||||
<div class="annotator-png-toolbox">
|
||||
<input type="file" class="annotator-png-upload-image" accept="image/png" aria-label="Upload image">
|
||||
<button class="annotator-png-add-arrow" aria-label="Add arrow">Add Arrow</button>
|
||||
<button class="annotator-png-add-textbox" aria-label="Add textbox">Add Textbox</button>
|
||||
<select class="annotator-png-add-symbol" aria-label="Select and add symbol">
|
||||
<option value="">Select Symbol</option>
|
||||
</select>
|
||||
<input type="color" class="annotator-png-colour-picker" value="#ff0000" aria-label="Colour picker">
|
||||
<button class="annotator-png-erase-mode" aria-label="Toggle erase mode">Erase Mode</button>
|
||||
<button class="annotator-png-save-image" aria-label="Save annotated image">Save Image</button>
|
||||
</div>
|
||||
<div class="annotator-png-container-annotation" style="height: 600px; width: 800px;">
|
||||
<canvas class="annotator-png-canvas-annotation" height="600px" width="800px" aria-label="Image canvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End of PNG Annnotation HTML -->
|
||||
|
||||
<div id="mocha"></div>
|
||||
<script class="mocha-init">
|
||||
mocha.setup({
|
||||
ui: 'bdd',
|
||||
globals: [
|
||||
'flagAnnotatorPNG', 'flagToolbox', 'flagContainer', 'flagContainerAnnotation',
|
||||
'flagUploadImage', 'flagAddArrow', 'flagAddTextbox', 'flagAddSymbol',
|
||||
'flagColourPicker', 'flagEraseMode', 'flagSaveImage', 'flagCanvasAnnotation',
|
||||
'keyFabric', 'keyIsEraseMode', 'annotatorSettings', 'flagInitialised'
|
||||
]
|
||||
});
|
||||
mocha.checkLeaks();
|
||||
const expect = chai.expect;
|
||||
</script>
|
||||
|
||||
<script src="plugin_display_png,js"></script>
|
||||
<script src="test_plugin_display_png_Mocha.js"></script>
|
||||
|
||||
<script class="mocha-exec">
|
||||
// mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
<!-- Start of PNG Annotation JavaScript -->
|
||||
<script>
|
||||
var flagAnnotatorPNG = "annotator-png";
|
||||
var flagToolbox = "annotator-png-toolbox";
|
||||
var flagContainer = "annotator-png-container";
|
||||
var flagContainerAnnotation = "annotator-png-container-annotation";
|
||||
var flagUploadImage = "annotator-png-upload-image";
|
||||
var flagAddArrow = "annotator-png-add-arrow";
|
||||
var flagAddTextbox = "annotator-png-add-textbox";
|
||||
var flagAddSymbol = "annotator-png-add-symbol";
|
||||
var flagColourPicker = "annotator-png-colour-picker";
|
||||
var flagEraseMode = "annotator-png-erase-mode";
|
||||
var flagSaveImage = "annotator-png-save-image";
|
||||
var flagCanvasAnnotation = "annotator-png-canvas-annotation";
|
||||
var keyFabric = "fabric";
|
||||
var keyIsEraseMode = "isEraseMode";
|
||||
|
||||
var annotatorSettings = {};
|
||||
$(document).ready(function() {
|
||||
$("." + flagAnnotatorPNG).annotatorPNG(annotatorSettings);
|
||||
});
|
||||
</script>
|
||||
<!-- End of PNG Annotation JavaScript -->
|
||||
</html>
|
||||
70
submission/README
Normal file
70
submission/README
Normal file
@@ -0,0 +1,70 @@
|
||||
# Screen Capture and Image Annotation Project
|
||||
|
||||
This project provides two main functionalities:
|
||||
1. Screen Capture: A pluggable web control to capture the current browser viewport.
|
||||
2. Image Annotation: A tool to annotate PNG images with arrows, textboxes, and symbols.
|
||||
|
||||
## Features
|
||||
|
||||
### Screen Capture
|
||||
- Capture the current browser viewport as a PNG image.
|
||||
- Exclude sensitive information from inputs and textareas.
|
||||
- Capture modals and floating elements.
|
||||
- Save the captured image as a PNG file.
|
||||
|
||||
### Image Annotation
|
||||
- Upload and display PNG images.
|
||||
- Add arrows, textboxes, and symbols to the image.
|
||||
- Erase annotated elements.
|
||||
- Save the annotated image as a Base64 PNG string.
|
||||
|
||||
## Usage
|
||||
|
||||
### Screen Capture
|
||||
|
||||
Include the necessary files in your HTML:
|
||||
|
||||
```html
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
|
||||
<script src="plugin_capture_screen.js"></script>
|
||||
<link rel="stylesheet" href="capture-screen.css">
|
||||
```
|
||||
|
||||
Initialize the plugin on a button:
|
||||
|
||||
```javascript
|
||||
$('.capture-screen').screenshotButton();
|
||||
```
|
||||
|
||||
### Image Annotation
|
||||
|
||||
Include the necessary files in your HTML:
|
||||
|
||||
```html
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="plugin_display_png.js"></script>
|
||||
<link rel="stylesheet" href="display-png.css">
|
||||
```
|
||||
|
||||
Initialize the plugin on a container:
|
||||
|
||||
```javascript
|
||||
$('.annotator-png').annotatorPNG();
|
||||
```
|
||||
|
||||
Include any necessary symbols for adding to the canvas as annotations:
|
||||
1. Add files to symbols folder
|
||||
2. Add file names to symbols list on line 51 in plugin_annotate_PNG.js
|
||||
|
||||
## Testing
|
||||
|
||||
To run the tests:
|
||||
1. Open the views in your browser.
|
||||
|
||||
To disable the tests:
|
||||
1. Comment out the line "mocha.run();" in the testing script header in the view
|
||||
|
||||
## Known bugs
|
||||
- Colour picker button must be clicked to set colour of active canvas object after selecting colour
|
||||
15
submission/annotate-PNG.css
Normal file
15
submission/annotate-PNG.css
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
.annotator-png-container-annotation {
|
||||
position: relative;
|
||||
border: 1px solid #ccc;
|
||||
margin: 20px auto;
|
||||
}
|
||||
.annotator-png-canvas-annotation {
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.annotator-png-toolbox {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
101
submission/capture-screen.css
Normal file
101
submission/capture-screen.css
Normal file
@@ -0,0 +1,101 @@
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
header {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
main {
|
||||
flex: 3;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Widgets */
|
||||
.modal {
|
||||
position: fixed;
|
||||
background-color: #f0f0f0;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
/*
|
||||
display: none;
|
||||
*/
|
||||
}
|
||||
.widget-toggle {
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#notification-widget {
|
||||
top: 100px;
|
||||
right: 20px;
|
||||
width: 250px;
|
||||
}
|
||||
#notification-toggle {
|
||||
top: 100px;
|
||||
right: 20px;
|
||||
}
|
||||
#chat-widget {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 300px;
|
||||
height: 400px;
|
||||
}
|
||||
#chat-toggle {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
#settings-widget {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 200px;
|
||||
}
|
||||
#settings-toggle {
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
}
|
||||
.modal:target {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Testing */
|
||||
|
||||
#modalTest {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 80%;
|
||||
}
|
||||
274
submission/plugin_annotate_PNG,js
Normal file
274
submission/plugin_annotate_PNG,js
Normal file
@@ -0,0 +1,274 @@
|
||||
|
||||
(function($) {
|
||||
$.fn.annotatorPNG = function(options) {
|
||||
var settings = $.extend({
|
||||
heightCanvas: "600px",
|
||||
widthCanvas: "800px",
|
||||
textButtonSave: "Save Image",
|
||||
}, options);
|
||||
|
||||
|
||||
function getCanvas() {
|
||||
let $annotatorPNG = $(document).find("." + flagAnnotatorPNG);
|
||||
let $canvasAnnotation = $($annotatorPNG.find("canvas." + flagCanvasAnnotation)[0]);
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
function deleteSelectedObjects() {
|
||||
let canvas = getCanvas();
|
||||
const activeObject = canvas.getActiveObject();
|
||||
if (activeObject) {
|
||||
console.log("active object type:", activeObject.type);
|
||||
if (activeObject.type === 'activeSelection') {
|
||||
activeObject.forEachObject(function(obj) {
|
||||
canvas.remove(obj);
|
||||
});
|
||||
canvas.discardActiveObject();
|
||||
} else {
|
||||
canvas.remove(activeObject);
|
||||
}
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
}
|
||||
|
||||
return this.each(function() {
|
||||
let $annotatorPNG = $(this);
|
||||
let $toolbox = $annotatorPNG.find("." + flagToolbox);
|
||||
let $inputUploadImage = $toolbox.find("." + flagUploadImage);
|
||||
let $buttonAddArrow = $toolbox.find("." + flagAddArrow);
|
||||
let $buttonAddTextbox = $toolbox.find("." + flagAddTextbox);
|
||||
let $selectAddSymbol = $toolbox.find("." + flagAddSymbol);
|
||||
let $inputColourPicker = $toolbox.find("." + flagColourPicker);
|
||||
let $buttonEraseMode = $toolbox.find("." + flagEraseMode);
|
||||
let $buttonSaveImage = $toolbox.find("." + flagSaveImage);
|
||||
let $containerAnnotation = $annotatorPNG.find("." + flagContainerAnnotation);
|
||||
let $canvasAnnotation = $containerAnnotation.find("." + flagCanvasAnnotation);
|
||||
|
||||
// Default values
|
||||
$inputUploadImage.val('');
|
||||
|
||||
const symbols = ['Orange Arrow.png'];
|
||||
symbols.forEach(symbol => {
|
||||
$selectAddSymbol.append($("<option>", {
|
||||
value: symbol,
|
||||
text: symbol.replace('.png', ''),
|
||||
}));
|
||||
});
|
||||
|
||||
$inputColourPicker.val('#ff0000');
|
||||
|
||||
$buttonEraseMode.textContent = "Erase Mode";
|
||||
$annotatorPNG.data(keyIsEraseMode, false);
|
||||
|
||||
$buttonSaveImage.text(settings.textButtonSave);
|
||||
|
||||
$canvasAnnotation.css({
|
||||
height: settings.heightCanvas,
|
||||
width: settings.widthCanvas,
|
||||
});
|
||||
console.log("canvas: ", $canvasAnnotation[0]);
|
||||
var canvas = new fabric.Canvas($canvasAnnotation[0], {
|
||||
// containerClass: flagContainerAnnotation,
|
||||
});
|
||||
canvas.clear();
|
||||
canvas.selection = true;
|
||||
$canvasAnnotation.data(keyFabric, canvas);
|
||||
|
||||
// Triggers
|
||||
if (!$inputUploadImage.hasClass(flagInitialised)) {
|
||||
$inputUploadImage.addClass(flagInitialised);
|
||||
$inputUploadImage.on("change", function(event) {
|
||||
console.log("File uploaded:", event.target.files[0]);
|
||||
const file = event.target.files[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
if (!(file.type == 'image/png')) {
|
||||
alert("Please upload a PNG file.");
|
||||
throw new Error("Invalid file type.");
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
let canvas = getCanvas();
|
||||
reader.onload = function(eventReader) {
|
||||
fabric.Image.fromURL(eventReader.target.result, function(image) {
|
||||
canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas), {
|
||||
scaleX: settings.widthCanvas.replace('px', '') / image.width,
|
||||
scaleY: settings.heightCanvas.replace('px', '') / image.height,
|
||||
});
|
||||
});
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
if (!$buttonAddArrow.hasClass(flagInitialised)) {
|
||||
$buttonAddArrow.addClass(flagInitialised);
|
||||
$buttonAddArrow.on("click", function() {
|
||||
console.log("Add Arrow clicked");
|
||||
let canvas = getCanvas();
|
||||
const arrow = new fabric.Path('M 0 0 L 200 100', {
|
||||
fill: $inputColourPicker.val(),
|
||||
stroke: $inputColourPicker.val(),
|
||||
strokeWidth: 2,
|
||||
left: 100,
|
||||
top: 100,
|
||||
selectable: true,
|
||||
});
|
||||
canvas.add(arrow);
|
||||
canvas.renderAll();
|
||||
});
|
||||
}
|
||||
|
||||
if (!$buttonAddTextbox.hasClass(flagInitialised)) {
|
||||
$buttonAddTextbox.addClass(flagInitialised);
|
||||
$buttonAddTextbox.on("click", function() {
|
||||
console.log("Add Textbox clicked");
|
||||
let canvas = getCanvas();
|
||||
const textbox = new fabric.Textbox('Type here', {
|
||||
left: 100,
|
||||
top: 100,
|
||||
width: 150,
|
||||
fontSize: 20,
|
||||
fill: $inputColourPicker.val(),
|
||||
selectable: true,
|
||||
});
|
||||
canvas.add(textbox);
|
||||
canvas.renderAll();
|
||||
});
|
||||
}
|
||||
|
||||
if (!$selectAddSymbol.hasClass(flagInitialised)) {
|
||||
$selectAddSymbol.addClass(flagInitialised);
|
||||
$selectAddSymbol.on("change", function() {
|
||||
console.log("Add Symbol changed:", this.value);
|
||||
if (this.value) {
|
||||
let canvas = getCanvas();
|
||||
fabric.Image.fromURL(`symbols/${this.value}`, function(image) {
|
||||
image.set({
|
||||
left: 100,
|
||||
top: 100,
|
||||
scaleX: 0.5,
|
||||
scaleY: 0.5,
|
||||
});
|
||||
canvas.add(image);
|
||||
});
|
||||
this.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!$inputColourPicker.hasClass(flagInitialised)) {
|
||||
$inputColourPicker.addClass(flagInitialised);
|
||||
$inputColourPicker.on("change", function() {
|
||||
console.log("Colour Picker changed:", this.value);
|
||||
let canvas = getCanvas();
|
||||
canvas.renderAll();
|
||||
const activeObject = canvas.getActiveObject();
|
||||
if (activeObject) {
|
||||
if (activeObject.type === 'textbox') {
|
||||
activeObject.set('fill', this.value);
|
||||
} else {
|
||||
activeObject.set('fill', this.value);
|
||||
activeObject.set('stroke', this.value);
|
||||
}
|
||||
canvas.renderAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!$buttonEraseMode.hasClass(flagInitialised)) {
|
||||
$buttonEraseMode.addClass(flagInitialised);
|
||||
$buttonEraseMode.data(keyIsEraseMode, false);
|
||||
$buttonEraseMode.on("click", function() {
|
||||
console.log("Erase Mode clicked");
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
isEraseMode = !isEraseMode;
|
||||
$annotatorPNG.data(keyIsEraseMode, isEraseMode);
|
||||
console.log("Erase Mode:", isEraseMode);
|
||||
this.textContent = isEraseMode ? 'Exit Erase Mode' : 'Erase Mode';
|
||||
|
||||
if (isEraseMode) {
|
||||
deleteSelectedObjects();
|
||||
|
||||
canvas.selection = false;
|
||||
canvas.isDrawingMode = false;
|
||||
|
||||
canvas.defaultCursor = 'crosshair';
|
||||
canvas.hoverCursor = 'crosshair';
|
||||
} else {
|
||||
canvas.selection = true;
|
||||
canvas.defaultCursor = 'default';
|
||||
canvas.hoverCursor = 'move';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (!$canvasAnnotation.hasClass(flagInitialised)) {
|
||||
$canvasAnnotation.addClass(flagInitialised);
|
||||
// Object selection in erase mode
|
||||
$canvasAnnotation.on('selection:created', function() {
|
||||
console.log("Selection created");
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
if (isEraseMode) {
|
||||
deleteSelectedObjects();
|
||||
}
|
||||
});
|
||||
|
||||
// Object click in erase mode
|
||||
$canvasAnnotation.on('mouse:down', function(event) {
|
||||
console.log("Canvas mouse down:", event);
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
if (isEraseMode && event.target) {
|
||||
let canvas = getCanvas();
|
||||
canvas.remove(event.target);
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent dragging in erase mode
|
||||
$canvasAnnotation.on('object:moving', function(event) {
|
||||
console.log("Canvas object moving:", event);
|
||||
let isEraseMode = $annotatorPNG.data(keyIsEraseMode);
|
||||
if (isEraseMode) {
|
||||
let canvas = getCanvas();
|
||||
event.target.setCoords();
|
||||
canvas.remove(event.target);
|
||||
canvas.requestRenderAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!$buttonSaveImage.hasClass(flagInitialised)) {
|
||||
$buttonSaveImage.addClass(flagInitialised);
|
||||
$buttonSaveImage.on("click", function() {
|
||||
let canvas = getCanvas();
|
||||
if (window.confirm("Please ensure there is no sensitive information or PII in your annotations. Do you want to proceed with saving?")) {
|
||||
const dataURL = canvas.toDataURL({
|
||||
format: 'png',
|
||||
quality: 1
|
||||
});
|
||||
|
||||
// Output
|
||||
console.log("Base64 string:", dataURL);
|
||||
|
||||
let a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = dataURL;
|
||||
a.download = settings.fileName;
|
||||
|
||||
/* Download image
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
*/
|
||||
|
||||
alert("Image saved as Base64 string. Check the console for the output.");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}(jQuery));
|
||||
98
submission/plugin_capture_screen.js
Normal file
98
submission/plugin_capture_screen.js
Normal file
@@ -0,0 +1,98 @@
|
||||
|
||||
(function($) {
|
||||
$.fn.screenshotButton = function(options) {
|
||||
var settings = $.extend({
|
||||
buttonText: 'Capture Screen',
|
||||
fileName: 'screenshot.png',
|
||||
modalsSelector: '.modal, .popup, .overlay, .dialog, .tooltip, [class*="modal"], [class*="popup"], [class*="overlay"], [class*="dialog"], [class*="tooltip"], [style*="position: fixed"], [style*="position: absolute"], [style*="z-index"]',
|
||||
}, options);
|
||||
|
||||
function exportToBase64String(canvas) {
|
||||
const dataURL = canvas.toDataURL({
|
||||
format: 'png',
|
||||
quality: 1
|
||||
});
|
||||
console.log("Base64 string:", dataURL);
|
||||
return dataURL;
|
||||
// alert("Image saved as Base64 string. Check the console and Downloads folder for the output.");
|
||||
}
|
||||
function exportToBlob(canvas) {
|
||||
canvas.toBlob(function(blob) {
|
||||
var url = URL.createObjectURL(blob);
|
||||
console.log("blob: ", blob);
|
||||
return url;
|
||||
});
|
||||
}
|
||||
function downloadPNG(url) {
|
||||
var a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = settings.fileName;
|
||||
|
||||
console.log("adding button a: ", a);
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
|
||||
return this.each(function() {
|
||||
var $button = $(this);
|
||||
|
||||
$button.text(settings.buttonText);
|
||||
$button.removeClass(flagIsHidden);
|
||||
$button.attr('aria-label', settings.buttonText);
|
||||
|
||||
if (!$button.hasClass(flagInitialised)) {
|
||||
$button.addClass(flagInitialised);
|
||||
$button.on('click', function() {
|
||||
if (typeof html2canvas === 'undefined') {
|
||||
console.error('html2canvas is not loaded. Please include the library.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide button and sensitive information in inputs
|
||||
// $button.hide();
|
||||
$button.addClass(flagIsHidden);
|
||||
$('input').each(function() {
|
||||
$(this).attr("previousValue", $(this).val());
|
||||
$(this).val('');
|
||||
});
|
||||
$('textarea').each(function() {
|
||||
$(this).attr("previousValue", $(this).val());
|
||||
$(this).val('');
|
||||
});
|
||||
|
||||
// set display: block on element for all visible modals and floating elements
|
||||
// elements not detected by html2canvas with cascaded class-based display style
|
||||
$(settings.modalsSelector).each(function() {
|
||||
$(this).css('display', $(this).css('display'));
|
||||
});
|
||||
|
||||
html2canvas(document.body, {
|
||||
logging: true,
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
}).then(function(canvas) {
|
||||
let url = exportToBase64String(canvas);
|
||||
// exportToBlob(canvas);
|
||||
|
||||
downloadPNG(url);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
// Show button and sensitive information in inputs
|
||||
// $button.show();
|
||||
$button.removeClass(flagIsHidden);
|
||||
$('input').each(function() {
|
||||
$(this).val($(this).attr("previousValue"));
|
||||
});
|
||||
$('textarea').each(function() {
|
||||
$(this).val($(this).attr("previousValue"));
|
||||
});
|
||||
}).catch(function(e) {
|
||||
console.error(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}(jQuery));
|
||||
BIN
submission/screenshot (16).png
Normal file
BIN
submission/screenshot (16).png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
13
submission/shared.js
Normal file
13
submission/shared.js
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
var flagInitialised = "initialised";
|
||||
var flagIsHidden = "is-hidden";
|
||||
var flagIsVisible = "is-visible";
|
||||
|
||||
async function waitForClick($element) {
|
||||
return new Promise((resolve) => {
|
||||
$element.on('click', function() {
|
||||
resolve();
|
||||
});
|
||||
$element.click();
|
||||
});
|
||||
}
|
||||
BIN
submission/symbols/Orange Arrow.png
Normal file
BIN
submission/symbols/Orange Arrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
496
submission/test_plugin_annotate_PNG.js
Normal file
496
submission/test_plugin_annotate_PNG.js
Normal file
@@ -0,0 +1,496 @@
|
||||
const keyCodeEnter = 13;
|
||||
|
||||
describe('Image Annotation Control', function() {
|
||||
var $annotatorPNG, $toolbox, $inputUploadImage, $buttonAddArrow, $buttonAddTextbox, $selectAddSymbol, $inputColourPicker, $buttonEraseMode, $buttonSaveImage, $containerAnnotation, $canvasAnnotation;
|
||||
|
||||
beforeEach(function() {
|
||||
$annotatorPNG = $(document).find("." + flagAnnotatorPNG);
|
||||
$annotatorPNG.annotatorPNG(annotatorSettings);
|
||||
$toolbox = $annotatorPNG.find("." + flagToolbox);
|
||||
$inputUploadImage = $toolbox.find("." + flagUploadImage);
|
||||
$buttonAddArrow = $toolbox.find("." + flagAddArrow);
|
||||
$buttonAddTextbox = $toolbox.find("." + flagAddTextbox);
|
||||
$selectAddSymbol = $toolbox.find("." + flagAddSymbol);
|
||||
$inputColourPicker = $toolbox.find("." + flagColourPicker);
|
||||
$buttonEraseMode = $toolbox.find("." + flagEraseMode);
|
||||
$buttonSaveImage = $toolbox.find("." + flagSaveImage);
|
||||
$containerAnnotation = $annotatorPNG.find("." + flagContainerAnnotation);
|
||||
$canvasAnnotation = $containerAnnotation.find("." + flagCanvasAnnotation);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
let canvas = $($containerAnnotation.children()[0].children[0]).clone();
|
||||
$containerAnnotation.empty();
|
||||
$containerAnnotation.append(canvas);
|
||||
});
|
||||
|
||||
describe('1. Initialization and Setup', function() {
|
||||
it('a. Should initialize correctly on a given DOM element', async function() {
|
||||
expect($annotatorPNG).to.exist;
|
||||
});
|
||||
|
||||
it('b. Should create canvas with correct dimensions', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
expect(canvas.width).to.equal(800);
|
||||
expect(canvas.height).to.equal(600);
|
||||
expect($canvasAnnotation.attr('width')).to.equal('800');
|
||||
expect($canvasAnnotation.attr('height')).to.equal('600');
|
||||
});
|
||||
|
||||
it('c. Should have all UI elements present with correct values after initialization', async function() {
|
||||
expect($annotatorPNG.find("." + flagToolbox)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagUploadImage)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagAddArrow)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagAddTextbox)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagAddSymbol)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagColourPicker)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagEraseMode)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagSaveImage)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagContainerAnnotation)).to.have.length(1);
|
||||
expect($annotatorPNG.find("." + flagCanvasAnnotation).length > 0).to.be.true;
|
||||
/*
|
||||
expect($toolbox).to.exist;
|
||||
expect($inputUploadImage).to.exist;
|
||||
expect($buttonAddArrow).to.exist;
|
||||
expect($buttonAddTextbox).to.exist;
|
||||
expect($selectAddSymbol).to.exist;
|
||||
expect($selectAddSymbol.find("option")).to.exist;
|
||||
expect($inputColourPicker).to.exist;
|
||||
expect($buttonEraseMode).to.exist;
|
||||
expect($buttonSaveImage).to.exist;
|
||||
expect($containerAnnotation).to.exist;
|
||||
expect($canvasAnnotation).to.exist;
|
||||
*/
|
||||
|
||||
expect($inputUploadImage.val()).to.equal('');
|
||||
expect($inputColourPicker.val()).to.equal('#ff0000');
|
||||
expect($buttonSaveImage.text().length > 0).to.be.true; // For unkown settings argument value
|
||||
expect($buttonSaveImage.text()).to.equal('Save Image');
|
||||
expect($canvasAnnotation.data(keyFabric)).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('2. Image Upload and Display', function() {
|
||||
it('a. Should successfully upload a PNG file', function(done) {
|
||||
const file = new File([''], 'screenshot (16).png', { type: 'image/png' });
|
||||
const fileInput = $inputUploadImage[0];
|
||||
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(file);
|
||||
fileInput.files = dataTransfer.files;
|
||||
|
||||
$(fileInput).trigger('change');
|
||||
|
||||
setTimeout(() => {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
if (canvas) console.log("canvas.backgroundImage: " + canvas.backgroundImage);
|
||||
expect(canvas.backgroundImage).to.exist;
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('b. Should display the uploaded image correctly on the canvas', function(done) {
|
||||
const file = new File([''], 'screenshot (16).png', { type: 'image/png' });
|
||||
const fileInput = $inputUploadImage[0];
|
||||
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(file);
|
||||
fileInput.files = dataTransfer.files;
|
||||
|
||||
$(fileInput).trigger('change');
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
let context = canvas.getContext('2d');
|
||||
let imageData = context.getImageData(0, 0, canvas.width, canvas.height);
|
||||
expect(canvas.width).to.equal(800);
|
||||
expect(canvas.height).to.equal(600);
|
||||
expect(imageData.data.length).to.equal(800 * 600 * 4);
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Error during canvas initialisation: " + e);
|
||||
done(e);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
it('d. Should handle invalid file types', async function() {
|
||||
const file = new File([''], 'test.jpg', { type: 'image/jpeg' });
|
||||
const fileInput = $inputUploadImage[0];
|
||||
|
||||
const confirmStub = sinon.stub(window, 'alert').returns(true);
|
||||
const dataTransfer = new DataTransfer();
|
||||
dataTransfer.items.add(file);
|
||||
fileInput.files = dataTransfer.files;
|
||||
|
||||
expect(() => $(fileInput).trigger('change')).to.throw();
|
||||
confirmStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('3. Arrow Addition', function() {
|
||||
it('a. Should create a new arrow on the canvas when "Add Arrow" is clicked', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
console.log("canvas: " + canvas);
|
||||
await waitForClick($buttonAddArrow);
|
||||
console.log("canvas after: " + canvas);
|
||||
expect(canvas.getObjects()).to.have.lengthOf(1);
|
||||
expect(canvas.getObjects()[0].type).to.equal('path');
|
||||
});
|
||||
|
||||
it('b. Should create arrow with correct default properties', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddArrow);
|
||||
const arrow = canvas.getObjects()[0];
|
||||
expect(arrow.fill).to.equal('#ff0000');
|
||||
expect(arrow.stroke).to.equal('#ff0000');
|
||||
expect(arrow.strokeWidth).to.equal(2);
|
||||
});
|
||||
|
||||
it('c. Should allow multiple arrows to be added to the canvas', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddArrow);
|
||||
await waitForClick($buttonAddArrow);
|
||||
expect(canvas.getObjects()).to.have.lengthOf(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('4. Textbox Addition', function() {
|
||||
it('a. Should create a new textbox on the canvas when "Add Textbox" is clicked', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
expect(canvas.getObjects()).to.have.lengthOf(1);
|
||||
expect(canvas.getObjects()[0].type).to.equal('textbox');
|
||||
});
|
||||
|
||||
it('b. Should create textbox with correct default properties', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
const textbox = canvas.getObjects()[0];
|
||||
expect(textbox.fontSize).to.equal(20);
|
||||
expect(textbox.left).to.equal(100);
|
||||
expect(textbox.top).to.equal(100);
|
||||
});
|
||||
|
||||
it('c. Should allow text to be entered and edited in the textbox', async function() {
|
||||
this.timeout(10000);
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
const textbox = canvas.getObjects()[0];
|
||||
console.log("textbox: " + textbox);
|
||||
textbox.text = 'New Text';
|
||||
console.log("textbox: " + textbox);
|
||||
canvas.renderAll();
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
expect(textbox.text).to.equal('New Text');
|
||||
});
|
||||
|
||||
it('d. Should allow multiple textboxes to be added to the canvas', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
expect(canvas.getObjects()).to.have.lengthOf(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('5. Element Manipulation', function() {
|
||||
it('a. Should allow arrows to be selected, moved, resized, and rotated', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddArrow);
|
||||
const arrow = canvas.getObjects()[0];
|
||||
arrow.set({ left: 150, top: 150, scaleX: 2, angle: 45 });
|
||||
canvas.renderAll();
|
||||
expect(arrow.left).to.equal(150);
|
||||
expect(arrow.top).to.equal(150);
|
||||
expect(arrow.scaleX).to.equal(2);
|
||||
expect(arrow.angle).to.equal(45);
|
||||
});
|
||||
|
||||
it('b. Should allow textboxes to be selected, moved, resized, and rotated', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddTextbox);
|
||||
const textbox = canvas.getObjects()[0];
|
||||
textbox.set({ left: 150, top: 150, scaleX: 2, angle: 45 });
|
||||
canvas.renderAll();
|
||||
expect(textbox.left).to.equal(150);
|
||||
expect(textbox.top).to.equal(150);
|
||||
expect(textbox.scaleX).to.equal(2);
|
||||
expect(textbox.angle).to.equal(45);
|
||||
});
|
||||
});
|
||||
|
||||
describe('6. Erasing Elements', function() {
|
||||
afterEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
$annotatorPNG.annotatorPNG(annotatorSettings);
|
||||
});
|
||||
|
||||
it('a. Should remove the currently selected arrow when "Erase Element" is clicked', async function() {
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
await waitForClick($buttonAddArrow);
|
||||
canvas.setActiveObject(canvas.getObjects()[0]);
|
||||
await waitForClick($buttonEraseMode);
|
||||
expect(canvas.getObjects()).to.have.lengthOf(0);
|
||||
});
|
||||
|
||||
it('b. Should remove the currently selected textbox when "Erase Element" is clicked', function(done) {
|
||||
this.timeout(10000);
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
waitForClick($buttonAddTextbox).then(() => {
|
||||
canvas.setActiveObject(canvas.getObjects()[0]);
|
||||
canvas.renderAll();
|
||||
waitForClick($buttonEraseMode).then(() => {
|
||||
canvas.renderAll();
|
||||
expect(canvas.getObjects()).to.have.lengthOf(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('c. Should not affect other elements on the canvas when erasing', function(done) {
|
||||
this.timeout(10000);
|
||||
let canvas = $canvasAnnotation.data(keyFabric);
|
||||
waitForClick($buttonAddArrow).then(() => {
|
||||
console.log("canvas.getObjects()[0]: ", canvas.getObjects()[0]);
|
||||
waitForClick($buttonAddTextbox).then(() => {
|
||||
canvas.setActiveObject(canvas.getObjects()[0]);
|
||||
waitForClick($buttonEraseMode).then(() => {
|
||||
expect(canvas.getObjects()).to.have.lengthOf(1);
|
||||
console.log("canvas.getObjects()[0]: ", canvas.getObjects()[0]);
|
||||
expect(canvas.getObjects()[0].text).to.exist;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('7. Saving Functionality', function() {
|
||||
it('a. Should trigger confirmation dialog when "Save" is clicked', async function() {
|
||||
const confirmStub = sinon.stub(window, 'confirm').returns(true);
|
||||
const alertStub = sinon.stub(window, 'alert').returns(true);
|
||||
await waitForClick($buttonSaveImage);
|
||||
expect(confirmStub.calledOnce).to.be.true;
|
||||
confirmStub.restore();
|
||||
alertStub.restore();
|
||||
});
|
||||
|
||||
it('b. Should prevent saving when confirmation dialog is canceled', async function() {
|
||||
const confirmStub = sinon.stub(window, 'confirm').returns(false);
|
||||
const consoleStub = sinon.stub(console, 'log');
|
||||
await waitForClick($buttonSaveImage);
|
||||
expect(consoleStub.called).to.be.false;
|
||||
confirmStub.restore();
|
||||
consoleStub.restore();
|
||||
});
|
||||
|
||||
it('c. Should generate a Base64 PNG string when saving is confirmed', async function() {
|
||||
sinon.stub(window, 'confirm').returns(true);
|
||||
const alertStub = sinon.stub(window, 'alert').returns(true);
|
||||
const consoleStub = sinon.stub(console, 'log');
|
||||
await waitForClick($buttonSaveImage);
|
||||
expect(consoleStub.calledOnce).to.be.true;
|
||||
const base64String = consoleStub.getCall(0).args[1];
|
||||
expect(base64String).to.be.a('string');
|
||||
expect(base64String).to.match(/^data:image\/png;base64,/);
|
||||
window.confirm.restore();
|
||||
consoleStub.restore();
|
||||
alertStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('8. Edge Cases and Error Handling', function() {
|
||||
it('a. Should handle initialization on invalid DOM element', async function() {
|
||||
expect(() => $('#non-existent-element').imageAnnotator()).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('9. Performance', function() {
|
||||
it('a. Should capture and generate screenshot within acceptable time', function(done) {
|
||||
this.timeout(5000);
|
||||
|
||||
const startTime = performance.now();
|
||||
const alertStub = sinon.stub(window, 'alert').returns(true);
|
||||
const confirmStub = sinon.stub(window, 'confirm').returns(true);
|
||||
|
||||
$buttonSaveImage.click();
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
expect(duration).to.be.below(1000);
|
||||
alertStub.restore();
|
||||
confirmStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('10. Accessibility', function() {
|
||||
it('a. Should have appropriate ARIA labels for all interactive elements', async function() {
|
||||
expect($inputUploadImage.attr('aria-label').length > 0).to.be.true;
|
||||
expect($buttonAddArrow.attr('aria-label').length > 0).to.be.true;
|
||||
expect($buttonAddTextbox.attr('aria-label').length > 0).to.be.true;
|
||||
expect($selectAddSymbol.attr('aria-label').length > 0).to.be.true;
|
||||
expect($inputColourPicker.attr('aria-label').length > 0).to.be.true;
|
||||
expect($buttonEraseMode.attr('aria-label').length > 0).to.be.true;
|
||||
expect($buttonSaveImage.attr('aria-label').length > 0).to.be.true;
|
||||
expect($canvasAnnotation.attr('aria-label').length > 0).to.be.true;
|
||||
});
|
||||
|
||||
it('b. File input should be keyboard accessible', function(done) {
|
||||
// By navigation with tab key
|
||||
expect($inputUploadImage).to.satisfy(function($element) {
|
||||
let tabindex = $element.attr('tabindex');
|
||||
let index = tabindex ? parseInt(tabindex) : NaN;
|
||||
return isNaN(index) || index >= 0;
|
||||
});
|
||||
// By pressing Enter key
|
||||
$inputUploadImage.on('keydown', function(e) {
|
||||
if (e.which === keyCodeEnter) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
const event = new KeyboardEvent('keydown', { 'keyCode': keyCodeEnter });
|
||||
$inputUploadImage[0].dispatchEvent(event);
|
||||
});
|
||||
|
||||
it('c. Add arrow button should be keyboard accessible', function(done) {
|
||||
// By navigation with tab key
|
||||
expect($buttonAddArrow).to.satisfy(function($element) {
|
||||
let tabindex = $element.attr('tabindex');
|
||||
let index = tabindex ? parseInt(tabindex) : NaN;
|
||||
return isNaN(index) || index >= 0;
|
||||
});
|
||||
// By pressing Enter key
|
||||
$buttonAddArrow.on('keydown', function(e) {
|
||||
if (e.which === keyCodeEnter) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
const event = new KeyboardEvent('keydown', { 'keyCode': keyCodeEnter });
|
||||
$buttonAddArrow[0].dispatchEvent(event);
|
||||
});
|
||||
|
||||
it('d. Add textbox button should be keyboard accessible', function(done) {
|
||||
// By navigation with tab key
|
||||
expect($buttonAddTextbox).to.satisfy(function($element) {
|
||||
let tabindex = $element.attr('tabindex');
|
||||
let index = tabindex ? parseInt(tabindex) : NaN;
|
||||
return isNaN(index) || index >= 0;
|
||||
});
|
||||
// By pressing Enter key
|
||||
$buttonAddTextbox.on('keydown', function(e) {
|
||||
if (e.which === keyCodeEnter) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
const event = new KeyboardEvent('keydown', { 'keyCode': keyCodeEnter });
|
||||
$buttonAddTextbox[0].dispatchEvent(event);
|
||||
});
|
||||
|
||||
it('e. Add symbol DDL should be keyboard accessible', function(done) {
|
||||
// By navigation with tab key
|
||||
expect($selectAddSymbol).to.satisfy(function($element) {
|
||||
let tabindex = $element.attr('tabindex');
|
||||
let index = tabindex ? parseInt(tabindex) : NaN;
|
||||
return isNaN(index) || index >= 0;
|
||||
});
|
||||
// By pressing Enter key
|
||||
$selectAddSymbol.on('keydown', function(e) {
|
||||
if (e.which === keyCodeEnter) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
const event = new KeyboardEvent('keydown', { 'keyCode': keyCodeEnter });
|
||||
$selectAddSymbol[0].dispatchEvent(event);
|
||||
});
|
||||
|
||||
it('f. Colour picker should be keyboard accessible', function(done) {
|
||||
// By navigation with tab key
|
||||
expect($inputColourPicker).to.satisfy(function($element) {
|
||||
let tabindex = $element.attr('tabindex');
|
||||
let index = tabindex ? parseInt(tabindex) : NaN;
|
||||
return isNaN(index) || index >= 0;
|
||||
});
|
||||
// By pressing Enter key
|
||||
$inputColourPicker.on('keydown', function(e) {
|
||||
if (e.which === keyCodeEnter) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
const event = new KeyboardEvent('keydown', { 'keyCode': keyCodeEnter });
|
||||
$inputColourPicker[0].dispatchEvent(event);
|
||||
});
|
||||
|
||||
it('g. Erase mode button should be keyboard accessible', function(done) {
|
||||
// By navigation with tab key
|
||||
expect($buttonEraseMode).to.satisfy(function($element) {
|
||||
let tabindex = $element.attr('tabindex');
|
||||
let index = tabindex ? parseInt(tabindex) : NaN;
|
||||
return isNaN(index) || index >= 0;
|
||||
});
|
||||
// By pressing Enter key
|
||||
$buttonEraseMode.on('keydown', function(e) {
|
||||
if (e.which === keyCodeEnter) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
const event = new KeyboardEvent('keydown', { 'keyCode': keyCodeEnter });
|
||||
$buttonEraseMode[0].dispatchEvent(event);
|
||||
});
|
||||
|
||||
it('h. Save image button should be keyboard accessible', function(done) {
|
||||
// By navigation with tab key
|
||||
expect($buttonSaveImage).to.satisfy(function($element) {
|
||||
let tabindex = $element.attr('tabindex');
|
||||
let index = tabindex ? parseInt(tabindex) : NaN;
|
||||
return isNaN(index) || index >= 0;
|
||||
});
|
||||
// By pressing Enter key
|
||||
$buttonSaveImage.on('keydown', function(e) {
|
||||
if (e.which === keyCodeEnter) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
const event = new KeyboardEvent('keydown', { 'keyCode': keyCodeEnter });
|
||||
$buttonSaveImage[0].dispatchEvent(event);
|
||||
});
|
||||
});
|
||||
|
||||
describe('11. jQuery Plugin Requirements', function() {
|
||||
it('a. Should return jQuery object for chaining', function() {
|
||||
const result = $annotatorPNG.annotatorPNG(annotatorSettings);
|
||||
expect(result).to.equal($annotatorPNG);
|
||||
});
|
||||
|
||||
it('b. Should initialize plugin on multiple elements', function() {
|
||||
$('body').append('<div class="test-class"></div><div class="test-class"></div>');
|
||||
$('.test-class').annotatorPNG(annotatorSettings);
|
||||
expect($('.test-class').length).to.equal(2);
|
||||
$('.test-class').remove();
|
||||
});
|
||||
});
|
||||
|
||||
describe('12. Memory Management', function() {
|
||||
it('a. Should not leak memory on repeated initialization and destruction', function() {
|
||||
const initialMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
for (let i = 0; i < 500; i++) {
|
||||
$annotatorPNG.annotatorPNG(annotatorSettings);
|
||||
$canvasAnnotation.data(keyFabric).dispose();
|
||||
}
|
||||
const finalMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
expect(finalMemory - initialMemory).to.be.below(1000000); // 1 MB ish
|
||||
});
|
||||
|
||||
it('b. Should remove all created DOM elements after use', function() {
|
||||
const initialChildCount = $annotatorPNG[0].childElementCount;
|
||||
const initialCanvasCount = $containerAnnotation[0].childElementCount;
|
||||
|
||||
$annotatorPNG.annotatorPNG(annotatorSettings);
|
||||
|
||||
expect($annotatorPNG[0].childElementCount).to.equal(initialChildCount);
|
||||
expect($containerAnnotation[0].childElementCount).to.equal(initialCanvasCount);
|
||||
});
|
||||
});
|
||||
});
|
||||
582
submission/test_plugin_capture_screen.js
Normal file
582
submission/test_plugin_capture_screen.js
Normal file
@@ -0,0 +1,582 @@
|
||||
|
||||
function hookupTestModals() {
|
||||
let $buttonToggleModalChat = $(idButtonToggleModalChat);
|
||||
let $modalChat = $(idModalChat);
|
||||
let $buttonToggleModalNotifications = $(idButtonToggleModalNotifications);
|
||||
let $modalNotifications = $(idModalNotifications);
|
||||
let $buttonToggleModalSettings = $(idButtonToggleModalSettings);
|
||||
let $modalSettings = $(idModalSettings);
|
||||
|
||||
if (!$buttonToggleModalChat.hasClass(flagInitialised)) {
|
||||
$buttonToggleModalChat.addClass(flagInitialised);
|
||||
$buttonToggleModalChat.on('click', function() {
|
||||
if ($modalChat.hasClass(flagIsHidden)) {
|
||||
$modalChat.removeClass(flagIsHidden);
|
||||
} else {
|
||||
$modalChat.addClass(flagIsHidden);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!$modalChat.hasClass(flagInitialised)) {
|
||||
$modalChat.addClass(flagInitialised);
|
||||
}
|
||||
$modalChat.addClass(flagIsHidden);
|
||||
|
||||
if (!$buttonToggleModalNotifications.hasClass(flagInitialised)) {
|
||||
$buttonToggleModalNotifications.addClass(flagInitialised);
|
||||
$buttonToggleModalNotifications.on('click', function() {
|
||||
if ($modalNotifications.hasClass(flagIsHidden)) {
|
||||
$modalNotifications.removeClass(flagIsHidden);
|
||||
} else {
|
||||
$modalNotifications.addClass(flagIsHidden);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!$modalNotifications.hasClass(flagInitialised)) {
|
||||
$modalNotifications.addClass(flagInitialised);
|
||||
}
|
||||
$modalNotifications.addClass(flagIsHidden);
|
||||
|
||||
if (!$buttonToggleModalSettings.hasClass(flagInitialised)) {
|
||||
$buttonToggleModalSettings.addClass(flagInitialised);
|
||||
$buttonToggleModalSettings.on('click', function() {
|
||||
if ($modalSettings.hasClass(flagIsHidden)) {
|
||||
$modalSettings.removeClass(flagIsHidden);
|
||||
} else {
|
||||
$modalSettings.addClass(flagIsHidden);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!$modalSettings.hasClass(flagInitialised)) {
|
||||
$modalSettings.addClass(flagInitialised);
|
||||
}
|
||||
$modalSettings.addClass(flagIsHidden);
|
||||
}
|
||||
|
||||
describe('Screen Capture Plugin', function() {
|
||||
var $button, sandbox, originalBodyHTML;
|
||||
|
||||
beforeEach(function() {
|
||||
originalBodyHTML = document.body.innerHTML;
|
||||
|
||||
$button = $('.' + flagCaptureScreen);
|
||||
$button.screenshotButton(defaultOptions);
|
||||
|
||||
sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
$button.screenshotButton(defaultOptions);
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
after(function() {
|
||||
document.body.innerHTML = originalBodyHTML;
|
||||
});
|
||||
|
||||
describe('1. Initialization and Setup', function() {
|
||||
afterEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
it('a. Should initialize correctly on a given DOM element', function() {
|
||||
expect($button).to.exist;
|
||||
});
|
||||
|
||||
it('b. Should create the button with correct text', function() {
|
||||
expect($button.text()).to.equal('Capture Screen');
|
||||
});
|
||||
|
||||
it('c. Should make the button visible on the page', function() {
|
||||
expect($button.is(':visible')).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('2. Button Functionality', function() {
|
||||
afterEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
it('a. Should trigger screenshot process on click', function() {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect(html2canvas.called).to.be.true;
|
||||
expect(html2canvas.callCount).to.equal(1);
|
||||
html2canvas.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('b. Should hide button during screenshot process', function(done) {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
$button.click();
|
||||
expect($button.hasClass(flagIsHidden)).to.be.true;
|
||||
html2canvas.restore();
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
it('c. Should show button after screenshot is taken', function() {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect($button.hasClass(flagIsHidden)).to.be.false;
|
||||
html2canvas.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('3. Input Handling', function() {
|
||||
let $input, $textarea;
|
||||
|
||||
beforeEach(function() {
|
||||
$input = $('<input type="text" value="test">').appendTo('body');
|
||||
$textarea = $('<textarea>test</textarea>').appendTo('body');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
$input.remove();
|
||||
$textarea.remove();
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
it('a. Should clear input and textarea values during screenshot', function(done) {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
$button.click();
|
||||
expect($input.val()).to.equal('');
|
||||
expect($textarea.val()).to.equal('');
|
||||
html2canvas.restore();
|
||||
done();
|
||||
});
|
||||
|
||||
it('b. Should restore input and textarea values after screenshot', function() {
|
||||
sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect($input.val()).to.equal('test');
|
||||
expect($textarea.val()).to.equal('test');
|
||||
html2canvas.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('4. Modal and Floating Element Handling', function() {
|
||||
let $modal;
|
||||
|
||||
beforeEach(function() {
|
||||
$modal = $('<div class="modal">Modal Content</div>').appendTo('body');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
$modal.remove();
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
|
||||
it('a. Should capture visible modals', function(done) {
|
||||
sinon.stub(window, 'html2canvas').callsFake(function(element) {
|
||||
expect($(element).find('.modal').length).to.equal(5);
|
||||
return Promise.resolve(document.createElement('canvas'));
|
||||
});
|
||||
$button.click();
|
||||
html2canvas.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('5. HTML2Canvas Integration', function() {
|
||||
let html2canvasStub, consoleErrorStub;
|
||||
|
||||
afterEach(function() {
|
||||
if (html2canvasStub && html2canvasStub.restore) {
|
||||
html2canvasStub.restore();
|
||||
}
|
||||
if (consoleErrorStub && consoleErrorStub.restore) {
|
||||
consoleErrorStub.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it('a. Should call html2canvas with correct parameters', function() {
|
||||
html2canvasStub = sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect(html2canvasStub.calledWith(document.body)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('b. Should handle html2canvas errors gracefully', function() {
|
||||
html2canvasStub = sinon.stub(window, 'html2canvas').rejects(new Error('Test error'));
|
||||
consoleErrorStub = sinon.stub(console, 'error');
|
||||
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect(consoleErrorStub.called).to.be.true;
|
||||
expect(consoleErrorStub.firstCall.args[0]).to.be.an('error');
|
||||
expect(consoleErrorStub.firstCall.args[0].message).to.equal('Test error');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('6. Screenshot Generation', function() {
|
||||
afterEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
/* Base64 string used instead of Blob
|
||||
it('a. Should create a blob from the canvas', function(done) {
|
||||
const canvas = document.createElement('canvas');
|
||||
sinon.stub(window, 'html2canvas').resolves(canvas);
|
||||
sinon.stub(canvas, 'toBlob').callsArgWith(0, new Blob());
|
||||
$button.click();
|
||||
html2canvas.restore();
|
||||
expect(canvas.toBlob.called).to.be.true;
|
||||
canvas.toBlob.restore();
|
||||
});
|
||||
*/
|
||||
|
||||
it('b. Should generate a Base64 PNG string when saving is confirmed', function(done) {
|
||||
const consoleStub = sinon.stub(console, 'log');
|
||||
$button.click();
|
||||
setTimeout(() => {
|
||||
expect(consoleStub.callCount).to.equal(2);
|
||||
const base64String = consoleStub.getCall(0).args[1];
|
||||
expect(base64String).to.be.a('string');
|
||||
expect(base64String).to.match(/^data:image\/png;base64,/);
|
||||
consoleStub.restore();
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('7. Download Functionality', function() {
|
||||
let createElementStub, appendChildStub, removeChildStub, html2canvasStub;
|
||||
let originalJQuery, jQueryStub, eachStub, originalCreateElement;
|
||||
|
||||
beforeEach(function() {
|
||||
originalJQuery = window.$;
|
||||
originalCreateElement = document.createElement;
|
||||
|
||||
jQueryStub = sinon.stub();
|
||||
eachStub = sinon.stub();
|
||||
jQueryStub.returns({
|
||||
each: eachStub,
|
||||
attr: sinon.stub(),
|
||||
val: sinon.stub(),
|
||||
addClass: sinon.stub(),
|
||||
removeClass: sinon.stub()
|
||||
});
|
||||
window.$ = jQueryStub;
|
||||
|
||||
html2canvasStub = sinon.stub(window, 'html2canvas').resolves(document.createElement('canvas'));
|
||||
|
||||
createElementStub = sinon.stub(document, 'createElement').callsFake(function(tagName) {
|
||||
if (tagName === 'a') {
|
||||
return {
|
||||
style: {},
|
||||
href: '',
|
||||
download: '',
|
||||
click: sinon.spy(),
|
||||
textContent: ''
|
||||
};
|
||||
}
|
||||
return originalCreateElement.call(document, tagName);
|
||||
});
|
||||
appendChildStub = sinon.stub(document.body, 'appendChild');
|
||||
removeChildStub = sinon.stub(document.body, 'removeChild');
|
||||
|
||||
sinon.stub(URL, 'createObjectURL').returns('blob:http://example.com/test');
|
||||
sinon.stub(URL, 'revokeObjectURL');
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sinon.restore();
|
||||
window.$ = originalJQuery;
|
||||
document.createElement = originalCreateElement;
|
||||
});
|
||||
|
||||
it('a. Should create a temporary anchor element for download', function() {
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect(html2canvasStub.calledWith(document.body)).to.be.true;
|
||||
expect(createElementStub.calledWith('a')).to.be.true;
|
||||
expect(appendChildStub.calledOnce).to.be.true;
|
||||
expect(removeChildStub.calledOnce).to.be.true;
|
||||
|
||||
const aElement = appendChildStub.getCall(0).args[0];
|
||||
expect(aElement.href).to.include('data:image/png;base64');
|
||||
expect(aElement.download).to.equal('screenshot.png');
|
||||
});
|
||||
});
|
||||
|
||||
it('b. Should click the temporary anchor element programmatically', function() {
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
console.log("createElementStub: ", createElementStub, createElementStub.calledOnce);
|
||||
expect(createElementStub.calledOnce).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('c. Should remove the temporary anchor after download', function(done) {
|
||||
$button.click();
|
||||
setTimeout(() => {
|
||||
expect(removeChildStub.calledOnce).to.be.true;
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('8. Performance', function() {
|
||||
it('a. Should capture and generate screenshot within acceptable time', function(done) {
|
||||
this.timeout(5000);
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
$button.click();
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - startTime;
|
||||
expect(duration).to.be.below(1000);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('9. Error Handling', function() {
|
||||
var html2canvasStub, consoleErrorStub;
|
||||
|
||||
beforeEach(function() {
|
||||
consoleErrorStub = sandbox.stub(console, 'error');
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
if (html2canvasStub && html2canvasStub.restore) {
|
||||
html2canvasStub.restore();
|
||||
}
|
||||
if (consoleErrorStub && consoleErrorStub.restore) {
|
||||
consoleErrorStub.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it('a. Should log error when html2canvas is not loaded', function(done) {
|
||||
const originalHtml2Canvas = window.html2canvas;
|
||||
delete window.html2canvas;
|
||||
|
||||
$button.click();
|
||||
|
||||
expect(consoleErrorStub.calledOnce).to.be.true;
|
||||
expect(consoleErrorStub.args[0][0]).to.include('html2canvas is not loaded');
|
||||
|
||||
window.html2canvas = originalHtml2Canvas;
|
||||
done();
|
||||
});
|
||||
|
||||
it('b. Should handle errors during capture process', function() {
|
||||
let errorName = 'Capture failed';
|
||||
html2canvasStub = sinon.stub(window, 'html2canvas').rejects(new Error(errorName));
|
||||
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect(consoleErrorStub.called).to.be.true;
|
||||
expect(consoleErrorStub.firstCall.args[0]).to.be.an('error');
|
||||
expect(consoleErrorStub.firstCall.args[0].message).to.equal(errorName);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('10. Configuration Options', function() {
|
||||
beforeEach(function() {
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sinon.restore();
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
it('a. Should apply custom button text correctly', function() {
|
||||
const customText = 'Custom Capture Screen';
|
||||
$button.screenshotButton({ buttonText: customText });
|
||||
expect($button.text()).to.equal(customText);
|
||||
});
|
||||
|
||||
it('b. Should use custom filename for the downloaded image', function(done) {
|
||||
console.log("11.b");
|
||||
this.timeout(5000);
|
||||
const customFileName = 'custom-screenshot.png';
|
||||
$button.screenshotButton({ fileName: customFileName });
|
||||
const consoleStub = sinon.stub(console, 'log');
|
||||
|
||||
$button.click();
|
||||
setTimeout(() => {
|
||||
console.log("consoleStub: ", consoleStub);
|
||||
try {
|
||||
expect(consoleStub.callCount).to.equal(2);
|
||||
const call = consoleStub.getCall(1);
|
||||
console.log("call: ", call);
|
||||
const a = call.args[1];
|
||||
console.log("a: ", a);
|
||||
// expect(consoleLogStub.calledWith("adding button a: ", customFileName)).to.be.true;
|
||||
} catch (e) {
|
||||
console.log("error: ", e);
|
||||
}
|
||||
consoleStub.restore();
|
||||
done();
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('11. Accessibility', function() {
|
||||
it('a. Should have appropriate ARIA attributes', function() {
|
||||
expect($button.attr('role')).to.equal('button');
|
||||
expect($button.attr('aria-label')).to.equal(defaultOptions.buttonText);
|
||||
});
|
||||
|
||||
it('b. Should be keyboard accessible', function(done) {
|
||||
// By navigation with tab key
|
||||
expect($button).to.satisfy(function($element) {
|
||||
let tabindex = $element.attr('tabindex');
|
||||
let index = tabindex ? parseInt(tabindex) : NaN;
|
||||
return isNaN(index) || index >= 0;
|
||||
});
|
||||
// By pressing Enter key
|
||||
$button.on('keydown', function(e) {
|
||||
if (e.which === 13) { // Enter key
|
||||
done();
|
||||
}
|
||||
});
|
||||
const event = new KeyboardEvent('keydown', { 'keyCode': 13 });
|
||||
$button[0].dispatchEvent(event);
|
||||
});
|
||||
});
|
||||
|
||||
describe('12. Security', function() {
|
||||
const inputValue = 'sensitive-info';
|
||||
const textareaValue = 'confidential-data';
|
||||
const idInput = 'testInput1';
|
||||
const idTextarea = 'testInput2';
|
||||
|
||||
afterEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
it('a. Should not capture sensitive information in inputs and textareas', function(done) {
|
||||
$('<input id="' + idInput + '" type="text" value="' + inputValue + '">').appendTo('body');
|
||||
$('<textarea id="' + idTextarea + '">' + textareaValue + '</textarea>').appendTo('body');
|
||||
$button.click();
|
||||
const inputs = document.querySelectorAll('input, textarea');
|
||||
try {
|
||||
inputs.forEach(input => {
|
||||
expect(input.value).to.be.empty;
|
||||
});
|
||||
if ($('#' + idInput)) $('#' + idInput).remove();
|
||||
if ($('#' + idTextarea)) $('#' + idTextarea).remove();
|
||||
} catch(e) {
|
||||
console.log("12.a error: ", e);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
it('b. Should restore input and textarea values after capture', function() {
|
||||
|
||||
$('#' + idInput).remove();
|
||||
$('#' + idTextarea).remove();
|
||||
$('<input id="' + idInput + '" type="text" value="' + inputValue + '">').appendTo('body');
|
||||
$('<textarea id="' + idTextarea + '">' + textareaValue + '</textarea>').appendTo('body');
|
||||
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
let $input = $('#' + idInput);
|
||||
let $textarea = $('#' + idTextarea);
|
||||
expect($input.val()).to.equal(inputValue);
|
||||
expect($textarea.val()).to.equal(textareaValue);
|
||||
$input.remove();
|
||||
$textarea.remove();
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('13. CSS Interaction', function() {
|
||||
it('a. Should not break existing page styles', function() {
|
||||
const originalStyles = getComputedStyle(document.body);
|
||||
const newStyles = getComputedStyle(document.body);
|
||||
expect(originalStyles.cssText).to.equal(newStyles.cssText);
|
||||
});
|
||||
|
||||
it('b. Should apply correct button styling', function() {
|
||||
const buttonStyles = getComputedStyle($button[0]);
|
||||
expect(buttonStyles.display).to.not.equal('none');
|
||||
expect($button.hasClass(flagIsHidden)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('14. jQuery Plugin Requirements', function() {
|
||||
it('a. Should return jQuery object for chaining', function() {
|
||||
const result = $button.screenshotButton();
|
||||
expect(result).to.equal($button);
|
||||
});
|
||||
|
||||
it('b. Should initialize plugin on multiple elements', function() {
|
||||
$('body').append('<div class="test-class"></div><div class="test-class"></div>');
|
||||
$('.test-class').screenshotButton();
|
||||
expect($('.test-class').length).to.equal(2);
|
||||
$('.test-class').remove();
|
||||
});
|
||||
});
|
||||
|
||||
describe('15. Memory Management', function() {
|
||||
it('a. Should not leak memory on repeated initialization and destruction', function() {
|
||||
const initialMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
for (let i = 0; i < 500; i++) {
|
||||
$button.screenshotButton();
|
||||
$button.empty();
|
||||
}
|
||||
const finalMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
|
||||
expect(finalMemory - initialMemory).to.be.below(1000000); // 1 MB ish
|
||||
});
|
||||
|
||||
it('b. Should remove all created DOM elements after use', function() {
|
||||
$button.screenshotButton();
|
||||
const initialChildCount = $button[0].childElementCount;
|
||||
|
||||
return waitForClick($button)
|
||||
.then(() => {
|
||||
expect($button[0].childElementCount).to.equal(initialChildCount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('16. Responsiveness', function() {
|
||||
var html2canvasStub;
|
||||
|
||||
beforeEach(function() {
|
||||
setTimeout(() => {}, 2000);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
if (html2canvasStub && html2canvasStub.restore) {
|
||||
html2canvasStub.restore();
|
||||
}
|
||||
$button.width(100).height(50);
|
||||
});
|
||||
|
||||
it('a. Should behave correctly on different screen sizes', function() {
|
||||
const viewports = [
|
||||
{width: 320, height: 568}, // iPhone 5
|
||||
{width: 1024, height: 768}, // iPad
|
||||
{width: 1920, height: 1080} // Full HD
|
||||
];
|
||||
|
||||
viewports.forEach(size => {
|
||||
$button.width(size.width).height(size.height);
|
||||
$button.screenshotButton();
|
||||
expect($button.hasClass(flagIsHidden)).to.be.false;
|
||||
expect($button.width()).to.be.at.most(size.width);
|
||||
});
|
||||
});
|
||||
|
||||
it('', function() {});
|
||||
});
|
||||
});
|
||||
84
submission/view_annotate_PNG.html
Normal file
84
submission/view_annotate_PNG.html
Normal file
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Image Annotation Control</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.3.2/mocha.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.3.4/chai.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/10.0.1/sinon.min.js"></script>
|
||||
<script src="shared.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.3.2/mocha.min.css">
|
||||
<link rel="stylesheet" href="annotate-PNG.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Start of PNG Annotation HTML -->
|
||||
<div class="annotator-png">
|
||||
<div class="annotator-png-toolbox">
|
||||
<input type="file" class="annotator-png-upload-image" accept="image/png" aria-label="Upload image">
|
||||
<button class="annotator-png-add-arrow" aria-label="Add arrow">Add Arrow</button>
|
||||
<button class="annotator-png-add-textbox" aria-label="Add textbox">Add Textbox</button>
|
||||
<select class="annotator-png-add-symbol" aria-label="Select and add symbol">
|
||||
<option value="">Select Symbol</option>
|
||||
</select>
|
||||
<input type="color" class="annotator-png-colour-picker" value="#ff0000" aria-label="Colour picker">
|
||||
<button class="annotator-png-erase-mode" aria-label="Toggle erase mode">Erase Mode</button>
|
||||
<button class="annotator-png-save-image" aria-label="Save annotated image">Save Image</button>
|
||||
</div>
|
||||
<div class="annotator-png-container-annotation" style="height: 600px; width: 800px;">
|
||||
<canvas class="annotator-png-canvas-annotation" height="600px" width="800px" aria-label="Image canvas"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End of PNG Annnotation HTML -->
|
||||
|
||||
<div id="mocha"></div>
|
||||
<script class="mocha-init">
|
||||
mocha.setup({
|
||||
ui: 'bdd',
|
||||
globals: [
|
||||
'flagAnnotatorPNG', 'flagToolbox', 'flagContainer', 'flagContainerAnnotation',
|
||||
'flagUploadImage', 'flagAddArrow', 'flagAddTextbox', 'flagAddSymbol',
|
||||
'flagColourPicker', 'flagEraseMode', 'flagSaveImage', 'flagCanvasAnnotation',
|
||||
'keyFabric', 'keyIsEraseMode', 'annotatorSettings', 'flagInitialised'
|
||||
]
|
||||
});
|
||||
mocha.checkLeaks();
|
||||
const expect = chai.expect;
|
||||
</script>
|
||||
|
||||
<script src="plugin_annotate_PNG,js"></script>
|
||||
<script src="test_plugin_annotate_PNG.js"></script>
|
||||
|
||||
<script class="mocha-exec">
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
<!-- Start of PNG Annotation JavaScript -->
|
||||
<script>
|
||||
var flagAnnotatorPNG = "annotator-png";
|
||||
var flagToolbox = "annotator-png-toolbox";
|
||||
var flagContainer = "annotator-png-container";
|
||||
var flagContainerAnnotation = "annotator-png-container-annotation";
|
||||
var flagUploadImage = "annotator-png-upload-image";
|
||||
var flagAddArrow = "annotator-png-add-arrow";
|
||||
var flagAddTextbox = "annotator-png-add-textbox";
|
||||
var flagAddSymbol = "annotator-png-add-symbol";
|
||||
var flagColourPicker = "annotator-png-colour-picker";
|
||||
var flagEraseMode = "annotator-png-erase-mode";
|
||||
var flagSaveImage = "annotator-png-save-image";
|
||||
var flagCanvasAnnotation = "annotator-png-canvas-annotation";
|
||||
var keyFabric = "fabric";
|
||||
var keyIsEraseMode = "isEraseMode";
|
||||
|
||||
var annotatorSettings = {};
|
||||
$(document).ready(function() {
|
||||
$("." + flagAnnotatorPNG).annotatorPNG(annotatorSettings);
|
||||
});
|
||||
</script>
|
||||
<!-- End of PNG Annotation JavaScript -->
|
||||
</html>
|
||||
114
submission/view_capture_screen.html
Normal file
114
submission/view_capture_screen.html
Normal file
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Simple Layout Template</title>
|
||||
|
||||
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
|
||||
<script src="shared.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.3.2/mocha.min.css">
|
||||
<link rel="stylesheet" href="capture-screen.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Website Title</h1>
|
||||
</header>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<main>
|
||||
<h2>Main Content</h2>
|
||||
<p>This is where the main content of your page goes. You can add articles, blog posts, or any other primary content here.</p>
|
||||
<input type="text" value="Test 1" />
|
||||
<input type="number" value="2" />
|
||||
<textarea>Test 3</textarea>
|
||||
|
||||
<!-- Start of Screen Capture Plugin HTML -->
|
||||
<button class="capture-screen" role="button">Capture Screen</button>
|
||||
<!-- End of Screen Capture Plugin HTML -->
|
||||
|
||||
<div id="notification-widget" class="modal">
|
||||
<h3>Notifications</h3>
|
||||
<p>You have 3 new messages.</p>
|
||||
</div>
|
||||
<button id="notification-toggle" class="widget-toggle">Toggle Notifications</button>
|
||||
|
||||
<div id="chat-widget" class="modal">
|
||||
<h3>Chat</h3>
|
||||
<p>Chat content goes here...</p>
|
||||
</div>
|
||||
<button id="chat-toggle" class="widget-toggle">Toggle Chat</button>
|
||||
|
||||
<div id="settings-widget" class="modal">
|
||||
<h3>Quick Settings</h3>
|
||||
<p>Adjust your settings here...</p>
|
||||
</div>
|
||||
<button id="settings-toggle" class="widget-toggle">Toggle Settings</button>
|
||||
|
||||
<div id="mocha"></div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div id="modalTest" class="modal">
|
||||
<div class="modal-content">
|
||||
<h2>Test Modal</h2>
|
||||
<p>This is a test modal.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/8.3.2/mocha.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.3.4/chai.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/10.0.0/sinon.min.js"></script>
|
||||
<div id="mocha"></div>
|
||||
|
||||
<script class="mocha-init">
|
||||
mocha.setup({
|
||||
ui: 'bdd',
|
||||
globals: [
|
||||
'flagInitialised',
|
||||
'defaultOptions', 'flagCaptureScreen',
|
||||
'idModalNotifications', 'idButtonToggleModalNotifications',
|
||||
'idModalChat', 'idButtonToggleModalChat',
|
||||
'idModalSettings', 'idButtonToggleModalSettings',
|
||||
'idModalTest'
|
||||
]
|
||||
});
|
||||
mocha.checkLeaks();
|
||||
const expect = chai.expect;
|
||||
</script>
|
||||
|
||||
<script src="plugin_capture_screen.js"></script>
|
||||
<script src="test_plugin_capture_screen.js"></script>
|
||||
|
||||
<script class="mocha-exec">
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
<!-- Start of Screen Capture Plugin JavaScript -->
|
||||
<script>
|
||||
var defaultOptions = {
|
||||
buttonText: "Capture Screen",
|
||||
fileName: "screenshot.png",
|
||||
};
|
||||
var flagCaptureScreen = "capture-screen";
|
||||
/* These variables are for testing only */
|
||||
var idModalNotifications = "#notification-widget";
|
||||
var idButtonToggleModalNotifications = "#notification-toggle";
|
||||
var idModalChat = "#chat-widget";
|
||||
var idButtonToggleModalChat = "#chat-toggle";
|
||||
var idModalSettings = "#settings-widget";
|
||||
var idButtonToggleModalSettings = "#settings-toggle";
|
||||
var idModalTest = "#modalTest";
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
$(".capture-screen").screenshotButton(defaultOptions);
|
||||
hookupTestModals();
|
||||
});
|
||||
</script>
|
||||
<!-- End of Screen Capture Plugin JavaScript -->
|
||||
</html>
|
||||
Reference in New Issue
Block a user