phetsims / joist

Joist is the main framework for PhET Interactive Simulations. Joist creates and displays the simulation content, home screen, navigation bar, About dialog, enables switching between tabs, and other framework-related features.
MIT License
8 stars 6 forks source link

Create a TModel type alias #783

Open zepumph opened 2 years ago

zepumph commented 2 years ago

This will have a step function. For improving upon type safety and documentation. It also allows for a space to expand the API in the future.

https://github.com/phetsims/ratio-and-proportion/issues/405

zepumph commented 2 years ago

Maybe this get's a reset function too?

samreid commented 2 years ago

I committed IModel today for a different issue. But it doesn't have a reset method yet.

samreid commented 2 years ago

I don't see any calls to reset in joist, so I'm not sure the advantage of adding that as an optional part of the interface. @zepumph can this be closed?

UPDATE: Tagging for https://github.com/phetsims/tasks/issues/1111

zepumph commented 2 years ago

In https://github.com/phetsims/joist/commit/0d6f15d4ba1fc319902af99ad6496ba6ea7ed36e I made IModel the default for Screen, which was nice to not have to give it parameters when really it is quite general.

pixelzoom commented 2 years ago

Based on conclusions in https://github.com/phetsims/chipper/issues/1283, and @samreid's proposal to ban interface... Perhaps type should be used instead of interface.

zepumph commented 2 years ago

It is: https://github.com/phetsims/joist/blob/791c659c5120ae8de5582166ec0a0dbeeb19f9c3/js/IModel.ts#L11

zepumph commented 2 years ago

I committed with @samreid today, we were able to use out as part of 4.7 to get IModel to typecheck even when a parameter to a passed in function. I'd like to mark this for dev meeting though as a PSA about a couple things:

pixelzoom commented 2 years ago

This is failing for me in a couple of places, and I'm dead in the water.

In Screen.ts, both WebStorm and tsc are complaining about the definiton of CreateView, despite the ts-ignore:

screenshot_1789

In Screen subclasses like LensScreen.ts, I'm seeing an "implicit any" error in WebStorm:

screenshot_1791
samreid commented 2 years ago

I messaged slack:

We have updated to TypeScript version 4.7, please see https://github.com/phetsims/joist/issues/783#issuecomment-1189650113 for context and detail. Next time you pull, please npm install in chipper.

As mentioned in https://github.com/phetsims/chipper/issues/1212#issuecomment-1137922311 this requires WebStorm 2022.1.2 RC or higher.

pixelzoom commented 2 years ago

Upgrading to TS 4.7 resolved the issues I reported in https://github.com/phetsims/joist/issues/783#issuecomment-1189667682.

samreid commented 2 years ago

I investigated how to implement the IModel constraint without any ts-ignore. The problem is that Sim and Screen specify IModel as a parameter in createModel, so when a Screen is parameterized as MyModel => MyView it needs MyModel =>MyView and will not accept IModel => MyView because that type parameter already told it to expect something more specific.

Brainstorming ideas:

```diff Index: main/joist/js/Screen.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/Screen.ts b/main/joist/js/Screen.ts --- a/main/joist/js/Screen.ts (revision 1c1f38e813b67a842aedbc212dd8c10bf75deed5) +++ b/main/joist/js/Screen.ts (date 1658284714759) @@ -68,12 +68,8 @@ }; export type ScreenOptions = SelfOptions & PhetioObjectOptions & PickRequired; -// Accept any subtype of IModel (defaults to supertype), and any subtype of ScreenView (defaults to subtype). -// @ts-ignore -type CreateView = ( model: M ) => V; - // Parameterized on M=Model and V=View -class Screen extends PhetioObject { +class Screen extends PhetioObject { public backgroundColorProperty: Property | Property | Property; @@ -89,7 +85,7 @@ public readonly keyboardHelpNode: Node | null; // joist-internal public readonly pdomDisplayNameProperty: IReadOnlyProperty; private readonly createModel: () => M; - private readonly createView: CreateView; + private readonly createView: ( m: M ) => V; private _model: M | null; private _view: V | null; @@ -98,7 +94,7 @@ public static MINIMUM_NAVBAR_ICON_SIZE: Dimension2; public static ScreenIO: IOType; - public constructor( createModel: () => M, createView: CreateView, providedOptions: ScreenOptions ) { + public constructor( createModel: () => M, createView: ( m: M ) => V, providedOptions: ScreenOptions ) { const options = optionize()( { Index: main/geometric-optics/js/GOSim.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/geometric-optics/js/GOSim.ts b/main/geometric-optics/js/GOSim.ts --- a/main/geometric-optics/js/GOSim.ts (revision 1c3af585b5db7788f9cb1c20efe992b28a1feaf4) +++ b/main/geometric-optics/js/GOSim.ts (date 1658287650900) @@ -14,6 +14,10 @@ import GOConstants from './common/GOConstants.js'; import optionize from '../../phet-core/js/optionize.js'; import GOOptionsNode from './common/view/GOOptionsNode.js'; +import LensModel from './lens/model/LensModel.js'; +import LensScreenView from './lens/view/LensScreenView.js'; +import MirrorModel from './mirror/model/MirrorModel.js'; +import MirrorScreenView from './mirror/view/MirrorScreenView.js'; type SelfOptions = { @@ -25,9 +29,9 @@ export type GOSimOptions = SelfOptions; -export default class GOSim extends Sim { +export default class GOSim { - public constructor( title: string, providedOptions: GOSimOptions ) { + public static create( title: string, providedOptions: GOSimOptions ) { const options = optionize()( { @@ -41,7 +45,7 @@ }, providedOptions ); - super( title, [ + return new Sim( title, [ new LensScreen( { isBasicsVersion: options.isBasicsVersion, tandem: Tandem.ROOT.createTandem( 'lensScreen' ) Index: main/joist/js/Sim.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/Sim.ts b/main/joist/js/Sim.ts --- a/main/joist/js/Sim.ts (revision 1c1f38e813b67a842aedbc212dd8c10bf75deed5) +++ b/main/joist/js/Sim.ts (date 1658287446977) @@ -109,7 +109,11 @@ export type SimOptions = SelfOptions & PickOptional; -export default class Sim extends PhetioObject { +export default class Sim extends PhetioObject { // (joist-internal) public readonly simNameProperty: IReadOnlyProperty; @@ -256,7 +260,12 @@ * @param allSimScreens - the possible screens for the sim in order of declaration (does not include the home screen) * @param [providedOptions] - see below for options */ - public constructor( name: string, allSimScreens: Screen[], providedOptions?: SimOptions ) { + public constructor( name: string, allSimScreens: [ Screen ], providedOptions?: SimOptions ); + public constructor( name: string, allSimScreens: [ Screen ] | [ Screen, Screen ], providedOptions?: SimOptions ); + public constructor( name: string, allSimScreens: [ Screen ] | [ Screen, Screen ] | [ Screen, Screen, Screen ], providedOptions?: SimOptions ); + public constructor( name: string, allSimScreens: [ Screen ] | [ Screen, Screen ] | [ Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen ], providedOptions?: SimOptions ); + public constructor( name: string, allSimScreens: [ Screen ] | [ Screen, Screen ] | [ Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen, Screen ], providedOptions?: SimOptions ); + public constructor( name: string, allSimScreens: [ Screen ] | [ Screen, Screen ] | [ Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen, Screen ], providedOptions?: SimOptions ) { window.phetSplashScreenDownloadComplete(); Index: main/geometric-optics/js/geometric-optics-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/geometric-optics/js/geometric-optics-main.ts b/main/geometric-optics/js/geometric-optics-main.ts --- a/main/geometric-optics/js/geometric-optics-main.ts (revision 1c3af585b5db7788f9cb1c20efe992b28a1feaf4) +++ b/main/geometric-optics/js/geometric-optics-main.ts (date 1658287725192) @@ -11,7 +11,7 @@ import GOSim from './GOSim.js'; simLauncher.launch( () => { - const sim = new GOSim( geometricOpticsStrings[ 'geometric-optics' ].title, { + const sim = GOSim.create( geometricOpticsStrings[ 'geometric-optics' ].title, { isBasicsVersion: false } ); sim.start(); Index: main/build-a-nucleus/js/common/model/BANModel.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/build-a-nucleus/js/common/model/BANModel.ts b/main/build-a-nucleus/js/common/model/BANModel.ts --- a/main/build-a-nucleus/js/common/model/BANModel.ts (revision 5ff02ee706f4344ed8abd34a57e92a7669681965) +++ b/main/build-a-nucleus/js/common/model/BANModel.ts (date 1658283848195) @@ -23,11 +23,12 @@ import ParticleType from '../../decay/view/ParticleType.js'; import BooleanProperty from '../../../../axon/js/BooleanProperty.js'; import Animation from '../../../../twixt/js/Animation.js'; +import IModel from '../../../../joist/js/IModel.js'; // types export type BANModelOptions = PickRequired; -class BANModel { +class BANModel implements IModel{ // the stability of the nuclide public readonly isStableBooleanProperty: IReadOnlyProperty; Index: main/geometric-optics-basics/js/geometric-optics-basics-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/geometric-optics-basics/js/geometric-optics-basics-main.ts b/main/geometric-optics-basics/js/geometric-optics-basics-main.ts --- a/main/geometric-optics-basics/js/geometric-optics-basics-main.ts (revision 103d4343f933a1f93463ffae5bd30c2508c3844d) +++ b/main/geometric-optics-basics/js/geometric-optics-basics-main.ts (date 1658287670199) @@ -25,7 +25,7 @@ GOOptions.add2FPointsCheckboxProperty.value = true; } - const sim = new GOSim( geometricOpticsBasicsStrings[ 'geometric-optics-basics' ].title, { + const sim = GOSim.create( geometricOpticsBasicsStrings[ 'geometric-optics-basics' ].title, { isBasicsVersion: true } ); sim.start(); ```

