diff --git a/source/ui/Logo.ts b/source/ui/Logo.ts
deleted file mode 100644
index af35805f..00000000
--- a/source/ui/Logo.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * 3D Foundation Project
- * Copyright 2019 Smithsonian Institution
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import CustomElement, { customElement, html } from "@ff/ui/CustomElement";
-
-////////////////////////////////////////////////////////////////////////////////
-
-@customElement("sv-logo")
-export default class Logo extends CustomElement
-{
- protected static readonly h = html`
`;
- protected static readonly text = html``;
-
- protected firstConnected()
- {
- super.firstConnected();
- this.classList.add("sv-logo");
- }
-
- protected render()
- {
- return html`${Logo.h}
${Logo.h}${Logo.text}
`;
- }
-}
\ No newline at end of file
diff --git a/source/ui/MainView.ts b/source/ui/MainView.ts
index dfabc4f0..b6030832 100644
--- a/source/ui/MainView.ts
+++ b/source/ui/MainView.ts
@@ -1,10 +1,10 @@
import { LitElement, html, customElement } from 'lit-element';
-import Notification from "@ff/ui/Notification";
-import styles from '!lit-css-loader?{"specifier":"lit-element"}!sass-loader!./styles.scss';
+import styles from '!lit-css-loader?{"specifier":"lit-element"}!sass-loader!./styles/main.scss';
+
+import "./styles/globals.scss";
-import "./globals.scss";
import "./composants/UploadButton";
import "./composants/navbar/NavLink";
@@ -19,28 +19,13 @@ import "./screens/UserSettings";
import "./screens/Home"
import "./composants/Modal";
+import Notification from "./composants/Notification";
+
import { updateLogin, withUser } from './state/auth';
import Modal from './composants/Modal';
import i18n from './state/translate';
import { route, router } from './state/router';
-/**
- * Simplified from path-to-regex for our simple use-case
- * @see https://github.com/pillarjs/path-to-regexp
- */
-function toRegex(path:string|RegExp){
- if(path instanceof RegExp) return path;
- const matcher = `[^\/#\?]+`
- let parts = path.split("/")
- .filter(p=>p)
- .map( p =>{
- let param = /:(\w+)/.exec(p);
- if(!param) return p;
- return `(?<${param[1]}>${matcher})`;
- })
- return new RegExp(`^/${parts.join("/")}\/?$`,"i")
-}
-
@customElement("ecorpus-main")
export default class MainView extends router(i18n(withUser(LitElement))){
@@ -57,7 +42,7 @@ export default class MainView extends router(i18n(withUser(LitElement))){
connectedCallback(): void {
super.connectedCallback();
- Notification.shadowRootNode = this.shadowRoot;
+ // FIXME : configure notifications
updateLogin().catch(e => {
Modal.show({header: "Error", body: e.message});
});
diff --git a/source/ui/assets/favicon.png b/source/ui/assets/favicon.png
new file mode 100644
index 00000000..6e781651
Binary files /dev/null and b/source/ui/assets/favicon.png differ
diff --git a/source/ui/assets/images/defaultSprite.svg b/source/ui/assets/images/defaultSprite.svg
new file mode 100644
index 00000000..933235e5
--- /dev/null
+++ b/source/ui/assets/images/defaultSprite.svg
@@ -0,0 +1,5 @@
+
+ Cube
+
+
+
\ No newline at end of file
diff --git a/source/ui/assets/images/logo-full.svg b/source/ui/assets/images/logo-full.svg
new file mode 100644
index 00000000..eea97313
--- /dev/null
+++ b/source/ui/assets/images/logo-full.svg
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/ui/assets/images/logo-sm.svg b/source/ui/assets/images/logo-sm.svg
new file mode 100644
index 00000000..b7b94ba4
--- /dev/null
+++ b/source/ui/assets/images/logo-sm.svg
@@ -0,0 +1,10 @@
+
+
+
diff --git a/source/ui/assets/images/sketch_ethesaurus.png b/source/ui/assets/images/sketch_ethesaurus.png
new file mode 100644
index 00000000..fcfcf854
Binary files /dev/null and b/source/ui/assets/images/sketch_ethesaurus.png differ
diff --git a/source/ui/assets/images/spinner.svg b/source/ui/assets/images/spinner.svg
new file mode 100644
index 00000000..ccf98dc0
--- /dev/null
+++ b/source/ui/assets/images/spinner.svg
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/source/ui/composants/Button.ts b/source/ui/composants/Button.ts
new file mode 100644
index 00000000..97eea62f
--- /dev/null
+++ b/source/ui/composants/Button.ts
@@ -0,0 +1,168 @@
+/**
+ * FF Typescript Foundation Library
+ * Copyright 2019 Ralph Wiedemeier, Frame Factory GmbH
+ *
+ * License: MIT
+ */
+
+import "./Icon";
+
+import { customElement, property, html, PropertyValues, LitElement } from "lit-element";
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Emitted by [[Button]] if clicked.
+ * @event
+ */
+export interface IButtonClickEvent extends MouseEvent
+{
+ type: "click";
+ target: Button;
+}
+
+export interface IButtonKeyboardEvent extends KeyboardEvent
+{
+ type: "click";
+ target: Button;
+}
+
+/**
+ * Custom element displaying a button with a text and/or an icon.
+ * The button emits a [[IButtonClickEvent]] if clicked.
+ * Classes assigned: "ff-button", "ff-control".
+ */
+@customElement("ui-button")
+export default class Button extends LitElement
+{
+ /** Optional name to identify the button. */
+ @property({ type: String })
+ name = "";
+
+ /** Optional index to identify the button. */
+ @property({ type: Number })
+ index = 0;
+
+ @property({ type: Number })
+ selectedIndex = -1;
+
+ @property({ type: Number })
+ tabbingIndex = 0;
+
+ /** If true, adds "ui-selected" class to element. */
+ @property({ type: Boolean, reflect: true })
+ selected = false;
+
+ /** If true, toggles selected state every time the button is clicked. */
+ @property({ type: Boolean })
+ selectable = false;
+
+ @property({ type: Boolean })
+ disabled = false;
+
+ /** Optional text to be displayed on the button. */
+ @property()
+ text: string;
+
+ /** Optional name of the icon to be displayed on the button. */
+ @property()
+ icon = "";
+
+ /** Optional role - defaults to 'button'. */
+ @property()
+ role = "button";
+
+ /** If true, displays a downward facing triangle at the right side. */
+ @property({ type: Boolean })
+ caret = false;
+
+ @property({ type: Boolean })
+ inline = false;
+
+ @property({ type: Boolean })
+ transparent = false;
+
+ constructor()
+ {
+ super();
+
+ this.addEventListener("click", (e) => this.onClick(e));
+ this.addEventListener("keydown", (e) => this.onKeyDown(e));
+ }
+
+ protected firstConnected()
+ {
+ this.tabIndex = this.tabbingIndex;
+ this.setAttribute("role", this.role);
+ this.classList.add("ff-button");
+ }
+
+ protected shouldUpdate(changedProperties: PropertyValues)
+ {
+ if (changedProperties.has("selectedIndex") || changedProperties.has("index")) {
+ if (this.selectedIndex >= 0) {
+ this.selected = this.index === this.selectedIndex;
+ }
+ }
+
+ if (changedProperties.has("disabled")) {
+ this.classList.toggle("ff-disabled");
+ }
+
+ return true;
+ }
+
+ protected update(changedProperties: PropertyValues)
+ {
+ this.classList.remove("ff-inline", "ff-transparent", "ff-control");
+
+ if (this.inline) {
+ this.classList.add("ff-inline");
+ }
+ else if (this.transparent) {
+ this.classList.add("ff-transparent");
+ }
+ else {
+ this.classList.add("ff-control");
+ }
+
+ super.update(changedProperties);
+ }
+
+ protected render()
+ {
+ return html`${this.renderIcon()}${this.renderText()}${this.renderCaret()}`;
+ }
+
+ protected renderIcon()
+ {
+ return this.icon ? html` ` : null;
+ }
+
+ protected renderText()
+ {
+ return this.text ? html`${this.text}
` : null;
+ }
+
+ protected renderCaret()
+ {
+ return this.caret ? html`
` : null;
+ }
+
+ protected onClick(event: MouseEvent)
+ {
+ if (this.selectable) {
+ this.selected = !this.selected;
+ }
+ }
+
+ protected onKeyDown(event: KeyboardEvent)
+ {
+ const activeElement = document.activeElement.shadowRoot ? document.activeElement.shadowRoot.activeElement : document.activeElement;
+
+ if (activeElement === this && (event.code === "Space" || event.code === "Enter")) {
+ event.preventDefault();
+ this.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ }
+ }
+}
diff --git a/source/ui/composants/DocView.ts b/source/ui/composants/DocView.ts
index 945779b8..9486137a 100644
--- a/source/ui/composants/DocView.ts
+++ b/source/ui/composants/DocView.ts
@@ -2,13 +2,12 @@
import { customElement, property, html, TemplateResult, LitElement, css } from "lit-element";
import {unsafeHTML} from 'lit-html/directives/unsafe-html.js';
-import "@ff/ui/Button";
-import "client/ui/Spinner";
+import "./Spinner";
import i18n from "../state/translate";
import { Language } from "../state/strings";
-import styles from '!lit-css-loader?{"specifier":"lit-element"}!sass-loader!../styles.scss';
+import styles from '!lit-css-loader?{"specifier":"lit-element"}!sass-loader!../styles/common.scss';
/**
* Main UI view for the Voyager Explorer application.
@@ -91,7 +90,7 @@ import styles from '!lit-css-loader?{"specifier":"lit-element"}!sass-loader!../s
if(this.error){
return html`Error ${this.error.message}
`;
}else if(!this.content || this.content =="loading..."){
- return html`Loading...
`
+ return html`Loading...
`
}
return html`${unsafeHTML(this.content)}`;
}
diff --git a/source/ui/composants/Icon.ts b/source/ui/composants/Icon.ts
index 18418c78..d288efcf 100644
--- a/source/ui/composants/Icon.ts
+++ b/source/ui/composants/Icon.ts
@@ -1,7 +1,88 @@
-import {html} from "lit-element";
-import Icon from "@ff/ui/Icon"
+import {LitElement, customElement, property, html, TemplateResult} from "lit-element";
+/**
+ * Imported from
+ * FF Typescript Foundation Library
+ * Copyright 2019 Ralph Wiedemeier, Frame Factory GmbH
+ *
+ * License: MIT
+ */
+@customElement("ui-icon")
+export default class Icon extends LitElement
+{
+ protected static templates = {};
+ createRenderRoot() {
+ return this;
+ }
+
+ static add(name: string, template: TemplateResult)
+ {
+ if (Icon.templates[name]) {
+ throw new Error(`icon already registered: '${name}'`);
+ }
+ Icon.templates[name] = template;
+ }
+
+ static getTemplateNames()
+ {
+ return Object.keys(Icon.templates);
+ }
+
+ @property({ attribute: false })
+ template: TemplateResult = null;
+
+ @property({ type: String })
+ name: string;
+
+ constructor(name?: string)
+ {
+ super();
+ this.name = name || "";
+ }
+
+ protected firstConnected()
+ {
+ this.classList.add("ui-icon");
+ }
+
+ protected render()
+ {
+ if (this.name) {
+ const template = (this.constructor as typeof Icon).templates[this.name];
+ if (!template) {
+ console.warn(`icon not found: '${this.name}'`);
+ }
+ return template;
+ }
+ if (this.template) {
+ return this.template;
+ }
+
+ return html`[icon undefined]`;
+ }
+}
+
+
+
+Icon.add("empty", html``);
+
+Icon.add("check", html` `);
+Icon.add("close", html` `);
+Icon.add("grip", html` `);
+Icon.add("up", html` `);
+Icon.add("down", html` `);
+
+Icon.add("caret-up", html` `);
+Icon.add("caret-down", html` `);
+
+Icon.add("folder", html` `);
+Icon.add("file", html` `);
+
+Icon.add("info", html` `);
+Icon.add("warning", html` `);
+Icon.add("error", html` `);
+Icon.add("prompt", html` `);
Icon.add("trash", html` `);
Icon.add("plus", html` `)
@@ -29,5 +110,3 @@ Icon.add("comment", html` `);
Icon.add("audio", html` `);
Icon.add("eye", html` `);
-
-export default Icon;
diff --git a/source/ui/composants/Modal.ts b/source/ui/composants/Modal.ts
index 486bf4f6..c8926a60 100644
--- a/source/ui/composants/Modal.ts
+++ b/source/ui/composants/Modal.ts
@@ -3,7 +3,7 @@ import { css, LitElement,customElement, property, html, TemplateResult, query }
import "./Icon"
-import styles from '!lit-css-loader?{"specifier":"lit-element"}!sass-loader!../styles.scss';
+import styles from '!lit-css-loader?{"specifier":"lit-element"}!sass-loader!../styles/common.scss';
interface ModalOptions{
header :TemplateResult|string;
@@ -82,7 +82,7 @@ interface ModalOptions{
${this.body}
${this.buttons}
-
+
`;
}
diff --git a/source/ui/composants/Notification.ts b/source/ui/composants/Notification.ts
new file mode 100644
index 00000000..3fa65e20
--- /dev/null
+++ b/source/ui/composants/Notification.ts
@@ -0,0 +1,65 @@
+import { LitElement, customElement, property } from "lit-element";
+
+
+type NotificationLevel = "info" | "success" | "warning" | "error";
+
+const _levelClasses = {
+ "info": "notification-info",
+ "success": "notification-success",
+ "warning": "notification-warning",
+ "error": "notification-error"
+} as const;
+
+const _levelIcons = {
+ "info": "info",
+ "success": "check",
+ "warning": "warning",
+ "error": "error"
+};
+
+const _levelTimeouts = {
+ "info": 2000,
+ "success": 2000,
+ "warning": 5000,
+ "error": 0
+} as const;
+
+
+@customElement("notification-line")
+class Notification extends LitElement{
+ @property({ type: String })
+ message: string;
+
+ @property({ type: String })
+ level: NotificationLevel;
+
+ @property({ type: Number })
+ timeout: number;
+
+
+ createRenderRoot() {
+ return this;
+ }
+
+ constructor(message?: string, level?: NotificationLevel, timeout?: number)
+ {
+ super();
+ this.message = message || "";
+ this.level = level || "info";
+ this.timeout = timeout !== undefined ? timeout : _levelTimeouts[this.level];
+ }
+
+}
+
+
+@customElement("notification-stack")
+export default class Notifications extends LitElement{
+ static container: HTMLElement = null;
+ static show(message: string, level?: NotificationLevel, timeout?: number){
+ let line = new Notification(message, level, timeout);
+ if(!Notifications.container){
+ return console.error("Notification stack not configured. Please mount in your DOM before calling Notification.show");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/source/ui/composants/SceneCard.ts b/source/ui/composants/SceneCard.ts
index d637ba62..c39e5a39 100644
--- a/source/ui/composants/SceneCard.ts
+++ b/source/ui/composants/SceneCard.ts
@@ -1,5 +1,7 @@
import { LitElement, customElement, property, html, TemplateResult, css } from "lit-element";
-import WebDAVProvider from "@ff/scene/assets/WebDAVProvider";
+
+import defaultSprite from "../assets/images/defaultSprite.svg";
+
import i18n from "../state/translate";
import { AccessType, AccessTypes, Scene } from "../state/withScenes";
@@ -13,7 +15,6 @@ import { AccessType, AccessTypes, Scene } from "../state/withScenes";
@customElement("scene-card")
export default class SceneCard extends i18n(LitElement)
{
- static _assets = new WebDAVProvider();
@property()
thumb :string;
@@ -52,13 +53,7 @@ import { AccessType, AccessTypes, Scene } from "../state/withScenes";
if(this.cardStyle == "grid") this.classList.add("card-grid");
if(!this.thumb ){
- SceneCard._assets.get(this.path, false).then(p=>{
- let thumbProps = p.find(f=> f.name.endsWith(`-image-thumb.jpg`));
- if(!thumbProps) return console.log("No thumbnail for", this.name);
- this.thumb = thumbProps.url;
- }, (e)=>{
- console.warn("Failed to PROPFIND %s :", this.path, e);
- });
+ console.warn("Failed to PROPFIND %s :", this.path);
}
}
@@ -72,7 +67,7 @@ import { AccessType, AccessTypes, Scene } from "../state/withScenes";
${(this.onChange? html`
diff --git a/source/ui/composants/Spinner.ts b/source/ui/composants/Spinner.ts
new file mode 100644
index 00000000..86514923
--- /dev/null
+++ b/source/ui/composants/Spinner.ts
@@ -0,0 +1,89 @@
+import { LitElement, property, customElement, html, css } from "lit-element";
+
+
+@customElement("spin-loader")
+export default class Spinner extends LitElement{
+
+ @property({type: Boolean})
+ overlay :boolean = false;
+ @property({type: Boolean})
+ visible :boolean = false;
+
+
+
+ render(){
+ return html`
+
+
+
`;
+ }
+ static readonly styles = [css`
+ .spin-loader{
+ position:relative;
+ }
+
+ .loading-overlay{
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.6);
+ transition: opacity 0.5s ease-out;
+ pointer-events: auto;
+ z-index: 10;
+ }
+
+ :host:not([visible]) .loading-overlay{
+ pointer-events: none;
+ opacity: 0;
+ }
+
+
+ .loader {
+ top: calc(50% - 48px);
+ left: calc(50% - 48px);
+ width: 96px;
+ height: 96px;
+ border: 6px solid #FFF;
+ border-radius: 50%;
+ display: inline-block;
+ position: relative;
+ box-sizing: border-box;
+ animation: rotation 1s linear infinite;
+ transition: transform 0.5s ease-out;
+ }
+
+ .loader::after {
+ content: '';
+ box-sizing: border-box;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ border: 6px solid;
+ border-color: var(--color-primary) transparent;
+ }
+
+ .load-text{
+ position: absolute;
+ bottom:10px;
+ left:0;
+ right:0;
+ text-align: center;
+ font-size: 2rem;
+ }
+ .load-text:empty{
+ display: none;
+ }
+
+ @keyframes rotation {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+ `];
+}
\ No newline at end of file
diff --git a/source/ui/composants/UserLogin.ts b/source/ui/composants/UserLogin.ts
index 97b9cf0f..6f0b3c93 100644
--- a/source/ui/composants/UserLogin.ts
+++ b/source/ui/composants/UserLogin.ts
@@ -5,8 +5,8 @@ import { css, LitElement,customElement, property, html, TemplateResult } from "l
import { doLogin } from "../state/auth";
import i18n from "../state/translate";
-import styles from '!lit-css-loader?{"specifier":"lit-element"}!sass-loader!../styles.scss';
-import Notification from "@ff/ui/Notification";
+import styles from '!lit-css-loader?{"specifier":"lit-element"}!sass-loader!../styles/common.scss';
+import Notification from "./Notification";
/**
* Main UI view for the Voyager Explorer application.
diff --git a/source/ui/composants/navbar/ChangeLocale.ts b/source/ui/composants/navbar/ChangeLocale.ts
index 94904502..85b7ebdf 100644
--- a/source/ui/composants/navbar/ChangeLocale.ts
+++ b/source/ui/composants/navbar/ChangeLocale.ts
@@ -1,11 +1,12 @@
import { LitElement, html, customElement, property, css, TemplateResult } from 'lit-element';
-import Button from "@ff/ui/Button";
+
import i18n, {Localization} from '../../state/translate';
@customElement("change-locale")
-export default class ChangeLocale extends i18n(Button){
+export default class ChangeLocale extends i18n(LitElement){
constructor(){
super();
+ this.addEventListener("click", (e) => this.onClick());
}
onclick = (ev :MouseEvent)=>{
ev.preventDefault();
@@ -15,12 +16,12 @@ export default class ChangeLocale extends i18n(Button){
protected createRenderRoot(): Element | ShadowRoot {
return this;
}
+
onClick = ()=>{
Localization.Instance.setLanguage(this.language == "fr"? "en": "fr");
}
+
protected render(): TemplateResult {
- this.text = this.language;
- console.log("lang render : ", Localization.Instance);
- return super.render();
+ return html`${this.language}
`;
}
}
\ No newline at end of file
diff --git a/source/ui/composants/navbar/Navbar.ts b/source/ui/composants/navbar/Navbar.ts
index 68e1c4bd..77fb9c32 100644
--- a/source/ui/composants/navbar/Navbar.ts
+++ b/source/ui/composants/navbar/Navbar.ts
@@ -3,6 +3,7 @@ import { css, customElement, html, LitElement, TemplateResult } from "lit-elemen
import styles from '!lit-css-loader?{"specifier":"lit-element"}!sass-loader!./styles.scss';
+import favicon from "../../assets/favicon.png";
/**
* Main UI view for the Voyager Explorer application.
@@ -20,7 +21,7 @@ import styles from '!lit-css-loader?{"specifier":"lit-element"}!sass-loader!./st
return html`
diff --git a/source/ui/composants/navbar/styles.scss b/source/ui/composants/navbar/styles.scss
index 2a24d01b..74b44a05 100644
--- a/source/ui/composants/navbar/styles.scss
+++ b/source/ui/composants/navbar/styles.scss
@@ -1,5 +1,4 @@
-@import "../../variables.scss";
-@import "@ff/ui/styles/_styles.scss";
+@import "../../styles/variables.scss";
:host(corpus-navbar) {
position: fixed;
diff --git a/source/ui/corpus.hbs b/source/ui/corpus.hbs
index 73f6e3fb..9458c48b 100644
--- a/source/ui/corpus.hbs
+++ b/source/ui/corpus.hbs
@@ -6,7 +6,7 @@
{{title}}
-
+