blob: e62968523da809f5b3e20860ac3723e0aa883db8 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {Action, DeferredAction} from 'chrome://resources/js/store_ts.js';
import {dedupingMixin, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {Store} from './store.js';
import {BookmarksPageState} from './types.js';
type Constructor<T> = new (...args: any[]) => T;
interface Watch {
localProperty: string;
valueGetter: (p: BookmarksPageState) => any;
}
export const StoreClientMixin = dedupingMixin(
<T extends Constructor<PolymerElement>>(superClass: T): T&
Constructor<StoreClientMixinInterface> => {
class StoreClientMixin extends superClass implements
StoreClientMixinInterface {
private watches_: Watch[] = [];
override connectedCallback() {
super.connectedCallback();
this.getStore().addObserver(this);
}
override disconnectedCallback() {
super.disconnectedCallback();
this.getStore().removeObserver(this);
}
/**
* Watches a particular part of the state tree, updating |localProperty|
* to the return value of |valueGetter| whenever the state changes. Eg,
* to keep |this.item| updated with the value of a node: watch('item',
* (state) => state.nodes[this.itemId]);
*
* Note that object identity is used to determine if the value has
* changed before updating the UI, rather than Polymer-style deep
* equality. If the getter function returns |undefined|, no changes will
* propagate to the UI.
*/
private watch_(
localProperty: string,
valueGetter: (p: BookmarksPageState) => any): void {
this.watches_.push({
localProperty: localProperty,
valueGetter: valueGetter,
});
}
dispatch(action: Action|null) {
this.getStore().dispatch(action);
}
dispatchAsync(action: DeferredAction) {
this.getStore().dispatchAsync(action);
}
onStateChanged(newState: BookmarksPageState) {
this.watches_.forEach((watch) => {
const oldValue = this.get(watch.localProperty);
const newValue = watch.valueGetter(newState);
// Avoid poking Polymer unless something has actually changed.
// Reducers must return new objects rather than mutating existing
// objects, so any real changes will pass through correctly.
if (oldValue === newValue || newValue === undefined) {
return;
}
this.set(watch.localProperty, newValue);
});
}
updateFromStore() {
if (this.getStore().isInitialized()) {
this.onStateChanged(this.getStore().data);
}
}
watch(
localProperty: string,
valueGetter: (p: BookmarksPageState) => any) {
this.watch_(localProperty, valueGetter);
}
getState() {
return this.getStore().data;
}
getStore() {
return Store.getInstance();
}
}
return StoreClientMixin;
});
export interface StoreClientMixinInterface {
/**
* Helper to dispatch an action to the store, which will update the store
* data and then (possibly) flow through to the UI.
*/
dispatch(action: Action|null): void;
/**
* Helper to dispatch a DeferredAction to the store, which will
* asynchronously perform updates to the store data and UI.
*/
dispatchAsync(action: DeferredAction): void;
onStateChanged(newState: BookmarksPageState): void;
updateFromStore(): void;
watch(localProperty: string, valueGetter: (p: BookmarksPageState) => any):
void;
getState(): BookmarksPageState;
getStore(): Store;
}