Add basic interaction
parent
a0d5b5f0b9
commit
28ca48667f
4
Makefile
4
Makefile
|
@ -60,7 +60,9 @@ assets/js/third-party/luxon.min.js:
|
|||
ICONS = \
|
||||
solid_sun \
|
||||
solid_moon \
|
||||
solid_adjust
|
||||
solid_adjust \
|
||||
solid_trash \
|
||||
solid_plus
|
||||
|
||||
.PHONY: download-icons
|
||||
download-icons: $(foreach icon,$(ICONS),templates/icon_$(icon).svg)
|
||||
|
|
|
@ -66,6 +66,12 @@ footer {
|
|||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
/* Web Component Fixes */
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Essentials */
|
||||
|
||||
.list-inline {
|
||||
|
@ -202,7 +208,7 @@ d-zoneerror {
|
|||
}
|
||||
|
||||
/* Left container */
|
||||
d-zoneinfo {
|
||||
.zoneinfo {
|
||||
flex: 1 1 0;
|
||||
width: 0px; /* Force sizing from zero */
|
||||
|
||||
|
@ -212,7 +218,7 @@ d-zoneinfo {
|
|||
margin-right: 1rem;
|
||||
}
|
||||
@media (max-width: 319px) {
|
||||
d-zoneinfo {
|
||||
.zoneinfo {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +227,7 @@ d-zonename {
|
|||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
d-zonename d-zonearea {
|
||||
d-zonename .zonearea {
|
||||
flex: 0 1 auto;
|
||||
|
||||
display: block;
|
||||
|
@ -230,7 +236,7 @@ d-zonename d-zonearea {
|
|||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
d-zonename d-zonecountry {
|
||||
d-zonename .zonecountry {
|
||||
flex: 0 0 auto;
|
||||
|
||||
display: block;
|
||||
|
@ -251,7 +257,7 @@ d-date {
|
|||
}
|
||||
|
||||
/* Right container */
|
||||
d-zonefigure {
|
||||
.zonefigure {
|
||||
display: block;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
|
|
|
@ -1 +1,339 @@
|
|||
// TODO
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* Compatibility Checks
|
||||
*/
|
||||
|
||||
if (!window.customElements) {
|
||||
console.warn('Custom Elements API is not available. Thus, interactivity is not available');
|
||||
}
|
||||
|
||||
/*
|
||||
* Custom Elements
|
||||
*/
|
||||
|
||||
const slotTemplate = document.createElement('template');
|
||||
slotTemplate.innerHTML = `
|
||||
<slot></slot>
|
||||
`;
|
||||
|
||||
// Icons
|
||||
|
||||
const iconSolidTrashTemplate = document.querySelector('#icon-solid-trash');
|
||||
class IconSolidTrashElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.appendChild(iconSolidTrashTemplate.content.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
const iconSolidPlusTemplate = document.querySelector('#icon-solid-plus');
|
||||
class IconSolidPlusElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.appendChild(iconSolidPlusTemplate.content.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
// Zone
|
||||
|
||||
const zoneTemplate = document.createElement('template');
|
||||
zoneTemplate.innerHTML = `
|
||||
<style>
|
||||
#toolbar {
|
||||
/*
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
*/
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.2em 0.5em;
|
||||
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
font-family: inherit;
|
||||
font-size: 1em;
|
||||
}
|
||||
button.delete {
|
||||
background: rgba(255, 0, 0, 0.125);
|
||||
color: rgba(255, 0, 0, 1);
|
||||
}
|
||||
</style>
|
||||
<slot></slot>
|
||||
<div id="toolbar">
|
||||
<button type="button" class="delete" title="Delete this zone"><icon-solid-trash /></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
class ZoneElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.appendChild(zoneTemplate.content.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
// ZoneName
|
||||
|
||||
const zoneNameTemplate = document.createElement('template');
|
||||
zoneNameTemplate.innerHTML = `
|
||||
<!--
|
||||
<style>
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.zonearea {
|
||||
flex: 0 1 auto;
|
||||
|
||||
display: block;
|
||||
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.zonecountry {
|
||||
flex: 0 0 auto;
|
||||
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
-->
|
||||
<slot></slot>
|
||||
`;
|
||||
|
||||
class ZoneNameElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.appendChild(zoneNameTemplate.content.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
// ZoneOffset
|
||||
|
||||
const zoneOffsetTemplate = document.createElement('template');
|
||||
zoneOffsetTemplate.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
|
||||
white-space: nowrap;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
<slot></slot>
|
||||
`;
|
||||
|
||||
class ZoneOffsetElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.appendChild(zoneOffsetTemplate.content.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
// Date
|
||||
|
||||
class DateElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.appendChild(slotTemplate.content.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
// Time
|
||||
|
||||
class TimeElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.appendChild(slotTemplate.content.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
// SearchList
|
||||
|
||||
const zoneSearchTemplate = document.createElement('template');
|
||||
zoneSearchTemplate.innerHTML = `
|
||||
<style>
|
||||
#layout {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input[type=search] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid rgba(127, 127, 127, 1);
|
||||
background: none;
|
||||
color: inherit;
|
||||
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
ul {
|
||||
/*
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
*/
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
list-style: none;
|
||||
box-shadow: 4px 12px 12px rgba(127, 127, 127, 0.5);
|
||||
}
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
border: 1px solid rgba(127, 127, 127, 1);
|
||||
border-top: none;
|
||||
}
|
||||
ul, li:last-of-type {
|
||||
border-bottom-right-radius: 6px;
|
||||
border-bottom-left-radius: 6px;
|
||||
}
|
||||
li .left {
|
||||
flex: 1 1 0;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
li .right {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
button {
|
||||
padding: 0.2em 0.5em;
|
||||
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
font-family: inherit;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
button.add {
|
||||
background: rgba(0, 180, 0, 0.125);
|
||||
color: rgba(0, 180, 0, 1);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="layout">
|
||||
<input type="search" placeholder="Search for a timezone...">
|
||||
|
||||
<template id="result-template">
|
||||
<li>
|
||||
<div class="left">
|
||||
<d-zonename>Zone Name</d-zonename><d-zoneoffset>+XX:XX</d-zoneoffset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button type="button" class="add" title="Add this zone"><icon-solid-plus /></button>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
<ul id="results"></ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
class ZoneSearchElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.appendChild(zoneSearchTemplate.content.cloneNode(true));
|
||||
this.shadowRoot.querySelector('input[type=search]').addEventListener('input', this.input.bind(this));
|
||||
}
|
||||
|
||||
input() {
|
||||
const text = this.shadowRoot.querySelector('input[type=search]').value;
|
||||
const e = new CustomEvent('searchinput', { detail: { text } });
|
||||
this.dispatchEvent(e);
|
||||
}
|
||||
|
||||
show(results) {
|
||||
const resultsElement = this.shadowRoot.querySelector('#results');
|
||||
const template = this.shadowRoot.querySelector('#result-template').content;
|
||||
|
||||
resultsElement.innerHTML = '';
|
||||
for (const result of results) {
|
||||
const resultElement = template.cloneNode(true);
|
||||
resultElement.querySelector('d-zonename').innerText = result.n;
|
||||
resultElement.querySelector('d-zoneoffset').innerText = '+to:do';
|
||||
resultElement.querySelector('.add').dataset.id = result.id;
|
||||
resultElement.querySelector('.add').addEventListener('click', (e) => {
|
||||
this.resultClick(result);
|
||||
});
|
||||
resultsElement.appendChild(resultElement);
|
||||
}
|
||||
}
|
||||
|
||||
resultClick(zone) {
|
||||
console.debug(zone.id);
|
||||
}
|
||||
}
|
||||
|
||||
// ZoneAdd
|
||||
|
||||
const zoneAddTemplate = document.createElement('template');
|
||||
zoneAddTemplate.innerHTML = `
|
||||
<style>
|
||||
#layout {
|
||||
margin-left: 0.75rem;
|
||||
margin-right: 0.753rem;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="layout">
|
||||
<d-zoneadd-search></d-zoneadd-search>
|
||||
</div>
|
||||
`;
|
||||
|
||||
class ZoneAddElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.attachShadow({ mode: 'open' });
|
||||
this.shadowRoot.appendChild(zoneAddTemplate.content.cloneNode(true));
|
||||
|
||||
this.searchElement = this.shadowRoot.querySelector('d-zoneadd-search');
|
||||
window.s = this.searchElement;
|
||||
this.searchElement.show([
|
||||
{ n: 'St. John\'s, Newfoundland and Labrador, CA', id: 'St_John\'s-Newfoundland_and_Labrador-CA' },
|
||||
{ n: 'Singapore, SG', id: 'Singapore-SG' },
|
||||
]);
|
||||
this.searchElement.addEventListener('searchinput', this.input.bind(this));
|
||||
}
|
||||
|
||||
input({ detail: { text } }) {
|
||||
console.debug(text);
|
||||
}
|
||||
}
|
||||
|
||||
// Definitions
|
||||
|
||||
window.customElements.define('icon-solid-trash', IconSolidTrashElement);
|
||||
window.customElements.define('icon-solid-plus', IconSolidPlusElement);
|
||||
|
||||
window.customElements.define('d-zone', ZoneElement);
|
||||
window.customElements.define('d-zonename', ZoneNameElement);
|
||||
window.customElements.define('d-zoneoffset', ZoneOffsetElement);
|
||||
window.customElements.define('d-date', DateElement);
|
||||
window.customElements.define('d-time', TimeElement);
|
||||
// TODO: Instead of relying on <slots>, render using attributes
|
||||
|
||||
window.customElements.define('d-zoneadd-search', ZoneSearchElement);
|
||||
window.customElements.define('d-zoneadd', ZoneAddElement);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z"/></svg>
|
After Width: | Height: | Size: 499 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16zM53.2 467a48 48 0 0 0 47.9 45h245.8a48 48 0 0 0 47.9-45L416 128H32z"/></svg>
|
After Width: | Height: | Size: 494 B |
|
@ -22,11 +22,11 @@
|
|||
</d-zoneerror>
|
||||
{{else}}
|
||||
{{$zt := $t.In .Location}}
|
||||
<d-zoneinfo>
|
||||
<div class="zoneinfo">
|
||||
{{if not .IsOffset}}
|
||||
<d-zonename>
|
||||
<d-zonearea>{{.FirstName}},</d-zonearea>
|
||||
<d-zonecountry> {{.City.Country.Ref}}</d-zonecountry>
|
||||
<span class="zonearea">{{.FirstName}},</span>
|
||||
<span class="zonecountry"> {{.City.Country.Ref}}</span>
|
||||
</d-zonename>
|
||||
{{else}}
|
||||
<d-zonename>
|
||||
|
@ -37,18 +37,22 @@
|
|||
<d-zoneoffset>{{.TimeOffset $t | formatOffset}}</d-zoneoffset>
|
||||
{{end}}
|
||||
<d-date date="{{$zt.Format "2006-01-02"}}">{{$zt.Format "2006-01-02"}}</d-date>
|
||||
</d-zoneinfo>
|
||||
<d-zonefigure>
|
||||
</div>
|
||||
<div class="zonefigure">
|
||||
<d-time time="{{$zt.Format "15:04"}}">{{$zt.Format "15:04"}}</d-time>
|
||||
</d-zonefigure>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</d-zone>
|
||||
{{end}}
|
||||
|
||||
<d-zoneadd></d-zoneadd>
|
||||
</main>
|
||||
|
||||
{{template "footer.html"}}
|
||||
|
||||
{{template "interactive-icons.html"}}
|
||||
|
||||
<script src="/js/third-party/luxon.min.js"></script>
|
||||
<script src="/js/interactive.js" async></script>
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<template id="icon-solid-trash">
|
||||
<style>
|
||||
svg {
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
svg path {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
{{template "icon_solid_trash.svg"}}
|
||||
</template>
|
||||
<template id="icon-solid-plus">
|
||||
<style>
|
||||
svg {
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
svg path {
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
{{template "icon_solid_plus.svg"}}
|
||||
</template>
|
Loading…
Reference in New Issue