diff --git a/submission - backup/README b/submission - backup/README deleted file mode 100644 index f1fb175..0000000 --- a/submission - backup/README +++ /dev/null @@ -1,72 +0,0 @@ -# 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 - - - - -``` - -Initialize the plugin on a button: - -```javascript -$('.capture-screen').screenshotButton(); -``` - -### Image Annotation - -Include the necessary files in your HTML: - -```html - - - - -``` - -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 \ No newline at end of file diff --git a/submission - backup/capture-screen.css b/submission - backup/capture-screen.css deleted file mode 100644 index 128dade..0000000 --- a/submission - backup/capture-screen.css +++ /dev/null @@ -1,101 +0,0 @@ - -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%; -} \ No newline at end of file diff --git a/submission - backup/display-png.css b/submission - backup/display-png.css deleted file mode 100644 index 759e92e..0000000 --- a/submission - backup/display-png.css +++ /dev/null @@ -1,15 +0,0 @@ - -.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; -} \ No newline at end of file diff --git a/submission - backup/plugin_capture_screen.js b/submission - backup/plugin_capture_screen.js deleted file mode 100644 index b222577..0000000 --- a/submission - backup/plugin_capture_screen.js +++ /dev/null @@ -1,152 +0,0 @@ - -(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); - } -} \ No newline at end of file diff --git a/submission - backup/plugin_display_png,js b/submission - backup/plugin_display_png,js deleted file mode 100644 index 97eb8ef..0000000 --- a/submission - backup/plugin_display_png,js +++ /dev/null @@ -1,274 +0,0 @@ - -(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($("", { - 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)); diff --git a/submission - backup/screenshot (16).png b/submission - backup/screenshot (16).png deleted file mode 100644 index 0bf60b0..0000000 Binary files a/submission - backup/screenshot (16).png and /dev/null differ diff --git a/submission - backup/shared.js b/submission - backup/shared.js deleted file mode 100644 index 7013b5f..0000000 --- a/submission - backup/shared.js +++ /dev/null @@ -1,13 +0,0 @@ - -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(); - }); -} \ No newline at end of file diff --git a/submission - backup/symbols/Orange Arrow.png b/submission - backup/symbols/Orange Arrow.png deleted file mode 100644 index 3c85229..0000000 Binary files a/submission - backup/symbols/Orange Arrow.png and /dev/null differ diff --git a/submission - backup/test_plugin_capture_screen_Mocha.js b/submission - backup/test_plugin_capture_screen_Mocha.js deleted file mode 100644 index d648d85..0000000 --- a/submission - backup/test_plugin_capture_screen_Mocha.js +++ /dev/null @@ -1,527 +0,0 @@ - -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 = $('').appendTo('body'); - $textarea = $('test').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 = $('Modal Content').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) { - $('').appendTo('body'); - $('' + textareaValue + '').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(); - $('').appendTo('body'); - $('' + textareaValue + '').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(''); - $('.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() {}); - }); -}); \ No newline at end of file diff --git a/submission - backup/test_plugin_display_png_Mocha.js b/submission - backup/test_plugin_display_png_Mocha.js deleted file mode 100644 index 38f0fb6..0000000 --- a/submission - backup/test_plugin_display_png_Mocha.js +++ /dev/null @@ -1,376 +0,0 @@ - -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(''); - $('.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); - }); - }); -}); \ No newline at end of file diff --git a/submission - backup/view_capture_screen.html b/submission - backup/view_capture_screen.html deleted file mode 100644 index 2b71544..0000000 --- a/submission - backup/view_capture_screen.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - Simple Layout Template - - - - - - - - - - - Website Title - - - - - Main Content - This is where the main content of your page goes. You can add articles, blog posts, or any other primary content here. - - - Test 3 - - - Capture Screen - - - - Notifications - You have 3 new messages. - - Toggle Notifications - - - Chat - Chat content goes here... - - Toggle Chat - - - Quick Settings - Adjust your settings here... - - Toggle Settings - - - - - - - - Test Modal - This is a test modal. - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/submission - backup/view_display_png.html b/submission - backup/view_display_png.html deleted file mode 100644 index 8fde85b..0000000 --- a/submission - backup/view_display_png.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - Image Annotation Control - - - - - - - - - - - - - - - - - Add Arrow - Add Textbox - - Select Symbol - - - Erase Mode - Save Image - - - - - - - - - - - - - - - - - - - - \ No newline at end of file
This is where the main content of your page goes. You can add articles, blog posts, or any other primary content here.
You have 3 new messages.
Chat content goes here...
Adjust your settings here...
This is a test modal.