Skip to content

Commit

Permalink
Merge pull request #45 from fredevb/feat/web-svg--navigation
Browse files Browse the repository at this point in the history
feat: dynamic svg viewbox
  • Loading branch information
asalzburger authored Sep 1, 2023
2 parents b5a4fe4 + 7e15ac0 commit d1d0ec9
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file not shown.
9 changes: 7 additions & 2 deletions web/include/actsvg/web/tools/scripts/template/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<title>SVG Viewer</title>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
Expand All @@ -12,8 +15,10 @@ <h1>Select SVGs</h1>
<!-- The checkboxes will be added here -->
</div>

<div id="result" class="result">
<!-- The resulting SVG will be added here -->
<div id="result-div" class="result-div" onmouseover="document.body.style.overflow='hidden';" onmouseout="document.body.style.overflow='auto';">
<svg id="result-svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-300 -300 600 600">

</svg>
</div>
</div>

Expand Down
184 changes: 115 additions & 69 deletions web/include/actsvg/web/tools/scripts/template/script.js
Original file line number Diff line number Diff line change
@@ -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 <svg> and </svg> tag to obtain its content.
function removeSVGTag(data){
let startTag = /<svg[^>]*>/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++) {
Expand All @@ -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 <svg> and </svg> tag to obtain its content.
function removeSVGTag(data){
let startTag = /<svg[^>]*>/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('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-300 -300 600 600">\n');
contents.forEach(e => {
result.push(e);
result.push('\n');
});
result.push('</svg>');
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();
}
14 changes: 10 additions & 4 deletions web/include/actsvg/web/tools/scripts/template/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

6 changes: 6 additions & 0 deletions web/include/actsvg/web/web_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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 <typename iterator_t>
void build(const std::filesystem::path& output_directory, const iterator_t& svgs, comparison_function order_comparator)
{
Expand All @@ -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 <typename iterator_t>
void build(const std::filesystem::path& output_directory, const iterator_t& svgs)
{
Expand Down
Loading

0 comments on commit d1d0ec9

Please sign in to comment.