From fca72a3303659641603c03283537fa3298444de5 Mon Sep 17 00:00:00 2001 From: Ralph Wiedemeier Date: Wed, 17 Apr 2019 21:57:41 +0200 Subject: [PATCH] annotation articles, article close button, tests, fixes --- libs/ff-browser | 2 +- libs/ff-scene | 2 +- libs/ff-ui | 2 +- source/client/annotations/AnnotationSprite.ts | 2 +- source/client/annotations/BeamSprite.ts | 32 ++++++--- source/client/components/CVAnnotationView.ts | 68 +++++++++++++------ source/client/components/CVAnnotationsTask.ts | 4 +- source/client/components/CVArticlesTask.ts | 6 +- source/client/components/CVReader.ts | 6 +- source/client/components/CVToursTask.ts | 9 +++ source/client/components/CVViewer.ts | 2 +- source/client/models/Annotation.ts | 14 ++-- source/client/ui/explorer/ContentView.ts | 18 +++-- source/client/ui/explorer/ReaderView.ts | 11 ++- source/client/ui/explorer/styles.scss | 24 +++++++ source/client/ui/story/AnnotationsTaskView.ts | 2 + source/client/ui/story/ArticleEditor.ts | 12 +++- source/client/ui/story/ArticlesTaskView.ts | 2 +- source/client/ui/story/MainView.ts | 4 +- source/client/ui/story/styles.scss | 7 ++ source/common/schema/model.schema.json | 16 +++-- source/common/types/model.ts | 3 +- 22 files changed, 183 insertions(+), 65 deletions(-) diff --git a/libs/ff-browser b/libs/ff-browser index 54bca2cb..31c8fe42 160000 --- a/libs/ff-browser +++ b/libs/ff-browser @@ -1 +1 @@ -Subproject commit 54bca2cb3ebd5a171ffb0561bcfb039e9e42a96e +Subproject commit 31c8fe42710ae37061b76b1bdc1b0db54b3066c1 diff --git a/libs/ff-scene b/libs/ff-scene index a40f2576..354e8117 160000 --- a/libs/ff-scene +++ b/libs/ff-scene @@ -1 +1 @@ -Subproject commit a40f257664726e604163ec71aae8c8dd23ba362c +Subproject commit 354e8117627e5ff0f5d3ffaed685171198a4edc5 diff --git a/libs/ff-ui b/libs/ff-ui index 3278d95e..da99ae96 160000 --- a/libs/ff-ui +++ b/libs/ff-ui @@ -1 +1 @@ -Subproject commit 3278d95eaff8b9ded2df17dae5367485d8f35d60 +Subproject commit da99ae9680417ec56686be2149e734643d7d4603 diff --git a/source/client/annotations/AnnotationSprite.ts b/source/client/annotations/AnnotationSprite.ts index 84aec45f..f5d41c74 100644 --- a/source/client/annotations/AnnotationSprite.ts +++ b/source/client/annotations/AnnotationSprite.ts @@ -133,7 +133,7 @@ export class AnnotationElement extends CustomElement this.classList.add("sv-annotation"); } - protected onClick(event: PointerEvent) + protected onClick(event: MouseEvent) { event.stopPropagation(); this.sprite.emitClickEvent(); diff --git a/source/client/annotations/BeamSprite.ts b/source/client/annotations/BeamSprite.ts index 475d68af..0d0889c3 100644 --- a/source/client/annotations/BeamSprite.ts +++ b/source/client/annotations/BeamSprite.ts @@ -19,7 +19,9 @@ import * as THREE from "three"; import math from "@ff/core/math"; -import { customElement, PropertyValues } from "@ff/ui/CustomElement"; +import { customElement, PropertyValues, html, render } from "@ff/ui/CustomElement"; +import Button from "@ff/ui/Button"; + import AnnotationSprite, { Annotation, AnnotationElement } from "./AnnotationSprite"; //////////////////////////////////////////////////////////////////////////////// @@ -99,7 +101,7 @@ export default class BeamSprite extends AnnotationSprite class BeamAnnotation extends AnnotationElement { protected titleElement: HTMLDivElement; - protected leadElement: HTMLDivElement; + protected contentElement: HTMLDivElement; protected wrapperElement: HTMLDivElement; protected handler = 0; protected isExpanded = true; @@ -110,13 +112,15 @@ class BeamAnnotation extends AnnotationElement { super(sprite); + this.onClickArticle = this.onClickArticle.bind(this); + this.titleElement = this.appendElement("div"); this.titleElement.classList.add("sv-content", "sv-title"); this.wrapperElement = this.appendElement("div"); - this.leadElement = this.createElement("div", null, this.wrapperElement); - this.leadElement.classList.add("sv-content", "sv-description"); + this.contentElement = this.createElement("div", null, this.wrapperElement); + this.contentElement.classList.add("sv-content", "sv-description"); } getOpacity() @@ -144,7 +148,11 @@ class BeamAnnotation extends AnnotationElement const annotation = this.sprite.annotation.data; this.titleElement.innerText = annotation.title; - this.leadElement.innerText = annotation.lead; + + const contentTemplate = html`

${annotation.lead}

+ ${annotation.articleId ? html`` : null}`; + + render(contentTemplate, this.contentElement); this.targetOpacity = annotation.visible ? 1 : 0; @@ -155,16 +163,22 @@ class BeamAnnotation extends AnnotationElement if (this.isExpanded) { this.classList.add("sv-expanded"); - this.leadElement.style.display = "inherit"; - this.leadElement.style.height = this.leadElement.scrollHeight + "px"; + this.contentElement.style.display = "inherit"; + this.contentElement.style.height = this.contentElement.scrollHeight + "px"; } else { this.classList.remove("sv-expanded"); - this.leadElement.style.height = "0"; - this.handler = window.setTimeout(() => this.leadElement.style.display = "none", 300); + this.contentElement.style.height = "0"; + this.handler = window.setTimeout(() => this.contentElement.style.display = "none", 300); } } } + + protected onClickArticle(event: MouseEvent) + { + event.stopPropagation(); + this.sprite.emitLinkEvent(this.sprite.annotation.data.articleId); + } } \ No newline at end of file diff --git a/source/client/components/CVAnnotationView.ts b/source/client/components/CVAnnotationView.ts index 63fcfc76..fd3cbfc6 100644 --- a/source/client/components/CVAnnotationView.ts +++ b/source/client/components/CVAnnotationView.ts @@ -33,6 +33,8 @@ import AnnotationSprite, { IAnnotationClickEvent, IAnnotationLinkEvent } from ". import PinSprite from "../annotations/PinSprite"; import BeamSprite from "../annotations/BeamSprite"; +import CVMeta from "./CVMeta"; +import CVReader from "./CVReader"; //////////////////////////////////////////////////////////////////////////////// @@ -50,6 +52,8 @@ const _inputs = { style: types.Enum("Annotation.Style", EAnnotationStyle, EAnnotationStyle.Default), scale: types.Scale("Annotation.Scale", 1), offset: types.Number("Annotation.Offset"), + article: types.Option("Annotation.Article", []), + image: types.String("Annotation.Image"), tilt: types.Number("Annotation.Tilt"), azimuth: types.Number("Annotation.Azimuth"), }; @@ -69,6 +73,16 @@ export default class CVAnnotationView extends CObject3D protected get model() { return this.getComponent(CVModel2); } + protected get meta() { + return this.getComponent(CVMeta, true); + } + protected get reader() { + return this.getGraphComponent(CVReader); + } + protected get articles() { + const meta = this.meta; + return meta ? meta.articles : null; + } get activeAnnotation() { return this._activeAnnotation; @@ -96,6 +110,21 @@ export default class CVAnnotationView extends CObject3D ins.scale.setValue(annotation ? annotation.data.scale : 1, true); ins.offset.setValue(annotation ? annotation.data.offset : 0, true); + const articles = this.articles; + if (articles) { + const names = articles.items.map(article => article.data.title); + names.unshift("(none)"); + ins.article.setOptions(names); + const article = annotation ? articles.getById(annotation.data.articleId) : null; + ins.article.setValue(article ? articles.getIndexOf(article) + 1 : 0, true); + } + else { + ins.article.setOptions([ "(none)" ]); + ins.article.setValue(0); + } + + ins.image.setValue(annotation ? annotation.data.imageUri : "", true); + this.emit({ type: "update", annotation }); } } @@ -109,6 +138,7 @@ export default class CVAnnotationView extends CObject3D this.onSpriteLink = this.onSpriteLink.bind(this); this.on("pointer-up", this.onPointerUp, this); + this.system.on("pointer-up", this.onPointerUp, this); this.object3D = new HTMLSpriteGroup(); } @@ -126,34 +156,31 @@ export default class CVAnnotationView extends CObject3D object3D.updateMatrix(); } - if (ins.title.changed) { - if (annotation) { + if (annotation) { + if (ins.title.changed) { annotation.set("title", ins.title.value); } - } - if (ins.lead.changed) { - if (annotation) { + if (ins.lead.changed) { annotation.set("lead", ins.lead.value); } - } - if (ins.style.changed) { - if (annotation) { + if (ins.style.changed) { annotation.set("style", ins.style.getValidatedValue()); this.createSprite(annotation); } - } - if (ins.scale.changed) { - if (annotation) { + if (ins.scale.changed) { annotation.set("scale", ins.scale.value); } - } - if (ins.offset.changed) { - if (annotation) { + if (ins.offset.changed) { annotation.set("offset", ins.offset.value); } - } + if (ins.image.changed) { + annotation.set("imageUri", ins.image.value); + } + if (ins.article.changed) { + const article = this.articles.getAt(ins.article.getValidatedValue() - 1); + annotation.set("articleId", article ? article.id : ""); + } - if (annotation) { this.updateSprite(annotation); this.emit({ type: "update", annotation }); } @@ -176,7 +203,9 @@ export default class CVAnnotationView extends CObject3D dispose() { (this.object3D as HTMLSpriteGroup).dispose(); + this.off("pointer-up", this.onPointerUp, this); + this.system.off("pointer-up", this.onPointerUp, this); this._viewports.forEach(viewport => viewport.off("dispose", this.onViewportDispose, this)); this._viewports.clear(); @@ -269,9 +298,7 @@ export default class CVAnnotationView extends CObject3D target = target.parent as AnnotationSprite; } - if (target) { - this.activeAnnotation = target.annotation; - } + this.activeAnnotation = target && target.annotation; } protected onViewportDispose(event: IViewportDisposeEvent) @@ -287,7 +314,8 @@ export default class CVAnnotationView extends CObject3D protected onSpriteLink(event: IAnnotationLinkEvent) { - + this.reader.ins.articleId.setValue(event.annotation.data.articleId); + this.reader.ins.enabled.setValue(true); } protected createSprite(annotation: Annotation) diff --git a/source/client/components/CVAnnotationsTask.ts b/source/client/components/CVAnnotationsTask.ts index 4d68b910..72cfac4d 100644 --- a/source/client/components/CVAnnotationsTask.ts +++ b/source/client/components/CVAnnotationsTask.ts @@ -191,8 +191,8 @@ export default class CVAnnotationsTask extends CVTask protected onActiveNode(previous: NVNode, next: NVNode) { - const prevAnnotations = previous ? previous.getComponent(CVAnnotationView) : null; - const nextAnnotations = next ? next.getComponent(CVAnnotationView) : null; + const prevAnnotations = previous ? previous.getComponent(CVAnnotationView, true) : null; + const nextAnnotations = next ? next.getComponent(CVAnnotationView, true) : null; if (prevAnnotations) { prevAnnotations.off("update", this.emitUpdateEvent, this); diff --git a/source/client/components/CVArticlesTask.ts b/source/client/components/CVArticlesTask.ts index 9c5c8e2b..e889a3ab 100644 --- a/source/client/components/CVArticlesTask.ts +++ b/source/client/components/CVArticlesTask.ts @@ -31,6 +31,7 @@ import ArticlesTaskView from "../ui/story/ArticlesTaskView"; import CAssetManager from "@ff/scene/components/CAssetManager"; import CVAssetWriter from "./CVAssetWriter"; import MessageBox from "@ff/ui/MessageBox"; +import uniqueId from "@ff/core/uniqueId"; //////////////////////////////////////////////////////////////////////////////// @@ -93,7 +94,10 @@ export default class CVArticlesTask extends CVTask const activeAsset = activeArticle ? this.assetManager.getAssetByPath(activeArticle.data.uri) : null; if (meta && ins.create.changed) { - meta.articles.append(new Article()); + const article = new Article(); + article.data.uri = `articles/new-article-${article.id}.html`; + meta.articles.append(article); + this.reader.ins.articleId.setValue(article.id); } if (activeArticle) { diff --git a/source/client/components/CVReader.ts b/source/client/components/CVReader.ts index 36e67ac2..07f20935 100644 --- a/source/client/components/CVReader.ts +++ b/source/client/components/CVReader.ts @@ -58,7 +58,7 @@ export default class CVReader extends Component protected _articles: Dictionary; get snapshotKeys() { - return [ "enabled" ]; + return [ "enabled", "articleId" ]; } get articles() { @@ -95,13 +95,11 @@ export default class CVReader extends Component const article = entry && entry.article; outs.node.setValue(entry && entry.node); outs.article.setValue(article); + outs.content.setValue(""); if (article) { this.readArticle(article); } - else { - outs.content.setValue(""); - } } return true; diff --git a/source/client/components/CVToursTask.ts b/source/client/components/CVToursTask.ts index 1ab7cfc0..a7bf91cc 100644 --- a/source/client/components/CVToursTask.ts +++ b/source/client/components/CVToursTask.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { Node } from "@ff/graph/Component"; import CTweenMachine, { EEasingCurve } from "@ff/graph/components/CTweenMachine"; import CVTask, { types } from "./CVTask"; @@ -63,6 +64,14 @@ export default class CVToursTask extends CVTask tours: CVTours = null; machine: CTweenMachine = null; + constructor(node: Node, id: string) + { + super(node, id); + + const configuration = this.configuration; + configuration.bracketsVisible = false; + } + update(context) { const ins = this.ins; diff --git a/source/client/components/CVViewer.ts b/source/client/components/CVViewer.ts index 855fbc96..84607519 100644 --- a/source/client/components/CVViewer.ts +++ b/source/client/components/CVViewer.ts @@ -39,7 +39,7 @@ export default class CVViewer extends CRenderable ins = this.addInputs(CVViewer.ins); get snapshotKeys() { - return [ "shader", "exposure" ]; + return [ "shader", "exposure", "annotationsVisible" ]; } create() diff --git a/source/client/models/Annotation.ts b/source/client/models/Annotation.ts index 447a528f..2b78d4d6 100644 --- a/source/client/models/Annotation.ts +++ b/source/client/models/Annotation.ts @@ -78,9 +78,11 @@ export default class Annotation extends Document if (data.tags.length > 0) { json.tags = data.tags; } - if (data.articles.length > 0) { - //TODO: Articles/IDs - //json.articles = data.articles.slice(); + if (data.articleId) { + json.articleId = data.articleId; + } + if (data.imageUri) { + json.imageUri = data.imageUri; } if (data.style !== EAnnotationStyle.Default) { json.style = EAnnotationStyle[data.style]; @@ -124,9 +126,9 @@ export default class Annotation extends Document data.title = json.title || ""; data.lead = json.lead || ""; data.tags = json.tags || []; - //TODO: Articles/IDs - data.articles = []; - //data.articles = json.articles ? json.articles.slice() : []; + + data.articleId = json.articleId || ""; + data.imageUri = json.imageUri || ""; data.style = EAnnotationStyle[json.style] || EAnnotationStyle.Default; data.visible = json.visible !== undefined ? json.visible : true; diff --git a/source/client/ui/explorer/ContentView.ts b/source/client/ui/explorer/ContentView.ts index 16dc2618..578de54a 100644 --- a/source/client/ui/explorer/ContentView.ts +++ b/source/client/ui/explorer/ContentView.ts @@ -39,6 +39,12 @@ export default class ContentView extends DocumentView protected get assetLoader() { return this.system.getMainComponent(CVAssetReader); } + protected get reader() { + return this.activeDocument ? this.activeDocument.setup.reader : null; + } + protected get tours() { + return this.activeDocument ? this.activeDocument.setup.tours : null; + } protected firstConnected() { @@ -67,9 +73,8 @@ export default class ContentView extends DocumentView let readerPosition = EReaderPosition.Overlay; let tourMenuVisible = false; - const document = this.activeDocument; - const reader = document && document.setup.reader; - const tours = document && document.setup.tours; + const reader = this.reader; + const tours = this.tours; if (tours) { tourMenuVisible = tours.ins.enabled.value && tours.outs.tourIndex.value === -1; @@ -102,7 +107,7 @@ export default class ContentView extends DocumentView if (readerPosition === EReaderPosition.Overlay) { return html`
${sceneView}
- +
`; } @@ -112,6 +117,11 @@ export default class ContentView extends DocumentView `; } + protected onReaderClose() + { + this.reader.ins.enabled.setValue(false); + } + protected onActiveDocument(previous: CVDocument, next: CVDocument) { if (previous) { diff --git a/source/client/ui/explorer/ReaderView.ts b/source/client/ui/explorer/ReaderView.ts index 75c87505..03bff4d8 100644 --- a/source/client/ui/explorer/ReaderView.ts +++ b/source/client/ui/explorer/ReaderView.ts @@ -57,14 +57,23 @@ export default class ReaderView extends DocumentView if (!reader.activeArticle) { const articles = reader.articles; return html`
+ ${articles.map(entry => this.renderMenuEntry(entry))}
`; } return html`
+ +
`; } + protected onClickClose(event: MouseEvent) + { + event.stopPropagation(); + this.dispatchEvent(new CustomEvent("close")); + } + protected onClickArticle(e: MouseEvent, articleId: string) { this.reader.ins.articleId.setValue(articleId); @@ -77,7 +86,7 @@ export default class ReaderView extends DocumentView const reader = this.reader; if (reader && reader.activeArticle) { - const container = this.getElementsByClassName("sv-article").item(0) as HTMLElement; + const container = this.getElementsByClassName("sv-container").item(0) as HTMLElement; container.innerHTML = reader.outs.content.value; } } diff --git a/source/client/ui/explorer/styles.scss b/source/client/ui/explorer/styles.scss index 7cf35ef2..e147d9d2 100644 --- a/source/client/ui/explorer/styles.scss +++ b/source/client/ui/explorer/styles.scss @@ -145,6 +145,19 @@ $pad: $canvas-border-width + $main-menu-button-size + 8px; cursor: pointer; pointer-events: auto; + p { + margin: 0.5em 0; + } + .ff-button { + .ff-icon { + fill: $color-icon; + } + + &:hover { + text-decoration: underline; + } + } + &.sv-expanded { min-width: 120px; } @@ -315,6 +328,17 @@ $pad: $canvas-border-width + $main-menu-button-size + 8px; $tour-entry-indent: 12px; .sv-tour-menu, .sv-reader-view { + .sv-article { + position: relative; + + .ff-icon { + height: 1.5em; + float: right; + cursor: pointer; + margin: 1em 0.5em; + } + } + .sv-entry { position: relative; cursor: pointer; diff --git a/source/client/ui/story/AnnotationsTaskView.ts b/source/client/ui/story/AnnotationsTaskView.ts index 661198b4..3b183be1 100644 --- a/source/client/ui/story/AnnotationsTaskView.ts +++ b/source/client/ui/story/AnnotationsTaskView.ts @@ -68,6 +68,8 @@ export default class AnnotationsTaskView extends TaskView + +
Title
Lead
diff --git a/source/client/ui/story/ArticleEditor.ts b/source/client/ui/story/ArticleEditor.ts index 799c0d5b..e4975da3 100644 --- a/source/client/ui/story/ArticleEditor.ts +++ b/source/client/ui/story/ArticleEditor.ts @@ -19,9 +19,9 @@ import * as QuillEditor from "quill"; import ImageResize from 'quill-image-resize-module'; import { html, render } from "@ff/ui/CustomElement"; +import Notification from "@ff/ui/Notification"; import CAssetManager, { IAssetOpenEvent } from "@ff/scene/components/CAssetManager"; - import SystemView, { customElement } from "@ff/scene/ui/SystemView"; import AssetTree from "@ff/scene/ui/AssetTree"; @@ -104,7 +104,13 @@ export default class ArticleEditor extends SystemView protected writeArticle(assetPath: string) { return this.assetWriter.putText(this._editor.root.innerHTML, assetPath) - .then(() => this._changed = false); + .then(() => { + this._changed = false; + new Notification(`Article successfully written to '${assetPath}'`, "info"); + }) + .catch(error => { + new Notification(`Failed to write article to '${assetPath}': ${error.message}`, "error"); + }); } protected clearArticle() @@ -157,6 +163,8 @@ export default class ArticleEditor extends SystemView }; this._container = this.appendElement("div"); + this._container.classList.add("sv-container"); + this._overlay = this.appendElement("div"); this._overlay.classList.add("sv-overlay"); diff --git a/source/client/ui/story/ArticlesTaskView.ts b/source/client/ui/story/ArticlesTaskView.ts index 8b61b977..b129900c 100644 --- a/source/client/ui/story/ArticlesTaskView.ts +++ b/source/client/ui/story/ArticlesTaskView.ts @@ -50,7 +50,7 @@ export default class ArticlesTaskView extends TaskView const activeArticle = task.activeArticle; if (!articles) { - return html`
Please select a node to edit its articles
`; + return html`
Please select a scene or model node to edit its articles.
`; } const props = task.ins; diff --git a/source/client/ui/story/MainView.ts b/source/client/ui/story/MainView.ts index 5b6ba4aa..4dd2afc1 100644 --- a/source/client/ui/story/MainView.ts +++ b/source/client/ui/story/MainView.ts @@ -209,7 +209,7 @@ export default class MainView extends CustomElement text: "Navigator" }, { contentId: "assets", - text: "Assets" + text: "Media" }] }, { type: "stack", @@ -267,7 +267,7 @@ export default class MainView extends CustomElement text: "Navigator" }, { contentId: "assets", - text: "Assets" + text: "Media" }] }, { type: "stack", diff --git a/source/client/ui/story/styles.scss b/source/client/ui/story/styles.scss index a9304e96..d4878e7a 100644 --- a/source/client/ui/story/styles.scss +++ b/source/client/ui/story/styles.scss @@ -276,6 +276,7 @@ ff-dock-view { .sv-property-value { flex: 1 1 75%; + @include ellipsis; } .sv-field-row { @@ -304,6 +305,12 @@ ff-dock-view { .sv-article-editor { @include fullsize; + display: flex; + flex-direction: column; + + .sv-container { + overflow: hidden; + } .sv-custom-buttons { float: left; diff --git a/source/common/schema/model.schema.json b/source/common/schema/model.schema.json index 4b1dc065..168e153c 100644 --- a/source/common/schema/model.schema.json +++ b/source/common/schema/model.schema.json @@ -27,13 +27,15 @@ "type": "string" } }, - "articles": { - "description": "Array of article ids, listing articles related to the annotation.", - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } + "articleId": { + "description": "Id of an article related to this annotation.", + "type": "string", + "minLength": 1 + }, + "imageUri": { + "description": "URI of an image resource for this annotation.", + "type": "string", + "minLength": 1 }, "style": { "type": "string" diff --git a/source/common/types/model.ts b/source/common/types/model.ts index 7ce7e047..ef6d2e2d 100644 --- a/source/common/types/model.ts +++ b/source/common/types/model.ts @@ -61,7 +61,8 @@ export interface IAnnotation title?: string; lead?: string; tags?: string[]; - articles?: Index[]; + articleId?: string; + imageUri?: string; style?: string; visible?: boolean;