Remove `viewStateChanged()` method from gr-change-view

The deed is done. This was the ultimate target of a large chain of
refactorings: Do not let the change view handle view state updates in a
complicated tree of paths, but instead let the change model react in
specific independent subscriptions. And let the view just subscribe to
model updates.

The major missing change that was still needed, was the handling of
firing and handling reload events. Unfortunately clearing the patchset
and forcing a reload was also been handled by a `reload` event, but
clearing the patchset is a view state change, so this should be a
direct command to the model. So we have looked at all `fireReload()`
and `fireReload(true)` instances and checked which one is more
appropriate: Keeping firing the "soft" reload or requesting a new
view state.

One issue was that the `forceReload` URL parameter was only handled by
the change view. We are handling this now in the change view model.

We have unified the entire reload handling in the change view model:
The `reload` event, the `reload()` method of the view model and the
`forceReload` URL parameter all end up in the same place: We are
always resetting the view state to `undefined` in order to trigger
a reload. That is very robust and reliable and allows us to also get
rid of some dedicated `reload$` observables that we don't need anymore.

Stop logging `change-view-re-rendered`. We don't need that anymore.

Release-Notes: skip
Change-Id: Ie6319089dee7586c04c01b2ae91c18bc75f0d85d
diff --git a/polygerrit-ui/app/models/views/change.ts b/polygerrit-ui/app/models/views/change.ts
index 5799bbd..713d401 100644
--- a/polygerrit-ui/app/models/views/change.ts
+++ b/polygerrit-ui/app/models/views/change.ts
@@ -69,9 +69,16 @@
 
   /** for scrolling a Change Log message into view in gr-change-view */
   messageHash?: string;
-  /** for logging where the user came from */
+  /**
+   * For logging where the user came from. This is handled by the router, so
+   * this is not inspected by the model.
+   */
   usp?: string;
-  /** triggers all change related data to be reloaded */
+  /**
+   * Triggers all change related data to be reloaded. This is implemented by
+   * intercepting change view state updates and `forceReload` causing the view
+   * state to be wiped clean as `undefined` in an intermediate update.
+   */
   forceReload?: boolean;
   /** triggers opening the reply dialog */
   openReplyDialog?: boolean;
@@ -328,6 +335,39 @@
         });
       }
     });
+    document.addEventListener('reload', this.reload);
+  }
+
+  override finalize(): void {
+    document.removeEventListener('reload', this.reload);
+  }
+
+  /**
+   * Calling this is the same as firing the 'reload' event. This is also the
+   * same as adding `forceReload` parameter in the URL. See below.
+   */
+  reload = () => {
+    const state = this.getState();
+    if (state !== undefined) this.forceLoad(state);
+  };
+
+  /**
+   * This is the destination of where the `reload()` method, the `reload` event
+   * and the `forceReload` URL parameter all end up.
+   */
+  private forceLoad(state: ChangeViewState) {
+    this.setState(undefined);
+    // We have to do this in a timeout, because we need the `undefined` value to
+    // be processed by all observers first and thus have the "reset" completed.
+    setTimeout(() => this.setState({...state, forceReload: undefined}));
+  }
+
+  override setState(state: ChangeViewState | undefined): void {
+    if (state?.forceReload) {
+      this.forceLoad(state);
+    } else {
+      super.setState(state);
+    }
   }
 
   toggleSelectedCheckRun(checkName: string) {