diff --git a/web/include/actsvg/web/tools/scripts/template/build-and-run.sh b/web/include/actsvg/web/tools/scripts/template/build-and-run.sh
index a09eeed..7c111b9 100755
--- a/web/include/actsvg/web/tools/scripts/template/build-and-run.sh
+++ b/web/include/actsvg/web/tools/scripts/template/build-and-run.sh
@@ -3,7 +3,7 @@
# Change to the directory of the Bash script
cd "$(dirname "$0")"
-# Run the build.py script
+# Run the build script
python3 rebuild.py
# Start the Python 3 HTTP server
diff --git a/web/include/actsvg/web/tools/scripts/template/favicon.ico b/web/include/actsvg/web/tools/scripts/template/favicon.ico
new file mode 100644
index 0000000..634d3f3
Binary files /dev/null and b/web/include/actsvg/web/tools/scripts/template/favicon.ico differ
diff --git a/web/include/actsvg/web/tools/scripts/template/index.html b/web/include/actsvg/web/tools/scripts/template/index.html
index e69e252..ef5a3e2 100644
--- a/web/include/actsvg/web/tools/scripts/template/index.html
+++ b/web/include/actsvg/web/tools/scripts/template/index.html
@@ -2,6 +2,9 @@
+
+
+
SVG Viewer
@@ -12,8 +15,10 @@ Select SVGs
-
diff --git a/web/include/actsvg/web/tools/scripts/template/script.js b/web/include/actsvg/web/tools/scripts/template/script.js
index 5776b8d..a13f42a 100644
--- a/web/include/actsvg/web/tools/scripts/template/script.js
+++ b/web/include/actsvg/web/tools/scripts/template/script.js
@@ -1,64 +1,86 @@
-// Initialize the page by loading a form with a button for each SVG available.
-// This is done by reading the paths in the config.json file.
-document.addEventListener("DOMContentLoaded", function () {
- fetch('./config.json').then(response => response.json()).then(json =>{
- load_form(json);
- });
-});
+let SVGResult = document.getElementById('result-svg');
+let formContainer = document.getElementById('formContainer');
+let SVGContainer = document.getElementById("result-div");
+
+// Given a collection of paths, returns a collection of containing the respective file text.
+async function readFiles(paths){
+ let result = []
+ for (const path of paths) {
+ let data = await (await fetch(path)).text();
+ result.push(data);
+ }
+ return result;
+}
+
+// Removes the and tag to obtain its content.
+function removeSVGTag(data){
+ let startTag = /]*>/i;
+ let endTag = /<\/svg>/i;
+ data = data.replace(startTag, '');
+ data = data.replace(endTag, '');
+ return data;
+}
+
+async function SVGContentMerge(paths){
+ let contents = await readFiles(paths);
+ return contents.map(removeSVGTag).join('\n');
+}
// Loads a form containing a button for each SVG available.
function load_form(group)
{
- let form = document.createElement("form");
- form.id = "checkboxForm";
+ let form = document.createElement('form');
+ form.id = 'checkboxForm';
group.forEach(item =>{
- itemDiv = document.createElement("div");
- itemDiv.classList.add("form-item");
+ itemDiv = document.createElement('div');
+ itemDiv.classList.add('form-item');
let label = document.createElement("label");
itemDiv.appendChild(label);
- let checkbox = document.createElement("input");
- checkbox.type = "checkbox";
- checkbox.name = "checkbox";
- checkbox.value = "./svgs/" + item;
+ let checkbox = document.createElement('input');
+ checkbox.type = 'checkbox';
+ checkbox.name = 'checkbox';
+ checkbox.value = './svgs/' + item;
label.append(checkbox);
- let span = document.createElement("span");
- span.textContent = get_name(item);
+ let span = document.createElement('span');
+ span.textContent = item.replace('.svg', '');
label.appendChild(span);
form.appendChild(itemDiv);
});
- form.append(document.createElement("br"));
+ form.append(document.createElement('br'));
// Create a button to apply the changes
- let apply_button = document.createElement("input");
- apply_button.type = "button";
- apply_button.value = "Apply Selection";
- apply_button.onclick = getSelectedValues;
- apply_button.classList.add("apply-button");
+ let apply_button = document.createElement('input');
+ apply_button.type = 'button';
+ apply_button.value = 'Apply Selection';
+ apply_button.onclick = applyChanges;
+ apply_button.classList.add('apply-button');
// Append the apply button to the form
form.append(apply_button);
// Append the form to the formContainer div
- const formContainer = document.getElementById("formContainer");
formContainer.appendChild(form);
}
-// For removing the file extension.
-function get_name(path){
- return path.replace(".svg", "");
-}
+// Initialize the page by loading a form with a button for each SVG available.
+// This is done by reading the paths in the config.json file.
+document.addEventListener('DOMContentLoaded', function () {
+ fetch('./config.json').then(response => response.json()).then(json =>{
+ load_form(json);
+ });
+});
// Updates the displayed SVG by combining the selected SVGs.
-async function getSelectedValues() {
- var checkboxes = document.getElementsByName("checkbox");
+async function applyChanges() {
+ var checkboxes = document.getElementsByName('checkbox');
var selectedValues = [];
for (var i = 0; i < checkboxes.length; i++) {
@@ -67,52 +89,76 @@ async function getSelectedValues() {
}
}
- selectedValues = selectedValues.reverse();
- let svg = await combineSVGS(selectedValues);
- var resultDiv = document.getElementById('result');
- resultDiv.innerHTML = svg;
+ paths = selectedValues.reverse();
+ SVGResult.innerHTML = await SVGContentMerge(paths);
}
-// Removes the and tag to obtain its content.
-function removeSVGTag(data){
- let startTag = /]*>/i;
- let endTag = /<\/svg>/i;
- data = data.replace(startTag, '');
- data = data.replace(endTag, '');
- return data;
+
+// For navigation on the svg:
+
+//--- Adjustable paramters:
+// Speed of zoom on scroll.
+const zoomFactor = 1.1;
+// To calculate the maximum width and height of the viewbox.
+const maxHalfLengths = { x: 3000, y: 3000 };
+//----
+
+let pivot = { x: 0, y: 0 };
+let position = { x: 0, y: 0 };
+let halfSize = { x: 300, y: 300 };
+const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
+
+setViewBox();
+
+SVGResult.addEventListener("mousemove", onMousemove);
+SVGResult.addEventListener("mousedown", onMousedown);
+SVGResult.addEventListener("wheel", onWheel);
+
+const mouse = {
+ position: { x: 0, y: 0 },
+ isDown: false,
+};
+
+function viewBoxDim(){
+ return {x: position.x - halfSize.x, y: position.y - halfSize.y, w: 2*halfSize.x, h: 2*halfSize.y};
}
-// Given a collection of paths, returns a collection of containing the respective file text.
-async function readFiles(paths){
- let result = []
- for (const path of paths) {
- try{
- let data = await (await fetch(path)).text();
- result.push(data);
- }
- catch(err)
- {
- console.error(err);
- }
+function onMousedown(e) {
+ mouse.position = screenToViewBoxPosition(e.pageX, e.pageY);
+ window.addEventListener("mouseup", onMouseup);
+ mouse.isDown = true;
+}
+
+function setViewBox() {
+ let dim = viewBoxDim();
+ SVGResult.setAttribute("viewBox", `${dim.x} ${dim.y} ${dim.w} ${dim.h}`);
+}
+
+function screenToViewBoxPosition(screenX, screenY){
+ return {
+ x: screenX * 2 * halfSize.x/SVGResult.clientWidth,
+ y: screenY * 2 * halfSize.y/SVGResult.clientHeight
}
- return result;
}
-// Creates an SVG from a list of svg objects by joining and appending svg start and end tags.
-function createSVG(contents){
- let result = []
- result.push('\n');
- contents.forEach(e => {
- result.push(e);
- result.push('\n');
- });
- result.push(' ');
- return result.join('');
+function onMousemove(e) {
+ if (mouse.isDown) {
+ let pos = screenToViewBoxPosition(e.pageX, e.pageY);
+ let dx = (pos.x - mouse.position.x);
+ let dy = (pos.y - mouse.position.y);
+ position = {x: clamp(position.x - dx, -maxHalfLengths.x, maxHalfLengths.x), y: clamp(position.y - dy, -maxHalfLengths.y, maxHalfLengths.y)};
+ mouse.position = pos;
+ setViewBox();
+ }
}
-// Given a list of paths to svgs, returns an svg of their combined content.
-async function combineSVGS(paths){
- let contents = await readFiles(paths);
- contents = contents.map(removeSVGTag);
- return createSVG(contents);
+function onMouseup(e) {
+ window.removeEventListener("mouseup", onMouseup);
+ mouse.isDown = false;
+}
+
+function onWheel(e) {
+ const scale = e.deltaY > 0 ? zoomFactor : 1/zoomFactor;
+ halfSize = {x: clamp(halfSize.x * scale, 1, maxHalfLengths.x), y: clamp(halfSize.y * scale, 1, maxHalfLengths.y)};
+ setViewBox();
}
diff --git a/web/include/actsvg/web/tools/scripts/template/styles.css b/web/include/actsvg/web/tools/scripts/template/styles.css
index 9dcf5cf..5ba90ca 100644
--- a/web/include/actsvg/web/tools/scripts/template/styles.css
+++ b/web/include/actsvg/web/tools/scripts/template/styles.css
@@ -59,9 +59,15 @@
font-family: monospace;
}
-.result{
- box-shadow: 0 4px 17px rgba(0, 0, 0, 0.35);
- width: 60%;
- height: 60%;
+.result-div{
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35);
+ height: 40%;
+ width: 40%;
display: inline-block;
+ cursor: grab;
}
+
+.result-div svg g{
+ cursor: pointer;
+}
+
diff --git a/web/include/actsvg/web/web_builder.hpp b/web/include/actsvg/web/web_builder.hpp
index facdcf5..24808f4 100644
--- a/web/include/actsvg/web/web_builder.hpp
+++ b/web/include/actsvg/web/web_builder.hpp
@@ -63,6 +63,8 @@ bool alphanumericCompare(const svg::object& svg1, const svg::object& svg2) {
};
/// @brief Class for generating a web page to view and merge svgs.
+/// @note When used a debugging tool and rebuilding mutliple times,
+/// if the webpage does not refresh as expected it is likely caused by browser caching.
class web_builder{
public:
@@ -73,6 +75,8 @@ class web_builder{
/// @param svgs the svgs avaible for selection on the web page.
/// @param order_comparator a compartor function to determine
/// the display order of the svgs.
+ /// @note When used a debugging tool and rebuilding mutliple times,
+ /// if the webpage does not refresh as expected it is likely caused by browser caching.
template
void build(const std::filesystem::path& output_directory, const iterator_t& svgs, comparison_function order_comparator)
{
@@ -89,6 +93,8 @@ class web_builder{
/// @param svgs the svgs avaible for selection on the web page.
/// @note uses an alpha-numeric comparator on the svgs' ids
/// to determine the display order.
+ /// @note When used a debugging tool and rebuilding mutliple times,
+ /// if the webpage does not refresh as expected it is likely caused by browser caching.
template
void build(const std::filesystem::path& output_directory, const iterator_t& svgs)
{
diff --git a/web/include/actsvg/web/webpage_text.hpp b/web/include/actsvg/web/webpage_text.hpp
index 3075ce8..b528a8d 100644
--- a/web/include/actsvg/web/webpage_text.hpp
+++ b/web/include/actsvg/web/webpage_text.hpp
@@ -16,6 +16,9 @@ const std::string index_text = R"(
+
+
+
SVG Viewer
@@ -26,8 +29,10 @@ const std::string index_text = R"(
-
@@ -37,67 +42,89 @@ const std::string index_text = R"(
)";
-const std::string script_text = R"(// Initialize the page by loading a form with a button for each SVG available.
-// This is done by reading the paths in the config.json file.
-document.addEventListener("DOMContentLoaded", function () {
- fetch('./config.json').then(response => response.json()).then(json =>{
- load_form(json);
- });
-});
+const std::string script_text = R"(let SVGResult = document.getElementById('result-svg');
+let formContainer = document.getElementById('formContainer');
+let SVGContainer = document.getElementById("result-div");
+
+// Given a collection of paths, returns a collection of containing the respective file text.
+async function readFiles(paths){
+ let result = []
+ for (const path of paths) {
+ let data = await (await fetch(path)).text();
+ result.push(data);
+ }
+ return result;
+}
+
+// Removes the and tag to obtain its content.
+function removeSVGTag(data){
+ let startTag = /]*>/i;
+ let endTag = /<\/svg>/i;
+ data = data.replace(startTag, '');
+ data = data.replace(endTag, '');
+ return data;
+}
+
+async function SVGContentMerge(paths){
+ let contents = await readFiles(paths);
+ return contents.map(removeSVGTag).join('\n');
+}
// Loads a form containing a button for each SVG available.
function load_form(group)
{
- let form = document.createElement("form");
- form.id = "checkboxForm";
+ let form = document.createElement('form');
+ form.id = 'checkboxForm';
group.forEach(item =>{
- itemDiv = document.createElement("div");
- itemDiv.classList.add("form-item");
+ itemDiv = document.createElement('div');
+ itemDiv.classList.add('form-item');
let label = document.createElement("label");
itemDiv.appendChild(label);
- let checkbox = document.createElement("input");
- checkbox.type = "checkbox";
- checkbox.name = "checkbox";
- checkbox.value = "./svgs/" + item;
+ let checkbox = document.createElement('input');
+ checkbox.type = 'checkbox';
+ checkbox.name = 'checkbox';
+ checkbox.value = './svgs/' + item;
label.append(checkbox);
- let span = document.createElement("span");
- span.textContent = get_name(item);
+ let span = document.createElement('span');
+ span.textContent = item.replace('.svg', '');
label.appendChild(span);
form.appendChild(itemDiv);
});
- form.append(document.createElement("br"));
+ form.append(document.createElement('br'));
// Create a button to apply the changes
- let apply_button = document.createElement("input");
- apply_button.type = "button";
- apply_button.value = "Apply Selection";
- apply_button.onclick = getSelectedValues;
- apply_button.classList.add("apply-button");
+ let apply_button = document.createElement('input');
+ apply_button.type = 'button';
+ apply_button.value = 'Apply Selection';
+ apply_button.onclick = applyChanges;
+ apply_button.classList.add('apply-button');
// Append the apply button to the form
form.append(apply_button);
// Append the form to the formContainer div
- const formContainer = document.getElementById("formContainer");
formContainer.appendChild(form);
}
-// For removing the file extension.
-function get_name(path){
- return path.replace(".svg", "");
-}
+// Initialize the page by loading a form with a button for each SVG available.
+// This is done by reading the paths in the config.json file.
+document.addEventListener('DOMContentLoaded', function () {
+ fetch('./config.json').then(response => response.json()).then(json =>{
+ load_form(json);
+ });
+});
// Updates the displayed SVG by combining the selected SVGs.
-async function getSelectedValues() {
- var checkboxes = document.getElementsByName("checkbox");
+async function applyChanges() {
+ var checkboxes = document.getElementsByName('checkbox');
var selectedValues = [];
for (var i = 0; i < checkboxes.length; i++) {
@@ -106,54 +133,78 @@ async function getSelectedValues() {
}
}
- selectedValues = selectedValues.reverse();
- let svg = await combineSVGS(selectedValues);
- var resultDiv = document.getElementById('result');
- resultDiv.innerHTML = svg;
+ paths = selectedValues.reverse();
+ SVGResult.innerHTML = await SVGContentMerge(paths);
}
-// Removes the and tag to obtain its content.
-function removeSVGTag(data){
- let startTag = /]*>/i;
- let endTag = /<\/svg>/i;
- data = data.replace(startTag, '');
- data = data.replace(endTag, '');
- return data;
+
+// For navigation on the svg:
+
+//--- Adjustable paramters:
+// Speed of zoom on scroll.
+const zoomFactor = 1.1;
+// To calculate the maximum width and height of the viewbox.
+const maxHalfLengths = { x: 3000, y: 3000 };
+//----
+
+let pivot = { x: 0, y: 0 };
+let position = { x: 0, y: 0 };
+let halfSize = { x: 300, y: 300 };
+const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
+
+setViewBox();
+
+SVGResult.addEventListener("mousemove", onMousemove);
+SVGResult.addEventListener("mousedown", onMousedown);
+SVGResult.addEventListener("wheel", onWheel);
+
+const mouse = {
+ position: { x: 0, y: 0 },
+ isDown: false,
+};
+
+function viewBoxDim(){
+ return {x: position.x - halfSize.x, y: position.y - halfSize.y, w: 2*halfSize.x, h: 2*halfSize.y};
}
-// Given a collection of paths, returns a collection of containing the respective file text.
-async function readFiles(paths){
- let result = []
- for (const path of paths) {
- try{
- let data = await (await fetch(path)).text();
- result.push(data);
- }
- catch(err)
- {
- console.error(err);
- }
+function onMousedown(e) {
+ mouse.position = screenToViewBoxPosition(e.pageX, e.pageY);
+ window.addEventListener("mouseup", onMouseup);
+ mouse.isDown = true;
+}
+
+function setViewBox() {
+ let dim = viewBoxDim();
+ SVGResult.setAttribute("viewBox", `${dim.x} ${dim.y} ${dim.w} ${dim.h}`);
+}
+
+function screenToViewBoxPosition(screenX, screenY){
+ return {
+ x: screenX * 2 * halfSize.x/SVGResult.clientWidth,
+ y: screenY * 2 * halfSize.y/SVGResult.clientHeight
}
- return result;
}
-// Creates an SVG from a list of svg objects by joining and appending svg start and end tags.
-function createSVG(contents){
- let result = []
- result.push('\n');
- contents.forEach(e => {
- result.push(e);
- result.push('\n');
- });
- result.push(' ');
- return result.join('');
+function onMousemove(e) {
+ if (mouse.isDown) {
+ let pos = screenToViewBoxPosition(e.pageX, e.pageY);
+ let dx = (pos.x - mouse.position.x);
+ let dy = (pos.y - mouse.position.y);
+ position = {x: clamp(position.x - dx, -maxHalfLengths.x, maxHalfLengths.x), y: clamp(position.y - dy, -maxHalfLengths.y, maxHalfLengths.y)};
+ mouse.position = pos;
+ setViewBox();
+ }
}
-// Given a list of paths to svgs, returns an svg of their combined content.
-async function combineSVGS(paths){
- let contents = await readFiles(paths);
- contents = contents.map(removeSVGTag);
- return createSVG(contents);
+function onMouseup(e) {
+ window.removeEventListener("mouseup", onMouseup);
+ mouse.isDown = false;
+}
+
+function onWheel(e) {
+ const scale = e.deltaY > 0 ? zoomFactor : 1/zoomFactor;
+ halfSize = {x: clamp(halfSize.x * scale, 1, maxHalfLengths.x), y: clamp(halfSize.y * scale, 1, maxHalfLengths.y)};
+ setViewBox();
}
)";
@@ -218,12 +269,18 @@ const std::string css_text = R"(.app{
font-family: monospace;
}
-.result{
- box-shadow: 0 4px 17px rgba(0, 0, 0, 0.35);
- width: 60%;
- height: 60%;
+.result-div{
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35);
+ height: 40%;
+ width: 40%;
display: inline-block;
+ cursor: grab;
}
+
+.result-div svg g{
+ cursor: pointer;
+}
+
)";
const std::string rebuild_text = R"(import os