Browse Source

refactored HTML injection system

- using a <div> container instead of an <iframe> (simplifies the build
system, avoids the quirks iframes)
- Bulma CSS is encapsulated in the #simulator ID to avoid polluting the
rest of the page, as well as increasing the chances to override the page
CSS
master
Youen Toupin 3 years ago
parent
commit
3121342337
  1. 1540
      simulator/package-lock.json
  2. 3
      simulator/package.json
  3. 50
      simulator/src/app/app.css
  4. 34
      simulator/src/app/app.ts
  5. 11
      simulator/src/app/tsconfig.json
  6. 12
      simulator/src/simulator-in-iframe.css
  7. 77
      simulator/src/simulator.scss
  8. 48
      simulator/src/simulator.ts
  9. 2
      simulator/src/tsconfig.json
  10. 3
      simulator/tools/build.js
  11. 21
      simulator/tools/embed-app.js
  12. 6
      simulator/tools/embed.js
  13. 8
      simulator/tools/purify.js
  14. 8
      simulator/www/vhelio-simulator.html

1540
simulator/package-lock.json generated

File diff suppressed because it is too large Load Diff

3
simulator/package.json

@ -8,10 +8,11 @@
"directory": "simulator"
},
"scripts": {
"build": "node tools/build.js"
"build": "node tools/build.js"
},
"dependencies": {},
"devDependencies": {
"node-sass": "^6.0.1",
"purify-css": "^1.2.5",
"typescript": "^4.4.3"
},

50
simulator/src/app/app.css

@ -1,50 +0,0 @@
input[type=number] {
-moz-appearance:textfield;
}
.wide-label .field-label {
flex-grow: 2.5;
}
.dropdown.is-fullwidth {
display: flex;
}
.dropdown.is-fullwidth .dropdown-trigger,
.dropdown.is-fullwidth .dropdown-menu {
width: 100%;
}
.dropdown-trigger.with-dropdown-icon::after {
border: 3px solid black;
border-radius: 2px;
border-right: 0;
border-top: 0;
content: " ";
display: block;
height: 0.625em;
margin-top: -0.4375em;
pointer-events: none;
position: absolute;
top: 50%;
right: 15px;
transform: rotate(-45deg);
transform-origin: center;
width: 0.625em;
}
.climate-zone {
cursor: pointer;
}
svg g {
filter: drop-shadow( 4px 4px 3px rgba(0, 0, 0, .7));
}
.climate-zone:hover {
filter: brightness(1.2);
}
svg text {
pointer-events: none;
}

34
simulator/src/app/app.ts

@ -1,34 +0,0 @@
function closest (el: Element, predicate: (e: Element) => boolean) {
do if (predicate(el)) return el;
while (el = el && <Element>el.parentNode);
}
document.addEventListener('DOMContentLoaded', function() {
// Load CSS
document.getElementsByTagName('head')[0].innerHTML += (<any>window)['app.css'];
// In order to be able to style SVG elements with CSS, and register events with javascript, we must use inline SVG (we can't use an img tag)
// For this purpose, the SVG file contents are embedded in a javascript file
document.getElementById('zones-map').innerHTML = (<any>window)['climate-zones-map.svg'];
document.querySelectorAll("[data-activate-modal]").forEach(elt => {
elt.addEventListener('click', e => {
document.getElementById(elt.getAttribute('data-activate-modal')).classList.toggle('is-active', true);
});
});
document.querySelectorAll('.modal-close, .modal-card-head .delete').forEach(elt => {
elt.addEventListener('click', e => {
closest(elt, e => e.classList.contains('modal')).classList.toggle('is-active', false);
});
});
let zoneSelector = <HTMLSelectElement>document.getElementById('zone-selector');
document.querySelectorAll('.climate-zone').forEach(elt => {
elt.addEventListener('click', e => {
let zoneName = elt.getAttribute('id');
zoneSelector.value = zoneName;
closest(elt, e => e.classList.contains('modal')).classList.toggle('is-active', false);
});
});
});

11
simulator/src/app/tsconfig.json

@ -1,11 +0,0 @@
{
"compilerOptions": {
"module": "system",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "../../.intermediate/app.js",
"sourceMap": false
},
"include": ["./**/*", "../../.intermediate/app/**/*"]
}

