107 lines
3.2 KiB
TypeScript
107 lines
3.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright (C) 2020 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import {PatchSetNum} from '../../types/common';
|
|
import {BehaviorSubject, combineLatest, Observable} from 'rxjs';
|
|
import {
|
|
map,
|
|
filter,
|
|
withLatestFrom,
|
|
distinctUntilChanged,
|
|
} from 'rxjs/operators';
|
|
import {routerPatchNum$, routerState$} from '../router/router-model';
|
|
import {
|
|
computeAllPatchSets,
|
|
computeLatestPatchNum,
|
|
} from '../../utils/patch-set-util';
|
|
import {ParsedChangeInfo} from '../../types/types';
|
|
|
|
interface ChangeState {
|
|
change?: ParsedChangeInfo;
|
|
}
|
|
|
|
// TODO: Figure out how to best enforce immutability of all states. Use Immer?
|
|
// Use DeepReadOnly?
|
|
const initialState: ChangeState = {};
|
|
|
|
const privateState$ = new BehaviorSubject(initialState);
|
|
|
|
// Re-exporting as Observable so that you can only subscribe, but not emit.
|
|
export const changeState$: Observable<ChangeState> = privateState$;
|
|
|
|
// Must only be used by the change service or whatever is in control of this
|
|
// model.
|
|
export function updateState(change?: ParsedChangeInfo) {
|
|
privateState$.next({
|
|
...privateState$.getValue(),
|
|
change,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* If you depend on both, router and change state, then you want to filter out
|
|
* inconsistent state, e.g. router changeNum already updated, change not yet
|
|
* reset to undefined.
|
|
*/
|
|
export const changeAndRouterConsistent$ = combineLatest([
|
|
routerState$,
|
|
changeState$,
|
|
]).pipe(
|
|
filter(([routerState, changeState]) => {
|
|
const changeNum = changeState.change?._number;
|
|
const routerChangeNum = routerState.changeNum;
|
|
return changeNum === undefined || changeNum === routerChangeNum;
|
|
}),
|
|
distinctUntilChanged()
|
|
);
|
|
|
|
export const change$ = changeState$.pipe(
|
|
map(changeState => changeState.change),
|
|
distinctUntilChanged()
|
|
);
|
|
|
|
export const changeNum$ = change$.pipe(
|
|
map(change => change?._number),
|
|
distinctUntilChanged()
|
|
);
|
|
|
|
export const latestPatchNum$ = change$.pipe(
|
|
map(change => computeLatestPatchNum(computeAllPatchSets(change))),
|
|
distinctUntilChanged()
|
|
);
|
|
|
|
/**
|
|
* Emits the current patchset number. If the route does not define the current
|
|
* patchset num, then this selector waits for the change to be defined and
|
|
* returns the number of the latest patchset.
|
|
*
|
|
* Note that this selector can emit a patchNum without the change being
|
|
* available!
|
|
*
|
|
* TODO: It would be good to assert/enforce somehow that currentPatchNum$ cannot
|
|
* emit 'PARENT'.
|
|
*/
|
|
export const currentPatchNum$: Observable<
|
|
PatchSetNum | undefined
|
|
> = changeAndRouterConsistent$.pipe(
|
|
withLatestFrom(routerPatchNum$, latestPatchNum$),
|
|
map(([_, routerPatchNum, latestPatchNum]) => {
|
|
return routerPatchNum || latestPatchNum;
|
|
}),
|
|
distinctUntilChanged()
|
|
);
|