From 146d2babb199db5e1d69b4af7b7f1aa117b210ab Mon Sep 17 00:00:00 2001 From: Xiao Gui <xgui3783@gmail.com> Date: Thu, 12 Jul 2018 08:38:57 +0200 Subject: [PATCH] angular elements export complete. introduced animation. markdown converter now also uses inner content, added highlight to regions search by text, added bigbrain citation --- package.json | 20 ++- .../atlasViewer.apiService.service.ts | 2 +- src/atlasViewer/atlasViewer.component.ts | 25 ++- src/atlasViewer/atlasViewer.template.html | 3 +- src/atlasViewerExports/export.html | 166 ++++++++++++++++++ src/atlasViewerExports/export.module.ts | 66 +++++++ src/atlasViewerExports/main.export.ts | 6 + .../sampleBox/sampleBox.component.ts | 44 +++++ .../sampleBox/sampleBox.style.css | 0 .../sampleBox/sampleBox.template.html | 19 ++ src/components/components.module.ts | 4 + src/components/markdown/markdown.component.ts | 15 +- .../markdown/markdown.template.html | 5 + src/components/panel/panel.animation.ts | 23 +++ src/components/panel/panel.component.ts | 29 ++- src/components/panel/panel.style.css | 16 +- src/components/panel/panel.template.html | 32 ++-- .../readmoore/readmore.animations.ts | 25 +++ .../readmoore/readmore.component.ts | 25 +-- .../readmoore/readmore.template.html | 9 +- src/components/tree/tree.animation.ts | 23 +++ src/components/tree/tree.component.ts | 21 ++- src/components/tree/tree.style.css | 5 + src/components/tree/tree.template.html | 37 ++-- src/layouts/layout.module.ts | 3 + src/layouts/mainside/mainside.animation.ts | 25 +++ src/layouts/mainside/mainside.component.ts | 14 +- src/layouts/mainside/mainside.template.html | 5 +- src/plugin_examples/jugex/script.js | 2 - src/res/css/extra_styles.css | 11 ++ src/res/ext/bigbrain.json | 2 +- src/res/images/HBP_Primary_RGB_BlackText.png | Bin 0 -> 21353 bytes src/res/images/HBP_Primary_RGB_WhiteText.png | Bin 0 -> 21359 bytes src/ui/banner/banner.component.ts | 63 ++++++- src/ui/databrowser/databrowser.component.ts | 4 +- .../nehubaContainer.component.ts | 127 +++++++++++--- .../nehubaContainer/nehubaContainer.style.css | 26 ++- .../nehubaContainer.template.html | 26 ++- src/util/generator.ts | 15 ++ webpack.common.js | 6 +- webpack.dev.js | 10 +- webpack.export.js | 27 +++ webpack.prod.js | 5 + 43 files changed, 876 insertions(+), 115 deletions(-) create mode 100644 src/atlasViewerExports/export.html create mode 100644 src/atlasViewerExports/export.module.ts create mode 100644 src/atlasViewerExports/main.export.ts create mode 100644 src/atlasViewerExports/sampleBox/sampleBox.component.ts create mode 100644 src/atlasViewerExports/sampleBox/sampleBox.style.css create mode 100644 src/atlasViewerExports/sampleBox/sampleBox.template.html create mode 100644 src/components/panel/panel.animation.ts create mode 100644 src/components/readmoore/readmore.animations.ts create mode 100644 src/components/tree/tree.animation.ts create mode 100644 src/layouts/mainside/mainside.animation.ts create mode 100644 src/res/images/HBP_Primary_RGB_BlackText.png create mode 100644 src/res/images/HBP_Primary_RGB_WhiteText.png create mode 100644 src/util/generator.ts create mode 100644 webpack.export.js diff --git a/package.json b/package.json index 04ddb7088..70aef493c 100644 --- a/package.json +++ b/package.json @@ -3,25 +3,29 @@ "version": "1.0.0", "description": "", "scripts": { + "dev-server-export": "webpack-dev-server --config webpack.export.js", + "build-export": "webpack --config webpack.export.js", "build-aot": "webpack --config webpack.aot.js", "build-min": "webpack --config webpack.prod.js", "build": "webpack --config webpack.dev.js", "dev-server": "webpack-dev-server --config webpack.dev.js --mode development", - "serve-plugins" : "node src/plugin_examples/server.js", + "serve-plugins": "node src/plugin_examples/server.js", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { - "@angular/common": "^6.0.3", - "@angular/compiler": "^6.0.3", + "@angular/animations": "^6.0.7", + "@angular/common": "^6.0.7", + "@angular/compiler": "^6.0.7", "@angular/compiler-cli": "^6.0.3", - "@angular/core": "^6.0.3", - "@angular/forms": "^6.0.3", - "@angular/http": "^6.0.3", - "@angular/platform-browser": "^6.0.3", - "@angular/platform-browser-dynamic": "^6.0.3", + "@angular/core": "^6.0.7", + "@angular/elements": "^6.0.7", + "@angular/forms": "^6.0.7", + "@angular/http": "^6.0.7", + "@angular/platform-browser": "^6.0.7", + "@angular/platform-browser-dynamic": "^6.0.7", "@ngrx/store": "^6.0.1", "@ngtools/webpack": "^6.0.5", "@types/chart.js": "^2.7.20", diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts index b0d0c5984..efa52221e 100644 --- a/src/atlasViewer/atlasViewer.apiService.service.ts +++ b/src/atlasViewer/atlasViewer.apiService.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Renderer2 } from "@angular/core"; +import { Injectable } from "@angular/core"; import { Store, select } from "@ngrx/store"; import { ViewerStateInterface, safeFilter } from "../services/stateStore.service"; import { Observable } from "rxjs"; diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts index f7b94e6fe..1fb5fa739 100644 --- a/src/atlasViewer/atlasViewer.component.ts +++ b/src/atlasViewer/atlasViewer.component.ts @@ -18,6 +18,7 @@ import { AtlasViewerAPIServices } from "./atlasViewer.apiService.service"; import { PluginServices } from "./atlasViewer.pluginService.service"; import '../res/css/extra_styles.css' +import { NehubaContainer } from "../ui/nehubaContainer/nehubaContainer.component"; @Component({ selector: 'atlas-viewer', @@ -33,16 +34,14 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { @ViewChild('floatingContainer', { read: ViewContainerRef }) floatingContainer: ViewContainerRef @ViewChild('databrowser', { read: ElementRef }) databrowser: ElementRef @ViewChild('temporaryContainer', { read: ViewContainerRef }) temporaryContainer: ViewContainerRef - @ViewChild('toastContainer', { read: ViewContainerRef }) toastContainer: ViewContainerRef @ViewChild('dedicatedViewerToast', { read: TemplateRef }) dedicatedViewerToast: TemplateRef<any> - @ViewChild('floatingMouseContextualContainer', { read: ViewContainerRef }) floatingMouseContextualContainer: ViewContainerRef - @ViewChild('pluginFactory', { read: ViewContainerRef }) pluginViewContainerRef: ViewContainerRef - @ViewChild(LayoutMainSide) layoutMainSide: LayoutMainSide + @ViewChild(NehubaContainer) nehubaContainer: NehubaContainer + @HostBinding('attr.darktheme') darktheme: boolean = false @@ -325,6 +324,24 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit { }) } + rafId : number | null + + panelAnimationFlag(flag:boolean){ + const redraw = ()=>{ + if( this.nehubaContainer && this.nehubaContainer.nehubaViewer && this.nehubaContainer.nehubaViewer.nehubaViewer ) + this.nehubaContainer.nehubaViewer.nehubaViewer.redraw() + this.rafId = requestAnimationFrame(()=>redraw()) + } + if( flag ){ + if(this.rafId) + cancelAnimationFrame(this.rafId) + this.rafId = requestAnimationFrame(()=>redraw()) + }else{ + cancelAnimationFrame(this.rafId) + this.rafId = null + } + } + clearDedicatedView() { this.store.dispatch({ type: UNLOAD_DEDICATED_LAYER diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html index 6c1d721e5..cd80d3508 100644 --- a/src/atlasViewer/atlasViewer.template.html +++ b/src/atlasViewer/atlasViewer.template.html @@ -1,7 +1,8 @@ <div *ngIf = "meetsRequirement" class = "atlas-container"> <layout-mainside - (panelShowStateChanged)="manualPanelToggle($event)"> + (panelAnimationFlag) = "panelAnimationFlag($event)" + (panelShowStateChanged) = "manualPanelToggle($event)"> <div maincontent> <ui-nehuba-container> </ui-nehuba-container> diff --git a/src/atlasViewerExports/export.html b/src/atlasViewerExports/export.html new file mode 100644 index 000000000..88c7be942 --- /dev/null +++ b/src/atlasViewerExports/export.html @@ -0,0 +1,166 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=720, initial-scale=1.0"> + <title>Interactive Viewer Exported Components</title> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> + + <script src = "https://unpkg.com/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script> + <script src = "export.js"></script> + + <script> + document.addEventListener('DOMContentLoaded',()=>{ + const script = ` +const inputItem = { + "name" : "drinks", + "children" : [{ + "name":"coffee", + "children":[{ + "name":"flatwhite", + "children":[] + },{ + "name":"espresso", + "children":[] + }] + },{ + "name" : "tea", + "children" : [{ + "name" : "green tea", + "children" : [], + },{ + "name" : "black tea", + "children" : [] + }] + }] +} +const tree = document.getElementById('tree-element') +tree.setAttribute('input-item',JSON.stringify(inputItem)) +` + const treeSampleBox = document.getElementById('tree-element-sample-box') + treeSampleBox.setAttribute('script-input',script) + eval(script); + + const markdownIncludeString = ` +You will also need \`bootstrap 3.3.7\` for formatting and \`custom-elements-es5-adapter\` for es5 shim: + +\`\`\` +<${''}script src = "https://unpkg.com/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"><${''}/script> +<${''}link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> +\`\`\` + +To use any of the components, include \`export.js\` in the header: + +\`\`\` +<${''}script src = "third_party/atlasViewer/dist/export/export.js"></${''}script> +\`\`\` + +` + const markdownInclude = document.getElementById('markdown-include') + markdownInclude.setAttribute('markdown',markdownIncludeString) + + const correctMarkDown = ` +\`\`\` +const correctMarkDown = \` +<\$\{''\}script> + console.log('NOT GOTCHA') +<\$\{''\}/script> +\` + +const markdownCorrect = document.getElementById('markdown-id') +markdownCorrect.setAttribute('markdown',correctmarkDown) +\`\`\` +` + const markdownCorrect = document.getElementById('markdown-correct') + markdownCorrect.setAttribute('markdown',correctMarkDown) + }) + </script> +</head> +<body> + + <div class = "jumbotron"> + <div class = "container"> + <div class = "row"> + <h1>interactive viewer components</h1> + </div> + <div class = "row"> +<markdown-element id = "markdown-include"> +</markdown-element> + </div> + </div> + </div> + <div id = "container" class = "container-fluid"> + <div class = "row"> + <div class = "col-md-3"> + +<sample-box sample-box-title = "readmore component"> +<readmore-element> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +</readmore-element> +</sample-box> + </div> + <div class = "col-md-3"> +<sample-box sample-box-title = "markdown component"> +<markdown-element> +# Heading1 +## Heading2 +### Heading3 + +this is a paragraph + +- a dot point +- a second dot point +</markdown-element> +</sample-box> +<h3> + n.b. +</h3> +<p> +<markdown-element> +- first attempts to parse `element.getAttribute('markdown')` then `element.innerHTML`, if non-empty. +- major gotcha: if markdown is rendered as a part of innerHTML, the dom will be rendered briefly. As a result, triple ticked script tags **will** will be evaluated. For example, the following script, if enclosed between `markdown-element` tags, will still be evaluated: + +``` +<script> +console.log('GOTCHA') +</script> +``` + +- To include script tags, pass it as an attribute instead: + +</markdown-element> +<markdown-element id = "markdown-correct"> +</markdown-element> +<markdown-element> +- The `${''}` is required to breakup the `</script>` tag, or the script tag will be terminated prematurely. +</markdown-element> +</p> + </div> + <div class = "col-md-3"> +<sample-box sample-box-title = "panel component"> +<panel-element body-collapsable = "true" show-footer = "true"> + <div style = "padding: 0.5em;" heading> + panel heading + </div> + <div style = "padding: 0.5em;" body> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + </div> + <div style = "opacity:0.7;padding: 0.5em;" footer> + panel footer + </div> +</panel-element> +</sample-box> + </div> + <div class = "col-md-3"> +<sample-box sample-box-title = "tree component" id = "tree-element-sample-box"> +<tree-element id = 'tree-element'> + +</tree-element> +</sample-box> + </div> + </div> + </div> +</body> +<footer> +</footer> +</html> \ No newline at end of file diff --git a/src/atlasViewerExports/export.module.ts b/src/atlasViewerExports/export.module.ts new file mode 100644 index 000000000..d17b61f71 --- /dev/null +++ b/src/atlasViewerExports/export.module.ts @@ -0,0 +1,66 @@ +import { NgModule, Injector } from "@angular/core"; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations' +import { createCustomElement } from '@angular/elements' +import { BrowserModule } from "@angular/platform-browser"; +import { FormsModule } from "@angular/forms"; +import { BsDropdownModule } from "ngx-bootstrap/dropdown"; +import { ReadmoreComponent } from "../components/readmoore/readmore.component"; +import { MarkdownDom } from '../components/markdown/markdown.component' +import { SafeHtmlPipe } from "../util/pipes/safeHtml.pipe"; +import { SampleBoxUnit } from "./sampleBox/sampleBox.component"; +import { PanelComponent } from "../components/panel/panel.component"; +import { HoverableBlockDirective } from "../components/hoverableBlock.directive"; +import { TreeComponent } from "../components/tree/tree.component"; +import { TreeSearchPipe } from "../util/pipes/treeSearch.pipe"; + +@NgModule({ + imports : [ + BrowserModule, + BrowserAnimationsModule, + FormsModule, + BsDropdownModule.forRoot() + ], + declarations : [ + SampleBoxUnit, + + ReadmoreComponent, + MarkdownDom, + PanelComponent, + TreeComponent, + + SafeHtmlPipe, + HoverableBlockDirective, + TreeSearchPipe + ], + entryComponents : [ + SampleBoxUnit, + + ReadmoreComponent, + MarkdownDom, + TreeComponent, + PanelComponent + ] +}) + +export class ExportModule{ + constructor(public injector:Injector){ + const SampleBox = createCustomElement(SampleBoxUnit,{injector:this.injector}) + customElements.define('sample-box',SampleBox) + + const ReadMore = createCustomElement(ReadmoreComponent,{ injector : this.injector }) + customElements.define('readmore-element',ReadMore) + + const MarkDown = createCustomElement(MarkdownDom,{injector : this.injector }) + customElements.define('markdown-element',MarkDown) + + const Panel = createCustomElement(PanelComponent,{injector : this.injector }) + customElements.define('panel-element',Panel) + + const Tree = createCustomElement(TreeComponent,{injector : this.injector }) + customElements.define('tree-element',Tree) + + } + ngDoBootstrap(){ + + } +} \ No newline at end of file diff --git a/src/atlasViewerExports/main.export.ts b/src/atlasViewerExports/main.export.ts new file mode 100644 index 000000000..6a908339a --- /dev/null +++ b/src/atlasViewerExports/main.export.ts @@ -0,0 +1,6 @@ +import 'zone.js' +import 'reflect-metadata' +import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; +import { ExportModule } from "./export.module"; + +platformBrowserDynamic().bootstrapModule(ExportModule) \ No newline at end of file diff --git a/src/atlasViewerExports/sampleBox/sampleBox.component.ts b/src/atlasViewerExports/sampleBox/sampleBox.component.ts new file mode 100644 index 000000000..bec5619ff --- /dev/null +++ b/src/atlasViewerExports/sampleBox/sampleBox.component.ts @@ -0,0 +1,44 @@ +import { + Component, + Input, + ViewChild, + ElementRef, + OnInit, + OnChanges, + Renderer2 +} from '@angular/core' + +@Component({ + selector : 'sample-box', + templateUrl : './sampleBox.template.html', + styleUrls : [ + './sampleBox.style.css' + ] +}) + +export class SampleBoxUnit implements OnInit, OnChanges{ + @Input() sampleBoxTitle = `` + @Input() scriptInput + + @ViewChild('ngContent',{read:ElementRef}) ngContent : ElementRef + + escapedHtml : string = `` + escapedScript : string = `` + + private scriptEl : HTMLScriptElement + + constructor(private rd2:Renderer2){ + this.scriptEl = this.rd2.createElement('script') + } + + ngOnInit(){ + this.escapedHtml = this.ngContent.nativeElement.innerHTML + } + + ngOnChanges(){ + this.escapedScript = this.scriptInput + if( this.scriptInput ){ + this.scriptEl.innerText = this.scriptInput + } + } +} \ No newline at end of file diff --git a/src/atlasViewerExports/sampleBox/sampleBox.style.css b/src/atlasViewerExports/sampleBox/sampleBox.style.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/atlasViewerExports/sampleBox/sampleBox.template.html b/src/atlasViewerExports/sampleBox/sampleBox.template.html new file mode 100644 index 000000000..b94af69fe --- /dev/null +++ b/src/atlasViewerExports/sampleBox/sampleBox.template.html @@ -0,0 +1,19 @@ +<h2> + {{ sampleBoxTitle }} +</h2> +<hr /> +<h3> + Result: +</h3> +<div class = "well" #ngContent> + <ng-content> + </ng-content> +</div> +<h3> + HTML: +</h3> +<pre>{{ escapedHtml }}</pre> +<h3 *ngIf = "scriptInput"> + Script: +</h3> +<pre *ngIf = "scriptInput">{{ escapedScript }}</pre> diff --git a/src/components/components.module.ts b/src/components/components.module.ts index 9f8c480e3..d85c8b202 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -1,5 +1,6 @@ import { NgModule } from '@angular/core' import { FormsModule } from '@angular/forms' +import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; @@ -22,6 +23,7 @@ import { TreeSearchPipe } from '../util/pipes/treeSearch.pipe'; imports : [ BrowserModule, FormsModule, + BrowserAnimationsModule, BsDropdownModule.forRoot(), ], declarations : [ @@ -44,6 +46,8 @@ import { TreeSearchPipe } from '../util/pipes/treeSearch.pipe'; TreeSearchPipe ], exports : [ + BrowserAnimationsModule, + MarkdownDom, ReadmoreComponent, DropdownComponent, diff --git a/src/components/markdown/markdown.component.ts b/src/components/markdown/markdown.component.ts index e04d83eff..de6521bf0 100644 --- a/src/components/markdown/markdown.component.ts +++ b/src/components/markdown/markdown.component.ts @@ -1,4 +1,4 @@ -import { Component, OnChanges, Input, ChangeDetectionStrategy } from '@angular/core' +import { Component, OnChanges, Input, ChangeDetectionStrategy, ViewChild, ElementRef, OnInit } from '@angular/core' import * as showdown from 'showdown' @Component({ @@ -10,15 +10,26 @@ import * as showdown from 'showdown' changeDetection : ChangeDetectionStrategy.OnPush }) -export class MarkdownDom implements OnChanges{ +export class MarkdownDom implements OnChanges,OnInit{ @Input() markdown : string = `` public innerHtml : string = `` private converter = new showdown.Converter() + constructor(){ this.converter.setFlavor('github') } + ngOnChanges(){ this.innerHtml = this.converter.makeHtml(this.markdown) } + + ngOnInit(){ + if(this.contentWrapper.nativeElement.innerHTML.replace(/\w|\n/g,'') !== '') + this.innerHtml = this.converter.makeHtml(this.contentWrapper.nativeElement.innerHTML) + } + + @ViewChild('ngContentWrapper', {read : ElementRef}) + contentWrapper : ElementRef + } diff --git a/src/components/markdown/markdown.template.html b/src/components/markdown/markdown.template.html index d72c87c90..bb29d3be5 100644 --- a/src/components/markdown/markdown.template.html +++ b/src/components/markdown/markdown.template.html @@ -1,3 +1,8 @@ <div [innerHTML] = "innerHtml | safeHtml"> +</div> +<div class = "hidden" #ngContentWrapper> + <ng-content> + + </ng-content> </div> \ No newline at end of file diff --git a/src/components/panel/panel.animation.ts b/src/components/panel/panel.animation.ts new file mode 100644 index 000000000..5bb5c3fc4 --- /dev/null +++ b/src/components/panel/panel.animation.ts @@ -0,0 +1,23 @@ +import { trigger, state, style, transition, animate } from "@angular/animations"; + + +export const panelAnimations = trigger('collapseState',[ + state('collapsed', + style({ + 'margin-top' : '-{{ fullHeight }}px' + }), + { params : { fullHeight : 9999 } } + ), + state('visible', + style({ + 'margin-top' : '0px' + }), + { params : { fullHeight : 0 } } + ), + transition('collapsed => visible',[ + animate('250ms ease-out') + ]), + transition('visible => collapsed',[ + animate('250ms ease-in') + ]) +]) \ No newline at end of file diff --git a/src/components/panel/panel.component.ts b/src/components/panel/panel.component.ts index 5f7220081..e030dc5bc 100644 --- a/src/components/panel/panel.component.ts +++ b/src/components/panel/panel.component.ts @@ -1,14 +1,18 @@ -import { Component, Input, ChangeDetectionStrategy } from "@angular/core"; +import { Component, Input, ViewChild, ElementRef, AfterContentChecked } from "@angular/core"; +import { panelAnimations } from "./panel.animation"; @Component({ selector : 'panel', templateUrl : './panel.template.html', styleUrls : [ `./panel.style.css` + ], + animations : [ + panelAnimations ] }) -export class PanelComponent{ +export class PanelComponent implements AfterContentChecked{ @Input() showHeading : boolean = true @Input() showBody : boolean = true @@ -19,9 +23,30 @@ export class PanelComponent{ @Input() containerClass : string = '' + @ViewChild('panelBody',{ read : ElementRef }) efPanelBody : ElementRef + @ViewChild('panelFooter',{ read : ElementRef }) efPanelFooter : ElementRef + + _fullHeight : number = 0 + + ngAfterContentChecked(){ + this.fullHeight = (this.efPanelBody ? this.efPanelBody.nativeElement.offsetHeight : 0) + + (this.efPanelFooter ? this.efPanelFooter.nativeElement.offsetHeight : 0) + } + + set fullHeight(num:number){ + this._fullHeight = num + } + + get fullHeight(){ + return this._fullHeight + } + toggleCollapseBody(event:Event){ if(this.bodyCollapsable){ this.collapseBody = !this.collapseBody + + this.fullHeight = (this.efPanelBody ? this.efPanelBody.nativeElement.offsetHeight : 0) + + (this.efPanelFooter ? this.efPanelFooter.nativeElement.offsetHeight : 0) } event.stopPropagation() event.preventDefault() diff --git a/src/components/panel/panel.style.css b/src/components/panel/panel.style.css index 1802a29a7..2163a4923 100644 --- a/src/components/panel/panel.style.css +++ b/src/components/panel/panel.style.css @@ -12,6 +12,11 @@ margin:0; } +[bodyFooterContainer] +{ + overflow:hidden; +} + .panel > .panel-heading { border-radius : 0; @@ -20,17 +25,14 @@ border:none; } -.panel > .panel-body, -.panel > .panel-footer +.panel > [bodyFooterContainer] > .panel-body, +.panel > [bodyFooterContainer] > .panel-footer { + margin-top:0px; + padding: 0px; border:none; } -.panel > .panel-body -{ - padding: 0; -} - div.panel { background:none; diff --git a/src/components/panel/panel.template.html b/src/components/panel/panel.template.html index b571a62ae..c6bab147f 100644 --- a/src/components/panel/panel.template.html +++ b/src/components/panel/panel.template.html @@ -10,22 +10,26 @@ </ng-content> </div> - <div - *ngIf = "showBody" - class = "panel-body" - [hidden] = "collapseBody"> + <div bodyFooterContainer> + <div + *ngIf = "showBody" + class = "panel-body" + [@collapseState] = "{ value : collapseBody ? 'collapsed' : 'visible', params : { fullHeight : fullHeight } }" + #panelBody> - <ng-content select="[body]"> - </ng-content> + <ng-content select="[body]"> + </ng-content> - </div> - <div - *ngIf = "showFooter" - [hidden] = "collapseBody" - class = "panel-footer"> - - <ng-content select="[footer]"> - </ng-content> + </div> + <div + *ngIf = "showFooter" + class = "panel-footer" + [@collapseState] = "{ value : collapseBody ? 'collapsed' : 'visible', params : { fullHeight : fullHeight } }" + #panelFooter> + + <ng-content select="[footer]"> + </ng-content> + </div> </div> </div> \ No newline at end of file diff --git a/src/components/readmoore/readmore.animations.ts b/src/components/readmoore/readmore.animations.ts new file mode 100644 index 000000000..9c54dc15d --- /dev/null +++ b/src/components/readmoore/readmore.animations.ts @@ -0,0 +1,25 @@ +import { + trigger, + state, + style, + transition, + animate, + AnimationTriggerMetadata +} from '@angular/animations' + +export const readmoreAnimations : AnimationTriggerMetadata = trigger('collapseState',[ + state('collapsed', + style({ 'height' : '{{ collapsedHeight }}px' }), + { params : { collapsedHeight : 45, fullHeight : 200 } } + ), + state('visible', + style({ 'height' : '{{ fullHeight }}px' }), + { params : { collapsedHeight : 45, fullHeight : 200 } } + ), + transition('collapsed => visible',[ + animate('180ms') + ]), + transition('visible => collapsed',[ + animate('180ms') + ]) +]) \ No newline at end of file diff --git a/src/components/readmoore/readmore.component.ts b/src/components/readmoore/readmore.component.ts index 9224ca5a9..bad3a90a7 100644 --- a/src/components/readmoore/readmore.component.ts +++ b/src/components/readmoore/readmore.component.ts @@ -1,31 +1,34 @@ -import { Component, Input, OnChanges, ViewChild, ElementRef } from "@angular/core"; +import { Component, Input, OnChanges, ViewChild, ElementRef, AfterContentChecked } from "@angular/core"; +import { readmoreAnimations } from "./readmore.animations"; @Component({ selector : 'readmore', templateUrl : './readmore.template.html', styleUrls : [ './readmore.style.css' - ] + ], + animations : [ readmoreAnimations ] }) -export class ReadmoreComponent implements OnChanges{ +export class ReadmoreComponent implements OnChanges, AfterContentChecked{ @Input() collapsedHeight : number = 45 @Input() show : boolean = false - @ViewChild('content') contentContainer : ElementRef + @ViewChild('contentContainer') contentContainer : ElementRef + public fullHeight : number = 200 + + ngAfterContentChecked(){ + this.fullHeight = this.contentContainer.nativeElement.offsetHeight + } + ngOnChanges(){ - + this.fullHeight = this.contentContainer.nativeElement.offsetHeight } public toggle(event:MouseEvent){ + this.show = !this.show event.stopPropagation() event.preventDefault() } - - get contentContainerMaxHeight(){ - return this.show ? - `9999px` : - `${this.collapsedHeight}px` - } } \ No newline at end of file diff --git a/src/components/readmoore/readmore.template.html b/src/components/readmoore/readmore.template.html index 6931e169c..a54e54bee 100644 --- a/src/components/readmoore/readmore.template.html +++ b/src/components/readmoore/readmore.template.html @@ -1,9 +1,10 @@ <div - [style.maxHeight]="contentContainerMaxHeight" - #content + [@collapseState] = "{ value : show ? 'visible' : 'collapsed', params : { collapsedHeight : collapsedHeight, fullHeight : fullHeight } }" content> - <ng-content> - </ng-content> + <div #contentContainer> + <ng-content> + </ng-content> + </div> </div> <div (click)="toggle($event)" diff --git a/src/components/tree/tree.animation.ts b/src/components/tree/tree.animation.ts new file mode 100644 index 000000000..b346e34b6 --- /dev/null +++ b/src/components/tree/tree.animation.ts @@ -0,0 +1,23 @@ +import { trigger, state, style, transition, animate } from "@angular/animations"; + + +export const treeAnimations = trigger('collapseState',[ + state('collapsed', + style({ + 'margin-top' : '-{{ fullHeight }}px' + }), + { params : { fullHeight : 9999 } } + ), + state('visible', + style({ + 'margin-top' : '0px' + }), + { params : { fullHeight : 0 } } + ), + transition('collapsed => visible',[ + animate('180ms') + ]), + transition('visible => collapsed',[ + animate('180ms') + ]) +]) \ No newline at end of file diff --git a/src/components/tree/tree.component.ts b/src/components/tree/tree.component.ts index 22d7698dd..3c19da127 100644 --- a/src/components/tree/tree.component.ts +++ b/src/components/tree/tree.component.ts @@ -1,4 +1,5 @@ -import { Component, Input, Output, EventEmitter, ViewChildren, QueryList, HostBinding, ChangeDetectionStrategy } from "@angular/core"; +import { Component, Input, Output, EventEmitter, ViewChildren, QueryList, HostBinding, ChangeDetectionStrategy, OnChanges, AfterContentChecked, ViewChild, ElementRef } from "@angular/core"; +import { treeAnimations } from "./tree.animation"; @Component({ @@ -7,10 +8,13 @@ import { Component, Input, Output, EventEmitter, ViewChildren, QueryList, HostBi styleUrls : [ './tree.style.css' ], + animations : [ + treeAnimations + ], changeDetection:ChangeDetectionStrategy.OnPush }) -export class TreeComponent{ +export class TreeComponent implements OnChanges,AfterContentChecked{ @Input() inputItem : any = { name : 'Untitled', children : [] @@ -26,6 +30,19 @@ export class TreeComponent{ @Output() mouseclicktree : EventEmitter<any> = new EventEmitter() @ViewChildren(TreeComponent) treeChildren : QueryList<TreeComponent> + @ViewChild('childrenContainer',{ read : ElementRef }) childrenContainer : ElementRef + + ngOnChanges(){ + if(typeof this.inputItem === 'string'){ + this.inputItem = JSON.parse(this.inputItem) + } + } + + fullHeight : number = 100 + + ngAfterContentChecked(){ + this.fullHeight = this.childrenContainer ? this.childrenContainer.nativeElement.offsetHeight : 0 + } get chevronClass():string{ return this.children ? diff --git a/src/components/tree/tree.style.css b/src/components/tree/tree.style.css index 22cd7ce11..8c4e5f0f2 100644 --- a/src/components/tree/tree.style.css +++ b/src/components/tree/tree.style.css @@ -104,3 +104,8 @@ div[itemMasterContainer]:before position:absolute; z-index: 0; } + +div[childrenOverflowContainer] +{ + overflow:hidden; +} \ No newline at end of file diff --git a/src/components/tree/tree.template.html b/src/components/tree/tree.template.html index 0b7192da4..9d918d9a8 100644 --- a/src/components/tree/tree.template.html +++ b/src/components/tree/tree.template.html @@ -1,7 +1,4 @@ <div - (mouseleave)="mouseleavetree.emit({inputItem:inputItem,node:this});handleEv($event)" - (mouseenter)="mouseentertree.emit({inputItem:inputItem,node:this});handleEv($event)" - (click)="mouseclicktree.emit({inputItem:inputItem,node:this});handleEv($event)" itemMasterContainer> <div itemContainer> <i @@ -11,21 +8,29 @@ glyphicon> </i> <span + (mouseleave)="mouseleavetree.emit({inputItem:inputItem,node:this});handleEv($event)" + (mouseenter)="mouseentertree.emit({inputItem:inputItem,node:this});handleEv($event)" + (click)="mouseclicktree.emit({inputItem:inputItem,node:this});handleEv($event)" [innerHTML] = "renderNode(inputItem)" itemName> </span> </div> - <tree - *ngFor = "let child of (findChildren(inputItem) | treeSearch : searchFilter : findChildren )" - [hidden] = "!childrenExpanded" - [childrenExpanded] = "childrenExpanded" - [inputItem] = "child" - [renderNode]="renderNode" - [searchFilter]="searchFilter" - [findChildren] = "findChildren" - (mouseentertree)="mouseentertree.emit($event)" - (mouseleavetree)="mouseleavetree.emit($event)" - (mouseclicktree)="mouseclicktree.emit($event)"> - - </tree> + <div childrenOverflowContainer> + <div + [@collapseState] = "{ value : childrenExpanded ? 'visible' : 'collapsed' , params : { fullHeight : fullHeight }}" + #childrenContainer> + <tree + *ngFor = "let child of (findChildren(inputItem) | treeSearch : searchFilter : findChildren )" + [childrenExpanded] = "childrenExpanded" + [inputItem] = "child" + [renderNode]="renderNode" + [searchFilter]="searchFilter" + [findChildren] = "findChildren" + (mouseentertree)="mouseentertree.emit($event)" + (mouseleavetree)="mouseleavetree.emit($event)" + (mouseclicktree)="mouseclicktree.emit($event)"> + + </tree> + </div> + </div> </div> \ No newline at end of file diff --git a/src/layouts/layout.module.ts b/src/layouts/layout.module.ts index 4541a74b9..703b12e69 100644 --- a/src/layouts/layout.module.ts +++ b/src/layouts/layout.module.ts @@ -4,10 +4,12 @@ import { LayoutsExample } from "./layoutsExample/layoutsExample.component"; import { BrowserModule } from "@angular/platform-browser"; import { ComponentsModule } from "../components/components.module"; import { FloatingLayoutContainer } from "./floating/floating.component"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; @NgModule({ imports : [ + BrowserAnimationsModule, BrowserModule, ComponentsModule ], @@ -18,6 +20,7 @@ import { FloatingLayoutContainer } from "./floating/floating.component"; LayoutsExample ], exports : [ + BrowserAnimationsModule, LayoutMainSide, FloatingLayoutContainer, diff --git a/src/layouts/mainside/mainside.animation.ts b/src/layouts/mainside/mainside.animation.ts new file mode 100644 index 000000000..e3a93afc4 --- /dev/null +++ b/src/layouts/mainside/mainside.animation.ts @@ -0,0 +1,25 @@ +import { trigger, state, style, transition, animate } from "@angular/animations"; + + +export const mainSideAnimation = trigger('collapseSide',[ + state('collapsed', + style({ + 'flex-basis' : '0px', + 'width' : '0px' + }), + { params : { sideWidth : 0 } } + ), + state('visible', + style({ + 'flex-basis' : '{{ sideWidth }}px', + 'width' : '{{ sideWidth }}px' + }), + { params : { sideWidth : 300 } } + ), + transition('collapsed => visible',[ + animate('180ms ease-out') + ]), + transition('visible => collapsed',[ + animate('180ms ease-in') + ]) +]) \ No newline at end of file diff --git a/src/layouts/mainside/mainside.component.ts b/src/layouts/mainside/mainside.component.ts index ab238235c..83eeaf357 100644 --- a/src/layouts/mainside/mainside.component.ts +++ b/src/layouts/mainside/mainside.component.ts @@ -1,11 +1,14 @@ import { Component, Input, EventEmitter, Output } from "@angular/core"; - +import { mainSideAnimation } from "./mainside.animation"; @Component({ selector : 'layout-mainside', templateUrl : './mainside.template.html', styleUrls : [ './mainside.style.css' + ], + animations : [ + mainSideAnimation ] }) @@ -16,9 +19,18 @@ export class LayoutMainSide{ @Input() sideWidth : number = 300 @Output() panelShowStateChanged : EventEmitter<boolean> = new EventEmitter() + @Output() panelAnimationFlag : EventEmitter<boolean> = new EventEmitter() togglePanelShow(){ this.showSide = !this.showSide this.panelShowStateChanged.emit(this.showSide) } + + animationStart(){ + this.panelAnimationFlag.emit(true) + } + + animationEnd(){ + this.panelAnimationFlag.emit(false) + } } \ No newline at end of file diff --git a/src/layouts/mainside/mainside.template.html b/src/layouts/mainside/mainside.template.html index 85db9a1f6..89609a3b3 100644 --- a/src/layouts/mainside/mainside.template.html +++ b/src/layouts/mainside/mainside.template.html @@ -13,8 +13,9 @@ </i> </div> <div - *ngIf = "showSide" - [style.flexBasis]="sideWidth + 'px'" + [@collapseSide] = "{ value : showSide ? 'visible' : 'collapsed' , params : { sideWidth : sideWith } }" + (@collapseSide.start) = "animationStart()" + (@collapseSide.done) = "animationEnd()" sidecontent> <ng-content select="[sidecontent]"> </ng-content> diff --git a/src/plugin_examples/jugex/script.js b/src/plugin_examples/jugex/script.js index 6a88f9f2c..5a267d9b0 100644 --- a/src/plugin_examples/jugex/script.js +++ b/src/plugin_examples/jugex/script.js @@ -546,8 +546,6 @@ domDownload.appendChild(col2) return [domDownload, col1, col2] } - - debugger container.removeChild(analysisCard) const resultCard = document.createElement('fzj-xg-webjugex-result-success-card') diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css index aa4a5e77b..09096fe46 100644 --- a/src/res/css/extra_styles.css +++ b/src/res/css/extra_styles.css @@ -114,7 +114,18 @@ span.regionSelected color : #dbb556 } +span.regionNotSelected, +span.regionSelected +{ + cursor : default; +} + markdown-dom pre code { white-space:pre; +} + +.highlight +{ + background-color:rgba(150,150,0,0.5); } \ No newline at end of file diff --git a/src/res/ext/bigbrain.json b/src/res/ext/bigbrain.json index fe9c346f8..98a8fa262 100644 --- a/src/res/ext/bigbrain.json +++ b/src/res/ext/bigbrain.json @@ -1 +1 @@ -{"name":"Big Brain (Histology)","type":"template","species":"Human","useTheme":"light","nehubaId":" grey value: ","nehubaConfigURL":"res/json/bigbrainNehubaConfig.json","parcellations":[{"name":"Grey/White matter","type":"parcellation","ngData":null,"ngId":" tissue type: ","regions":[{"name":"Grey matter","labelIndex":100,"rgb":[200,200,200],"children":[]},{"name":"White matter","labelIndex":200,"rgb":[255,255,255],"children":[]}]}],"properties":{"name":"Big Brain (Histology)","description":"An ultrahigh resolution 3D model of a complete human brain (20 micron isotropic resolution), developed in a collaborative effort between the teams of Dr. Katrin Amunts and Dr. Karl Zilles (Forschungszentrum Jülich) and Dr. Alan Evans (Montreal Neurological Institute). Based on 7404 digitized histological brain sections, this so far unique reconstruction provides unprecedented neuroanatomical insight. The dataset contains a complete gray and white matter classification with corresponding surface reconstructions"}} \ No newline at end of file +{"name":"Big Brain (Histology)","type":"template","species":"Human","useTheme":"light","nehubaId":" grey value: ","nehubaConfigURL":"res/json/bigbrainNehubaConfig.json","parcellations":[{"name":"Grey/White matter","type":"parcellation","ngData":null,"ngId":" tissue type: ","regions":[{"name":"Grey matter","labelIndex":100,"rgb":[200,200,200],"children":[]},{"name":"White matter","labelIndex":200,"rgb":[255,255,255],"children":[]}]}],"properties":{"name":"Big Brain (Histology)","description":"An ultrahigh resolution 3D model of a complete human brain (20 micron isotropic resolution), developed in a collaborative effort between the teams of Dr. Katrin Amunts and Dr. Karl Zilles (Forschungszentrum Jülich) and Dr. Alan Evans (Montreal Neurological Institute). Based on 7404 digitized histological brain sections, this so far unique reconstruction provides unprecedented neuroanatomical insight. The dataset contains a complete gray and white matter classification with corresponding surface reconstructions","publications":[{"doi":"https://doi.org/10.1126/science.1235381","citation":"K. Amunts, A. Evans et al.: BigBrain: An Ultrahigh-Resolution 3D Human Brain Model. Science 2013"},{"doi":"http://bigbrain.loris.ca","citation":"http://bigbrain.loris.ca"}]}} \ No newline at end of file diff --git a/src/res/images/HBP_Primary_RGB_BlackText.png b/src/res/images/HBP_Primary_RGB_BlackText.png new file mode 100644 index 0000000000000000000000000000000000000000..3b39d00490c4274351e6ea4dcd4851bfc33da14d GIT binary patch literal 21353 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 SfdBF@0dSj3=ZRL{_x}emx=(Ka literal 0 HcmV?d00001 diff --git a/src/res/images/HBP_Primary_RGB_WhiteText.png b/src/res/images/HBP_Primary_RGB_WhiteText.png new file mode 100644 index 0000000000000000000000000000000000000000..891e8847fb5bc8fdffbb6784b0cc3db3129ddbc3 GIT binary patch literal 21359 zcmV*SKwZCyP)<h;3K|Lk000e1NJLTq0077U004yu1^@s69-g->00004XF*Lt006O% z3;baP0000WV@Og>004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 zVoOIv0RI600RN!9r;`8x00(qQO+^Rd1r`Q72pleqq5uF=PDw;TRCwC$y?LCa*ID2B zea?B8yVPFQyIQTKB}<kqTVAoT%@P<-zyvRo!6Y^lj2XZfkY*+%B!OW9NlX$%GMQv1 z8AFW0tO>-%P67nR2{zzOk}X@3wXeNa*M9GPmvhcDf1Goxs#}sbx?OUqp8mX_Tm9bm zzRNl1_dfgc2zzu%Ngw}X^z0{4tw3v_0l$Xy_mvvf4k7jRNb_<r^()WiSKajgZhUKa zxXNxTME*1v^YOx%yC-aR>TFXteVdURixop@Er0vQ8+ZEgc0I{X-@tAocjs?Gy$ISM zT7%Z;>fOlx=l2Q2!_Zt0%}vm}NKAblQXfV}6XXSFitG7}!8dT%(BeYS!kCR*<cVC& z-MKNhXJT&4#C*QA*@*`SgW+Y}9=WmP#!!f1x+wVGYp>qvA=p*42avxY`A`1`v>iZb zBOv?`fBDPAoW5;VLUjyno(J`dh317&A6KpSL!BcPP=SmPQzNr4;_=4wE2Gy^$pZJR zuaFx<E^#Z2As5?ZV$S5o+?R{JBNOwf+?dan);{uvLkCxX^NCa1v6j+UaxqL~8E-y* z`A!ef*EH<`<jbV?KfQ<cz7J!M97iT5VYEc{f^W%n`O$s2(bZ`6LbQ1?T0bAHkD=8p zS{XC}LMxE*R)Y+ocMvoG^*p-t=e%q57M?rx96r9f$Z}g#$`(kh7&5UG#!`r-Fec>2 zE)~W;T!{HhVa%rrYd_ty*#lQiPcHuF2cK{=Mb7G|;jXi*eBh1WxYNV;HAmS_-@w-r z-3Qbk{zc4X*AxVQRBV17GlyOj9{tRVpuHZgj}WQ}LIpaCI)_#$6+)c=Q6o~LrUug> zxrP4q;0H*1mc_QE(ZJQc2|l_~QLCqrfJHE3FnEkuGBFflOd)omFm|D1%u7mR-&PvC zzAo<Zb5(QK{$A&{5cKvC^z*s!*wz2~{p|xk@SQt7j9*i<$3ebCTKUUYV~%z)b93`# z^0S2Y+a<RL$W0+OfN@|w7?0QhHXylApJ^*Vaszn=MGkp}%-x7Cyb9x&SgBX}#o=q2 z&gYoOEFWJv$Nj@~T2(4l2C57cMhZ;`h*R<ag^!;m2&B(+3#iw+c6vHserH&<KfSt9 ztuFn|H}CXte$9~G=^OZ3pp6gSgFg71AUba*CVw8$?<0(+bz{-$a0%*Fv{{FC0B#6= z2%!Q~BhnzYMJz~Wk*tKggXA5ge*_j@jdTtpCZjRaEb0|Z7Ja6RlAf_FH8oBZ0USc3 zPy>UY3{|OIplw>}#xZPKHX6rj)v#8ztd1H6t<Qon)44G(wIXvX8>5e8-~6V!{o~)+ z>0$kvpgn+m5gPo%TX28yGl<c7nV9@}Gc)^E+{mapWTKDw7ErJtR=`RGLuoO+K2qjL zULZvmDSA-!kfICa6wJQ@Ca*=ZKD4K)gK}?sh?bm%PKU0I67W)8BY+iQ{0|XCEY?^E ziuH<UycJZC0GYLPdtHK8axtb~<Tp8|a~q@PBgHqqsdgXU1IZU7v(q>5OeQ_!Xm9&Z zATm#u|Gb%=dn;LH2m$IUa)ilA$hO`yVia;gx_zWOgOoE!F@@xlkWWH33B@c-Jr5?X z1+frDs9Q$~s44^3Qu{zA!qxpruIf!NVKXL-WhQO2CS$>7%;p6}Q4q2WF@~|!Y^<f| zcEA|grX{G7XVxxEbicn}=I;us)BT@%-%bzlGoJQ1$kWiq&)m%L|NUPvnVm=dt!94y zt!8%KXsDsBV6={O3Med6WQaP*0#as3r-PZ;hv`lsc?o$J$vaR^Al*6W?n9=phrAEQ zf?tF747GImd~<{iC-k$7OoV_^8pB5GFo}a$5lS)i#Lzbu5n&WU<QxhjLRpqrV^9}p zM-643kz31Do*U=&rcveQ)aysOuYN;a|H<#}^l(38X^(?E1%>-Yv>*R{#M*gnn_Hzn z@fI_8z<@&Vpo#{MwhaJMbdZ^Oq&tC>J*1pO`U_A_Lop5I4AMJ@bPqw+M>+=}>w$XE z0PPBd7St0^T2<EDmVn|_>DioxvP0Kc7V?7qS;2zM*k?2L+nmFBK`+nAvW%iEnCkZ^ z@+h8p=LjJH;DQ2hc&cZoyXE(b$h#D#XMf^Xc6!L4v9!lQE}+5Ry_w;=e;g_7yf*c% z(k<R%4<9wA-$&gD)S<3{Y6B@OW?~A;4$|#odXtDXNN*A|btnR-Gll8xLy8`PgRFy0 zUJfRU+`|aePk`3c(&g^vC>!3OC{sm&3yRYyZmqN}USTrJnaVN>W5~qNv6c<5WFpL# zCAqOt&{AkeEujsRc}`|5Mqwhi<kp&vQG1hjVcsZz(tpJp>(QU?agqyZkArNZjk{K8 z{w#5hs=gH?Z!!B08R<{P1KJu?5lQA+QOL@;`14-WIVKMw69+N91!VFlGI<2@9-vXN z?j8UPsy^=XBXFyL2RuQQpvt;yX}m{KLWls_Uv|=+DqUkaRCKtkEZLtI9PN}G?RA+i z3MTW6ex6ZUix|OX79)bHQu_c1)187tGku#`^LCV<1E%M1dGAh7!84Bb0Fr3qZ{AGv zgRj^Ehw59++&*Kb=a8(3;#AQ1*3SzlOUQGuSp-PYMe-8Uo5SqC0j94&${D~!F-jFJ z=b)HL|4-tTTZ?944XB1qfDD~u;2c2}4+LN~FPO=5g2w-gz;vE-Wv|BzCMTKC3wqWv znPr<C#$=YP$S5*PW&{@kjrWw+urSeKeyVE`c{{2<hcG?!qrbe<Q}K+VJ%9l2bEj!O zvW;_Sc&kiJiCH)h!C-9U9$6lNP<D|{Kk6DLj|zCvMf&@YxvP+=%cBA=AOt{BTO!?q zAQ^~B0P)Z+gVs^}B5dj%O$e;D?IvIhLS_tyI$a7AMKC4WtR}LID|$Vy?seH$7W6aA zM3#{op~y3`BBRVRy1Avy43!U2(YJ;JQ#~enrB#u)YvAXAnb{wH&rVOtGlrh^0NJ?Z z7S{g8HzC$8XjT7$YIv*kI%4)8M#>J77x7JROjN*&Zv5;_BAuxSn(j1a?n-3lD3p^? zzd%rv!`O4t*^g#DlE*M!TphGa5Zoq52^s<7G_c$@_~azaOtfD0vyA;^hp|DoFa`l~ z5vH<?%e!5!?Dv>1GIC?+7dhQLr<YlJSw=6jh(vcuX)V1X<KS$cqRcJA+eLX7FthN3 z|8}RR<{3fHY9-x$>>-B#*WX8SyP!>TD-zx!-Av4pBbb?4q%4uL09Qu`V5fuWMBjpl zA`?@X`6HNpS0H5%4Q+G+7JUc}B;iQ15_${qcLdasxJU)n!yq29IhFLev$=wGZ>WQ( z)xca)l5g{GSP?ol!>h(9Uu&^OD2!zk0+kO;Bqbi8^%}*qET?Ns1gpk3W6xSt6(a@( z232d+g_{V>i}I0)n_gcHZ+rhvPt%UlvlbxhH{ZhOpO3*&-wNR^lt#>feb{{mkg|kM ziR2mh8Yzm%QOXj@bIjx{_Tbf+iCM%*<N%>Xdb5xfkvrg{^RByq*bLDI#YGO270{l8 z>d^=sn=vvIe5SdQb#G|YQ-#1>Q81O~1avdxq_mcPmf=*fBG{A@;#FB~9jcK-Oy@cK zIwc07BZiI<jDkVX0Nu<YDritLYiONP*UsSeg(iWd`-1PRMt3}WLGr8=vD!boo8}Mx z0HRoRD<QlED`KXnG4lr?FCj0Ycm<G`QST@_$m9a{@O9Wj*F<jNTeKNQE`uUjC%)@N zqBvDfMJtxN=<7h@CV6xahH6V6QNJl-c~z?58G28i#3%vU5C}n;$a1FhJOV`Gw_R%q zC{6H;oX3U0L}q#Z+!W89pJJigp`S-FtF&?IJDH)(44urff2vEbS0dI};QL5A$@KU9 z;!aQ5j?uG@gPc2XE%k?f8!55{ZJS#~!&}HjWU7Zbas)Fm5k2JH5-Cc+BY76Zse{L` z`!7fG9NJM7qe8nSP8D5b^1zmIZZjnBgWEtto%|jqaf}j1k3w@Au{mTJl}vC)a}{ge zP-~zGfrX;O{!Rz4QB;Z`i3<8evS^72nGtepI9E3eU7!u==)^JR%YyyA4xP->x<Cj4 zL)2jiO7bC+8N;Az2~Lgp@IoJ$SLGww4S%I-@BF|{Pv2Lao>c%jbND(;0aE4*+P1%d z`nO0XLchcwI)u#5BAqgBWe0MLWEp1v5$t8hF_SYy?_0H%MHpICBS6gLexyGSgy{3g z3UC|Xt0;yU8%3?LgRngZ?U^Ws*__HuaC?0<Yr)WjfC2~0E^|dmNLDN29Z&~P0D4&t z#D!fL!-n^qts8tuT8m0l=95ElrYxB6l^7AK#*-x@f`(`<%OnO23~L8YP4MA`UL#1% zbHAhVpZmR?p2n{#J!=56c-ak6>JgpC`>1nR5$P0|eG8cV`!~Tsw}X@gW^x*P^f+eU z;rQ0~Erb?zRSa#{Hf=6)jfJa_vQJ#X9@;hVRs5~7k%L$ZHjBUWYY^5Dngoz3Zf}mW z>J3%!OlCQAdC6p+M@Jvg|1s7v>Jac6b&||ja%*q_o*0b?W0nA*LTL<HGGCQ8V}HNP zbeU7Tfb$`qGZ2Fst)X#&y7A!EcpqNqgDxoiiMj4Os&Ln{2PDr59c1yc8%RrQ9_3b4 z--3upULaFb$ie~?QSTtjz`5u#pP5HpgKn%sGeW&Xy$9c>%|VDxKx`iUB4WXfV012i ziXai0aYLe+H#G6HI6$;^MX$#847P=$#0WIr$3R694CBu@c|-3w*VNR(lZ#;{&&iTi z$_H>>aUswKO<^m_$T^lrHMR51^-5~5G(M19iTX%x*+1Q-(<`vK#fVsxw`t%X12dgB z{Og^b(ytordJeL9*$apZka>_>5q+zO81f96=wpr?!pzPf{cf~ag%)iq>=oBwCMM8x zC(-qDFj$6W0P2v?M$Ukb;U*H0{w%U^6$}^AjVHiJ6rGGgZ2VMX6DP^G!b|cVxU-10 zRCbn6w~>QbW4OHAk9i=*(6d=|8WQms-H-9_CMZj7OKvPPMb61l#n2^C!KNNEYCUHM z6{punEDdT#jpsnWi-_>x(mI5wljJ5kB?TB%4xHkGnh^9Rf-VUB$?S8#vl`yL`#H(3 z2FT)NH?jo|egW0D0#U45n8#jo753O=n0^njg8CNP8Z$SGJ$@Yok5+^Dwr@x1Xc^r& zi>{qUhs%gj#AaX(GIx0twU+OXRxoR~05TS@k~tR<v|WlErI>;+M6{x^GkiLUSPMnT zfwF_sq>yL%CLrQx+#eJa!3X7us-j~pAS~6<$+%v(Jib2S^sr`mR5Pp{At;$Kl-4lY zDVXb(oLH?`uN)TW7{f%7GuJC9lDOsCK;3vzuQ%n$LdTjvnRw3Ys=+<G8zj5BXoG#> zjkphgctNHnZY9s&B2!ah4(-Pr+=t9eAk%%op=|}eiJK_q=v7G^B3hl;CLRTk4%Y~S zQ~1Re(<w323z)ow6-6fJHa+Q4tTLOQVoWssj`bE>g6%^03K*_4b_Ql*INa?~SW6wE zMarvUL@*LXD>D|e1Voj>2zd}bxwH&kd0=^sGlPmj<Io@+>~`3n7f2G-1jQO5Bwxz) z`=|KG`WY%0qO;#XC$n60aEb$yT{bovPM#a^#F-5<T&uosRJF8~`|;QQt@p3|{?F~+ zLJ+$aAZtJN^SIA^WWnBe<E=71^%isZpvdeLSd)Af9=wmBaATdK!5lt}nVE;C2JaDL z(dgLN0*Nt^BZLvUzKUOe9DnLA?19U%$8JQ-A;=~euQU~lh#}_Xns`kFObJpToukk` zj!2BUoyiLhcDlHrG$!CwX*JL_89|hwNe@x50#0Dy9FJ8Mx1U|&#PSBMSFFVPScRCV zyTB$uKw?6cDvk3Tn(T6Dvda_811w-PN{^*6<e6b%rpsk>6TJA!eXOii=G5u+?;8vo zsT%j=KmOj^m;dqi+_ckEeo1K80wnwTYcPi<-llc+76hSf(58ynfNcZ0LEH8`a7dA1 zj$R2Npgx*#w-jsG;tIqCFG+M#0r6<NK{#_aQk_L6_rcVDq&tUXc>+t?f&<e5+lw3| zpM>s}P_L7V<!G<ZR9-T4ElvY2#b{X(ycn!R>(wX(&NVG3o0=1K!-=Y9Ehydyge~L1 zb{j#u{ms8?=TQ@NkLT>4;?(Mp+J&*ZhUg|yybl-)-7MqSfhn#zG-Cwb)+@6+io*QM zou2PYMZ4CD6&_kF$ctCY^t9kSTGtU6ZHu~OeQKJtFK*`G5u`tXwl%mmhPbP41IgH* zJr<o3NU}UC=|yh~0M#<AJ_@T3!tg{i3q$-KL9&UMU1)nq)<e2iBeq8`%Q)Jfq?2WI zY(_WB=x5Pev8gya$H`H}L&E`&jD{??Ek5}$x_L&gQ)28EFvjB8=G~`#L8116#`$Q` z>K0r+)2D77we!?2#&(4eW4am_kFA<OP?(+Qm^`zuHu0f$CGA=cqQUQD=&qA)j`Vxz zXaG$cxdvfNyz&jSHPY{54j+vi#WkB=at-bI)x(4c3}ZoxSof;vBfS{ei^;dXHx^K| z9fB+(wBWnoH=r{gxrgn7DWE(A)e`e%hkcz6K?5Pn@F=zSEH@1b44q@Gbqu{j$FW$+ z{9B07u_pR(6kHp~)L_IUy+gKiE1>lO37|#^$!>i0!UT_=+n{QEWO%9+#^8g}Xuzt_ zvC3pOr}cr_`5Vg2%mHV2dd@Ee?Mi?QfAb^wdp>gwZF^XKi%fQr*=a%o37Mj8h@zEi zA$aW3V@Oe;O$s0JDTE|6Ng$W=IEiEh*aFF?k$xZPc4G)iSs<Mb(&-^ZC&hIoAB!!( zt)cZvAe}cWQ4C@xkn(a4OP_w0F>ozy@T@j9XR3<jrbaqK$66*4a%1TFKtBYAJ}_wm zQ&~n;1nMF%C`uNGHO?#esNl0HUPaxb_8v8gYV@8Hlzx$M^}-~der#z=7cns#v2{wX zV_BH)Vy&<?Xc6J4z*X!4<ZFlA9iISeZ=!WQYKIgS>2|S)4&WaeL<ird!>>l>rZEQ& zMsWB#0i;0_KqQlBE=sIK#Gp2VEQY{K*+=pc>2zX#dVdP(&On*!VC5aiJCK#J^+9JY z{l634gP{czC_6_vkWEu-bnXelbY9Z683#IDCi0wKmQ(vc<vk<s7&^~->sW0a%g!+n z&j3%Rf-3{-l|#|<MJ&LDK=6tt_q-TIO{}eTY;J;w7B^ViXpmIr%MiKKv3-;D3d^u^ z4C|I6vr_^${^YNG@?$^q)|c$`)L(Skl>m{Vuv*nGrghL%XeUE*gG^0guf7uh(1S1< zC2Nvmk6sbELtDq_+>k`C1Q1n0$~+10CI%r=MBtTuBrBpKZj1Q0Euv@LCN5(;kaf|l z131XKP)tD9Lu>~zC76Pa=`oXa7<o^}W=w}b?H!|(ehbjG8G=tQe8rh40+rU#4Hjph z5hWO9I?wP<X<eW&CKjGcN_*u3>NNu2D5yaLU}EUXwfm;{*!o#&=aHbybPJw)c$Vo- zK~*`1wPV!yv03aT6QzAOpkMK%Jws_%0z`+S8N7cUZHv|oYz(1WKqi=d`;bnKyW=)= zP+^W7L}q8B67RQkkMlT42v9@^pP0x!M$QsKj&ad3A;~ErnL!$25|oBmCdfI&x0Jmi z4!9|zu3|D2%V^XPG)Di{8mL-F8v?Zt)ZSBjPvcrz?~y#mDR_-*(JGXgr7MCHg9jQP z2v+F^%X9$)brfQvk}nZh!`5R0r~ws;qL$H^%oX6^RF{1d9ZoC{nCNx5>DU6Z-GcRD zOXCBBs-<o{nKcO9xUM=0tnKvNUu@cy0HJBGRPT?`w$M6sI6|6UH0^4@96CtG3vhRQ z3VZbOXwh<!Yv?x4L1;i5)U`;ji)0qas9VENeBe&*cwz->%_e6tb{t9)m2}0Ro6bVZ z-?`;)@NIwTMX2N;IU*VB8$*8NfBiqW_x?x7^D=tHRg=d&hM@;loOd|yBNs3Ru@;*b z6x}ZUxq0TVxPrsadp=WF9j9LuF?>BJ{S3q_Wzt(Rgw_RyttW?6h{9|JD{4~KOKUj3 zf0AM4`1)%PaAc;(`lyXXA6n0#b~G-KJCDFsLG>u`xt*T+i%q)@AZtJNkI>fLi1Sml z(P|~?(5gndIe3pchdp)-ne3rW6}d;)@`pq{q(QYt(kE>n$_Y><%L`s}{ZX#IY#%1G zNS?9YDF5pBKEavgbtIM8`HpY-I==Z;FGa<I%{g}TVEo(UAQ_61WtJ<hIL7Ut`8;>s zbvxd<R6_K8`nET|iR*8AA%vJB+qjmMl~o>n_#r<3iH~ve-~TpcugB##zK|PU_g6Ub zvR6i{T5ynAtU@M2hGE0C44tDCLl6i-QBg4QQ>~)GFxM-2-Hn%VXsXMgiXkfAE2Gv^ zH69m~ybTD<XKHTviT8Z$bN}dXynLsZz(u892N3C$qJz<k@!n|bA_obI3`gix4~ZeJ z(HBw_h-=W=B@R-9uajt1L#Sh}N2t(hfJ{vTN-ibe^@``j!qi29bi14xI)3X<?qYFu zfH8s(fp7e}7jpAE{(2Ot@(#3vSPyF)ZW5r|?eWt;^>=y4J8tH;e*53?bN}R@a`NOU z%r=mS2(S6pZ{^$H{ASd7a$~83r*fW+s^W=Lr}*d}{5~J}rFV1BfBP^Ged-fj`_13N zOTYIAC>IWZS8R|35MjM>jGT|)5JkmAQ7c4{h$_ZHFN@`Z8W->>8rTPAn7D@zN^LwM z*r2@RhRf!DYp3V`V$-g36o#dh3EK9B+O*N6+axh-RBZvndvrL2QH834wP@R*b%i!{ z3^QqyW3j2xrXdX0q7Gs+usPo3cru*H38utJ!G-Nsf(odhK~S&pTIgmStZ{`Ck%9vU z4)7!I{84`4)?Z+Db~YWmMYVId*5SQJy+?e&XrRb44jw+t>%QlI;z$0;tsFUi4NW!T zbN}&!{P92jS*o+A>Es!?6>=khvRpUR&eM2D<$c_or*eVHd0HQHLV_xTx@Dtw5g<O$ z`oOSuv_2%qVMtCzf76HWI@#Un`M=n->j0v4bD1{o>M=MpdC#ev=s9-|ZCi9yMYC?x z#D!lJ(fr#s5iCvG+UQ7RV>N!C(qplC${$JQ+pIj#2^UA#L~+XIF(1&4pjl7e`51O0 zrjsh1!~pg;y#Dok_nW_av!3plLaO>+IhRswW7*Nj6(9$m_tKZ~oj>?P<YfmD;n7e0 zIiLLHcO$MPw}#vrS|8Y`8X6y{T!`NCHZXEAkG=6qCyxcOYv-w3&qm#DCRUBT?#W5G z36jQzYe%g+xYP50v1wNUWbKxBLDN2$ww<SGiCn~?E^Sp6lcCx+0%Ht-CT8?Ww;wr7 zQ=b=tLffFDjp&Wf@?^rzW7*LxIRfpNgKT^ACw)W3AoViTD@mcx5<rR=u*jB=C9{?{ zz3EL%Pfwr6MQj4!YTK~Z))<LtwwW=p%dIhFz>B`&8#!|Iae^u$hWkGJUpRf|rzujU z#r3*nqiLyK<QjGB)87FXl<7`RZVh$oBXC;JsPR;7<Se7s<5Jj3okC9%Kn^r*xbC0+ z?j1Y53@$3|Du7Jh`nRBIU#xYLZHicR6tVCgMlnKH+ZJu=C{ERpQ{dX@<eQuZ@8b7u z6*pI-^$1-*hZu{D%l0I2h)n>wkb_9HnM$<;^>VUiWr$?)({|G_uw6X6=9=RiI&|nf zfSANJj0kH@!&=*-8VgpB0g?;M&CPT9wa-CA%!{u#HhAP?A0-p8MpztH3|mj-qc}Ad zuR>7v^$I$frD|gwRuY}4T1VYFS|62jA7Tb#<02PoT2IsZvUTC6_y750JG~4pD(xyI zef7KkmyR~=i)bCRJ_aVXE@t#O7deV*<Q%S!Vejgq7ugei7`;BsE}*9#0#|`XQ!k++ zET4|+I7>-VW6>%rV_(7xbP#nl49>*5ysC)JBe+Zwt+sOy+2SG-{r;0UhzOf;S1u@( zb8Ki!Kg%}@%Vnx`@|@Xy3+LU+@}m!9Tni@W)OtnZ0s-*JZz5JWG}&RI$YOM_S2QRZ zz$2(tNdK=}zp0C~F2n!>por$^7ayMMWq{l1wQ#X$R{=x^)d8CJIb(oqhO>|J%(HQb zd-8j@Cbly$0m+CSb8IZ}vv81b;@;TmASF13m8EDl?sXAslZX|)=9u=$9K^-SCrD~X zAX&-{$zzb>dBGnSfa?M!ecU)F6GMwbiQyt=s*35Npl5S(u{aYj<5p~mLQ|6gL+?2^ ztY}^ALt;hAEL=G=#cZdD4o0Uj1SKfy6)%#kTPeF(Vvyrl2N^$d2ue`%+|^OL0G!(C zwQ#X$*8pVY*7u?x`mfKSbq96KL0klg=CLB76a_jMK_)3tDsd325gaP0Iz$aJvw-mx z{?WUm`BywT+&~BG$kYsECW6C^0rFHsInE$V+{L7P5Yvgu`*{&kTeQBm#?sPKN^FW0 zxrf0TL3|80G-6P7EY&q5>*?n?)(4i)oH_4&{i$iXWy#6q0fVN+htyKR3fJzRWdB4- z<2>Fe-o+TL5CE@aNHhUkP_Go3A!sZH;eDVP10-^g%e?Awb`d~!jSix}`a!7Mo3w5_ z)JX}ilLf2wNIzCv*Q$c5f;yRVw<+p6R>%|Qk$s0TmtBpf_R6#kVRbPn=tbOYzG<m? zQdjM<7v1DB{z@!j#@xefahwY{&z+zBEGJK%JP#lp8_SEv+PrpCAIZ|bb9Kez!y%_m zpXTAa@7i=WiU?Oe??!sv4rew-G(Pr0v?w?1pXRd3-loF6W$N9oe?%Q+)HnupENj{? zGn@~2H`YV^SXuAaZFs>q{GAW(^m4dpv}-sB`(`s;T6-~V3ogb#Y04Hfa}zK*5#zF4 zgJu8@e2a)nRS*CSiXh@qlf{?);VTfM_=i6Sp(ZS!N%gIYSl`NKF{Y|o+e)R~qy!M# zg(4~Bc`tDfNtuY}i&(?qkY9f9dl?J{Si6;nlpDiDo<oQZx{-Hyb+OhJQHft|TmJBO z{ymRB^dQC<Lhu|sb|o+U*4JWy(;Fi~%I|p5!Fi5NPtc?)jAOAWcqRA%E|yL)s%Wq@ z2>2*!O?7ihYglVNMXDm93L&Tj)t4+xlqsU?YoB%vKy+n&o~F5h)+KPHaCY&?!G(BG zokB@kpCVXWRARKMZnjq*=bEP0&V|8T@f;{D{((Et)w8Jgn3NBK2zu^8^whmEtYbT! zSF|CERo}CzBx;$h^u7d+jlqChZ@rZd{NC@K=ShbMklC^TA6Rc&gqS3i*_g{=oa4^_ z?~nMS_xvK>$3BWvGqe1UKXMCKTyrgF)`y%OR+QHAvLpMrdS;SQasW0y(D;CJQ3t_m zw1Rn!2~`3Fut+TCU@iNnI-Fb@qHUlAP$gJ{SDt@zZ8Qxm@AR6u7_@5uLRDWy(;iiq z0Fp8W&A}ODW-2-j$NA^lrp(t^fjz$KV#tNG1~n$>B{udR7a_a)c}QXLkKc`sHehx? zv^D;*&ta;w#FV`AY0F|YuBuo@jw2ZmW%;&gSXy4;_S^5^-@fNPeCR*^N1CQNPrL%K zHrA^uGK=*dn<8`D;fRMGe2_nU|NHr)-~LV3&MhJc{Uew0_22&vUh<c}ozfUqtLQ!d z`eO&UZef~H$`kRbv>^h-`;e?xN^oO<2%c!!DvTLhu9=xAnd;^&uGc6~1SN}w>8_}R zW9$Ls8AeU>LT%gx-ld##2ZdlRKN8KXZd-wd(4bb4ES3mOH5a02CI);4YRBnO$;N8Q zp7Ua)Q=o%obnOi8p^qW$I?T**q5fe+_?`d!5$=EVWK_&;9`i!PMqoI{XjHMhyu#y; zKh7hMKFVM)h!Mf^WQ*4Oe&tvB_{Tp^>!J^%^^VoGHBLVE7>_(~Kda{!$%>MNV^?tb zD_+HAul;u1fy?;dqo;VyfqB;ImajXypBwhgGHTpr3a${vBcI}|LQ=kikNF}dRZ%77 ziWnn^6|5DG?VsS>dPUuOyc!Bs3e{PoJRi7Yr<cUVpj`tHZQXHPE9za$!JtKolCX3Z z&YenKoDy(wNMX>9jUreUqOCuRT?r^H(kqcsiBw%=)QP6uUKg|P2-F)ipZr}!TV!UQ zr?Nx=EH15b?}Lv6u{bpnbGy9{c<&jGD$Xq~vUu(sbzNhveTuV0dg8<rv~JvzEv{Lc zXUy;4$7RQlGqG<U^H&{b>Y8hDa|<lhj#GmX_pA&!wKm|nQ<EH+>ht*ekYODYo(7d; zSUE<uqp3Z<@ks}XCSFOBsH;8B#eDP(q0pHBE@=S`osWg=)SwYl$9H-?TrAo(072v- zOtROif?9MiLhrwqt=hM-*ppa|2~@pUbV02H;}9FrEI`L1-6C$i0_m1Ww}f7a>Gd!R z2e1pr(Dl>l+E2cnpvv36>vi1xgKtJ{2fFi!Em6q{7*q{S>seY_;d6I=j^F#>2l?QK zKE#<bXU<d3<D&^T-+T+-_1)jiuxY4#V5zQYH0lR~plo>0O6ypxYR-)6SoA?*ri@KK zo1i?pvd*AtsTxmRJBGEVsvK?O3AIORPi(%c5Cj{*cxWrfVASHhqE5+uAWvOHwJgYT zi?t?&zDTe}9^UEYaIt9D07Q!74vZV%WCAg$H!1tagH()*Lq~5{QpC@5#wFp}7$nPL zo!&f)u~%t}=H1xY)=ce#`NQbOsm(ycCk<|9>2!N&K7o{}dWk8L&m{Up=H})(e*9`) z_wC=#M?d;e{@zdh6d(Q5KYg;2UgUJT9fGll_e@&LM%%`=&b}r1KyWT9*F?+Gp>E0H z*=Y=LcB7&&LSqfy1cEUXM)8?Jz2dA!hiOPYp>5Z;Mlcc+ePiv4SeY^88F_BwW`rU$ zSZh}E%zS32*Tco4UE>F#Q#?SPKQ4I=d6sg-^B53#VF_ZJtVo-SdRsNM%x(oFX2q7L z+-95LAox^hu7^2vJu-86TTIn<(rD>28LG+ZsTzN;sc~nrhFWWR&1+u6FTMNSeBH}m z9xYMZ&!lW-8LC1qF-TF-aUb1HYqb&K@_v^aXQ$}rHhRdFt(Yx){$|rA){L8qoOfYs zqRz4Xv4S<2Jfp}hom3ItW)_><N3tS&Xs4IM#iCsU5Si#NQs#G2#MWt2<Vc<hC&)PF z2_WTkjNj0bYHUITPF`#|0n;W~^mmBOVnsziwum)x5ND3Y4!2LLpC&O@YaHbpTx_`( z>I9A_bDiVIkMmPM`IF4d%$yf)5_8mJpdiGzcww?JvxvCUSRwJsevhO5E=tT#ANx5n z5eh4*g1ey{|7{J{O4NbOxJc>t#}IOBxBQs`lDJ1FGZeYS<d!@)l$p76zE`g7^m@2h zv}*vOCl*_Z{5HxQ${g|tkSH3JN&6-_?y@caL{hNDNa8#RV0p2{G3*vcQq|~jALp<8 z77aeOf3_K$i(Wx(DmfZFv_sTwprK~VhnCpF1weVtt6$A4zV7SJ<07$+RbZoy6;6VN z)WFlAssxl?X1T1_Wh&2TL+rKQBol7sWBk>)y+KT%iq%j`Ysjs|W(Jd`?pK>&5LARb zi=KM`$t+os(a8*1ZZVmm$P8s>DKq=2jjG)hPD0p40I}ctVx%Z;lOnGvVgWab94YeD zkTZI+CCfIWaW@@y#z3A!k?L2Cn|RuMv&>KACV470m%>uUT*RkJDCgyZ5OtGw5R;_Z zVQSQ=+jO5Ng9D)3?efZ3zVbYC?`D}F4Gf)Qqjd~jyV+Q0GSAu9>5v<VevJf-CPZM3 zyr&LZC4zEe$!u&RU0O?#7I~f-Y-Tp=`l^XF!At^?F@~a3(8+B&-j0Dmk(oh}nL9r8 zQ-5ivm%~M)T?3HmfA!PQ$?u`ePfG&Fc#}LsiVl)>;zA$y2;6LNV`F}LUd9~rJaG<V zkSvcJCCg*UpSDgum2t>?0*G&)wARH{vFT1~o+cW2YVPr5kQ_U9j4V6fhBB^jGX6Jo zv1ZnEUND^(D7p!X_!&j3%6mpBX383&u$IDZiB^S;{R{I1kUX;(YY-FLEyRYsLS9(9 zWk#=vfXH%7ksHd~q)&UIQ`r0VPfzUha=2);YphrR(koACCHG>@<zfV^pyHz!(1X|s z$P{eqaA`v9ACPAe6h)CrgeK0CjV)P`d#L)Derp2iYQ%N6KoZ6aeVhC~uG0&NbEH~X zq#8T29U$8b20&Sso7GXq6o`#BH%~9I^|KKnAXsMzuq9drrShJk^Rdr}kBvVIYj8H; zlSRu5<W^8?qm$pq$Xy@8W)o0?(kXLHrsNWp`Yf{)nWfB36xpn~yWh!oRSD8v!$APd z3$GtZk>5^{K_`bYOCnYt3%^bFk*SFk_F-buleLM{B;8{i1)C+kV=P)_1+;Ypgl}Up z2RAM@=hzBe-^LA$g}l2N3a{#;t!a~!jKT6`4zjYk8W-r8QmrhnF?8Rgs9q6C%{-&E zDK{ypD%IYtTwv%tjgJw#wGR|FTC<{pZd36$*)-LTH)CQI##k87(C_7xog9)FR*ttk zTCvK^e(JLiue3Y84le4N+UXm(n52)KfFl2t6orp^NZh21oIoaeFxij#$v8%;$dICp zO1Vj<Uz_xlEZ;0EDmFu_VNA#MZi{oMZZq*-z(GRNIZ`RovA;wK+jNm9i&zTWb=Tc^ z=g!-wiGx^)1seiUC#ih|fDd7_{j*J7ajW1N`j~IthQOe85g-?AnT?G<GZPzm7J1|j zl0>Z{H%xSMdfgnG8H%WL(8&#*$Vr+aH@Cg%RhRGdGPtO;s{k_nOFs)m{yB<#Nj7zm zEP*7pL6B|-nd-+XiFp>qsv?0SgxFhfn{#k;gBYafeJXw(Ls~TWO>5SZNJBF1x~;Zs zy1CSOIz8mnsndM;!yi6RNe_vGj8ku&23&|+6QZ8sR0*1NkHl5R!H6z}$*-q4tT8ak z?I1B`lM|JhrOXYP6|4~^IywC!W4e>kNn%yG4Jbus&g7Z7=Uv~kS2*q&O;Y5Ks*wjV zvM<W;Ne3w{bV@KDkzOngM;k>jk+|@;=^L`0dt^EI3ay4nrf6u9kj0Xuo1Ek+;!V^i z>sGRqMPYuMgK)bCUIBymo_GJ^FY>w1efGSNlbGNaV@m?MS;(zToFR%{f>&_Ka#j1l z$ZfTN_951}@*z-W7VlH>Yin>>z-I>U4UO|;0!3yC9<ofB>|}JT!C0X!6$_Mk(mygw zX63#zHz#&_{x3G|I)Ip|iS@8P_%z16Tm^(2#UW)9r^KQLgvn@O5{+4h-FyoS$$zr> zndRtUDOQS?EJptN786>;HAwJJiogYs1O`!;hB|@^lYzznKN%-_W7*i);N8FUOZ>t= z`vpP>7jO__sE3IPxlbnFu}Qb~p3E595Tb)nqwX>Cev{K^h<eDNaqR0xlW=JTZv|%z z!N#19U;=Fu9gE$<(ziBNSu_T#!emGBPB+0(=7!A39arw39`5w~Uu@cS0MVvVitJN3 zqaui^M<gKq5+uavT}!k?X&p<7cC+&Uv8kYsLriRrZmgsN5TH(3TpQ6ki)kR!9%p@v z+om%Ju)4Ovi4!N$RC;t~W|Cg717Yl_%<$fD-@W(n@lV{wzx(&U%YXT!KYGf1kmcni zo_OL3ymP1s#pER3X3<L?JVWovgKjG90#x49rd}f<RaR-eXSr%P((mD{!CR%U7H<_l z28cBTqtuOOGRx=}8KsSSk5!>CIit!aABdsMjJL*oa%rR5>FK}dB!BT!Jo9MvNB+Uf z@!=0>+bQZ1ibp2O%_=6C0Hbwubukug=oc}(y<0%Hj1j!O4$>)*ZU=IM`|N*12WK(e zl93hO`ClI6lc%c~o?$bb!ijV1G_l$_s<1FS$;|X5<D8Oz^pii#8~(~Sq2!Qt5L>dc z?s&uh_`UqeNB$J={gWbj<0J`l`xfX=OrQ!`p7Ue>;2&`P%U;e}U9*uI=!~sbE3Id> zZh35D#3SoN)`tzQc4*_N8_&UBmv6lM0Bsu>HL=gfD22dRO`xtFqbhDj;b6bZWSJp> z&91qFks~w0L?@%n?V>gEm;S{Me*LZqQXJE+;~)T1<PYNgqbfX);G&hPw6S`*Cniia z&6dM(tcd5yNtoxGRxGj{+z1`6MX|^`a%=dj*H7~eH(Z9DnuE+Dd5$T&KuD(J)G)`! z#yVsLH(YmBEF)wEf<uj^Q^1eD<8A!)@BS{t<WYPIN)!C1<;jIWlSHMl&tu<_%VO!z zv4}P193qi(gg`&DWFnD+j57s;vOKD3leyQ8eIeH1tR+}Qts!KF*2H2Jqt-K7WMoz- z#L%@d(=r65$Srwh9?GnFbf@S2QqZmh2%U0?rv03VJWtgjy)tJ05MmqX;V3TRvWONa zYg61-H1BSTS4r&B^|R4IXo29c!Se0b&XOO!7MYwz$^uefLbQgm3%yzBOhVa*BDP|~ zcA{}W#(GFbS(d!v4c`_^0ntMdhDph8ypQeHf}?T{mrT4P=h$c(25lRI7(<{6o{<ma zVm3ho%3P7Ns6#?X>9i;#=h<i+2YVfyG0|aY6>mclv6Mz)h<lM4G9zr%Ez_NXB9E4< z+#0IJ$8ZyC@9Y+K*EP?9-3pMZ_7ugZ5WY)HC+Z)rjrHF~6@(B0VFF^3snup%aax;J ztQf%?HddmRoF$1=1KORziA9<^D%!@xPd|#`k9jwF(k-zGvw_@1J;)8?CoM>Y`XORF zI2p3)`V4(^@-^GcxOHlt<3v&gcE8PI9xYaD&eOAO1uCXI@|mLG>?r1-OX5&VqlTlC zJ#xDpAo0`LX!*)*<QAndco!Jej_D$g;V3DiFt=8V%zV<F=hfUjv?~E(Z+H&jk;iVw zF`|=qrIVu@v7&qQlpDxW<r1Uut*_y{2;j{M?g+ZRyw&AaK>9P#os6KffEUDBlp6df za+(nkD#Wj$Z3mh%1xzaHS7P~}avCY;pqK?pM%78qxqWo<X=)!>X=+xQn)S9}<UP}Q zERa1+Eel3IF!Y`c?^$)873ZSw!|Qp#>6JMXMb3H~B8Y*-QH2o_KrAi+Bv@tL$HH>m z+)$b*rV(O@{jl*&b(Bt#jr$#L<e7Z>|M=@K-RWt+B(y65(tq1`vi`IG`d*AVCEff= zd>aeK8HxIZ3#p%=q%x!)t!u<gZ{~k&TC#kLZmey3*`+&ynLiwRs5iq{&BPgK3>pSN zjj1Qls)v+aq}z{TR@q0(Nu-=40(257=8<eNavE*%)nlBfyIejsMP_1zE~HTRwBXmB zW3_47@Q!uoS@(f;?^$u4<;Kw!p@XG$A^JI7)Iau5bnvZby=pN6OQQxCq7Nk4fH#I< zEsaDoaC9QtSji-SBxj-s3~SF+r(mL!(|Z4Sk(mc}dd@Eu?Mi?Eh|Nw@=J$}vmAbwb zL*0uUO{U&xlF3uRqQ)eqrh)0{ZAyENuCAcXDE>arF#9e?vOHFM4?Y%C3lanT>P4he zqGgHr27HG41|fE{rJRNC0-~d6K312wmjSl|?P<<dN0}^Z3L9gr0)f&R_IJAE#vpag z$a}nlI_V#cDvieI-nG^<l@+P-VXW?86{ho?BU61IY}TkvV54yiTE{}C#2bUpVzGun z6I-oya!X+%KwKNhv#rp0Bf_Zm%ui)xR_-!}b2~lXmx^{(sL6I(`rB`-QUB+<z7du1 zve*)#@_e(D5BZk=LpRm|-PAqA=25)zZEVuH?=oa!I(55D;UU4rwr%YI9WJBoD2A*^ zhID3-iK8%i1@sQY@}m%c?}kwv^HmIZtRDrx!G>!&IU3M<PwibS`|12<6mQ2`_Lm)w z^?F>^?J|?+m}K5<oM%|oR4#VO^+_oYL=|eLQ*d~)hY_LifpepVJjG!ZHint3jM@}B z5<^I=iTNhEH8C48GnBc-7+4=R8iM|*>h7uLej)8z4l?^U{{`(`fBtSpqnl)A`t|0} z0hs8;s9g!Ex}rf$v8juwuh8{Xn3;(j#2CU_d^wugeVF+}K=gobfh3^bMX$UH(wWB0 zA4jGSBHgLfT*satmxW2-u(3!T9vwZ5YV?FZKHOkBlF<Q*gLm^4N5i0fu%;J%_@ z(&p?dN*=8$nyTfrR*ak@!)`{?N3|n5506as7&MNPYZYe(HFkeo{J{qn2Q@Az-7Myd zl*XVQtd*E~m|0L*Tp4oe?7FV4*S|T@&42Z^FS&ZBr~8u8t_8^CKmAcwzURkIwEy9E zepn_api{oy%+Fzt976WZA)VOwoFYp)NkB|MS6ATRL742vsNJ<Cq&JB<bae`7bi|O6 z5JO3d9Mhjcrk@jY;Cp?fHyxF5ND;k~0F~mftVNwid>uPbxpUB*2D=}xfpb;GVrN8Y zY|KMf#Yzk>3EMWG!nh`^<+8G*H$TV5)D)EqJTa_T9<{VCAgU>4E9R^ppPOX8a-1Dh z_@I>5veeX^9n_I~BybeQP^Vn>VdZ#ad5y<Tud}=~R6lBeV|~58`G5G9>(Bi7n{V9d z>AqyNYXJh_{`ilPsi_m7KTK5<o_ON*;p7R-^dxd%9&>0OIlMpVBFUoVM}%`{$rkpb zgEeSs%&}{ukHg1MBQY8DrZH19n5o$WiYb^rh@QI}Z3nUE;kcAg;y9`S!KWe;Ci+CQ zhU!ryG|8eCb&FHgh{K&8ZE_A8iK(>X!f|19(MOBbz<H+fjF-*N^Vnd>75yHob<62t z&El{oQ`rocpDJ>$Uzq0pbL$LR$8?_a*xHBy^s+5*v`#s>I^^7P#nRG{rL~H-@tOyI zBR6vM%v9&hfBE>XDPZ!Hw5tKK@c5l9UiN&zpFsHGv|wMa8|&aJ{KhgmT*Y2?80i&| zXQ(L&Ys-YeI=ZojEF8wn9sqGjK8bWXP*_Afl4odUfq)LqVGdr09J~=dduPl=&kHuQ z2i2tD#c{8Y9CQS31L{-BkqP6__tVvg*}TB1(o5kdK_eh^42~26zu_F+%<_VnS#mKf z)-_#YnJWvXvz#k>J&Y<%6GKbXD@P`~3~I-6)#9A8IIQR-aEQWM<#^=m24~I<al@A3 zs3oYX5&4ZEdNXilrVc#(hd;d2(|k#3R|5pbAh`k16F{6z4X@X>MXC{cc9BqKn0<3G z2CFQAmT>wBq~F0_^*qeP1e8V0A(0UEhjxRld=3|t^wA1t>L9Z326XxJvGI<L0AdV| zamuYNpk0S%3EX=0w2N*AzpuNN<+h<`qBUt;xg;o?;*tWR;3=%-y2&YK^PESABL>b> z<T0>NQF1Fx*_>|If-0?xfr$Mqqi#J9Z*0)ZEgdm97Z^02v#TS{u8r`mqir2N1O<Kr zBR?iV&z4ShJvd+&Td}s&!sDMK#burl;fE3a1Hs3%+0l@2?kqYSpsfSd=({kIbdM{L zg~LdHI@Qcd6&6$F_Lxf&f5&Jg=6@7@n7ukyaUWMQ*`6GwK9(WU0L_`WPh_+B8^lA; z)v<DVZe$zhNdJ-^k{iSEi9Wr|(uTlt+n_{m`Z&32+~_k;iBPlMf}=Biu9=@=BF`v| zVYX8MQ2W4o)pB}mgljyZ@wixw>o-K?$AGi#uwn5({oqbd>sN)I6#xNPc>J!hsWggP z!tbh2AlX<aoI8WoRqTM<wozwTJ%@kzE_CH=^crWyX8BP`ou$OsST)gDv|dGB6a6E$ zfZl$v9bnvZFjZ9yE{RwUX;(MfA&k*6?nk)VHUu=L*J{c^+?K*#TFZg5Bo~8A@mQ;E z8v_(oG21)frk2q~YWR72qvErtmsuXw(Glrl<&#n4Ikh@sRJDZ0MR17xx*3DRd%_?5 z=uS`PSB-YPh_#*eKXDJ|jywkpClKBVK<nQ?U85^YXum|t1JFwJjGF@1m+|#FI=g_` zcPMtjWn9)Y01gp@+7jB)mhLfbrjzxeV{#nMGA=nP212T*mD;l*W;37MB2*!!(Tc=O zK_3Dkp{So_o7~2!VpU^;l=GA(f-Os3agD~15-Wzas^y8bA&cuHoKrf+r1opEEYR8Y ziq%0)Xg%VCg8sS@ZuaQecEiJmerTts@vBPDDu4jYKk<3a9lHkfe30m<BAh!N^Fs1j zv=t<e;U!uP2}>u@dId9g05dh4dJ~3}$DUy_i>gEG4aCmGRA2Q-Xk$S-vET=(pmt&d z0xBjIeXt02(_%H=fYe%G%e>pmG6GP!m_=w(?)jRF-ETYA;*<3&7lR>|AU!o4acXVI zpmx-4pg=aOF4jJ<GHjyW(YPq1DZg$+ZuY8YgAarcJv+pz?ewez2*CU!_i^s(;{Ycx z+c?Q!NLW0TKw_Y^kP9?b<O0T{=UT$X8R$=7X7)urB<sW+57o#)bduC2Hgb%573oZ+ zI=xZUlF*{2AA6;z_RvTy?>X*=UK&HkSe%Baod}f1;#FB~Tdc&+yyM~wL+4^qYca7- zRsz_`LB*hMX&TS4iQakV0%exP+;ta2Pfo9nXlh4rK@Ixr7;YwTc2rr~-~PUxp0*vK zXC*)Y<{!A9rK^uamN<zj+W0rHwn8}9#gsYZf@EdXN9tkpUszB-gpJd2;ZM$BCZ?mA zHfRhx85d;;A+~#S>p%;=`Ka|NQM}4}ko8k+m<0@mE$-1X869g=o_Xw%?om!xF&=9o z%P|tUi__>2$&H~DLl9-DZdj=rPH$9n#UiP;Z{2!wjYZ~SVF%Bt)gc?hn$UVx)n7-r z88~ZdPy5?%+v(}rF?!Yl1Yqug`&qg!fyD5`sH)cPjbZ5wGSNkfg_y%(ELsnuNR?^q zg6l`<T0n<q!5=}U7Ggyd0^%KN1W}K;QOsbhN065(nwQ8~Y$rDL3@Mm$Q?!Z<X)4bt zj7^z@(VnvIT9%qR9_OPHE<h7BRd*Mf5I8-ocw~Ku3Bp?I@S;di2F}wuPbalSsGVnV zP_w*Vp%;KdhK@78_We6OO*=}@YJdRD-E%)n&%Xv#Pa?dN0F3H4hO;M;eg_JJ<Te(J zt6gmFX*}u%1mln*L)XthyN*oGL2nX56Z@fucFXq>Dj1!P#j(w}kxvf!EHp!65=qoS zEP@roR9=vYP$`5|!Q^yRQHQW8YSliZB&isZvedSms~U!_XQTDBJ~{`*Dki|Nja_}M z2(=5W)-9)2hxo>+s{Wb^w+Ni2^_+R%vl|?+2M_?}?!1qs7hDZFCnbSIH#XiF&Yr?{ ziYN-%9GdmmV_h8T2B0-!CeXHu9dmUAZ6Ayx6AMVPGwHZsT)ha*6xan~Lqr<^(w<FK zPRyowRhS46ktmfpRhHX^rKTa<)~rWSM&5I(X?U!v*>H~5r81+zvsSl+Hc*6U%^Eq+ zOr8-`8MclSD+8)gqek`D1a1M&%7(D`%WvQ5X?aG_9zYV!efj|wUwSn*<0OW65)@rI z_r@^M!<Gk;qD0+#EV`f#G#jW|v|A$02$ekgGr|aMma*X)==5XzfYhQugGYxakaA|L zu}(G#HWqsTiy?xfYiu&*Mt?@@JtsyZoNCP6mFO?2Lf}j@;-29!dfB~Fxu{pvK2SFf zbxN%gH5`GiwbVXvdc9(0P%Emx26_u{wyiybUw-!L92e3aK(^7sCmvw=<&l$Mc&9p# zE}nd2nCM_;W|EHKlDJjHP<hv&jUtkxK~Nn;v8&TVT|ZSyDH2Ew>eiuMjtzIjM2BHE z0Xj%}NGwd<&oT;QX@bXyu#KE&P!meHhC?$*Q4pmFQlb=52ptJcF^W=E2uL$j=>$!X zPC!J1C`}HEAc07cARvN4dX97u=`~;?D4|271W3D_U-#FYxidSnzuwt*cE0&$cW0k> zMn|YQg>_lP^;UMw)iYVAHX@rUY4?^ag|t4B?hefwq&39rS6{Wyfn0khJ)Y-$#&7f2 z#Ea#A`IM>U^nbyuLt>~+`}r>5Ay<FgDpxifsY|^1Bs{=P^haLAdsO3%duIGY;i8^{ zg@m>bmb^(C@iK#%8m`cyB&GJ2XFk1xno`bcAXb}vGjF<7=qXZIdRdda_Tvf3j>gf^ zo~y^Cf8GWbkEyi03vzLAwIoG2<PB?PQ~nvhwiU{+0DJN*Ra3}iVEzJ6g)=7*7gZxF z-v8;>w(O;RXfz;F;4KSrJak>Us?#d;#GiK!)g@8uzjJ>ND&W@TU1sq*1v>yYxVaW^ z)@WI!twZvPr`L+N%q>*<&x=n^S4b4Tx$-G}pnxp{8v33q)#ja_mI4Z`tn{M!!yNLw zB)K(n9YTp^1dmBb?atsO)#LU3?|UYlPs$b7kBU%fUlJ-E`x&>I`8<Y98vdQ48#fyL zhN~VT{r-OJw&S1vlHtNq$~3n5w_cFeQ1mOS;`N*J;zuj5nfDA7%9n7>4`$$!W_gcZ ziN76W6e3Xo;b}gbPJfT_F)XMin=6lfJM|=E>yQ(FPjPyn7WlJTJnQy1*E8;A{+ktT zwa3C_$*Nqohrn?l|ILeiFFhjfuGGI=jt%CoyX#Rs{lSUM(=~EfO)Y?aCKY)1U?bH_ zY3JO|=Ajv<pt_h>n09+-xxs&<w*7y%^Pd2Y%6<o^1?h;9;-3m-XpN5t{GBBFZcCyT zL`V2YK&;FYz4x_JU+8&~!cP9u<IP@@GUfl)oX84$338a{u_k4Hk!>=!lW^TIdReuE zvIx4b$^CMx;p^>Y#At)di|)C_4DCJoOw&jk^F@iTwa$j41ri-<>Tx!{-)6b9*D&ns z?naj|@>{RK^k`70L-6%g)#(US?Nf(xzZ%&s^BKr=W!Jg$`td_&2lHM=VcwZ(+!a;& zoM-i<xMA+MU$@4eVg&Di#ir=FpVh0*Ps4aBu0AGP6%A);&pDgxV)%+rq27o5tkc7X zR6B?R`vRjWdnf$T5@U|)&>3l=yB3Aag+LpB!zkr?`}F4VZR$d|=48C&@$p1qjBq+% z`s73RyoV_W5mRU^kfvBGKD%c$JoRi+UE^|mNVX;_6{t6UH(U+IZ+}`cpx{nNp=5!O z#jz}>>)FrsGR=se<tG8lV+>*@{TENz3g=P&R7VGztq`%@Y-s!uE_Rqb0nXSxguM4D z%_#E0Upp;L4$tl;ZT|ivc4Ve6xbumGS+wx<s(Cqg1&*3`x|X|4G(bbwQ+NE=n204? zNX`rhOkY=sX>1R$({wr_D<vEebICo+=y$#3ee3IRBIaMuJMNwrg`G<VzD+g|^L{|r zwR_ioB-y@mlZ##di43HhIy4T=A=nefp)pebxk=o`J{|h*^PwBCJu=#6TPMe$FnU`L z!lNP1kQY^zVij=cOr?45S-&wyRp^B*0Y4l&BaE2cv&xVhHWngSg8xM3ofhtCAG~Q6 z$}fNY&5Z(;7lk60A*<ItzR%ZBHVS%~z$BxiyQZTTdjI}zWsbVNca)u$GgN;5sj9c0 zKF<j#eMSp>{>TTNUM2pHk*YpoO5*^6XKK0hFCW+$gm}qKyYj3F7DQGL8o~WjujmSX zao1fad~KH=o>8{qG$xDvQ0dk2+URRbZ+G;#Yag-8D9tsf`-RhwcB8!HdToiV9;?+- zZ7<n3pQh$MBZCJ@Y;H*kh{i2Goi{YLf{z^TL5kJ-09(rJs?IlpNPze52anB3Ast7R z{;k4D&EclYFAQz&t&aR~wb|fL{OfFD0QnIsooY9hUQ7sC7Wr{EgMrix_x3dL>4?!2 z+v-L#UR?+e`55SNQI89x|Fku19vL3R|LD|VpE&V~F=>0Y786nM@)X59O@5?HsQ~hL zbRtj*jH}_4{A#zXWVHP&_-(B`G5Uw@PVki-g4uWDBu(WXdj0Mu;O=(lnR7{%%+-{T z=_T`-u|wxoTKl$a6%6RanddhIBZ2-Vb{G3wBFU}NO=jq=-@|h+?X<1eVpj!KGM!&Z zvX?Bw9drNqLe|bN!F@i1_f4_21LU2F0;6g9g`;OfBf}pTBAL04O;M$Y@U&gO|KC`g zGRMu|7ei;f>aJ1t{9}2h%F;lIwDIeX8#QM)@AZMRt+~`EM2snEgU>G>=IOkSg6w|t znnrzx%lN<zM}c<)N&QrUznweJT{+uz#kclc_Y;I+!RkZ0N(slP18aK*JfDsUsnd|$ zsFB!<aJ*oQLT~&EhKJouw2VgsnGYHYzv#$2ib{0kCQJZ0qI>QK?3#FTaxPiH8K4eK z<uT>H$jy1e|Kci9A3%+#b03G}q9cSi1&NFV?4RKm90dv>RBV$)Y$lqXee5M0VM62E zClRl{J<W3UUKZ)v?I3c5W+IQ~ZwZckHAum;pI&e9A+-qBaSksE+9bMZt{(~6>I{>H zHM_v5b!+Hqya^63XE(N@-m&*#84epuz_Xv@7hdlfkh2z;t@wcYV2%fjbrsfp6$Xz? z&PSTT&~TWe{f@MH2hyZtE5EM9+KlxzkT2c>uNeJn7u98yyZG4G=H+F*E>VgerH$B! zK{Q)xUY6s%C&=Wbsl}CH#pY%Z=pSN41UDGKR=EAUak7@HiDz#k!L;$@z`lK23(GXx z2dxeYj!w?AXw0|#_$I8{pErg#A_j;Rquj<-9)Kv*mZ*AQys7O(eHO}KQynC)|9~dZ z3yf4HsDj|*Z18JB>sZU+eoN+x8#LriFZeHj(vrp4^Q1>>cI|*ne1meNZ?;axzNlh! z<Zq>%(Jm=9T=+(1oapkE-P5F8CAOaCES%^1>m#(sJM>Kv`e5Gb;5(*wb&uIcj8`kq zv1Ujl_mEz5X}8yHG`H(}ACbAq;QNa0eh6;r@~2o3ZgE6DHLWq6a0yr9Ro#$}KRmH1 ziKWy2NmiZD;U`t<+n%gi=Vi;`_zAIx-|kBJD9bu}bbP>#R@3zST-0i;zftEzF0gmI ziN}4Zd^3+aMDT5X+O5Em&IpwC=xc}+pTsWQ*X$=h66r6F?i*<`IWkJhV%bDF++3Nv zNc!4M*pjiPekS@;lGlzT26tH(d39yiD-RoVAqm*8k1Xe+gf6q}K@J&54$26ET)H=F z7nq=-gD{P?I-&<<&3}_^F^w%{Z2;I8HrI&yr3nI1-qu_{-DnsDo*T!JrTjfC$j2z= z1KhQ>1Jukebj<)4woZ>nXQjqQp62I@%k&d{yKgaW0U1DI#)*z1xr0opO(n_?o!g|Y zrHSF1X8GU^+j+nygaR+>aRsuqHWBe{OZAG@zv}>KUS>rwU5j%qf2)+uJy5^jMC#-i z5M73A*q0tWa04<`H!X=55~#(|d&ZR95rj4c4fy$Xya)2&7aIZ1Ks9}6^yf6jlWnQ@ zN%U7lPG;vv8#v2t?rop?Edrc}89N7YQEstfgDHl|;J18p&f4u^aXP__P6-|S!Ag%a z4Gi1K&A%UwhAeEPA?#s|_yb>jlo>27n3RTR`$5=1^+`_M2pe63p9mpwX7Q)(cV_WX z9RwEm&R7_X)kC{lj)#rW@yq_OW}`Fu<@oTjWkgv(^%~u=Y*`A%Lc&;ju(e>uI>Uaf zd>PKra_lXKg%#oH<;xT0%ShNxG5)}`e7W`JICjkeTZw0v;nnfWB$%$_7-CBA*a-gM zJdCwSAUDf9g|dJ0Ib=I32~M{*ySugyB3q1&sp06Hf&CI4<+}1^{qp4t!}Xkee9KkX z@!-bJEPR|Z40~&=HGg4Icu5NF0J{c5Uxp#<#sbwNO2W_ek#{6kIbU>sfJu*Rlkn^& zeB5tt!8v*fEozn!TXkm)IK1D*P#W2qslWqAS|mzzcU?$T9DC3>3wq(Z-4J}1jXlQ+ z*$tj;K}!$sb2FqtBspy0<rtC^B_o*O2I0J1YOJ0`C#ht<tXjj&bt4mwiaJ?LNT<Xz z;s<!zg6qCy{*-|yXP$4bn=powfkfqG#6F$d^f;48w5;x>o&N0A+)9=#g=eX*lrVdI zX=a|P2RcD8w!J|mnN-3T4Z3W|)R?3JgB!>{$$`Jql08XDfeJ;wO@?Qp#rNu<<f$DZ z=U7bE2W;>}p@|SvXfr&%u2fSx#W8o_Z7S;s1(<+t4xZ_n*B53AQ%uF`skQ(+XsTh$ zPkRVCYUWGihS#*rN+~zy@7UH0vbmNNB9|ZYxyU3*%PGXLv-IDKn1>NIC!LO!a^NZ^ z7N?1Jw(l3wisH?*wEn}FNFo{#qV(K_1E21!6)?(I`tu43Tzm8Ln|QY&(K0!ovDUjO zahA)*qyw4C+4{#>{mg@EM^d;+hs@F2Rgj!~+F59dH1v@AA38YI*=pUTCMv-QOpfX^ zhEw+MnV(F85cLnM>MyE7ac<hg=H!kIz>>7Sb8gdZDaVG!w|M@a?b4cuu0j_^`RtAR zzicn<!Z$g>xQ}}voK?W&9Y1V<d%2Fv>lXy;U9AU?@3o6x?zA`<nQaR==r6~B6EnO0 zTV!fR-jusNr)GhNxECS<HrPH(HBoQLI_-5VlYCkEr<bTA#m`8DSo(R~v6I7+t7n&y zx>RXp?wb$yJyAWq>~k7!C%_1tEKQ9S;K|=ES4@?nB}t4rw$=DgejbI{h7a-~_7b7j zqMm-RLTCFa3dH!-zLK3$%b+lQjdIu*v2*%Tw@=@t##Aqv2|zVZ5<I!EsYjrLe}ekE zUI$q?jHS$}*sDjl6bVYtkv8uP>2sM{#{@D>hMvQUg3agP1Sgwau?pXmm?c>X=jfgS zE@Jm6Xt2@9;Kp`f4m)0@<cv@5hu57!*?!3kjZKQgrDrG93KCQ*-4=K1pf<XXglEia z@AL-&p$E|kA8L&DK|KL}!*ajAfw2M6crVQA$`w#x-p@xfu%JQ*?1GWR`b25NGZZ?L z@F`eyp}nsC&9Rz4MOzYi;ml}GN9BIf;$w1-;QCR}WpdtbtKi;uz5(~nE_i4~+Y*wo zCF7MM$SD1)XG^>Idd{HhWd3@ct-h^<48xy7#VC=yBw{!LxXR7r``oVjLmQ&-=j0pC zdlDZT1Bc1J2`-1d%@Mxo$~VO8R^;^z8Zt8V=-uo4(9&_qxHK_x4EXHlY6d2Sd4S3= zI-{MD>ZI&!$yOW^*fxez@4m)-H&vYeOBX%0XpRw>L_r1=Jna`*(9)@)+E(+d*$u_% z(kf+D2X!~P2~KO*7n-y-zZ9n&4_BC;g8#GL&!aDLilfS6-@}3Eu5+aOOe-*`w}n(; zDlwc<>t>D8t0>y4IsreSFWOPmdOgOgVOwZIM1nJ=BW}0E?gW<}2avNnH)ULYR39%| zX;z)}eb${LXN%m*)R9F(TiZsAsTtrcgDR^nJz4B?KOM;uhOBESGo$v%_;9s;@qSC1 zS~?#N>zlJkI$)t%`i5s(g}<@{XGR{K*?5MDE08<)aMq)NoK&mcIl<i7-dBUp6IuR3 zX~hg_LqWMCafyrCk=Nc%B;xR|OL;mGZPiW{7N+93aDDW1uK=9m-DJczRrFfo2z0<N zCj=t$r{j-KMkHh)f-|-4&gYWw%B#HRoJ)2dEo`9pD1^UB<K0TNj+7Lw-#vanlq?_? zRE@%l_iGqWBuyBd1=&i|-{VJuB0K6*KIpAUmuULeSudWu{X`NuO54j9&8XhLgF&=X zOEhENNS@MogbFF5_F%vTG~;NT%0y%LfC0ACP5qK@1fCosEz!6(DR9X!t`jx0H;~va z;hnwkH)A`paO%J@VS<OO0>XZ!$QaN%v~w!!vO6mx1&qB+U2txFdbS68c1Y8TQlpAr z&%hSfe}%jUdzbA$>|F8ek~Mynztxu<ejwoWUjFe`EHNBFmIu|vo~Q0vtgkJl_T&fu zT1MYKu;^T|-0#;8mTj3~>_1H9fQTnS$}*o7;~aug+8W<XFg&|flNvXMv|qNQO5SD! z5MGF;iFS-vLDsmu)AIdmFOSwMVGerE-9GNbO3B;4Q0yxXC%^YKR`lWcVg$BXP(>z| zr`%BCDVu|osh%l3JT4!GO)8?U-9>qOpnM=+4}A_NfR={lB{dB#H7!khO<jnt9z;j` zf`%qUL&K;d@xKtv{}luyy#4P-{@()YO+&(=K<d90>=5@+;T{it09U*bo<8z7gFSqG a?)Z3kM?UEB`7Z?%aPyiCw9?G|@&5pEUU7&3 literal 0 HcmV?d00001 diff --git a/src/ui/banner/banner.component.ts b/src/ui/banner/banner.component.ts index 78ac4ccfc..70ae80f8f 100644 --- a/src/ui/banner/banner.component.ts +++ b/src/ui/banner/banner.component.ts @@ -1,8 +1,8 @@ import { Component, OnDestroy } from "@angular/core"; import { Store, select } from "@ngrx/store"; -import { ViewerStateInterface, safeFilter, SELECT_PARCELLATION, extractLabelIdx, SELECT_REGIONS, NEWVIEWER, getLabelIndexMap, isDefined } from "../../services/stateStore.service"; -import { Observable, Subscription, merge } from "rxjs"; -import { map, filter } from "rxjs/operators"; +import { ViewerStateInterface, safeFilter, SELECT_PARCELLATION, extractLabelIdx, SELECT_REGIONS, NEWVIEWER, getLabelIndexMap, isDefined, CHANGE_NAVIGATION } from "../../services/stateStore.service"; +import { Observable, Subscription, merge, Subject } from "rxjs"; +import { map, filter, debounceTime, buffer } from "rxjs/operators"; import { FilterNameBySearch } from "../../util/pipes/filterNameBySearch.pipe"; @Component({ @@ -25,6 +25,7 @@ export class AtlasBanner implements OnDestroy{ public selectedTemplate : any public selectedParcellation : any public selectedRegions : any[] = [] + private navigation : {position : [number,number,number]} = {position : [0,0,0]} private subscriptions : Subscription[] = [] @@ -73,8 +74,28 @@ export class AtlasBanner implements OnDestroy{ this.selectedRegions = ev }) ) + + this.subscriptions.push( + this.handleRegionTreeClickSubject.pipe( + buffer( + this.handleRegionTreeClickSubject.pipe( + debounceTime(200) + ) + ) + ).subscribe(arr=>arr.length > 1 ? this.doubleClick(arr[0]) : this.singleClick(arr[0])) + ) + + + this.subscriptions.push( + this.store.pipe( + select('viewerState'), + safeFilter('navigation'), + map(obj=>obj.navigation) + ).subscribe((navigation:any)=>this.navigation = navigation) + ) } + ngOnDestroy(){ this.subscriptions.forEach(s=>s.unsubscribe()) } @@ -104,7 +125,25 @@ export class AtlasBanner implements OnDestroy{ }) } - handleClickRegion(obj:any){ + /* double click navigate to the interested area */ + private doubleClick(obj:any){ + if( !(obj && obj.inputItem && obj.inputItem.position) ){ + return + } + + this.store.dispatch({ + type : CHANGE_NAVIGATION, + navigation : { + position : obj.inputItem.position, + animation : { + /* empty object is enough to be truthy */ + } + }, + }) + } + + /* single click selects/deselects region(s) */ + private singleClick(obj:any){ const region = obj.inputItem const selectedSet = new Set(extractLabelIdx(region)) const intersection = new Set([...this.selectedRegions.map(r=>r.labelIndex)].filter(v=>selectedSet.has(v))) @@ -116,6 +155,12 @@ export class AtlasBanner implements OnDestroy{ }) } + private handleRegionTreeClickSubject : Subject<any> = new Subject() + + handleClickRegion(obj:any){ + this.handleRegionTreeClickSubject.next(obj) + } + displayActiveTemplate(template:any){ return `<small>Template</small> <small class = "mute-text">${template ? '(' + template.name + ')' : ''}</small> <span class = "caret"></span>` } @@ -124,12 +169,18 @@ export class AtlasBanner implements OnDestroy{ return `<small>Parcellation</small> <small class = "mute-text">${parcellation ? '(' + parcellation.name + ')' : ''}</small> <span class = "caret"></span>` } + private insertHighlight(name:string,searchTerm:string):string{ + const regex = new RegExp(searchTerm,'gi') + return name.replace(regex,(s)=>`<span class = "highlight">${s}</span>`) + } + displayTreeNode(item:any){ return typeof item.labelIndex !== 'undefined' && this.selectedRegions.findIndex(re=>re.labelIndex === Number(item.labelIndex)) >= 0 ? - `<span class = "regionSelected">${item.name}</span>` : - `<span class = "regionNotSelected">${item.name}</span>` + `<span class = "regionSelected">${this.insertHighlight(item.name,this.searchTerm)}</span>` : + `<span class = "regionNotSelected">${this.insertHighlight(item.name,this.searchTerm)}</span>` } + /* TODO obsolete? */ get treeHeaderText():string{ return '' } diff --git a/src/ui/databrowser/databrowser.component.ts b/src/ui/databrowser/databrowser.component.ts index 85eb07f47..96f6e9d06 100644 --- a/src/ui/databrowser/databrowser.component.ts +++ b/src/ui/databrowser/databrowser.component.ts @@ -323,7 +323,9 @@ export class DataBrowserUI implements OnDestroy,OnInit{ type : CHANGE_NAVIGATION, navigation : { position : position, - positionReal : true + animation : { + + } } }) } diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts index 5f25d436a..3eb347cb3 100644 --- a/src/ui/nehubaContainer/nehubaContainer.component.ts +++ b/src/ui/nehubaContainer/nehubaContainer.component.ts @@ -6,6 +6,7 @@ import { Observable, Subscription, fromEvent, combineLatest, merge } from "rxjs" import { filter,map, take, scan, debounceTime, distinctUntilChanged } from "rxjs/operators"; import * as export_nehuba from 'export_nehuba' import { AtlasViewerAPIServices } from "../../atlasViewer/atlasViewer.apiService.service"; +import { timedValues } from "../../util/generator"; @Component({ selector : 'ui-nehuba-container', @@ -36,7 +37,6 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{ public onHoverSegment$ : Observable<any> private navigationChanges$ : Observable<any> - private redrawObservable$ : Observable<any> public spatialResultsVisible$ : Observable<number> private selectedTemplate : any | null @@ -44,7 +44,7 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{ public fetchedSpatialData : DataEntry[] = [] private cr : ComponentRef<NehubaViewerUnit> - private nehubaViewer : NehubaViewerUnit + public nehubaViewer : NehubaViewerUnit private regionsLabelIndexMap : Map<number,any> = new Map() private subscriptions : Subscription[] = [] @@ -71,7 +71,9 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{ this.loadedParcellation$ = this.store.pipe( select('viewerState'), safeFilter('parcellationSelected'), - map(state=>state.parcellationSelected)) + map(state=>state.parcellationSelected), + distinctUntilChanged() + ) this.selectedRegions$ = this.store.pipe( select('viewerState'), @@ -92,11 +94,6 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{ map(state=>state.fetchedSpatialData) ) - this.redrawObservable$ = this.store.pipe( - select('uiState'), - safeFilter('sidePanelOpen') - ) - this.navigationChanges$ = this.store.pipe( select('viewerState'), safeFilter('navigation'), @@ -205,13 +202,6 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{ this.dedicatedView$.subscribe((this.handleDedicatedView).bind(this)) ) - this.subscriptions.push( - this.redrawObservable$.subscribe(()=>{ - if(this.nehubaViewer) - this.nehubaViewer.nehubaViewer.redraw() - }) - ) - /* setup init view state */ combineLatest( this.navigationChanges$, @@ -495,11 +485,63 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{ } handleDispatchedNavigationChange(navigation){ - /* set this.oldnavigation to represent the state of the store */ - this.oldNavigation = Object.assign({},this.oldNavigation,navigation) - this.nehubaViewer.setNavigationState(Object.assign({},this.oldNavigation,{ - positionReal : true - })) + + /* extract the animation object */ + const { animation, ..._navigation } = navigation + + if( animation ){ + /* animated */ + + const gen = timedValues() + const dest = Object.assign({},_navigation) + /* this.oldNavigation is old */ + const delta = Object.assign({}, ...Object.keys(dest).filter(key=>key !== 'positionReal').map(key=>{ + const returnObj = {} + returnObj[key] = typeof dest[key] === 'number' ? + dest[key] - this.oldNavigation[key] : + typeof dest[key] === 'object' ? + dest[key].map((val,idx)=>val - this.oldNavigation[key][idx]) : + true + return returnObj + })) + + const animate = ()=>{ + const next = gen.next() + const d = next.value + + this.nehubaViewer.setNavigationState( + Object.assign({}, ...Object.keys(dest).filter(k=>k !== 'positionReal').map(key=>{ + const returnObj = {} + returnObj[key] = typeof dest[key] === 'number' ? + dest[key] - ( delta[key] * ( 1 - d ) ) : + dest[key].map((val,idx)=>val - ( delta[key][idx] * ( 1 - d ) ) ) + return returnObj + }),{ + positionReal : true + }) + ) + + if( !next.done ){ + requestAnimationFrame(()=>animate()) + }else{ + + /* set this.oldnavigation to represent the state of the store */ + /* animation done, set this.oldNavigation */ + this.oldNavigation = Object.assign({},dest) + } + } + requestAnimationFrame(()=>animate()) + } else { + /* not animated */ + + /* set this.oldnavigation to represent the state of the store */ + /* since the emitted change of navigation state is debounced, we can safely set this.oldNavigation to the destination */ + this.oldNavigation = Object.assign({},this.oldNavigation,_navigation) + + this.nehubaViewer.setNavigationState(Object.assign({},_navigation,{ + positionReal : true + })) + } } /* related to info-card */ @@ -558,6 +600,51 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{ Array.from(this.nehubaViewer.navPosVoxel.map(n=> isNaN(n) ? 0 : n)).join(' , ') : `[0,0,0] (neubaViewer is undefined)` } + + get showCitation(){ + return this.selectedTemplate && this.selectedTemplate.properties && this.selectedTemplate.properties.publications && this.selectedTemplate.properties.publications.constructor === Array + } + + resetNavigation(){ + const initialNgState = this.selectedTemplate.nehubaConfig.dataset.initialNgState + + const perspectiveZoom = initialNgState ? initialNgState.perspectiveZoom : undefined + const perspectiveOrientation = initialNgState ? initialNgState.perspectiveOrientation : undefined + const zoom = initialNgState ? + initialNgState.navigation ? + initialNgState.navigation.zoomFactor : + undefined : + undefined + + const position = initialNgState ? + initialNgState.navigation ? + initialNgState.navigation.pose ? + initialNgState.navigation.pose.position.voxelCoordinates ? + initialNgState.navigation.pose.position.voxelCoordinates : + undefined : + undefined : + undefined : + undefined + + const orientation = [0,0,0,1] + + this.store.dispatch({ + type : CHANGE_NAVIGATION, + navigation : Object.assign({}, + { + perspectiveZoom, + perspectiveOrientation, + zoom, + position, + orientation + },{ + positionReal : false, + animation : { + + } + }) + }) + } } export const CM_THRESHOLD = `0.05` diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css index 1adeb5bcc..7062e973d 100644 --- a/src/ui/nehubaContainer/nehubaContainer.style.css +++ b/src/ui/nehubaContainer/nehubaContainer.style.css @@ -36,7 +36,6 @@ div[statusCard] left:1em; bottom:1em; width : 20em; - height:7em; pointer-events: all; } @@ -89,4 +88,29 @@ div[landmarkMasterContainer] > div > [landmarkContainer] > div small[onHoverSegment] { margin-left:2em; +} + +div[citationContainer] +{ + max-width:300px; +} + +div[citationContainer] > a +{ + font-size: 90%; + text-align: left; + display:block; + white-space: normal; + padding-bottom:0px; +} + + +div[citationContainer] > a:not(:first-child) +{ + padding-top:0px; +} + +hr +{ + margin : 0.2em 2em; } \ No newline at end of file diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html index 5e810777c..89b48be2b 100644 --- a/src/ui/nehubaContainer/nehubaContainer.template.html +++ b/src/ui/nehubaContainer/nehubaContainer.template.html @@ -66,15 +66,35 @@ *ngIf = "viewerLoaded"> <div statusCard> + <!-- perhaps use a dedicated publication card next time (?) --> + <div + *ngIf = "showCitation" + citationContainer> + <a + *ngFor = "let publication of selectedTemplate.properties.publications" + href = "{{ publication.doi }}" + class = "btn btn-link"> + {{ publication.citation }} + </a> + </div> + + <hr /> + <span class = "btn btn-link" (click)="statusPanelRealSpace = !statusPanelRealSpace"> {{statusPanelRealSpace ? 'RealSpace' : 'VoxelSpace'}} </span> + + <span + (click) = "resetNavigation()" + class = "btn btn-link"> + reset navigation + </span> <br /> <div textContainer> - Navigation: + <small>Navigation: </small> <input (keydown.enter) = "textNavigateTo(navigateInput.value)" (keydown.tab) = "textNavigateTo(navigateInput.value)" @@ -84,13 +104,13 @@ navigateInput/> <br /> - Mouse: + <small>Mouse: </small> <small> {{ mouseCoord }} </small> <br /> <small onHoverSegment> - {{ onHoverSegment$ | async }} + {{ onHoverSegmentName$ | async }} </small> </div> </div> diff --git a/src/util/generator.ts b/src/util/generator.ts new file mode 100644 index 000000000..6409a61fe --- /dev/null +++ b/src/util/generator.ts @@ -0,0 +1,15 @@ +export function* timedValues(sec:number = 500,mode:string = 'linear'){ + const startTime = Date.now() + + const getValue = (fraction) =>{ + switch (mode){ + case 'linear': + default: + return fraction < 1 ? fraction : 1 + } + } + while((Date.now() - startTime) < sec){ + yield getValue( (Date.now() - startTime) / sec ) + } + return 1 +} \ No newline at end of file diff --git a/webpack.common.js b/webpack.common.js index c8eb77d96..cd251dee6 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -1,5 +1,4 @@ const webpack = require('webpack') -const HtmlWebpackPlugin = require('html-webpack-plugin') const path = require('path') module.exports = { @@ -42,10 +41,7 @@ module.exports = { ] }, plugins : [ - new webpack.ContextReplacementPlugin(/@angular(\\|\/)core(\\|\/)/,path.join(__dirname,'src')), - new HtmlWebpackPlugin({ - template : 'src/index.html' - }) + new webpack.ContextReplacementPlugin(/@angular(\\|\/)core(\\|\/)/,path.join(__dirname,'src')) ], resolve : { extensions : [ diff --git a/webpack.dev.js b/webpack.dev.js index c2197f870..82f9a8dcb 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -2,6 +2,8 @@ const common = require('./webpack.common.js') const merge = require('webpack-merge') const path = require('path') const ngAssets = require('./webpack.ngassets') +const HtmlWebpackPlugin = require('html-webpack-plugin') + module.exports = merge(common,ngAssets,{ entry : './src/main.ts', @@ -10,5 +12,11 @@ module.exports = merge(common,ngAssets,{ filename : 'main.js', path : path.resolve(__dirname,'dist/dev') }, - devtool:'source-map' + devtool:'source-map', + + plugins : [ + new HtmlWebpackPlugin({ + template : 'src/index.html' + }) + ] }) \ No newline at end of file diff --git a/webpack.export.js b/webpack.export.js new file mode 100644 index 000000000..1bb833dc1 --- /dev/null +++ b/webpack.export.js @@ -0,0 +1,27 @@ +const merge = require('webpack-merge') +const common = require('./webpack.common.js') +const ngAssets = require('./webpack.ngassets') +const path = require('path') +const ClosureCompilerPlugin = require('webpack-closure-compiler') +const HtmlWebpackPlugin = require('html-webpack-plugin') + +module.exports = merge(common,ngAssets,{ + entry : './src/atlasViewerExports/main.export.ts', + mode : 'development', + output : { + filename : 'export.js', + path : path.resolve(__dirname,'dist/export') + }, + plugins : [ + // new ClosureCompilerPlugin({ + // compiler : { + // compilation_level : 'SIMPLE' + // }, + // concurrency : 4 + // }), + + new HtmlWebpackPlugin({ + template : './src/atlasViewerExports/export.html' + }) + ] +}) \ No newline at end of file diff --git a/webpack.prod.js b/webpack.prod.js index 87339cbca..485b3b908 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -4,6 +4,7 @@ const Uglify = require('uglifyjs-webpack-plugin') const path = require('path') const ClosureCompilerPlugin = require('webpack-closure-compiler') const ngAssets = require('./webpack.ngassets') +const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = merge(common,ngAssets,{ entry : './src/main.ts', @@ -17,6 +18,10 @@ module.exports = merge(common,ngAssets,{ compilation_level : 'SIMPLE' }, concurrency : 4 + }), + + new HtmlWebpackPlugin({ + template : 'src/index.html' }) ] }) \ No newline at end of file -- GitLab