12
simulator/src/simulator-in-iframe.css

@ -1,12 +0,0 @@
.modal-background {
display: none;
}
.modal-card-head {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.modal-card {
box-shadow: 4px 3px 10px 3px rgba(0,0,0,0.7);
}

77
simulator/src/simulator.scss

@ -0,0 +1,77 @@
#simulator {
@import "3rdparty/bulma/bulma.sass";
// properties that Bulma would set on the <html> tag
font-size: $body-size;
text-rendering: $body-rendering;
box-sizing: border-box;
margin: 0;
padding: 0;
background-color: $body-background-color;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
min-width: $body-min-width;
overflow-x: hidden;
overflow-y: auto;
text-size-adjust: 100%;
// properties that Bulma would set on the <body> tag
font-weight: $body-weight;
line-height: $body-line-height;
font-family: $body-family;
color: $body-color;
& > section, & > footer {
font-size: $body-font-size; // we must set this on each direct child of the #simulator div, because if it's specified in em, it's multiplied by the parent font-size
}
input[type=number] {
-moz-appearance:textfield;
}
.wide-label .field-label {
flex-grow: 2.5;
}
.dropdown.is-fullwidth {
display: flex;
}
.dropdown.is-fullwidth .dropdown-trigger,
.dropdown.is-fullwidth .dropdown-menu {
width: 100%;
}
.dropdown-trigger.with-dropdown-icon::after {
border: 3px solid black;
border-radius: 2px;
border-right: 0;
border-top: 0;
content: " ";
display: block;
height: 0.625em;
margin-top: -0.4375em;
pointer-events: none;
position: absolute;
top: 50%;
right: 15px;
transform: rotate(-45deg);
transform-origin: center;
width: 0.625em;
}
.climate-zone {
cursor: pointer;
}
svg g {
filter: drop-shadow( 4px 4px 3px rgba(0, 0, 0, .7));
}
.climate-zone:hover {
filter: brightness(1.2);
}
svg text {
pointer-events: none;
}
}

48
simulator/src/simulator.ts

@ -1,26 +1,36 @@
function closest (el: Element, predicate: (e: Element) => boolean) {
do if (predicate(el)) return el;
while (el = el && <Element>el.parentNode);
}
document.addEventListener('DOMContentLoaded', function() {
let frame = <HTMLIFrameElement>document.querySelector('iframe#simulator');
let doc = frame.contentWindow.document;
let container = document.getElementById('simulator');
frame.style.width = '100%';
frame.style.border = 'none';
frame.setAttribute('scrolling', 'no');
// Insert HTML code in the container
container.innerHTML += (<any>window)['simulator.html'];
// Insert HTML code in the iframe
doc.open();
doc.write((<any>window)['simulator.html']);
doc.close();
// In order to be able to style SVG elements with CSS, and register events with javascript, we must use inline SVG (we can't use an img tag)
// For this purpose, the SVG file contents are embedded in a javascript file
document.getElementById('zones-map').innerHTML = (<any>window)['climate-zones-map.svg'];
// Load iframe specific CSS
doc.head.innerHTML += (<any>window)['simulator-in-iframe.css'];
document.querySelectorAll("[data-activate-modal]").forEach(elt => {
elt.addEventListener('click', e => {
document.getElementById(elt.getAttribute('data-activate-modal')).classList.toggle('is-active', true);
});
});
// Add script inside frame
let script = doc.createElement('script');
script.type = "text/javascript";
script.innerText = (<any>window)['app.js'];
doc.body.appendChild(script);
document.querySelectorAll('.modal-close, .modal-card-head .delete').forEach(elt => {
elt.addEventListener('click', e => {
closest(elt, e => e.classList.contains('modal')).classList.toggle('is-active', false);
});
});
setInterval(() => {
frame.height = Math.max(doc.body.scrollHeight, 700) + 'px';
}, 100);
let zoneSelector = <HTMLSelectElement>document.getElementById('zone-selector');
document.querySelectorAll('.climate-zone').forEach(elt => {
elt.addEventListener('click', e => {
let zoneName = elt.getAttribute('id');
zoneSelector.value = zoneName;
closest(elt, e => e.classList.contains('modal')).classList.toggle('is-active', false);
});
});
});

