Merge view state of DIFF and EDIT into CHANGE

The motivation is that we want to move more control from views into
models. In particular we would like to the change model (and the
change view model) to control the patch range (patchNum and
basePatchNum).

The DIFF/CHANGE/EDIT views already share a change model, but they
still had their own view model each. We want to works towards a
unified view of which patchset range is currently selected.

A clearly visible sign of the current messy design is that router
model still has the references to change number and patchset numbers.
This change will enable us to move this information into change view
model in a follow-up change.

We are not super happy with how the view models deal with hierarchies.
The `childView` property and lots of optional properties that only
make sense for some child views do not seem to be the ultimate clean
design to this problem. But we are consistent here with other views
(see admin and repo views), and we believe that this is better handled
in a larger, separate effort. Our change definitely makes this
simpler to achieve.

In a follow-up we consider to add a wrapper between app-element and
change-/diff-/edit-view. That will allow us to remove the app-element
dependency on change model. And we will have a clear element that can
act as the change model provider.

Release-Notes: skip
Google-Bug-Id: b/247042673
Change-Id: Ibf04e462ee76d830dbab02003288f4d7e99ecd13
diff --git a/polygerrit-ui/app/models/views/change.ts b/polygerrit-ui/app/models/views/change.ts
index 31d511a..9701b54 100644
--- a/polygerrit-ui/app/models/views/change.ts
+++ b/polygerrit-ui/app/models/views/change.ts
@@ -25,19 +25,33 @@
 import {define} from '../dependency';
 import {Model} from '../model';
 import {ViewState} from './base';
+import {createDiffUrl} from './diff';
+import {createEditUrl} from './edit';
+
+export enum ChangeChildView {
+  OVERVIEW = 'OVERVIEW',
+  DIFF = 'DIFF',
+  EDIT = 'EDIT',
+}
 
 export interface ChangeViewState extends ViewState {
   view: GerritView.CHANGE;
+  childView: ChangeChildView;
 
   changeNum: NumericChangeId;
   repo: RepoName;
-  edit?: boolean;
   patchNum?: RevisionPatchSetNum;
   basePatchNum?: BasePatchSetNum;
   commentId?: UrlEncodedCommentId;
+
+  // TODO: Move properties that only apply to OVERVIEW into a submessage.
+
+  edit?: boolean;
   /** This can be a string only for plugin provided tabs. */
   tab?: Tab | string;
 
+  // TODO: Move properties that only apply to CHECKS tab into a submessage.
+
   /** Checks related view state */
 
   /** selected patchset for check runs (undefined=latest) */
@@ -61,6 +75,20 @@
   forceReload?: boolean;
   /** triggers opening the reply dialog */
   openReplyDialog?: boolean;
+
+  /** These properties apply to the DIFF child view only. */
+  diffView?: {
+    path?: string;
+    lineNum?: number;
+    leftSide?: boolean;
+    commentLink?: boolean;
+  };
+
+  /** These properties apply to the EDIT child view only. */
+  editView?: {
+    path?: string;
+    lineNum?: number;
+  };
 }
 
 /**
@@ -70,7 +98,7 @@
  */
 export type CreateChangeUrlObject = Omit<
   ChangeViewState,
-  'view' | 'changeNum' | 'repo'
+  'view' | 'childView' | 'changeNum' | 'repo'
 > & {
   change: Pick<ChangeInfo, '_number' | 'project'>;
 };
@@ -82,7 +110,9 @@
 }
 
 export function objToState(
-  obj: CreateChangeUrlObject | Omit<ChangeViewState, 'view'>
+  obj:
+    | (CreateChangeUrlObject & {childView: ChangeChildView})
+    | Omit<ChangeViewState, 'view'>
 ): ChangeViewState {
   if (isCreateChangeUrlObject(obj)) {
     return {
@@ -95,10 +125,24 @@
   return {...obj, view: GerritView.CHANGE};
 }
 
+export function createChangeViewUrl(state: ChangeViewState): string {
+  switch (state.childView) {
+    case ChangeChildView.OVERVIEW:
+      return createChangeUrl(state);
+    case ChangeChildView.DIFF:
+      return createDiffUrl(state);
+    case ChangeChildView.EDIT:
+      return createEditUrl(state);
+  }
+}
+
 export function createChangeUrl(
-  obj: CreateChangeUrlObject | Omit<ChangeViewState, 'view'>
+  obj: CreateChangeUrlObject | Omit<ChangeViewState, 'view' | 'childView'>
 ) {
-  const state: ChangeViewState = objToState(obj);
+  const state: ChangeViewState = objToState({
+    ...obj,
+    childView: ChangeChildView.OVERVIEW,
+  });
   let range = getPatchRangeExpression(state);
   if (range.length) {
     range = '/' + range;
@@ -156,6 +200,8 @@
   define<ChangeViewModel>('change-view-model');
 
 export class ChangeViewModel extends Model<ChangeViewState | undefined> {
+  public readonly childView$ = select(this.state$, state => state?.childView);
+
   public readonly tab$ = select(this.state$, state => state?.tab);
 
   public readonly checksPatchset$ = select(