Skip to content

Commit

Permalink
feat: added DbxWidgetViewComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
dereekb committed Aug 7, 2022
1 parent ed3430f commit 6cf8d3a
Show file tree
Hide file tree
Showing 16 changed files with 255 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div>
<p>Example Widget - Type: "{{ type }}"</p>
<p>
Icon:
<mat-icon>{{ icon }}</mat-icon>
</p>
<p>Data: {{ data | json }}</p>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { OnInit, Component } from '@angular/core';
import { AbstractDbxWidgetComponent } from '@dereekb/dbx-web';

export const DOC_EXTENSION_WIDGET_EXAMPLE_TYPE = 'widgetExample';

export interface DocExtensionWidgetExampleData {
icon: string;
data: object;
}

@Component({
templateUrl: './widget.example.component.html'
})
export class DocExtensionWidgetExampleComponent extends AbstractDbxWidgetComponent<DocExtensionWidgetExampleData> {
readonly type = DOC_EXTENSION_WIDGET_EXAMPLE_TYPE;

get icon() {
return this.data.icon;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<dbx-content-container>
<doc-feature-layout header="Widget Extension" hint="">
<!-- Examples -->
<doc-feature-example header="dbx-widget-view" hint="Used to render a pre-configured widget based on the input data.">
<h2>Example with data</h2>
<p>The input config has a type that instructs which widget to inject into the view, and passes the data field directly.</p>
<p>Input Config: {{ examplePair | json }}</p>
<dbx-content-border>
<dbx-widget-view [config]="examplePair"></dbx-widget-view>
</dbx-content-border>
<h2>Example without data</h2>
<p>When not given content, the widget produces no visual output.</p>
<dbx-content-border>
<dbx-widget-view></dbx-widget-view>
</dbx-content-border>
</doc-feature-example>
</doc-feature-layout>
</dbx-content-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { OnInit, Component } from '@angular/core';
import { DbxWidgetDataPair } from '@dereekb/dbx-web';
import { DocExtensionWidgetExampleData, DOC_EXTENSION_WIDGET_EXAMPLE_TYPE } from '../component/widget.example.component';

@Component({
templateUrl: './widget.component.html'
})
export class DocExtensionWidgetComponent implements OnInit {
readonly examplePair: DbxWidgetDataPair = {
type: DOC_EXTENSION_WIDGET_EXAMPLE_TYPE,
data: {
icon: 'code',
data: {
test: true
}
} as DocExtensionWidgetExampleData
};

ngOnInit(): void {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,34 @@ import { DocSharedModule } from '../shared/doc.shared.module';
import { DocExtensionLayoutComponent } from './container/layout.component';
import { DocExtensionCalendarComponent } from './container/calendar.component';
import { STATES } from './doc.extension.router';
import { DbxCalendarRootModule } from '@dereekb/dbx-web';
import { DbxCalendarRootModule, DbxWidgetModule, DbxWidgetService } from '@dereekb/dbx-web';
import { DocExtensionWidgetComponent } from './container/widget.component';
import { DOC_EXTENSION_WIDGET_EXAMPLE_TYPE, DocExtensionWidgetExampleComponent } from './component/widget.example.component';

@NgModule({
imports: [
DocSharedModule,
DbxCalendarRootModule,
DbxWidgetModule,
UIRouterModule.forChild({
states: STATES
})
],
declarations: [
// component
DocExtensionWidgetExampleComponent,
// container
DocExtensionLayoutComponent,
DocExtensionHomeComponent,
DocExtensionCalendarComponent
DocExtensionCalendarComponent,
DocExtensionWidgetComponent
]
})
export class DocExtensionModule {}
export class DocExtensionModule {
constructor(dbxWidgetService: DbxWidgetService) {
dbxWidgetService.register({
type: DOC_EXTENSION_WIDGET_EXAMPLE_TYPE,
componentClass: DocExtensionWidgetExampleComponent
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Ng2StateDeclaration } from '@uirouter/angular';
import { DocExtensionLayoutComponent } from './container/layout.component';
import { DocExtensionCalendarComponent } from './container/calendar.component';
import { DocExtensionHomeComponent } from './container/home.component';
import { DocExtensionWidgetComponent } from './container/widget.component';

export const layoutState: Ng2StateDeclaration = {
url: '/extension',
Expand All @@ -22,4 +23,16 @@ export const docExtensionCalendarState: Ng2StateDeclaration = {
component: DocExtensionCalendarComponent
};

export const STATES: Ng2StateDeclaration[] = [layoutState, homeState, docExtensionCalendarState];
export const docExtensionWidgetState: Ng2StateDeclaration = {
url: '/widget',
name: 'doc.extension.widget',
component: DocExtensionWidgetComponent
};

export const STATES: Ng2StateDeclaration[] = [
//
layoutState,
homeState,
docExtensionCalendarState,
docExtensionWidgetState
];
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ export const DOC_EXTENSION_ROUTES = [
title: 'Calendar',
detail: 'dbx-calendar',
ref: 'doc.extension.calendar'
},
{
icon: 'code',
title: 'Widget',
detail: 'dbx-widget-view',
ref: 'doc.extension.widget'
}
];

Expand Down
4 changes: 2 additions & 2 deletions packages/dbx-core/src/lib/storage/storage.accessor.simple.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Observable, map } from 'rxjs';
import { timeHasExpired, unixTimeNumberForNow } from '@dereekb/date';
import { DataDoesNotExistError, DataIsExpiredError, ReadStoredData, StoredData, StoredDataStorageKey, StoredDataString, Maybe, hasNonNullValue } from '@dereekb/util';
import { DataDoesNotExistError, DataIsExpiredError, ReadStoredData, StoredData, StoredDataStorageKey, StoredDataString, Maybe, hasNonNullValue, splitJoinRemainder } from '@dereekb/util';
import { StorageAccessor } from './storage.accessor';

// MARK: SimpleStorageAccessor
Expand Down Expand Up @@ -223,7 +223,7 @@ export class SimpleStorageAccessor<T> implements StorageAccessor<T> {
}

protected decodeStorageKey(storageKey: StoredDataStorageKey): string {
const split = storageKey.split(this._config.prefixSplitter, 2);
const split = splitJoinRemainder(storageKey, this._config.prefixSplitter, 2);
return split[1];
}

Expand Down
1 change: 1 addition & 0 deletions packages/dbx-web/src/lib/extension/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './calendar';
export * from './widget';
6 changes: 6 additions & 0 deletions packages/dbx-web/src/lib/extension/widget/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './widget';
export * from './widget.component';
export * from './widget.directive';
// export * from './widget.list.component';
export * from './widget.module';
export * from './widget.service';
52 changes: 52 additions & 0 deletions packages/dbx-web/src/lib/extension/widget/widget.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Observable, BehaviorSubject, map } from 'rxjs';
import { Component, Input, OnDestroy } from '@angular/core';
import { Maybe } from '@dereekb/util';
import { DbxInjectionComponentConfig } from '@dereekb/dbx-core';
import { DbxWidgetDataPair } from './widget';
import { DbxWidgetService } from './widget.service';

/**
* Used to display a corresponding widget based on the input data.
*/
@Component({
selector: 'dbx-widget-view',
template: `
<dbx-injection [config]="config$ | async"></dbx-injection>
`,
host: {
class: 'dbx-widget-view'
}
})
export class DbxWidgetViewComponent implements OnDestroy {
private _config = new BehaviorSubject<Maybe<DbxWidgetDataPair>>(undefined);

readonly config$: Observable<Maybe<DbxInjectionComponentConfig>> = this._config.pipe(
map((pair: Maybe<DbxWidgetDataPair>) => {
let config: Maybe<DbxInjectionComponentConfig>;

if (pair != null) {
const entry = this.dbxWidgetService.getWidgetEntry(pair.type);

if (entry) {
config = {
componentClass: entry.componentClass,
data: pair.data
};
}
}

return config;
})
);

constructor(readonly dbxWidgetService: DbxWidgetService) {}

ngOnDestroy(): void {
this._config.complete();
}

@Input()
set config(config: Maybe<DbxWidgetDataPair>) {
this._config.next(config);
}
}
7 changes: 7 additions & 0 deletions packages/dbx-web/src/lib/extension/widget/widget.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Directive, Inject } from '@angular/core';
import { DBX_INJECTION_COMPONENT_DATA } from '@dereekb/dbx-core';

@Directive()
export abstract class AbstractDbxWidgetComponent<T> {
constructor(@Inject(DBX_INJECTION_COMPONENT_DATA) readonly data: T) {}
}
Empty file.
18 changes: 18 additions & 0 deletions packages/dbx-web/src/lib/extension/widget/widget.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { DbxInjectionComponentModule } from '@dereekb/dbx-core';
import { DbxWidgetViewComponent } from './widget.component';

/**
* Contains components related to displaying "widgets" for pieces of data.
*/
@NgModule({
imports: [
//
CommonModule,
DbxInjectionComponentModule
],
declarations: [DbxWidgetViewComponent],
exports: [DbxWidgetViewComponent]
})
export class DbxWidgetModule {}
52 changes: 52 additions & 0 deletions packages/dbx-web/src/lib/extension/widget/widget.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Inject, Injectable, Optional, Type } from '@angular/core';
import { Maybe, filterMaybeValues, mapIterable } from '@dereekb/util';
import { DbxWidgetType } from './widget';

export interface DbxWidgetEntry {
/**
* Widget type to respond to.
*/
readonly type: DbxWidgetType;
/**
* Widget component class to use.
*/
readonly componentClass: Type<unknown>;
}

/**
* Service used to register widgets.
*/
@Injectable({
providedIn: 'root'
})
export class DbxWidgetService {
private _entries = new Map<DbxWidgetType, DbxWidgetEntry>();

/**
* Used to register an entry. If an entry with the same type is already registered, this will override it by default.
*
* @param entry
* @param override
*/
register(entry: DbxWidgetEntry, override: boolean = true): boolean {
if (override || !this._entries.has(entry.type)) {
this._entries.set(entry.type, entry);
return true;
} else {
return false;
}
}

// MARK: Get
getWidgetIdentifiers(): DbxWidgetType[] {
return Array.from(this._entries.keys());
}

getWidgetEntry(type: DbxWidgetType): Maybe<DbxWidgetEntry> {
return this._entries.get(type);
}

getWidgetEntries(types: Iterable<DbxWidgetType>): DbxWidgetEntry[] {
return filterMaybeValues(mapIterable(types ?? [], (x) => this._entries.get(x)));
}
}
16 changes: 16 additions & 0 deletions packages/dbx-web/src/lib/extension/widget/widget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ModelTypeDataPair, TypedModel, MapFunction, ReadKeyFunction } from '@dereekb/util';

/**
* Widget type identifier
*/
export type DbxWidgetType = string;

/**
* Type and data pair for a DbxWidget.
*/
export type DbxWidgetDataPair<T = unknown> = ModelTypeDataPair<T>;

/**
* Used for converting the input data into a DbxWidgetDataPair value.
*/
export type DbxWidgetDataPairFactory<T> = MapFunction<T, DbxWidgetDataPair<T>>;

0 comments on commit 6cf8d3a

Please sign in to comment.