2
simulator/src/tsconfig.json

@ -7,5 +7,5 @@
"outFile": "../www/simulator.js",
"sourceMap": true
},
"include": ["./*", "../.intermediate/simulator.html.ts", "../.intermediate/simulator-in-iframe.css.ts", "../.intermediate/app.js.ts"]
"include": ["./**/*", "../.intermediate/**/*"]
}

3
simulator/tools/build.js

@ -15,10 +15,9 @@ if (!fs.existsSync(intermediateDir+'/app')){
}
let commands = [
() => child_process.fork(nodeModulesDir + '/node-sass/bin/node-sass', ['simulator.scss', '../.intermediate/simulator.css'], {'cwd': srcDir}),
() => child_process.fork(toolsDir + '/purify.js', {'cwd': srcDir}),
() => child_process.fork(toolsDir + '/embed.js'),
() => child_process.fork(nodeModulesDir + '/typescript/lib/tsc.js', {'cwd': srcDir+'/app'}),
() => child_process.fork(toolsDir + '/embed-app.js'),
() => child_process.fork(nodeModulesDir + '/typescript/lib/tsc.js', {'cwd': srcDir})
];

21
simulator/tools/embed-app.js

@ -1,21 +0,0 @@
let fs = require('fs')
function embedJs(src, dst) {
fs.readFile(src, 'utf8', function(err, data) {
if(err) throw err;
data = data.replace(/\\"/g, '\\\\"');
data = "(<any>window)['"+src.replace(/^.*[\\\/]/, '')+"'] = `" + data + "`;";
fs.writeFile(dst, data, function(err) {
if(err) throw err;
});
});
}
let toolsDir = __dirname;
let dataDir = toolsDir + "/../data";
let srcDir = toolsDir + "/../src";
let intermediateDir = toolsDir + "/../.intermediate";
embedJs(intermediateDir+'/app.js', intermediateDir+'/app.js.ts');

6
simulator/tools/embed.js

@ -82,7 +82,5 @@ let srcDir = toolsDir + "/../src";
let intermediateDir = toolsDir + "/../.intermediate";
embedHtml(srcDir+'/simulator.html', intermediateDir+'/simulator.html.ts');
embedCss(intermediateDir+'/app.css', intermediateDir+'/app/app.css.ts');
embedCss(srcDir+'/simulator-in-iframe.css', intermediateDir+'/simulator-in-iframe.css.ts');
embedSvg(dataDir+'/climate-zones-map.svg', intermediateDir+'/app/climate-zones-map.svg.ts');
embedCsv(dataDir+'/climate-zones-data.csv', intermediateDir+'/app/climate-zones-data.ts');
embedSvg(dataDir+'/climate-zones-map.svg', intermediateDir+'/climate-zones-map.svg.ts');
embedCsv(dataDir+'/climate-zones-data.csv', intermediateDir+'/climate-zones-data.ts');

8
simulator/tools/purify.js

@ -4,12 +4,12 @@ const purify = require("purify-css")
let content = ['./*.html'];
// Reference of all CSS files from root directory
let css = ['3rdparty/bulma/css/bulma.css', './app/app.css'];
let css = ['../.intermediate/simulator.css'];
let options = {
output: '../.intermediate/app.css',
whitelist: ['is-multiple', 'is-loading', 'is-narrow', 'is-active', 'climate-zone'],
minify: true,
output: '../www/simulator.css',
whitelist: ['is-multiple', 'is-loading', 'is-narrow', 'is-active', 'climate-zone', 'is-max-desktop', 'is-max-widescreen'],
minify: false,
info: false
};

8
simulator/www/vhelio-simulator.html

@ -3,16 +3,20 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="simulator.css">
<style>
body { margin: 0; }
html, body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<h1>Simulateur de vhélio</h1>
<p>Estimez votre autonomie et votre consommation d'énergie</p>
<iframe id="simulator"></iframe>
<div id="simulator"></div>
<script type="text/javascript" src="simulator.js"></script>
</body>

Loading…
Cancel
Save