I followed the philosophy from DerivedProperty, which is repetitive and explicit. However, it doesn't work as well here since we can't always use inference and it is unfortunate to have to repeat ourselves with type parameters.

If we (a) use inference at all call sites and (b) invent a type like UnknownSim where it knows everything but the model types, then this could be doable. But maybe it would be better to put selective ts-ignore and transform a type safety problem into a runtime check.

samreid commented 2 years ago

At today's developer meeting, we agreed the final proposal and patch in the about bullet point sounds promising, but we want to run it past @pixelzoom before implementing it.

pixelzoom commented 2 years ago

I can't say that I'm wild about the approach in the patch in https://github.com/phetsims/joist/issues/783#issuecomment-1189781470. But I don't have a better suggestion.

EDIT: I really dislike this bit. And is it correct?

+export default class Sim<M1 extends IModel, V1 extends ScreenView,
+  M2 extends IModel, V2 extends ScreenView,
+  M3 extends IModel, V3 extends ScreenView,
+  M4 extends IModel, V4 extends ScreenView,
+  M5 extends IModel, V5 extends ScreenView> extends PhetioObject {

+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ], providedOptions?: SimOptions );
+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ], providedOptions?: SimOptions );
+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ], providedOptions?: SimOptions );
+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4> ], providedOptions?: SimOptions );
+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4>, Screen<M5, V5> ], providedOptions?: SimOptions );
+  public constructor( name: string, allSimScreens: [ Screen<M1, V1> ] | [ Screen<M1, V1>, Screen<M2, V2> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4> ] | [ Screen<M1, V1>, Screen<M2, V2>, Screen<M3, V3>, Screen<M4, V4>, Screen<M5, V5> ], providedOptions?: SimOptions ) {

That said...

If create is the new way of instantiating Sim, then Sim's constructor should be protected.

IModel should be renamed to TModel, or perhaps something without a prefix. (I don't feel strongly about eradicating the 'T' prefix.)

samreid commented 2 years ago

If create is the new way of instantiating Sim, then Sim's constructor should be protected.

The proposed strategy gets type inference to work well in all sites that use new Sim. For instance, in Fourier, it could infer all the model and view types from this part:

  const sim = new Sim( fourierMakingWavesTitleString, [
    new DiscreteScreen( { tandem: Tandem.ROOT.createTandem( 'discreteScreen' ) } ),
    new WaveGameScreen( { tandem: Tandem.ROOT.createTandem( 'waveGameScreen' ) } ),
    new WavePacketScreen( { tandem: Tandem.ROOT.createTandem( 'wavePacketScreen' ) } )
  ], simOptions );
  sim.start();

Sim.create was our workaround for sites that extend Sim where inference cannot be used. But maybe it would be better if all sites could use new Sim.

EDIT: I really dislike this bit. And is it correct?

I skimmed it and did not see any obvious defects.

Let's discuss further before proceeding.

pixelzoom commented 2 years ago

@samreid let me know when you'd like to discuss.

samreid commented 2 years ago

I added a note to my calendar to reach out to @pixelzoom in the coming week. Self-unassigning until then.

UPDATE: Perhaps an on-hold label would be good instead.

samreid commented 2 years ago

Removing the hold to update the patch before discussing with @pixelzoom

samreid commented 2 years ago

Current patch:

``` Index: main/joist/js/toolbar/VoicingToolbarAlertManager.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/toolbar/VoicingToolbarAlertManager.ts b/main/joist/js/toolbar/VoicingToolbarAlertManager.ts --- a/main/joist/js/toolbar/VoicingToolbarAlertManager.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/toolbar/VoicingToolbarAlertManager.ts (date 1661801709410) @@ -10,17 +10,17 @@ import TReadOnlyProperty from '../../../axon/js/TReadOnlyProperty.js'; import joist from '../joist.js'; -import Screen from '../Screen.js'; +import { UnknownScreen } from '../Screen.js'; class VoicingToolbarAlertManager { // The active Screen for the simulation, to generate Voicing descriptions that are related to the active screen. - private readonly screenProperty: TReadOnlyProperty; + private readonly screenProperty: TReadOnlyProperty; /** * @param screenProperty - indicates the active screen */ - public constructor( screenProperty: TReadOnlyProperty ) { + public constructor( screenProperty: TReadOnlyProperty ) { this.screenProperty = screenProperty; } Index: main/joist/js/HomeScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/HomeScreenView.ts b/main/joist/js/HomeScreenView.ts --- a/main/joist/js/HomeScreenView.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/HomeScreenView.ts (date 1661803022498) @@ -16,18 +16,18 @@ import joist from './joist.js'; import joistStrings from './joistStrings.js'; import ScreenView, { ScreenViewOptions } from './ScreenView.js'; -import Screen from './Screen.js'; +import Screen, { UnknownScreen } from './Screen.js'; import HomeScreenModel from './HomeScreenModel.js'; import Property from '../../axon/js/Property.js'; import optionize from '../../phet-core/js/optionize.js'; import IntentionalAny from '../../phet-core/js/types/IntentionalAny.js'; import TReadOnlyProperty from '../../axon/js/TReadOnlyProperty.js'; import PickRequired from '../../phet-core/js/types/PickRequired.js'; +import HomeScreen from './HomeScreen.js'; +import TModel from './TModel.js'; const homeScreenDescriptionPatternString = joistStrings.a11y.homeScreenDescriptionPattern; -type GeneralScreen = Screen; - type SelfOptions = { // to display below the icons as a warning if available @@ -36,10 +36,10 @@ type HomeScreenViewOptions = SelfOptions & PickRequired; -class HomeScreenView extends ScreenView { +class HomeScreenView extends ScreenView { private homeScreenScreenSummaryIntro!: string; - private selectedScreenProperty: Property; + private selectedScreenProperty: Property<( Screen | Screen | Screen | Screen | Screen | Screen )>; public screenButtons: HomeScreenButton[]; // NOTE: In https://github.com/phetsims/joist/issues/640, we attempted to use ScreenView.DEFAULT_LAYOUT_BOUNDS here. @@ -55,7 +55,7 @@ * @param model * @param [providedOptions] */ - public constructor( simNameProperty: TReadOnlyProperty, model: HomeScreenModel, providedOptions?: HomeScreenViewOptions ) { + public constructor( simNameProperty: TReadOnlyProperty, model: HomeScreenModel, providedOptions?: HomeScreenViewOptions ) { const options = optionize()( { layoutBounds: HomeScreenView.LAYOUT_BOUNDS, @@ -95,7 +95,7 @@ const buttonGroupTandem = options.tandem.createTandem( 'buttonGroup' ); - this.screenButtons = _.map( model.simScreens, ( screen: GeneralScreen ) => { + this.screenButtons = _.map( model.simScreens, ( screen: UnknownScreen ) => { assert && assert( screen.nameProperty.value, `name is required for screen ${model.simScreens.indexOf( screen )}` ); assert && assert( screen.homeScreenIcon, `homeScreenIcon is required for screen ${screen.nameProperty.value}` ); Index: main/joist/js/Heartbeat.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/Heartbeat.ts b/main/joist/js/Heartbeat.ts --- a/main/joist/js/Heartbeat.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/Heartbeat.ts (date 1661800779105) @@ -9,7 +9,7 @@ */ import joist from './joist.js'; -import Sim from './Sim.js'; +import Sim, { UnknownSim } from './Sim.js'; // variables let started = false; @@ -22,7 +22,7 @@ /** * Initializes the heartbeat div to begin ticking to prevent Safari from going to sleep. */ - start: function( sim: Sim ): void { + start: function( sim: UnknownSim ): void { assert && assert( !started, 'Heartbeat can only be started once' ); started = true; Index: main/joist/js/SimInfo.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/SimInfo.ts b/main/joist/js/SimInfo.ts --- a/main/joist/js/SimInfo.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/SimInfo.ts (date 1661801932375) @@ -20,9 +20,9 @@ import ObjectLiteralIO from '../../tandem/js/types/ObjectLiteralIO.js'; import StringIO from '../../tandem/js/types/StringIO.js'; import joist from './joist.js'; -import Screen from './Screen.js'; +import Screen, { UnknownScreen } from './Screen.js'; import packageJSON from './packageJSON.js'; -import Sim from './Sim.js'; +import Sim, { UnknownSim } from './Sim.js'; export type ScreenState = { name: string; @@ -54,7 +54,7 @@ class SimInfo extends PhetioObject { public readonly info: SimInfoState = {} as SimInfoState; - public constructor( sim: Sim ) { + public constructor( sim: UnknownSim ) { super( { tandem: Tandem.GENERAL_MODEL.createTandem( 'simInfo' ), phetioType: SimInfo.SimInfoIO, @@ -110,7 +110,7 @@ this.putInfo( 'simName', sim.simNameProperty.value ); this.putInfo( 'simVersion', sim.version ); this.putInfo( 'repoName', packageJSON.name ); - this.putInfo( 'screens', sim.screens.map( ( screen: Screen ) => { + this.putInfo( 'screens', sim.screens.map( ( screen: UnknownScreen ) => { const screenObject: ScreenState = { // likely null for single screen sims, so use the sim name as a default Index: main/joist/js/audioManager.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/audioManager.ts b/main/joist/js/audioManager.ts --- a/main/joist/js/audioManager.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/audioManager.ts (date 1661800676843) @@ -26,7 +26,7 @@ import PhetioObject from '../../tandem/js/PhetioObject.js'; import Tandem from '../../tandem/js/Tandem.js'; import joist from './joist.js'; -import Sim from './Sim.js'; +import Sim, { UnknownSim } from './Sim.js'; import TReadOnlyProperty from '../../axon/js/TReadOnlyProperty.js'; class AudioManager extends PhetioObject { @@ -84,7 +84,7 @@ /** * Initialize the AudioManager and subcomponents. */ - public initialize( sim: Sim ): void { + public initialize( sim: UnknownSim ): void { if ( sim.preferencesModel.audioModel.supportsSound ) { soundManager.initialize( Index: main/joist/js/PhetButton.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/PhetButton.ts b/main/joist/js/PhetButton.ts --- a/main/joist/js/PhetButton.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/PhetButton.ts (date 1661800945606) @@ -20,7 +20,7 @@ import joistStrings from './joistStrings.js'; import KebabMenuIcon from './KebabMenuIcon.js'; import PhetMenu from './PhetMenu.js'; -import Sim from './Sim.js'; +import Sim, { UnknownSim } from './Sim.js'; import updateCheck from './updateCheck.js'; import UpdateState from './UpdateState.js'; @@ -35,7 +35,7 @@ class PhetButton extends JoistButton { public static PhetButtonIO: IOType; - public constructor( sim: Sim, backgroundFillProperty: TReadOnlyProperty, tandem: Tandem ) { + public constructor( sim: UnknownSim, backgroundFillProperty: TReadOnlyProperty, tandem: Tandem ) { // Dynamic modules are loaded in simLauncher and accessed through their namespace const Brand: TBrand = phet.brand.Brand; Index: main/wave-interference/js/wave-interference-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/wave-interference/js/wave-interference-main.ts b/main/wave-interference/js/wave-interference-main.ts --- a/main/wave-interference/js/wave-interference-main.ts (revision 19d020ea56b6652cfa0aefb9c2b0990fada24fbf) +++ b/main/wave-interference/js/wave-interference-main.ts (date 1661802207786) @@ -42,12 +42,11 @@ matchVertical: false } ); - const screens = [ + const sim = new Sim( waveInterferenceTitleString, [ new WavesScreen( alignGroup ), new InterferenceScreen( alignGroup ), new SlitsScreen( alignGroup ), new DiffractionScreen() - ]; - const sim = new Sim( waveInterferenceTitleString, screens, simOptions ); + ], simOptions ); sim.start(); } ); \ No newline at end of file Index: main/sun/js/Dialog.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/sun/js/Dialog.ts b/main/sun/js/Dialog.ts --- a/main/sun/js/Dialog.ts (revision a8a5b6aea0640527cdced0a6d5115139421abf2d) +++ b/main/sun/js/Dialog.ts (date 1661801849122) @@ -12,7 +12,7 @@ import Multilink, { UnknownMultilink } from '../../axon/js/Multilink.js'; import Bounds2 from '../../dot/js/Bounds2.js'; import ScreenView from '../../joist/js/ScreenView.js'; -import Sim from '../../joist/js/Sim.js'; +import { UnknownSim } from '../../joist/js/Sim.js'; import getGlobal from '../../phet-core/js/getGlobal.js'; import optionize from '../../phet-core/js/optionize.js'; import StrictOmit from '../../phet-core/js/types/StrictOmit.js'; @@ -129,7 +129,7 @@ openedSoundPlayer?: TSoundPlayer; closedSoundPlayer?: TSoundPlayer; - sim?: Sim; + sim?: UnknownSim; // Called after the dialog is shown, see https://github.com/phetsims/joist/issues/478 showCallback?: ( () => void ) | null; @@ -145,7 +145,7 @@ export default class Dialog extends Popupable( Panel, 1 ) { private readonly closeButton: CloseButton; - private readonly sim: Sim; + private readonly sim: UnknownSim; private readonly disposeDialog: () => void; /** Index: main/sun/js/sun-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/sun/js/sun-main.ts b/main/sun/js/sun-main.ts --- a/main/sun/js/sun-main.ts (revision a8a5b6aea0640527cdced0a6d5115139421abf2d) +++ b/main/sun/js/sun-main.ts (date 1661801859358) @@ -25,14 +25,12 @@ simLauncher.launch( () => { - const screens = [ + const sim = new Sim( sunStrings.sun.title, [ new ButtonScreen( Tandem.ROOT.createTandem( 'buttonsScreen' ) ), new ComponentsScreen( Tandem.ROOT.createTandem( 'componentsScreen' ) ), new DialogsScreen( Tandem.ROOT.createTandem( 'dialogsScreen' ) ), new LayoutScreen( Tandem.ROOT.createTandem( 'layoutScreen' ) ) - ]; - - const sim = new Sim( sunStrings.sun.title, screens, { + ], { credits: { leadDesign: 'PhET Interactive Simulations' }, Index: main/joist/js/ScreenshotGenerator.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/ScreenshotGenerator.ts b/main/joist/js/ScreenshotGenerator.ts --- a/main/joist/js/ScreenshotGenerator.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/ScreenshotGenerator.ts (date 1661801688703) @@ -10,11 +10,11 @@ import Matrix3 from '../../dot/js/Matrix3.js'; import { CanvasContextWrapper, Utils } from '../../scenery/js/imports.js'; import joist from './joist.js'; -import Sim from './Sim.js'; +import Sim, { UnknownSim } from './Sim.js'; class ScreenshotGenerator { - private static generateScreenshotAtIncreasedResolution( sim: Sim, scale: number ): HTMLCanvasElement { + private static generateScreenshotAtIncreasedResolution( sim: UnknownSim, scale: number ): HTMLCanvasElement { // set up our Canvas with the correct background color const canvas = document.createElement( 'canvas' ); const context = canvas.getContext( '2d' )!; @@ -47,7 +47,7 @@ } // Default to PNG - public static generateScreenshot( sim: Sim, mimeType = 'image/png' ): string { + public static generateScreenshot( sim: UnknownSim, mimeType = 'image/png' ): string { const res2x = ScreenshotGenerator.generateScreenshotAtIncreasedResolution( sim, 2 ); const res1x = ScreenshotGenerator.renderAtScale( res2x, 1 / 2 ); Index: main/geometric-optics/js/GOSim.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/geometric-optics/js/GOSim.ts b/main/geometric-optics/js/GOSim.ts --- a/main/geometric-optics/js/GOSim.ts (revision b7832067338cb8ed3dc00cf515667373ecd3d72d) +++ b/main/geometric-optics/js/GOSim.ts (date 1661800144425) @@ -17,6 +17,10 @@ import PickOptional from '../../phet-core/js/types/PickOptional.js'; import PreferencesModel from '../../joist/js/preferences/PreferencesModel.js'; import GOPreferences from './common/model/GOPreferences.js'; +import LensModel from './lens/model/LensModel.js'; +import LensScreenView from './lens/view/LensScreenView.js'; +import MirrorModel from './mirror/model/MirrorModel.js'; +import MirrorScreenView from './mirror/view/MirrorScreenView.js'; type SelfOptions = { @@ -28,9 +32,9 @@ export type GOSimOptions = SelfOptions & PickOptional; -export default class GOSim extends Sim { +export default class GOSim { - public constructor( title: string, providedOptions: GOSimOptions ) { + public static create( title: string, providedOptions: GOSimOptions ) { const options = optionize()( { @@ -54,7 +58,7 @@ } ) }, providedOptions ); - super( title, [ + return new Sim( title, [ new LensScreen( { isBasicsVersion: options.isBasicsVersion, tandem: Tandem.ROOT.createTandem( 'lensScreen' ) Index: main/joist/js/HomeScreenButton.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/HomeScreenButton.ts b/main/joist/js/HomeScreenButton.ts --- a/main/joist/js/HomeScreenButton.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/HomeScreenButton.ts (date 1661800856876) @@ -24,7 +24,7 @@ import Frame from './Frame.js'; import HomeScreenModel from './HomeScreenModel.js'; import joist from './joist.js'; -import Screen from './Screen.js'; +import Screen, { UnknownScreen } from './Screen.js'; import Utterance from '../../utterance-queue/js/Utterance.js'; // constants @@ -37,9 +37,9 @@ export type HomeScreenButtonOptions = SelfOptions & ParentOptions; class HomeScreenButton extends Voicing( VBox ) { - public readonly screen: Screen; + public readonly screen: UnknownScreen; - public constructor( screen: Screen, homeScreenModel: HomeScreenModel, providedOptions?: HomeScreenButtonOptions ) { + public constructor( screen: UnknownScreen, homeScreenModel: HomeScreenModel, providedOptions?: HomeScreenButtonOptions ) { const options = optionize()( { cursor: 'pointer', Index: main/geometric-optics/js/geometric-optics-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/geometric-optics/js/geometric-optics-main.ts b/main/geometric-optics/js/geometric-optics-main.ts --- a/main/geometric-optics/js/geometric-optics-main.ts (revision b7832067338cb8ed3dc00cf515667373ecd3d72d) +++ b/main/geometric-optics/js/geometric-optics-main.ts (date 1661800221755) @@ -11,7 +11,7 @@ import GOSim from './GOSim.js'; simLauncher.launch( () => { - const sim = new GOSim( geometricOpticsStrings[ 'geometric-optics' ].title, { + const sim = GOSim.create( geometricOpticsStrings[ 'geometric-optics' ].title, { isBasicsVersion: false, phetioDesigned: true } ); Index: main/joist/js/EngagementMetrics.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/EngagementMetrics.ts b/main/joist/js/EngagementMetrics.ts --- a/main/joist/js/EngagementMetrics.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/EngagementMetrics.ts (date 1661800779109) @@ -16,7 +16,7 @@ import Property from '../../axon/js/Property.js'; import IntentionalAny from '../../phet-core/js/types/IntentionalAny.js'; import joist from './joist.js'; -import Sim from './Sim.js'; +import Sim, { UnknownSim } from './Sim.js'; import TemporalCounter from './TemporalCounter.js'; ///////////////////////////////// @@ -71,7 +71,7 @@ private startTimestamp: number | null = null; // number, the timestamp of the start of the sim. - public constructor( sim: Sim ) { + public constructor( sim: UnknownSim ) { const dataStream = phet && phet.phetio && phet.phetio.dataStream; assert && assert( dataStream, 'cannot add dataStream listener because dataStream is not defined' ); Index: main/number-play/js/number-play-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/number-play/js/number-play-main.ts b/main/number-play/js/number-play-main.ts --- a/main/number-play/js/number-play-main.ts (revision 1462a16b629bf45dff11340806d116d6859b0958) +++ b/main/number-play/js/number-play-main.ts (date 1661801739588) @@ -21,7 +21,7 @@ import DerivedProperty from '../../axon/js/DerivedProperty.js'; import audioManager from '../../joist/js/audioManager.js'; import SpeechSynthesisAnnouncer from '../../utterance-queue/js/SpeechSynthesisAnnouncer.js'; -import Screen from '../../joist/js/Screen.js'; +import Screen, { UnknownScreen } from '../../joist/js/Screen.js'; import soundManager from '../../tambo/js/soundManager.js'; import NumberPlayModel from './common/model/NumberPlayModel.js'; @@ -89,7 +89,7 @@ // screen has its own control for the speech synthesis locale, so the locale for the browser tab needs to be updated // to match whenever the screen changes. if ( NumberPlayQueryParameters.secondLocale ) { - sim.selectedScreenProperty.lazyLink( ( screen: Screen ) => { + sim.selectedScreenProperty.lazyLink( ( screen: UnknownScreen ) => { if ( screen.model instanceof NumberPlayModel && numberPlaySpeechSynthesisAnnouncer.initialized && screen.model.isPrimaryLocaleProperty ) { Index: main/nitroglycerin/js/nitroglycerin-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/nitroglycerin/js/nitroglycerin-main.ts b/main/nitroglycerin/js/nitroglycerin-main.ts --- a/main/nitroglycerin/js/nitroglycerin-main.ts (revision 008b9987999a9d894360c10b5724bf95816dfbdc) +++ b/main/nitroglycerin/js/nitroglycerin-main.ts (date 1661801729849) @@ -23,21 +23,19 @@ simLauncher.launch( () => { - const screens = [ + const simOptions: SimOptions = { + credits: { + leadDesign: 'PhET' + } + }; + + const sim = new Sim( title, [ new Screen( () => new Model(), () => new MoleculesScreenView(), { name: 'Molecules', backgroundColorProperty: new Property( Color.grayColor( 90 ) ), tandem: Tandem.OPT_OUT - } ) ]; - - const simOptions: SimOptions = { - credits: { - leadDesign: 'PhET' - } - }; - - const sim = new Sim( title, screens, simOptions ); + } ) ], simOptions ); sim.start(); } ); \ No newline at end of file Index: main/build-a-nucleus/js/common/model/BANModel.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/build-a-nucleus/js/common/model/BANModel.ts b/main/build-a-nucleus/js/common/model/BANModel.ts --- a/main/build-a-nucleus/js/common/model/BANModel.ts (revision b9e709d83409da09444acf5a5811dc32657bc0c0) +++ b/main/build-a-nucleus/js/common/model/BANModel.ts (date 1661800393015) @@ -22,11 +22,12 @@ import ParticleType from '../view/ParticleType.js'; import BooleanProperty from '../../../../axon/js/BooleanProperty.js'; import Animation from '../../../../twixt/js/Animation.js'; +import TModel from '../../../../joist/js/TModel.js'; // types export type BANModelOptions = PickRequired; -class BANModel { +class BANModel implements TModel { // the stability of the nuclide public readonly isStableBooleanProperty: TReadOnlyProperty; Index: main/simula-rasa/js/simula-rasa-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/simula-rasa/js/simula-rasa-main.ts b/main/simula-rasa/js/simula-rasa-main.ts --- a/main/simula-rasa/js/simula-rasa-main.ts (revision d4fcef83b53e955b02cafd28c49b4713f326ff67) +++ b/main/simula-rasa/js/simula-rasa-main.ts (date 1661801826345) @@ -19,10 +19,6 @@ const title = simulaRasaStrings[ 'simula-rasa' ].title; - const screens = [ - new SimulaRasaScreen( { tandem: Tandem.ROOT.createTandem( 'simulaRasaScreen' ) } ) - ]; - const options: SimOptions = { //TODO fill in credits, all of these fields are optional, see joist.CreditsNode @@ -38,6 +34,8 @@ } }; - const sim = new Sim( title, screens, options ); + const sim = new Sim( title, [ + new SimulaRasaScreen( { tandem: Tandem.ROOT.createTandem( 'simulaRasaScreen' ) } ) + ], options ); sim.start(); } ); \ No newline at end of file Index: main/joist/js/Profiler.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/Profiler.ts b/main/joist/js/Profiler.ts --- a/main/joist/js/Profiler.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/Profiler.ts (date 1661801555578) @@ -31,7 +31,7 @@ import Utils from '../../dot/js/Utils.js'; import joist from './joist.js'; -import Sim from './Sim.js'; +import Sim, { UnknownSim } from './Sim.js'; // constants const FIELD_SEPARATOR = ' \u2014 '; // em dash, a long horizontal dash @@ -58,7 +58,7 @@ $( 'body' ).append( '
' ); } - public static start( sim: Sim ): void { + public static start( sim: UnknownSim ): void { const profiler = new Profiler(); sim.frameStartedEmitter.addListener( () => profiler.frameStarted() ); sim.frameEndedEmitter.addListener( () => profiler.frameEnded() ); Index: main/joist/js/Sim.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/Sim.ts b/main/joist/js/Sim.ts --- a/main/joist/js/Sim.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/Sim.ts (date 1661803205697) @@ -51,7 +51,7 @@ import PreferencesModel from './preferences/PreferencesModel.js'; import Profiler from './Profiler.js'; import QueryParametersWarningDialog from './QueryParametersWarningDialog.js'; -import Screen from './Screen.js'; +import Screen, { UnknownScreen, UnknownSimScreen } from './Screen.js'; import ScreenSelectionSoundGenerator from './ScreenSelectionSoundGenerator.js'; import ScreenshotGenerator from './ScreenshotGenerator.js'; import selectScreens from './selectScreens.js'; @@ -70,6 +70,8 @@ import Permutation from '../../dot/js/Permutation.js'; import ArrayIO from '../../tandem/js/types/ArrayIO.js'; import StringIO from '../../tandem/js/types/StringIO.js'; +import TModel from './TModel.js'; +import ScreenView from './ScreenView.js'; // constants const PROGRESS_BAR_WIDTH = 273; @@ -108,7 +110,14 @@ export type SimOptions = SelfOptions & PickOptional; -export default class Sim extends PhetioObject { +export type UnknownSim = Sim; + +export default class Sim extends PhetioObject { // (joist-internal) public readonly simNameProperty: TReadOnlyProperty; @@ -150,20 +159,20 @@ public readonly stepSimulationAction: PhetioAction<[ number ]>; // the ordered list of sim-specific screens that appear in this runtime of the sim - public readonly simScreens: Screen[]; + public readonly simScreens: UnknownScreen[]; // all screens that appear in the runtime of this sim, with the homeScreen first if it was created - public readonly screens: Screen[]; + public readonly screens: UnknownScreen[]; // the displayed name in the sim. This depends on what screens are shown this runtime (effected by query parameters). public readonly displayedSimNameProperty: TReadOnlyProperty; - public readonly selectedScreenProperty: Property; + public readonly selectedScreenProperty: Property | Screen | Screen | Screen | Screen | Screen | HomeScreen>; // true if all possible screens are present (order-independent) private readonly allScreensCreated: boolean; private availableScreensProperty!: Property; - public activeSimScreensProperty!: ReadOnlyProperty; + public activeSimScreensProperty!: ReadOnlyProperty<( Screen | Screen | Screen | Screen | Screen | Screen )[]>; // When the sim is active, scenery processes inputs and stepSimulation(dt) runs from the system clock. // Set to false for when the sim will be paused. @@ -257,7 +266,13 @@ * @param allSimScreens - the possible screens for the sim in order of declaration (does not include the home screen) * @param [providedOptions] - see below for options */ - public constructor( name: string | TReadOnlyProperty, allSimScreens: Screen[], providedOptions?: SimOptions ) { + public constructor( name: string | TReadOnlyProperty, allSimScreens: [ Screen ], providedOptions?: SimOptions ); + public constructor( name: string | TReadOnlyProperty, allSimScreens: [ Screen ] | [ Screen, Screen ], providedOptions?: SimOptions ); + public constructor( name: string | TReadOnlyProperty, allSimScreens: [ Screen ] | [ Screen, Screen ] | [ Screen, Screen, Screen ], providedOptions?: SimOptions ); + public constructor( name: string | TReadOnlyProperty, allSimScreens: [ Screen ] | [ Screen, Screen ] | [ Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen ], providedOptions?: SimOptions ); + public constructor( name: string | TReadOnlyProperty, allSimScreens: [ Screen ] | [ Screen, Screen ] | [ Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen, Screen ], providedOptions?: SimOptions ); + public constructor( name: string | TReadOnlyProperty, allSimScreens: [ Screen ] | [ Screen, Screen ] | [ Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen, Screen, Screen ], providedOptions?: SimOptions ); + public constructor( name: string | TReadOnlyProperty, allSimScreens: [ Screen ] | [ Screen, Screen ] | [ Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen, Screen ] | [ Screen, Screen, Screen, Screen, Screen, Screen ], providedOptions?: SimOptions ) { window.phetSplashScreenDownloadComplete(); @@ -478,7 +493,7 @@ const screensTandem = Tandem.GENERAL_MODEL.createTandem( 'screens' ); - const screenData = selectScreens( + const screenData = selectScreens( allSimScreens, phet.chipper.queryParameters.homeScreen, QueryStringMachine.containsKey( 'homeScreen' ), @@ -520,7 +535,7 @@ this.screens = screenData.screens; this.allScreensCreated = screenData.allScreensCreated; - this.selectedScreenProperty = new Property( screenData.initialScreen, { + this.selectedScreenProperty = new Property( screenData.initialScreen, { tandem: screensTandem.createTandem( 'selectedScreenProperty' ), phetioFeatured: true, phetioDocumentation: 'Determines which screen is selected in the simulation', @@ -729,7 +744,7 @@ animationFrameTimer.runOnNextTick( () => phet.joist.display.updateDisplay() ); } - private finishInit( screens: Screen[] ): void { + private finishInit( screens: UnknownScreen[] ): void { _.each( screens, screen => { screen.view.layerSplit = true; Index: main/joist/js/PhetMenu.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/PhetMenu.ts b/main/joist/js/PhetMenu.ts --- a/main/joist/js/PhetMenu.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/PhetMenu.ts (date 1661801548543) @@ -26,7 +26,7 @@ import joist from './joist.js'; import joistStrings from './joistStrings.js'; import ScreenshotGenerator from './ScreenshotGenerator.js'; -import Sim from './Sim.js'; +import Sim, { UnknownSim } from './Sim.js'; import updateCheck from './updateCheck.js'; import UpdateDialog from './UpdateDialog.js'; import UpdateState from './UpdateState.js'; @@ -63,7 +63,7 @@ private readonly disposePhetMenu: () => void; - public constructor( sim: Sim, providedOptions?: PhetMenuOptions ) { + public constructor( sim: UnknownSim, providedOptions?: PhetMenuOptions ) { // Only show certain features for PhET Sims, such as links to our website const isPhETBrand = phet.chipper.brand === 'phet'; Index: main/joist/js/joist-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/joist-main.ts b/main/joist/js/joist-main.ts --- a/main/joist/js/joist-main.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/joist-main.ts (date 1661801882975) @@ -28,7 +28,7 @@ simLauncher.launch( () => { - const screens = [ + new Sim( joistTitleString, [ new Screen( ( () => new DemoModel() ), ( () => new DialogsScreenView() ), { @@ -36,7 +36,5 @@ backgroundColorProperty: new Property( 'white' ), tandem: Tandem.OPT_OUT } ) - ]; - - new Sim( joistTitleString, screens, simOptions ).start(); + ], simOptions ).start(); } ); \ No newline at end of file Index: main/joist/js/A11yButtonsHBox.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/A11yButtonsHBox.ts b/main/joist/js/A11yButtonsHBox.ts --- a/main/joist/js/A11yButtonsHBox.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/A11yButtonsHBox.ts (date 1661800676839) @@ -14,7 +14,7 @@ import KeyboardHelpButton from './KeyboardHelpButton.js'; import NavigationBarAudioToggleButton from './NavigationBarAudioToggleButton.js'; import NavigationBarPreferencesButton from './preferences/NavigationBarPreferencesButton.js'; -import Sim from './Sim.js'; +import { UnknownSim } from './Sim.js'; import TReadOnlyProperty from '../../axon/js/TReadOnlyProperty.js'; import optionize, { EmptySelfOptions } from '../../phet-core/js/optionize.js'; import StrictOmit from '../../phet-core/js/types/StrictOmit.js'; @@ -24,7 +24,7 @@ class A11yButtonsHBox extends HBox { - public constructor( sim: Sim, backgroundColorProperty: TReadOnlyProperty, providedOptions?: A11yButtonsHBoxOptions ) { + public constructor( sim: UnknownSim, backgroundColorProperty: TReadOnlyProperty, providedOptions?: A11yButtonsHBoxOptions ) { const options = optionize()( { align: 'center', Index: main/joist/js/HomeScreen.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/HomeScreen.ts b/main/joist/js/HomeScreen.ts --- a/main/joist/js/HomeScreen.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/HomeScreen.ts (date 1661801307345) @@ -16,7 +16,9 @@ import HomeScreenView from './HomeScreenView.js'; import joist from './joist.js'; import joistStrings from './joistStrings.js'; -import Screen, { ScreenOptions } from './Screen.js'; +import Screen, { ScreenOptions, UnknownScreen } from './Screen.js'; +import TModel from './TModel.js'; +import ScreenView from './ScreenView.js'; // constants const homeString = joistStrings.a11y.home; @@ -32,9 +34,9 @@ public constructor( simNameProperty: TReadOnlyProperty, - getScreenProperty: () => Property, - simScreens: Screen[], - activeSimScreensProperty: ReadOnlyProperty, + getScreenProperty: () => Property, + simScreens: UnknownScreen[], + activeSimScreensProperty: ReadOnlyProperty, providedOptions: HomeScreenOptions ) { Index: main/joist/js/demo/DialogsScreenView.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/demo/DialogsScreenView.ts b/main/joist/js/demo/DialogsScreenView.ts --- a/main/joist/js/demo/DialogsScreenView.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/demo/DialogsScreenView.ts (date 1661800779116) @@ -14,20 +14,20 @@ import joist from '../joist.js'; import KeyboardHelpButton from '../KeyboardHelpButton.js'; import ScreenView from '../ScreenView.js'; -import Screen from '../Screen.js'; -import Sim from '../Sim.js'; +import Screen, { UnknownScreen } from '../Screen.js'; +import Sim, { UnknownSim } from '../Sim.js'; class DialogsScreenView extends ScreenView { public constructor() { super(); - const sim = phet.joist.sim as Sim; + const sim = phet.joist.sim as UnknownSim; const keyboardHelpDialogContent = new BasicActionsKeyboardHelpSection(); const keyboardHelpButton = new KeyboardHelpButton( - new Property( { keyboardHelpNode: keyboardHelpDialogContent } as unknown as Screen ), + new Property( { keyboardHelpNode: keyboardHelpDialogContent } as unknown as UnknownScreen ), sim.lookAndFeel.navigationBarFillProperty, { tandem: Tandem.GENERAL_VIEW.createTandem( 'keyboardHelpButton' ) } ); Index: main/joist/js/toolbar/Toolbar.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/toolbar/Toolbar.ts b/main/joist/js/toolbar/Toolbar.ts --- a/main/joist/js/toolbar/Toolbar.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/toolbar/Toolbar.ts (date 1661801698782) @@ -32,7 +32,7 @@ import VoicingToolbarAlertManager from './VoicingToolbarAlertManager.js'; import VoicingToolbarItem from './VoicingToolbarItem.js'; import LookAndFeel from '../LookAndFeel.js'; -import Screen from '../Screen.js'; +import Screen, { UnknownScreen } from '../Screen.js'; // constants const MAX_ANIMATION_SPEED = 250; // in view coordinates per second, assuming 60 fps @@ -91,7 +91,7 @@ private readonly contentMargin: number; private readonly disposeToolbar: () => void; - public constructor( enabledProperty: TReadOnlyProperty, selectedScreenProperty: TReadOnlyProperty, + public constructor( enabledProperty: TReadOnlyProperty, selectedScreenProperty: TReadOnlyProperty, lookAndFeel: LookAndFeel, providedOptions?: ToolbarOptions ) { const options = combineOptions( { Index: main/joist/js/Helper.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/Helper.ts b/main/joist/js/Helper.ts --- a/main/joist/js/Helper.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/Helper.ts (date 1661801155476) @@ -20,7 +20,7 @@ import AquaRadioButtonGroup from '../../sun/js/AquaRadioButtonGroup.js'; import Tandem from '../../tandem/js/Tandem.js'; import joist from './joist.js'; -import Sim from './Sim.js'; +import Sim, { UnknownSim } from './Sim.js'; import SimDisplay from './SimDisplay.js'; import BooleanProperty from '../../axon/js/BooleanProperty.js'; import Checkbox, { CheckboxOptions } from '../../sun/js/Checkbox.js'; @@ -61,7 +61,7 @@ }; export default class Helper { - private sim: Sim; + private sim: UnknownSim; private simDisplay: Display; private helperDisplay?: Display; @@ -123,7 +123,7 @@ // The pixel color under the pointer public colorProperty: TReadOnlyProperty; - public constructor( sim: Sim, simDisplay: SimDisplay ) { + public constructor( sim: UnknownSim, simDisplay: SimDisplay ) { // NOTE: Don't pause the sim, don't use foreign object rasterization (do the smarter instant approach) // NOTE: Inform about preserveDrawingBuffer query parameter @@ -860,7 +860,7 @@ // Singleton, lazily created so we don't slow down startup public static helper?: Helper; - public static initialize( sim: Sim, simDisplay: SimDisplay ): void { + public static initialize( sim: UnknownSim, simDisplay: SimDisplay ): void { // Ctrl + shift + H (will open the helper) document.addEventListener( 'keydown', ( event: KeyboardEvent ) => { if ( event.ctrlKey && event.key === 'H' ) { Index: main/quadrilateral/js/quadrilateral-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/quadrilateral/js/quadrilateral-main.ts b/main/quadrilateral/js/quadrilateral-main.ts --- a/main/quadrilateral/js/quadrilateral-main.ts (revision cf9b4377a2707984672d49ede787a92b3c724115) +++ b/main/quadrilateral/js/quadrilateral-main.ts (date 1661801942123) @@ -62,9 +62,8 @@ backgroundColorProperty: new ColorProperty( new Color( 'white' ) ), tandem: Tandem.ROOT.createTandem( 'calibrationDemoScreen' ) } ); - const simScreens = QuadrilateralQueryParameters.calibrationDemo ? [ quadrilateralScreen, calibrationDemoScreen ] : [ quadrilateralScreen ]; - const sim = new Sim( quadrilateralTitleString, simScreens, simOptions ); + const sim = new Sim( quadrilateralTitleString, QuadrilateralQueryParameters.calibrationDemo ? [ quadrilateralScreen, calibrationDemoScreen ] : [ quadrilateralScreen ], simOptions ); sim.start(); // @ts-ignore Index: main/scenery-phet/js/scenery-phet-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/scenery-phet/js/scenery-phet-main.ts b/main/scenery-phet/js/scenery-phet-main.ts --- a/main/scenery-phet/js/scenery-phet-main.ts (revision c380dd8234f656881282af5ad5c7c42431b37c7d) +++ b/main/scenery-phet/js/scenery-phet-main.ts (date 1661801750961) @@ -34,16 +34,14 @@ // Create and start sim simLauncher.launch( () => { - const screens = [ + const sim = new Sim( sceneryPhetStrings[ 'scenery-phet' ].title, [ new ButtonsScreen( Tandem.ROOT.createTandem( 'buttonsScreen' ) ), new ComponentsScreen( Tandem.ROOT.createTandem( 'componentsScreen' ) ), new DialogsScreen( Tandem.ROOT.createTandem( 'dialogsScreen' ) ), new KeyboardScreen( Tandem.ROOT.createTandem( 'keyboardScreen' ) ), new SlidersScreen( Tandem.ROOT.createTandem( 'slidersScreen' ) ), new SpinnersScreen( Tandem.ROOT.createTandem( 'spinnersScreen' ) ) - ]; - - const sim = new Sim( sceneryPhetStrings[ 'scenery-phet' ].title, screens, { + ], { credits: { leadDesign: 'PhET' }, Index: main/joist/js/selectScreensTests.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/selectScreensTests.ts b/main/joist/js/selectScreensTests.ts --- a/main/joist/js/selectScreensTests.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/selectScreensTests.ts (date 1661801253711) @@ -9,13 +9,13 @@ */ import selectScreens, { ScreenReturnType } from './selectScreens.js'; -import Screen from './Screen.js'; +import Screen, { UnknownScreen } from './Screen.js'; import HomeScreen from './HomeScreen.js'; // test screen constants. Since these are tests, it is actually more valuable to typecast instead of making these actual screens. -const a = 'a' as unknown as Screen; -const b = 'b' as unknown as Screen; -const c = 'c' as unknown as Screen; +const a = 'a' as unknown as UnknownScreen; +const b = 'b' as unknown as UnknownScreen; +const c = 'c' as unknown as UnknownScreen; const hs = 'hs' as unknown as HomeScreen; const getQueryParameterValues = ( queryString: string ) => { @@ -56,7 +56,7 @@ /** * Format the query string + all sim screens to uniquely identify the test. */ -const getDescription = ( queryString: string, allSimScreens: Screen[] ): string => `${queryString} ${JSON.stringify( allSimScreens )}`; +const getDescription = ( queryString: string, allSimScreens: UnknownScreen[] ): string => `${queryString} ${JSON.stringify( allSimScreens )}`; QUnit.test( 'valid selectScreens', async assert => { @@ -64,7 +64,7 @@ * Tests a valid combination of allSimScreens and screens-related query parameters, where the expectedResult should * equal the result returned from ScreenSelector.select */ - const testValidScreenSelector = ( queryString: string, allSimScreens: Screen[], expectedResult: ScreenReturnType ) => { + const testValidScreenSelector = ( queryString: string, allSimScreens: UnknownScreen[], expectedResult: ScreenReturnType ) => { const queryParameterValues = getQueryParameterValues( queryString ); const result = selectScreens( @@ -228,7 +228,7 @@ * Tests an invalid combination of allSimScreens and screens-related query parameters, where selectScreens should * throw an error */ - const testInvalidScreenSelector = ( queryString: string, allSimScreens: Screen[] ) => { + const testInvalidScreenSelector = ( queryString: string, allSimScreens: UnknownScreen[] ) => { const queryParameterValues = getQueryParameterValues( queryString ); const description = getDescription( queryString, allSimScreens ); Index: main/joist/js/Screen.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/Screen.ts b/main/joist/js/Screen.ts --- a/main/joist/js/Screen.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/Screen.ts (date 1661802291886) @@ -35,6 +35,8 @@ import Multilink from '../../axon/js/Multilink.js'; import TModel from './TModel.js'; import ReadOnlyProperty from '../../axon/js/ReadOnlyProperty.js'; +import HomeScreenModel from './HomeScreenModel.js'; +import HomeScreenView from './HomeScreenView.js'; const screenNamePatternString = joistStrings.a11y.screenNamePattern; const screenSimPatternString = joistStrings.a11y.screenSimPattern; @@ -69,12 +71,11 @@ }; export type ScreenOptions = SelfOptions & PhetioObjectOptions & PickRequired; -// Accept any subtype of TModel (defaults to supertype), and any subtype of ScreenView (defaults to subtype). -// @ts-ignore -type CreateView = ( model: M ) => V; +export type UnknownScreen = Screen | Screen; +export type UnknownSimScreen = Screen; // Parameterized on M=Model and V=View -class Screen extends PhetioObject { +class Screen extends PhetioObject { public backgroundColorProperty: Property | Property | Property; @@ -90,7 +91,7 @@ public readonly keyboardHelpNode: Node | null; // joist-internal public readonly pdomDisplayNameProperty: TReadOnlyProperty; private readonly createModel: () => M; - private readonly createView: CreateView; + private readonly createView: ( m: M ) => V; private _model: M | null; private _view: V | null; @@ -99,7 +100,7 @@ public static MINIMUM_NAVBAR_ICON_SIZE: Dimension2; public static ScreenIO: IOType; - public constructor( createModel: () => M, createView: CreateView, providedOptions: ScreenOptions ) { + public constructor( createModel: () => M, createView: ( m: M ) => V, providedOptions: ScreenOptions ) { const options = optionize()( { Index: main/wave-interference/js/diffraction/DiffractionScreen.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/wave-interference/js/diffraction/DiffractionScreen.ts b/main/wave-interference/js/diffraction/DiffractionScreen.ts --- a/main/wave-interference/js/diffraction/DiffractionScreen.ts (revision 19d020ea56b6652cfa0aefb9c2b0990fada24fbf) +++ b/main/wave-interference/js/diffraction/DiffractionScreen.ts (date 1661801054504) @@ -19,7 +19,7 @@ const screenDiffractionString = waveInterferenceStrings.screen.diffraction; -class DiffractionScreen extends Screen { +class DiffractionScreen extends Screen { public constructor() { const options = { Index: main/joist/js/NavigationBarScreenButton.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/NavigationBarScreenButton.ts b/main/joist/js/NavigationBarScreenButton.ts --- a/main/joist/js/NavigationBarScreenButton.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/NavigationBarScreenButton.ts (date 1661800884695) @@ -23,7 +23,7 @@ import Tandem from '../../tandem/js/Tandem.js'; import HighlightNode from './HighlightNode.js'; import joist from './joist.js'; -import Screen from './Screen.js'; +import Screen, { UnknownScreen } from './Screen.js'; // constants const HIGHLIGHT_SPACING = 4; @@ -38,7 +38,7 @@ class NavigationBarScreenButton extends Voicing( Node ) { private readonly buttonModel: PushButtonModel; - public readonly screen: Screen; + public readonly screen: UnknownScreen; /** * @param navigationBarFillProperty - the color of the navbar, as a string. @@ -48,8 +48,8 @@ * @param navBarHeight * @param [providedOptions] */ - public constructor( navigationBarFillProperty: TReadOnlyProperty, screenProperty: Property>, - screen: Screen, simScreenIndex: number, navBarHeight: number, + public constructor( navigationBarFillProperty: TReadOnlyProperty, screenProperty: Property, + screen: UnknownScreen, simScreenIndex: number, navBarHeight: number, providedOptions: NavigationBarScreenButtonOptions ) { assert && assert( screen.nameProperty.value, `name is required for screen ${simScreenIndex}` ); Index: main/wave-interference/js/interference/InterferenceScreen.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/wave-interference/js/interference/InterferenceScreen.ts b/main/wave-interference/js/interference/InterferenceScreen.ts --- a/main/wave-interference/js/interference/InterferenceScreen.ts (revision 19d020ea56b6652cfa0aefb9c2b0990fada24fbf) +++ b/main/wave-interference/js/interference/InterferenceScreen.ts (date 1661802221365) @@ -19,7 +19,7 @@ const screenInterferenceString = waveInterferenceStrings.screen.interference; -class InterferenceScreen extends Screen { +class InterferenceScreen extends Screen { /** * @param alignGroup - for aligning the control panels on the right side of the lattice Index: main/models-of-the-hydrogen-atom/js/models-of-the-hydrogen-atom-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/models-of-the-hydrogen-atom/js/models-of-the-hydrogen-atom-main.ts b/main/models-of-the-hydrogen-atom/js/models-of-the-hydrogen-atom-main.ts --- a/main/models-of-the-hydrogen-atom/js/models-of-the-hydrogen-atom-main.ts (revision f02cdfd6b7285e7a9daf5730ff46f950546ac9d2) +++ b/main/models-of-the-hydrogen-atom/js/models-of-the-hydrogen-atom-main.ts (date 1661801720228) @@ -18,11 +18,6 @@ const title = modelsOfTheHydrogenAtomStrings[ 'models-of-the-hydrogen-atom' ].title; - const screens = [ - new SpectraScreen( { tandem: Tandem.ROOT.createTandem( 'spectraScreen' ) } ), - new EnergyLevelsScreen( { tandem: Tandem.ROOT.createTandem( 'energyLevelsScreen' ) } ) - ]; - const options: SimOptions = { @@ -42,6 +37,9 @@ } }; - const sim = new Sim( title, screens, options ); + const sim = new Sim( title, [ + new SpectraScreen( { tandem: Tandem.ROOT.createTandem( 'spectraScreen' ) } ), + new EnergyLevelsScreen( { tandem: Tandem.ROOT.createTandem( 'energyLevelsScreen' ) } ) + ], options ); sim.start(); } ); \ No newline at end of file Index: main/wave-interference/js/common/BaseScreen.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/wave-interference/js/common/BaseScreen.ts b/main/wave-interference/js/common/BaseScreen.ts --- a/main/wave-interference/js/common/BaseScreen.ts (revision 19d020ea56b6652cfa0aefb9c2b0990fada24fbf) +++ b/main/wave-interference/js/common/BaseScreen.ts (date 1661802185458) @@ -22,7 +22,7 @@ }; export type BaseScreenOptions = SelfOptions & ScreenOptions; -class BaseScreen extends Screen { +class BaseScreen extends Screen { /** * @param alignGroup - for aligning the control panels on the right side of the lattice Index: main/joist/js/KeyboardHelpButton.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/KeyboardHelpButton.ts b/main/joist/js/KeyboardHelpButton.ts --- a/main/joist/js/KeyboardHelpButton.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/KeyboardHelpButton.ts (date 1661800868529) @@ -18,7 +18,7 @@ import JoistButton, { JoistButtonOptions } from './JoistButton.js'; import joistStrings from './joistStrings.js'; import KeyboardHelpDialog from './KeyboardHelpDialog.js'; -import Screen from './Screen.js'; +import Screen, { UnknownScreen } from './Screen.js'; import PickRequired from '../../phet-core/js/types/PickRequired.js'; import TReadOnlyProperty from '../../axon/js/TReadOnlyProperty.js'; @@ -37,7 +37,7 @@ * @param backgroundColorProperty * @param [providedOptions] */ - public constructor( screenProperty: Property, + public constructor( screenProperty: Property, backgroundColorProperty: TReadOnlyProperty, providedOptions: KeyboardHelpButtonOptions ) { Index: main/joist/js/NavigationBar.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/NavigationBar.ts b/main/joist/js/NavigationBar.ts --- a/main/joist/js/NavigationBar.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/NavigationBar.ts (date 1661801541138) @@ -38,10 +38,10 @@ import joistStrings from './joistStrings.js'; import NavigationBarScreenButton from './NavigationBarScreenButton.js'; import PhetButton from './PhetButton.js'; -import Sim from './Sim.js'; +import Sim, { UnknownSim } from './Sim.js'; import ReadOnlyProperty from '../../axon/js/ReadOnlyProperty.js'; import Bounds2 from '../../dot/js/Bounds2.js'; -import Screen from './Screen.js'; +import Screen, { UnknownScreen } from './Screen.js'; import BooleanProperty from '../../axon/js/BooleanProperty.js'; // constants @@ -71,7 +71,7 @@ private readonly localeNode!: Node; private readonly homeButton: HomeButton | null = null; // mutated if multiscreen sim - public constructor( sim: Sim, tandem: Tandem ) { + public constructor( sim: UnknownSim, tandem: Tandem ) { super(); @@ -245,7 +245,7 @@ } )!.width ); const maxScreenButtonHeight = _.maxBy( screenButtons, button => button.height )!.height; - const screenButtonMap = new Map, Node>(); + const screenButtonMap = new Map(); screenButtons.forEach( screenButton => { screenButtonMap.set( screenButton.screen, new AlignBox( screenButton, { excludeInvisibleChildrenFromBounds: true, Index: main/joist/js/selectScreens.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/selectScreens.ts b/main/joist/js/selectScreens.ts --- a/main/joist/js/selectScreens.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/selectScreens.ts (date 1661802865755) @@ -2,12 +2,14 @@ import joist from './joist.js'; import HomeScreen from './HomeScreen.js'; import Screen from './Screen.js'; +import ScreenView from './ScreenView.js'; +import TModel from './TModel.js'; -export type ScreenReturnType = { +export type ScreenReturnType = { homeScreen: HomeScreen | null; - initialScreen: Screen; - selectedSimScreens: Screen[]; - screens: Screen[]; + initialScreen: ( Screen | Screen | Screen | Screen | Screen | Screen | HomeScreen ); + selectedSimScreens: ( Screen | Screen | Screen | Screen | Screen | Screen )[]; + screens: ( Screen | Screen | Screen | Screen | Screen | Screen | HomeScreen )[]; allScreensCreated: boolean; }; @@ -29,19 +31,21 @@ * @param initialScreenQueryParameterProvided * @param screensQueryParameter - from phet.chipper.queryParameters.screens * @param screensQueryParameterProvided + * @param setupScreens * @param createHomeScreen * @returns - duck-typed for tests * @throws Error if incompatible data is provided */ -export default function selectScreens( allSimScreens: Screen[], - homeScreenQueryParameter: boolean, - homeScreenQueryParameterProvided: boolean, - initialScreenIndex: number, - initialScreenQueryParameterProvided: boolean, - screensQueryParameter: number[], - screensQueryParameterProvided: boolean, - setupScreens: ( screens: Screen[] ) => void, - createHomeScreen: ( screens: Screen[] ) => HomeScreen ): ScreenReturnType { +export default function selectScreens( + allSimScreens: ( Screen | Screen | Screen | Screen | Screen | Screen )[], + homeScreenQueryParameter: boolean, + homeScreenQueryParameterProvided: boolean, + initialScreenIndex: number, + initialScreenQueryParameterProvided: boolean, + screensQueryParameter: number[], + screensQueryParameterProvided: boolean, + setupScreens: ( screens: ( Screen | Screen | Screen | Screen | Screen | Screen )[] ) => void, + createHomeScreen: ( screens: ( Screen | Screen | Screen | Screen | Screen | Screen )[] ) => HomeScreen ): ScreenReturnType { if ( allSimScreens.length === 1 && homeScreenQueryParameterProvided && homeScreenQueryParameter ) { const errorMessage = 'cannot specify homeScreen=true for single-screen sims'; @@ -54,7 +58,7 @@ } // the ordered list of sim screens for this runtime - let selectedSimScreens = []; + let selectedSimScreens: ( Screen | Screen | Screen | Screen | Screen | Screen )[] = []; // If a subset of screens was specified with the `screens` query parameter, add them to selectedSimScreens. Otherwise, // use all of the available sim screens as the default. Note that if the value of `screens` did not pass validation @@ -119,7 +123,7 @@ assert && assert( false, errorMessage ); } - const screens = selectedSimScreens.slice(); + const screens: ( Screen | Screen | Screen | Screen | Screen | Screen | HomeScreen )[] = selectedSimScreens.slice(); let homeScreen = null; Index: main/joist/js/HomeScreenModel.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/HomeScreenModel.ts b/main/joist/js/HomeScreenModel.ts --- a/main/joist/js/HomeScreenModel.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/HomeScreenModel.ts (date 1661803080972) @@ -13,13 +13,15 @@ import IntentionalAny from '../../phet-core/js/types/IntentionalAny.js'; import Tandem from '../../tandem/js/Tandem.js'; import joist from './joist.js'; -import Screen from './Screen.js'; +import Screen, { UnknownScreen, UnknownSimScreen } from './Screen.js'; +import TModel from './TModel.js'; +import ScreenView from './ScreenView.js'; -class HomeScreenModel { - public simScreens: Screen[]; // screens in the simulations that are not the HomeScreen - public screenProperty: Property>; - public selectedScreenProperty: Property>; - public readonly activeSimScreensProperty: ReadOnlyProperty; +class HomeScreenModel implements TModel { + public simScreens: UnknownScreen[]; // screens in the simulations that are not the HomeScreen + public screenProperty: Property; + public selectedScreenProperty: Property | Screen | Screen | Screen | Screen | Screen>; + public readonly activeSimScreensProperty: ReadOnlyProperty; /** * @param screenProperty - the screen that is displayed to the user in the main area above the @@ -27,7 +29,7 @@ * @param simScreens * @param tandem */ - public constructor( screenProperty: Property>, simScreens: Screen[], activeSimScreensProperty: ReadOnlyProperty, tandem: Tandem ) { + public constructor( screenProperty: Property>, simScreens: Screen[], activeSimScreensProperty: ReadOnlyProperty, tandem: Tandem ) { this.simScreens = simScreens; this.screenProperty = screenProperty; Index: main/joist/js/thirdPartySupport/LegendsOfLearningSupport.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/thirdPartySupport/LegendsOfLearningSupport.ts b/main/joist/js/thirdPartySupport/LegendsOfLearningSupport.ts --- a/main/joist/js/thirdPartySupport/LegendsOfLearningSupport.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/thirdPartySupport/LegendsOfLearningSupport.ts (date 1661801691755) @@ -10,13 +10,13 @@ */ import joist from '../joist.js'; -import Sim from '../Sim.js'; +import { UnknownSim } from '../Sim.js'; class LegendsOfLearningSupport { - private readonly sim: Sim; + private readonly sim: UnknownSim; - public constructor( sim: Sim ) { + public constructor( sim: UnknownSim ) { this.sim = sim; // Respond to pause/resume commands from the Legends of Learning platform Index: main/geometric-optics-basics/js/geometric-optics-basics-main.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/geometric-optics-basics/js/geometric-optics-basics-main.ts b/main/geometric-optics-basics/js/geometric-optics-basics-main.ts --- a/main/geometric-optics-basics/js/geometric-optics-basics-main.ts (revision 9665271fa7c73ca1c1a5249f5578470237af7871) +++ b/main/geometric-optics-basics/js/geometric-optics-basics-main.ts (date 1661800421187) @@ -25,7 +25,7 @@ GOPreferences.add2FPointsCheckboxProperty.value = true; } - const sim = new GOSim( geometricOpticsBasicsStrings[ 'geometric-optics-basics' ].title, { + const sim = GOSim.create( geometricOpticsBasicsStrings[ 'geometric-optics-basics' ].title, { isBasicsVersion: true, phetioDesigned: true } ); Index: main/joist/js/ScreenSelectionSoundGenerator.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/joist/js/ScreenSelectionSoundGenerator.ts b/main/joist/js/ScreenSelectionSoundGenerator.ts --- a/main/joist/js/ScreenSelectionSoundGenerator.ts (revision 967a7adee65fefc039bffad000ae7726fc668b95) +++ b/main/joist/js/ScreenSelectionSoundGenerator.ts (date 1661801560684) @@ -11,12 +11,12 @@ import SoundClip, { SoundClipOptions } from '../../tambo/js/sound-generators/SoundClip.js'; import screenSelection_mp3 from '../sounds/screenSelection_mp3.js'; import joist from './joist.js'; -import Screen from './Screen.js'; +import Screen, { UnknownScreen } from './Screen.js'; import HomeScreen from './HomeScreen.js'; class ScreenSelectionSoundGenerator extends SoundClip { - public constructor( screenProperty: ReadOnlyProperty, homeScreen: HomeScreen | null, options?: SoundClipOptions ) { + public constructor( screenProperty: ReadOnlyProperty, homeScreen: HomeScreen | null, options?: SoundClipOptions ) { super( screenSelection_mp3, options ); Index: main/wave-interference/js/slits/SlitsScreen.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/wave-interference/js/slits/SlitsScreen.ts b/main/wave-interference/js/slits/SlitsScreen.ts --- a/main/wave-interference/js/slits/SlitsScreen.ts (revision 19d020ea56b6652cfa0aefb9c2b0990fada24fbf) +++ b/main/wave-interference/js/slits/SlitsScreen.ts (date 1661802199410) @@ -19,7 +19,7 @@ const screenSlitsString = waveInterferenceStrings.screen.slits; -class SlitsScreen extends Screen { +class SlitsScreen extends Screen { /** * @param alignGroup - for aligning the control panels on the right side of the lattice ```
pixelzoom commented 2 years ago

Discussed with @samreid. There's definitiely something odd going on here, or maybe something about TypeScript that we don't understand. But this seems like lower prioirty at the moment, so I recommend keepin the ts-ignore in Screen.ts, with a link to this issue. I.e.:

// @ts-ignore
type CreateView<out M extends TModel, V> = ( model: M ) => V;

@samreid please feel free to add additional notes that might be helpful in the future, then feel free to label as deferred.

samreid commented 2 years ago

Summarizing key points from my discussion with @pixelzoom:

This is a buggy type annotation because M is in the in location, and out is not correct. Removing the variance annotation like so: type CreateView<M extends TModel, V> = ( model: M ) => V; gives 73 errors in sim code like:

/Users/samreid/apache-document-root/main/geometric-optics/js/GOSim.ts(58,7): error TS2322: Type 'LensScreen' is not assignable to type 'Screen<TModel, ScreenView>'.
  Types of property 'createView' are incompatible.
    Type 'CreateView<LensModel, LensScreenView>' is not assignable to type 'CreateView<TModel, ScreenView>'.
      Type 'TModel' is missing the following properties from type 'LensModel': lens, reset, opticalObjectChoiceProperty, raysTypeProperty, and 12 more.

The problem is that Sim only knows that it has a TModel. So type checking createView(LensModel) fails because it doesn't know for sure that it is a LensModel. That is why the proposal in https://github.com/phetsims/joist/issues/783#issuecomment-1230800671 is to add more type information to Sim so it knows what the sim model and view types are. However, that patch leads to complex, verbose and redundant code like:

+export default function selectScreens<M1 extends TModel, V1 extends ScreenView, M2 extends TModel, V2 extends ScreenView, M3 extends TModel, V3 extends ScreenView, M4 extends TModel, V4 extends ScreenView, M5 extends TModel, V5 extends ScreenView, M6 extends TModel, V6 extends ScreenView>(
+  allSimScreens: ( Screen<M1, V1> | Screen<M2, V2> | Screen<M3, V3> | Screen<M4, V4> | Screen<M5, V5> | Screen<M6, V6> )[],
+  homeScreenQueryParameter: boolean,

And still has many type errors left to correct. Those changes are nice because it distinguishes between sim screens and the homescreen, but there is just too much overhead to justify it.

The other problem @pixelzoom and I observed is that Screen<TModel,ScreenView> could not accept a value of HomeScreen<HomeScreenModel,HomeScreenView> even though HomeScreenModel implements TModel and HomeScreenView extends ScreenView. This is likely because HomeScreenModel appears in the contravariant position and the view appears in the covariant position.

Our conclusion is that we think if we had better TypeScript expertise, there would be a more elegant solution, perhaps using a more array-like strategy for the Sim's individual Screen types. But for now, we would like to shut off this error with an isolated ts-ignore or any or something. The existing commit does that (in an awkward way).

samreid commented 2 years ago

I changed from the incorrect variance annotation to one IntentionalAny in the commit.