blob: b2bdcfecfb190b1b10e4f9b1ffc9ec9502da641c [file] [log] [blame]
/**
* @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()
);