Commit 3076932e authored by Klaus Ramm's avatar Klaus Ramm

Change global options to icon based theming and adapt JS for that

parent a914650e
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 2; /* Sit on top */
align-items: center;
bottom: 0;
display: none;
justify-content: center;
left: 0;
opacity: 0;
overflow: hidden;
padding: 0.5em;
position: fixed;
right: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
/* Modal Content/Box */
.modal-content {
background-color: #fefefe;
margin: 15% auto; /* 15% from the top and centered */
padding: 20px;
border: 1px solid #888;
width: 80%; /* Could be more or less, depending on screen size */
}
&:target,
&.active {
display: flex;
opacity: 1;
z-index: 2;
.modal-overlay {
background: rgba(0,0,0,0.4);
bottom: 0;
cursor: default;
display: block;
left: 0;
position: absolute;
right: 0;
top: 0;
}
/* The Close Button */
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
.modal-container {
animation: slide-down .2s ease 1;
z-index: 1;
}
}
&.modal-sm {
.modal-container {
max-width: 10em;
padding: 0 1em;
}
}
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
.modal-container {
background: rgb(250, 250, 250);
border-radius: 0.1em;
display: flex;
flex-direction: column;
max-height: 75vh;
max-width: 25em;
padding: 0 1em;
width: 100%;
&.modal-fullheight {
max-height: 100vh;
}
.modal-header {
padding: 0 1em;
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
}
.modal-body {
overflow-y: auto;
padding: 0 1em;
position: relative;
}
.modal-footer {
padding: 1em;
text-align: right;
.modal-buttons {
flex: auto;
min-height: 1.5em;
margin: 0 1em;
border: 0.15em solid rgb(38, 64, 64);
border-radius: 0.2em;
}
}
}
......@@ -12,7 +12,13 @@ $main-background-color: rgba(128,212,255, 0.5);
}
.fixedHalfHeight {
flex: 0 0 1em;
flex-grow: 0;
flex-shrink: 0;
flex-basis: 0.7em;
transition: flex-basis 0.2s ease-in-out;
&:hover {
flex-basis: 1.3em;
}
}
.fixedHeight {
......@@ -41,15 +47,19 @@ $main-background-color: rgba(128,212,255, 0.5);
}
.symbol {
margin-top: 6.5px;
margin: 6.5px 0;
.input-group {
margin-top: -6px;
}
}
.margin-small {
margin-left: 0.2em;
}
.symbolHeight {
height: 1em;
flex: auto;
}
.columnInput {
......@@ -63,16 +73,16 @@ $main-background-color: rgba(128,212,255, 0.5);
}
.insertButton {
border: 0.15em solid black;
border: 0.15em solid rgb(38, 64, 64);
text-align: center;
margin: 0.2em;
border-radius: 0.2em;
display: flex;
justify-content: center;
.buttonLogo {
float: left;
height: 1em;
height: 1.3em;
min-width: 2em;
margin: auto;
flex: 1;
......@@ -105,14 +115,12 @@ $main-background-color: rgba(128,212,255, 0.5);
}
}
.columnText {
flex: auto;
}
.optionContainer {
position: absolute;
z-index: 1;
top: 0;
right: 1em;
height: 100%;
......@@ -136,55 +144,6 @@ $main-background-color: rgba(128,212,255, 0.5);
}
}
.tooltip {
position: relative;
&:after {
background: rgba(50, 50, 50, 0.95);
border-radius: 0;
bottom: 100%;
color: white;
content: attr(data-tooltip);
display: block;
left: -100%;
max-width: 15em;
opacity: 0;
overflow: hidden;
padding: 0.3em;
pointer-events: none;
position: absolute;
text-overflow: ellipsis;
transform: translate(-50%, 0.3em);
transition: opacity .2s, transform .2s;
white-space: pre;
z-index: 2;
}
&:focus,
&:hover {
&::after {
opacity: 1;
transform: translate(-50%, -0.3em);
}
}
&[disabled],
&.disabled {
pointer-events: auto;
}
&.tooltip-bottom {
&::after {
bottom: auto;
top: 75%;
transform: translate(-50%, -0.3em);
}
&:focus,
&:hover {
&::after {
transform: translate(-50%, 0.3em);
}
}
}
}
.columnFull {
flex-basis: 100%;
......@@ -198,12 +157,6 @@ $main-background-color: rgba(128,212,255, 0.5);
align-self: flex-end;
}
// display borders around elements
.simpleBorder {
margin: 0 1px;
background-color: $main-background-color;
}
.loopShift {
justify-content: flex-end !important;
......
......@@ -5,6 +5,10 @@
$font-stack: "Verdana", Geneva, sans-serif;
header {
margin-top: 0.5em;
}
body {
font: 100% $font-stack;
max-width: 1500px;
......@@ -13,6 +17,7 @@ body {
display: flex;
flex-direction: column;
justify-content: center;
background-color: rgb(250, 250, 250);
main {
flex: 1;
......@@ -58,6 +63,17 @@ body {
flex-wrap: wrap;
align-items: center;
justify-content: center;
a {
text-decoration: none;
margin-left: calc(0.5em + 2px);
}
strong {
padding-left: 1em;
color: rgb(38, 64, 64);
font-size: 1.5em;
}
}
.nav-col {
......@@ -65,12 +81,29 @@ body {
margin: auto;
}
.nav-col-opt {
flex-basis: 100%;
margin-top: 0.5em;
}
@media screen and (min-width: 731px) {
.nav-col-opt {
flex: auto;
margin: auto;
}
}
.options-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: baseline;
justify-content: flex-end;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
}
.options-element {
height: 3em;
flex-basis: 5em;
}
.hand {
......@@ -78,6 +111,7 @@ body {
}
.columnEditorFull {
margin-top: 0.5em;
flex-basis: 100%;
}
......@@ -119,3 +153,75 @@ body {
.ov-hidden {
overflow: hidden;
}
.tooltip {
position: relative;
&:after {
background: rgba(50, 50, 50, 0.95);
border-radius: 0;
bottom: 100%;
color: white;
content: attr(data-tooltip);
display: block;
left: 50%;
max-width: 15em;
opacity: 0;
overflow: hidden;
padding: 0.3em;
pointer-events: none;
position: absolute;
text-overflow: ellipsis;
transform: translate(-50%, 0.3em);
transition: opacity .2s, transform .2s;
white-space: pre;
z-index: 1;
}
&:focus,
&:hover {
&::after {
opacity: 1;
transform: translate(-50%, -0.3em);
}
}
&[disabled],
&.disabled {
pointer-events: auto;
}
&.tooltip-bottom {
&::after {
bottom: auto;
top: 100%;
transform: translate(-50%, -0.3em);
}
&:focus,
&:hover {
&::after {
transform: translate(-50%, 0.3em);
}
}
}
&.tooltip-bottoml {
&::after {
bottom: auto;
top: 75%;
left: -100%;
transform: translate(-50%, -0.3em);
}
&:focus,
&:hover {
&::after {
transform: translate(-50%, 0.3em);
}
}
}
}
.divider {
display: block;
position: relative;
border-top: 0.1em solid rgb(38, 64, 64);
height: 0.1em;
margin: 0.2em 0;
}
......@@ -13,47 +13,49 @@
<header class="container">
<section class="nav-col">
<div class="nav-logo-container">
<div class="logo-container logo"></div>
<a href="index.html">
<strong>Struktog.</strong>
</a>
<a href="index.html" class="column container">
<div class="logo-container logo"></div>
<strong class="nav-col">Struktog.</strong>
</a>
</div>
</section>
<section class="nav-col">
<div id="optionButtons" class="options-container">
<div class="column container">
<button class="column" onclick="document.getElementById('IEModal').style.display = 'block';">
Import / Export
</button>
</div>
</div>
<section class="nav-col-opt">
<div id="optionButtons" class="options-container"></div>
</section>
</header>
<div class="divider"></div>
<main>
<div id="editorDisplay" class="container">
</div>
<!-- Popup modal for import and export -->
<div id="IEModal" class="modal">
<div class="modal-content">
<span class="close"
onclick="document.getElementById('IEModal').style.display = 'none';">
&times;
</span>
<p>
JSON-Import:
</p>
<input id="ImportForm" type="file" class="form-input" \>
<p>
Export:
</p>
<div id="Export" class="text-center pt-1"></div>
<div class="modal" id="IEModal">
<div class="modal-overlay"
aria-label="Close"
onclick="document.getElementById('IEModal').classList.remove('active');">
</div>
<div class="modal-container">
<div class="modal-header">
<span class="close hand"
onclick="document.getElementById('IEModal').classList.remove('active');">
&times;
</span>
</div>
<div class="modal-body">
<div id="modal-content"
class="content">
Content
</div>
</div>
<div id="modal-footer"
class="modal-footer container">
Footer
</div>
</div>
</div>
</main>
<footer class="container">
<div class="column">
<span>
&#169; 2019 Klaus Ramm
© 2019 Klaus Ramm
</span>
<span>
(
......
......@@ -32,13 +32,29 @@ window.onload = function() {
// reset button must be last defined
let resetButtonDiv = document.createElement('div');
resetButtonDiv.classList.add('column', 'container');
let resetButton = document.createElement('button');
resetButton.classList.add('column');
resetButton.addEventListener('click', () => presenter.resetModel());
resetButton.appendChild(document.createTextNode('Reset'));
resetButtonDiv.classList.add('options-element', 'resetIcon', 'tooltip', 'tooltip-bottom', 'hand');
resetButtonDiv.setAttribute('data-tooltip', 'Reset');
resetButtonDiv.addEventListener('click', () => {
const content = document.getElementById('modal-content');
const footer = document.getElementById('modal-footer');
while (content.hasChildNodes()) {
content.removeChild(content.lastChild);
}
while (footer.hasChildNodes()) {
footer.removeChild(footer.lastChild);
}
content.appendChild(document.createTextNode("Alles löschen?"));
const doButton = document.createElement('div');
doButton.classList.add('modal-buttons', 'acceptIcon', 'hand');
doButton.addEventListener('click', () => presenter.resetModel());
footer.appendChild(doButton);
const cancelButton = document.createElement('div');
cancelButton.classList.add('modal-buttons', 'deleteIcon', 'hand');
cancelButton.addEventListener('click', () => document.getElementById('IEModal').classList.remove('active'));
footer.appendChild(cancelButton);
resetButtonDiv.appendChild(resetButton);
document.getElementById('IEModal').classList.add('active');
});
document.getElementById('optionButtons').appendChild(resetButtonDiv);
presenter.init();
......
......@@ -21,6 +21,11 @@ export class Presenter {
}
getModelTree() {
return this.model.getTree();
}
resetButtons() {
for (const view of this.views) {
view.resetButtons();
......@@ -284,6 +289,7 @@ export class Presenter {
this.model.reset();
this.updateBrowserStore();
this.renderAllViews();
document.getElementById('IEModal').classList.remove('active');
}
/**
......@@ -395,6 +401,24 @@ export class Presenter {
}
getStringifiedTree() {
return JSON.stringify(this.model.getTree());
}
saveDialog() {
// define the data url to start a download on click
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(this.getStringifiedTree());
// create filename with current date in the name
const exportFileDefaultName = 'struktog_' + (new Date(Date.now()).toJSON()).substring(0, 10) + '.json';
// generate the download button element and append it to the node
let linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click();
}
/**
* Read input from a JSON file and replace the current model
*/
......
......@@ -148,14 +148,10 @@ export class CodeView {
this.domRoot = document.getElementById('Sourcecode');
let options = document.createElement('div');
options.classList.add('column', 'container');
let optionButton = document.createElement('button');
optionButton.classList.add('column');
optionButton.id = 'ToggleSourcecode';
optionButton.addEventListener('click', (event) => this.presenter.alterSourcecodeDisplay(event));
optionButton.appendChild(document.createTextNode('Quellcode einblenden'));
options.appendChild(optionButton);
options.classList.add('options-element', 'codeIcon', 'tooltip', 'tooltip-bottom', 'hand');
options.setAttribute('data-tooltip', 'Quellcode einblenden');
options.id = 'ToggleSourcecode';
options.addEventListener('click', (event) => this.presenter.alterSourcecodeDisplay(event));
document.getElementById('optionButtons').appendChild(options);
if (typeof(Storage) !== "undefined") {
......@@ -508,12 +504,10 @@ export class CodeView {
*/
displaySourcecode(buttonId) {
if (this.presenter.getSourcecodeDisplay()) {
document.getElementById(buttonId).textContent = "Quellcode ausblenden";
//document.getElementById(buttonId).classList.add('btn-primary');
document.getElementById(buttonId).setAttribute('data-tooltip', 'Quellcode ausblenden');
document.getElementById('SourcecodeDisplay').style.display = "block";
} else {
document.getElementById(buttonId).textContent = "Quellcode einblenden";
//document.getElementById(buttonId).classList.remove('btn-primary');
document.getElementById(buttonId).setAttribute('data-tooltip', 'Quellcode einblenden');
document.getElementById('SourcecodeDisplay').style.display = "none";
}
}
......
......@@ -2,48 +2,37 @@ export class ImportExport {
constructor(presenter, domRoot) {
this.presenter = presenter;
this.domRoot = domRoot;
this.printHeight = 48;
this.printHeight = 32;
document.getElementById('ImportForm').addEventListener('change', (e) => this.presenter.readFile(e));
this.preRender();
}
/**
* Generate a JSON file of the current model status and append a button element for download
*/
render(model) {
// remove old export elements
while (this.domRoot.hasChildNodes()) {
this.domRoot.removeChild(this.domRoot.lastChild);
}
// transform the model into a JSON object
const dataStr = JSON.stringify(model);
// define the data url to start a download on click
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
render(model) {}
// create filename with current date in the name
const exportFileDefaultName = 'struktog_' + (new Date(Date.now()).toJSON()).substring(0, 10) + '.json';
// generate the download button element and append it to the node
let linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.classList.add('btn');
linkElement.classList.add('btn-sm');
linkElement.appendChild(document.createTextNode('JSON'));
linkElement.addEventListener('click', () => {
document.getElementById('IEModal').classList.remove('active');
});
let div = document.createElement('div');
div.classList.add('column');
div.classList.add('elementButtonColumns');
preRender() {
const importDiv = document.createElement('div');
importDiv.classList.add('options-element', 'uploadIcon', 'tooltip', 'tooltip-bottom', 'hand');
importDiv.setAttribute('data-tooltip', 'Laden');
const importInput = document.createElement('input');
importInput.setAttribute('type', 'file');
importInput.addEventListener('change', (e) => this.presenter.readFile(e));
importDiv.addEventListener('click', () => importInput.click());
document.getElementById('optionButtons').appendChild(importDiv);
div.appendChild(linkElement);
this.domRoot.appendChild(div);
const saveDiv = document.createElement('div');
saveDiv.classList.add('options-element', 'saveIcon', 'tooltip', 'tooltip-bottom', 'hand');
saveDiv.setAttribute('data-tooltip', 'Speichern');
saveDiv.addEventListener('click', () => this.presenter.saveDialog());
document.getElementById('optionButtons').appendChild(saveDiv);
this.exportAsPng(model);
// right now only png export exists, in the future a dialog should be opened
const exportDiv = document.createElement('div');
exportDiv.classList.add('options-element', 'exportIcon', 'tooltip', 'tooltip-bottom', 'hand');
exportDiv.setAttribute('data-tooltip', 'Bildexport');
exportDiv.addEventListener('click', () => this.exportAsPng(this.presenter.getModelTree()));
document.getElementById('optionButtons').appendChild(exportDiv);
}
......@@ -62,6 +51,7 @@ export class ImportExport {
if (subTree === null) {
return y
} else {
const defaultMargin = 22;
// use for every possible element type a different drawing strategie
switch (subTree.type) {
case 'InsertNode':
......@@ -98,7 +88,7 @@ export class ImportExport {
ctx.lineTo(xmax, y + this.printHeight);
ctx.stroke();