Repository: theatre-js/theatre Branch: main Commit: 6ea82b938ea4 Files: 996 Total size: 7.0 MB Directory structure: gitextract_zim1ay1i/ ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github/ │ ├── .yarnrc.publish.yml │ ├── actions/ │ │ └── yarn-nm-install/ │ │ └── action.yml │ └── workflows/ │ ├── ci.yml │ └── release-insiders.yml ├── .gitignore ├── .husky/ │ ├── .gitignore │ └── pre-commit ├── .prettierignore ├── .prettierrc ├── .yarn/ │ ├── plugins/ │ │ └── @yarnpkg/ │ │ ├── plugin-compat.cjs │ │ └── plugin-interactive-tools.cjs │ └── releases/ │ └── yarn-3.6.3.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Notes.md ├── README.md ├── compat-tests/ │ ├── .gitignore │ ├── README.md │ ├── fixtures/ │ │ ├── basic-react17/ │ │ │ ├── package/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── App/ │ │ │ │ │ │ ├── Scene.tsx │ │ │ │ │ │ └── useDrag.ts │ │ │ │ │ └── index.tsx │ │ │ │ └── tsconfig.json │ │ │ └── react17.compat-test.ts │ │ ├── basic-vite4/ │ │ │ ├── basic-vite4.compat-test.ts │ │ │ └── package/ │ │ │ ├── .gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── r3f-cra/ │ │ │ ├── cra.compat-test.ts │ │ │ └── package/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── public/ │ │ │ │ └── index.html │ │ │ └── src/ │ │ │ ├── App/ │ │ │ │ ├── App.tsx │ │ │ │ └── state.json │ │ │ └── index.tsx │ │ ├── r3f-next-latest/ │ │ │ ├── next-latest.compat-test.ts │ │ │ └── package/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── next-env.d.ts │ │ │ ├── package.json │ │ │ ├── pages/ │ │ │ │ └── index.js │ │ │ ├── src/ │ │ │ │ └── App/ │ │ │ │ ├── App.tsx │ │ │ │ └── state.json │ │ │ └── tsconfig.json │ │ ├── r3f-parcel1/ │ │ │ ├── package/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── App/ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ └── state.json │ │ │ │ │ └── index.tsx │ │ │ │ └── tsconfig.json │ │ │ └── parcel1.compat-test.ts │ │ ├── r3f-react18/ │ │ │ ├── package/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── App/ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ └── state.json │ │ │ │ │ └── index.js │ │ │ │ └── tsconfig.json │ │ │ └── react18.compat-test.ts │ │ ├── r3f-vite2/ │ │ │ ├── package/ │ │ │ │ ├── .gitignore │ │ │ │ ├── index.html │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── App/ │ │ │ │ │ │ ├── App.tsx │ │ │ │ │ │ └── state.json │ │ │ │ │ ├── main.tsx │ │ │ │ │ └── vite-env.d.ts │ │ │ │ ├── tsconfig.json │ │ │ │ ├── tsconfig.node.json │ │ │ │ └── vite.config.ts │ │ │ └── vite2.compat-test.ts │ │ └── r3f-vite4/ │ │ ├── package/ │ │ │ ├── .gitignore │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── App/ │ │ │ │ │ ├── App.tsx │ │ │ │ │ └── state.json │ │ │ │ ├── main.tsx │ │ │ │ └── vite-env.d.ts │ │ │ ├── tsconfig.json │ │ │ ├── tsconfig.node.json │ │ │ └── vite.config.ts │ │ └── vite4.compat-test.ts │ ├── integrity.compat-test.ts │ ├── package.json │ ├── scripts/ │ │ ├── clean.ts │ │ ├── install-fixtures.ts │ │ └── scripts.ts │ ├── tsconfig.json │ ├── utils/ │ │ └── testUtils.ts │ └── verdaccio.yml ├── credits.txt ├── devEnv/ │ ├── api-extractor-base.json │ ├── cli.ts │ ├── ensurePublishing.js │ ├── eslint/ │ │ └── rules/ │ │ └── no-relative-imports.js │ ├── getAliasesFromTsConfig.d.ts │ ├── getAliasesFromTsConfig.js │ ├── tsconfig.json │ ├── typecheck-all-projects/ │ │ └── tsconfig.all.json │ └── verify-docker-compose.test.ts ├── docker-compose.yml ├── examples/ │ ├── basic-dom/ │ │ ├── .gitignore │ │ ├── Scene.tsx │ │ ├── index.html │ │ ├── index.tsx │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── useDrag.ts │ ├── dom-cra/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ ├── manifest.json │ │ │ └── robots.txt │ │ ├── src/ │ │ │ ├── App.css │ │ │ ├── App.js │ │ │ ├── App.test.js │ │ │ ├── index.css │ │ │ ├── index.js │ │ │ ├── reportWebVitals.js │ │ │ ├── setupTests.js │ │ │ └── useDrag.js │ │ └── tsconfig.json │ └── r3f-cra/ │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public/ │ │ ├── index.html │ │ ├── manifest.json │ │ └── robots.txt │ ├── src/ │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── react-app-env.d.ts │ │ ├── reportWebVitals.js │ │ ├── setupTests.js │ │ └── useDrag.js │ └── tsconfig.json ├── jest.compat-tests.config.js ├── jest.config.js ├── knip.config.json ├── lerna.json ├── package.json ├── packages/ │ ├── app/ │ │ ├── .gitignore │ │ ├── .vscode/ │ │ │ └── settings.json │ │ ├── LICENSE │ │ ├── README.md │ │ ├── components.json │ │ ├── devEnv/ │ │ │ └── cli.ts │ │ ├── docker-compose.yml │ │ ├── next.config.js │ │ ├── package.json │ │ ├── postcss.config.js │ │ ├── prisma/ │ │ │ ├── .gitignore │ │ │ ├── migrations/ │ │ │ │ ├── 20230409133858_init/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230514190125_lib_auth/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230813123201_1/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230813131020_2/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230820072612_3/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230820093151_4/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230820095524_/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230827165303_5/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20230828173601_2/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231005010012_replace_auth0_with_next_auth/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231014131018_referesh_token_grace_period/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231017030424_add_teams_and_workspaces/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231017070342_add_teams_and_workspaces/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231127144216_/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231127153849_pkce/ │ │ │ │ │ └── migration.sql │ │ │ │ ├── 20231202190130_scopes/ │ │ │ │ │ └── migration.sql │ │ │ │ └── migration_lock.toml │ │ │ ├── schema.prisma │ │ │ └── seed.ts │ │ ├── src/ │ │ │ ├── .eslintrc.json │ │ │ ├── app/ │ │ │ │ ├── (protected)/ │ │ │ │ │ ├── _components/ │ │ │ │ │ │ ├── AccountSettingsPrompt.tsx │ │ │ │ │ │ ├── AccountSwitcher.tsx │ │ │ │ │ │ ├── EditWorkspaceDialog.tsx │ │ │ │ │ │ ├── InviteGuestsDialog.tsx │ │ │ │ │ │ ├── InviteTeamMembersPrompt.tsx │ │ │ │ │ │ ├── Navigation.tsx │ │ │ │ │ │ ├── NewWorkspaceDialog.tsx │ │ │ │ │ │ ├── NotificationsPopover.tsx │ │ │ │ │ │ ├── SessionProvider.tsx │ │ │ │ │ │ ├── TeamMembers.tsx │ │ │ │ │ │ ├── TeamSettingsPrompt.tsx │ │ │ │ │ │ └── WorkspaceThumb.tsx │ │ │ │ │ ├── account-setup/ │ │ │ │ │ │ ├── _components/ │ │ │ │ │ │ │ └── AccountSetupForm.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── recents/ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── shared-with-me/ │ │ │ │ │ │ ├── _components/ │ │ │ │ │ │ │ └── Shared.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── team/ │ │ │ │ │ │ └── [id]/ │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ └── Team.tsx │ │ │ │ │ │ ├── not-found.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── workspace/ │ │ │ │ │ └── [id]/ │ │ │ │ │ └── page.tsx │ │ │ │ ├── (public)/ │ │ │ │ │ ├── _components/ │ │ │ │ │ │ └── SignIn.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ └── somethingpublic/ │ │ │ │ │ └── page.tsx │ │ │ │ ├── _components/ │ │ │ │ │ └── Prompts.tsx │ │ │ │ ├── api/ │ │ │ │ │ ├── auth/ │ │ │ │ │ │ └── [...nextauth]/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── jwt-public-key/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── studio-auth/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── studio-trpc/ │ │ │ │ │ │ └── [trpc]/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── trpc/ │ │ │ │ │ └── [trpc]/ │ │ │ │ │ └── route.ts │ │ │ │ ├── global.css │ │ │ │ └── layout.tsx │ │ │ ├── env.d.ts │ │ │ ├── env.test.ts │ │ │ ├── envSchema.ts │ │ │ ├── prisma.ts │ │ │ ├── schemas/ │ │ │ │ └── index.ts │ │ │ ├── server/ │ │ │ │ ├── api/ │ │ │ │ │ ├── root.ts │ │ │ │ │ ├── routes/ │ │ │ │ │ │ ├── meRouter.ts │ │ │ │ │ │ ├── projectsRouter.ts │ │ │ │ │ │ ├── teamsRouter.ts │ │ │ │ │ │ └── workspaceRouter.ts │ │ │ │ │ └── trpc.ts │ │ │ │ └── studio-api/ │ │ │ │ ├── root.ts │ │ │ │ └── routes/ │ │ │ │ └── studioAuthRouter.ts │ │ │ ├── trpc/ │ │ │ │ ├── react.tsx │ │ │ │ ├── server.ts │ │ │ │ └── shared.ts │ │ │ ├── types.ts │ │ │ ├── ui/ │ │ │ │ ├── components/ │ │ │ │ │ └── ui/ │ │ │ │ │ ├── alert-dialog.tsx │ │ │ │ │ ├── avatar.tsx │ │ │ │ │ ├── button.tsx │ │ │ │ │ ├── context-menu.tsx │ │ │ │ │ ├── dialog.tsx │ │ │ │ │ ├── form.tsx │ │ │ │ │ ├── input.tsx │ │ │ │ │ ├── label.tsx │ │ │ │ │ ├── popover.tsx │ │ │ │ │ ├── select.tsx │ │ │ │ │ ├── separator.tsx │ │ │ │ │ ├── toast.tsx │ │ │ │ │ ├── toaster.tsx │ │ │ │ │ └── use-toast.ts │ │ │ │ └── lib/ │ │ │ │ └── utils.ts │ │ │ ├── useApi.ts │ │ │ └── utils/ │ │ │ ├── authUtils.ts │ │ │ └── index.ts │ │ ├── tailwind.config.js │ │ └── tsconfig.json │ ├── benchmarks/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── devEnv/ │ │ │ └── serveBenchmarks.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── Bench project 1.theatre-project-state.json │ │ │ ├── benchmarks-globals.d.ts │ │ │ ├── index.html │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── browser-bundles/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── devEnv/ │ │ │ ├── build.ts │ │ │ └── tsconfig.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── core-and-studio.ts │ │ │ └── core-only.ts │ │ ├── test.html │ │ └── tsconfig.json │ ├── core/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── devEnv/ │ │ │ ├── cli.ts │ │ │ ├── declarations-bundler/ │ │ │ │ ├── README.md │ │ │ │ └── rollup.config.js │ │ │ └── definedGlobals.ts │ │ ├── globals.d.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── .eslintrc.js │ │ │ ├── CoreBundle.ts │ │ │ ├── copyToClipboard.ts │ │ │ ├── coreExports.ts │ │ │ ├── coreTicker.ts │ │ │ ├── env.ts │ │ │ ├── envSchema.ts │ │ │ ├── globals.ts │ │ │ ├── index.ts │ │ │ ├── privateAPIs.ts │ │ │ ├── projects/ │ │ │ │ ├── Project.ts │ │ │ │ ├── TheatreProject.ts │ │ │ │ ├── initialiseProjectState.ts │ │ │ │ └── projectsSingleton.ts │ │ │ ├── propTypes/ │ │ │ │ ├── index.ts │ │ │ │ ├── internals.ts │ │ │ │ └── utils.ts │ │ │ ├── rafDrivers.ts │ │ │ ├── sequences/ │ │ │ │ ├── Sequence.ts │ │ │ │ ├── TheatreSequence.ts │ │ │ │ ├── interpolationTripleAtPosition.ts │ │ │ │ └── playbackControllers/ │ │ │ │ ├── AudioPlaybackController.ts │ │ │ │ └── DefaultPlaybackController.ts │ │ │ ├── sheetObjects/ │ │ │ │ ├── SheetObject.ts │ │ │ │ ├── SheetObjectTemplate.ts │ │ │ │ ├── TheatreSheetObject.ts │ │ │ │ ├── getOrderingOfPropTypeConfig.ts │ │ │ │ └── getPropDefaultsOfSheetObject.ts │ │ │ ├── sheets/ │ │ │ │ ├── Sheet.ts │ │ │ │ ├── SheetTemplate.ts │ │ │ │ └── TheatreSheet.ts │ │ │ ├── types/ │ │ │ │ ├── private/ │ │ │ │ │ ├── core.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── studio/ │ │ │ │ │ ├── ahistoric.ts │ │ │ │ │ ├── ephemeral.ts │ │ │ │ │ ├── historic.ts │ │ │ │ │ └── index.ts │ │ │ │ └── public.ts │ │ │ └── utils/ │ │ │ ├── ids.ts │ │ │ ├── instanceTypes.ts │ │ │ ├── keyframeUtils.ts │ │ │ └── notify.ts │ │ └── tsconfig.json │ ├── dataverse/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── api/ │ │ │ ├── .nojekyll │ │ │ ├── README.md │ │ │ ├── classes/ │ │ │ │ ├── Atom.md │ │ │ │ ├── PointerProxy.md │ │ │ │ └── Ticker.md │ │ │ ├── interfaces/ │ │ │ │ ├── PointerToPrismProvider.md │ │ │ │ └── Prism-1.md │ │ │ └── modules/ │ │ │ └── prism.md │ │ ├── devEnv/ │ │ │ ├── api-extractor.json │ │ │ ├── api-extractor.tsconfig.json │ │ │ ├── build.ts │ │ │ └── tsconfig.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── Atom.test.ts │ │ │ ├── Atom.ts │ │ │ ├── PointerProxy.ts │ │ │ ├── Ticker.test.ts │ │ │ ├── Ticker.ts │ │ │ ├── atom.typeTest.ts │ │ │ ├── dataverse.test.ts │ │ │ ├── index.ts │ │ │ ├── integration.test.ts │ │ │ ├── pointer.test.ts │ │ │ ├── pointer.ts │ │ │ ├── pointer.typeTest.ts │ │ │ ├── pointerToPrism.ts │ │ │ ├── prism/ │ │ │ │ ├── Interface.ts │ │ │ │ ├── asyncIterateOver.ts │ │ │ │ ├── discoveryMechanism.ts │ │ │ │ ├── iterateAndCountTicks.ts │ │ │ │ ├── iterateOver.test.ts │ │ │ │ ├── iterateOver.ts │ │ │ │ ├── prism.test.ts │ │ │ │ └── prism.ts │ │ │ ├── setupTestEnv.ts │ │ │ ├── types.ts │ │ │ ├── utils/ │ │ │ │ ├── Stack.ts │ │ │ │ ├── typeTestUtils.ts │ │ │ │ └── updateDeep.ts │ │ │ └── val.ts │ │ ├── tsconfig.json │ │ └── typedoc.json │ ├── dataverse-experiments/ │ │ ├── .babelrc.js │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── Atom.ts │ │ │ ├── Box.ts │ │ │ ├── Ticker.ts │ │ │ ├── atom.typeTest.ts │ │ │ ├── derivations/ │ │ │ │ ├── AbstractDerivation.ts │ │ │ │ ├── AbstractDerivation.typeTest.ts │ │ │ │ ├── ConstantDerivation.ts │ │ │ │ ├── DerivationEmitter.ts │ │ │ │ ├── DerivationFromSource.ts │ │ │ │ ├── Freshener.ts │ │ │ │ ├── IDerivation.ts │ │ │ │ ├── flatMap.ts │ │ │ │ ├── iterateAndCountTicks.ts │ │ │ │ ├── iterateOver.test.ts │ │ │ │ ├── iterateOver.ts │ │ │ │ ├── map.ts │ │ │ │ └── prism/ │ │ │ │ ├── discoveryMechanism.ts │ │ │ │ ├── prism.test.ts │ │ │ │ └── prism.ts │ │ │ ├── index.ts │ │ │ ├── integration.test.ts │ │ │ ├── pointer.ts │ │ │ ├── setupTestEnv.ts │ │ │ ├── types.ts │ │ │ └── utils/ │ │ │ ├── Emitter.test.ts │ │ │ ├── Emitter.ts │ │ │ ├── EventEmitter.ts │ │ │ ├── PathBasedReducer.ts │ │ │ ├── Stack.ts │ │ │ ├── Tappable.ts │ │ │ ├── typeTestUtils.ts │ │ │ └── updateDeep.ts │ │ └── tsconfig.json │ ├── playground/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── devEnv/ │ │ │ ├── .gitignore │ │ │ ├── playwright-report/ │ │ │ │ └── index.html │ │ │ └── playwright.config.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── .gitignore │ │ │ ├── home/ │ │ │ │ ├── ItemSectionWithPreviews.tsx │ │ │ │ ├── PlaygroundHeader.tsx │ │ │ │ └── PlaygroundPage.tsx │ │ │ ├── index.html │ │ │ ├── index.tsx │ │ │ ├── playground-globals.d.ts │ │ │ ├── public/ │ │ │ │ └── _redirects │ │ │ ├── shared/ │ │ │ │ ├── camera/ │ │ │ │ │ ├── App.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── scene.glb │ │ │ │ ├── custom-editable-components/ │ │ │ │ │ ├── App.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx │ │ │ │ ├── custom-raf-driver/ │ │ │ │ │ ├── App.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx │ │ │ │ ├── dom/ │ │ │ │ │ ├── Scene.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── useDrag.ts │ │ │ │ ├── dom-basic/ │ │ │ │ │ ├── Box3D.tsx │ │ │ │ │ ├── Scene.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx │ │ │ │ ├── file/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx │ │ │ │ ├── hello-world-extension/ │ │ │ │ │ ├── App.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx │ │ │ │ ├── hello-world-extension-dataverse/ │ │ │ │ │ ├── App.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx │ │ │ │ ├── hello-world-extension-using-sheet-object/ │ │ │ │ │ ├── App.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx │ │ │ │ ├── image/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx │ │ │ │ ├── instances/ │ │ │ │ │ ├── App.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── scene.glb │ │ │ │ ├── notifications/ │ │ │ │ │ ├── Scene.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── useDrag.ts │ │ │ │ ├── r3f-rocket/ │ │ │ │ │ ├── App.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── scene.glb │ │ │ │ ├── remote/ │ │ │ │ │ ├── Box3D.tsx │ │ │ │ │ ├── Remote.ts │ │ │ │ │ ├── RemoteController.ts │ │ │ │ │ ├── Scene.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx │ │ │ │ ├── sync/ │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx │ │ │ │ ├── theatric/ │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── state.json │ │ │ │ ├── three-basic/ │ │ │ │ │ ├── ThreeScene.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.tsx │ │ │ │ ├── turtle/ │ │ │ │ │ ├── TurtleRenderer.tsx │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── turtle.ts │ │ │ │ │ └── utils.ts │ │ │ │ └── utils/ │ │ │ │ └── useExtensionButton.ts │ │ │ └── tests/ │ │ │ ├── hot-reload-extension-pane/ │ │ │ │ ├── index.html │ │ │ │ ├── index.tsx │ │ │ │ └── test.e2e.ts │ │ │ ├── hot-reload-extension-toolbar/ │ │ │ │ ├── index.html │ │ │ │ ├── index.tsx │ │ │ │ └── test.e2e.ts │ │ │ ├── r3f-dynamic-tree/ │ │ │ │ ├── App.tsx │ │ │ │ ├── index.html │ │ │ │ ├── index.tsx │ │ │ │ └── test.e2e.ts │ │ │ ├── r3f-stress-test/ │ │ │ │ ├── App.tsx │ │ │ │ ├── SpaceStress.theatre-project-state.json │ │ │ │ ├── index.html │ │ │ │ ├── index.tsx │ │ │ │ └── scene.glb │ │ │ ├── reading-obj-value/ │ │ │ │ ├── index.html │ │ │ │ ├── index.tsx │ │ │ │ └── reading obj value.theatre-project-state.json │ │ │ └── setting-static-props/ │ │ │ ├── index.html │ │ │ ├── index.tsx │ │ │ └── test.e2e.ts │ │ ├── tsconfig.json │ │ └── vite.config.ts │ ├── r3f/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── devEnv/ │ │ │ ├── bundle.ts │ │ │ └── tsconfig.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── .eslintrc.js │ │ │ ├── drei/ │ │ │ │ ├── OrthographicCamera.tsx │ │ │ │ ├── PerspectiveCamera.tsx │ │ │ │ └── index.ts │ │ │ ├── extension/ │ │ │ │ ├── .eslintrc.js │ │ │ │ ├── InfiniteGridHelper/ │ │ │ │ │ └── index.ts │ │ │ │ ├── components/ │ │ │ │ │ ├── DragDetector.tsx │ │ │ │ │ ├── EditableProxy.tsx │ │ │ │ │ ├── OrbitControls/ │ │ │ │ │ │ ├── OrbitControlsImpl.ts │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── ProxyManager.tsx │ │ │ │ │ ├── ReferenceWindow/ │ │ │ │ │ │ ├── ReferenceWindow.tsx │ │ │ │ │ │ └── noiseImage.ts │ │ │ │ │ ├── SnapshotEditor.tsx │ │ │ │ │ ├── TransformControls.tsx │ │ │ │ │ ├── useRefAndState.ts │ │ │ │ │ ├── useSelected.tsx │ │ │ │ │ └── useSnapshotEditorCamera.tsx │ │ │ │ ├── editorStuff.ts │ │ │ │ ├── icons.tsx │ │ │ │ ├── index.ts │ │ │ │ └── useExtensionStore.ts │ │ │ ├── globals.d.ts │ │ │ ├── index.ts │ │ │ ├── main/ │ │ │ │ ├── .eslintrc.js │ │ │ │ ├── RafDriverProvider.tsx │ │ │ │ ├── RefreshSnapshot.tsx │ │ │ │ ├── SheetProvider.tsx │ │ │ │ ├── defaultEditableFactoryConfig.ts │ │ │ │ ├── editable.tsx │ │ │ │ ├── editableFactoryConfigUtils.ts │ │ │ │ ├── store.ts │ │ │ │ ├── useInvalidate.ts │ │ │ │ └── utils.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── react/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── devEnv/ │ │ │ ├── api-extractor.json │ │ │ ├── api-extractor.tsconfig.json │ │ │ ├── build.ts │ │ │ └── tsconfig.json │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── saaz/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── devEnv/ │ │ │ ├── api-extractor.json │ │ │ ├── api-extractor.tsconfig.json │ │ │ ├── build.ts │ │ │ └── tsconfig.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __snapshots__/ │ │ │ │ └── rogue.test.ts.snap │ │ │ ├── back/ │ │ │ │ ├── BackMemoryAdapter.ts │ │ │ │ ├── BackStorage.ts │ │ │ │ └── SaazBack.ts │ │ │ ├── front/ │ │ │ │ ├── FrontIdbAdapter.ts │ │ │ │ ├── FrontMemoryAdapter.ts │ │ │ │ ├── FrontStorage.ts │ │ │ │ └── SaazFront.ts │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ ├── rogue.test.ts │ │ │ ├── rogue.ts │ │ │ ├── shared/ │ │ │ │ ├── GeneratorSpy.ts │ │ │ │ ├── transactions.ts │ │ │ │ └── utils.ts │ │ │ └── types.ts │ │ ├── tsconfig.json │ │ └── typedoc.json │ ├── studio/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .npmignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── devEnv/ │ │ │ └── cli.ts │ │ ├── globals.d.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── .eslintrc.js │ │ │ ├── Auth.ts │ │ │ ├── IDBStorage.ts │ │ │ ├── PaneManager.ts │ │ │ ├── Scrub.ts │ │ │ ├── Storno/ │ │ │ │ └── Storno.ts │ │ │ ├── Studio.ts │ │ │ ├── StudioBundle.ts │ │ │ ├── StudioStore/ │ │ │ │ ├── StudioStore.ts │ │ │ │ ├── createTransactionPrivateApi.ts │ │ │ │ └── generateDiskStateRevision.ts │ │ │ ├── SyncStore/ │ │ │ │ ├── AppLink.ts │ │ │ │ ├── SyncServerLink.ts │ │ │ │ ├── enhancedTrpcWsClient.ts │ │ │ │ └── utils.ts │ │ │ ├── TheatreStudio.ts │ │ │ ├── UI/ │ │ │ │ ├── UI.ts │ │ │ │ └── UINonSSRBits.ts │ │ │ ├── UIRoot/ │ │ │ │ ├── PanelsRoot.tsx │ │ │ │ ├── PointerCapturing.tsx │ │ │ │ ├── ProvideTheme.tsx │ │ │ │ ├── UIRoot.tsx │ │ │ │ └── useKeyboardShortcuts.ts │ │ │ ├── checkForUpdates.ts │ │ │ ├── css.tsx │ │ │ ├── env.ts │ │ │ ├── getStudio.ts │ │ │ ├── index.ts │ │ │ ├── integration-tests/ │ │ │ │ ├── Sequence.test.ts │ │ │ │ ├── SheetObject.test.ts │ │ │ │ ├── SheetObjectTemplate.test.ts │ │ │ │ ├── setupIntegrationTestEnv.ts │ │ │ │ └── testUtils.ts │ │ │ ├── notify.tsx │ │ │ ├── panels/ │ │ │ │ ├── BasePanel/ │ │ │ │ │ ├── BasePanel.tsx │ │ │ │ │ ├── ExtensionPaneWrapper.tsx │ │ │ │ │ ├── PanelDragZone.tsx │ │ │ │ │ ├── PanelResizeHandle.tsx │ │ │ │ │ ├── PanelResizers.tsx │ │ │ │ │ ├── PanelWrapper.tsx │ │ │ │ │ └── common.tsx │ │ │ │ ├── DetailPanel/ │ │ │ │ │ ├── DetailPanel.tsx │ │ │ │ │ ├── DeterminePropEditorForDetail/ │ │ │ │ │ │ ├── DetailCompoundPropEditor.tsx │ │ │ │ │ │ ├── DetailSimplePropEditor.tsx │ │ │ │ │ │ ├── SingleRowPropEditor.tsx │ │ │ │ │ │ ├── collapsedMap.tsx │ │ │ │ │ │ ├── getDetailRowHighlightBackground.tsx │ │ │ │ │ │ └── rowIndentationFormulaCSS.tsx │ │ │ │ │ ├── DeterminePropEditorForDetail.tsx │ │ │ │ │ ├── EmptyState.tsx │ │ │ │ │ ├── ObjectDetails.tsx │ │ │ │ │ ├── ProjectDetails/ │ │ │ │ │ │ └── StateConflictRow.tsx │ │ │ │ │ └── ProjectDetails.tsx │ │ │ │ ├── OutlinePanel/ │ │ │ │ │ ├── BaseItem.tsx │ │ │ │ │ ├── ObjectsList/ │ │ │ │ │ │ ├── ObjectItem.tsx │ │ │ │ │ │ └── ObjectsList.tsx │ │ │ │ │ ├── OutlinePanel.tsx │ │ │ │ │ ├── ProjectsList/ │ │ │ │ │ │ ├── ProjectListItem.tsx │ │ │ │ │ │ └── ProjectsList.tsx │ │ │ │ │ ├── SheetsList/ │ │ │ │ │ │ ├── SheetInstanceItem.tsx │ │ │ │ │ │ ├── SheetItem.tsx │ │ │ │ │ │ └── SheetsList.tsx │ │ │ │ │ └── outlinePanelUtils.ts │ │ │ │ └── SequenceEditorPanel/ │ │ │ │ ├── AGGREGATE_COPY_PASTE.md │ │ │ │ ├── DopeSheet/ │ │ │ │ │ ├── DopeSheet.tsx │ │ │ │ │ ├── Left/ │ │ │ │ │ │ ├── AnyCompositeRow.tsx │ │ │ │ │ │ ├── Left.tsx │ │ │ │ │ │ ├── PrimitivePropRow.tsx │ │ │ │ │ │ ├── PropWithChildrenRow.tsx │ │ │ │ │ │ ├── SheetObjectRow.tsx │ │ │ │ │ │ ├── SheetRow.tsx │ │ │ │ │ │ └── usePropHighlightMouseEnter.tsx │ │ │ │ │ ├── Right/ │ │ │ │ │ │ ├── AggregatedKeyframeTrack/ │ │ │ │ │ │ │ ├── AggregateKeyframeEditor/ │ │ │ │ │ │ │ │ ├── AggregateKeyframeConnector.tsx │ │ │ │ │ │ │ │ ├── AggregateKeyframeDot.tsx │ │ │ │ │ │ │ │ ├── AggregateKeyframeEditor.tsx │ │ │ │ │ │ │ │ ├── AggregateKeyframeVisualDot.tsx │ │ │ │ │ │ │ │ ├── iif.tsx │ │ │ │ │ │ │ │ └── useAggregateKeyframeEditorUtils.tsx │ │ │ │ │ │ │ └── AggregatedKeyframeTrack.tsx │ │ │ │ │ │ ├── BasicKeyframedTrack/ │ │ │ │ │ │ │ ├── BasicKeyframedTrack.tsx │ │ │ │ │ │ │ └── KeyframeEditor/ │ │ │ │ │ │ │ ├── BasicKeyframeConnector.tsx │ │ │ │ │ │ │ ├── CurveEditorPopover/ │ │ │ │ │ │ │ │ ├── CurveEditorPopover.tsx │ │ │ │ │ │ │ │ ├── CurveSegmentEditor.tsx │ │ │ │ │ │ │ │ ├── EasingOption.tsx │ │ │ │ │ │ │ │ ├── SVGCurveSegment.tsx │ │ │ │ │ │ │ │ ├── colors.ts │ │ │ │ │ │ │ │ ├── shared.ts │ │ │ │ │ │ │ │ ├── useFreezableMemo.ts │ │ │ │ │ │ │ │ └── useUIOptionGrid.tsx │ │ │ │ │ │ │ ├── DeterminePropEditorForSingleKeyframe.tsx │ │ │ │ │ │ │ ├── SingleKeyframeDot.tsx │ │ │ │ │ │ │ ├── SingleKeyframeEditor.tsx │ │ │ │ │ │ │ ├── useSingleKeyframeInlineEditorPopover.tsx │ │ │ │ │ │ │ └── useTempTransactionEditingTools.tsx │ │ │ │ │ │ ├── DopeSheetBackground.tsx │ │ │ │ │ │ ├── DopeSheetSelectionView.tsx │ │ │ │ │ │ ├── FocusRangeCurtains.tsx │ │ │ │ │ │ ├── HorizontallyScrollableArea.tsx │ │ │ │ │ │ ├── KeyframeSnapTarget.tsx │ │ │ │ │ │ ├── LengthIndicator/ │ │ │ │ │ │ │ ├── LengthEditorPopover.tsx │ │ │ │ │ │ │ └── LengthIndicator.tsx │ │ │ │ │ │ ├── PrimitivePropRow.tsx │ │ │ │ │ │ ├── PropWithChildrenRow.tsx │ │ │ │ │ │ ├── Right.tsx │ │ │ │ │ │ ├── Row.tsx │ │ │ │ │ │ ├── SheetObjectRow.tsx │ │ │ │ │ │ ├── SheetRow.tsx │ │ │ │ │ │ ├── collectAggregateKeyframes.tsx │ │ │ │ │ │ └── keyframeRowUI/ │ │ │ │ │ │ └── ConnectorLine.tsx │ │ │ │ │ ├── selections.ts │ │ │ │ │ └── setCollapsedSheetObjectOrCompoundProp.tsx │ │ │ │ ├── FrameGrid/ │ │ │ │ │ ├── FrameGrid.tsx │ │ │ │ │ ├── StampsGrid.tsx │ │ │ │ │ └── createGrid.ts │ │ │ │ ├── FrameStampPositionProvider.tsx │ │ │ │ ├── GraphEditor/ │ │ │ │ │ ├── BasicKeyframedTrack/ │ │ │ │ │ │ ├── BasicKeyframedTrack.tsx │ │ │ │ │ │ └── KeyframeEditor/ │ │ │ │ │ │ ├── Curve.tsx │ │ │ │ │ │ ├── CurveHandle.tsx │ │ │ │ │ │ ├── GraphEditorDotNonScalar.tsx │ │ │ │ │ │ ├── GraphEditorDotScalar.tsx │ │ │ │ │ │ ├── GraphEditorNonScalarDash.tsx │ │ │ │ │ │ └── KeyframeEditor.tsx │ │ │ │ │ ├── GraphEditor.tsx │ │ │ │ │ └── PrimitivePropGraph.tsx │ │ │ │ ├── GraphEditorToggle.tsx │ │ │ │ ├── RightOverlay/ │ │ │ │ │ ├── DopeSnap.tsx │ │ │ │ │ ├── DopeSnapHitZoneUI.tsx │ │ │ │ │ ├── FocusRangeZone/ │ │ │ │ │ │ ├── FocusRangeStrip.tsx │ │ │ │ │ │ ├── FocusRangeThumb.tsx │ │ │ │ │ │ └── FocusRangeZone.tsx │ │ │ │ │ ├── FrameStamp.tsx │ │ │ │ │ ├── HorizontalScrollbar.tsx │ │ │ │ │ ├── Markers/ │ │ │ │ │ │ ├── MarkerDot.tsx │ │ │ │ │ │ ├── MarkerEditorPopover.tsx │ │ │ │ │ │ └── Markers.tsx │ │ │ │ │ ├── Playhead.tsx │ │ │ │ │ ├── PlayheadPositionPopover.tsx │ │ │ │ │ ├── RightOverlay.tsx │ │ │ │ │ └── TopStrip.tsx │ │ │ │ ├── SequenceEditorPanel.tsx │ │ │ │ ├── VerticalScrollContainer.tsx │ │ │ │ ├── layout/ │ │ │ │ │ ├── layout.ts │ │ │ │ │ └── tree.ts │ │ │ │ └── whatPropIsHighlighted.ts │ │ │ ├── propEditors/ │ │ │ │ ├── DefaultValueIndicator.tsx │ │ │ │ ├── NextPrevKeyframeCursors.tsx │ │ │ │ ├── getNearbyKeyframesOfTrack.tsx │ │ │ │ ├── simpleEditors/ │ │ │ │ │ ├── BooleanPropEditor.tsx │ │ │ │ │ ├── FilePropEditor.tsx │ │ │ │ │ ├── ISimplePropEditorReactProps.ts │ │ │ │ │ ├── ImagePropEditor.tsx │ │ │ │ │ ├── NumberPropEditor.tsx │ │ │ │ │ ├── RgbaPropEditor.tsx │ │ │ │ │ ├── StringLiteralPropEditor.tsx │ │ │ │ │ ├── StringPropEditor.tsx │ │ │ │ │ └── simplePropEditorByPropType.ts │ │ │ │ ├── useEditingToolsForCompoundProp.tsx │ │ │ │ ├── useEditingToolsForSimpleProp.tsx │ │ │ │ └── utils/ │ │ │ │ ├── IEditingTools.tsx │ │ │ │ ├── PropConfigForType.ts │ │ │ │ ├── getPropTypeByPointer.tsx │ │ │ │ └── propNameTextCSS.tsx │ │ │ ├── selectors.ts │ │ │ ├── toolbars/ │ │ │ │ ├── ExtensionToolbar/ │ │ │ │ │ ├── ExtensionToolbar.tsx │ │ │ │ │ ├── Toolset.tsx │ │ │ │ │ └── tools/ │ │ │ │ │ ├── ExtensionFlyoutMenu.tsx │ │ │ │ │ ├── IconButton.tsx │ │ │ │ │ └── Switch.tsx │ │ │ │ ├── GlobalToolbar/ │ │ │ │ │ ├── GlobalToolbar.tsx │ │ │ │ │ ├── LeftStrip/ │ │ │ │ │ │ ├── AppButton/ │ │ │ │ │ │ │ └── AppButton.tsx │ │ │ │ │ │ ├── LeftStrip.tsx │ │ │ │ │ │ └── WorkspaceButton/ │ │ │ │ │ │ └── WorkspaceButton.tsx │ │ │ │ │ ├── MoreMenu/ │ │ │ │ │ │ └── MoreMenu.tsx │ │ │ │ │ ├── RightStrip/ │ │ │ │ │ │ ├── AuthState/ │ │ │ │ │ │ │ ├── AuthState.tsx │ │ │ │ │ │ │ ├── Avatar.tsx │ │ │ │ │ │ │ ├── Unauthorized.tsx │ │ │ │ │ │ │ └── shared.tsx │ │ │ │ │ │ └── RightStrip.tsx │ │ │ │ │ └── globalToolbarHooks.tsx │ │ │ │ └── PinButton.tsx │ │ │ ├── uiComponents/ │ │ │ │ ├── DetailPanelButton.tsx │ │ │ │ ├── ExternalLink.tsx │ │ │ │ ├── PointerEventsHandler.tsx │ │ │ │ ├── Popover/ │ │ │ │ │ ├── ArrowContext.tsx │ │ │ │ │ ├── BasicPopover.tsx │ │ │ │ │ ├── BasicTooltip.tsx │ │ │ │ │ ├── ErrorTooltip.tsx │ │ │ │ │ ├── MinimalTooltip.tsx │ │ │ │ │ ├── PopoverArrow.tsx │ │ │ │ │ ├── PopoverPositioner.tsx │ │ │ │ │ ├── SimplePopover.tsx │ │ │ │ │ ├── TooltipContext.tsx │ │ │ │ │ ├── TooltipWithIcon.tsx │ │ │ │ │ ├── usePopover.tsx │ │ │ │ │ ├── usePopoverPosition.ts │ │ │ │ │ └── useTooltip.tsx │ │ │ │ ├── RoomToClick.tsx │ │ │ │ ├── SVGIcon.tsx │ │ │ │ ├── ShowMousePosition.tsx │ │ │ │ ├── chordial/ │ │ │ │ │ ├── ChordialOverlay.tsx │ │ │ │ │ ├── ContextOverlay.tsx │ │ │ │ │ ├── PopoverOverlay.tsx │ │ │ │ │ ├── TooltipOverlay.tsx │ │ │ │ │ ├── chordialInternals.ts │ │ │ │ │ ├── contextActor.ts │ │ │ │ │ ├── gestureActor.ts │ │ │ │ │ ├── hoverActor.ts │ │ │ │ │ ├── mousedownActor.ts │ │ │ │ │ ├── popoverActor.ts │ │ │ │ │ ├── tooltipActor.ts │ │ │ │ │ └── useChodrial.tsx │ │ │ │ ├── colorPicker/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── EditingProvider.tsx │ │ │ │ │ │ ├── RgbaColorPicker.tsx │ │ │ │ │ │ └── common/ │ │ │ │ │ │ ├── Alpha.tsx │ │ │ │ │ │ ├── AlphaColorPicker.tsx │ │ │ │ │ │ ├── Hue.tsx │ │ │ │ │ │ ├── Interactive.tsx │ │ │ │ │ │ ├── Pointer.tsx │ │ │ │ │ │ └── Saturation.tsx │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ ├── useColorManipulation.ts │ │ │ │ │ │ ├── useEventCallback.ts │ │ │ │ │ │ └── useIsomorphicLayoutEffect.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils/ │ │ │ │ │ ├── clamp.ts │ │ │ │ │ ├── compare.ts │ │ │ │ │ ├── convert.ts │ │ │ │ │ ├── round.ts │ │ │ │ │ └── validate.ts │ │ │ │ ├── createCursorLock.ts │ │ │ │ ├── form/ │ │ │ │ │ ├── BasicCheckbox.tsx │ │ │ │ │ ├── BasicNumberInput.tsx │ │ │ │ │ ├── BasicSelect.tsx │ │ │ │ │ ├── BasicStringInput.tsx │ │ │ │ │ └── BasicSwitch.tsx │ │ │ │ ├── icons/ │ │ │ │ │ ├── AddImage.tsx │ │ │ │ │ ├── ArrowClockwise.tsx │ │ │ │ │ ├── ArrowsOutCardinal.tsx │ │ │ │ │ ├── Bell.tsx │ │ │ │ │ ├── Camera.tsx │ │ │ │ │ ├── ChevronDown.tsx │ │ │ │ │ ├── ChevronLeft.tsx │ │ │ │ │ ├── ChevronRight.tsx │ │ │ │ │ ├── Cube.tsx │ │ │ │ │ ├── CubeFull.tsx │ │ │ │ │ ├── CubeHalf.tsx │ │ │ │ │ ├── CubeRendered.tsx │ │ │ │ │ ├── Details.tsx │ │ │ │ │ ├── DoubleChevronLeft.tsx │ │ │ │ │ ├── DoubleChevronRight.tsx │ │ │ │ │ ├── DropdownChevron.tsx │ │ │ │ │ ├── Ellipsis.tsx │ │ │ │ │ ├── EllipsisFill.tsx │ │ │ │ │ ├── GlobeSimple.tsx │ │ │ │ │ ├── Outline.tsx │ │ │ │ │ ├── Package.tsx │ │ │ │ │ ├── Resize.tsx │ │ │ │ │ ├── Trash.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── isSafari.ts │ │ │ │ ├── onPointerOutside.ts │ │ │ │ ├── selects/ │ │ │ │ │ └── BasicSelect.tsx │ │ │ │ ├── simpleContextMenu/ │ │ │ │ │ ├── ContextMenu/ │ │ │ │ │ │ ├── BaseMenu.tsx │ │ │ │ │ │ ├── ContextMenu.tsx │ │ │ │ │ │ └── Item.tsx │ │ │ │ │ ├── useContextMenu.tsx │ │ │ │ │ └── useMenu.tsx │ │ │ │ ├── toolbar/ │ │ │ │ │ ├── ToolbarButton.tsx │ │ │ │ │ ├── ToolbarDropdownSelect.tsx │ │ │ │ │ ├── ToolbarIconButton.tsx │ │ │ │ │ ├── ToolbarSwitchSelect.tsx │ │ │ │ │ └── ToolbarSwitchSelectContainer.ts │ │ │ │ ├── useBoundingClientRect.ts │ │ │ │ ├── useDebugRefreshEvery.tsx │ │ │ │ ├── useDrag.ts │ │ │ │ ├── useHotspot.ts │ │ │ │ ├── useHover.ts │ │ │ │ ├── useHoverWithoutDescendants.ts │ │ │ │ ├── useKeyDown.ts │ │ │ │ ├── useKeyDownCallback.tsx │ │ │ │ ├── useLockSet.ts │ │ │ │ ├── useLogger.tsx │ │ │ │ ├── useOnClickOutside.ts │ │ │ │ ├── useOnKeyDown.ts │ │ │ │ ├── usePresence.tsx │ │ │ │ └── useValToAtom.ts │ │ │ └── utils/ │ │ │ ├── absoluteDims.tsx │ │ │ ├── assets.ts │ │ │ ├── contextualWebComponents.tsx │ │ │ ├── createStudioSheetItemKey.ts │ │ │ ├── derive-utils.tsx │ │ │ ├── ids.ts │ │ │ ├── invariant.ts │ │ │ ├── mousePositionD.ts │ │ │ ├── renderInPortalInContext.tsx │ │ │ ├── selectClosestHTMLAncestor.ts │ │ │ └── useRefAndState.ts │ │ └── tsconfig.json │ ├── sync-server/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── devEnv/ │ │ │ └── cli.ts │ │ ├── docker-compose.yml │ │ ├── env.d.ts │ │ ├── package.json │ │ ├── prisma/ │ │ │ ├── .gitignore │ │ │ ├── migrations/ │ │ │ │ ├── 20230409105406_init/ │ │ │ │ │ └── migration.sql │ │ │ │ └── migration_lock.toml │ │ │ ├── schema.prisma │ │ │ └── seed.ts │ │ ├── src/ │ │ │ ├── appClient.ts │ │ │ ├── env.test.ts │ │ │ ├── env.ts │ │ │ ├── envSchema.ts │ │ │ ├── index.ts │ │ │ ├── prisma.ts │ │ │ ├── saaz/ │ │ │ │ └── index.ts │ │ │ ├── state/ │ │ │ │ └── schema.ts │ │ │ ├── trpc/ │ │ │ │ ├── index.ts │ │ │ │ └── routes/ │ │ │ │ ├── index.ts │ │ │ │ └── projectStateRouter.ts │ │ │ ├── types.ts │ │ │ └── utils/ │ │ │ └── authUtils.ts │ │ └── tsconfig.json │ ├── theatric/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── devEnv/ │ │ │ ├── api-extractor.json │ │ │ ├── api-extractor.tsconfig.json │ │ │ ├── build.ts │ │ │ └── tsconfig.json │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ └── utils/ │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── devEnv/ │ │ ├── api-extractor.json │ │ ├── api-extractor.tsconfig.json │ │ ├── build.ts │ │ └── tsconfig.json │ ├── package.json │ ├── src/ │ │ ├── Nominal.ts │ │ ├── PointableSet.ts │ │ ├── SimpleCache.ts │ │ ├── WeakMapWithGetOrSet.ts │ │ ├── _logger/ │ │ │ ├── logger.shouldLog.test.ts │ │ │ ├── logger.test-helpers.ts │ │ │ ├── logger.test.ts │ │ │ └── logger.ts │ │ ├── basicFSM.ts │ │ ├── color.ts │ │ ├── deepEqual.ts │ │ ├── deepMergeWithCache.ts │ │ ├── defer.ts │ │ ├── delay.ts │ │ ├── devStringify.ts │ │ ├── didYouMean.ts │ │ ├── ellipsify.ts │ │ ├── errors.ts │ │ ├── getDeep.ts │ │ ├── globals.d.ts │ │ ├── index.ts │ │ ├── isMac.ts │ │ ├── keyboardUtils.ts │ │ ├── logger.ts │ │ ├── memoizeFn.ts │ │ ├── minimalOverride.ts │ │ ├── mutableSetDeep.ts │ │ ├── niceNumberUtils.test.ts │ │ ├── niceNumberUtils.ts │ │ ├── noop.ts │ │ ├── pathToProp.ts │ │ ├── persistAtom.ts │ │ ├── pointerDeep.ts │ │ ├── removePathFromObject.test.ts │ │ ├── removePathFromObject.ts │ │ ├── resolvedPromise.ts │ │ ├── sanitizers.ts │ │ ├── setDeepImmutable.ts │ │ ├── slashedPaths.ts │ │ ├── stableJsonStringify.ts │ │ ├── subPrism.ts │ │ ├── subscribeDebounced.ts │ │ ├── tightJsonStringify.test.ts │ │ ├── tightJsonStringify.ts │ │ ├── transformNumber.ts │ │ ├── types.ts │ │ ├── uniqueKeyForAnyObject.ts │ │ ├── updateDeep.ts │ │ ├── userReadableTypeOfValue.ts │ │ ├── valToAtom.ts │ │ └── waitForPrism.ts │ ├── tsconfig.json │ └── typedoc.json ├── render.yaml ├── tsconfig.base.json └── wallaby.conf.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ indent_style = space indent_size = 2 end_of_line = lf ================================================ FILE: .eslintignore ================================================ **/prisma/client-generated/**/* **/prisma/client-generated/* **/.next/** ================================================ FILE: .eslintrc.js ================================================ /** * @remarks * Notes on plugins we _don't_ use: * * ## plugin:react-hooks * We don't use the react hooks plugin because it disallows valid use-cases * such as this: * * ```ts * export default function useValToAtom(val: S): Atom { * const atom = useMemo(() => { * return new Atom(val) * }, []) // <-- we don't _need_ to include `val` here, but the lint rule will require it * * useLayoutEffect(() => { * atom.setState(val) * }, [val]) // <-- we also know `atom` will never change, but the lint rule doesn't * * return atom * ``` * * @type {import("eslint").Linter.Config} */ module.exports = { root: true, plugins: ['unused-imports', 'eslint-plugin-tsdoc', 'import', 'react'], settings: { react: { version: '18.2', }, }, extends: [], rules: { 'unused-imports/no-unused-imports': 'warn', 'tsdoc/syntax': 'warn', 'no-debugger': 'error', 'react/no-deprecated': 'error', 'no-restricted-imports': [ 'error', { paths: [ { name: 'lodash', message: 'Use lodash-es which is tree-shaking friendly', }, ], }, ], }, ignorePatterns: ['*.d.ts', '*.ignore.ts', 'compat-tests/*'], overrides: [ { files: ['*.ts', '*.tsx'], parser: '@typescript-eslint/parser', plugins: ['@typescript-eslint'], parserOptions: { project: [ './packages/*/tsconfig.json', './packages/*/devEnv/tsconfig.json', './examples/*/tsconfig.json', './devEnv/tsconfig.json', './compat-tests/tsconfig.json', ], }, rules: { '@typescript-eslint/await-thenable': 'warn', '@typescript-eslint/no-throw-literal': 'warn', '@typescript-eslint/switch-exhaustiveness-check': 'error', '@typescript-eslint/consistent-type-imports': [ 'warn', { prefer: 'type-imports', }, ], '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-floating-promises': 'warn', }, }, { plugins: ['react'], files: ['*.mjs', '*.js'], rules: { 'react/jsx-uses-react': 'error', 'react/jsx-uses-vars': 'error', 'tsdoc/syntax': 'off', }, parser: 'espree', parserOptions: { sourceType: 'module', ecmaVersion: 2021, ecmaFeatures: { jsx: true, }, }, }, ], } ================================================ FILE: .github/.yarnrc.publish.yml ================================================ # Auth config for publishing to npm registry. # It's put in /.github so it's only picked up by # github actions. npmPublishRegistry: 'https://registry.npmjs.org' npmRegistries: //registry.npmjs.org: npmAlwaysAuth: true npmAuthToken: ${NODE_AUTH_TOKEN} ================================================ FILE: .github/actions/yarn-nm-install/action.yml ================================================ name: yarn-nm-install description: Installs deps via yarn and re-uses the cache runs: using: composite steps: # A shared action to install dependencies via yarn and re-use the cache. # This will skip the install step if the cache is hit. - name: Restore node_modules id: yarn-node-modules-cache uses: buildjet/cache@v3 with: path: | **/node_modules .yarn/cache key: # Ideally we'd only have to list the lockfile, and `yarn install` # would take care of the rest. But that's not the case, because # `yarn install` would still build packages, even if they'd already # been built before. Yarn's message is: # `YN0007: │ esbuild@npm:0.16.7 must be built because it never has been before or the last one failed` # I couldn't figure out how to make it not build packages, so I # added the `package.json` files to the cache key (so in a later step, we can entirely skip the install step). # # However, this means that if we add a new workspace under any of the # existing workspaces, run this action, and then change the package.json # of that new workspace, then the cache will be hit, and the new package.json # will be ignored. ${{ runner.os }}-yarn-mono-nm-node-modules-${{ hashFiles('yarn.lock', '.yarnrc.yml', 'package.json', '*/package.json', '*/*/package.json') }} # Thanks to https://github.com/rafaelbiten for this step https://github.com/microsoft/playwright/issues/7249#issuecomment-1385567519 - name: Cache Playwright Browsers for Playwright's Version id: cache-playwright-browsers uses: buildjet/cache@v3 with: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }} # This step is only needed if the cache was not hit. - run: yarn install --immutable --inline-builds if: steps.yarn-node-modules-cache.outputs.cache-hit != 'true' shell: bash env: YARN_NM_MODE: 'hardlinks-local' # This step is only needed if the job runs playwright tests. But we have # to include it in all jobs. The reason is, both `yarn install` and `yarn run playwright install` # modify the `~/.cache/ms-playwright` folder. If we don't include this step in all jobs, # then the cache will change in some jobs and not in others, which would make the cache useless. - name: Download playwright if: steps.cache-playwright-browsers.outputs.cache-hit != 'true' shell: bash run: yarn workspace playground run playwright install --with-deps - name: Run postinstall shell: bash run: yarn postinstall # - name: Update browserlist # shell: bash # run: npx browserslist@latest --update-db ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: [main] pull_request: branches: [main] jobs: Build: runs-on: buildjet-8vcpu-ubuntu-2204 strategy: matrix: node-version: [20.9] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - uses: ./.github/actions/yarn-nm-install - run: yarn cli build Lint: runs-on: buildjet-8vcpu-ubuntu-2204 strategy: matrix: node-version: [20.9] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - uses: ./.github/actions/yarn-nm-install - run: | export NODE_OPTIONS="--max_old_space_size=4096" yarn lint:all --max-warnings 0 Test: runs-on: buildjet-8vcpu-ubuntu-2204 strategy: matrix: node-version: [20.9] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - uses: ./.github/actions/yarn-nm-install - run: yarn test VisualRegression: # skip this until the new API is ready if: false name: Visual regression tests runs-on: buildjet-8vcpu-ubuntu-2204 strategy: matrix: node-version: [20.9] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - uses: ./.github/actions/yarn-nm-install - name: Run e2e tests run: yarn test:e2e:ci Compatibility-Tests: # skip this until the new API is ready if: false runs-on: buildjet-8vcpu-ubuntu-2204 strategy: matrix: node-version: [20.9] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - uses: ./.github/actions/yarn-nm-install # re-enable the following line if we start to get EINTEGRITY errors again # - run: npm cache clean || npm cache verify # This will test whether `npm install`/`yarn install` can actually run on each compatibility test fixture. See `compat-tests/README.md` for more info. - run: yarn workspace @theatre/compat-tests run install-fixtures --verbose # after that, we run the jest tests for each fixture - run: yarn test:compat:run --verbose ================================================ FILE: .github/workflows/release-insiders.yml ================================================ name: Release insiders # This workflow is triggered when a comment is created on a PR that contains the string "/release insiders". # The comment must be created by a user with write access to the repo. on: # only run when a comment is created under a pr issue_comment: types: [created] env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: insiders: # run if the comment contains "/release insiders" and if the comment is created under a PR if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/release insiders') }} runs-on: buildjet-8vcpu-ubuntu-2204 permissions: issues: write # allow writing comments on the PR contents: write pull-requests: write steps: # check if the user is an owner of the repo - name: Has write access id: has-write-access uses: actions/github-script@v6 with: script: | const res = await github.rest.repos.getCollaboratorPermissionLevel({ ...context.repo, username: context.payload.comment.user.login, }); const hasPermission = ['admin', 'write', 'maintain'].includes(res.data.permission) if (!hasPermission) { // if the user doesn't have write access, comment on the PR await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: `🚫 Cannot publish because user @${context.payload.comment.user.login} doesn't have write access.` }) } return hasPermission # stop the workflow if the user doesn't have write access - name: Stop if user doesn't have write access if: ${{ !steps.has-write-access.outputs.result }} run: | echo "User doesn't have write access. Stopping the workflow." exit 1 - name: Start the comment id: start-comment uses: actions/github-script@v6 with: result-encoding: string script: | const result = await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: ` ⏳ Okay @${context.payload.comment.user.login}, I'm releasing an insiders build to npm. I will update this comment when done.
More detail In case this fails, follow the workflow run [here](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) to debug.
` }) const commentId = result.data.id return commentId - name: Get the pull request's ref id: pr-ref uses: actions/github-script@v6 with: result-encoding: string script: | const {data: pullRequest} = await github.rest.pulls.get({ ...context.repo, pull_number: context.issue.number }) return pullRequest.head.sha # checkout the repo at the latest commit of the PR - uses: actions/checkout@v2 with: ref: ${{ steps.pr-ref.outputs.result }} - name: Use Node.js uses: actions/setup-node@v3 with: node-version: 18.x - uses: ./.github/actions/yarn-nm-install - name: Build the Theatre.js packages run: yarn cli build - name: Update .yarnrc.yml with the auth config for the npmPublishRegistry run: cat .github/.yarnrc.publish.yml >> .yarnrc.yml - name: Publish the Theatre.js packages id: publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: yarn zx scripts/prerelease.mjs - name: Update the comment id: update-comment uses: actions/github-script@v6 with: script: | const published = JSON.parse(${{ toJSON( steps.publish.outputs.data ) }}) const result = await github.rest.issues.updateComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, comment_id: "${{ steps.start-comment.outputs.result }}", body: `
🎉 Released an insiders' build to npm. Here is how to upgrade: * 🚧 This is an insiders' build. It possibly **is not stable and _may_ corrupt your data**. Always **backup your work** before upgrading. * If you do try this build, welcome aboard, insider 😉. If you're feeling generous, share some feedback here or on [\`#contributing\`](https://discord.com/channels/870988717190426644/940301611023073400) at Discord. * 🔼 To upgrade, edit \`package.json\` and replace the versions of all \`@theatre\` or \`theatric\` packages with their new versions: \`\`\`diff ${published.map((pkg) => { return `--- ${pkg.packageName}: "old-version",\n+++ ${pkg.packageName}: "${pkg.version}",` }).join('\n')} \`\`\` All published packages are on npm: ${published.map((pkg) => { return `* [\`${pkg.packageName}@${pkg.version}\`](https://www.npmjs.com/package/${pkg.packageName}/v/${pkg.version})` }).join('\n')} Published at the request of @${context.payload.comment.user.login}, on commit ${{ steps.pr-ref.outputs.result }}. Workflow log [here](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) to debug.
` }) ================================================ FILE: .gitignore ================================================ **/node_modules **/xeno /packages/*/dist /examples/*/dist *.log *.temp /.vscode /.history **/.DS_Store *.tsbuildinfo .eslintcache .yarn/* # !.yarn/cache !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/sdks !.yarn/versions # Required by the `parcel_v2` # compatibility tests .parcel-cache /compat-tests/*/.yarn /compat-tests/*/build /TODO.md ================================================ FILE: .husky/.gitignore ================================================ _ ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" yarn lint-staged yarn workspace @theatre/dataverse run precommit # if there are unstaged changes in ./packages/dataverse/docs, fail if ! git diff --quiet --exit-code -- docs; then echo "Please run 'yarn workspace @theatre/dataverse run doc' and commit the changes to the docs folder" exit 1 fi ================================================ FILE: .prettierignore ================================================ **/prisma/client-generated/**/* **/prisma/client-generated/* ================================================ FILE: .prettierrc ================================================ { "printWidth": 80, "semi": false, "singleQuote": true, "trailingComma": "all", "bracketSpacing": false, "proseWrap": "always" } ================================================ FILE: .yarn/plugins/@yarnpkg/plugin-compat.cjs ================================================ module.exports = { name: `@yarnpkg/plugin-compat`, factory: (require) => { // dummy implementation to override the built-in version of this plugin return {} }, } ================================================ FILE: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs ================================================ /* eslint-disable */ module.exports = { name: "@yarnpkg/plugin-interactive-tools", factory: function (require) { var plugin;plugin=(()=>{var __webpack_modules__={7560:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>K});function r(e,t,n,r){var i,o=arguments.length,u=o<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)u=Reflect.decorate(e,t,n,r);else for(var a=e.length-1;a>=0;a--)(i=e[a])&&(u=(o<3?i(u):o>3?i(t,n,u):i(t,n))||u);return o>3&&u&&Object.defineProperty(t,n,u),u}const i=require("@yarnpkg/cli"),o=require("@yarnpkg/core");var u=n(9245),a=n(7382);const l=(0,a.memo)(({active:e})=>{const t=(0,a.useMemo)(()=>e?"◉":"◯",[e]),n=(0,a.useMemo)(()=>e?"green":"yellow",[e]);return a.createElement(u.Text,{color:n},t)});function s({active:e},t,n){const{stdin:r}=(0,u.useStdin)(),i=(0,a.useCallback)((e,n)=>t(e,n),n);(0,a.useEffect)(()=>{if(e&&r)return r.on("keypress",i),()=>{r.off("keypress",i)}},[e,i,r])}var c;!function(e){e.BEFORE="before",e.AFTER="after"}(c||(c={}));const f=function(e,t,{active:n,minus:r,plus:i,set:o,loop:u=!0}){s({active:n},(n,a)=>{const l=t.indexOf(e);switch(a.name){case r:{const e=l-1;if(u)return void o(t[(t.length+e)%t.length]);if(e<0)return;o(t[e])}break;case i:{const e=l+1;if(u)return void o(t[e%t.length]);if(e>=t.length)return;o(t[e])}}},[t,e,i,o,u])},d=({active:e=!0,children:t=[],radius:n=10,size:r=1,loop:i=!0,onFocusRequest:o,willReachEnd:l})=>{const d=a.Children.map(t,e=>(e=>{if(null===e.key)throw new Error("Expected all children to have a key");return e.key})(e)),p=d[0],[h,v]=(0,a.useState)(p),m=d.indexOf(h);(0,a.useEffect)(()=>{d.includes(h)||v(p)},[t]),(0,a.useEffect)(()=>{l&&m>=d.length-2&&l()},[m]),function({active:e},t,n){s({active:e},(e,n)=>{"tab"===n.name&&(n.shift?t(c.BEFORE):t(c.AFTER))},n)}({active:e&&!!o},e=>{null==o||o(e)},[o]),f(h,d,{active:e,minus:"up",plus:"down",set:v,loop:i});let g=m-n,y=m+n;y>d.length&&(g-=y-d.length,y=d.length),g<0&&(y+=-g,g=0),y>=d.length&&(y=d.length-1);const _=[];for(let n=g;n<=y;++n){const i=d[n],o=e&&i===h;_.push(a.createElement(u.Box,{key:i,height:r},a.createElement(u.Box,{marginLeft:1,marginRight:1},a.createElement(u.Text,null,o?a.createElement(u.Text,{color:"cyan",bold:!0},">"):" ")),a.createElement(u.Box,null,a.cloneElement(t[n],{active:o}))))}return a.createElement(u.Box,{flexDirection:"column",width:"100%"},_)},p=require("readline"),h=a.createContext(null),v=({children:e})=>{const{stdin:t,setRawMode:n}=(0,u.useStdin)();(0,a.useEffect)(()=>{n&&n(!0),t&&(0,p.emitKeypressEvents)(t)},[t,n]);const[r,i]=(0,a.useState)(new Map),o=(0,a.useMemo)(()=>({getAll:()=>r,get:e=>r.get(e),set:(e,t)=>i(new Map([...r,[e,t]]))}),[r,i]);return a.createElement(h.Provider,{value:o,children:e})};function m(e,t){const n=(0,a.useContext)(h);if(null===n)throw new Error("Expected this hook to run with a ministore context attached");if(void 0===e)return n.getAll();const r=(0,a.useCallback)(t=>{n.set(e,t)},[e,n.set]);let i=n.get(e);return void 0===i&&(i=t),[i,r]}async function g(e,t){let n;const{waitUntilExit:r}=(0,u.render)(a.createElement(v,null,a.createElement(e,Object.assign({},t,{useSubmit:e=>{const{exit:t}=(0,u.useApp)();s({active:!0},(r,i)=>{"return"===i.name&&(n=e,t())},[t,e])}}))));return await r(),n}const y=require("clipanion");var _=n(7840),b=n(4410);const w={appId:"OFCNCOG2CU",apiKey:"6fe4476ee5a1832882e326b506d14126",indexName:"npm-search"},E=n.n(b)()(w.appId,w.apiKey).initIndex(w.indexName),D=async(e,t=0)=>await E.search(e,{analyticsTags:["yarn-plugin-interactive-tools"],attributesToRetrieve:["name","version","owner","repository","humanDownloadsLast30Days"],page:t,hitsPerPage:10}),S=["regular","dev","peer"];class C extends i.BaseCommand{async execute(){const e=await o.Configuration.find(this.context.cwd,this.context.plugins),t=()=>a.createElement(u.Box,{flexDirection:"row"},a.createElement(u.Box,{flexDirection:"column",width:48},a.createElement(u.Box,null,a.createElement(u.Text,null,"Press ",a.createElement(u.Text,{bold:!0,color:"cyanBright"},""),"/",a.createElement(u.Text,{bold:!0,color:"cyanBright"},"")," to move between packages.")),a.createElement(u.Box,null,a.createElement(u.Text,null,"Press ",a.createElement(u.Text,{bold:!0,color:"cyanBright"},"")," to select a package.")),a.createElement(u.Box,null,a.createElement(u.Text,null,"Press ",a.createElement(u.Text,{bold:!0,color:"cyanBright"},"")," again to change the target."))),a.createElement(u.Box,{flexDirection:"column"},a.createElement(u.Box,{marginLeft:1},a.createElement(u.Text,null,"Press ",a.createElement(u.Text,{bold:!0,color:"cyanBright"},"")," to install the selected packages.")),a.createElement(u.Box,{marginLeft:1},a.createElement(u.Text,null,"Press ",a.createElement(u.Text,{bold:!0,color:"cyanBright"},"")," to abort.")))),n=()=>a.createElement(a.Fragment,null,a.createElement(u.Box,{width:15},a.createElement(u.Text,{bold:!0,underline:!0,color:"gray"},"Owner")),a.createElement(u.Box,{width:11},a.createElement(u.Text,{bold:!0,underline:!0,color:"gray"},"Version")),a.createElement(u.Box,{width:10},a.createElement(u.Text,{bold:!0,underline:!0,color:"gray"},"Downloads"))),r=()=>a.createElement(u.Box,{width:17},a.createElement(u.Text,{bold:!0,underline:!0,color:"gray"},"Target")),i=({hit:t,active:n})=>{const[r,i]=m(t.name,null);s({active:n},(e,t)=>{if("space"!==t.name)return;if(!r)return void i(S[0]);const n=S.indexOf(r)+1;n===S.length?i(null):i(S[n])},[r,i]);const l=o.structUtils.parseIdent(t.name),c=o.structUtils.prettyIdent(e,l);return a.createElement(u.Box,null,a.createElement(u.Box,{width:45},a.createElement(u.Text,{bold:!0,wrap:"wrap"},c)),a.createElement(u.Box,{width:14,marginLeft:1},a.createElement(u.Text,{bold:!0,wrap:"truncate"},t.owner.name)),a.createElement(u.Box,{width:10,marginLeft:1},a.createElement(u.Text,{italic:!0,wrap:"truncate"},t.version)),a.createElement(u.Box,{width:16,marginLeft:1},a.createElement(u.Text,null,t.humanDownloadsLast30Days)))},c=({name:t,active:n})=>{const[r]=m(t,null),i=o.structUtils.parseIdent(t);return a.createElement(u.Box,null,a.createElement(u.Box,{width:47},a.createElement(u.Text,{bold:!0}," - ",o.structUtils.prettyIdent(e,i))),S.map(e=>a.createElement(u.Box,{key:e,width:14,marginLeft:1},a.createElement(u.Text,null," ",a.createElement(l,{active:r===e})," ",a.createElement(u.Text,{bold:!0},e)))))},f=()=>a.createElement(u.Box,{marginTop:1},a.createElement(u.Text,null,"Powered by Algolia.")),p=await g(({useSubmit:e})=>{const o=m();e(o);const l=Array.from(o.keys()).filter(e=>null!==o.get(e)),[s,p]=(0,a.useState)(""),[h,v]=(0,a.useState)(0),[g,y]=(0,a.useState)([]);return(0,a.useEffect)(()=>{s?(async()=>{v(0);const e=await D(s);e.query===s&&y(e.hits)})():y([])},[s]),a.createElement(u.Box,{flexDirection:"column"},a.createElement(t,null),a.createElement(u.Box,{flexDirection:"row",marginTop:1},a.createElement(u.Text,{bold:!0},"Search: "),a.createElement(u.Box,{width:41},a.createElement(_.ZP,{value:s,onChange:e=>{e.match(/\t| /)||p(e)},placeholder:"i.e. babel, webpack, react...",showCursor:!1})),a.createElement(n,null)),g.length?a.createElement(d,{radius:2,loop:!1,children:g.map(e=>a.createElement(i,{key:e.name,hit:e,active:!1})),willReachEnd:async()=>{const e=await D(s,h+1);e.query===s&&e.page-1===h&&(v(e.page),y([...g,...e.hits]))}}):a.createElement(u.Text,{color:"gray"},"Start typing..."),a.createElement(u.Box,{flexDirection:"row",marginTop:1},a.createElement(u.Box,{width:49},a.createElement(u.Text,{bold:!0},"Selected:")),a.createElement(r,null)),l.length?l.map(e=>a.createElement(c,{key:e,name:e,active:!1})):a.createElement(u.Text,{color:"gray"},"No selected packages..."),a.createElement(f,null))},{});if(void 0===p)return 1;const h=Array.from(p.keys()).filter(e=>"regular"===p.get(e)),v=Array.from(p.keys()).filter(e=>"dev"===p.get(e)),y=Array.from(p.keys()).filter(e=>"peer"===p.get(e));return h.length&&await this.cli.run(["add",...h]),v.length&&await this.cli.run(["add","--dev",...v]),y&&await this.cli.run(["add","--peer",...y]),0}}C.usage=y.Command.Usage({category:"Interactive commands",description:"open the search interface",details:"\n This command opens a fullscreen terminal interface where you can search for and install packages from the npm registry.\n ",examples:[["Open the search window","yarn search"]]}),r([y.Command.Path("search")],C.prototype,"execute",null);var k=n(5882),T=n.n(k);const x=({length:e,active:t})=>{if(0===e)return null;const n=e>1?" "+T().underline(" ".repeat(e-1)):" ";return a.createElement(u.Text,{dimColor:!t},n)},A=function({active:e,skewer:t,options:n,value:r,onChange:i,sizes:o=[]}){const s=n.map(({value:e})=>e),c=s.indexOf(r);return f(r,s,{active:e,minus:"left",plus:"right",set:i}),a.createElement(a.Fragment,null,n.map(({label:n},r)=>{const i=r===c,s=o[r]-1||0,f=n.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,""),d=Math.max(0,s-f.length-2);return a.createElement(u.Box,{key:n,width:s,marginLeft:1},a.createElement(u.Text,{wrap:"truncate"},a.createElement(l,{active:i})," ",n),t?a.createElement(x,{active:e,length:d}):null)}))},O=require("@yarnpkg/plugin-essentials");function P(){}function I(e,t,n,r,i){for(var o=0,u=t.length,a=0,l=0;oe.length?n:e})),s.value=e.join(f)}else s.value=e.join(n.slice(a,a+s.count));a+=s.count,s.added||(l+=s.count)}}var d=t[u-1];return u>1&&"string"==typeof d.value&&(d.added||d.removed)&&e.equals("",d.value)&&(t[u-2].value+=d.value,t.pop()),t}function N(e){return{newPos:e.newPos,components:e.components.slice(0)}}P.prototype={diff:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.callback;"function"==typeof n&&(r=n,n={}),this.options=n;var i=this;function o(e){return r?(setTimeout((function(){r(void 0,e)}),0),!0):e}e=this.castInput(e),t=this.castInput(t),e=this.removeEmpty(this.tokenize(e));var u=(t=this.removeEmpty(this.tokenize(t))).length,a=e.length,l=1,s=u+a,c=[{newPos:-1,components:[]}],f=this.extractCommon(c[0],t,e,0);if(c[0].newPos+1>=u&&f+1>=a)return o([{value:this.join(t),count:t.length}]);function d(){for(var n=-1*l;n<=l;n+=2){var r=void 0,s=c[n-1],f=c[n+1],d=(f?f.newPos:0)-n;s&&(c[n-1]=void 0);var p=s&&s.newPos+1=u&&d+1>=a)return o(I(i,r.components,t,e,i.useLongestToken));c[n]=r}else c[n]=void 0}l++}if(r)!function e(){setTimeout((function(){if(l>s)return r();d()||e()}),0)}();else for(;l<=s;){var p=d();if(p)return p}},pushComponent:function(e,t,n){var r=e[e.length-1];r&&r.added===t&&r.removed===n?e[e.length-1]={count:r.count+1,added:t,removed:n}:e.push({count:1,added:t,removed:n})},extractCommon:function(e,t,n,r){for(var i=t.length,o=n.length,u=e.newPos,a=u-r,l=0;u+1=?)?)([0-9]+)(\.[0-9]+)(\.[0-9]+)((?:-\S+)?)$/;class Y extends i.BaseCommand{async execute(){const e=await o.Configuration.find(this.context.cwd,this.context.plugins),{project:t,workspace:n}=await o.Project.find(e,this.context.cwd),r=await o.Cache.find(e);if(!n)throw new i.WorkspaceRequiredError(t.cwd,this.context.cwd);const l=(t,n)=>{const r=(i=t,u=n,a=M(a,{ignoreWhitespace:!0}),L.diff(i,u,a));var i,u,a;let l="";for(const t of r)t.added?l+=o.formatUtils.pretty(e,t.value,"green"):t.removed||(l+=t.value);return l},s=(t,n)=>{if(t===n)return n;const r=o.structUtils.parseRange(t),i=o.structUtils.parseRange(n),u=r.selector.match($),a=i.selector.match($);if(!u||!a)return l(t,n);const s=["gray","red","yellow","green","magenta"];let c=null,f="";for(let t=1;t{const u=await O.suggestUtils.fetchDescriptorFrom(e,o,{project:t,cache:r,preserveModifier:i,workspace:n});return null!==u?u.range:e.range},f=()=>a.createElement(u.Box,{flexDirection:"row"},a.createElement(u.Box,{flexDirection:"column",width:49},a.createElement(u.Box,{marginLeft:1},a.createElement(u.Text,null,"Press ",a.createElement(u.Text,{bold:!0,color:"cyanBright"},""),"/",a.createElement(u.Text,{bold:!0,color:"cyanBright"},"")," to select packages.")),a.createElement(u.Box,{marginLeft:1},a.createElement(u.Text,null,"Press ",a.createElement(u.Text,{bold:!0,color:"cyanBright"},""),"/",a.createElement(u.Text,{bold:!0,color:"cyanBright"},"")," to select versions."))),a.createElement(u.Box,{flexDirection:"column"},a.createElement(u.Box,{marginLeft:1},a.createElement(u.Text,null,"Press ",a.createElement(u.Text,{bold:!0,color:"cyanBright"},"")," to install.")),a.createElement(u.Box,{marginLeft:1},a.createElement(u.Text,null,"Press ",a.createElement(u.Text,{bold:!0,color:"cyanBright"},"")," to abort.")))),p=()=>a.createElement(u.Box,{flexDirection:"row",paddingTop:1,paddingBottom:1},a.createElement(u.Box,{width:50},a.createElement(u.Text,{bold:!0},a.createElement(u.Text,{color:"greenBright"},"?")," Pick the packages you want to upgrade.")),a.createElement(u.Box,{width:17},a.createElement(u.Text,{bold:!0,underline:!0,color:"gray"},"Current")),a.createElement(u.Box,{width:17},a.createElement(u.Text,{bold:!0,underline:!0,color:"gray"},"Range")),a.createElement(u.Box,{width:17},a.createElement(u.Text,{bold:!0,underline:!0,color:"gray"},"Latest"))),h=({active:t,descriptor:n,suggestions:r})=>{const[i,l]=m(n.descriptorHash,null),s=o.structUtils.stringifyIdent(n),c=Math.max(0,45-s.length);return a.createElement(a.Fragment,null,a.createElement(u.Box,null,a.createElement(u.Box,{width:45},a.createElement(u.Text,{bold:!0},o.structUtils.prettyIdent(e,n)),a.createElement(x,{active:t,length:c})),null!==r?a.createElement(A,{active:t,options:r,value:i,skewer:!0,onChange:l,sizes:[17,17,17]}):a.createElement(u.Box,{marginLeft:2},a.createElement(u.Text,{color:"gray"},"Fetching suggestions..."))))},v=({dependencies:e})=>{const[t,n]=(0,a.useState)(null),r=(0,a.useRef)(!0);return(0,a.useEffect)(()=>()=>{r.current=!1}),(0,a.useEffect)(()=>{Promise.all(e.map(e=>(async e=>{const t=G().valid(e.range)?"^"+e.range:e.range,[n,r]=await Promise.all([c(e,e.range,t).catch(()=>null),c(e,e.range,"latest").catch(()=>null)]),i=[{value:null,label:e.range}];return n&&n!==e.range&&i.push({value:n,label:s(e.range,n)}),r&&r!==n&&r!==e.range&&i.push({value:r,label:s(e.range,r)}),i})(e))).then(t=>{const i=e.map((e,n)=>[e,t[n]]).filter(([e,t])=>t.length>1);r.current&&n(i)})},[]),t?t.length?a.createElement(d,{radius:10,children:t.map(([e,t])=>a.createElement(h,{key:e.descriptorHash,active:!1,descriptor:e,suggestions:t}))}):a.createElement(u.Text,null,"No upgrades found"):a.createElement(u.Text,null,"Fetching suggestions...")},y=await g(({useSubmit:e})=>{e(m());const n=new Map;for(const e of t.workspaces)for(const r of["dependencies","devDependencies"])for(const i of e.manifest[r].values())null===t.tryWorkspaceByDescriptor(i)&&n.set(i.descriptorHash,i);const r=o.miscUtils.sortMap(n.values(),e=>o.structUtils.stringifyDescriptor(e));return a.createElement(u.Box,{flexDirection:"column"},a.createElement(f,null),a.createElement(p,null),a.createElement(v,{dependencies:r}))},{});if(void 0===y)return 1;let _=!1;for(const e of t.workspaces)for(const t of["dependencies","devDependencies"]){const n=e.manifest[t];for(const e of n.values()){const t=y.get(e.descriptorHash);null!=t&&(n.set(e.identHash,o.structUtils.makeDescriptor(e,t)),_=!0)}}if(!_)return 0;return(await o.StreamReport.start({configuration:e,stdout:this.context.stdout,includeLogs:!this.context.quiet},async e=>{await t.install({cache:r,report:e})})).exitCode()}}Y.usage=y.Command.Usage({category:"Interactive commands",description:"open the upgrade interface",details:"\n This command opens a fullscreen terminal interface where you can see any out of date packages used by your application, their status compared to the latest versions available on the remote registry, and select packages to upgrade.\n ",examples:[["Open the upgrade window","yarn upgrade-interactive"]]}),r([y.Command.Path("upgrade-interactive")],Y.prototype,"execute",null);const K={commands:[C,Y]}},7840:(e,t,n)=>{"use strict";const r=n(7382),i=n(7382),o=n(9245),u=n(1525),a=({value:e,placeholder:t="",focus:n=!0,mask:a,highlightPastedText:l=!1,showCursor:s=!0,onChange:c,onSubmit:f})=>{const[{cursorOffset:d,cursorWidth:p},h]=i.useState({cursorOffset:(e||"").length,cursorWidth:0});i.useEffect(()=>{h(t=>{if(!n||!s)return t;const r=e||"";return t.cursorOffset>r.length-1?{cursorOffset:r.length,cursorWidth:0}:t})},[e,n,s]);const v=l?p:0,m=a?a.repeat(e.length):e;let g=m,y=t?u.grey(t):void 0;if(s&&n){y=t.length>0?u.inverse(t[0])+u.grey(t.slice(1)):u.inverse(" "),g=m.length>0?"":u.inverse(" ");let e=0;for(const t of m)g+=e>=d-v&&e<=d?u.inverse(t):t,e++;m.length>0&&d===m.length&&(g+=u.inverse(" "))}return o.useInput((t,n)=>{if(n.upArrow||n.downArrow||n.ctrl&&"c"===t||n.tab||n.shift&&n.tab)return;if(n.return)return void(f&&f(e));let r=d,i=e,o=0;n.leftArrow?s&&r--:n.rightArrow?s&&r++:n.backspace||n.delete?d>0&&(i=e.slice(0,d-1)+e.slice(d,e.length),r--):(i=e.slice(0,d)+t+e.slice(d,e.length),r+=t.length,t.length>1&&(o=t.length)),d<0&&(r=0),d>e.length&&(r=e.length),h({cursorOffset:r,cursorWidth:o}),i!==e&&c(i)},{isActive:n}),r.createElement(o.Text,null,t?m.length>0?g:y:g)};t.ZP=a},9902:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(1525)),o=/^(rgb|hsl|hsv|hwb)\(\s?(\d+),\s?(\d+),\s?(\d+)\s?\)$/,u=/^(ansi|ansi256)\(\s?(\d+)\s?\)$/,a=(e,t)=>"foreground"===t?e:"bg"+e[0].toUpperCase()+e.slice(1);t.default=(e,t,n)=>{if(!t)return e;if(t in i.default){const r=a(t,n);return i.default[r](e)}if(t.startsWith("#")){const r=a("hex",n);return i.default[r](t)(e)}if(t.startsWith("ansi")){const r=u.exec(t);if(!r)return e;const o=a(r[1],n),l=Number(r[2]);return i.default[o](l)(e)}if(t.startsWith("rgb")||t.startsWith("hsl")||t.startsWith("hsv")||t.startsWith("hwb")){const r=o.exec(t);if(!r)return e;const u=a(r[1],n),l=Number(r[2]),s=Number(r[3]),c=Number(r[4]);return i.default[u](l,s,c)(e)}return e}},2773:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.hasOwnProperty.call(e,n)&&r(t,e,n);return i(t,e),t},u=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const a=o(n(7382)),l=u(n(1696)),s=u(n(5512)),c=u(n(1489)),f=u(n(6834)),d=u(n(5001)),p=u(n(2560)),h=u(n(9052));class v extends a.PureComponent{constructor(){super(...arguments),this.state={isFocusEnabled:!0,activeFocusId:void 0,focusables:[],error:void 0},this.rawModeEnabledCount=0,this.handleSetRawMode=e=>{const{stdin:t}=this.props;if(!this.isRawModeSupported())throw t===process.stdin?new Error("Raw mode is not supported on the current process.stdin, which Ink uses as input stream by default.\nRead about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported"):new Error("Raw mode is not supported on the stdin provided to Ink.\nRead about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported");if(t.setEncoding("utf8"),e)return 0===this.rawModeEnabledCount&&(t.addListener("data",this.handleInput),t.resume(),t.setRawMode(!0)),void this.rawModeEnabledCount++;0==--this.rawModeEnabledCount&&(t.setRawMode(!1),t.removeListener("data",this.handleInput),t.pause())},this.handleInput=e=>{""===e&&this.props.exitOnCtrlC&&this.handleExit(),""===e&&this.state.activeFocusId&&this.setState({activeFocusId:void 0}),this.state.isFocusEnabled&&this.state.focusables.length>0&&("\t"===e&&this.focusNext(),""===e&&this.focusPrevious())},this.handleExit=e=>{this.isRawModeSupported()&&this.handleSetRawMode(!1),this.props.onExit(e)},this.enableFocus=()=>{this.setState({isFocusEnabled:!0})},this.disableFocus=()=>{this.setState({isFocusEnabled:!1})},this.focusNext=()=>{this.setState(e=>{const t=e.focusables[0].id;return{activeFocusId:this.findNextFocusable(e)||t}})},this.focusPrevious=()=>{this.setState(e=>{const t=e.focusables[e.focusables.length-1].id;return{activeFocusId:this.findPreviousFocusable(e)||t}})},this.addFocusable=(e,{autoFocus:t})=>{this.setState(n=>{let r=n.activeFocusId;return!r&&t&&(r=e),{activeFocusId:r,focusables:[...n.focusables,{id:e,isActive:!0}]}})},this.removeFocusable=e=>{this.setState(t=>({activeFocusId:t.activeFocusId===e?void 0:t.activeFocusId,focusables:t.focusables.filter(t=>t.id!==e)}))},this.activateFocusable=e=>{this.setState(t=>({focusables:t.focusables.map(t=>t.id!==e?t:{id:e,isActive:!0})}))},this.deactivateFocusable=e=>{this.setState(t=>({activeFocusId:t.activeFocusId===e?void 0:t.activeFocusId,focusables:t.focusables.map(t=>t.id!==e?t:{id:e,isActive:!1})}))},this.findNextFocusable=e=>{for(let t=e.focusables.findIndex(t=>t.id===e.activeFocusId)+1;t{for(let t=e.focusables.findIndex(t=>t.id===e.activeFocusId)-1;t>=0;t--)if(e.focusables[t].isActive)return e.focusables[t].id}}static getDerivedStateFromError(e){return{error:e}}isRawModeSupported(){return this.props.stdin.isTTY}render(){return a.default.createElement(s.default.Provider,{value:{exit:this.handleExit}},a.default.createElement(c.default.Provider,{value:{stdin:this.props.stdin,setRawMode:this.handleSetRawMode,isRawModeSupported:this.isRawModeSupported(),internal_exitOnCtrlC:this.props.exitOnCtrlC}},a.default.createElement(f.default.Provider,{value:{stdout:this.props.stdout,write:this.props.writeToStdout}},a.default.createElement(d.default.Provider,{value:{stderr:this.props.stderr,write:this.props.writeToStderr}},a.default.createElement(p.default.Provider,{value:{activeId:this.state.activeFocusId,add:this.addFocusable,remove:this.removeFocusable,activate:this.activateFocusable,deactivate:this.deactivateFocusable,enableFocus:this.enableFocus,disableFocus:this.disableFocus,focusNext:this.focusNext,focusPrevious:this.focusPrevious}},this.state.error?a.default.createElement(h.default,{error:this.state.error}):this.props.children)))))}componentDidMount(){l.default.hide(this.props.stdout)}componentWillUnmount(){l.default.show(this.props.stdout),this.isRawModeSupported()&&this.handleSetRawMode(!1)}componentDidCatch(e){this.handleExit(e)}}t.default=v,v.displayName="InternalApp"},5512:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(7382).createContext({exit:()=>{}});r.displayName="InternalAppContext",t.default=r},5277:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.hasOwnProperty.call(e,n)&&r(t,e,n);return i(t,e),t},u=this&&this.__rest||function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);i{var{children:n}=e,r=u(e,["children"]);const i=Object.assign(Object.assign({},r),{marginLeft:r.marginLeft||r.marginX||r.margin||0,marginRight:r.marginRight||r.marginX||r.margin||0,marginTop:r.marginTop||r.marginY||r.margin||0,marginBottom:r.marginBottom||r.marginY||r.margin||0,paddingLeft:r.paddingLeft||r.paddingX||r.padding||0,paddingRight:r.paddingRight||r.paddingX||r.padding||0,paddingTop:r.paddingTop||r.paddingY||r.padding||0,paddingBottom:r.paddingBottom||r.paddingY||r.padding||0});return a.default.createElement("ink-box",{ref:t,style:i},n)});l.displayName="Box",l.defaultProps={flexDirection:"row",flexGrow:0,flexShrink:1},t.default=l},9052:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.hasOwnProperty.call(e,n)&&r(t,e,n);return i(t,e),t},u=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const a=o(n(5747)),l=u(n(7382)),s=u(n(9796)),c=u(n(9908)),f=u(n(5277)),d=u(n(9146)),p=new s.default({cwd:process.cwd(),internals:s.default.nodeInternals()});t.default=({error:e})=>{const t=e.stack?e.stack.split("\n").slice(1):void 0,n=t?p.parseLine(t[0]):void 0;let r,i=0;if((null==n?void 0:n.file)&&(null==n?void 0:n.line)&&a.existsSync(n.file)){const e=a.readFileSync(n.file,"utf8");if(r=c.default(e,n.line),r)for(const{line:e}of r)i=Math.max(i,String(e).length)}return l.default.createElement(f.default,{flexDirection:"column",padding:1},l.default.createElement(f.default,null,l.default.createElement(d.default,{backgroundColor:"red",color:"white"}," ","ERROR"," "),l.default.createElement(d.default,null," ",e.message)),n&&l.default.createElement(f.default,{marginTop:1},l.default.createElement(d.default,{dimColor:!0},n.file,":",n.line,":",n.column)),n&&r&&l.default.createElement(f.default,{marginTop:1,flexDirection:"column"},r.map(({line:e,value:t})=>l.default.createElement(f.default,{key:e},l.default.createElement(f.default,{width:i+1},l.default.createElement(d.default,{dimColor:e!==n.line,backgroundColor:e===n.line?"red":void 0,color:e===n.line?"white":void 0},String(e).padStart(i," "),":")),l.default.createElement(d.default,{key:e,backgroundColor:e===n.line?"red":void 0,color:e===n.line?"white":void 0}," "+t)))),e.stack&&l.default.createElement(f.default,{marginTop:1,flexDirection:"column"},e.stack.split("\n").slice(1).map(e=>{const t=p.parseLine(e);return t?l.default.createElement(f.default,{key:e},l.default.createElement(d.default,{dimColor:!0},"- "),l.default.createElement(d.default,{dimColor:!0,bold:!0},t.function),l.default.createElement(d.default,{dimColor:!0,color:"gray"}," ","(",t.file,":",t.line,":",t.column,")")):l.default.createElement(f.default,{key:e},l.default.createElement(d.default,{dimColor:!0},"- "),l.default.createElement(d.default,{dimColor:!0,bold:!0},e))})))}},2560:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(7382).createContext({activeId:void 0,add:()=>{},remove:()=>{},activate:()=>{},deactivate:()=>{},enableFocus:()=>{},disableFocus:()=>{},focusNext:()=>{},focusPrevious:()=>{}});r.displayName="InternalFocusContext",t.default=r},8200:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(7382)),o=({count:e=1})=>i.default.createElement("ink-text",null,"\n".repeat(e));o.displayName="Newline",t.default=o},2198:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(7382)),o=r(n(5277)),u=()=>i.default.createElement(o.default,{flexGrow:1});u.displayName="Spacer",t.default=u},8915:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.hasOwnProperty.call(e,n)&&r(t,e,n);return i(t,e),t};Object.defineProperty(t,"__esModule",{value:!0});const u=o(n(7382)),a=e=>{const{items:t,children:n,style:r}=e,[i,o]=u.useState(0),a=u.useMemo(()=>t.slice(i),[t,i]);u.useLayoutEffect(()=>{o(t.length)},[t.length]);const l=a.map((e,t)=>n(e,i+t)),s=u.useMemo(()=>Object.assign({position:"absolute",flexDirection:"column"},r),[r]);return u.default.createElement("ink-box",{internal_static:!0,style:s},l)};a.displayName="Static",t.default=a},5001:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(7382).createContext({stderr:void 0,write:()=>{}});r.displayName="InternalStderrContext",t.default=r},1489:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(7382).createContext({stdin:void 0,setRawMode:()=>{},isRawModeSupported:!1,internal_exitOnCtrlC:!0});r.displayName="InternalStdinContext",t.default=r},6834:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=n(7382).createContext({stdout:void 0,write:()=>{}});r.displayName="InternalStdoutContext",t.default=r},9146:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(7382)),o=r(n(1525)),u=r(n(9902)),a=({color:e,backgroundColor:t,dimColor:n,bold:r,italic:a,underline:l,strikethrough:s,inverse:c,wrap:f,children:d})=>{if(null==d)return null;return i.default.createElement("ink-text",{style:{flexGrow:0,flexShrink:1,flexDirection:"row",textWrap:f},internal_transform:i=>(n&&(i=o.default.dim(i)),e&&(i=u.default(i,e,"foreground")),t&&(i=u.default(i,t,"background")),r&&(i=o.default.bold(i)),a&&(i=o.default.italic(i)),l&&(i=o.default.underline(i)),s&&(i=o.default.strikethrough(i)),c&&(i=o.default.inverse(i)),i)},d)};a.displayName="Text",a.defaultProps={dimColor:!1,bold:!1,italic:!1,underline:!1,strikethrough:!1,wrap:"wrap"},t.default=a},4592:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(7382)),o=({children:e,transform:t})=>null==e?null:i.default.createElement("ink-text",{style:{flexGrow:0,flexShrink:1,flexDirection:"row"},internal_transform:t},e);o.displayName="Transform",t.default=o},146:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(3296)),o=n(5187),u=global;u.WebSocket||(u.WebSocket=i.default),u.window||(u.window=global),u.window.__REACT_DEVTOOLS_COMPONENT_FILTERS__=[{type:1,value:7,isEnabled:!0},{type:2,value:"InternalApp",isEnabled:!0,isValid:!0},{type:2,value:"InternalAppContext",isEnabled:!0,isValid:!0},{type:2,value:"InternalStdoutContext",isEnabled:!0,isValid:!0},{type:2,value:"InternalStderrContext",isEnabled:!0,isValid:!0},{type:2,value:"InternalStdinContext",isEnabled:!0,isValid:!0},{type:2,value:"InternalFocusContext",isEnabled:!0,isValid:!0}],o.connectToDevTools()},9864:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.setTextNodeValue=t.createTextNode=t.setStyle=t.setAttribute=t.removeChildNode=t.insertBeforeNode=t.appendChildNode=t.createNode=t.TEXT_NAME=void 0;const i=r(n(6401)),o=r(n(8113)),u=r(n(5809)),a=r(n(2030)),l=r(n(9099));t.TEXT_NAME="#text",t.createNode=e=>{var t;const n={nodeName:e,style:{},attributes:{},childNodes:[],parentNode:null,yogaNode:"ink-virtual-text"===e?void 0:i.default.Node.create()};return"ink-text"===e&&(null===(t=n.yogaNode)||void 0===t||t.setMeasureFunc(s.bind(null,n))),n},t.appendChildNode=(e,n)=>{var r;n.parentNode&&t.removeChildNode(n.parentNode,n),n.parentNode=e,e.childNodes.push(n),n.yogaNode&&(null===(r=e.yogaNode)||void 0===r||r.insertChild(n.yogaNode,e.yogaNode.getChildCount())),"ink-text"!==e.nodeName&&"ink-virtual-text"!==e.nodeName||f(e)},t.insertBeforeNode=(e,n,r)=>{var i,o;n.parentNode&&t.removeChildNode(n.parentNode,n),n.parentNode=e;const u=e.childNodes.indexOf(r);if(u>=0)return e.childNodes.splice(u,0,n),void(n.yogaNode&&(null===(i=e.yogaNode)||void 0===i||i.insertChild(n.yogaNode,u)));e.childNodes.push(n),n.yogaNode&&(null===(o=e.yogaNode)||void 0===o||o.insertChild(n.yogaNode,e.yogaNode.getChildCount())),"ink-text"!==e.nodeName&&"ink-virtual-text"!==e.nodeName||f(e)},t.removeChildNode=(e,t)=>{var n,r;t.yogaNode&&(null===(r=null===(n=t.parentNode)||void 0===n?void 0:n.yogaNode)||void 0===r||r.removeChild(t.yogaNode)),t.parentNode=null;const i=e.childNodes.indexOf(t);i>=0&&e.childNodes.splice(i,1),"ink-text"!==e.nodeName&&"ink-virtual-text"!==e.nodeName||f(e)},t.setAttribute=(e,t,n)=>{e.attributes[t]=n},t.setStyle=(e,t)=>{e.style=t,e.yogaNode&&u.default(e.yogaNode,t)},t.createTextNode=e=>{const n={nodeName:"#text",nodeValue:e,yogaNode:void 0,parentNode:null,style:{}};return t.setTextNodeValue(n,e),n};const s=function(e,t){var n,r;const i="#text"===e.nodeName?e.nodeValue:l.default(e),u=o.default(i);if(u.width<=t)return u;if(u.width>=1&&t>0&&t<1)return u;const s=null!==(r=null===(n=e.style)||void 0===n?void 0:n.textWrap)&&void 0!==r?r:"wrap",c=a.default(i,t,s);return o.default(c)},c=e=>{var t;if(e&&e.parentNode)return null!==(t=e.yogaNode)&&void 0!==t?t:c(e.parentNode)},f=e=>{const t=c(e);null==t||t.markDirty()};t.setTextNodeValue=(e,t)=>{"string"!=typeof t&&(t=String(t)),e.nodeValue=t,f(e)}},317:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(6401));t.default=e=>e.getComputedWidth()-e.getComputedPadding(i.default.EDGE_LEFT)-e.getComputedPadding(i.default.EDGE_RIGHT)-e.getComputedBorder(i.default.EDGE_LEFT)-e.getComputedBorder(i.default.EDGE_RIGHT)},4699:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=n(7382),o=r(n(5512));t.default=()=>i.useContext(o.default)},5442:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=n(7382),o=r(n(2560));t.default=()=>{const e=i.useContext(o.default);return{enableFocus:e.enableFocus,disableFocus:e.disableFocus,focusNext:e.focusNext,focusPrevious:e.focusPrevious}}},8230:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=n(7382),o=r(n(2560)),u=r(n(1541));t.default=({isActive:e=!0,autoFocus:t=!1}={})=>{const{isRawModeSupported:n,setRawMode:r}=u.default(),{activeId:a,add:l,remove:s,activate:c,deactivate:f}=i.useContext(o.default),d=i.useMemo(()=>Math.random().toString().slice(2,7),[]);return i.useEffect(()=>(l(d,{autoFocus:t}),()=>{s(d)}),[d,t]),i.useEffect(()=>{e?c(d):f(d)},[e,d]),i.useEffect(()=>{if(n&&e)return r(!0),()=>{r(!1)}},[e]),{isFocused:Boolean(d)&&a===d}}},4495:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=n(7382),o=r(n(1541));t.default=(e,t={})=>{const{stdin:n,setRawMode:r,internal_exitOnCtrlC:u}=o.default();i.useEffect(()=>{if(!1!==t.isActive)return r(!0),()=>{r(!1)}},[t.isActive,r]),i.useEffect(()=>{if(!1===t.isActive)return;const r=t=>{let n=String(t);const r={upArrow:""===n,downArrow:""===n,leftArrow:""===n,rightArrow:""===n,pageDown:"[6~"===n,pageUp:"[5~"===n,return:"\r"===n,escape:""===n,ctrl:!1,shift:!1,tab:"\t"===n||""===n,backspace:"\b"===n,delete:""===n||"[3~"===n,meta:!1};n<=""&&!r.return&&(n=String.fromCharCode(n.charCodeAt(0)+"a".charCodeAt(0)-1),r.ctrl=!0),n.startsWith("")&&(n=n.slice(1),r.meta=!0);const i=n>="A"&&n<="Z",o=n>="А"&&n<="Я";1===n.length&&(i||o)&&(r.shift=!0),r.tab&&"[Z"===n&&(r.shift=!0),(r.tab||r.backspace||r.delete)&&(n=""),"c"===n&&r.ctrl&&u||e(n,r)};return null==n||n.on("data",r),()=>{null==n||n.off("data",r)}},[t.isActive,n,u,e])}},1686:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=n(7382),o=r(n(5001));t.default=()=>i.useContext(o.default)},1541:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=n(7382),o=r(n(1489));t.default=()=>i.useContext(o.default)},9890:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=n(7382),o=r(n(6834));t.default=()=>i.useContext(o.default)},9245:(e,t,n)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(9417);Object.defineProperty(t,"render",{enumerable:!0,get:function(){return r.default}});var i=n(5277);Object.defineProperty(t,"Box",{enumerable:!0,get:function(){return i.default}});var o=n(9146);Object.defineProperty(t,"Text",{enumerable:!0,get:function(){return o.default}});var u=n(8915);Object.defineProperty(t,"Static",{enumerable:!0,get:function(){return u.default}});var a=n(4592);Object.defineProperty(t,"Transform",{enumerable:!0,get:function(){return a.default}});var l=n(8200);Object.defineProperty(t,"Newline",{enumerable:!0,get:function(){return l.default}});var s=n(2198);Object.defineProperty(t,"Spacer",{enumerable:!0,get:function(){return s.default}});var c=n(4495);Object.defineProperty(t,"useInput",{enumerable:!0,get:function(){return c.default}});var f=n(4699);Object.defineProperty(t,"useApp",{enumerable:!0,get:function(){return f.default}});var d=n(1541);Object.defineProperty(t,"useStdin",{enumerable:!0,get:function(){return d.default}});var p=n(9890);Object.defineProperty(t,"useStdout",{enumerable:!0,get:function(){return p.default}});var h=n(1686);Object.defineProperty(t,"useStderr",{enumerable:!0,get:function(){return h.default}});var v=n(8230);Object.defineProperty(t,"useFocus",{enumerable:!0,get:function(){return v.default}});var m=n(5442);Object.defineProperty(t,"useFocusManager",{enumerable:!0,get:function(){return m.default}});var g=n(3887);Object.defineProperty(t,"measureElement",{enumerable:!0,get:function(){return g.default}})},3206:function(e,t,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n),Object.defineProperty(e,r,{enumerable:!0,get:function(){return t[n]}})}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),i=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.hasOwnProperty.call(e,n)&&r(t,e,n);return i(t,e),t},u=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const a=u(n(7382)),l=n(464),s=u(n(503)),c=u(n(7589)),f=u(n(2738)),d=u(n(2633)),p=u(n(5117)),h=u(n(5691)),v=u(n(6458)),m=u(n(8070)),g=o(n(9864)),y=u(n(9679)),_=u(n(2773)),b="false"!==process.env.CI&&f.default,w=()=>{};t.default=class{constructor(e){this.resolveExitPromise=()=>{},this.rejectExitPromise=()=>{},this.unsubscribeExit=()=>{},this.onRender=()=>{if(this.isUnmounted)return;const{output:e,outputHeight:t,staticOutput:n}=h.default(this.rootNode,this.options.stdout.columns||80),r=n&&"\n"!==n;return this.options.debug?(r&&(this.fullStaticOutput+=n),void this.options.stdout.write(this.fullStaticOutput+e)):b?(r&&this.options.stdout.write(n),void(this.lastOutput=e)):(r&&(this.fullStaticOutput+=n),t>=this.options.stdout.rows?(this.options.stdout.write(c.default.clearTerminal+this.fullStaticOutput+e),void(this.lastOutput=e)):(r&&(this.log.clear(),this.options.stdout.write(n),this.log(e)),r||e===this.lastOutput||this.throttledLog(e),void(this.lastOutput=e)))},d.default(this),this.options=e,this.rootNode=g.createNode("ink-root"),this.rootNode.onRender=e.debug?this.onRender:l.throttle(this.onRender,32,{leading:!0,trailing:!0}),this.rootNode.onImmediateRender=this.onRender,this.log=s.default.create(e.stdout),this.throttledLog=e.debug?this.log:l.throttle(this.log,void 0,{leading:!0,trailing:!0}),this.isUnmounted=!1,this.lastOutput="",this.fullStaticOutput="",this.container=p.default.createContainer(this.rootNode,!1,!1),this.unsubscribeExit=v.default(this.unmount,{alwaysLast:!1}),"true"===process.env.DEV&&p.default.injectIntoDevTools({bundleType:0,version:"16.13.1",rendererPackageName:"ink"}),e.patchConsole&&this.patchConsole(),b||(e.stdout.on("resize",this.onRender),this.unsubscribeResize=()=>{e.stdout.off("resize",this.onRender)})}render(e){const t=a.default.createElement(_.default,{stdin:this.options.stdin,stdout:this.options.stdout,stderr:this.options.stderr,writeToStdout:this.writeToStdout,writeToStderr:this.writeToStderr,exitOnCtrlC:this.options.exitOnCtrlC,onExit:this.unmount},e);p.default.updateContainer(t,this.container,null,w)}writeToStdout(e){this.isUnmounted||(this.options.debug?this.options.stdout.write(e+this.fullStaticOutput+this.lastOutput):b?this.options.stdout.write(e):(this.log.clear(),this.options.stdout.write(e),this.log(this.lastOutput)))}writeToStderr(e){if(!this.isUnmounted)return this.options.debug?(this.options.stderr.write(e),void this.options.stdout.write(this.fullStaticOutput+this.lastOutput)):void(b?this.options.stderr.write(e):(this.log.clear(),this.options.stderr.write(e),this.log(this.lastOutput)))}unmount(e){this.isUnmounted||(this.onRender(),this.unsubscribeExit(),"function"==typeof this.restoreConsole&&this.restoreConsole(),"function"==typeof this.unsubscribeResize&&this.unsubscribeResize(),b?this.options.stdout.write(this.lastOutput+"\n"):this.options.debug||this.log.done(),this.isUnmounted=!0,p.default.updateContainer(null,this.container,null,w),y.default.delete(this.options.stdout),e instanceof Error?this.rejectExitPromise(e):this.resolveExitPromise())}waitUntilExit(){return this.exitPromise||(this.exitPromise=new Promise((e,t)=>{this.resolveExitPromise=e,this.rejectExitPromise=t})),this.exitPromise}clear(){b||this.options.debug||this.log.clear()}patchConsole(){this.options.debug||(this.restoreConsole=m.default((e,t)=>{if("stdout"===e&&this.writeToStdout(t),"stderr"===e){t.startsWith("The above error occurred")||this.writeToStderr(t)}}))}}},9679:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=new WeakMap},503:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(7589)),o=r(n(1696));t.default={create:(e,{showCursor:t=!1}={})=>{let n=0,r="",u=!1;const a=a=>{t||u||(o.default.hide(),u=!0);const l=a+"\n";l!==r&&(r=l,e.write(i.default.eraseLines(n)+l),n=l.split("\n").length)};return a.clear=()=>{e.write(i.default.eraseLines(n)),r="",n=0},a.done=()=>{r="",n=0,t||(o.default.show(),u=!1)},a}}},3887:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=e=>{var t,n,r,i;return{width:null!==(n=null===(t=e.yogaNode)||void 0===t?void 0:t.getComputedWidth())&&void 0!==n?n:0,height:null!==(i=null===(r=e.yogaNode)||void 0===r?void 0:r.getComputedHeight())&&void 0!==i?i:0}}},8113:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(8949)),o={};t.default=e=>{if(0===e.length)return{width:0,height:0};if(o[e])return o[e];const t=i.default(e),n=e.split("\n").length;return o[e]={width:t,height:n},{width:t,height:n}}},4110:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(1566)),o=r(n(3262));t.default=class{constructor(e){this.writes=[];const{width:t,height:n}=e;this.width=t,this.height=n}write(e,t,n,r){const{transformers:i}=r;n&&this.writes.push({x:e,y:t,text:n,transformers:i})}get(){const e=[];for(let t=0;te.trimRight()).join("\n"),height:e.length}}}},5117:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=n(7181),o=r(n(7714)),u=r(n(6401)),a=n(9864);"true"===process.env.DEV&&n(146);const l=e=>{null==e||e.unsetMeasureFunc(),null==e||e.freeRecursive()};t.default=o.default({schedulePassiveEffects:i.unstable_scheduleCallback,cancelPassiveEffects:i.unstable_cancelCallback,now:Date.now,getRootHostContext:()=>({isInsideText:!1}),prepareForCommit:()=>{},resetAfterCommit:e=>{if(e.isStaticDirty)return e.isStaticDirty=!1,void("function"==typeof e.onImmediateRender&&e.onImmediateRender());"function"==typeof e.onRender&&e.onRender()},getChildHostContext:(e,t)=>{const n="ink-text"===t||"ink-virtual-text"===t;return e.isInsideText===n?e:{isInsideText:n}},shouldSetTextContent:()=>!1,createInstance:(e,t,n,r)=>{if(r.isInsideText&&"ink-box"===e)throw new Error(" can’t be nested inside component");const i="ink-text"===e&&r.isInsideText?"ink-virtual-text":e,o=a.createNode(i);for(const[e,n]of Object.entries(t))"children"!==e&&("style"===e?a.setStyle(o,n):"internal_transform"===e?o.internal_transform=n:"internal_static"===e?o.internal_static=!0:a.setAttribute(o,e,n));return o},createTextInstance:(e,t,n)=>{if(!n.isInsideText)throw new Error(`Text string "${e}" must be rendered inside component`);return a.createTextNode(e)},resetTextContent:()=>{},hideTextInstance:e=>{a.setTextNodeValue(e,"")},unhideTextInstance:(e,t)=>{a.setTextNodeValue(e,t)},getPublicInstance:e=>e,hideInstance:e=>{var t;null===(t=e.yogaNode)||void 0===t||t.setDisplay(u.default.DISPLAY_NONE)},unhideInstance:e=>{var t;null===(t=e.yogaNode)||void 0===t||t.setDisplay(u.default.DISPLAY_FLEX)},appendInitialChild:a.appendChildNode,appendChild:a.appendChildNode,insertBefore:a.insertBeforeNode,finalizeInitialChildren:(e,t,n,r)=>(e.internal_static&&(r.isStaticDirty=!0,r.staticNode=e),!1),supportsMutation:!0,appendChildToContainer:a.appendChildNode,insertInContainerBefore:a.insertBeforeNode,removeChildFromContainer:(e,t)=>{a.removeChildNode(e,t),l(t.yogaNode)},prepareUpdate:(e,t,n,r,i)=>{e.internal_static&&(i.isStaticDirty=!0);const o={},u=Object.keys(r);for(const e of u)if(r[e]!==n[e]){if("style"===e&&"object"==typeof r.style&&"object"==typeof n.style){const e=r.style,t=n.style,i=Object.keys(e);for(const n of i){if("borderStyle"===n||"borderColor"===n){if("object"!=typeof o.style){const e={};o.style=e}o.style.borderStyle=e.borderStyle,o.style.borderColor=e.borderColor}if(e[n]!==t[n]){if("object"!=typeof o.style){const e={};o.style=e}o.style[n]=e[n]}}continue}o[e]=r[e]}return o},commitUpdate:(e,t)=>{for(const[n,r]of Object.entries(t))"children"!==n&&("style"===n?a.setStyle(e,r):"internal_transform"===n?e.internal_transform=r:"internal_static"===n?e.internal_static=!0:a.setAttribute(e,n,r))},commitTextUpdate:(e,t,n)=>{a.setTextNodeValue(e,n)},removeChild:(e,t)=>{a.removeChildNode(e,t),l(t.yogaNode)}})},4907:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(4097)),o=r(n(9902));t.default=(e,t,n,r)=>{if("string"==typeof n.style.borderStyle){const u=n.yogaNode.getComputedWidth(),a=n.yogaNode.getComputedHeight(),l=n.style.borderColor,s=i.default[n.style.borderStyle],c=o.default(s.topLeft+s.horizontal.repeat(u-2)+s.topRight,l,"foreground"),f=(o.default(s.vertical,l,"foreground")+"\n").repeat(a-2),d=o.default(s.bottomLeft+s.horizontal.repeat(u-2)+s.bottomRight,l,"foreground");r.write(e,t,c,{transformers:[]}),r.write(e,t+1,f,{transformers:[]}),r.write(e+u-1,t+1,f,{transformers:[]}),r.write(e,t+a-1,d,{transformers:[]})}}},3782:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(6401)),o=r(n(8949)),u=r(n(9646)),a=r(n(2030)),l=r(n(317)),s=r(n(9099)),c=r(n(4907)),f=(e,t,n)=>{var r;const{offsetX:d=0,offsetY:p=0,transformers:h=[],skipStaticElements:v}=n;if(v&&e.internal_static)return;const{yogaNode:m}=e;if(m){if(m.getDisplay()===i.default.DISPLAY_NONE)return;const n=d+m.getComputedLeft(),g=p+m.getComputedTop();let y=h;if("function"==typeof e.internal_transform&&(y=[e.internal_transform,...h]),"ink-text"===e.nodeName){let i=s.default(e);if(i.length>0){const s=o.default(i),c=l.default(m);if(s>c){const t=null!==(r=e.style.textWrap)&&void 0!==r?r:"wrap";i=a.default(i,c,t)}i=((e,t)=>{var n;const r=null===(n=e.childNodes[0])||void 0===n?void 0:n.yogaNode;if(r){const e=r.getComputedLeft(),n=r.getComputedTop();t="\n".repeat(n)+u.default(t,e)}return t})(e,i),t.write(n,g,i,{transformers:y})}return}if("ink-box"===e.nodeName&&c.default(n,g,e,t),"ink-root"===e.nodeName||"ink-box"===e.nodeName)for(const r of e.childNodes)f(r,t,{offsetX:n,offsetY:g,transformers:y,skipStaticElements:v})}};t.default=f},9417:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(3206)),o=r(n(9679)),u=n(2413);t.default=(e,t)=>{const n=Object.assign({stdout:process.stdout,stdin:process.stdin,stderr:process.stderr,debug:!1,exitOnCtrlC:!0,patchConsole:!0},a(t)),r=l(n.stdout,()=>new i.default(n));return r.render(e),{rerender:r.render,unmount:()=>r.unmount(),waitUntilExit:r.waitUntilExit,cleanup:()=>o.default.delete(n.stdout),clear:r.clear}};const a=(e={})=>e instanceof u.Stream?{stdout:e,stdin:process.stdin}:e,l=(e,t)=>{let n;return o.default.has(e)?n=o.default.get(e):(n=t(),o.default.set(e,n)),n}},5691:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(6401)),o=r(n(3782)),u=r(n(4110));t.default=(e,t)=>{var n;if(e.yogaNode.setWidth(t),e.yogaNode){e.yogaNode.calculateLayout(void 0,void 0,i.default.DIRECTION_LTR);const t=new u.default({width:e.yogaNode.getComputedWidth(),height:e.yogaNode.getComputedHeight()});let r;o.default(e,t,{skipStaticElements:!0}),(null===(n=e.staticNode)||void 0===n?void 0:n.yogaNode)&&(r=new u.default({width:e.staticNode.yogaNode.getComputedWidth(),height:e.staticNode.yogaNode.getComputedHeight()}),o.default(e.staticNode,r,{skipStaticElements:!1}));const{output:a,height:l}=t.get();return{output:a,outputHeight:l,staticOutput:r?r.get().output+"\n":""}}return{output:"",outputHeight:0,staticOutput:""}}},9099:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const n=e=>{let t="";if(e.childNodes.length>0)for(const r of e.childNodes){let e="";"#text"===r.nodeName?e=r.nodeValue:("ink-text"!==r.nodeName&&"ink-virtual-text"!==r.nodeName||(e=n(r)),e.length>0&&"function"==typeof r.internal_transform&&(e=r.internal_transform(e))),t+=e}return t};t.default=n},5809:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(6401));t.default=(e,t={})=>{((e,t)=>{"position"in t&&e.setPositionType("absolute"===t.position?i.default.POSITION_TYPE_ABSOLUTE:i.default.POSITION_TYPE_RELATIVE)})(e,t),((e,t)=>{"marginLeft"in t&&e.setMargin(i.default.EDGE_START,t.marginLeft||0),"marginRight"in t&&e.setMargin(i.default.EDGE_END,t.marginRight||0),"marginTop"in t&&e.setMargin(i.default.EDGE_TOP,t.marginTop||0),"marginBottom"in t&&e.setMargin(i.default.EDGE_BOTTOM,t.marginBottom||0)})(e,t),((e,t)=>{"paddingLeft"in t&&e.setPadding(i.default.EDGE_LEFT,t.paddingLeft||0),"paddingRight"in t&&e.setPadding(i.default.EDGE_RIGHT,t.paddingRight||0),"paddingTop"in t&&e.setPadding(i.default.EDGE_TOP,t.paddingTop||0),"paddingBottom"in t&&e.setPadding(i.default.EDGE_BOTTOM,t.paddingBottom||0)})(e,t),((e,t)=>{var n;"flexGrow"in t&&e.setFlexGrow(null!==(n=t.flexGrow)&&void 0!==n?n:0),"flexShrink"in t&&e.setFlexShrink("number"==typeof t.flexShrink?t.flexShrink:1),"flexDirection"in t&&("row"===t.flexDirection&&e.setFlexDirection(i.default.FLEX_DIRECTION_ROW),"row-reverse"===t.flexDirection&&e.setFlexDirection(i.default.FLEX_DIRECTION_ROW_REVERSE),"column"===t.flexDirection&&e.setFlexDirection(i.default.FLEX_DIRECTION_COLUMN),"column-reverse"===t.flexDirection&&e.setFlexDirection(i.default.FLEX_DIRECTION_COLUMN_REVERSE)),"flexBasis"in t&&("number"==typeof t.flexBasis?e.setFlexBasis(t.flexBasis):"string"==typeof t.flexBasis?e.setFlexBasisPercent(Number.parseInt(t.flexBasis,10)):e.setFlexBasis(NaN)),"alignItems"in t&&("stretch"!==t.alignItems&&t.alignItems||e.setAlignItems(i.default.ALIGN_STRETCH),"flex-start"===t.alignItems&&e.setAlignItems(i.default.ALIGN_FLEX_START),"center"===t.alignItems&&e.setAlignItems(i.default.ALIGN_CENTER),"flex-end"===t.alignItems&&e.setAlignItems(i.default.ALIGN_FLEX_END)),"alignSelf"in t&&("auto"!==t.alignSelf&&t.alignSelf||e.setAlignSelf(i.default.ALIGN_AUTO),"flex-start"===t.alignSelf&&e.setAlignSelf(i.default.ALIGN_FLEX_START),"center"===t.alignSelf&&e.setAlignSelf(i.default.ALIGN_CENTER),"flex-end"===t.alignSelf&&e.setAlignSelf(i.default.ALIGN_FLEX_END)),"justifyContent"in t&&("flex-start"!==t.justifyContent&&t.justifyContent||e.setJustifyContent(i.default.JUSTIFY_FLEX_START),"center"===t.justifyContent&&e.setJustifyContent(i.default.JUSTIFY_CENTER),"flex-end"===t.justifyContent&&e.setJustifyContent(i.default.JUSTIFY_FLEX_END),"space-between"===t.justifyContent&&e.setJustifyContent(i.default.JUSTIFY_SPACE_BETWEEN),"space-around"===t.justifyContent&&e.setJustifyContent(i.default.JUSTIFY_SPACE_AROUND))})(e,t),((e,t)=>{var n,r;"width"in t&&("number"==typeof t.width?e.setWidth(t.width):"string"==typeof t.width?e.setWidthPercent(Number.parseInt(t.width,10)):e.setWidthAuto()),"height"in t&&("number"==typeof t.height?e.setHeight(t.height):"string"==typeof t.height?e.setHeightPercent(Number.parseInt(t.height,10)):e.setHeightAuto()),"minWidth"in t&&("string"==typeof t.minWidth?e.setMinWidthPercent(Number.parseInt(t.minWidth,10)):e.setMinWidth(null!==(n=t.minWidth)&&void 0!==n?n:0)),"minHeight"in t&&("string"==typeof t.minHeight?e.setMinHeightPercent(Number.parseInt(t.minHeight,10)):e.setMinHeight(null!==(r=t.minHeight)&&void 0!==r?r:0))})(e,t),((e,t)=>{"display"in t&&e.setDisplay("flex"===t.display?i.default.DISPLAY_FLEX:i.default.DISPLAY_NONE)})(e,t),((e,t)=>{if("borderStyle"in t){const n="string"==typeof t.borderStyle?1:0;e.setBorder(i.default.EDGE_TOP,n),e.setBorder(i.default.EDGE_BOTTOM,n),e.setBorder(i.default.EDGE_LEFT,n),e.setBorder(i.default.EDGE_RIGHT,n)}})(e,t)}},2030:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});const i=r(n(4332)),o=r(n(5301)),u={};t.default=(e,t,n)=>{const r=e+String(t)+String(n);if(u[r])return u[r];let a=e;if("wrap"===n&&(a=i.default(e,t,{trim:!1,hard:!0})),n.startsWith("truncate")){let r="end";"truncate-middle"===n&&(r="middle"),"truncate-start"===n&&(r="start"),a=o.default(e,t,{position:r})}return u[r]=a,a}},5767:(e,t,n)=>{ /** @license React v0.24.0 * react-reconciler.production.min.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ e.exports=function t(r){"use strict";var i=n(9381),o=n(7382),u=n(7181);function a(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;nOe||(e.current=Ae[Oe],Ae[Oe]=null,Oe--)}function Ie(e,t){Oe++,Ae[Oe]=e.current,e.current=t}var Ne={},Me={current:Ne},Re={current:!1},Fe=Ne;function Le(e,t){var n=e.type.contextTypes;if(!n)return Ne;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var i,o={};for(i in n)o[i]=t[i];return r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function Be(e){return null!=(e=e.childContextTypes)}function je(e){Pe(Re),Pe(Me)}function Ue(e){Pe(Re),Pe(Me)}function ze(e,t,n){if(Me.current!==Ne)throw Error(a(168));Ie(Me,t),Ie(Re,n)}function We(e,t,n){var r=e.stateNode;if(e=t.childContextTypes,"function"!=typeof r.getChildContext)return n;for(var o in r=r.getChildContext())if(!(o in e))throw Error(a(108,C(t)||"Unknown",o));return i({},n,{},r)}function He(e){var t=e.stateNode;return t=t&&t.__reactInternalMemoizedMergedChildContext||Ne,Fe=Me.current,Ie(Me,t),Ie(Re,Re.current),!0}function Ve(e,t,n){var r=e.stateNode;if(!r)throw Error(a(169));n?(t=We(e,t,Fe),r.__reactInternalMemoizedMergedChildContext=t,Pe(Re),Pe(Me),Ie(Me,t)):Pe(Re),Ie(Re,n)}var qe=u.unstable_runWithPriority,Ge=u.unstable_scheduleCallback,$e=u.unstable_cancelCallback,Ye=u.unstable_shouldYield,Ke=u.unstable_requestPaint,Xe=u.unstable_now,Qe=u.unstable_getCurrentPriorityLevel,Je=u.unstable_ImmediatePriority,Ze=u.unstable_UserBlockingPriority,et=u.unstable_NormalPriority,tt=u.unstable_LowPriority,nt=u.unstable_IdlePriority,rt={},it=void 0!==Ke?Ke:function(){},ot=null,ut=null,at=!1,lt=Xe(),st=1e4>lt?Xe:function(){return Xe()-lt};function ct(){switch(Qe()){case Je:return 99;case Ze:return 98;case et:return 97;case tt:return 96;case nt:return 95;default:throw Error(a(332))}}function ft(e){switch(e){case 99:return Je;case 98:return Ze;case 97:return et;case 96:return tt;case 95:return nt;default:throw Error(a(332))}}function dt(e,t){return e=ft(e),qe(e,t)}function pt(e,t,n){return e=ft(e),Ge(e,t,n)}function ht(e){return null===ot?(ot=[e],ut=Ge(Je,mt)):ot.push(e),rt}function vt(){if(null!==ut){var e=ut;ut=null,$e(e)}mt()}function mt(){if(!at&&null!==ot){at=!0;var e=0;try{var t=ot;dt(99,(function(){for(;e=t&&(dr=!0),e.firstContext=null)}function It(e,t){if(kt!==e&&!1!==t&&0!==t)if("number"==typeof t&&1073741823!==t||(kt=e,t=1073741823),t={context:e,observedBits:t,next:null},null===Ct){if(null===St)throw Error(a(308));Ct=t,St.dependencies={expirationTime:0,firstContext:t,responders:null}}else Ct=Ct.next=t;return q?e._currentValue:e._currentValue2}var Nt=!1;function Mt(e){return{baseState:e,firstUpdate:null,lastUpdate:null,firstCapturedUpdate:null,lastCapturedUpdate:null,firstEffect:null,lastEffect:null,firstCapturedEffect:null,lastCapturedEffect:null}}function Rt(e){return{baseState:e.baseState,firstUpdate:e.firstUpdate,lastUpdate:e.lastUpdate,firstCapturedUpdate:null,lastCapturedUpdate:null,firstEffect:null,lastEffect:null,firstCapturedEffect:null,lastCapturedEffect:null}}function Ft(e,t){return{expirationTime:e,suspenseConfig:t,tag:0,payload:null,callback:null,next:null,nextEffect:null}}function Lt(e,t){null===e.lastUpdate?e.firstUpdate=e.lastUpdate=t:(e.lastUpdate.next=t,e.lastUpdate=t)}function Bt(e,t){var n=e.alternate;if(null===n){var r=e.updateQueue,i=null;null===r&&(r=e.updateQueue=Mt(e.memoizedState))}else r=e.updateQueue,i=n.updateQueue,null===r?null===i?(r=e.updateQueue=Mt(e.memoizedState),i=n.updateQueue=Mt(n.memoizedState)):r=e.updateQueue=Rt(i):null===i&&(i=n.updateQueue=Rt(r));null===i||r===i?Lt(r,t):null===r.lastUpdate||null===i.lastUpdate?(Lt(r,t),Lt(i,t)):(Lt(r,t),i.lastUpdate=t)}function jt(e,t){var n=e.updateQueue;null===(n=null===n?e.updateQueue=Mt(e.memoizedState):Ut(e,n)).lastCapturedUpdate?n.firstCapturedUpdate=n.lastCapturedUpdate=t:(n.lastCapturedUpdate.next=t,n.lastCapturedUpdate=t)}function Ut(e,t){var n=e.alternate;return null!==n&&t===n.updateQueue&&(t=e.updateQueue=Rt(t)),t}function zt(e,t,n,r,o,u){switch(n.tag){case 1:return"function"==typeof(e=n.payload)?e.call(u,r,o):e;case 3:e.effectTag=-4097&e.effectTag|64;case 0:if(null==(o="function"==typeof(e=n.payload)?e.call(u,r,o):e))break;return i({},r,o);case 2:Nt=!0}return r}function Wt(e,t,n,r,i){Nt=!1;for(var o=(t=Ut(e,t)).baseState,u=null,a=0,l=t.firstUpdate,s=o;null!==l;){var c=l.expirationTime;cd?(p=f,f=null):p=f.sibling;var h=m(i,f,a[d],l);if(null===h){null===f&&(f=p);break}e&&f&&null===h.alternate&&t(i,f),u=o(h,u,d),null===c?s=h:c.sibling=h,c=h,f=p}if(d===a.length)return n(i,f),s;if(null===f){for(;dp?(h=d,d=null):h=d.sibling;var _=m(i,d,y.value,s);if(null===_){null===d&&(d=h);break}e&&d&&null===_.alternate&&t(i,d),u=o(_,u,p),null===f?c=_:f.sibling=_,f=_,d=h}if(y.done)return n(i,d),c;if(null===d){for(;!y.done;p++,y=l.next())null!==(y=v(i,y.value,s))&&(u=o(y,u,p),null===f?c=y:f.sibling=y,f=y);return c}for(d=r(i,d);!y.done;p++,y=l.next())null!==(y=g(d,i,p,y.value,s))&&(e&&null!==y.alternate&&d.delete(null===y.key?p:y.key),u=o(y,u,p),null===f?c=y:f.sibling=y,f=y);return e&&d.forEach((function(e){return t(i,e)})),c}return function(e,r,o,l){var s="object"==typeof o&&null!==o&&o.type===d&&null===o.key;s&&(o=o.props.children);var p="object"==typeof o&&null!==o;if(p)switch(o.$$typeof){case c:e:{for(p=o.key,s=r;null!==s;){if(s.key===p){if(7===s.tag?o.type===d:s.elementType===o.type){n(e,s.sibling),(r=i(s,o.type===d?o.props.children:o.props)).ref=en(e,s,o),r.return=e,e=r;break e}n(e,s);break}t(e,s),s=s.sibling}o.type===d?((r=so(o.props.children,e.mode,l,o.key)).return=e,e=r):((l=lo(o.type,o.key,o.props,null,e.mode,l)).ref=en(e,r,o),l.return=e,e=l)}return u(e);case f:e:{for(s=o.key;null!==r;){if(r.key===s){if(4===r.tag&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),(r=i(r,o.children||[])).return=e,e=r;break e}n(e,r);break}t(e,r),r=r.sibling}(r=fo(o,e.mode,l)).return=e,e=r}return u(e)}if("string"==typeof o||"number"==typeof o)return o=""+o,null!==r&&6===r.tag?(n(e,r.sibling),(r=i(r,o)).return=e,e=r):(n(e,r),(r=co(o,e.mode,l)).return=e,e=r),u(e);if(Zt(o))return y(e,r,o,l);if(S(o))return _(e,r,o,l);if(p&&tn(e,o),void 0===o&&!s)switch(e.tag){case 1:case 0:throw e=e.type,Error(a(152,e.displayName||e.name||"Component"))}return n(e,r)}}var rn=nn(!0),on=nn(!1),un={},an={current:un},ln={current:un},sn={current:un};function cn(e){if(e===un)throw Error(a(174));return e}function fn(e,t){Ie(sn,t),Ie(ln,e),Ie(an,un),t=P(t),Pe(an),Ie(an,t)}function dn(e){Pe(an),Pe(ln),Pe(sn)}function pn(e){var t=cn(sn.current),n=cn(an.current);n!==(t=I(n,e.type,t))&&(Ie(ln,e),Ie(an,t))}function hn(e){ln.current===e&&(Pe(an),Pe(ln))}var vn={current:0};function mn(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||ye(n)||_e(n)))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(0!=(64&t.effectTag))return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function gn(e,t){return{responder:e,props:t}}var yn=l.ReactCurrentDispatcher,_n=l.ReactCurrentBatchConfig,bn=0,wn=null,En=null,Dn=null,Sn=null,Cn=null,kn=null,Tn=0,xn=null,An=0,On=!1,Pn=null,In=0;function Nn(){throw Error(a(321))}function Mn(e,t){if(null===t)return!1;for(var n=0;nTn&&zi(Tn=f)):(Ui(f,s.suspenseConfig),o=s.eagerReducer===e?s.eagerState:e(o,s.action)),u=s,s=s.next}while(null!==s&&s!==r);c||(l=u,i=o),_t(o,t.memoizedState)||(dr=!0),t.memoizedState=o,t.baseUpdate=l,t.baseState=i,n.lastRenderedState=o}return[t.memoizedState,n.dispatch]}function zn(e){var t=Ln();return"function"==typeof e&&(e=e()),t.memoizedState=t.baseState=e,e=(e=t.queue={last:null,dispatch:null,lastRenderedReducer:jn,lastRenderedState:e}).dispatch=Jn.bind(null,wn,e),[t.memoizedState,e]}function Wn(e){return Un(jn)}function Hn(e,t,n,r){return e={tag:e,create:t,destroy:n,deps:r,next:null},null===xn?(xn={lastEffect:null}).lastEffect=e.next=e:null===(t=xn.lastEffect)?xn.lastEffect=e.next=e:(n=t.next,t.next=e,e.next=n,xn.lastEffect=e),e}function Vn(e,t,n,r){var i=Ln();An|=e,i.memoizedState=Hn(t,n,void 0,void 0===r?null:r)}function qn(e,t,n,r){var i=Bn();r=void 0===r?null:r;var o=void 0;if(null!==En){var u=En.memoizedState;if(o=u.destroy,null!==r&&Mn(r,u.deps))return void Hn(0,n,o,r)}An|=e,i.memoizedState=Hn(t,n,o,r)}function Gn(e,t){return Vn(516,192,e,t)}function $n(e,t){return qn(516,192,e,t)}function Yn(e,t){return"function"==typeof t?(e=e(),t(e),function(){t(null)}):null!=t?(e=e(),t.current=e,function(){t.current=null}):void 0}function Kn(){}function Xn(e,t){return Ln().memoizedState=[e,void 0===t?null:t],e}function Qn(e,t){var n=Bn();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&Mn(t,r[1])?r[0]:(n.memoizedState=[e,t],e)}function Jn(e,t,n){if(!(25>In))throw Error(a(301));var r=e.alternate;if(e===wn||null!==r&&r===wn)if(On=!0,e={expirationTime:bn,suspenseConfig:null,action:n,eagerReducer:null,eagerState:null,next:null},null===Pn&&(Pn=new Map),void 0===(n=Pn.get(t)))Pn.set(t,e);else{for(t=n;null!==t.next;)t=t.next;t.next=e}else{var i=xi(),o=qt.suspense;o={expirationTime:i=Ai(i,e,o),suspenseConfig:o,action:n,eagerReducer:null,eagerState:null,next:null};var u=t.last;if(null===u)o.next=o;else{var l=u.next;null!==l&&(o.next=l),u.next=o}if(t.last=o,0===e.expirationTime&&(null===r||0===r.expirationTime)&&null!==(r=t.lastRenderedReducer))try{var s=t.lastRenderedState,c=r(s,n);if(o.eagerReducer=r,o.eagerState=c,_t(c,s))return}catch(e){}Oi(e,i)}}var Zn={readContext:It,useCallback:Nn,useContext:Nn,useEffect:Nn,useImperativeHandle:Nn,useLayoutEffect:Nn,useMemo:Nn,useReducer:Nn,useRef:Nn,useState:Nn,useDebugValue:Nn,useResponder:Nn,useDeferredValue:Nn,useTransition:Nn},er={readContext:It,useCallback:Xn,useContext:It,useEffect:Gn,useImperativeHandle:function(e,t,n){return n=null!=n?n.concat([e]):null,Vn(4,36,Yn.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Vn(4,36,e,t)},useMemo:function(e,t){var n=Ln();return t=void 0===t?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Ln();return t=void 0!==n?n(t):t,r.memoizedState=r.baseState=t,e=(e=r.queue={last:null,dispatch:null,lastRenderedReducer:e,lastRenderedState:t}).dispatch=Jn.bind(null,wn,e),[r.memoizedState,e]},useRef:function(e){return e={current:e},Ln().memoizedState=e},useState:zn,useDebugValue:Kn,useResponder:gn,useDeferredValue:function(e,t){var n=zn(e),r=n[0],i=n[1];return Gn((function(){u.unstable_next((function(){var n=_n.suspense;_n.suspense=void 0===t?null:t;try{i(e)}finally{_n.suspense=n}}))}),[e,t]),r},useTransition:function(e){var t=zn(!1),n=t[0],r=t[1];return[Xn((function(t){r(!0),u.unstable_next((function(){var n=_n.suspense;_n.suspense=void 0===e?null:e;try{r(!1),t()}finally{_n.suspense=n}}))}),[e,n]),n]}},tr={readContext:It,useCallback:Qn,useContext:It,useEffect:$n,useImperativeHandle:function(e,t,n){return n=null!=n?n.concat([e]):null,qn(4,36,Yn.bind(null,t,e),n)},useLayoutEffect:function(e,t){return qn(4,36,e,t)},useMemo:function(e,t){var n=Bn();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&Mn(t,r[1])?r[0]:(e=e(),n.memoizedState=[e,t],e)},useReducer:Un,useRef:function(){return Bn().memoizedState},useState:Wn,useDebugValue:Kn,useResponder:gn,useDeferredValue:function(e,t){var n=Wn(),r=n[0],i=n[1];return $n((function(){u.unstable_next((function(){var n=_n.suspense;_n.suspense=void 0===t?null:t;try{i(e)}finally{_n.suspense=n}}))}),[e,t]),r},useTransition:function(e){var t=Wn(),n=t[0],r=t[1];return[Qn((function(t){r(!0),u.unstable_next((function(){var n=_n.suspense;_n.suspense=void 0===e?null:e;try{r(!1),t()}finally{_n.suspense=n}}))}),[e,n]),n]}},nr=null,rr=null,ir=!1;function or(e,t){var n=oo(5,null,null,0);n.elementType="DELETED",n.type="DELETED",n.stateNode=t,n.return=e,n.effectTag=8,null!==e.lastEffect?(e.lastEffect.nextEffect=n,e.lastEffect=n):e.firstEffect=e.lastEffect=n}function ur(e,t){switch(e.tag){case 5:return null!==(t=me(t,e.type,e.pendingProps))&&(e.stateNode=t,!0);case 6:return null!==(t=ge(t,e.pendingProps))&&(e.stateNode=t,!0);case 13:default:return!1}}function ar(e){if(ir){var t=rr;if(t){var n=t;if(!ur(e,t)){if(!(t=be(n))||!ur(e,t))return e.effectTag=-1025&e.effectTag|2,ir=!1,void(nr=e);or(nr,n)}nr=e,rr=we(t)}else e.effectTag=-1025&e.effectTag|2,ir=!1,nr=e}}function lr(e){for(e=e.return;null!==e&&5!==e.tag&&3!==e.tag&&13!==e.tag;)e=e.return;nr=e}function sr(e){if(!Y||e!==nr)return!1;if(!ir)return lr(e),ir=!0,!1;var t=e.type;if(5!==e.tag||"head"!==t&&"body"!==t&&!j(t,e.memoizedProps))for(t=rr;t;)or(e,t),t=be(t);if(lr(e),13===e.tag){if(!Y)throw Error(a(316));if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(a(317));rr=Se(e)}else rr=nr?be(e.stateNode):null;return!0}function cr(){Y&&(rr=nr=null,ir=!1)}var fr=l.ReactCurrentOwner,dr=!1;function pr(e,t,n,r){t.child=null===e?on(t,null,n,r):rn(t,e.child,n,r)}function hr(e,t,n,r,i){n=n.render;var o=t.ref;return Pt(t,i),r=Rn(e,t,n,r,o,i),null===e||dr?(t.effectTag|=1,pr(e,t,r,i),t.child):(t.updateQueue=e.updateQueue,t.effectTag&=-517,e.expirationTime<=i&&(e.expirationTime=0),Pr(e,t,i))}function vr(e,t,n,r,i,o){if(null===e){var u=n.type;return"function"!=typeof u||uo(u)||void 0!==u.defaultProps||null!==n.compare||void 0!==n.defaultProps?((e=lo(n.type,null,r,null,t.mode,o)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=u,mr(e,t,u,r,i,o))}return u=e.child,it)&&Si.set(e,t))}}function Pi(e,t){e.expirationTime(e=e.nextKnownPendingLevel)?t:e:t}function Ni(e){if(0!==e.lastExpiredTime)e.callbackExpirationTime=1073741823,e.callbackPriority=99,e.callbackNode=ht(Ri.bind(null,e));else{var t=Ii(e),n=e.callbackNode;if(0===t)null!==n&&(e.callbackNode=null,e.callbackExpirationTime=0,e.callbackPriority=90);else{var r=xi();if(1073741823===t?r=99:1===t||2===t?r=95:r=0>=(r=10*(1073741821-t)-10*(1073741821-r))?99:250>=r?98:5250>=r?97:95,null!==n){var i=e.callbackPriority;if(e.callbackExpirationTime===t&&i>=r)return;n!==rt&&$e(n)}e.callbackExpirationTime=t,e.callbackPriority=r,t=1073741823===t?ht(Ri.bind(null,e)):pt(r,Mi.bind(null,e),{timeout:10*(1073741821-t)-st()}),e.callbackNode=t}}}function Mi(e,t){if(Ti=0,t)return go(e,t=xi()),Ni(e),null;var n=Ii(e);if(0!==n){if(t=e.callbackNode,0!=(48&oi))throw Error(a(327));if(Xi(),e===ui&&n===li||Li(e,n),null!==ai){var r=oi;oi|=ii;for(var i=ji();;)try{Hi();break}catch(t){Bi(e,t)}if(Tt(),oi=r,ni.current=i,1===si)throw t=ci,Li(e,n),vo(e,n),Ni(e),t;if(null===ai)switch(i=e.finishedWork=e.current.alternate,e.finishedExpirationTime=n,r=si,ui=null,r){case 0:case 1:throw Error(a(345));case 2:go(e,2=n){e.lastPingedTime=n,Li(e,n);break}}if(0!==(o=Ii(e))&&o!==n)break;if(0!==r&&r!==n){e.lastPingedTime=r;break}e.timeoutHandle=W($i.bind(null,e),i);break}$i(e);break;case 4:if(vo(e,n),n===(r=e.lastSuspendedTime)&&(e.nextKnownPendingLevel=Gi(i)),vi&&(0===(i=e.lastPingedTime)||i>=n)){e.lastPingedTime=n,Li(e,n);break}if(0!==(i=Ii(e))&&i!==n)break;if(0!==r&&r!==n){e.lastPingedTime=r;break}if(1073741823!==di?r=10*(1073741821-di)-st():1073741823===fi?r=0:(r=10*(1073741821-fi)-5e3,0>(r=(i=st())-r)&&(r=0),(n=10*(1073741821-n)-i)<(r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*ti(r/1960))-r)&&(r=n)),10=(r=0|u.busyMinDurationMs)?r=0:(i=0|u.busyDelayMs,r=(o=st()-(10*(1073741821-o)-(0|u.timeoutMs||5e3)))<=i?0:i+r-o),10 component higher in the tree to provide a loading indicator or placeholder to display."+xe(i))}5!==si&&(si=2),o=Fr(o,i),l=r;do{switch(l.tag){case 3:u=o,l.effectTag|=4096,l.expirationTime=t,jt(l,Jr(l,u,t));break e;case 1:u=o;var g=l.type,y=l.stateNode;if(0==(64&l.effectTag)&&("function"==typeof g.getDerivedStateFromError||null!==y&&"function"==typeof y.componentDidCatch&&(null===bi||!bi.has(y)))){l.effectTag|=4096,l.expirationTime=t,jt(l,Zr(l,u,t));break e}}l=l.return}while(null!==l)}ai=qi(ai)}catch(e){t=e;continue}break}}function ji(){var e=ni.current;return ni.current=Zn,null===e?Zn:e}function Ui(e,t){ehi&&(hi=e)}function Wi(){for(;null!==ai;)ai=Vi(ai)}function Hi(){for(;null!==ai&&!Ye();)ai=Vi(ai)}function Vi(e){var t=ei(e.alternate,e,li);return e.memoizedProps=e.pendingProps,null===t&&(t=qi(e)),ri.current=null,t}function qi(e){ai=e;do{var t=ai.alternate;if(e=ai.return,0==(2048&ai.effectTag)){e:{var n=t,r=li,i=(t=ai).pendingProps;switch(t.tag){case 2:case 16:break;case 15:case 0:break;case 1:Be(t.type)&&je();break;case 3:dn(),Ue(),(i=t.stateNode).pendingContext&&(i.context=i.pendingContext,i.pendingContext=null),(null===n||null===n.child)&&sr(t)&&Ir(t),Dr(t);break;case 5:hn(t);var o=cn(sn.current);if(r=t.type,null!==n&&null!=t.stateNode)Sr(n,t,r,i,o),n.ref!==t.ref&&(t.effectTag|=128);else if(i){if(n=cn(an.current),sr(t)){if(i=t,!Y)throw Error(a(175));n=Ee(i.stateNode,i.type,i.memoizedProps,o,n,i),i.updateQueue=n,(n=null!==n)&&Ir(t)}else{var u=R(r,i,o,n,t);Er(u,t,!1,!1),t.stateNode=u,L(u,r,i,o,n)&&Ir(t)}null!==t.ref&&(t.effectTag|=128)}else if(null===t.stateNode)throw Error(a(166));break;case 6:if(n&&null!=t.stateNode)Cr(n,t,n.memoizedProps,i);else{if("string"!=typeof i&&null===t.stateNode)throw Error(a(166));if(n=cn(sn.current),o=cn(an.current),sr(t)){if(n=t,!Y)throw Error(a(176));(n=De(n.stateNode,n.memoizedProps,n))&&Ir(t)}else t.stateNode=z(i,n,o,t)}break;case 11:break;case 13:if(Pe(vn),i=t.memoizedState,0!=(64&t.effectTag)){t.expirationTime=r;break e}i=null!==i,o=!1,null===n?void 0!==t.memoizedProps.fallback&&sr(t):(o=null!==(r=n.memoizedState),i||null===r||null!==(r=n.child.sibling)&&(null!==(u=t.firstEffect)?(t.firstEffect=r,r.nextEffect=u):(t.firstEffect=t.lastEffect=r,r.nextEffect=null),r.effectTag=8)),i&&!o&&0!=(2&t.mode)&&(null===n&&!0!==t.memoizedProps.unstable_avoidThisFallback||0!=(1&vn.current)?0===si&&(si=3):(0!==si&&3!==si||(si=4),0!==hi&&null!==ui&&(vo(ui,li),mo(ui,hi)))),$&&i&&(t.effectTag|=4),G&&(i||o)&&(t.effectTag|=4);break;case 7:case 8:case 12:break;case 4:dn(),Dr(t);break;case 10:At(t);break;case 9:case 14:break;case 17:Be(t.type)&&je();break;case 19:if(Pe(vn),null===(i=t.memoizedState))break;if(o=0!=(64&t.effectTag),null===(u=i.rendering)){if(o)Mr(i,!1);else if(0!==si||null!==n&&0!=(64&n.effectTag))for(n=t.child;null!==n;){if(null!==(u=mn(n))){for(t.effectTag|=64,Mr(i,!1),null!==(n=u.updateQueue)&&(t.updateQueue=n,t.effectTag|=4),null===i.lastEffect&&(t.firstEffect=null),t.lastEffect=i.lastEffect,n=r,i=t.child;null!==i;)r=n,(o=i).effectTag&=2,o.nextEffect=null,o.firstEffect=null,o.lastEffect=null,null===(u=o.alternate)?(o.childExpirationTime=0,o.expirationTime=r,o.child=null,o.memoizedProps=null,o.memoizedState=null,o.updateQueue=null,o.dependencies=null):(o.childExpirationTime=u.childExpirationTime,o.expirationTime=u.expirationTime,o.child=u.child,o.memoizedProps=u.memoizedProps,o.memoizedState=u.memoizedState,o.updateQueue=u.updateQueue,r=u.dependencies,o.dependencies=null===r?null:{expirationTime:r.expirationTime,firstContext:r.firstContext,responders:r.responders}),i=i.sibling;Ie(vn,1&vn.current|2),t=t.child;break e}n=n.sibling}}else{if(!o)if(null!==(n=mn(u))){if(t.effectTag|=64,o=!0,null!==(n=n.updateQueue)&&(t.updateQueue=n,t.effectTag|=4),Mr(i,!0),null===i.tail&&"hidden"===i.tailMode&&!u.alternate){null!==(t=t.lastEffect=i.lastEffect)&&(t.nextEffect=null);break}}else st()>i.tailExpiration&&1i&&(i=r),(u=o.childExpirationTime)>i&&(i=u),o=o.sibling;n.childExpirationTime=i}if(null!==t)return t;null!==e&&0==(2048&e.effectTag)&&(null===e.firstEffect&&(e.firstEffect=ai.firstEffect),null!==ai.lastEffect&&(null!==e.lastEffect&&(e.lastEffect.nextEffect=ai.firstEffect),e.lastEffect=ai.lastEffect),1(e=e.childExpirationTime)?t:e}function $i(e){var t=ct();return dt(99,Yi.bind(null,e,t)),null}function Yi(e,t){do{Xi()}while(null!==Ei);if(0!=(48&oi))throw Error(a(327));var n=e.finishedWork,r=e.finishedExpirationTime;if(null===n)return null;if(e.finishedWork=null,e.finishedExpirationTime=0,n===e.current)throw Error(a(177));e.callbackNode=null,e.callbackExpirationTime=0,e.callbackPriority=90,e.nextKnownPendingLevel=0;var i=Gi(n);if(e.firstPendingTime=i,r<=e.lastSuspendedTime?e.firstSuspendedTime=e.lastSuspendedTime=e.nextKnownPendingLevel=0:r<=e.firstSuspendedTime&&(e.firstSuspendedTime=r-1),r<=e.lastPingedTime&&(e.lastPingedTime=0),r<=e.lastExpiredTime&&(e.lastExpiredTime=0),e===ui&&(ai=ui=null,li=0),1=n?Tr(e,t,n):(Ie(vn,1&vn.current),null!==(t=Pr(e,t,n))?t.sibling:null);Ie(vn,1&vn.current);break;case 19:if(r=t.childExpirationTime>=n,0!=(64&e.effectTag)){if(r)return Or(e,t,n);t.effectTag|=64}if(null!==(i=t.memoizedState)&&(i.rendering=null,i.tail=null),Ie(vn,vn.current),!r)return null}return Pr(e,t,n)}dr=!1}}else dr=!1;switch(t.expirationTime=0,t.tag){case 2:if(r=t.type,null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),e=t.pendingProps,i=Le(t,Me.current),Pt(t,n),i=Rn(null,t,r,e,i,n),t.effectTag|=1,"object"==typeof i&&null!==i&&"function"==typeof i.render&&void 0===i.$$typeof){if(t.tag=1,Fn(),Be(r)){var o=!0;He(t)}else o=!1;t.memoizedState=null!==i.state&&void 0!==i.state?i.state:null;var u=r.getDerivedStateFromProps;"function"==typeof u&&$t(t,r,u,e),i.updater=Yt,t.stateNode=i,i._reactInternalFiber=t,Jt(t,r,e,n),t=br(null,t,r,!0,o,n)}else t.tag=0,pr(null,t,i,n),t=t.child;return t;case 16:if(i=t.elementType,null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),e=t.pendingProps,function(e){if(-1===e._status){e._status=0;var t=e._ctor;t=t(),e._result=t,t.then((function(t){0===e._status&&(t=t.default,e._status=1,e._result=t)}),(function(t){0===e._status&&(e._status=2,e._result=t)}))}}(i),1!==i._status)throw i._result;switch(i=i._result,t.type=i,o=t.tag=function(e){if("function"==typeof e)return uo(e)?1:0;if(null!=e){if((e=e.$$typeof)===y)return 11;if(e===w)return 14}return 2}(i),e=Et(i,e),o){case 0:t=yr(null,t,i,e,n);break;case 1:t=_r(null,t,i,e,n);break;case 11:t=hr(null,t,i,e,n);break;case 14:t=vr(null,t,i,Et(i.type,e),r,n);break;default:throw Error(a(306,i,""))}return t;case 0:return r=t.type,i=t.pendingProps,yr(e,t,r,i=t.elementType===r?i:Et(r,i),n);case 1:return r=t.type,i=t.pendingProps,_r(e,t,r,i=t.elementType===r?i:Et(r,i),n);case 3:if(wr(t),null===(r=t.updateQueue))throw Error(a(282));if(i=null!==(i=t.memoizedState)?i.element:null,Wt(t,r,t.pendingProps,null,n),(r=t.memoizedState.element)===i)cr(),t=Pr(e,t,n);else{if((i=t.stateNode.hydrate)&&(Y?(rr=we(t.stateNode.containerInfo),nr=t,i=ir=!0):i=!1),i)for(n=on(t,null,r,n),t.child=n;n;)n.effectTag=-3&n.effectTag|1024,n=n.sibling;else pr(e,t,r,n),cr();t=t.child}return t;case 5:return pn(t),null===e&&ar(t),r=t.type,i=t.pendingProps,o=null!==e?e.memoizedProps:null,u=i.children,j(r,i)?u=null:null!==o&&j(r,o)&&(t.effectTag|=16),gr(e,t),4&t.mode&&1!==n&&U(r,i)?(t.expirationTime=t.childExpirationTime=1,t=null):(pr(e,t,u,n),t=t.child),t;case 6:return null===e&&ar(t),null;case 13:return Tr(e,t,n);case 4:return fn(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=rn(t,null,r,n):pr(e,t,r,n),t.child;case 11:return r=t.type,i=t.pendingProps,hr(e,t,r,i=t.elementType===r?i:Et(r,i),n);case 7:return pr(e,t,t.pendingProps,n),t.child;case 8:case 12:return pr(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,i=t.pendingProps,u=t.memoizedProps,xt(t,o=i.value),null!==u){var l=u.value;if(0===(o=_t(l,o)?0:0|("function"==typeof r._calculateChangedBits?r._calculateChangedBits(l,o):1073741823))){if(u.children===i.children&&!Re.current){t=Pr(e,t,n);break e}}else for(null!==(l=t.child)&&(l.return=t);null!==l;){var s=l.dependencies;if(null!==s){u=l.child;for(var c=s.firstContext;null!==c;){if(c.context===r&&0!=(c.observedBits&o)){1===l.tag&&((c=Ft(n,null)).tag=2,Bt(l,c)),l.expirationTime=t&&e<=t}function vo(e,t){var n=e.firstSuspendedTime,r=e.lastSuspendedTime;nt||0===n)&&(e.lastSuspendedTime=t),t<=e.lastPingedTime&&(e.lastPingedTime=0),t<=e.lastExpiredTime&&(e.lastExpiredTime=0)}function mo(e,t){t>e.firstPendingTime&&(e.firstPendingTime=t);var n=e.firstSuspendedTime;0!==n&&(t>=n?e.firstSuspendedTime=e.lastSuspendedTime=e.nextKnownPendingLevel=0:t>=e.lastSuspendedTime&&(e.lastSuspendedTime=t+1),t>e.nextKnownPendingLevel&&(e.nextKnownPendingLevel=t))}function go(e,t){var n=e.lastExpiredTime;(0===n||n>t)&&(e.lastExpiredTime=t)}function yo(e){var t=e._reactInternalFiber;if(void 0===t){if("function"==typeof e.render)throw Error(a(188));throw Error(a(268,Object.keys(e)))}return null===(e=A(t))?null:e.stateNode}function _o(e,t){null!==(e=e.memoizedState)&&null!==e.dehydrated&&e.retryTime{"use strict";e.exports=n(5767)},3296:(e,t,n)=>{"use strict";const r=n(5760);r.createWebSocketStream=n(6387),r.Server=n(43),r.Receiver=n(1762),r.Sender=n(9576),e.exports=r},8716:(e,t,n)=>{"use strict";const{EMPTY_BUFFER:r}=n(5739);function i(e,t){if(0===e.length)return r;if(1===e.length)return e[0];const n=Buffer.allocUnsafe(t);let i=0;for(let t=0;t{"use strict";e.exports={BINARY_TYPES:["nodebuffer","arraybuffer","fragments"],GUID:"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",kStatusCode:Symbol("status-code"),kWebSocket:Symbol("websocket"),EMPTY_BUFFER:Buffer.alloc(0),NOOP:()=>{}}},7002:e=>{"use strict";class t{constructor(e,t){this.target=t,this.type=e}}class n extends t{constructor(e,t){super("message",t),this.data=e}}class r extends t{constructor(e,t,n){super("close",n),this.wasClean=n._closeFrameReceived&&n._closeFrameSent,this.reason=t,this.code=e}}class i extends t{constructor(e){super("open",e)}}class o extends t{constructor(e,t){super("error",t),this.message=e.message,this.error=e}}const u={addEventListener(e,t,u){if("function"!=typeof t)return;function a(e){t.call(this,new n(e,this))}function l(e,n){t.call(this,new r(e,n,this))}function s(e){t.call(this,new o(e,this))}function c(){t.call(this,new i(this))}const f=u&&u.once?"once":"on";"message"===e?(a._listener=t,this[f](e,a)):"close"===e?(l._listener=t,this[f](e,l)):"error"===e?(s._listener=t,this[f](e,s)):"open"===e?(c._listener=t,this[f](e,c)):this[f](e,t)},removeEventListener(e,t){const n=this.listeners(e);for(let r=0;r{"use strict";const t=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0];function n(e,t,n){void 0===e[t]?e[t]=[n]:e[t].push(n)}e.exports={format:function(e){return Object.keys(e).map(t=>{let n=e[t];return Array.isArray(n)||(n=[n]),n.map(e=>[t].concat(Object.keys(e).map(t=>{let n=e[t];return Array.isArray(n)||(n=[n]),n.map(e=>!0===e?t:`${t}=${e}`).join("; ")})).join("; ")).join(", ")}).join(", ")},parse:function(e){const r=Object.create(null);if(void 0===e||""===e)return r;let i,o,u=Object.create(null),a=!1,l=!1,s=!1,c=-1,f=-1,d=0;for(;d{"use strict";const t=Symbol("kDone"),n=Symbol("kRun");e.exports=class{constructor(e){this[t]=()=>{this.pending--,this[n]()},this.concurrency=e||1/0,this.jobs=[],this.pending=0}add(e){this.jobs.push(e),this[n]()}[n](){if(this.pending!==this.concurrency&&this.jobs.length){const e=this.jobs.shift();this.pending++,e(this[t])}}}},2309:(e,t,n)=>{"use strict";const r=n(8761),i=n(8716),o=n(1390),{kStatusCode:u,NOOP:a}=n(5739),l=Buffer.from([0,0,255,255]),s=Symbol("permessage-deflate"),c=Symbol("total-length"),f=Symbol("callback"),d=Symbol("buffers"),p=Symbol("error");let h;function v(e){this[d].push(e),this[c]+=e.length}function m(e){this[c]+=e.length,this[s]._maxPayload<1||this[c]<=this[s]._maxPayload?this[d].push(e):(this[p]=new RangeError("Max payload size exceeded"),this[p][u]=1009,this.removeListener("data",m),this.reset())}function g(e){this[s]._inflate=null,e[u]=1007,this[f](e)}e.exports=class{constructor(e,t,n){if(this._maxPayload=0|n,this._options=e||{},this._threshold=void 0!==this._options.threshold?this._options.threshold:1024,this._isServer=!!t,this._deflate=null,this._inflate=null,this.params=null,!h){const e=void 0!==this._options.concurrencyLimit?this._options.concurrencyLimit:10;h=new o(e)}}static get extensionName(){return"permessage-deflate"}offer(){const e={};return this._options.serverNoContextTakeover&&(e.server_no_context_takeover=!0),this._options.clientNoContextTakeover&&(e.client_no_context_takeover=!0),this._options.serverMaxWindowBits&&(e.server_max_window_bits=this._options.serverMaxWindowBits),this._options.clientMaxWindowBits?e.client_max_window_bits=this._options.clientMaxWindowBits:null==this._options.clientMaxWindowBits&&(e.client_max_window_bits=!0),e}accept(e){return e=this.normalizeParams(e),this.params=this._isServer?this.acceptAsServer(e):this.acceptAsClient(e),this.params}cleanup(){if(this._inflate&&(this._inflate.close(),this._inflate=null),this._deflate){const e=this._deflate[f];this._deflate.close(),this._deflate=null,e&&e(new Error("The deflate stream was closed while data was being processed"))}}acceptAsServer(e){const t=this._options,n=e.find(e=>!(!1===t.serverNoContextTakeover&&e.server_no_context_takeover||e.server_max_window_bits&&(!1===t.serverMaxWindowBits||"number"==typeof t.serverMaxWindowBits&&t.serverMaxWindowBits>e.server_max_window_bits)||"number"==typeof t.clientMaxWindowBits&&!e.client_max_window_bits));if(!n)throw new Error("None of the extension offers can be accepted");return t.serverNoContextTakeover&&(n.server_no_context_takeover=!0),t.clientNoContextTakeover&&(n.client_no_context_takeover=!0),"number"==typeof t.serverMaxWindowBits&&(n.server_max_window_bits=t.serverMaxWindowBits),"number"==typeof t.clientMaxWindowBits?n.client_max_window_bits=t.clientMaxWindowBits:!0!==n.client_max_window_bits&&!1!==t.clientMaxWindowBits||delete n.client_max_window_bits,n}acceptAsClient(e){const t=e[0];if(!1===this._options.clientNoContextTakeover&&t.client_no_context_takeover)throw new Error('Unexpected parameter "client_no_context_takeover"');if(t.client_max_window_bits){if(!1===this._options.clientMaxWindowBits||"number"==typeof this._options.clientMaxWindowBits&&t.client_max_window_bits>this._options.clientMaxWindowBits)throw new Error('Unexpected or invalid parameter "client_max_window_bits"')}else"number"==typeof this._options.clientMaxWindowBits&&(t.client_max_window_bits=this._options.clientMaxWindowBits);return t}normalizeParams(e){return e.forEach(e=>{Object.keys(e).forEach(t=>{let n=e[t];if(n.length>1)throw new Error(`Parameter "${t}" must have only a single value`);if(n=n[0],"client_max_window_bits"===t){if(!0!==n){const e=+n;if(!Number.isInteger(e)||e<8||e>15)throw new TypeError(`Invalid value for parameter "${t}": ${n}`);n=e}else if(!this._isServer)throw new TypeError(`Invalid value for parameter "${t}": ${n}`)}else if("server_max_window_bits"===t){const e=+n;if(!Number.isInteger(e)||e<8||e>15)throw new TypeError(`Invalid value for parameter "${t}": ${n}`);n=e}else{if("client_no_context_takeover"!==t&&"server_no_context_takeover"!==t)throw new Error(`Unknown parameter "${t}"`);if(!0!==n)throw new TypeError(`Invalid value for parameter "${t}": ${n}`)}e[t]=n})}),e}decompress(e,t,n){h.add(r=>{this._decompress(e,t,(e,t)=>{r(),n(e,t)})})}compress(e,t,n){h.add(r=>{this._compress(e,t,(e,t)=>{r(),n(e,t)})})}_decompress(e,t,n){const o=this._isServer?"client":"server";if(!this._inflate){const e=o+"_max_window_bits",t="number"!=typeof this.params[e]?r.Z_DEFAULT_WINDOWBITS:this.params[e];this._inflate=r.createInflateRaw({...this._options.zlibInflateOptions,windowBits:t}),this._inflate[s]=this,this._inflate[c]=0,this._inflate[d]=[],this._inflate.on("error",g),this._inflate.on("data",m)}this._inflate[f]=n,this._inflate.write(e),t&&this._inflate.write(l),this._inflate.flush(()=>{const e=this._inflate[p];if(e)return this._inflate.close(),this._inflate=null,void n(e);const r=i.concat(this._inflate[d],this._inflate[c]);t&&this.params[o+"_no_context_takeover"]?(this._inflate.close(),this._inflate=null):(this._inflate[c]=0,this._inflate[d]=[]),n(null,r)})}_compress(e,t,n){const o=this._isServer?"server":"client";if(!this._deflate){const e=o+"_max_window_bits",t="number"!=typeof this.params[e]?r.Z_DEFAULT_WINDOWBITS:this.params[e];this._deflate=r.createDeflateRaw({...this._options.zlibDeflateOptions,windowBits:t}),this._deflate[c]=0,this._deflate[d]=[],this._deflate.on("error",a),this._deflate.on("data",v)}this._deflate[f]=n,this._deflate.write(e),this._deflate.flush(r.Z_SYNC_FLUSH,()=>{if(!this._deflate)return;let e=i.concat(this._deflate[d],this._deflate[c]);t&&(e=e.slice(0,e.length-4)),this._deflate[f]=null,t&&this.params[o+"_no_context_takeover"]?(this._deflate.close(),this._deflate=null):(this._deflate[c]=0,this._deflate[d]=[]),n(null,e)})}}},1762:(e,t,n)=>{"use strict";const{Writable:r}=n(2413),i=n(2309),{BINARY_TYPES:o,EMPTY_BUFFER:u,kStatusCode:a,kWebSocket:l}=n(5739),{concat:s,toArrayBuffer:c,unmask:f}=n(8716),{isValidStatusCode:d,isValidUTF8:p}=n(9498);function h(e,t,n,r){const i=new e(n?"Invalid WebSocket frame: "+t:t);return Error.captureStackTrace(i,h),i[a]=r,i}e.exports=class extends r{constructor(e,t,n,r){super(),this._binaryType=e||o[0],this[l]=void 0,this._extensions=t||{},this._isServer=!!n,this._maxPayload=0|r,this._bufferedBytes=0,this._buffers=[],this._compressed=!1,this._payloadLength=0,this._mask=void 0,this._fragmented=0,this._masked=!1,this._fin=!1,this._opcode=0,this._totalPayloadLength=0,this._messageLength=0,this._fragments=[],this._state=0,this._loop=!1}_write(e,t,n){if(8===this._opcode&&0==this._state)return n();this._bufferedBytes+=e.length,this._buffers.push(e),this.startLoop(n)}consume(e){if(this._bufferedBytes-=e,e===this._buffers[0].length)return this._buffers.shift();if(e=n.length?t.set(this._buffers.shift(),r):(t.set(new Uint8Array(n.buffer,n.byteOffset,e),r),this._buffers[0]=n.slice(e)),e-=n.length}while(e>0);return t}startLoop(e){let t;this._loop=!0;do{switch(this._state){case 0:t=this.getInfo();break;case 1:t=this.getPayloadLength16();break;case 2:t=this.getPayloadLength64();break;case 3:this.getMask();break;case 4:t=this.getData(e);break;default:return void(this._loop=!1)}}while(this._loop);e(t)}getInfo(){if(this._bufferedBytes<2)return void(this._loop=!1);const e=this.consume(2);if(0!=(48&e[0]))return this._loop=!1,h(RangeError,"RSV2 and RSV3 must be clear",!0,1002);const t=64==(64&e[0]);if(t&&!this._extensions[i.extensionName])return this._loop=!1,h(RangeError,"RSV1 must be clear",!0,1002);if(this._fin=128==(128&e[0]),this._opcode=15&e[0],this._payloadLength=127&e[1],0===this._opcode){if(t)return this._loop=!1,h(RangeError,"RSV1 must be clear",!0,1002);if(!this._fragmented)return this._loop=!1,h(RangeError,"invalid opcode 0",!0,1002);this._opcode=this._fragmented}else if(1===this._opcode||2===this._opcode){if(this._fragmented)return this._loop=!1,h(RangeError,"invalid opcode "+this._opcode,!0,1002);this._compressed=t}else{if(!(this._opcode>7&&this._opcode<11))return this._loop=!1,h(RangeError,"invalid opcode "+this._opcode,!0,1002);if(!this._fin)return this._loop=!1,h(RangeError,"FIN must be set",!0,1002);if(t)return this._loop=!1,h(RangeError,"RSV1 must be clear",!0,1002);if(this._payloadLength>125)return this._loop=!1,h(RangeError,"invalid payload length "+this._payloadLength,!0,1002)}if(this._fin||this._fragmented||(this._fragmented=this._opcode),this._masked=128==(128&e[1]),this._isServer){if(!this._masked)return this._loop=!1,h(RangeError,"MASK must be set",!0,1002)}else if(this._masked)return this._loop=!1,h(RangeError,"MASK must be clear",!0,1002);if(126===this._payloadLength)this._state=1;else{if(127!==this._payloadLength)return this.haveLength();this._state=2}}getPayloadLength16(){if(!(this._bufferedBytes<2))return this._payloadLength=this.consume(2).readUInt16BE(0),this.haveLength();this._loop=!1}getPayloadLength64(){if(this._bufferedBytes<8)return void(this._loop=!1);const e=this.consume(8),t=e.readUInt32BE(0);return t>Math.pow(2,21)-1?(this._loop=!1,h(RangeError,"Unsupported WebSocket frame: payload length > 2^53 - 1",!1,1009)):(this._payloadLength=t*Math.pow(2,32)+e.readUInt32BE(4),this.haveLength())}haveLength(){if(this._payloadLength&&this._opcode<8&&(this._totalPayloadLength+=this._payloadLength,this._totalPayloadLength>this._maxPayload&&this._maxPayload>0))return this._loop=!1,h(RangeError,"Max payload size exceeded",!1,1009);this._masked?this._state=3:this._state=4}getMask(){this._bufferedBytes<4?this._loop=!1:(this._mask=this.consume(4),this._state=4)}getData(e){let t=u;if(this._payloadLength){if(this._bufferedBytes7?this.controlMessage(t):this._compressed?(this._state=5,void this.decompress(t,e)):(t.length&&(this._messageLength=this._totalPayloadLength,this._fragments.push(t)),this.dataMessage())}decompress(e,t){this._extensions[i.extensionName].decompress(e,this._fin,(e,n)=>{if(e)return t(e);if(n.length){if(this._messageLength+=n.length,this._messageLength>this._maxPayload&&this._maxPayload>0)return t(h(RangeError,"Max payload size exceeded",!1,1009));this._fragments.push(n)}const r=this.dataMessage();if(r)return t(r);this.startLoop(t)})}dataMessage(){if(this._fin){const e=this._messageLength,t=this._fragments;if(this._totalPayloadLength=0,this._messageLength=0,this._fragmented=0,this._fragments=[],2===this._opcode){let n;n="nodebuffer"===this._binaryType?s(t,e):"arraybuffer"===this._binaryType?c(s(t,e)):t,this.emit("message",n)}else{const n=s(t,e);if(!p(n))return this._loop=!1,h(Error,"invalid UTF-8 sequence",!0,1007);this.emit("message",n.toString())}}this._state=0}controlMessage(e){if(8===this._opcode)if(this._loop=!1,0===e.length)this.emit("conclude",1005,""),this.end();else{if(1===e.length)return h(RangeError,"invalid payload length 1",!0,1002);{const t=e.readUInt16BE(0);if(!d(t))return h(RangeError,"invalid status code "+t,!0,1002);const n=e.slice(2);if(!p(n))return h(Error,"invalid UTF-8 sequence",!0,1007);this.emit("conclude",t,n.toString()),this.end()}}else 9===this._opcode?this.emit("ping",e):this.emit("pong",e);this._state=0}}},9576:(e,t,n)=>{"use strict";const{randomFillSync:r}=n(6417),i=n(2309),{EMPTY_BUFFER:o}=n(5739),{isValidStatusCode:u}=n(9498),{mask:a,toBuffer:l}=n(8716),s=Buffer.alloc(4);class c{constructor(e,t){this._extensions=t||{},this._socket=e,this._firstFragment=!0,this._compress=!1,this._bufferedBytes=0,this._deflating=!1,this._queue=[]}static frame(e,t){const n=t.mask&&t.readOnly;let i=t.mask?6:2,o=e.length;e.length>=65536?(i+=8,o=127):e.length>125&&(i+=2,o=126);const u=Buffer.allocUnsafe(n?e.length+i:i);return u[0]=t.fin?128|t.opcode:t.opcode,t.rsv1&&(u[0]|=64),u[1]=o,126===o?u.writeUInt16BE(e.length,2):127===o&&(u.writeUInt32BE(0,2),u.writeUInt32BE(e.length,6)),t.mask?(r(s,0,4),u[1]|=128,u[i-4]=s[0],u[i-3]=s[1],u[i-2]=s[2],u[i-1]=s[3],n?(a(e,s,u,i,e.length),[u]):(a(e,s,e,0,e.length),[u,e])):[u,e]}close(e,t,n,r){let i;if(void 0===e)i=o;else{if("number"!=typeof e||!u(e))throw new TypeError("First argument must be a valid error code number");if(void 0===t||""===t)i=Buffer.allocUnsafe(2),i.writeUInt16BE(e,0);else{const n=Buffer.byteLength(t);if(n>123)throw new RangeError("The message must not be greater than 123 bytes");i=Buffer.allocUnsafe(2+n),i.writeUInt16BE(e,0),i.write(t,2)}}this._deflating?this.enqueue([this.doClose,i,n,r]):this.doClose(i,n,r)}doClose(e,t,n){this.sendFrame(c.frame(e,{fin:!0,rsv1:!1,opcode:8,mask:t,readOnly:!1}),n)}ping(e,t,n){const r=l(e);if(r.length>125)throw new RangeError("The data size must not be greater than 125 bytes");this._deflating?this.enqueue([this.doPing,r,t,l.readOnly,n]):this.doPing(r,t,l.readOnly,n)}doPing(e,t,n,r){this.sendFrame(c.frame(e,{fin:!0,rsv1:!1,opcode:9,mask:t,readOnly:n}),r)}pong(e,t,n){const r=l(e);if(r.length>125)throw new RangeError("The data size must not be greater than 125 bytes");this._deflating?this.enqueue([this.doPong,r,t,l.readOnly,n]):this.doPong(r,t,l.readOnly,n)}doPong(e,t,n,r){this.sendFrame(c.frame(e,{fin:!0,rsv1:!1,opcode:10,mask:t,readOnly:n}),r)}send(e,t,n){const r=l(e),o=this._extensions[i.extensionName];let u=t.binary?2:1,a=t.compress;if(this._firstFragment?(this._firstFragment=!1,a&&o&&(a=r.length>=o._threshold),this._compress=a):(a=!1,u=0),t.fin&&(this._firstFragment=!0),o){const e={fin:t.fin,rsv1:a,opcode:u,mask:t.mask,readOnly:l.readOnly};this._deflating?this.enqueue([this.dispatch,r,this._compress,e,n]):this.dispatch(r,this._compress,e,n)}else this.sendFrame(c.frame(r,{fin:t.fin,rsv1:!1,opcode:u,mask:t.mask,readOnly:l.readOnly}),n)}dispatch(e,t,n,r){if(!t)return void this.sendFrame(c.frame(e,n),r);const o=this._extensions[i.extensionName];this._bufferedBytes+=e.length,this._deflating=!0,o.compress(e,n.fin,(t,i)=>{if(this._socket.destroyed){const e=new Error("The socket was closed while data was being compressed");"function"==typeof r&&r(e);for(let t=0;t{"use strict";const{Duplex:r}=n(2413);function i(e){e.emit("close")}function o(){!this.destroyed&&this._writableState.finished&&this.destroy()}function u(e){this.removeListener("error",u),this.destroy(),0===this.listenerCount("error")&&this.emit("error",e)}e.exports=function(e,t){let n=!0;function a(){n&&e._socket.resume()}e.readyState===e.CONNECTING?e.once("open",(function(){e._receiver.removeAllListeners("drain"),e._receiver.on("drain",a)})):(e._receiver.removeAllListeners("drain"),e._receiver.on("drain",a));const l=new r({...t,autoDestroy:!1,emitClose:!1,objectMode:!1,writableObjectMode:!1});return e.on("message",(function(t){l.push(t)||(n=!1,e._socket.pause())})),e.once("error",(function(e){l.destroyed||l.destroy(e)})),e.once("close",(function(){l.destroyed||l.push(null)})),l._destroy=function(t,n){if(e.readyState===e.CLOSED)return n(t),void process.nextTick(i,l);let r=!1;e.once("error",(function(e){r=!0,n(e)})),e.once("close",(function(){r||n(t),process.nextTick(i,l)})),e.terminate()},l._final=function(t){e.readyState!==e.CONNECTING?null!==e._socket&&(e._socket._writableState.finished?(t(),l._readableState.endEmitted&&l.destroy()):(e._socket.once("finish",(function(){t()})),e.close())):e.once("open",(function(){l._final(t)}))},l._read=function(){e.readyState!==e.OPEN||n||(n=!0,e._receiver._writableState.needDrain||e._socket.resume())},l._write=function(t,n,r){e.readyState!==e.CONNECTING?e.send(t,r):e.once("open",(function(){l._write(t,n,r)}))},l.on("end",o),l.on("error",u),l}},9498:(e,t,n)=>{"use strict";try{const e=n(Object(function(){var e=new Error("Cannot find module 'utf-8-validate'");throw e.code="MODULE_NOT_FOUND",e}()));t.isValidUTF8="object"==typeof e?e.Validation.isValidUTF8:e}catch(e){t.isValidUTF8=()=>!0}t.isValidStatusCode=e=>e>=1e3&&e<=1014&&1004!==e&&1005!==e&&1006!==e||e>=3e3&&e<=4999},43:(e,t,n)=>{"use strict";const r=n(8614),{createHash:i}=n(6417),{createServer:o,STATUS_CODES:u}=n(8605),a=n(2309),l=n(5760),{format:s,parse:c}=n(8162),{GUID:f,kWebSocket:d}=n(5739),p=/^[+/0-9A-Za-z]{22}==$/;function h(e){e.emit("close")}function v(){this.destroy()}function m(e,t,n,r){e.writable&&(n=n||u[t],r={Connection:"close","Content-Type":"text/html","Content-Length":Buffer.byteLength(n),...r},e.write(`HTTP/1.1 ${t} ${u[t]}\r\n`+Object.keys(r).map(e=>`${e}: ${r[e]}`).join("\r\n")+"\r\n\r\n"+n)),e.removeListener("error",v),e.destroy()}e.exports=class extends r{constructor(e,t){if(super(),null==(e={maxPayload:104857600,perMessageDeflate:!1,handleProtocols:null,clientTracking:!0,verifyClient:null,noServer:!1,backlog:null,server:null,host:null,path:null,port:null,...e}).port&&!e.server&&!e.noServer)throw new TypeError('One of the "port", "server", or "noServer" options must be specified');null!=e.port?(this._server=o((e,t)=>{const n=u[426];t.writeHead(426,{"Content-Length":n.length,"Content-Type":"text/plain"}),t.end(n)}),this._server.listen(e.port,e.host,e.backlog,t)):e.server&&(this._server=e.server),this._server&&(this._removeListeners=function(e,t){for(const n of Object.keys(t))e.on(n,t[n]);return function(){for(const n of Object.keys(t))e.removeListener(n,t[n])}}(this._server,{listening:this.emit.bind(this,"listening"),error:this.emit.bind(this,"error"),upgrade:(e,t,n)=>{this.handleUpgrade(e,t,n,t=>{this.emit("connection",t,e)})}})),!0===e.perMessageDeflate&&(e.perMessageDeflate={}),e.clientTracking&&(this.clients=new Set),this.options=e}address(){if(this.options.noServer)throw new Error('The server is operating in "noServer" mode');return this._server?this._server.address():null}close(e){if(e&&this.once("close",e),this.clients)for(const e of this.clients)e.terminate();const t=this._server;t&&(this._removeListeners(),this._removeListeners=this._server=null,null!=this.options.port)?t.close(()=>this.emit("close")):process.nextTick(h,this)}shouldHandle(e){if(this.options.path){const t=e.url.indexOf("?");if((-1!==t?e.url.slice(0,t):e.url)!==this.options.path)return!1}return!0}handleUpgrade(e,t,n,r){t.on("error",v);const i=void 0!==e.headers["sec-websocket-key"]&&e.headers["sec-websocket-key"].trim(),o=+e.headers["sec-websocket-version"],u={};if("GET"!==e.method||"websocket"!==e.headers.upgrade.toLowerCase()||!i||!p.test(i)||8!==o&&13!==o||!this.shouldHandle(e))return m(t,400);if(this.options.perMessageDeflate){const n=new a(this.options.perMessageDeflate,!0,this.options.maxPayload);try{const t=c(e.headers["sec-websocket-extensions"]);t[a.extensionName]&&(n.accept(t[a.extensionName]),u[a.extensionName]=n)}catch(e){return m(t,400)}}if(this.options.verifyClient){const a={origin:e.headers[""+(8===o?"sec-websocket-origin":"origin")],secure:!(!e.connection.authorized&&!e.connection.encrypted),req:e};if(2===this.options.verifyClient.length)return void this.options.verifyClient(a,(o,a,l,s)=>{if(!o)return m(t,a||401,l,s);this.completeUpgrade(i,u,e,t,n,r)});if(!this.options.verifyClient(a))return m(t,401)}this.completeUpgrade(i,u,e,t,n,r)}completeUpgrade(e,t,n,r,o,u){if(!r.readable||!r.writable)return r.destroy();if(r[d])throw new Error("server.handleUpgrade() was called more than once with the same socket, possibly due to a misconfiguration");const c=["HTTP/1.1 101 Switching Protocols","Upgrade: websocket","Connection: Upgrade","Sec-WebSocket-Accept: "+i("sha1").update(e+f).digest("base64")],p=new l(null);let h=n.headers["sec-websocket-protocol"];if(h&&(h=h.trim().split(/ *, */),h=this.options.handleProtocols?this.options.handleProtocols(h,n):h[0],h&&(c.push("Sec-WebSocket-Protocol: "+h),p.protocol=h)),t[a.extensionName]){const e=t[a.extensionName].params,n=s({[a.extensionName]:[e]});c.push("Sec-WebSocket-Extensions: "+n),p._extensions=t}this.emit("headers",c,n),r.write(c.concat("\r\n").join("\r\n")),r.removeListener("error",v),p.setSocket(r,o,this.options.maxPayload),this.clients&&(this.clients.add(p),p.on("close",()=>this.clients.delete(p))),u(p)}}},5760:(e,t,n)=>{"use strict";const r=n(8614),i=n(7211),o=n(8605),u=n(1631),a=n(4016),{randomBytes:l,createHash:s}=n(6417),{URL:c}=n(8835),f=n(2309),d=n(1762),p=n(9576),{BINARY_TYPES:h,EMPTY_BUFFER:v,GUID:m,kStatusCode:g,kWebSocket:y,NOOP:_}=n(5739),{addEventListener:b,removeEventListener:w}=n(7002),{format:E,parse:D}=n(8162),{toBuffer:S}=n(8716),C=["CONNECTING","OPEN","CLOSING","CLOSED"],k=[8,13];class T extends r{constructor(e,t,n){super(),this.readyState=T.CONNECTING,this.protocol="",this._binaryType=h[0],this._closeFrameReceived=!1,this._closeFrameSent=!1,this._closeMessage="",this._closeTimer=null,this._closeCode=1006,this._extensions={},this._receiver=null,this._sender=null,this._socket=null,null!==e?(this._bufferedAmount=0,this._isServer=!1,this._redirects=0,Array.isArray(t)?t=t.join(", "):"object"==typeof t&&null!==t&&(n=t,t=void 0),function e(t,n,r,u){const a={protocolVersion:k[1],maxPayload:104857600,perMessageDeflate:!0,followRedirects:!1,maxRedirects:10,...u,createConnection:void 0,socketPath:void 0,hostname:void 0,protocol:void 0,timeout:void 0,method:void 0,host:void 0,path:void 0,port:void 0};if(!k.includes(a.protocolVersion))throw new RangeError(`Unsupported protocol version: ${a.protocolVersion} (supported versions: ${k.join(", ")})`);let d;n instanceof c?(d=n,t.url=n.href):(d=new c(n),t.url=n);const p="ws+unix:"===d.protocol;if(!(d.host||p&&d.pathname))throw new Error("Invalid URL: "+t.url);const h="wss:"===d.protocol||"https:"===d.protocol,v=h?443:80,g=l(16).toString("base64"),y=h?i.get:o.get;let _;a.createConnection=h?A:x,a.defaultPort=a.defaultPort||v,a.port=d.port||v,a.host=d.hostname.startsWith("[")?d.hostname.slice(1,-1):d.hostname,a.headers={"Sec-WebSocket-Version":a.protocolVersion,"Sec-WebSocket-Key":g,Connection:"Upgrade",Upgrade:"websocket",...a.headers},a.path=d.pathname+d.search,a.timeout=a.handshakeTimeout,a.perMessageDeflate&&(_=new f(!0!==a.perMessageDeflate?a.perMessageDeflate:{},!1,a.maxPayload),a.headers["Sec-WebSocket-Extensions"]=E({[f.extensionName]:_.offer()}));r&&(a.headers["Sec-WebSocket-Protocol"]=r);a.origin&&(a.protocolVersion<13?a.headers["Sec-WebSocket-Origin"]=a.origin:a.headers.Origin=a.origin);(d.username||d.password)&&(a.auth=`${d.username}:${d.password}`);if(p){const e=a.path.split(":");a.socketPath=e[0],a.path=e[1]}let b=t._req=y(a);a.timeout&&b.on("timeout",()=>{O(t,b,"Opening handshake has timed out")});b.on("error",e=>{t._req.aborted||(b=t._req=null,t.readyState=T.CLOSING,t.emit("error",e),t.emitClose())}),b.on("response",i=>{const o=i.headers.location,l=i.statusCode;if(o&&a.followRedirects&&l>=300&&l<400){if(++t._redirects>a.maxRedirects)return void O(t,b,"Maximum redirects exceeded");b.abort();const i=new c(o,n);e(t,i,r,u)}else t.emit("unexpected-response",b,i)||O(t,b,"Unexpected server response: "+i.statusCode)}),b.on("upgrade",(e,n,i)=>{if(t.emit("upgrade",e),t.readyState!==T.CONNECTING)return;b=t._req=null;const o=s("sha1").update(g+m).digest("base64");if(e.headers["sec-websocket-accept"]!==o)return void O(t,n,"Invalid Sec-WebSocket-Accept header");const u=e.headers["sec-websocket-protocol"],l=(r||"").split(/, */);let c;if(!r&&u?c="Server sent a subprotocol but none was requested":r&&!u?c="Server sent no subprotocol":u&&!l.includes(u)&&(c="Server sent an invalid subprotocol"),c)O(t,n,c);else{if(u&&(t.protocol=u),_)try{const n=D(e.headers["sec-websocket-extensions"]);n[f.extensionName]&&(_.accept(n[f.extensionName]),t._extensions[f.extensionName]=_)}catch(e){return void O(t,n,"Invalid Sec-WebSocket-Extensions header")}t.setSocket(n,i,a.maxPayload)}})}(this,e,t,n)):this._isServer=!0}get CONNECTING(){return T.CONNECTING}get CLOSING(){return T.CLOSING}get CLOSED(){return T.CLOSED}get OPEN(){return T.OPEN}get binaryType(){return this._binaryType}set binaryType(e){h.includes(e)&&(this._binaryType=e,this._receiver&&(this._receiver._binaryType=e))}get bufferedAmount(){return this._socket?this._socket._writableState.length+this._sender._bufferedBytes:this._bufferedAmount}get extensions(){return Object.keys(this._extensions).join()}setSocket(e,t,n){const r=new d(this._binaryType,this._extensions,this._isServer,n);this._sender=new p(e,this._extensions),this._receiver=r,this._socket=e,r[y]=this,e[y]=this,r.on("conclude",I),r.on("drain",N),r.on("error",M),r.on("message",F),r.on("ping",L),r.on("pong",B),e.setTimeout(0),e.setNoDelay(),t.length>0&&e.unshift(t),e.on("close",j),e.on("data",U),e.on("end",z),e.on("error",W),this.readyState=T.OPEN,this.emit("open")}emitClose(){if(!this._socket)return this.readyState=T.CLOSED,void this.emit("close",this._closeCode,this._closeMessage);this._extensions[f.extensionName]&&this._extensions[f.extensionName].cleanup(),this._receiver.removeAllListeners(),this.readyState=T.CLOSED,this.emit("close",this._closeCode,this._closeMessage)}close(e,t){if(this.readyState!==T.CLOSED){if(this.readyState===T.CONNECTING){const e="WebSocket was closed before the connection was established";return O(this,this._req,e)}this.readyState!==T.CLOSING?(this.readyState=T.CLOSING,this._sender.close(e,t,!this._isServer,e=>{e||(this._closeFrameSent=!0,this._closeFrameReceived&&this._socket.end())}),this._closeTimer=setTimeout(this._socket.destroy.bind(this._socket),3e4)):this._closeFrameSent&&this._closeFrameReceived&&this._socket.end()}}ping(e,t,n){if(this.readyState===T.CONNECTING)throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");"function"==typeof e?(n=e,e=t=void 0):"function"==typeof t&&(n=t,t=void 0),"number"==typeof e&&(e=e.toString()),this.readyState===T.OPEN?(void 0===t&&(t=!this._isServer),this._sender.ping(e||v,t,n)):P(this,e,n)}pong(e,t,n){if(this.readyState===T.CONNECTING)throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");"function"==typeof e?(n=e,e=t=void 0):"function"==typeof t&&(n=t,t=void 0),"number"==typeof e&&(e=e.toString()),this.readyState===T.OPEN?(void 0===t&&(t=!this._isServer),this._sender.pong(e||v,t,n)):P(this,e,n)}send(e,t,n){if(this.readyState===T.CONNECTING)throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");if("function"==typeof t&&(n=t,t={}),"number"==typeof e&&(e=e.toString()),this.readyState!==T.OPEN)return void P(this,e,n);const r={binary:"string"!=typeof e,mask:!this._isServer,compress:!0,fin:!0,...t};this._extensions[f.extensionName]||(r.compress=!1),this._sender.send(e||v,r,n)}terminate(){if(this.readyState!==T.CLOSED){if(this.readyState===T.CONNECTING){const e="WebSocket was closed before the connection was established";return O(this,this._req,e)}this._socket&&(this.readyState=T.CLOSING,this._socket.destroy())}}}function x(e){return e.path=e.socketPath,u.connect(e)}function A(e){return e.path=void 0,e.servername||""===e.servername||(e.servername=e.host),a.connect(e)}function O(e,t,n){e.readyState=T.CLOSING;const r=new Error(n);Error.captureStackTrace(r,O),t.setHeader?(t.abort(),t.once("abort",e.emitClose.bind(e)),e.emit("error",r)):(t.destroy(r),t.once("error",e.emit.bind(e,"error")),t.once("close",e.emitClose.bind(e)))}function P(e,t,n){if(t){const n=S(t).length;e._socket?e._sender._bufferedBytes+=n:e._bufferedAmount+=n}if(n){n(new Error(`WebSocket is not open: readyState ${e.readyState} (${C[e.readyState]})`))}}function I(e,t){const n=this[y];n._socket.removeListener("data",U),n._socket.resume(),n._closeFrameReceived=!0,n._closeMessage=t,n._closeCode=e,1005===e?n.close():n.close(e,t)}function N(){this[y]._socket.resume()}function M(e){const t=this[y];t._socket.removeListener("data",U),t.readyState=T.CLOSING,t._closeCode=e[g],t.emit("error",e),t._socket.destroy()}function R(){this[y].emitClose()}function F(e){this[y].emit("message",e)}function L(e){const t=this[y];t.pong(e,!t._isServer,_),t.emit("ping",e)}function B(e){this[y].emit("pong",e)}function j(){const e=this[y];this.removeListener("close",j),this.removeListener("end",z),e.readyState=T.CLOSING,e._socket.read(),e._receiver.end(),this.removeListener("data",U),this[y]=void 0,clearTimeout(e._closeTimer),e._receiver._writableState.finished||e._receiver._writableState.errorEmitted?e.emitClose():(e._receiver.on("error",R),e._receiver.on("finish",R))}function U(e){this[y]._receiver.write(e)||this.pause()}function z(){const e=this[y];e.readyState=T.CLOSING,e._receiver.end(),this.end()}function W(){const e=this[y];this.removeListener("error",W),this.on("error",_),e&&(e.readyState=T.CLOSING,this.destroy())}C.forEach((e,t)=>{T[e]=t}),["open","error","close","message"].forEach(e=>{Object.defineProperty(T.prototype,"on"+e,{get(){const t=this.listeners(e);for(let e=0;e{"use strict";function r(e){const t=[...e.caches],n=t.shift();return void 0===n?i():{get:(e,i,o={miss:()=>Promise.resolve()})=>n.get(e,i,o).catch(()=>r({caches:t}).get(e,i,o)),set:(e,i)=>n.set(e,i).catch(()=>r({caches:t}).set(e,i)),delete:e=>n.delete(e).catch(()=>r({caches:t}).delete(e)),clear:()=>n.clear().catch(()=>r({caches:t}).clear())}}function i(){return{get:(e,t,n={miss:()=>Promise.resolve()})=>t().then(e=>Promise.all([e,n.miss(e)])).then(([e])=>e),set:(e,t)=>Promise.resolve(t),delete:e=>Promise.resolve(),clear:()=>Promise.resolve()}}n.r(t),n.d(t,{createFallbackableCache:()=>r,createNullCache:()=>i})},6712:(e,t,n)=>{"use strict";function r(e={serializable:!0}){let t={};return{get(n,r,i={miss:()=>Promise.resolve()}){const o=JSON.stringify(n);if(o in t)return Promise.resolve(e.serializable?JSON.parse(t[o]):t[o]);const u=r(),a=i&&i.miss||(()=>Promise.resolve());return u.then(e=>a(e)).then(()=>u)},set:(n,r)=>(t[JSON.stringify(n)]=e.serializable?JSON.stringify(r):r,Promise.resolve(r)),delete:e=>(delete t[JSON.stringify(e)],Promise.resolve()),clear:()=>(t={},Promise.resolve())}}n.r(t),n.d(t,{createInMemoryCache:()=>r})},2223:(e,t,n)=>{"use strict";n.r(t),n.d(t,{addABTest:()=>a,createAnalyticsClient:()=>u,deleteABTest:()=>l,getABTest:()=>s,getABTests:()=>c,stopABTest:()=>f});var r=n(1757),i=n(7858),o=n(5541);const u=e=>{const t=e.region||"us",n=(0,r.createAuth)(r.AuthMode.WithinHeaders,e.appId,e.apiKey),o=(0,i.createTransporter)({hosts:[{url:`analytics.${t}.algolia.com`}],...e,headers:{...n.headers(),"content-type":"application/json",...e.headers},queryParameters:{...n.queryParameters(),...e.queryParameters}}),u=e.appId;return(0,r.addMethods)({appId:u,transporter:o},e.methods)},a=e=>(t,n)=>e.transporter.write({method:o.N.Post,path:"2/abtests",data:t},n),l=e=>(t,n)=>e.transporter.write({method:o.N.Delete,path:(0,r.encode)("2/abtests/%s",t)},n),s=e=>(t,n)=>e.transporter.read({method:o.N.Get,path:(0,r.encode)("2/abtests/%s",t)},n),c=e=>t=>e.transporter.read({method:o.N.Get,path:"2/abtests"},t),f=e=>(t,n)=>e.transporter.write({method:o.N.Post,path:(0,r.encode)("2/abtests/%s/stop",t)},n)},1757:(e,t,n)=>{"use strict";function r(e,t,n){const r={"x-algolia-api-key":n,"x-algolia-application-id":t};return{headers:()=>e===f.WithinHeaders?r:{},queryParameters:()=>e===f.WithinQueryParameters?r:{}}}function i(e){let t=0;const n=()=>(t++,new Promise(r=>{setTimeout(()=>{r(e(n))},Math.min(100*t,1e3))}));return e(n)}function o(e,t=((e,t)=>Promise.resolve())){return Object.assign(e,{wait:n=>o(e.then(e=>Promise.all([t(e,n),e])).then(e=>e[1]))})}function u(e){let t=e.length-1;for(;t>0;t--){const n=Math.floor(Math.random()*(t+1)),r=e[t];e[t]=e[n],e[n]=r}return e}function a(e,t){return Object.keys(void 0!==t?t:{}).forEach(n=>{e[n]=t[n](e)}),e}function l(e,...t){let n=0;return e.replace(/%s/g,()=>encodeURIComponent(t[n++]))}n.r(t),n.d(t,{AuthMode:()=>f,addMethods:()=>a,createAuth:()=>r,createRetryablePromise:()=>i,createWaitablePromise:()=>o,destroy:()=>c,encode:()=>l,shuffle:()=>u,version:()=>s});const s="4.2.0",c=e=>()=>e.transporter.requester.destroy(),f={WithinQueryParameters:0,WithinHeaders:1}},103:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createRecommendationClient:()=>u,getPersonalizationStrategy:()=>a,setPersonalizationStrategy:()=>l});var r=n(1757),i=n(7858),o=n(5541);const u=e=>{const t=e.region||"us",n=(0,r.createAuth)(r.AuthMode.WithinHeaders,e.appId,e.apiKey),o=(0,i.createTransporter)({hosts:[{url:`recommendation.${t}.algolia.com`}],...e,headers:{...n.headers(),"content-type":"application/json",...e.headers},queryParameters:{...n.queryParameters(),...e.queryParameters}});return(0,r.addMethods)({appId:e.appId,transporter:o},e.methods)},a=e=>t=>e.transporter.read({method:o.N.Get,path:"1/strategies/personalization"},t),l=e=>(t,n)=>e.transporter.write({method:o.N.Post,path:"1/strategies/personalization",data:t},n)},6586:(e,t,n)=>{"use strict";n.r(t),n.d(t,{ApiKeyACLEnum:()=>Te,BatchActionEnum:()=>xe,ScopeEnum:()=>Ae,StrategyEnum:()=>Oe,SynonymEnum:()=>Pe,addApiKey:()=>d,assignUserID:()=>p,assignUserIDs:()=>h,batch:()=>z,browseObjects:()=>W,browseRules:()=>H,browseSynonyms:()=>V,chunkedBatch:()=>q,clearObjects:()=>G,clearRules:()=>$,clearSynonyms:()=>Y,copyIndex:()=>v,copyRules:()=>m,copySettings:()=>g,copySynonyms:()=>y,createBrowsablePromise:()=>a,createMissingObjectIDError:()=>s,createObjectNotFoundError:()=>c,createSearchClient:()=>l,createValidUntilNotFoundError:()=>f,deleteApiKey:()=>_,deleteBy:()=>K,deleteIndex:()=>X,deleteObject:()=>Q,deleteObjects:()=>J,deleteRule:()=>Z,deleteSynonym:()=>ee,exists:()=>te,findObject:()=>ne,generateSecuredApiKey:()=>b,getApiKey:()=>w,getLogs:()=>E,getObject:()=>re,getObjectPosition:()=>ie,getObjects:()=>oe,getRule:()=>ue,getSecuredApiKeyRemainingValidity:()=>D,getSettings:()=>ae,getSynonym:()=>le,getTask:()=>se,getTopUserIDs:()=>S,getUserID:()=>C,hasPendingMappings:()=>k,initIndex:()=>T,listApiKeys:()=>x,listClusters:()=>A,listIndices:()=>O,listUserIDs:()=>P,moveIndex:()=>I,multipleBatch:()=>N,multipleGetObjects:()=>M,multipleQueries:()=>R,multipleSearchForFacetValues:()=>F,partialUpdateObject:()=>ce,partialUpdateObjects:()=>fe,removeUserID:()=>L,replaceAllObjects:()=>de,replaceAllRules:()=>pe,replaceAllSynonyms:()=>he,restoreApiKey:()=>B,saveObject:()=>ve,saveObjects:()=>me,saveRule:()=>ge,saveRules:()=>ye,saveSynonym:()=>_e,saveSynonyms:()=>be,search:()=>we,searchForFacetValues:()=>Ee,searchRules:()=>De,searchSynonyms:()=>Se,searchUserIDs:()=>j,setSettings:()=>Ce,updateApiKey:()=>U,waitTask:()=>ke});var r=n(1757),i=n(7858),o=n(5541),u=n(6417);function a(e){const t=n=>e.request(n).then(r=>{if(void 0!==e.batch&&e.batch(r.hits),!e.shouldStop(r))return r.cursor?t({cursor:r.cursor}):t({page:(n.page||0)+1})});return t({})}const l=e=>{const t=e.appId,n=(0,r.createAuth)(void 0!==e.authMode?e.authMode:r.AuthMode.WithinHeaders,t,e.apiKey),o=(0,i.createTransporter)({hosts:[{url:t+"-dsn.algolia.net",accept:i.CallEnum.Read},{url:t+".algolia.net",accept:i.CallEnum.Write}].concat((0,r.shuffle)([{url:t+"-1.algolianet.com"},{url:t+"-2.algolianet.com"},{url:t+"-3.algolianet.com"}])),...e,headers:{...n.headers(),"content-type":"application/x-www-form-urlencoded",...e.headers},queryParameters:{...n.queryParameters(),...e.queryParameters}}),u={transporter:o,appId:t,addAlgoliaAgent(e,t){o.userAgent.add({segment:e,version:t})},clearCache:()=>Promise.all([o.requestsCache.clear(),o.responsesCache.clear()]).then(()=>{})};return(0,r.addMethods)(u,e.methods)};function s(){return{name:"MissingObjectIDError",message:"All objects must have an unique objectID (like a primary key) to be valid. Algolia is also able to generate objectIDs automatically but *it's not recommended*. To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option."}}function c(){return{name:"ObjectNotFoundError",message:"Object not found."}}function f(){return{name:"ValidUntilNotFoundError",message:"ValidUntil not found in given secured api key."}}const d=e=>(t,n)=>{const{queryParameters:i,...u}=n||{},a={acl:t,...void 0!==i?{queryParameters:i}:{}};return(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:"1/keys",data:a},u),(t,n)=>(0,r.createRetryablePromise)(r=>w(e)(t.key,n).catch(e=>{if(404!==e.status)throw e;return r()})))},p=e=>(t,n,r)=>{const u=(0,i.createMappedRequestOptions)(r);return u.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:o.N.Post,path:"1/clusters/mapping",data:{cluster:n}},u)},h=e=>(t,n,r)=>e.transporter.write({method:o.N.Post,path:"1/clusters/mapping/batch",data:{users:t,cluster:n}},r),v=e=>(t,n,i)=>(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/operation",t),data:{operation:"copy",destination:n}},i),(n,r)=>T(e)(t,{methods:{waitTask:ke}}).waitTask(n.taskID,r)),m=e=>(t,n,r)=>v(e)(t,n,{...r,scope:[Ae.Rules]}),g=e=>(t,n,r)=>v(e)(t,n,{...r,scope:[Ae.Settings]}),y=e=>(t,n,r)=>v(e)(t,n,{...r,scope:[Ae.Synonyms]}),_=e=>(t,n)=>(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Delete,path:(0,r.encode)("1/keys/%s",t)},n),(n,i)=>(0,r.createRetryablePromise)(n=>w(e)(t,i).then(n).catch(e=>{if(404!==e.status)throw e}))),b=()=>(e,t)=>{const n=(0,i.serializeQueryParameters)(t),r=(0,u.createHmac)("sha256",e).update(n).digest("hex");return Buffer.from(r+n).toString("base64")},w=e=>(t,n)=>e.transporter.read({method:o.N.Get,path:(0,r.encode)("1/keys/%s",t)},n),E=e=>t=>e.transporter.read({method:o.N.Get,path:"1/logs"},t),D=()=>e=>{const t=Buffer.from(e,"base64").toString("ascii").match(/validUntil=(\d+)/);if(null===t)throw{name:"ValidUntilNotFoundError",message:"ValidUntil not found in given secured api key."};return parseInt(t[1],10)-Math.round((new Date).getTime()/1e3)},S=e=>t=>e.transporter.read({method:o.N.Get,path:"1/clusters/mapping/top"},t),C=e=>(t,n)=>e.transporter.read({method:o.N.Get,path:(0,r.encode)("1/clusters/mapping/%s",t)},n),k=e=>t=>{const{retrieveMappings:n,...r}=t||{};return!0===n&&(r.getClusters=!0),e.transporter.read({method:o.N.Get,path:"1/clusters/mapping/pending"},r)},T=e=>(t,n={})=>{const i={transporter:e.transporter,appId:e.appId,indexName:t};return(0,r.addMethods)(i,n.methods)},x=e=>t=>e.transporter.read({method:o.N.Get,path:"1/keys"},t),A=e=>t=>e.transporter.read({method:o.N.Get,path:"1/clusters"},t),O=e=>t=>e.transporter.read({method:o.N.Get,path:"1/indexes"},t),P=e=>t=>e.transporter.read({method:o.N.Get,path:"1/clusters/mapping"},t),I=e=>(t,n,i)=>(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/operation",t),data:{operation:"move",destination:n}},i),(n,r)=>T(e)(t,{methods:{waitTask:ke}}).waitTask(n.taskID,r)),N=e=>(t,n)=>(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:"1/indexes/*/batch",data:{requests:t}},n),(t,n)=>Promise.all(Object.keys(t.taskID).map(r=>T(e)(r,{methods:{waitTask:ke}}).waitTask(t.taskID[r],n)))),M=e=>(t,n)=>e.transporter.read({method:o.N.Post,path:"1/indexes/*/objects",data:{requests:t}},n),R=e=>(t,n)=>{const r=t.map(e=>({...e,params:(0,i.serializeQueryParameters)(e.params||{})}));return e.transporter.read({method:o.N.Post,path:"1/indexes/*/queries",data:{requests:r},cacheable:!0},n)},F=e=>(t,n)=>Promise.all(t.map(t=>{const{facetName:r,facetQuery:i,...o}=t.params;return T(e)(t.indexName,{methods:{searchForFacetValues:Ee}}).searchForFacetValues(r,i,{...n,...o})})),L=e=>(t,n)=>{const r=(0,i.createMappedRequestOptions)(n);return r.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:o.N.Delete,path:"1/clusters/mapping"},r)},B=e=>(t,n)=>(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:(0,r.encode)("1/keys/%s/restore",t)},n),(n,i)=>(0,r.createRetryablePromise)(n=>w(e)(t,i).catch(e=>{if(404!==e.status)throw e;return n()}))),j=e=>(t,n)=>e.transporter.read({method:o.N.Post,path:"1/clusters/mapping/search",data:{query:t}},n),U=e=>(t,n)=>{const i=Object.assign({},n),{queryParameters:u,...a}=n||{},l=u?{queryParameters:u}:{},s=["acl","indexes","referers","restrictSources","queryParameters","description","maxQueriesPerIPPerHour","maxHitsPerQuery"];return(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Put,path:(0,r.encode)("1/keys/%s",t),data:l},a),(n,o)=>(0,r.createRetryablePromise)(n=>w(e)(t,o).then(e=>(e=>Object.keys(i).filter(e=>-1!==s.indexOf(e)).every(t=>e[t]===i[t]))(e)?Promise.resolve():n())))},z=e=>(t,n)=>(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/batch",e.indexName),data:{requests:t}},n),(t,n)=>ke(e)(t.taskID,n)),W=e=>t=>a({...t,shouldStop:e=>void 0===e.cursor,request:n=>e.transporter.read({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/browse",e.indexName),data:n},t)}),H=e=>t=>{const n={hitsPerPage:1e3,...t};return a({...n,shouldStop:e=>e.hits.lengthDe(e)("",{...n,...t}).then(e=>({...e,hits:e.hits.map(e=>(delete e._highlightResult,e))}))})},V=e=>t=>{const n={hitsPerPage:1e3,...t};return a({...n,shouldStop:e=>e.hits.lengthSe(e)("",{...n,...t}).then(e=>({...e,hits:e.hits.map(e=>(delete e._highlightResult,e))}))})},q=e=>(t,n,i)=>{const{batchSize:o,...u}=i||{},a={taskIDs:[],objectIDs:[]},l=(r=0)=>{const i=[];let s;for(s=r;s({action:n,body:e})),u).then(e=>(a.objectIDs=a.objectIDs.concat(e.objectIDs),a.taskIDs.push(e.taskID),s++,l(s)))};return(0,r.createWaitablePromise)(l(),(t,n)=>Promise.all(t.taskIDs.map(t=>ke(e)(t,n))))},G=e=>t=>(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/clear",e.indexName)},t),(t,n)=>ke(e)(t.taskID,n)),$=e=>t=>{const{forwardToReplicas:n,...u}=t||{},a=(0,i.createMappedRequestOptions)(u);return n&&(a.queryParameters.forwardToReplicas=1),(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/rules/clear",e.indexName)},a),(t,n)=>ke(e)(t.taskID,n))},Y=e=>t=>{const{forwardToReplicas:n,...u}=t||{},a=(0,i.createMappedRequestOptions)(u);return n&&(a.queryParameters.forwardToReplicas=1),(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/synonyms/clear",e.indexName)},a),(t,n)=>ke(e)(t.taskID,n))},K=e=>(t,n)=>(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/deleteByQuery",e.indexName),data:t},n),(t,n)=>ke(e)(t.taskID,n)),X=e=>t=>(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Delete,path:(0,r.encode)("1/indexes/%s",e.indexName)},t),(t,n)=>ke(e)(t.taskID,n)),Q=e=>(t,n)=>(0,r.createWaitablePromise)(J(e)([t],n).then(e=>({taskID:e.taskIDs[0]})),(t,n)=>ke(e)(t.taskID,n)),J=e=>(t,n)=>{const r=t.map(e=>({objectID:e}));return q(e)(r,xe.DeleteObject,n)},Z=e=>(t,n)=>{const{forwardToReplicas:u,...a}=n||{},l=(0,i.createMappedRequestOptions)(a);return u&&(l.queryParameters.forwardToReplicas=1),(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Delete,path:(0,r.encode)("1/indexes/%s/rules/%s",e.indexName,t)},l),(t,n)=>ke(e)(t.taskID,n))},ee=e=>(t,n)=>{const{forwardToReplicas:u,...a}=n||{},l=(0,i.createMappedRequestOptions)(a);return u&&(l.queryParameters.forwardToReplicas=1),(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Delete,path:(0,r.encode)("1/indexes/%s/synonyms/%s",e.indexName,t)},l),(t,n)=>ke(e)(t.taskID,n))},te=e=>t=>ae(e)(t).then(()=>!0).catch(e=>{if(404!==e.status)throw e;return!1}),ne=e=>(t,n)=>{const{query:r,paginate:i,...o}=n||{};let u=0;const a=()=>we(e)(r||"",{...o,page:u}).then(e=>{for(const[n,r]of Object.entries(e.hits))if(t(r))return{object:r,position:parseInt(n,10),page:u};if(u++,!1===i||u>=e.nbPages)throw{name:"ObjectNotFoundError",message:"Object not found."};return a()});return a()},re=e=>(t,n)=>e.transporter.read({method:o.N.Get,path:(0,r.encode)("1/indexes/%s/%s",e.indexName,t)},n),ie=()=>(e,t)=>{for(const[n,r]of Object.entries(e.hits))if(r.objectID===t)return parseInt(n,10);return-1},oe=e=>(t,n)=>{const{attributesToRetrieve:r,...i}=n||{},u=t.map(t=>({indexName:e.indexName,objectID:t,...r?{attributesToRetrieve:r}:{}}));return e.transporter.read({method:o.N.Post,path:"1/indexes/*/objects",data:{requests:u}},i)},ue=e=>(t,n)=>e.transporter.read({method:o.N.Get,path:(0,r.encode)("1/indexes/%s/rules/%s",e.indexName,t)},n),ae=e=>t=>e.transporter.read({method:o.N.Get,path:(0,r.encode)("1/indexes/%s/settings",e.indexName),data:{getVersion:2}},t),le=e=>(t,n)=>e.transporter.read({method:o.N.Get,path:(0,r.encode)("1/indexes/%s/synonyms/%s",e.indexName,t)},n),se=e=>(t,n)=>e.transporter.read({method:o.N.Get,path:(0,r.encode)("1/indexes/%s/task/%s",e.indexName,t.toString())},n),ce=e=>(t,n)=>(0,r.createWaitablePromise)(fe(e)([t],n).then(e=>({objectID:e.objectIDs[0],taskID:e.taskIDs[0]})),(t,n)=>ke(e)(t.taskID,n)),fe=e=>(t,n)=>{const{createIfNotExists:r,...i}=n||{},o=r?xe.PartialUpdateObject:xe.PartialUpdateObjectNoCreate;return q(e)(t,o,i)},de=e=>(t,n)=>{const{safe:i,autoGenerateObjectIDIfNotExist:u,batchSize:a,...l}=n||{},s=(t,n,i,u)=>(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/operation",t),data:{operation:i,destination:n}},u),(t,n)=>ke(e)(t.taskID,n)),c=Math.random().toString(36).substring(7),f=`${e.indexName}_tmp_${c}`,d=me({appId:e.appId,transporter:e.transporter,indexName:f});let p=[];const h=s(e.indexName,f,"copy",{...l,scope:["settings","synonyms","rules"]});p.push(h);const v=(i?h.wait(l):h).then(()=>{const e=d(t,{...l,autoGenerateObjectIDIfNotExist:u,batchSize:a});return p.push(e),i?e.wait(l):e}).then(()=>{const t=s(f,e.indexName,"move",l);return p.push(t),i?t.wait(l):t}).then(()=>Promise.all(p)).then(([e,t,n])=>({objectIDs:t.objectIDs,taskIDs:[e.taskID,...t.taskIDs,n.taskID]}));return(0,r.createWaitablePromise)(v,(e,t)=>Promise.all(p.map(e=>e.wait(t))))},pe=e=>(t,n)=>ye(e)(t,{...n,clearExistingRules:!0}),he=e=>(t,n)=>be(e)(t,{...n,replaceExistingSynonyms:!0}),ve=e=>(t,n)=>(0,r.createWaitablePromise)(me(e)([t],n).then(e=>({objectID:e.objectIDs[0],taskID:e.taskIDs[0]})),(t,n)=>ke(e)(t.taskID,n)),me=e=>(t,n)=>{const{autoGenerateObjectIDIfNotExist:i,...o}=n||{},u=i?xe.AddObject:xe.UpdateObject;if(u===xe.UpdateObject)for(const e of t)if(void 0===e.objectID)return(0,r.createWaitablePromise)(Promise.reject({name:"MissingObjectIDError",message:"All objects must have an unique objectID (like a primary key) to be valid. Algolia is also able to generate objectIDs automatically but *it's not recommended*. To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option."}));return q(e)(t,u,o)},ge=e=>(t,n)=>ye(e)([t],n),ye=e=>(t,n)=>{const{forwardToReplicas:u,clearExistingRules:a,...l}=n||{},s=(0,i.createMappedRequestOptions)(l);return u&&(s.queryParameters.forwardToReplicas=1),a&&(s.queryParameters.clearExistingRules=1),(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/rules/batch",e.indexName),data:t},s),(t,n)=>ke(e)(t.taskID,n))},_e=e=>(t,n)=>be(e)([t],n),be=e=>(t,n)=>{const{forwardToReplicas:u,replaceExistingSynonyms:a,...l}=n||{},s=(0,i.createMappedRequestOptions)(l);return u&&(s.queryParameters.forwardToReplicas=1),a&&(s.queryParameters.replaceExistingSynonyms=1),(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/synonyms/batch",e.indexName),data:t},s),(t,n)=>ke(e)(t.taskID,n))},we=e=>(t,n)=>e.transporter.read({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/query",e.indexName),data:{query:t},cacheable:!0},n),Ee=e=>(t,n,i)=>e.transporter.read({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/facets/%s/query",e.indexName,t),data:{facetQuery:n},cacheable:!0},i),De=e=>(t,n)=>e.transporter.read({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/rules/search",e.indexName),data:{query:t}},n),Se=e=>(t,n)=>e.transporter.read({method:o.N.Post,path:(0,r.encode)("1/indexes/%s/synonyms/search",e.indexName),data:{query:t}},n),Ce=e=>(t,n)=>{const{forwardToReplicas:u,...a}=n||{},l=(0,i.createMappedRequestOptions)(a);return u&&(l.queryParameters.forwardToReplicas=1),(0,r.createWaitablePromise)(e.transporter.write({method:o.N.Put,path:(0,r.encode)("1/indexes/%s/settings",e.indexName),data:t},l),(t,n)=>ke(e)(t.taskID,n))},ke=e=>(t,n)=>(0,r.createRetryablePromise)(r=>se(e)(t,n).then(e=>"published"!==e.status?r():void 0)),Te={AddObject:"addObject",Analytics:"analytics",Browser:"browse",DeleteIndex:"deleteIndex",DeleteObject:"deleteObject",EditSettings:"editSettings",ListIndexes:"listIndexes",Logs:"logs",Recommendation:"recommendation",Search:"search",SeeUnretrievableAttributes:"seeUnretrievableAttributes",Settings:"settings",Usage:"usage"},xe={AddObject:"addObject",UpdateObject:"updateObject",PartialUpdateObject:"partialUpdateObject",PartialUpdateObjectNoCreate:"partialUpdateObjectNoCreate",DeleteObject:"deleteObject"},Ae={Settings:"settings",Synonyms:"synonyms",Rules:"rules"},Oe={None:"none",StopIfEnoughMatches:"stopIfEnoughMatches"},Pe={Synonym:"synonym",OneWaySynonym:"oneWaySynonym",AltCorrection1:"altCorrection1",AltCorrection2:"altCorrection2",Placeholder:"placeholder"}},8045:(e,t,n)=>{"use strict";function r(){return{debug:(e,t)=>Promise.resolve(),info:(e,t)=>Promise.resolve(),error:(e,t)=>Promise.resolve()}}n.r(t),n.d(t,{LogLevelEnum:()=>i,createNullLogger:()=>r});const i={Debug:1,Info:2,Error:3}},5541:(e,t,n)=>{"use strict";n.d(t,{N:()=>r});const r={Delete:"DELETE",Get:"GET",Post:"POST",Put:"PUT"}},9178:(e,t,n)=>{"use strict";n.r(t),n.d(t,{createNodeHttpRequester:()=>u});var r=n(8605),i=n(7211),o=n(8835);function u(){const e={keepAlive:!0},t=new r.Agent(e),n=new i.Agent(e);return{send:e=>new Promise(u=>{const a=(0,o.parse)(e.url),l=null===a.query?a.pathname:`${a.pathname}?${a.query}`,s={agent:"https:"===a.protocol?n:t,hostname:a.hostname,path:l,method:e.method,headers:e.headers,...void 0!==a.port?{port:a.port||""}:{}},c=("https:"===a.protocol?i:r).request(s,e=>{let t="";e.on("data",e=>t+=e),e.on("end",()=>{clearTimeout(d),clearTimeout(p),u({status:e.statusCode||0,content:t,isTimedOut:!1})})}),f=(e,t)=>setTimeout(()=>{c.abort(),u({status:0,content:t,isTimedOut:!0})},1e3*e),d=f(e.connectTimeout,"Connection timeout");let p;c.on("error",e=>{clearTimeout(d),clearTimeout(p),u({status:0,content:e.message,isTimedOut:!1})}),c.once("response",()=>{clearTimeout(d),p=f(e.responseTimeout,"Socket timeout")}),void 0!==e.data&&c.write(e.data),c.end()}),destroy:()=>(t.destroy(),n.destroy(),Promise.resolve())}}},7858:(e,t,n)=>{"use strict";n.r(t),n.d(t,{CallEnum:()=>o,HostStatusEnum:()=>u,createApiError:()=>E,createDeserializationError:()=>D,createMappedRequestOptions:()=>i,createRetryError:()=>S,createStatefulHost:()=>a,createStatelessHost:()=>c,createTransporter:()=>d,createUserAgent:()=>p,deserializeFailure:()=>v,deserializeSuccess:()=>h,isStatefulHostTimeouted:()=>s,isStatefulHostUp:()=>l,serializeData:()=>y,serializeHeaders:()=>_,serializeQueryParameters:()=>g,serializeUrl:()=>m,stackFrameWithoutCredentials:()=>w,stackTraceWithoutCredentials:()=>b});var r=n(5541);function i(e,t){const n=e||{},r=n.data||{};return Object.keys(n).forEach(e=>{-1===["timeout","headers","queryParameters","data","cacheable"].indexOf(e)&&(r[e]=n[e])}),{data:Object.entries(r).length>0?r:void 0,timeout:n.timeout||t,headers:n.headers||{},queryParameters:n.queryParameters||{},cacheable:n.cacheable}}const o={Read:1,Write:2,Any:3},u={Up:1,Down:2,Timeouted:3};function a(e,t=u.Up){return{...e,status:t,lastUpdate:Date.now()}}function l(e){return e.status===u.Up||Date.now()-e.lastUpdate>12e4}function s(e){return e.status===u.Timeouted&&Date.now()-e.lastUpdate<=12e4}function c(e){return{protocol:e.protocol||"https",url:e.url,accept:e.accept||o.Any}}function f(e,t,n,i){const o=[],f=y(n,i),d=_(e,i),p=n.method,g=n.method!==r.N.Get?{}:{...n.data,...i.data},E={"x-algolia-agent":e.userAgent.value,...e.queryParameters,...g,...i.queryParameters};let D=0;const C=(t,r)=>{const l=t.pop();if(void 0===l)throw S(b(o));const s={data:f,headers:d,method:p,url:m(l,n.path,E),connectTimeout:r(D,e.timeouts.connect),responseTimeout:r(D,i.timeout)},c=e=>{const n={request:s,response:e,host:l,triesLeft:t.length};return o.push(n),n},g={onSucess:e=>h(e),onRetry(n){const i=c(n);return n.isTimedOut&&D++,Promise.all([e.logger.info("Retryable failure",w(i)),e.hostsCache.set(l,a(l,n.isTimedOut?u.Timeouted:u.Down))]).then(()=>C(t,r))},onFail(e){throw c(e),v(e,b(o))}};return e.requester.send(s).then(e=>((e,t)=>(e=>{const t=e.status;return e.isTimedOut||(({isTimedOut:e,status:t})=>!e&&0==~~t)(e)||2!=~~(t/100)&&4!=~~(t/100)})(e)?t.onRetry(e):(({status:e})=>2==~~(e/100))(e)?t.onSucess(e):t.onFail(e))(e,g))};return function(e,t){return Promise.all(t.map(t=>e.get(t,()=>Promise.resolve(a(t))))).then(e=>{const n=e.filter(e=>l(e)),r=e.filter(e=>s(e)),i=[...n,...r];return{getTimeout:(e,t)=>(0===r.length&&0===e?1:r.length+3+e)*t,statelessHosts:i.length>0?i.map(e=>c(e)):t}})}(e.hostsCache,t).then(e=>C([...e.statelessHosts].reverse(),e.getTimeout))}function d(e){const{hostsCache:t,logger:n,requester:r,requestsCache:u,responsesCache:a,timeouts:l,userAgent:s,hosts:d,queryParameters:p,headers:h}=e,v={hostsCache:t,logger:n,requester:r,requestsCache:u,responsesCache:a,timeouts:l,userAgent:s,headers:h,queryParameters:p,hosts:d.map(e=>c(e)),read(e,t){const n=i(t,v.timeouts.read),r=()=>f(v,v.hosts.filter(e=>0!=(e.accept&o.Read)),e,n);if(!0!==(void 0!==n.cacheable?n.cacheable:e.cacheable))return r();const u={request:e,mappedRequestOptions:n,transporter:{queryParameters:v.queryParameters,headers:v.headers}};return v.responsesCache.get(u,()=>v.requestsCache.get(u,()=>v.requestsCache.set(u,r()).then(e=>Promise.all([v.requestsCache.delete(u),e]),e=>Promise.all([v.requestsCache.delete(u),Promise.reject(e)])).then(([e,t])=>t)),{miss:e=>v.responsesCache.set(u,e)})},write:(e,t)=>f(v,v.hosts.filter(e=>0!=(e.accept&o.Write)),e,i(t,v.timeouts.write))};return v}function p(e){const t={value:`Algolia for JavaScript (${e})`,add(e){const n=`; ${e.segment}${void 0!==e.version?` (${e.version})`:""}`;return-1===t.value.indexOf(n)&&(t.value=`${t.value}${n}`),t}};return t}function h(e){try{return JSON.parse(e.content)}catch(t){throw D(t.message,e)}}function v({content:e,status:t},n){let r=e;try{r=JSON.parse(e).message}catch(e){}return E(r,t,n)}function m(e,t,n){const r=g(n);let i=`${e.protocol}://${e.url}/${"/"===t.charAt(0)?t.substr(1):t}`;return r.length&&(i+="?"+r),i}function g(e){return Object.keys(e).map(t=>{return function(e,...t){let n=0;return e.replace(/%s/g,()=>encodeURIComponent(t[n++]))}("%s=%s",t,(n=e[t],"[object Object]"===Object.prototype.toString.call(n)||"[object Array]"===Object.prototype.toString.call(n)?JSON.stringify(e[t]):e[t]));var n}).join("&")}function y(e,t){if(e.method===r.N.Get||void 0===e.data&&void 0===t.data)return;const n=Array.isArray(e.data)?e.data:{...e.data,...t.data};return JSON.stringify(n)}function _(e,t){const n={...e.headers,...t.headers},r={};return Object.keys(n).forEach(e=>{const t=n[e];r[e.toLowerCase()]=t}),r}function b(e){return e.map(e=>w(e))}function w(e){const t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return{...e,request:{...e.request,headers:{...e.request.headers,...t}}}}function E(e,t,n){return{name:"ApiError",message:e,status:t,transporterStackTrace:n}}function D(e,t){return{name:"DeserializationError",message:e,response:t}}function S(e){return{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:e}}},8774:(e,t,n)=>{"use strict";var r=n(469),i=n(6712),o=n(2223),u=n(1757),a=n(103),l=n(6586),s=n(8045),c=n(9178),f=n(7858);function d(e,t,n){const d={appId:e,apiKey:t,timeouts:{connect:2,read:5,write:30},requester:c.createNodeHttpRequester(),logger:s.createNullLogger(),responsesCache:r.createNullCache(),requestsCache:r.createNullCache(),hostsCache:i.createInMemoryCache(),userAgent:f.createUserAgent(u.version).add({segment:"Node.js",version:process.versions.node})};return l.createSearchClient({...d,...n,methods:{search:l.multipleQueries,searchForFacetValues:l.multipleSearchForFacetValues,multipleBatch:l.multipleBatch,multipleGetObjects:l.multipleGetObjects,multipleQueries:l.multipleQueries,copyIndex:l.copyIndex,copySettings:l.copySettings,copyRules:l.copyRules,copySynonyms:l.copySynonyms,moveIndex:l.moveIndex,listIndices:l.listIndices,getLogs:l.getLogs,listClusters:l.listClusters,multipleSearchForFacetValues:l.multipleSearchForFacetValues,getApiKey:l.getApiKey,addApiKey:l.addApiKey,listApiKeys:l.listApiKeys,updateApiKey:l.updateApiKey,deleteApiKey:l.deleteApiKey,restoreApiKey:l.restoreApiKey,assignUserID:l.assignUserID,assignUserIDs:l.assignUserIDs,getUserID:l.getUserID,searchUserIDs:l.searchUserIDs,listUserIDs:l.listUserIDs,getTopUserIDs:l.getTopUserIDs,removeUserID:l.removeUserID,hasPendingMappings:l.hasPendingMappings,generateSecuredApiKey:l.generateSecuredApiKey,getSecuredApiKeyRemainingValidity:l.getSecuredApiKeyRemainingValidity,destroy:u.destroy,initIndex:e=>t=>l.initIndex(e)(t,{methods:{batch:l.batch,delete:l.deleteIndex,getObject:l.getObject,getObjects:l.getObjects,saveObject:l.saveObject,saveObjects:l.saveObjects,search:l.search,searchForFacetValues:l.searchForFacetValues,waitTask:l.waitTask,setSettings:l.setSettings,getSettings:l.getSettings,partialUpdateObject:l.partialUpdateObject,partialUpdateObjects:l.partialUpdateObjects,deleteObject:l.deleteObject,deleteObjects:l.deleteObjects,deleteBy:l.deleteBy,clearObjects:l.clearObjects,browseObjects:l.browseObjects,getObjectPosition:l.getObjectPosition,findObject:l.findObject,exists:l.exists,saveSynonym:l.saveSynonym,saveSynonyms:l.saveSynonyms,getSynonym:l.getSynonym,searchSynonyms:l.searchSynonyms,browseSynonyms:l.browseSynonyms,deleteSynonym:l.deleteSynonym,clearSynonyms:l.clearSynonyms,replaceAllObjects:l.replaceAllObjects,replaceAllSynonyms:l.replaceAllSynonyms,searchRules:l.searchRules,getRule:l.getRule,deleteRule:l.deleteRule,saveRule:l.saveRule,saveRules:l.saveRules,replaceAllRules:l.replaceAllRules,browseRules:l.browseRules,clearRules:l.clearRules}}),initAnalytics:()=>e=>o.createAnalyticsClient({...d,...e,methods:{addABTest:o.addABTest,getABTest:o.getABTest,getABTests:o.getABTests,stopABTest:o.stopABTest,deleteABTest:o.deleteABTest}}),initRecommendation:()=>e=>a.createRecommendationClient({...d,...e,methods:{getPersonalizationStrategy:a.getPersonalizationStrategy,setPersonalizationStrategy:a.setPersonalizationStrategy}})}})}d.version=u.version,e.exports=d},4410:(e,t,n)=>{const r=n(8774);e.exports=r,e.exports.default=r},7589:e=>{"use strict";const t=e.exports;e.exports.default=t;const n="[",r="]",i="",o=";",u="Apple_Terminal"===process.env.TERM_PROGRAM;t.cursorTo=(e,t)=>{if("number"!=typeof e)throw new TypeError("The `x` argument is required");return"number"!=typeof t?n+(e+1)+"G":n+(t+1)+";"+(e+1)+"H"},t.cursorMove=(e,t)=>{if("number"!=typeof e)throw new TypeError("The `x` argument is required");let r="";return e<0?r+=n+-e+"D":e>0&&(r+=n+e+"C"),t<0?r+=n+-t+"A":t>0&&(r+=n+t+"B"),r},t.cursorUp=(e=1)=>n+e+"A",t.cursorDown=(e=1)=>n+e+"B",t.cursorForward=(e=1)=>n+e+"C",t.cursorBackward=(e=1)=>n+e+"D",t.cursorLeft="",t.cursorSavePosition=u?"7":"",t.cursorRestorePosition=u?"8":"",t.cursorGetPosition="",t.cursorNextLine="",t.cursorPrevLine="",t.cursorHide="[?25l",t.cursorShow="[?25h",t.eraseLines=e=>{let n="";for(let r=0;r[r,"8",o,o,t,i,e,r,"8",o,o,i].join(""),t.image=(e,t={})=>{let n=r+"1337;File=inline=1";return t.width&&(n+=";width="+t.width),t.height&&(n+=";height="+t.height),!1===t.preserveAspectRatio&&(n+=";preserveAspectRatio=0"),n+":"+e.toString("base64")+i},t.iTerm={setCwd:(e=process.cwd())=>`${r}50;CurrentDir=${e}${i}`,annotation:(e,t={})=>{let n=r+"1337;";const o=void 0!==t.x,u=void 0!==t.y;if((o||u)&&(!o||!u||void 0===t.length))throw new Error("`x`, `y` and `length` must be defined when `x` or `y` is defined");return e=e.replace(/\|/g,""),n+=t.isHidden?"AddHiddenAnnotation=":"AddAnnotation=",t.length>0?n+=(o?[e,t.length,t.x,t.y]:[t.length,e]).join("|"):n+=e,n+i}}},5378:e=>{"use strict";e.exports=e=>{e=Object.assign({onlyFirst:!1},e);const t=["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)","(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"].join("|");return new RegExp(t,e.onlyFirst?void 0:"g")}},1337:e=>{"use strict";e.exports=({onlyFirst:e=!1}={})=>{const t=["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)","(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"].join("|");return new RegExp(t,e?void 0:"g")}},8483:(e,t,n)=>{"use strict";e=n.nmd(e);const r=(e,t)=>(...n)=>`[${e(...n)+t}m`,i=(e,t)=>(...n)=>{const r=e(...n);return`[${38+t};5;${r}m`},o=(e,t)=>(...n)=>{const r=e(...n);return`[${38+t};2;${r[0]};${r[1]};${r[2]}m`},u=e=>e,a=(e,t,n)=>[e,t,n],l=(e,t,n)=>{Object.defineProperty(e,t,{get:()=>{const r=n();return Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0}),r},enumerable:!0,configurable:!0})};let s;const c=(e,t,r,i)=>{void 0===s&&(s=n(2744));const o=i?10:0,u={};for(const[n,i]of Object.entries(s)){const a="ansi16"===n?"ansi":n;n===t?u[a]=e(r,o):"object"==typeof i&&(u[a]=e(i[t],o))}return u};Object.defineProperty(e,"exports",{enumerable:!0,get:function(){const e=new Map,t={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};t.color.gray=t.color.blackBright,t.bgColor.bgGray=t.bgColor.bgBlackBright,t.color.grey=t.color.blackBright,t.bgColor.bgGrey=t.bgColor.bgBlackBright;for(const[n,r]of Object.entries(t)){for(const[n,i]of Object.entries(r))t[n]={open:`[${i[0]}m`,close:`[${i[1]}m`},r[n]=t[n],e.set(i[0],i[1]);Object.defineProperty(t,n,{value:r,enumerable:!1})}return Object.defineProperty(t,"codes",{value:e,enumerable:!1}),t.color.close="",t.bgColor.close="",l(t.color,"ansi",()=>c(r,"ansi16",u,!1)),l(t.color,"ansi256",()=>c(i,"ansi256",u,!1)),l(t.color,"ansi16m",()=>c(o,"rgb",a,!1)),l(t.bgColor,"ansi",()=>c(r,"ansi16",u,!0)),l(t.bgColor,"ansi256",()=>c(i,"ansi256",u,!0)),l(t.bgColor,"ansi16m",()=>c(o,"rgb",a,!0)),t}})},5640:e=>{"use strict";e.exports=e=>e&&e.exact?new RegExp("^[\ud800-\udbff][\udc00-\udfff]$"):new RegExp("[\ud800-\udbff][\udc00-\udfff]","g")},409:e=>{"use strict";e.exports=e=>e&&e.exact?new RegExp("^[\ud800-\udbff][\udc00-\udfff]$"):new RegExp("[\ud800-\udbff][\udc00-\udfff]","g")},2633:e=>{"use strict";e.exports=(e,{include:t,exclude:n}={})=>{const r=e=>{const r=t=>"string"==typeof t?e===t:t.test(e);return t?t.some(r):!n||!n.some(r)};for(const[t,n]of(e=>{const t=new Set;do{for(const n of Reflect.ownKeys(e))t.add([e,n])}while((e=Reflect.getPrototypeOf(e))&&e!==Object.prototype);return t})(e.constructor.prototype)){if("constructor"===n||!r(n))continue;const i=Reflect.getOwnPropertyDescriptor(t,n);i&&"function"==typeof i.value&&(e[n]=e[n].bind(e))}return e}},5882:(e,t,n)=>{"use strict";const r=n(8483),{stdout:i,stderr:o}=n(9428),{stringReplaceAll:u,stringEncaseCRLFWithFirstIndex:a}=n(3327),l=["ansi","ansi","ansi256","ansi16m"],s=Object.create(null);class c{constructor(e){return f(e)}}const f=e=>{const t={};return((e,t={})=>{if(t.level>3||t.level<0)throw new Error("The `level` option should be an integer from 0 to 3");const n=i?i.level:0;e.level=void 0===t.level?n:t.level})(t,e),t.template=(...e)=>_(t.template,...e),Object.setPrototypeOf(t,d.prototype),Object.setPrototypeOf(t.template,t),t.template.constructor=()=>{throw new Error("`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.")},t.template.Instance=c,t.template};function d(e){return f(e)}for(const[e,t]of Object.entries(r))s[e]={get(){const n=m(this,v(t.open,t.close,this._styler),this._isEmpty);return Object.defineProperty(this,e,{value:n}),n}};s.visible={get(){const e=m(this,this._styler,!0);return Object.defineProperty(this,"visible",{value:e}),e}};const p=["rgb","hex","keyword","hsl","hsv","hwb","ansi","ansi256"];for(const e of p)s[e]={get(){const{level:t}=this;return function(...n){const i=v(r.color[l[t]][e](...n),r.color.close,this._styler);return m(this,i,this._isEmpty)}}};for(const e of p){s["bg"+e[0].toUpperCase()+e.slice(1)]={get(){const{level:t}=this;return function(...n){const i=v(r.bgColor[l[t]][e](...n),r.bgColor.close,this._styler);return m(this,i,this._isEmpty)}}}}const h=Object.defineProperties(()=>{},{...s,level:{enumerable:!0,get(){return this._generator.level},set(e){this._generator.level=e}}}),v=(e,t,n)=>{let r,i;return void 0===n?(r=e,i=t):(r=n.openAll+e,i=t+n.closeAll),{open:e,close:t,openAll:r,closeAll:i,parent:n}},m=(e,t,n)=>{const r=(...e)=>g(r,1===e.length?""+e[0]:e.join(" "));return r.__proto__=h,r._generator=e,r._styler=t,r._isEmpty=n,r},g=(e,t)=>{if(e.level<=0||!t)return e._isEmpty?"":t;let n=e._styler;if(void 0===n)return t;const{openAll:r,closeAll:i}=n;if(-1!==t.indexOf(""))for(;void 0!==n;)t=u(t,n.close,n.open),n=n.parent;const o=t.indexOf("\n");return-1!==o&&(t=a(t,i,r,o)),r+t+i};let y;const _=(e,...t)=>{const[r]=t;if(!Array.isArray(r))return t.join(" ");const i=t.slice(1),o=[r.raw[0]];for(let e=1;e{"use strict";const t=/(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi,n=/(?:^|\.)(\w+)(?:\(([^)]*)\))?/g,r=/^(['"])((?:\\.|(?!\1)[^\\])*)\1$/,i=/\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.)|([^\\])/gi,o=new Map([["n","\n"],["r","\r"],["t","\t"],["b","\b"],["f","\f"],["v","\v"],["0","\0"],["\\","\\"],["e",""],["a",""]]);function u(e){const t="u"===e[0],n="{"===e[1];return t&&!n&&5===e.length||"x"===e[0]&&3===e.length?String.fromCharCode(parseInt(e.slice(1),16)):t&&n?String.fromCodePoint(parseInt(e.slice(2,-1),16)):o.get(e)||e}function a(e,t){const n=[],o=t.trim().split(/\s*,\s*/g);let a;for(const t of o){const o=Number(t);if(Number.isNaN(o)){if(!(a=t.match(r)))throw new Error(`Invalid Chalk template style argument: ${t} (in style '${e}')`);n.push(a[2].replace(i,(e,t,n)=>t?u(t):n))}else n.push(o)}return n}function l(e){n.lastIndex=0;const t=[];let r;for(;null!==(r=n.exec(e));){const e=r[1];if(r[2]){const n=a(e,r[2]);t.push([e].concat(n))}else t.push([e])}return t}function s(e,t){const n={};for(const e of t)for(const t of e.styles)n[t[0]]=e.inverse?null:t.slice(1);let r=e;for(const[e,t]of Object.entries(n))if(Array.isArray(t)){if(!(e in r))throw new Error("Unknown Chalk style: "+e);r=t.length>0?r[e](...t):r[e]}return r}e.exports=(e,n)=>{const r=[],i=[];let o=[];if(n.replace(t,(t,n,a,c,f,d)=>{if(n)o.push(u(n));else if(c){const t=o.join("");o=[],i.push(0===r.length?t:s(e,r)(t)),r.push({inverse:a,styles:l(c)})}else if(f){if(0===r.length)throw new Error("Found extraneous } in Chalk template literal");i.push(s(e,r)(o.join(""))),o=[],r.pop()}else o.push(d)}),i.push(o.join("")),r.length>0){const e=`Chalk template literal is missing ${r.length} closing bracket${1===r.length?"":"s"} (\`}\`)`;throw new Error(e)}return i.join("")}},3327:e=>{"use strict";e.exports={stringReplaceAll:(e,t,n)=>{let r=e.indexOf(t);if(-1===r)return e;const i=t.length;let o=0,u="";do{u+=e.substr(o,r-o)+t+n,o=r+i,r=e.indexOf(t,o)}while(-1!==r);return u+=e.substr(o),u},stringEncaseCRLFWithFirstIndex:(e,t,n,r)=>{let i=0,o="";do{const u="\r"===e[r-1];o+=e.substr(i,(u?r-1:r)-i)+t+(u?"\r\n":"\n")+n,i=r+1,r=e.indexOf("\n",i)}while(-1!==r);return o+=e.substr(i),o}}},1525:(e,t,n)=>{"use strict";const r=n(8483),{stdout:i,stderr:o}=n(9428),{stringReplaceAll:u,stringEncaseCRLFWithFirstIndex:a}=n(6539),{isArray:l}=Array,s=["ansi","ansi","ansi256","ansi16m"],c=Object.create(null);class f{constructor(e){return d(e)}}const d=e=>{const t={};return((e,t={})=>{if(t.level&&!(Number.isInteger(t.level)&&t.level>=0&&t.level<=3))throw new Error("The `level` option should be an integer from 0 to 3");const n=i?i.level:0;e.level=void 0===t.level?n:t.level})(t,e),t.template=(...e)=>b(t.template,...e),Object.setPrototypeOf(t,p.prototype),Object.setPrototypeOf(t.template,t),t.template.constructor=()=>{throw new Error("`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.")},t.template.Instance=f,t.template};function p(e){return d(e)}for(const[e,t]of Object.entries(r))c[e]={get(){const n=g(this,m(t.open,t.close,this._styler),this._isEmpty);return Object.defineProperty(this,e,{value:n}),n}};c.visible={get(){const e=g(this,this._styler,!0);return Object.defineProperty(this,"visible",{value:e}),e}};const h=["rgb","hex","keyword","hsl","hsv","hwb","ansi","ansi256"];for(const e of h)c[e]={get(){const{level:t}=this;return function(...n){const i=m(r.color[s[t]][e](...n),r.color.close,this._styler);return g(this,i,this._isEmpty)}}};for(const e of h){c["bg"+e[0].toUpperCase()+e.slice(1)]={get(){const{level:t}=this;return function(...n){const i=m(r.bgColor[s[t]][e](...n),r.bgColor.close,this._styler);return g(this,i,this._isEmpty)}}}}const v=Object.defineProperties(()=>{},{...c,level:{enumerable:!0,get(){return this._generator.level},set(e){this._generator.level=e}}}),m=(e,t,n)=>{let r,i;return void 0===n?(r=e,i=t):(r=n.openAll+e,i=t+n.closeAll),{open:e,close:t,openAll:r,closeAll:i,parent:n}},g=(e,t,n)=>{const r=(...e)=>l(e[0])&&l(e[0].raw)?y(r,b(r,...e)):y(r,1===e.length?""+e[0]:e.join(" "));return Object.setPrototypeOf(r,v),r._generator=e,r._styler=t,r._isEmpty=n,r},y=(e,t)=>{if(e.level<=0||!t)return e._isEmpty?"":t;let n=e._styler;if(void 0===n)return t;const{openAll:r,closeAll:i}=n;if(-1!==t.indexOf(""))for(;void 0!==n;)t=u(t,n.close,n.open),n=n.parent;const o=t.indexOf("\n");return-1!==o&&(t=a(t,i,r,o)),r+t+i};let _;const b=(e,...t)=>{const[r]=t;if(!l(r)||!l(r.raw))return t.join(" ");const i=t.slice(1),o=[r.raw[0]];for(let e=1;e{"use strict";const t=/(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi,n=/(?:^|\.)(\w+)(?:\(([^)]*)\))?/g,r=/^(['"])((?:\\.|(?!\1)[^\\])*)\1$/,i=/\\(u(?:[a-f\d]{4}|{[a-f\d]{1,6}})|x[a-f\d]{2}|.)|([^\\])/gi,o=new Map([["n","\n"],["r","\r"],["t","\t"],["b","\b"],["f","\f"],["v","\v"],["0","\0"],["\\","\\"],["e",""],["a",""]]);function u(e){const t="u"===e[0],n="{"===e[1];return t&&!n&&5===e.length||"x"===e[0]&&3===e.length?String.fromCharCode(parseInt(e.slice(1),16)):t&&n?String.fromCodePoint(parseInt(e.slice(2,-1),16)):o.get(e)||e}function a(e,t){const n=[],o=t.trim().split(/\s*,\s*/g);let a;for(const t of o){const o=Number(t);if(Number.isNaN(o)){if(!(a=t.match(r)))throw new Error(`Invalid Chalk template style argument: ${t} (in style '${e}')`);n.push(a[2].replace(i,(e,t,n)=>t?u(t):n))}else n.push(o)}return n}function l(e){n.lastIndex=0;const t=[];let r;for(;null!==(r=n.exec(e));){const e=r[1];if(r[2]){const n=a(e,r[2]);t.push([e].concat(n))}else t.push([e])}return t}function s(e,t){const n={};for(const e of t)for(const t of e.styles)n[t[0]]=e.inverse?null:t.slice(1);let r=e;for(const[e,t]of Object.entries(n))if(Array.isArray(t)){if(!(e in r))throw new Error("Unknown Chalk style: "+e);r=t.length>0?r[e](...t):r[e]}return r}e.exports=(e,n)=>{const r=[],i=[];let o=[];if(n.replace(t,(t,n,a,c,f,d)=>{if(n)o.push(u(n));else if(c){const t=o.join("");o=[],i.push(0===r.length?t:s(e,r)(t)),r.push({inverse:a,styles:l(c)})}else if(f){if(0===r.length)throw new Error("Found extraneous } in Chalk template literal");i.push(s(e,r)(o.join(""))),o=[],r.pop()}else o.push(d)}),i.push(o.join("")),r.length>0){const e=`Chalk template literal is missing ${r.length} closing bracket${1===r.length?"":"s"} (\`}\`)`;throw new Error(e)}return i.join("")}},6539:e=>{"use strict";e.exports={stringReplaceAll:(e,t,n)=>{let r=e.indexOf(t);if(-1===r)return e;const i=t.length;let o=0,u="";do{u+=e.substr(o,r-o)+t+n,o=r+i,r=e.indexOf(t,o)}while(-1!==r);return u+=e.substr(o),u},stringEncaseCRLFWithFirstIndex:(e,t,n,r)=>{let i=0,o="";do{const u="\r"===e[r-1];o+=e.substr(i,(u?r-1:r)-i)+t+(u?"\r\n":"\n")+n,i=r+1,r=e.indexOf("\n",i)}while(-1!==r);return o+=e.substr(i),o}}},5864:(e,t,n)=>{"use strict";var r=n(5832),i=process.env;function o(e){return"string"==typeof e?!!i[e]:Object.keys(e).every((function(t){return i[t]===e[t]}))}Object.defineProperty(t,"_vendors",{value:r.map((function(e){return e.constant}))}),t.name=null,t.isPR=null,r.forEach((function(e){var n=(Array.isArray(e.env)?e.env:[e.env]).every((function(e){return o(e)}));if(t[e.constant]=n,n)switch(t.name=e.name,typeof e.pr){case"string":t.isPR=!!i[e.pr];break;case"object":"env"in e.pr?t.isPR=e.pr.env in i&&i[e.pr.env]!==e.pr.ne:"any"in e.pr?t.isPR=e.pr.any.some((function(e){return!!i[e]})):t.isPR=o(e.pr);break;default:t.isPR=null}})),t.isCI=!!(i.CI||i.CONTINUOUS_INTEGRATION||i.BUILD_NUMBER||i.RUN_ID||t.name)},5832:e=>{"use strict";e.exports=JSON.parse('[{"name":"AppVeyor","constant":"APPVEYOR","env":"APPVEYOR","pr":"APPVEYOR_PULL_REQUEST_NUMBER"},{"name":"Azure Pipelines","constant":"AZURE_PIPELINES","env":"SYSTEM_TEAMFOUNDATIONCOLLECTIONURI","pr":"SYSTEM_PULLREQUEST_PULLREQUESTID"},{"name":"Bamboo","constant":"BAMBOO","env":"bamboo_planKey"},{"name":"Bitbucket Pipelines","constant":"BITBUCKET","env":"BITBUCKET_COMMIT","pr":"BITBUCKET_PR_ID"},{"name":"Bitrise","constant":"BITRISE","env":"BITRISE_IO","pr":"BITRISE_PULL_REQUEST"},{"name":"Buddy","constant":"BUDDY","env":"BUDDY_WORKSPACE_ID","pr":"BUDDY_EXECUTION_PULL_REQUEST_ID"},{"name":"Buildkite","constant":"BUILDKITE","env":"BUILDKITE","pr":{"env":"BUILDKITE_PULL_REQUEST","ne":"false"}},{"name":"CircleCI","constant":"CIRCLE","env":"CIRCLECI","pr":"CIRCLE_PULL_REQUEST"},{"name":"Cirrus CI","constant":"CIRRUS","env":"CIRRUS_CI","pr":"CIRRUS_PR"},{"name":"AWS CodeBuild","constant":"CODEBUILD","env":"CODEBUILD_BUILD_ARN"},{"name":"Codeship","constant":"CODESHIP","env":{"CI_NAME":"codeship"}},{"name":"Drone","constant":"DRONE","env":"DRONE","pr":{"DRONE_BUILD_EVENT":"pull_request"}},{"name":"dsari","constant":"DSARI","env":"DSARI"},{"name":"GitLab CI","constant":"GITLAB","env":"GITLAB_CI"},{"name":"GoCD","constant":"GOCD","env":"GO_PIPELINE_LABEL"},{"name":"Hudson","constant":"HUDSON","env":"HUDSON_URL"},{"name":"Jenkins","constant":"JENKINS","env":["JENKINS_URL","BUILD_ID"],"pr":{"any":["ghprbPullId","CHANGE_ID"]}},{"name":"Magnum CI","constant":"MAGNUM","env":"MAGNUM"},{"name":"Netlify CI","constant":"NETLIFY","env":"NETLIFY_BUILD_BASE","pr":{"env":"PULL_REQUEST","ne":"false"}},{"name":"Sail CI","constant":"SAIL","env":"SAILCI","pr":"SAIL_PULL_REQUEST_NUMBER"},{"name":"Semaphore","constant":"SEMAPHORE","env":"SEMAPHORE","pr":"PULL_REQUEST_NUMBER"},{"name":"Shippable","constant":"SHIPPABLE","env":"SHIPPABLE","pr":{"IS_PULL_REQUEST":"true"}},{"name":"Solano CI","constant":"SOLANO","env":"TDDIUM","pr":"TDDIUM_PR_ID"},{"name":"Strider CD","constant":"STRIDER","env":"STRIDER"},{"name":"TaskCluster","constant":"TASKCLUSTER","env":["TASK_ID","RUN_ID"]},{"name":"TeamCity","constant":"TEAMCITY","env":"TEAMCITY_VERSION"},{"name":"Travis CI","constant":"TRAVIS","env":"TRAVIS","pr":{"env":"TRAVIS_PULL_REQUEST","ne":"false"}}]')},4163:e=>{"use strict";e.exports=JSON.parse('{"single":{"topLeft":"┌","topRight":"┐","bottomRight":"┘","bottomLeft":"└","vertical":"│","horizontal":"─"},"double":{"topLeft":"╔","topRight":"╗","bottomRight":"╝","bottomLeft":"╚","vertical":"║","horizontal":"═"},"round":{"topLeft":"╭","topRight":"╮","bottomRight":"╯","bottomLeft":"╰","vertical":"│","horizontal":"─"},"bold":{"topLeft":"┏","topRight":"┓","bottomRight":"┛","bottomLeft":"┗","vertical":"┃","horizontal":"━"},"singleDouble":{"topLeft":"╓","topRight":"╖","bottomRight":"╜","bottomLeft":"╙","vertical":"║","horizontal":"─"},"doubleSingle":{"topLeft":"╒","topRight":"╕","bottomRight":"╛","bottomLeft":"╘","vertical":"│","horizontal":"═"},"classic":{"topLeft":"+","topRight":"+","bottomRight":"+","bottomLeft":"+","vertical":"|","horizontal":"-"}}')},4097:(e,t,n)=>{"use strict";const r=n(4163);e.exports=r,e.exports.default=r},1696:(e,t,n)=>{"use strict";const r=n(3390);let i=!1;t.show=(e=process.stderr)=>{e.isTTY&&(i=!1,e.write("[?25h"))},t.hide=(e=process.stderr)=>{e.isTTY&&(r(),i=!0,e.write("[?25l"))},t.toggle=(e,n)=>{void 0!==e&&(i=e),i?t.show(n):t.hide(n)}},5301:(e,t,n)=>{"use strict";const r=n(1566),i=n(5043);function o(e,t,n){if(" "===e.charAt(t))return t;for(let r=1;r<=3;r++)if(n){if(" "===e.charAt(t+r))return t+r}else if(" "===e.charAt(t-r))return t-r;return t}e.exports=(e,t,n)=>{n={position:"end",preferTruncationOnSpace:!1,...n};const{position:u,space:a,preferTruncationOnSpace:l}=n;let s="…",c=1;if("string"!=typeof e)throw new TypeError("Expected `input` to be a string, got "+typeof e);if("number"!=typeof t)throw new TypeError("Expected `columns` to be a number, got "+typeof t);if(t<1)return"";if(1===t)return s;const f=i(e);if(f<=t)return e;if("start"===u){if(l){const n=o(e,f-t+1,!0);return s+r(e,n,f).trim()}return!0===a&&(s+=" ",c=2),s+r(e,f-t+c,f)}if("middle"===u){!0===a&&(s=" "+s+" ",c=3);const n=Math.floor(t/2);if(l){const i=o(e,n),u=o(e,f-(t-n)+1,!0);return r(e,0,i)+s+r(e,u,f).trim()}return r(e,0,n)+s+r(e,f-(t-n)+c,f)}if("end"===u){if(l){const n=o(e,t-1);return r(e,0,n)+s}return!0===a&&(s=" "+s,c=2),r(e,0,t-c)+s}throw new Error("Expected `options.position` to be either `start`, `middle` or `end`, got "+u)}},9908:(e,t,n)=>{"use strict";const r=n(3287);e.exports=(e,t,n)=>{if("string"!=typeof e)throw new TypeError("Source code is missing.");if(!t||t<1)throw new TypeError("Line number must start from `1`.");if(!(t>(e=r(e).split(/\r?\n/)).length))return((e,t)=>{const n=[],r=e+t;for(let i=e-t;i<=r;i++)n.push(i);return n})(t,(n={around:3,...n}).around).filter(t=>void 0!==e[t-1]).map(t=>({line:t,value:e[t-1]}))}},5311:(e,t,n)=>{const r=n(3300),i={};for(const e of Object.keys(r))i[r[e]]=e;const o={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};e.exports=o;for(const e of Object.keys(o)){if(!("channels"in o[e]))throw new Error("missing channels property: "+e);if(!("labels"in o[e]))throw new Error("missing channel labels property: "+e);if(o[e].labels.length!==o[e].channels)throw new Error("channel and label counts mismatch: "+e);const{channels:t,labels:n}=o[e];delete o[e].channels,delete o[e].labels,Object.defineProperty(o[e],"channels",{value:t}),Object.defineProperty(o[e],"labels",{value:n})}o.rgb.hsl=function(e){const t=e[0]/255,n=e[1]/255,r=e[2]/255,i=Math.min(t,n,r),o=Math.max(t,n,r),u=o-i;let a,l;o===i?a=0:t===o?a=(n-r)/u:n===o?a=2+(r-t)/u:r===o&&(a=4+(t-n)/u),a=Math.min(60*a,360),a<0&&(a+=360);const s=(i+o)/2;return l=o===i?0:s<=.5?u/(o+i):u/(2-o-i),[a,100*l,100*s]},o.rgb.hsv=function(e){let t,n,r,i,o;const u=e[0]/255,a=e[1]/255,l=e[2]/255,s=Math.max(u,a,l),c=s-Math.min(u,a,l),f=function(e){return(s-e)/6/c+.5};return 0===c?(i=0,o=0):(o=c/s,t=f(u),n=f(a),r=f(l),u===s?i=r-n:a===s?i=1/3+t-r:l===s&&(i=2/3+n-t),i<0?i+=1:i>1&&(i-=1)),[360*i,100*o,100*s]},o.rgb.hwb=function(e){const t=e[0],n=e[1];let r=e[2];const i=o.rgb.hsl(e)[0],u=1/255*Math.min(t,Math.min(n,r));return r=1-1/255*Math.max(t,Math.max(n,r)),[i,100*u,100*r]},o.rgb.cmyk=function(e){const t=e[0]/255,n=e[1]/255,r=e[2]/255,i=Math.min(1-t,1-n,1-r);return[100*((1-t-i)/(1-i)||0),100*((1-n-i)/(1-i)||0),100*((1-r-i)/(1-i)||0),100*i]},o.rgb.keyword=function(e){const t=i[e];if(t)return t;let n,o=1/0;for(const t of Object.keys(r)){const i=r[t],l=(a=i,((u=e)[0]-a[0])**2+(u[1]-a[1])**2+(u[2]-a[2])**2);l.04045?((t+.055)/1.055)**2.4:t/12.92,n=n>.04045?((n+.055)/1.055)**2.4:n/12.92,r=r>.04045?((r+.055)/1.055)**2.4:r/12.92;return[100*(.4124*t+.3576*n+.1805*r),100*(.2126*t+.7152*n+.0722*r),100*(.0193*t+.1192*n+.9505*r)]},o.rgb.lab=function(e){const t=o.rgb.xyz(e);let n=t[0],r=t[1],i=t[2];n/=95.047,r/=100,i/=108.883,n=n>.008856?n**(1/3):7.787*n+16/116,r=r>.008856?r**(1/3):7.787*r+16/116,i=i>.008856?i**(1/3):7.787*i+16/116;return[116*r-16,500*(n-r),200*(r-i)]},o.hsl.rgb=function(e){const t=e[0]/360,n=e[1]/100,r=e[2]/100;let i,o,u;if(0===n)return u=255*r,[u,u,u];i=r<.5?r*(1+n):r+n-r*n;const a=2*r-i,l=[0,0,0];for(let e=0;e<3;e++)o=t+1/3*-(e-1),o<0&&o++,o>1&&o--,u=6*o<1?a+6*(i-a)*o:2*o<1?i:3*o<2?a+(i-a)*(2/3-o)*6:a,l[e]=255*u;return l},o.hsl.hsv=function(e){const t=e[0];let n=e[1]/100,r=e[2]/100,i=n;const o=Math.max(r,.01);r*=2,n*=r<=1?r:2-r,i*=o<=1?o:2-o;return[t,100*(0===r?2*i/(o+i):2*n/(r+n)),100*((r+n)/2)]},o.hsv.rgb=function(e){const t=e[0]/60,n=e[1]/100;let r=e[2]/100;const i=Math.floor(t)%6,o=t-Math.floor(t),u=255*r*(1-n),a=255*r*(1-n*o),l=255*r*(1-n*(1-o));switch(r*=255,i){case 0:return[r,l,u];case 1:return[a,r,u];case 2:return[u,r,l];case 3:return[u,a,r];case 4:return[l,u,r];case 5:return[r,u,a]}},o.hsv.hsl=function(e){const t=e[0],n=e[1]/100,r=e[2]/100,i=Math.max(r,.01);let o,u;u=(2-n)*r;const a=(2-n)*i;return o=n*i,o/=a<=1?a:2-a,o=o||0,u/=2,[t,100*o,100*u]},o.hwb.rgb=function(e){const t=e[0]/360;let n=e[1]/100,r=e[2]/100;const i=n+r;let o;i>1&&(n/=i,r/=i);const u=Math.floor(6*t),a=1-r;o=6*t-u,0!=(1&u)&&(o=1-o);const l=n+o*(a-n);let s,c,f;switch(u){default:case 6:case 0:s=a,c=l,f=n;break;case 1:s=l,c=a,f=n;break;case 2:s=n,c=a,f=l;break;case 3:s=n,c=l,f=a;break;case 4:s=l,c=n,f=a;break;case 5:s=a,c=n,f=l}return[255*s,255*c,255*f]},o.cmyk.rgb=function(e){const t=e[0]/100,n=e[1]/100,r=e[2]/100,i=e[3]/100;return[255*(1-Math.min(1,t*(1-i)+i)),255*(1-Math.min(1,n*(1-i)+i)),255*(1-Math.min(1,r*(1-i)+i))]},o.xyz.rgb=function(e){const t=e[0]/100,n=e[1]/100,r=e[2]/100;let i,o,u;return i=3.2406*t+-1.5372*n+-.4986*r,o=-.9689*t+1.8758*n+.0415*r,u=.0557*t+-.204*n+1.057*r,i=i>.0031308?1.055*i**(1/2.4)-.055:12.92*i,o=o>.0031308?1.055*o**(1/2.4)-.055:12.92*o,u=u>.0031308?1.055*u**(1/2.4)-.055:12.92*u,i=Math.min(Math.max(0,i),1),o=Math.min(Math.max(0,o),1),u=Math.min(Math.max(0,u),1),[255*i,255*o,255*u]},o.xyz.lab=function(e){let t=e[0],n=e[1],r=e[2];t/=95.047,n/=100,r/=108.883,t=t>.008856?t**(1/3):7.787*t+16/116,n=n>.008856?n**(1/3):7.787*n+16/116,r=r>.008856?r**(1/3):7.787*r+16/116;return[116*n-16,500*(t-n),200*(n-r)]},o.lab.xyz=function(e){let t,n,r;n=(e[0]+16)/116,t=e[1]/500+n,r=n-e[2]/200;const i=n**3,o=t**3,u=r**3;return n=i>.008856?i:(n-16/116)/7.787,t=o>.008856?o:(t-16/116)/7.787,r=u>.008856?u:(r-16/116)/7.787,t*=95.047,n*=100,r*=108.883,[t,n,r]},o.lab.lch=function(e){const t=e[0],n=e[1],r=e[2];let i;i=360*Math.atan2(r,n)/2/Math.PI,i<0&&(i+=360);return[t,Math.sqrt(n*n+r*r),i]},o.lch.lab=function(e){const t=e[0],n=e[1],r=e[2]/360*2*Math.PI;return[t,n*Math.cos(r),n*Math.sin(r)]},o.rgb.ansi16=function(e,t=null){const[n,r,i]=e;let u=null===t?o.rgb.hsv(e)[2]:t;if(u=Math.round(u/50),0===u)return 30;let a=30+(Math.round(i/255)<<2|Math.round(r/255)<<1|Math.round(n/255));return 2===u&&(a+=60),a},o.hsv.ansi16=function(e){return o.rgb.ansi16(o.hsv.rgb(e),e[2])},o.rgb.ansi256=function(e){const t=e[0],n=e[1],r=e[2];if(t===n&&n===r)return t<8?16:t>248?231:Math.round((t-8)/247*24)+232;return 16+36*Math.round(t/255*5)+6*Math.round(n/255*5)+Math.round(r/255*5)},o.ansi16.rgb=function(e){let t=e%10;if(0===t||7===t)return e>50&&(t+=3.5),t=t/10.5*255,[t,t,t];const n=.5*(1+~~(e>50));return[(1&t)*n*255,(t>>1&1)*n*255,(t>>2&1)*n*255]},o.ansi256.rgb=function(e){if(e>=232){const t=10*(e-232)+8;return[t,t,t]}let t;e-=16;return[Math.floor(e/36)/5*255,Math.floor((t=e%36)/6)/5*255,t%6/5*255]},o.rgb.hex=function(e){const t=(((255&Math.round(e[0]))<<16)+((255&Math.round(e[1]))<<8)+(255&Math.round(e[2]))).toString(16).toUpperCase();return"000000".substring(t.length)+t},o.hex.rgb=function(e){const t=e.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!t)return[0,0,0];let n=t[0];3===t[0].length&&(n=n.split("").map(e=>e+e).join(""));const r=parseInt(n,16);return[r>>16&255,r>>8&255,255&r]},o.rgb.hcg=function(e){const t=e[0]/255,n=e[1]/255,r=e[2]/255,i=Math.max(Math.max(t,n),r),o=Math.min(Math.min(t,n),r),u=i-o;let a,l;return a=u<1?o/(1-u):0,l=u<=0?0:i===t?(n-r)/u%6:i===n?2+(r-t)/u:4+(t-n)/u,l/=6,l%=1,[360*l,100*u,100*a]},o.hsl.hcg=function(e){const t=e[1]/100,n=e[2]/100,r=n<.5?2*t*n:2*t*(1-n);let i=0;return r<1&&(i=(n-.5*r)/(1-r)),[e[0],100*r,100*i]},o.hsv.hcg=function(e){const t=e[1]/100,n=e[2]/100,r=t*n;let i=0;return r<1&&(i=(n-r)/(1-r)),[e[0],100*r,100*i]},o.hcg.rgb=function(e){const t=e[0]/360,n=e[1]/100,r=e[2]/100;if(0===n)return[255*r,255*r,255*r];const i=[0,0,0],o=t%1*6,u=o%1,a=1-u;let l=0;switch(Math.floor(o)){case 0:i[0]=1,i[1]=u,i[2]=0;break;case 1:i[0]=a,i[1]=1,i[2]=0;break;case 2:i[0]=0,i[1]=1,i[2]=u;break;case 3:i[0]=0,i[1]=a,i[2]=1;break;case 4:i[0]=u,i[1]=0,i[2]=1;break;default:i[0]=1,i[1]=0,i[2]=a}return l=(1-n)*r,[255*(n*i[0]+l),255*(n*i[1]+l),255*(n*i[2]+l)]},o.hcg.hsv=function(e){const t=e[1]/100,n=t+e[2]/100*(1-t);let r=0;return n>0&&(r=t/n),[e[0],100*r,100*n]},o.hcg.hsl=function(e){const t=e[1]/100,n=e[2]/100*(1-t)+.5*t;let r=0;return n>0&&n<.5?r=t/(2*n):n>=.5&&n<1&&(r=t/(2*(1-n))),[e[0],100*r,100*n]},o.hcg.hwb=function(e){const t=e[1]/100,n=t+e[2]/100*(1-t);return[e[0],100*(n-t),100*(1-n)]},o.hwb.hcg=function(e){const t=e[1]/100,n=1-e[2]/100,r=n-t;let i=0;return r<1&&(i=(n-r)/(1-r)),[e[0],100*r,100*i]},o.apple.rgb=function(e){return[e[0]/65535*255,e[1]/65535*255,e[2]/65535*255]},o.rgb.apple=function(e){return[e[0]/255*65535,e[1]/255*65535,e[2]/255*65535]},o.gray.rgb=function(e){return[e[0]/100*255,e[0]/100*255,e[0]/100*255]},o.gray.hsl=function(e){return[0,0,e[0]]},o.gray.hsv=o.gray.hsl,o.gray.hwb=function(e){return[0,100,e[0]]},o.gray.cmyk=function(e){return[0,0,0,e[0]]},o.gray.lab=function(e){return[e[0],0,0]},o.gray.hex=function(e){const t=255&Math.round(e[0]/100*255),n=((t<<16)+(t<<8)+t).toString(16).toUpperCase();return"000000".substring(n.length)+n},o.rgb.gray=function(e){return[(e[0]+e[1]+e[2])/3/255*100]}},2744:(e,t,n)=>{const r=n(5311),i=n(8577),o={};Object.keys(r).forEach(e=>{o[e]={},Object.defineProperty(o[e],"channels",{value:r[e].channels}),Object.defineProperty(o[e],"labels",{value:r[e].labels});const t=i(e);Object.keys(t).forEach(n=>{const r=t[n];o[e][n]=function(e){const t=function(...t){const n=t[0];if(null==n)return n;n.length>1&&(t=n);const r=e(t);if("object"==typeof r)for(let e=r.length,t=0;t1&&(t=n),e(t))};return"conversion"in e&&(t.conversion=e.conversion),t}(r)})}),e.exports=o},8577:(e,t,n)=>{const r=n(5311);function i(e){const t=function(){const e={},t=Object.keys(r);for(let n=t.length,r=0;r{"use strict";e.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}},3287:e=>{"use strict";e.exports=(e,t)=>e.replace(/^\t+/gm,e=>" ".repeat(e.length*(t||2)))},1013:e=>{"use strict";e.exports=function(){return/\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F|\uD83D\uDC68(?:\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68\uD83C\uDFFB|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|[\u2695\u2696\u2708]\uFE0F|\uD83D[\uDC66\uDC67]|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708])\uFE0F|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C[\uDFFB-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)\uD83C\uDFFB|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB\uDFFC])|\uD83D\uDC69(?:\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB-\uDFFD])|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|(?:(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)\uFE0F|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\u200D[\u2640\u2642])|\uD83C\uDFF4\u200D\u2620)\uFE0F|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDF6\uD83C\uDDE6|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDB5\uDDB6\uDDBB\uDDD2-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5\uDEEB\uDEEC\uDEF4-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g}},8759:e=>{"use strict";const t=/[|\\{}()[\]^$+*?.-]/g;e.exports=e=>{if("string"!=typeof e)throw new TypeError("Expected a string");return e.replace(t,"\\$&")}},2918:e=>{"use strict";e.exports=(e,t=process.argv)=>{const n=e.startsWith("-")?"":1===e.length?"-":"--",r=t.indexOf(n+e),i=t.indexOf("--");return-1!==r&&(-1===i||r{"use strict";e.exports=(e,t=1,n)=>{if(n={indent:" ",includeEmptyLines:!1,...n},"string"!=typeof e)throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof e}\``);if("number"!=typeof t)throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof t}\``);if("string"!=typeof n.indent)throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof n.indent}\``);if(0===t)return e;const r=n.includeEmptyLines?/^/gm:/^(?!\s*$)/gm;return e.replace(r,n.indent.repeat(t))}},2738:(e,t,n)=>{"use strict";e.exports=n(5864).isCI},7347:e=>{"use strict";const t=e=>!Number.isNaN(e)&&(e>=4352&&(e<=4447||9001===e||9002===e||11904<=e&&e<=12871&&12351!==e||12880<=e&&e<=19903||19968<=e&&e<=42182||43360<=e&&e<=43388||44032<=e&&e<=55203||63744<=e&&e<=64255||65040<=e&&e<=65049||65072<=e&&e<=65131||65281<=e&&e<=65376||65504<=e&&e<=65510||110592<=e&&e<=110593||127488<=e&&e<=127569||131072<=e&&e<=262141));e.exports=t,e.exports.default=t},464:function(e,t,n){var r; /** * @license * Lodash * Copyright OpenJS Foundation and other contributors * Released under MIT license * Based on Underscore.js 1.8.3 * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors */e=n.nmd(e),function(){var i="Expected a function",o="__lodash_placeholder__",u=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]],a="[object Arguments]",l="[object Array]",s="[object Boolean]",c="[object Date]",f="[object Error]",d="[object Function]",p="[object GeneratorFunction]",h="[object Map]",v="[object Number]",m="[object Object]",g="[object RegExp]",y="[object Set]",_="[object String]",b="[object Symbol]",w="[object WeakMap]",E="[object ArrayBuffer]",D="[object DataView]",S="[object Float32Array]",C="[object Float64Array]",k="[object Int8Array]",T="[object Int16Array]",x="[object Int32Array]",A="[object Uint8Array]",O="[object Uint16Array]",P="[object Uint32Array]",I=/\b__p \+= '';/g,N=/\b(__p \+=) '' \+/g,M=/(__e\(.*?\)|\b__t\)) \+\n'';/g,R=/&(?:amp|lt|gt|quot|#39);/g,F=/[&<>"']/g,L=RegExp(R.source),B=RegExp(F.source),j=/<%-([\s\S]+?)%>/g,U=/<%([\s\S]+?)%>/g,z=/<%=([\s\S]+?)%>/g,W=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,H=/^\w*$/,V=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,q=/[\\^$.*+?()[\]{}|]/g,G=RegExp(q.source),$=/^\s+|\s+$/g,Y=/^\s+/,K=/\s+$/,X=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,Q=/\{\n\/\* \[wrapped with (.+)\] \*/,J=/,? & /,Z=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,ee=/\\(\\)?/g,te=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,ne=/\w*$/,re=/^[-+]0x[0-9a-f]+$/i,ie=/^0b[01]+$/i,oe=/^\[object .+?Constructor\]$/,ue=/^0o[0-7]+$/i,ae=/^(?:0|[1-9]\d*)$/,le=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,se=/($^)/,ce=/['\n\r\u2028\u2029\\]/g,fe="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",de="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",pe="[\\ud800-\\udfff]",he="["+de+"]",ve="["+fe+"]",me="\\d+",ge="[\\u2700-\\u27bf]",ye="[a-z\\xdf-\\xf6\\xf8-\\xff]",_e="[^\\ud800-\\udfff"+de+me+"\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde]",be="\\ud83c[\\udffb-\\udfff]",we="[^\\ud800-\\udfff]",Ee="(?:\\ud83c[\\udde6-\\uddff]){2}",De="[\\ud800-\\udbff][\\udc00-\\udfff]",Se="[A-Z\\xc0-\\xd6\\xd8-\\xde]",Ce="(?:"+ye+"|"+_e+")",ke="(?:"+Se+"|"+_e+")",Te="(?:"+ve+"|"+be+")"+"?",xe="[\\ufe0e\\ufe0f]?"+Te+("(?:\\u200d(?:"+[we,Ee,De].join("|")+")[\\ufe0e\\ufe0f]?"+Te+")*"),Ae="(?:"+[ge,Ee,De].join("|")+")"+xe,Oe="(?:"+[we+ve+"?",ve,Ee,De,pe].join("|")+")",Pe=RegExp("['’]","g"),Ie=RegExp(ve,"g"),Ne=RegExp(be+"(?="+be+")|"+Oe+xe,"g"),Me=RegExp([Se+"?"+ye+"+(?:['’](?:d|ll|m|re|s|t|ve))?(?="+[he,Se,"$"].join("|")+")",ke+"+(?:['’](?:D|LL|M|RE|S|T|VE))?(?="+[he,Se+Ce,"$"].join("|")+")",Se+"?"+Ce+"+(?:['’](?:d|ll|m|re|s|t|ve))?",Se+"+(?:['’](?:D|LL|M|RE|S|T|VE))?","\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",me,Ae].join("|"),"g"),Re=RegExp("[\\u200d\\ud800-\\udfff"+fe+"\\ufe0e\\ufe0f]"),Fe=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Le=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],Be=-1,je={};je[S]=je[C]=je[k]=je[T]=je[x]=je[A]=je["[object Uint8ClampedArray]"]=je[O]=je[P]=!0,je[a]=je[l]=je[E]=je[s]=je[D]=je[c]=je[f]=je[d]=je[h]=je[v]=je[m]=je[g]=je[y]=je[_]=je[w]=!1;var Ue={};Ue[a]=Ue[l]=Ue[E]=Ue[D]=Ue[s]=Ue[c]=Ue[S]=Ue[C]=Ue[k]=Ue[T]=Ue[x]=Ue[h]=Ue[v]=Ue[m]=Ue[g]=Ue[y]=Ue[_]=Ue[b]=Ue[A]=Ue["[object Uint8ClampedArray]"]=Ue[O]=Ue[P]=!0,Ue[f]=Ue[d]=Ue[w]=!1;var ze={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},We=parseFloat,He=parseInt,Ve="object"==typeof global&&global&&global.Object===Object&&global,qe="object"==typeof self&&self&&self.Object===Object&&self,Ge=Ve||qe||Function("return this")(),$e=t&&!t.nodeType&&t,Ye=$e&&e&&!e.nodeType&&e,Ke=Ye&&Ye.exports===$e,Xe=Ke&&Ve.process,Qe=function(){try{var e=Ye&&Ye.require&&Ye.require("util").types;return e||Xe&&Xe.binding&&Xe.binding("util")}catch(e){}}(),Je=Qe&&Qe.isArrayBuffer,Ze=Qe&&Qe.isDate,et=Qe&&Qe.isMap,tt=Qe&&Qe.isRegExp,nt=Qe&&Qe.isSet,rt=Qe&&Qe.isTypedArray;function it(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}function ot(e,t,n,r){for(var i=-1,o=null==e?0:e.length;++i-1}function ft(e,t,n){for(var r=-1,i=null==e?0:e.length;++r-1;);return n}function Nt(e,t){for(var n=e.length;n--&&bt(t,e[n],0)>-1;);return n}function Mt(e,t){for(var n=e.length,r=0;n--;)e[n]===t&&++r;return r}var Rt=Ct({À:"A",Á:"A",Â:"A",Ã:"A",Ä:"A",Å:"A",à:"a",á:"a",â:"a",ã:"a",ä:"a",å:"a",Ç:"C",ç:"c",Ð:"D",ð:"d",È:"E",É:"E",Ê:"E",Ë:"E",è:"e",é:"e",ê:"e",ë:"e",Ì:"I",Í:"I",Î:"I",Ï:"I",ì:"i",í:"i",î:"i",ï:"i",Ñ:"N",ñ:"n",Ò:"O",Ó:"O",Ô:"O",Õ:"O",Ö:"O",Ø:"O",ò:"o",ó:"o",ô:"o",õ:"o",ö:"o",ø:"o",Ù:"U",Ú:"U",Û:"U",Ü:"U",ù:"u",ú:"u",û:"u",ü:"u",Ý:"Y",ý:"y",ÿ:"y",Æ:"Ae",æ:"ae",Þ:"Th",þ:"th",ß:"ss",Ā:"A",Ă:"A",Ą:"A",ā:"a",ă:"a",ą:"a",Ć:"C",Ĉ:"C",Ċ:"C",Č:"C",ć:"c",ĉ:"c",ċ:"c",č:"c",Ď:"D",Đ:"D",ď:"d",đ:"d",Ē:"E",Ĕ:"E",Ė:"E",Ę:"E",Ě:"E",ē:"e",ĕ:"e",ė:"e",ę:"e",ě:"e",Ĝ:"G",Ğ:"G",Ġ:"G",Ģ:"G",ĝ:"g",ğ:"g",ġ:"g",ģ:"g",Ĥ:"H",Ħ:"H",ĥ:"h",ħ:"h",Ĩ:"I",Ī:"I",Ĭ:"I",Į:"I",İ:"I",ĩ:"i",ī:"i",ĭ:"i",į:"i",ı:"i",Ĵ:"J",ĵ:"j",Ķ:"K",ķ:"k",ĸ:"k",Ĺ:"L",Ļ:"L",Ľ:"L",Ŀ:"L",Ł:"L",ĺ:"l",ļ:"l",ľ:"l",ŀ:"l",ł:"l",Ń:"N",Ņ:"N",Ň:"N",Ŋ:"N",ń:"n",ņ:"n",ň:"n",ŋ:"n",Ō:"O",Ŏ:"O",Ő:"O",ō:"o",ŏ:"o",ő:"o",Ŕ:"R",Ŗ:"R",Ř:"R",ŕ:"r",ŗ:"r",ř:"r",Ś:"S",Ŝ:"S",Ş:"S",Š:"S",ś:"s",ŝ:"s",ş:"s",š:"s",Ţ:"T",Ť:"T",Ŧ:"T",ţ:"t",ť:"t",ŧ:"t",Ũ:"U",Ū:"U",Ŭ:"U",Ů:"U",Ű:"U",Ų:"U",ũ:"u",ū:"u",ŭ:"u",ů:"u",ű:"u",ų:"u",Ŵ:"W",ŵ:"w",Ŷ:"Y",ŷ:"y",Ÿ:"Y",Ź:"Z",Ż:"Z",Ž:"Z",ź:"z",ż:"z",ž:"z",IJ:"IJ",ij:"ij",Œ:"Oe",œ:"oe",ʼn:"'n",ſ:"s"}),Ft=Ct({"&":"&","<":"<",">":">",'"':""","'":"'"});function Lt(e){return"\\"+ze[e]}function Bt(e){return Re.test(e)}function jt(e){var t=-1,n=Array(e.size);return e.forEach((function(e,r){n[++t]=[r,e]})),n}function Ut(e,t){return function(n){return e(t(n))}}function zt(e,t){for(var n=-1,r=e.length,i=0,u=[];++n",""":'"',"'":"'"});var $t=function e(t){var n,r=(t=null==t?Ge:$t.defaults(Ge.Object(),t,$t.pick(Ge,Le))).Array,fe=t.Date,de=t.Error,pe=t.Function,he=t.Math,ve=t.Object,me=t.RegExp,ge=t.String,ye=t.TypeError,_e=r.prototype,be=pe.prototype,we=ve.prototype,Ee=t["__core-js_shared__"],De=be.toString,Se=we.hasOwnProperty,Ce=0,ke=(n=/[^.]+$/.exec(Ee&&Ee.keys&&Ee.keys.IE_PROTO||""))?"Symbol(src)_1."+n:"",Te=we.toString,xe=De.call(ve),Ae=Ge._,Oe=me("^"+De.call(Se).replace(q,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),Ne=Ke?t.Buffer:void 0,Re=t.Symbol,ze=t.Uint8Array,Ve=Ne?Ne.allocUnsafe:void 0,qe=Ut(ve.getPrototypeOf,ve),$e=ve.create,Ye=we.propertyIsEnumerable,Xe=_e.splice,Qe=Re?Re.isConcatSpreadable:void 0,gt=Re?Re.iterator:void 0,Ct=Re?Re.toStringTag:void 0,Yt=function(){try{var e=Zi(ve,"defineProperty");return e({},"",{}),e}catch(e){}}(),Kt=t.clearTimeout!==Ge.clearTimeout&&t.clearTimeout,Xt=fe&&fe.now!==Ge.Date.now&&fe.now,Qt=t.setTimeout!==Ge.setTimeout&&t.setTimeout,Jt=he.ceil,Zt=he.floor,en=ve.getOwnPropertySymbols,tn=Ne?Ne.isBuffer:void 0,nn=t.isFinite,rn=_e.join,on=Ut(ve.keys,ve),un=he.max,an=he.min,ln=fe.now,sn=t.parseInt,cn=he.random,fn=_e.reverse,dn=Zi(t,"DataView"),pn=Zi(t,"Map"),hn=Zi(t,"Promise"),vn=Zi(t,"Set"),mn=Zi(t,"WeakMap"),gn=Zi(ve,"create"),yn=mn&&new mn,_n={},bn=To(dn),wn=To(pn),En=To(hn),Dn=To(vn),Sn=To(mn),Cn=Re?Re.prototype:void 0,kn=Cn?Cn.valueOf:void 0,Tn=Cn?Cn.toString:void 0;function xn(e){if(Vu(e)&&!Nu(e)&&!(e instanceof In)){if(e instanceof Pn)return e;if(Se.call(e,"__wrapped__"))return xo(e)}return new Pn(e)}var An=function(){function e(){}return function(t){if(!Hu(t))return{};if($e)return $e(t);e.prototype=t;var n=new e;return e.prototype=void 0,n}}();function On(){}function Pn(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=void 0}function In(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}function Nn(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t=t?e:t)),e}function Xn(e,t,n,r,i,o){var u,l=1&t,f=2&t,w=4&t;if(n&&(u=i?n(e,r,i,o):n(e)),void 0!==u)return u;if(!Hu(e))return e;var I=Nu(e);if(I){if(u=function(e){var t=e.length,n=new e.constructor(t);t&&"string"==typeof e[0]&&Se.call(e,"index")&&(n.index=e.index,n.input=e.input);return n}(e),!l)return gi(e,u)}else{var N=no(e),M=N==d||N==p;if(Lu(e))return fi(e,l);if(N==m||N==a||M&&!i){if(u=f||M?{}:io(e),!l)return f?function(e,t){return yi(e,to(e),t)}(e,function(e,t){return e&&yi(t,wa(t),e)}(u,e)):function(e,t){return yi(e,eo(e),t)}(e,Gn(u,e))}else{if(!Ue[N])return i?e:{};u=function(e,t,n){var r=e.constructor;switch(t){case E:return di(e);case s:case c:return new r(+e);case D:return function(e,t){var n=t?di(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.byteLength)}(e,n);case S:case C:case k:case T:case x:case A:case"[object Uint8ClampedArray]":case O:case P:return pi(e,n);case h:return new r;case v:case _:return new r(e);case g:return function(e){var t=new e.constructor(e.source,ne.exec(e));return t.lastIndex=e.lastIndex,t}(e);case y:return new r;case b:return i=e,kn?ve(kn.call(i)):{}}var i}(e,N,l)}}o||(o=new Ln);var R=o.get(e);if(R)return R;o.set(e,u),Ku(e)?e.forEach((function(r){u.add(Xn(r,t,n,r,e,o))})):qu(e)&&e.forEach((function(r,i){u.set(i,Xn(r,t,n,i,e,o))}));var F=I?void 0:(w?f?Gi:qi:f?wa:ba)(e);return ut(F||e,(function(r,i){F&&(r=e[i=r]),Hn(u,i,Xn(r,t,n,i,e,o))})),u}function Qn(e,t,n){var r=n.length;if(null==e)return!r;for(e=ve(e);r--;){var i=n[r],o=t[i],u=e[i];if(void 0===u&&!(i in e)||!o(u))return!1}return!0}function Jn(e,t,n){if("function"!=typeof e)throw new ye(i);return bo((function(){e.apply(void 0,n)}),t)}function Zn(e,t,n,r){var i=-1,o=ct,u=!0,a=e.length,l=[],s=t.length;if(!a)return l;n&&(t=dt(t,At(n))),r?(o=ft,u=!1):t.length>=200&&(o=Pt,u=!1,t=new Fn(t));e:for(;++i-1},Mn.prototype.set=function(e,t){var n=this.__data__,r=Vn(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this},Rn.prototype.clear=function(){this.size=0,this.__data__={hash:new Nn,map:new(pn||Mn),string:new Nn}},Rn.prototype.delete=function(e){var t=Qi(this,e).delete(e);return this.size-=t?1:0,t},Rn.prototype.get=function(e){return Qi(this,e).get(e)},Rn.prototype.has=function(e){return Qi(this,e).has(e)},Rn.prototype.set=function(e,t){var n=Qi(this,e),r=n.size;return n.set(e,t),this.size+=n.size==r?0:1,this},Fn.prototype.add=Fn.prototype.push=function(e){return this.__data__.set(e,"__lodash_hash_undefined__"),this},Fn.prototype.has=function(e){return this.__data__.has(e)},Ln.prototype.clear=function(){this.__data__=new Mn,this.size=0},Ln.prototype.delete=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n},Ln.prototype.get=function(e){return this.__data__.get(e)},Ln.prototype.has=function(e){return this.__data__.has(e)},Ln.prototype.set=function(e,t){var n=this.__data__;if(n instanceof Mn){var r=n.__data__;if(!pn||r.length<199)return r.push([e,t]),this.size=++n.size,this;n=this.__data__=new Rn(r)}return n.set(e,t),this.size=n.size,this};var er=wi(lr),tr=wi(sr,!0);function nr(e,t){var n=!0;return er(e,(function(e,r,i){return n=!!t(e,r,i)})),n}function rr(e,t,n){for(var r=-1,i=e.length;++r0&&n(a)?t>1?or(a,t-1,n,r,i):pt(i,a):r||(i[i.length]=a)}return i}var ur=Ei(),ar=Ei(!0);function lr(e,t){return e&&ur(e,t,ba)}function sr(e,t){return e&&ar(e,t,ba)}function cr(e,t){return st(t,(function(t){return Uu(e[t])}))}function fr(e,t){for(var n=0,r=(t=ai(t,e)).length;null!=e&&nt}function vr(e,t){return null!=e&&Se.call(e,t)}function mr(e,t){return null!=e&&t in ve(e)}function gr(e,t,n){for(var i=n?ft:ct,o=e[0].length,u=e.length,a=u,l=r(u),s=1/0,c=[];a--;){var f=e[a];a&&t&&(f=dt(f,At(t))),s=an(f.length,s),l[a]=!n&&(t||o>=120&&f.length>=120)?new Fn(a&&f):void 0}f=e[0];var d=-1,p=l[0];e:for(;++d=a)return l;var s=n[r];return l*("desc"==s?-1:1)}}return e.index-t.index}(e,t,n)}))}function Nr(e,t,n){for(var r=-1,i=t.length,o={};++r-1;)a!==e&&Xe.call(a,l,1),Xe.call(e,l,1);return e}function Rr(e,t){for(var n=e?t.length:0,r=n-1;n--;){var i=t[n];if(n==r||i!==o){var o=i;uo(i)?Xe.call(e,i,1):Zr(e,i)}}return e}function Fr(e,t){return e+Zt(cn()*(t-e+1))}function Lr(e,t){var n="";if(!e||t<1||t>9007199254740991)return n;do{t%2&&(n+=e),(t=Zt(t/2))&&(e+=e)}while(t);return n}function Br(e,t){return wo(vo(e,t,Ga),e+"")}function jr(e){return jn(Aa(e))}function Ur(e,t){var n=Aa(e);return So(n,Kn(t,0,n.length))}function zr(e,t,n,r){if(!Hu(e))return e;for(var i=-1,o=(t=ai(t,e)).length,u=o-1,a=e;null!=a&&++io?0:o+t),(n=n>o?o:n)<0&&(n+=o),o=t>n?0:n-t>>>0,t>>>=0;for(var u=r(o);++i>>1,u=e[o];null!==u&&!Qu(u)&&(n?u<=t:u=200){var s=t?null:Li(e);if(s)return Wt(s);u=!1,i=Pt,l=new Fn}else l=t?[]:a;e:for(;++r=r?e:qr(e,t,n)}var ci=Kt||function(e){return Ge.clearTimeout(e)};function fi(e,t){if(t)return e.slice();var n=e.length,r=Ve?Ve(n):new e.constructor(n);return e.copy(r),r}function di(e){var t=new e.constructor(e.byteLength);return new ze(t).set(new ze(e)),t}function pi(e,t){var n=t?di(e.buffer):e.buffer;return new e.constructor(n,e.byteOffset,e.length)}function hi(e,t){if(e!==t){var n=void 0!==e,r=null===e,i=e==e,o=Qu(e),u=void 0!==t,a=null===t,l=t==t,s=Qu(t);if(!a&&!s&&!o&&e>t||o&&u&&l&&!a&&!s||r&&u&&l||!n&&l||!i)return 1;if(!r&&!o&&!s&&e1?n[i-1]:void 0,u=i>2?n[2]:void 0;for(o=e.length>3&&"function"==typeof o?(i--,o):void 0,u&&ao(n[0],n[1],u)&&(o=i<3?void 0:o,i=1),t=ve(t);++r-1?i[o?t[u]:u]:void 0}}function Ti(e){return Vi((function(t){var n=t.length,r=n,o=Pn.prototype.thru;for(e&&t.reverse();r--;){var u=t[r];if("function"!=typeof u)throw new ye(i);if(o&&!a&&"wrapper"==Yi(u))var a=new Pn([],!0)}for(r=a?r:n;++r1&&_.reverse(),f&&sa))return!1;var s=o.get(e),c=o.get(t);if(s&&c)return s==t&&c==e;var f=-1,d=!0,p=2&n?new Fn:void 0;for(o.set(e,t),o.set(t,e);++f-1&&e%1==0&&e1?"& ":"")+t[r],t=t.join(n>2?", ":" "),e.replace(X,"{\n/* [wrapped with "+t+"] */\n")}(r,function(e,t){return ut(u,(function(n){var r="_."+n[0];t&n[1]&&!ct(e,r)&&e.push(r)})),e.sort()}(function(e){var t=e.match(Q);return t?t[1].split(J):[]}(r),n)))}function Do(e){var t=0,n=0;return function(){var r=ln(),i=16-(r-n);if(n=r,i>0){if(++t>=800)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}function So(e,t){var n=-1,r=e.length,i=r-1;for(t=void 0===t?r:t;++n1?e[t-1]:void 0;return n="function"==typeof n?(e.pop(),n):void 0,Yo(e,n)}));function tu(e){var t=xn(e);return t.__chain__=!0,t}function nu(e,t){return t(e)}var ru=Vi((function(e){var t=e.length,n=t?e[0]:0,r=this.__wrapped__,i=function(t){return Yn(t,e)};return!(t>1||this.__actions__.length)&&r instanceof In&&uo(n)?((r=r.slice(n,+n+(t?1:0))).__actions__.push({func:nu,args:[i],thisArg:void 0}),new Pn(r,this.__chain__).thru((function(e){return t&&!e.length&&e.push(void 0),e}))):this.thru(i)}));var iu=_i((function(e,t,n){Se.call(e,n)?++e[n]:$n(e,n,1)}));var ou=ki(Io),uu=ki(No);function au(e,t){return(Nu(e)?ut:er)(e,Xi(t,3))}function lu(e,t){return(Nu(e)?at:tr)(e,Xi(t,3))}var su=_i((function(e,t,n){Se.call(e,n)?e[n].push(t):$n(e,n,[t])}));var cu=Br((function(e,t,n){var i=-1,o="function"==typeof t,u=Ru(e)?r(e.length):[];return er(e,(function(e){u[++i]=o?it(t,e,n):yr(e,t,n)})),u})),fu=_i((function(e,t,n){$n(e,n,t)}));function du(e,t){return(Nu(e)?dt:Tr)(e,Xi(t,3))}var pu=_i((function(e,t,n){e[n?0:1].push(t)}),(function(){return[[],[]]}));var hu=Br((function(e,t){if(null==e)return[];var n=t.length;return n>1&&ao(e,t[0],t[1])?t=[]:n>2&&ao(t[0],t[1],t[2])&&(t=[t[0]]),Ir(e,or(t,1),[])})),vu=Xt||function(){return Ge.Date.now()};function mu(e,t,n){return t=n?void 0:t,ji(e,128,void 0,void 0,void 0,void 0,t=e&&null==t?e.length:t)}function gu(e,t){var n;if("function"!=typeof t)throw new ye(i);return e=ra(e),function(){return--e>0&&(n=t.apply(this,arguments)),e<=1&&(t=void 0),n}}var yu=Br((function(e,t,n){var r=1;if(n.length){var i=zt(n,Ki(yu));r|=32}return ji(e,r,t,n,i)})),_u=Br((function(e,t,n){var r=3;if(n.length){var i=zt(n,Ki(_u));r|=32}return ji(t,r,e,n,i)}));function bu(e,t,n){var r,o,u,a,l,s,c=0,f=!1,d=!1,p=!0;if("function"!=typeof e)throw new ye(i);function h(t){var n=r,i=o;return r=o=void 0,c=t,a=e.apply(i,n)}function v(e){return c=e,l=bo(g,t),f?h(e):a}function m(e){var n=e-s;return void 0===s||n>=t||n<0||d&&e-c>=u}function g(){var e=vu();if(m(e))return y(e);l=bo(g,function(e){var n=t-(e-s);return d?an(n,u-(e-c)):n}(e))}function y(e){return l=void 0,p&&r?h(e):(r=o=void 0,a)}function _(){var e=vu(),n=m(e);if(r=arguments,o=this,s=e,n){if(void 0===l)return v(s);if(d)return ci(l),l=bo(g,t),h(s)}return void 0===l&&(l=bo(g,t)),a}return t=oa(t)||0,Hu(n)&&(f=!!n.leading,u=(d="maxWait"in n)?un(oa(n.maxWait)||0,t):u,p="trailing"in n?!!n.trailing:p),_.cancel=function(){void 0!==l&&ci(l),c=0,r=s=o=l=void 0},_.flush=function(){return void 0===l?a:y(vu())},_}var wu=Br((function(e,t){return Jn(e,1,t)})),Eu=Br((function(e,t,n){return Jn(e,oa(t)||0,n)}));function Du(e,t){if("function"!=typeof e||null!=t&&"function"!=typeof t)throw new ye(i);var n=function(){var r=arguments,i=t?t.apply(this,r):r[0],o=n.cache;if(o.has(i))return o.get(i);var u=e.apply(this,r);return n.cache=o.set(i,u)||o,u};return n.cache=new(Du.Cache||Rn),n}function Su(e){if("function"!=typeof e)throw new ye(i);return function(){var t=arguments;switch(t.length){case 0:return!e.call(this);case 1:return!e.call(this,t[0]);case 2:return!e.call(this,t[0],t[1]);case 3:return!e.call(this,t[0],t[1],t[2])}return!e.apply(this,t)}}Du.Cache=Rn;var Cu=li((function(e,t){var n=(t=1==t.length&&Nu(t[0])?dt(t[0],At(Xi())):dt(or(t,1),At(Xi()))).length;return Br((function(r){for(var i=-1,o=an(r.length,n);++i=t})),Iu=_r(function(){return arguments}())?_r:function(e){return Vu(e)&&Se.call(e,"callee")&&!Ye.call(e,"callee")},Nu=r.isArray,Mu=Je?At(Je):function(e){return Vu(e)&&pr(e)==E};function Ru(e){return null!=e&&Wu(e.length)&&!Uu(e)}function Fu(e){return Vu(e)&&Ru(e)}var Lu=tn||ol,Bu=Ze?At(Ze):function(e){return Vu(e)&&pr(e)==c};function ju(e){if(!Vu(e))return!1;var t=pr(e);return t==f||"[object DOMException]"==t||"string"==typeof e.message&&"string"==typeof e.name&&!$u(e)}function Uu(e){if(!Hu(e))return!1;var t=pr(e);return t==d||t==p||"[object AsyncFunction]"==t||"[object Proxy]"==t}function zu(e){return"number"==typeof e&&e==ra(e)}function Wu(e){return"number"==typeof e&&e>-1&&e%1==0&&e<=9007199254740991}function Hu(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}function Vu(e){return null!=e&&"object"==typeof e}var qu=et?At(et):function(e){return Vu(e)&&no(e)==h};function Gu(e){return"number"==typeof e||Vu(e)&&pr(e)==v}function $u(e){if(!Vu(e)||pr(e)!=m)return!1;var t=qe(e);if(null===t)return!0;var n=Se.call(t,"constructor")&&t.constructor;return"function"==typeof n&&n instanceof n&&De.call(n)==xe}var Yu=tt?At(tt):function(e){return Vu(e)&&pr(e)==g};var Ku=nt?At(nt):function(e){return Vu(e)&&no(e)==y};function Xu(e){return"string"==typeof e||!Nu(e)&&Vu(e)&&pr(e)==_}function Qu(e){return"symbol"==typeof e||Vu(e)&&pr(e)==b}var Ju=rt?At(rt):function(e){return Vu(e)&&Wu(e.length)&&!!je[pr(e)]};var Zu=Mi(kr),ea=Mi((function(e,t){return e<=t}));function ta(e){if(!e)return[];if(Ru(e))return Xu(e)?qt(e):gi(e);if(gt&&e[gt])return function(e){for(var t,n=[];!(t=e.next()).done;)n.push(t.value);return n}(e[gt]());var t=no(e);return(t==h?jt:t==y?Wt:Aa)(e)}function na(e){return e?(e=oa(e))===1/0||e===-1/0?17976931348623157e292*(e<0?-1:1):e==e?e:0:0===e?e:0}function ra(e){var t=na(e),n=t%1;return t==t?n?t-n:t:0}function ia(e){return e?Kn(ra(e),0,4294967295):0}function oa(e){if("number"==typeof e)return e;if(Qu(e))return NaN;if(Hu(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=Hu(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace($,"");var n=ie.test(e);return n||ue.test(e)?He(e.slice(2),n?2:8):re.test(e)?NaN:+e}function ua(e){return yi(e,wa(e))}function aa(e){return null==e?"":Qr(e)}var la=bi((function(e,t){if(fo(t)||Ru(t))yi(t,ba(t),e);else for(var n in t)Se.call(t,n)&&Hn(e,n,t[n])})),sa=bi((function(e,t){yi(t,wa(t),e)})),ca=bi((function(e,t,n,r){yi(t,wa(t),e,r)})),fa=bi((function(e,t,n,r){yi(t,ba(t),e,r)})),da=Vi(Yn);var pa=Br((function(e,t){e=ve(e);var n=-1,r=t.length,i=r>2?t[2]:void 0;for(i&&ao(t[0],t[1],i)&&(r=1);++n1),t})),yi(e,Gi(e),n),r&&(n=Xn(n,7,Wi));for(var i=t.length;i--;)Zr(n,t[i]);return n}));var Ca=Vi((function(e,t){return null==e?{}:function(e,t){return Nr(e,t,(function(t,n){return ma(e,n)}))}(e,t)}));function ka(e,t){if(null==e)return{};var n=dt(Gi(e),(function(e){return[e]}));return t=Xi(t),Nr(e,n,(function(e,n){return t(e,n[0])}))}var Ta=Bi(ba),xa=Bi(wa);function Aa(e){return null==e?[]:Ot(e,ba(e))}var Oa=Si((function(e,t,n){return t=t.toLowerCase(),e+(n?Pa(t):t)}));function Pa(e){return ja(aa(e).toLowerCase())}function Ia(e){return(e=aa(e))&&e.replace(le,Rt).replace(Ie,"")}var Na=Si((function(e,t,n){return e+(n?"-":"")+t.toLowerCase()})),Ma=Si((function(e,t,n){return e+(n?" ":"")+t.toLowerCase()})),Ra=Di("toLowerCase");var Fa=Si((function(e,t,n){return e+(n?"_":"")+t.toLowerCase()}));var La=Si((function(e,t,n){return e+(n?" ":"")+ja(t)}));var Ba=Si((function(e,t,n){return e+(n?" ":"")+t.toUpperCase()})),ja=Di("toUpperCase");function Ua(e,t,n){return e=aa(e),void 0===(t=n?void 0:t)?function(e){return Fe.test(e)}(e)?function(e){return e.match(Me)||[]}(e):function(e){return e.match(Z)||[]}(e):e.match(t)||[]}var za=Br((function(e,t){try{return it(e,void 0,t)}catch(e){return ju(e)?e:new de(e)}})),Wa=Vi((function(e,t){return ut(t,(function(t){t=ko(t),$n(e,t,yu(e[t],e))})),e}));function Ha(e){return function(){return e}}var Va=Ti(),qa=Ti(!0);function Ga(e){return e}function $a(e){return Dr("function"==typeof e?e:Xn(e,1))}var Ya=Br((function(e,t){return function(n){return yr(n,e,t)}})),Ka=Br((function(e,t){return function(n){return yr(e,n,t)}}));function Xa(e,t,n){var r=ba(t),i=cr(t,r);null!=n||Hu(t)&&(i.length||!r.length)||(n=t,t=e,e=this,i=cr(t,ba(t)));var o=!(Hu(n)&&"chain"in n&&!n.chain),u=Uu(e);return ut(i,(function(n){var r=t[n];e[n]=r,u&&(e.prototype[n]=function(){var t=this.__chain__;if(o||t){var n=e(this.__wrapped__),i=n.__actions__=gi(this.__actions__);return i.push({func:r,args:arguments,thisArg:e}),n.__chain__=t,n}return r.apply(e,pt([this.value()],arguments))})})),e}function Qa(){}var Ja=Pi(dt),Za=Pi(lt),el=Pi(mt);function tl(e){return lo(e)?St(ko(e)):function(e){return function(t){return fr(t,e)}}(e)}var nl=Ni(),rl=Ni(!0);function il(){return[]}function ol(){return!1}var ul=Oi((function(e,t){return e+t}),0),al=Fi("ceil"),ll=Oi((function(e,t){return e/t}),1),sl=Fi("floor");var cl,fl=Oi((function(e,t){return e*t}),1),dl=Fi("round"),pl=Oi((function(e,t){return e-t}),0);return xn.after=function(e,t){if("function"!=typeof t)throw new ye(i);return e=ra(e),function(){if(--e<1)return t.apply(this,arguments)}},xn.ary=mu,xn.assign=la,xn.assignIn=sa,xn.assignInWith=ca,xn.assignWith=fa,xn.at=da,xn.before=gu,xn.bind=yu,xn.bindAll=Wa,xn.bindKey=_u,xn.castArray=function(){if(!arguments.length)return[];var e=arguments[0];return Nu(e)?e:[e]},xn.chain=tu,xn.chunk=function(e,t,n){t=(n?ao(e,t,n):void 0===t)?1:un(ra(t),0);var i=null==e?0:e.length;if(!i||t<1)return[];for(var o=0,u=0,a=r(Jt(i/t));oi?0:i+n),(r=void 0===r||r>i?i:ra(r))<0&&(r+=i),r=n>r?0:ia(r);n>>0)?(e=aa(e))&&("string"==typeof t||null!=t&&!Yu(t))&&!(t=Qr(t))&&Bt(e)?si(qt(e),0,n):e.split(t,n):[]},xn.spread=function(e,t){if("function"!=typeof e)throw new ye(i);return t=null==t?0:un(ra(t),0),Br((function(n){var r=n[t],i=si(n,0,t);return r&&pt(i,r),it(e,this,i)}))},xn.tail=function(e){var t=null==e?0:e.length;return t?qr(e,1,t):[]},xn.take=function(e,t,n){return e&&e.length?qr(e,0,(t=n||void 0===t?1:ra(t))<0?0:t):[]},xn.takeRight=function(e,t,n){var r=null==e?0:e.length;return r?qr(e,(t=r-(t=n||void 0===t?1:ra(t)))<0?0:t,r):[]},xn.takeRightWhile=function(e,t){return e&&e.length?ti(e,Xi(t,3),!1,!0):[]},xn.takeWhile=function(e,t){return e&&e.length?ti(e,Xi(t,3)):[]},xn.tap=function(e,t){return t(e),e},xn.throttle=function(e,t,n){var r=!0,o=!0;if("function"!=typeof e)throw new ye(i);return Hu(n)&&(r="leading"in n?!!n.leading:r,o="trailing"in n?!!n.trailing:o),bu(e,t,{leading:r,maxWait:t,trailing:o})},xn.thru=nu,xn.toArray=ta,xn.toPairs=Ta,xn.toPairsIn=xa,xn.toPath=function(e){return Nu(e)?dt(e,ko):Qu(e)?[e]:gi(Co(aa(e)))},xn.toPlainObject=ua,xn.transform=function(e,t,n){var r=Nu(e),i=r||Lu(e)||Ju(e);if(t=Xi(t,4),null==n){var o=e&&e.constructor;n=i?r?new o:[]:Hu(e)&&Uu(o)?An(qe(e)):{}}return(i?ut:lr)(e,(function(e,r,i){return t(n,e,r,i)})),n},xn.unary=function(e){return mu(e,1)},xn.union=Vo,xn.unionBy=qo,xn.unionWith=Go,xn.uniq=function(e){return e&&e.length?Jr(e):[]},xn.uniqBy=function(e,t){return e&&e.length?Jr(e,Xi(t,2)):[]},xn.uniqWith=function(e,t){return t="function"==typeof t?t:void 0,e&&e.length?Jr(e,void 0,t):[]},xn.unset=function(e,t){return null==e||Zr(e,t)},xn.unzip=$o,xn.unzipWith=Yo,xn.update=function(e,t,n){return null==e?e:ei(e,t,ui(n))},xn.updateWith=function(e,t,n,r){return r="function"==typeof r?r:void 0,null==e?e:ei(e,t,ui(n),r)},xn.values=Aa,xn.valuesIn=function(e){return null==e?[]:Ot(e,wa(e))},xn.without=Ko,xn.words=Ua,xn.wrap=function(e,t){return ku(ui(t),e)},xn.xor=Xo,xn.xorBy=Qo,xn.xorWith=Jo,xn.zip=Zo,xn.zipObject=function(e,t){return ii(e||[],t||[],Hn)},xn.zipObjectDeep=function(e,t){return ii(e||[],t||[],zr)},xn.zipWith=eu,xn.entries=Ta,xn.entriesIn=xa,xn.extend=sa,xn.extendWith=ca,Xa(xn,xn),xn.add=ul,xn.attempt=za,xn.camelCase=Oa,xn.capitalize=Pa,xn.ceil=al,xn.clamp=function(e,t,n){return void 0===n&&(n=t,t=void 0),void 0!==n&&(n=(n=oa(n))==n?n:0),void 0!==t&&(t=(t=oa(t))==t?t:0),Kn(oa(e),t,n)},xn.clone=function(e){return Xn(e,4)},xn.cloneDeep=function(e){return Xn(e,5)},xn.cloneDeepWith=function(e,t){return Xn(e,5,t="function"==typeof t?t:void 0)},xn.cloneWith=function(e,t){return Xn(e,4,t="function"==typeof t?t:void 0)},xn.conformsTo=function(e,t){return null==t||Qn(e,t,ba(t))},xn.deburr=Ia,xn.defaultTo=function(e,t){return null==e||e!=e?t:e},xn.divide=ll,xn.endsWith=function(e,t,n){e=aa(e),t=Qr(t);var r=e.length,i=n=void 0===n?r:Kn(ra(n),0,r);return(n-=t.length)>=0&&e.slice(n,i)==t},xn.eq=Au,xn.escape=function(e){return(e=aa(e))&&B.test(e)?e.replace(F,Ft):e},xn.escapeRegExp=function(e){return(e=aa(e))&&G.test(e)?e.replace(q,"\\$&"):e},xn.every=function(e,t,n){var r=Nu(e)?lt:nr;return n&&ao(e,t,n)&&(t=void 0),r(e,Xi(t,3))},xn.find=ou,xn.findIndex=Io,xn.findKey=function(e,t){return yt(e,Xi(t,3),lr)},xn.findLast=uu,xn.findLastIndex=No,xn.findLastKey=function(e,t){return yt(e,Xi(t,3),sr)},xn.floor=sl,xn.forEach=au,xn.forEachRight=lu,xn.forIn=function(e,t){return null==e?e:ur(e,Xi(t,3),wa)},xn.forInRight=function(e,t){return null==e?e:ar(e,Xi(t,3),wa)},xn.forOwn=function(e,t){return e&&lr(e,Xi(t,3))},xn.forOwnRight=function(e,t){return e&&sr(e,Xi(t,3))},xn.get=va,xn.gt=Ou,xn.gte=Pu,xn.has=function(e,t){return null!=e&&ro(e,t,vr)},xn.hasIn=ma,xn.head=Ro,xn.identity=Ga,xn.includes=function(e,t,n,r){e=Ru(e)?e:Aa(e),n=n&&!r?ra(n):0;var i=e.length;return n<0&&(n=un(i+n,0)),Xu(e)?n<=i&&e.indexOf(t,n)>-1:!!i&&bt(e,t,n)>-1},xn.indexOf=function(e,t,n){var r=null==e?0:e.length;if(!r)return-1;var i=null==n?0:ra(n);return i<0&&(i=un(r+i,0)),bt(e,t,i)},xn.inRange=function(e,t,n){return t=na(t),void 0===n?(n=t,t=0):n=na(n),function(e,t,n){return e>=an(t,n)&&e=-9007199254740991&&e<=9007199254740991},xn.isSet=Ku,xn.isString=Xu,xn.isSymbol=Qu,xn.isTypedArray=Ju,xn.isUndefined=function(e){return void 0===e},xn.isWeakMap=function(e){return Vu(e)&&no(e)==w},xn.isWeakSet=function(e){return Vu(e)&&"[object WeakSet]"==pr(e)},xn.join=function(e,t){return null==e?"":rn.call(e,t)},xn.kebabCase=Na,xn.last=jo,xn.lastIndexOf=function(e,t,n){var r=null==e?0:e.length;if(!r)return-1;var i=r;return void 0!==n&&(i=(i=ra(n))<0?un(r+i,0):an(i,r-1)),t==t?function(e,t,n){for(var r=n+1;r--;)if(e[r]===t)return r;return r}(e,t,i):_t(e,Et,i,!0)},xn.lowerCase=Ma,xn.lowerFirst=Ra,xn.lt=Zu,xn.lte=ea,xn.max=function(e){return e&&e.length?rr(e,Ga,hr):void 0},xn.maxBy=function(e,t){return e&&e.length?rr(e,Xi(t,2),hr):void 0},xn.mean=function(e){return Dt(e,Ga)},xn.meanBy=function(e,t){return Dt(e,Xi(t,2))},xn.min=function(e){return e&&e.length?rr(e,Ga,kr):void 0},xn.minBy=function(e,t){return e&&e.length?rr(e,Xi(t,2),kr):void 0},xn.stubArray=il,xn.stubFalse=ol,xn.stubObject=function(){return{}},xn.stubString=function(){return""},xn.stubTrue=function(){return!0},xn.multiply=fl,xn.nth=function(e,t){return e&&e.length?Pr(e,ra(t)):void 0},xn.noConflict=function(){return Ge._===this&&(Ge._=Ae),this},xn.noop=Qa,xn.now=vu,xn.pad=function(e,t,n){e=aa(e);var r=(t=ra(t))?Vt(e):0;if(!t||r>=t)return e;var i=(t-r)/2;return Ii(Zt(i),n)+e+Ii(Jt(i),n)},xn.padEnd=function(e,t,n){e=aa(e);var r=(t=ra(t))?Vt(e):0;return t&&rt){var r=e;e=t,t=r}if(n||e%1||t%1){var i=cn();return an(e+i*(t-e+We("1e-"+((i+"").length-1))),t)}return Fr(e,t)},xn.reduce=function(e,t,n){var r=Nu(e)?ht:kt,i=arguments.length<3;return r(e,Xi(t,4),n,i,er)},xn.reduceRight=function(e,t,n){var r=Nu(e)?vt:kt,i=arguments.length<3;return r(e,Xi(t,4),n,i,tr)},xn.repeat=function(e,t,n){return t=(n?ao(e,t,n):void 0===t)?1:ra(t),Lr(aa(e),t)},xn.replace=function(){var e=arguments,t=aa(e[0]);return e.length<3?t:t.replace(e[1],e[2])},xn.result=function(e,t,n){var r=-1,i=(t=ai(t,e)).length;for(i||(i=1,e=void 0);++r9007199254740991)return[];var n=4294967295,r=an(e,4294967295);e-=4294967295;for(var i=xt(r,t=Xi(t));++n=o)return e;var a=n-Vt(r);if(a<1)return r;var l=u?si(u,0,a).join(""):e.slice(0,a);if(void 0===i)return l+r;if(u&&(a+=l.length-a),Yu(i)){if(e.slice(a).search(i)){var s,c=l;for(i.global||(i=me(i.source,aa(ne.exec(i))+"g")),i.lastIndex=0;s=i.exec(c);)var f=s.index;l=l.slice(0,void 0===f?a:f)}}else if(e.indexOf(Qr(i),a)!=a){var d=l.lastIndexOf(i);d>-1&&(l=l.slice(0,d))}return l+r},xn.unescape=function(e){return(e=aa(e))&&L.test(e)?e.replace(R,Gt):e},xn.uniqueId=function(e){var t=++Ce;return aa(e)+t},xn.upperCase=Ba,xn.upperFirst=ja,xn.each=au,xn.eachRight=lu,xn.first=Ro,Xa(xn,(cl={},lr(xn,(function(e,t){Se.call(xn.prototype,t)||(cl[t]=e)})),cl),{chain:!1}),xn.VERSION="4.17.20",ut(["bind","bindKey","curry","curryRight","partial","partialRight"],(function(e){xn[e].placeholder=xn})),ut(["drop","take"],(function(e,t){In.prototype[e]=function(n){n=void 0===n?1:un(ra(n),0);var r=this.__filtered__&&!t?new In(this):this.clone();return r.__filtered__?r.__takeCount__=an(n,r.__takeCount__):r.__views__.push({size:an(n,4294967295),type:e+(r.__dir__<0?"Right":"")}),r},In.prototype[e+"Right"]=function(t){return this.reverse()[e](t).reverse()}})),ut(["filter","map","takeWhile"],(function(e,t){var n=t+1,r=1==n||3==n;In.prototype[e]=function(e){var t=this.clone();return t.__iteratees__.push({iteratee:Xi(e,3),type:n}),t.__filtered__=t.__filtered__||r,t}})),ut(["head","last"],(function(e,t){var n="take"+(t?"Right":"");In.prototype[e]=function(){return this[n](1).value()[0]}})),ut(["initial","tail"],(function(e,t){var n="drop"+(t?"":"Right");In.prototype[e]=function(){return this.__filtered__?new In(this):this[n](1)}})),In.prototype.compact=function(){return this.filter(Ga)},In.prototype.find=function(e){return this.filter(e).head()},In.prototype.findLast=function(e){return this.reverse().find(e)},In.prototype.invokeMap=Br((function(e,t){return"function"==typeof e?new In(this):this.map((function(n){return yr(n,e,t)}))})),In.prototype.reject=function(e){return this.filter(Su(Xi(e)))},In.prototype.slice=function(e,t){e=ra(e);var n=this;return n.__filtered__&&(e>0||t<0)?new In(n):(e<0?n=n.takeRight(-e):e&&(n=n.drop(e)),void 0!==t&&(n=(t=ra(t))<0?n.dropRight(-t):n.take(t-e)),n)},In.prototype.takeRightWhile=function(e){return this.reverse().takeWhile(e).reverse()},In.prototype.toArray=function(){return this.take(4294967295)},lr(In.prototype,(function(e,t){var n=/^(?:filter|find|map|reject)|While$/.test(t),r=/^(?:head|last)$/.test(t),i=xn[r?"take"+("last"==t?"Right":""):t],o=r||/^find/.test(t);i&&(xn.prototype[t]=function(){var t=this.__wrapped__,u=r?[1]:arguments,a=t instanceof In,l=u[0],s=a||Nu(t),c=function(e){var t=i.apply(xn,pt([e],u));return r&&f?t[0]:t};s&&n&&"function"==typeof l&&1!=l.length&&(a=s=!1);var f=this.__chain__,d=!!this.__actions__.length,p=o&&!f,h=a&&!d;if(!o&&s){t=h?t:new In(this);var v=e.apply(t,u);return v.__actions__.push({func:nu,args:[c],thisArg:void 0}),new Pn(v,f)}return p&&h?e.apply(this,u):(v=this.thru(c),p?r?v.value()[0]:v.value():v)})})),ut(["pop","push","shift","sort","splice","unshift"],(function(e){var t=_e[e],n=/^(?:push|sort|unshift)$/.test(e)?"tap":"thru",r=/^(?:pop|shift)$/.test(e);xn.prototype[e]=function(){var e=arguments;if(r&&!this.__chain__){var i=this.value();return t.apply(Nu(i)?i:[],e)}return this[n]((function(n){return t.apply(Nu(n)?n:[],e)}))}})),lr(In.prototype,(function(e,t){var n=xn[t];if(n){var r=n.name+"";Se.call(_n,r)||(_n[r]=[]),_n[r].push({name:t,func:n})}})),_n[xi(void 0,2).name]=[{name:"wrapper",func:void 0}],In.prototype.clone=function(){var e=new In(this.__wrapped__);return e.__actions__=gi(this.__actions__),e.__dir__=this.__dir__,e.__filtered__=this.__filtered__,e.__iteratees__=gi(this.__iteratees__),e.__takeCount__=this.__takeCount__,e.__views__=gi(this.__views__),e},In.prototype.reverse=function(){if(this.__filtered__){var e=new In(this);e.__dir__=-1,e.__filtered__=!0}else(e=this.clone()).__dir__*=-1;return e},In.prototype.value=function(){var e=this.__wrapped__.value(),t=this.__dir__,n=Nu(e),r=t<0,i=n?e.length:0,o=function(e,t,n){var r=-1,i=n.length;for(;++r=this.__values__.length;return{done:e,value:e?void 0:this.__values__[this.__index__++]}},xn.prototype.plant=function(e){for(var t,n=this;n instanceof On;){var r=xo(n);r.__index__=0,r.__values__=void 0,t?i.__wrapped__=r:t=r;var i=r;n=n.__wrapped__}return i.__wrapped__=e,t},xn.prototype.reverse=function(){var e=this.__wrapped__;if(e instanceof In){var t=e;return this.__actions__.length&&(t=new In(this)),(t=t.reverse()).__actions__.push({func:nu,args:[Ho],thisArg:void 0}),new Pn(t,this.__chain__)}return this.thru(Ho)},xn.prototype.toJSON=xn.prototype.valueOf=xn.prototype.value=function(){return ni(this.__wrapped__,this.__actions__)},xn.prototype.first=xn.prototype.head,gt&&(xn.prototype[gt]=function(){return this}),xn}();Ge._=$t,void 0===(r=function(){return $t}.call(t,n,t,e))||(e.exports=r)}.call(this)},1573:e=>{"use strict";const t=(e,t)=>{for(const n of Reflect.ownKeys(t))Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n));return e};e.exports=t,e.exports.default=t},9381:e=>{"use strict"; /* object-assign (c) Sindre Sorhus @license MIT */var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;function i(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,o){for(var u,a,l=i(e),s=1;s{"use strict";const r=n(1573),i=new WeakMap,o=(e,t={})=>{if("function"!=typeof e)throw new TypeError("Expected a function");let n,o=!1,u=0;const a=e.displayName||e.name||"",l=function(...r){if(i.set(l,++u),o){if(!0===t.throw)throw new Error(`Function \`${a}\` can only be called once`);return n}return o=!0,n=e.apply(this,r),e=null,n};return r(l,e),i.set(l,u),l};e.exports=o,e.exports.default=o,e.exports.callCount=e=>{if(!i.has(e))throw new Error(`The given function \`${e.name}\` is not wrapped by the \`onetime\` package`);return i.get(e)}},8070:(e,t,n)=>{"use strict";const r=n(2413),i=["assert","count","countReset","debug","dir","dirxml","error","group","groupCollapsed","groupEnd","info","log","table","time","timeEnd","timeLog","trace","warn"];let o={};e.exports=e=>{const t=new r.PassThrough,n=new r.PassThrough;t.write=t=>e("stdout",t),n.write=t=>e("stderr",t);const u=new console.Console(t,n);for(const e of i)o[e]=console[e],console[e]=u[e];return()=>{for(const e of i)console[e]=o[e];o={}}}},5187:e=>{window,e.exports=function(e){var t={};function n(r){if(t[r])return t[r].exports;var i=t[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(r,i,function(t){return e[t]}.bind(null,i));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=20)}([function(e,t,n){"use strict";e.exports=n(12)},function(e,t,n){"use strict"; /* object-assign (c) Sindre Sorhus @license MIT */var r=Object.getOwnPropertySymbols,i=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable;function u(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,a,l=u(e),s=1;s=t||n<0||f&&e-s>=o}function w(){var e=h();if(b(e))return E(e);a=setTimeout(w,function(e){var n=t-(e-l);return f?p(n,o-(e-s)):n}(e))}function E(e){return a=void 0,m&&r?y(e):(r=i=void 0,u)}function D(){var e=h(),n=b(e);if(r=arguments,i=this,l=e,n){if(void 0===a)return _(l);if(f)return a=setTimeout(w,t),y(l)}return void 0===a&&(a=setTimeout(w,t)),u}return t=g(t)||0,v(n)&&(c=!!n.leading,o=(f="maxWait"in n)?d(g(n.maxWait)||0,t):o,m="trailing"in n?!!n.trailing:m),D.cancel=function(){void 0!==a&&clearTimeout(a),s=0,r=l=i=a=void 0},D.flush=function(){return void 0===a?u:E(h())},D}(e,t,{leading:r,maxWait:t,trailing:i})}}).call(this,n(4))},function(e,t,n){(function(n){function r(e){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var i;t=e.exports=p,i="object"===(void 0===n?"undefined":r(n))&&n.env&&n.env.NODE_DEBUG&&/\bsemver\b/i.test(n.env.NODE_DEBUG)?function(){var e=Array.prototype.slice.call(arguments,0);e.unshift("SEMVER"),console.log.apply(console,e)}:function(){},t.SEMVER_SPEC_VERSION="2.0.0";var o=Number.MAX_SAFE_INTEGER||9007199254740991,u=t.re=[],a=t.src=[],l=t.tokens={},s=0;function c(e){l[e]=s++}c("NUMERICIDENTIFIER"),a[l.NUMERICIDENTIFIER]="0|[1-9]\\d*",c("NUMERICIDENTIFIERLOOSE"),a[l.NUMERICIDENTIFIERLOOSE]="[0-9]+",c("NONNUMERICIDENTIFIER"),a[l.NONNUMERICIDENTIFIER]="\\d*[a-zA-Z-][a-zA-Z0-9-]*",c("MAINVERSION"),a[l.MAINVERSION]="("+a[l.NUMERICIDENTIFIER]+")\\.("+a[l.NUMERICIDENTIFIER]+")\\.("+a[l.NUMERICIDENTIFIER]+")",c("MAINVERSIONLOOSE"),a[l.MAINVERSIONLOOSE]="("+a[l.NUMERICIDENTIFIERLOOSE]+")\\.("+a[l.NUMERICIDENTIFIERLOOSE]+")\\.("+a[l.NUMERICIDENTIFIERLOOSE]+")",c("PRERELEASEIDENTIFIER"),a[l.PRERELEASEIDENTIFIER]="(?:"+a[l.NUMERICIDENTIFIER]+"|"+a[l.NONNUMERICIDENTIFIER]+")",c("PRERELEASEIDENTIFIERLOOSE"),a[l.PRERELEASEIDENTIFIERLOOSE]="(?:"+a[l.NUMERICIDENTIFIERLOOSE]+"|"+a[l.NONNUMERICIDENTIFIER]+")",c("PRERELEASE"),a[l.PRERELEASE]="(?:-("+a[l.PRERELEASEIDENTIFIER]+"(?:\\."+a[l.PRERELEASEIDENTIFIER]+")*))",c("PRERELEASELOOSE"),a[l.PRERELEASELOOSE]="(?:-?("+a[l.PRERELEASEIDENTIFIERLOOSE]+"(?:\\."+a[l.PRERELEASEIDENTIFIERLOOSE]+")*))",c("BUILDIDENTIFIER"),a[l.BUILDIDENTIFIER]="[0-9A-Za-z-]+",c("BUILD"),a[l.BUILD]="(?:\\+("+a[l.BUILDIDENTIFIER]+"(?:\\."+a[l.BUILDIDENTIFIER]+")*))",c("FULL"),c("FULLPLAIN"),a[l.FULLPLAIN]="v?"+a[l.MAINVERSION]+a[l.PRERELEASE]+"?"+a[l.BUILD]+"?",a[l.FULL]="^"+a[l.FULLPLAIN]+"$",c("LOOSEPLAIN"),a[l.LOOSEPLAIN]="[v=\\s]*"+a[l.MAINVERSIONLOOSE]+a[l.PRERELEASELOOSE]+"?"+a[l.BUILD]+"?",c("LOOSE"),a[l.LOOSE]="^"+a[l.LOOSEPLAIN]+"$",c("GTLT"),a[l.GTLT]="((?:<|>)?=?)",c("XRANGEIDENTIFIERLOOSE"),a[l.XRANGEIDENTIFIERLOOSE]=a[l.NUMERICIDENTIFIERLOOSE]+"|x|X|\\*",c("XRANGEIDENTIFIER"),a[l.XRANGEIDENTIFIER]=a[l.NUMERICIDENTIFIER]+"|x|X|\\*",c("XRANGEPLAIN"),a[l.XRANGEPLAIN]="[v=\\s]*("+a[l.XRANGEIDENTIFIER]+")(?:\\.("+a[l.XRANGEIDENTIFIER]+")(?:\\.("+a[l.XRANGEIDENTIFIER]+")(?:"+a[l.PRERELEASE]+")?"+a[l.BUILD]+"?)?)?",c("XRANGEPLAINLOOSE"),a[l.XRANGEPLAINLOOSE]="[v=\\s]*("+a[l.XRANGEIDENTIFIERLOOSE]+")(?:\\.("+a[l.XRANGEIDENTIFIERLOOSE]+")(?:\\.("+a[l.XRANGEIDENTIFIERLOOSE]+")(?:"+a[l.PRERELEASELOOSE]+")?"+a[l.BUILD]+"?)?)?",c("XRANGE"),a[l.XRANGE]="^"+a[l.GTLT]+"\\s*"+a[l.XRANGEPLAIN]+"$",c("XRANGELOOSE"),a[l.XRANGELOOSE]="^"+a[l.GTLT]+"\\s*"+a[l.XRANGEPLAINLOOSE]+"$",c("COERCE"),a[l.COERCE]="(^|[^\\d])(\\d{1,16})(?:\\.(\\d{1,16}))?(?:\\.(\\d{1,16}))?(?:$|[^\\d])",c("COERCERTL"),u[l.COERCERTL]=new RegExp(a[l.COERCE],"g"),c("LONETILDE"),a[l.LONETILDE]="(?:~>?)",c("TILDETRIM"),a[l.TILDETRIM]="(\\s*)"+a[l.LONETILDE]+"\\s+",u[l.TILDETRIM]=new RegExp(a[l.TILDETRIM],"g"),c("TILDE"),a[l.TILDE]="^"+a[l.LONETILDE]+a[l.XRANGEPLAIN]+"$",c("TILDELOOSE"),a[l.TILDELOOSE]="^"+a[l.LONETILDE]+a[l.XRANGEPLAINLOOSE]+"$",c("LONECARET"),a[l.LONECARET]="(?:\\^)",c("CARETTRIM"),a[l.CARETTRIM]="(\\s*)"+a[l.LONECARET]+"\\s+",u[l.CARETTRIM]=new RegExp(a[l.CARETTRIM],"g"),c("CARET"),a[l.CARET]="^"+a[l.LONECARET]+a[l.XRANGEPLAIN]+"$",c("CARETLOOSE"),a[l.CARETLOOSE]="^"+a[l.LONECARET]+a[l.XRANGEPLAINLOOSE]+"$",c("COMPARATORLOOSE"),a[l.COMPARATORLOOSE]="^"+a[l.GTLT]+"\\s*("+a[l.LOOSEPLAIN]+")$|^$",c("COMPARATOR"),a[l.COMPARATOR]="^"+a[l.GTLT]+"\\s*("+a[l.FULLPLAIN]+")$|^$",c("COMPARATORTRIM"),a[l.COMPARATORTRIM]="(\\s*)"+a[l.GTLT]+"\\s*("+a[l.LOOSEPLAIN]+"|"+a[l.XRANGEPLAIN]+")",u[l.COMPARATORTRIM]=new RegExp(a[l.COMPARATORTRIM],"g"),c("HYPHENRANGE"),a[l.HYPHENRANGE]="^\\s*("+a[l.XRANGEPLAIN]+")\\s+-\\s+("+a[l.XRANGEPLAIN]+")\\s*$",c("HYPHENRANGELOOSE"),a[l.HYPHENRANGELOOSE]="^\\s*("+a[l.XRANGEPLAINLOOSE]+")\\s+-\\s+("+a[l.XRANGEPLAINLOOSE]+")\\s*$",c("STAR"),a[l.STAR]="(<|>)?=?\\s*\\*";for(var f=0;f256)return null;if(!(t.loose?u[l.LOOSE]:u[l.FULL]).test(e))return null;try{return new p(e,t)}catch(e){return null}}function p(e,t){if(t&&"object"===r(t)||(t={loose:!!t,includePrerelease:!1}),e instanceof p){if(e.loose===t.loose)return e;e=e.version}else if("string"!=typeof e)throw new TypeError("Invalid Version: "+e);if(e.length>256)throw new TypeError("version is longer than 256 characters");if(!(this instanceof p))return new p(e,t);i("SemVer",e,t),this.options=t,this.loose=!!t.loose;var n=e.trim().match(t.loose?u[l.LOOSE]:u[l.FULL]);if(!n)throw new TypeError("Invalid Version: "+e);if(this.raw=e,this.major=+n[1],this.minor=+n[2],this.patch=+n[3],this.major>o||this.major<0)throw new TypeError("Invalid major version");if(this.minor>o||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>o||this.patch<0)throw new TypeError("Invalid patch version");n[4]?this.prerelease=n[4].split(".").map((function(e){if(/^[0-9]+$/.test(e)){var t=+e;if(t>=0&&t=0;)"number"==typeof this.prerelease[n]&&(this.prerelease[n]++,n=-2);-1===n&&this.prerelease.push(0)}t&&(this.prerelease[0]===t?isNaN(this.prerelease[1])&&(this.prerelease=[t,0]):this.prerelease=[t,0]);break;default:throw new Error("invalid increment argument: "+e)}return this.format(),this.raw=this.version,this},t.inc=function(e,t,n,r){"string"==typeof n&&(r=n,n=void 0);try{return new p(e,n).inc(t,r).version}catch(e){return null}},t.diff=function(e,t){if(_(e,t))return null;var n=d(e),r=d(t),i="";if(n.prerelease.length||r.prerelease.length){i="pre";var o="prerelease"}for(var u in n)if(("major"===u||"minor"===u||"patch"===u)&&n[u]!==r[u])return i+u;return o},t.compareIdentifiers=v;var h=/^[0-9]+$/;function v(e,t){var n=h.test(e),r=h.test(t);return n&&r&&(e=+e,t=+t),e===t?0:n&&!r?-1:r&&!n?1:e0}function y(e,t,n){return m(e,t,n)<0}function _(e,t,n){return 0===m(e,t,n)}function b(e,t,n){return 0!==m(e,t,n)}function w(e,t,n){return m(e,t,n)>=0}function E(e,t,n){return m(e,t,n)<=0}function D(e,t,n,i){switch(t){case"===":return"object"===r(e)&&(e=e.version),"object"===r(n)&&(n=n.version),e===n;case"!==":return"object"===r(e)&&(e=e.version),"object"===r(n)&&(n=n.version),e!==n;case"":case"=":case"==":return _(e,n,i);case"!=":return b(e,n,i);case">":return g(e,n,i);case">=":return w(e,n,i);case"<":return y(e,n,i);case"<=":return E(e,n,i);default:throw new TypeError("Invalid operator: "+t)}}function S(e,t){if(t&&"object"===r(t)||(t={loose:!!t,includePrerelease:!1}),e instanceof S){if(e.loose===!!t.loose)return e;e=e.value}if(!(this instanceof S))return new S(e,t);i("comparator",e,t),this.options=t,this.loose=!!t.loose,this.parse(e),this.semver===C?this.value="":this.value=this.operator+this.semver.version,i("comp",this)}t.rcompareIdentifiers=function(e,t){return v(t,e)},t.major=function(e,t){return new p(e,t).major},t.minor=function(e,t){return new p(e,t).minor},t.patch=function(e,t){return new p(e,t).patch},t.compare=m,t.compareLoose=function(e,t){return m(e,t,!0)},t.compareBuild=function(e,t,n){var r=new p(e,n),i=new p(t,n);return r.compare(i)||r.compareBuild(i)},t.rcompare=function(e,t,n){return m(t,e,n)},t.sort=function(e,n){return e.sort((function(e,r){return t.compareBuild(e,r,n)}))},t.rsort=function(e,n){return e.sort((function(e,r){return t.compareBuild(r,e,n)}))},t.gt=g,t.lt=y,t.eq=_,t.neq=b,t.gte=w,t.lte=E,t.cmp=D,t.Comparator=S;var C={};function k(e,t){if(t&&"object"===r(t)||(t={loose:!!t,includePrerelease:!1}),e instanceof k)return e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease?e:new k(e.raw,t);if(e instanceof S)return new k(e.value,t);if(!(this instanceof k))return new k(e,t);if(this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease,this.raw=e,this.set=e.split(/\s*\|\|\s*/).map((function(e){return this.parseRange(e.trim())}),this).filter((function(e){return e.length})),!this.set.length)throw new TypeError("Invalid SemVer Range: "+e);this.format()}function T(e,t){for(var n=!0,r=e.slice(),i=r.pop();n&&r.length;)n=r.every((function(e){return i.intersects(e,t)})),i=r.pop();return n}function x(e){return!e||"x"===e.toLowerCase()||"*"===e}function A(e,t,n,r,i,o,u,a,l,s,c,f,d){return((t=x(n)?"":x(r)?">="+n+".0.0":x(i)?">="+n+"."+r+".0":">="+t)+" "+(a=x(l)?"":x(s)?"<"+(+l+1)+".0.0":x(c)?"<"+l+"."+(+s+1)+".0":f?"<="+l+"."+s+"."+c+"-"+f:"<="+a)).trim()}function O(e,t,n){for(var r=0;r0){var o=e[r].semver;if(o.major===t.major&&o.minor===t.minor&&o.patch===t.patch)return!0}return!1}return!0}function P(e,t,n){try{t=new k(t,n)}catch(e){return!1}return t.test(e)}function I(e,t,n,r){var i,o,u,a,l;switch(e=new p(e,r),t=new k(t,r),n){case">":i=g,o=E,u=y,a=">",l=">=";break;case"<":i=y,o=w,u=g,a="<",l="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(P(e,t,r))return!1;for(var s=0;s=0.0.0")),f=f||e,d=d||e,i(e.semver,f.semver,r)?f=e:u(e.semver,d.semver,r)&&(d=e)})),f.operator===a||f.operator===l)return!1;if((!d.operator||d.operator===a)&&o(e,d.semver))return!1;if(d.operator===l&&u(e,d.semver))return!1}return!0}S.prototype.parse=function(e){var t=this.options.loose?u[l.COMPARATORLOOSE]:u[l.COMPARATOR],n=e.match(t);if(!n)throw new TypeError("Invalid comparator: "+e);this.operator=void 0!==n[1]?n[1]:"","="===this.operator&&(this.operator=""),n[2]?this.semver=new p(n[2],this.options.loose):this.semver=C},S.prototype.toString=function(){return this.value},S.prototype.test=function(e){if(i("Comparator.test",e,this.options.loose),this.semver===C||e===C)return!0;if("string"==typeof e)try{e=new p(e,this.options)}catch(e){return!1}return D(e,this.operator,this.semver,this.options)},S.prototype.intersects=function(e,t){if(!(e instanceof S))throw new TypeError("a Comparator is required");var n;if(t&&"object"===r(t)||(t={loose:!!t,includePrerelease:!1}),""===this.operator)return""===this.value||(n=new k(e.value,t),P(this.value,n,t));if(""===e.operator)return""===e.value||(n=new k(this.value,t),P(e.semver,n,t));var i=!(">="!==this.operator&&">"!==this.operator||">="!==e.operator&&">"!==e.operator),o=!("<="!==this.operator&&"<"!==this.operator||"<="!==e.operator&&"<"!==e.operator),u=this.semver.version===e.semver.version,a=!(">="!==this.operator&&"<="!==this.operator||">="!==e.operator&&"<="!==e.operator),l=D(this.semver,"<",e.semver,t)&&(">="===this.operator||">"===this.operator)&&("<="===e.operator||"<"===e.operator),s=D(this.semver,">",e.semver,t)&&("<="===this.operator||"<"===this.operator)&&(">="===e.operator||">"===e.operator);return i||o||u&&a||l||s},t.Range=k,k.prototype.format=function(){return this.range=this.set.map((function(e){return e.join(" ").trim()})).join("||").trim(),this.range},k.prototype.toString=function(){return this.range},k.prototype.parseRange=function(e){var t=this.options.loose;e=e.trim();var n=t?u[l.HYPHENRANGELOOSE]:u[l.HYPHENRANGE];e=e.replace(n,A),i("hyphen replace",e),e=e.replace(u[l.COMPARATORTRIM],"$1$2$3"),i("comparator trim",e,u[l.COMPARATORTRIM]),e=(e=(e=e.replace(u[l.TILDETRIM],"$1~")).replace(u[l.CARETTRIM],"$1^")).split(/\s+/).join(" ");var r=t?u[l.COMPARATORLOOSE]:u[l.COMPARATOR],o=e.split(" ").map((function(e){return function(e,t){return i("comp",e,t),e=function(e,t){return e.trim().split(/\s+/).map((function(e){return function(e,t){i("caret",e,t);var n=t.loose?u[l.CARETLOOSE]:u[l.CARET];return e.replace(n,(function(t,n,r,o,u){var a;return i("caret",e,t,n,r,o,u),x(n)?a="":x(r)?a=">="+n+".0.0 <"+(+n+1)+".0.0":x(o)?a="0"===n?">="+n+"."+r+".0 <"+n+"."+(+r+1)+".0":">="+n+"."+r+".0 <"+(+n+1)+".0.0":u?(i("replaceCaret pr",u),a="0"===n?"0"===r?">="+n+"."+r+"."+o+"-"+u+" <"+n+"."+r+"."+(+o+1):">="+n+"."+r+"."+o+"-"+u+" <"+n+"."+(+r+1)+".0":">="+n+"."+r+"."+o+"-"+u+" <"+(+n+1)+".0.0"):(i("no pr"),a="0"===n?"0"===r?">="+n+"."+r+"."+o+" <"+n+"."+r+"."+(+o+1):">="+n+"."+r+"."+o+" <"+n+"."+(+r+1)+".0":">="+n+"."+r+"."+o+" <"+(+n+1)+".0.0"),i("caret return",a),a}))}(e,t)})).join(" ")}(e,t),i("caret",e),e=function(e,t){return e.trim().split(/\s+/).map((function(e){return function(e,t){var n=t.loose?u[l.TILDELOOSE]:u[l.TILDE];return e.replace(n,(function(t,n,r,o,u){var a;return i("tilde",e,t,n,r,o,u),x(n)?a="":x(r)?a=">="+n+".0.0 <"+(+n+1)+".0.0":x(o)?a=">="+n+"."+r+".0 <"+n+"."+(+r+1)+".0":u?(i("replaceTilde pr",u),a=">="+n+"."+r+"."+o+"-"+u+" <"+n+"."+(+r+1)+".0"):a=">="+n+"."+r+"."+o+" <"+n+"."+(+r+1)+".0",i("tilde return",a),a}))}(e,t)})).join(" ")}(e,t),i("tildes",e),e=function(e,t){return i("replaceXRanges",e,t),e.split(/\s+/).map((function(e){return function(e,t){e=e.trim();var n=t.loose?u[l.XRANGELOOSE]:u[l.XRANGE];return e.replace(n,(function(n,r,o,u,a,l){i("xRange",e,n,r,o,u,a,l);var s=x(o),c=s||x(u),f=c||x(a),d=f;return"="===r&&d&&(r=""),l=t.includePrerelease?"-0":"",s?n=">"===r||"<"===r?"<0.0.0-0":"*":r&&d?(c&&(u=0),a=0,">"===r?(r=">=",c?(o=+o+1,u=0,a=0):(u=+u+1,a=0)):"<="===r&&(r="<",c?o=+o+1:u=+u+1),n=r+o+"."+u+"."+a+l):c?n=">="+o+".0.0"+l+" <"+(+o+1)+".0.0"+l:f&&(n=">="+o+"."+u+".0"+l+" <"+o+"."+(+u+1)+".0"+l),i("xRange return",n),n}))}(e,t)})).join(" ")}(e,t),i("xrange",e),e=function(e,t){return i("replaceStars",e,t),e.trim().replace(u[l.STAR],"")}(e,t),i("stars",e),e}(e,this.options)}),this).join(" ").split(/\s+/);return this.options.loose&&(o=o.filter((function(e){return!!e.match(r)}))),o.map((function(e){return new S(e,this.options)}),this)},k.prototype.intersects=function(e,t){if(!(e instanceof k))throw new TypeError("a Range is required");return this.set.some((function(n){return T(n,t)&&e.set.some((function(e){return T(e,t)&&n.every((function(n){return e.every((function(e){return n.intersects(e,t)}))}))}))}))},t.toComparators=function(e,t){return new k(e,t).set.map((function(e){return e.map((function(e){return e.value})).join(" ").trim().split(" ")}))},k.prototype.test=function(e){if(!e)return!1;if("string"==typeof e)try{e=new p(e,this.options)}catch(e){return!1}for(var t=0;t":0===t.prerelease.length?t.patch++:t.prerelease.push(0),t.raw=t.format();case"":case">=":n&&!g(n,t)||(n=t);break;case"<":case"<=":break;default:throw new Error("Unexpected operation: "+e.operator)}}));return n&&e.test(n)?n:null},t.validRange=function(e,t){try{return new k(e,t).range||"*"}catch(e){return null}},t.ltr=function(e,t,n){return I(e,t,"<",n)},t.gtr=function(e,t,n){return I(e,t,">",n)},t.outside=I,t.prerelease=function(e,t){var n=d(e,t);return n&&n.prerelease.length?n.prerelease:null},t.intersects=function(e,t,n){return e=new k(e,n),t=new k(t,n),e.intersects(t)},t.coerce=function(e,t){if(e instanceof p)return e;if("number"==typeof e&&(e=String(e)),"string"!=typeof e)return null;var n=null;if((t=t||{}).rtl){for(var r;(r=u[l.COERCERTL].exec(e))&&(!n||n.index+n[0].length!==e.length);)n&&r.index+r[0].length===n.index+n[0].length||(n=r),u[l.COERCERTL].lastIndex=r.index+r[1].length+r[2].length;u[l.COERCERTL].lastIndex=-1}else n=e.match(u[l.COERCE]);return null===n?null:d(n[2]+"."+(n[3]||"0")+"."+(n[4]||"0"),t)}}).call(this,n(5))},function(e,t){function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(e){"object"===("undefined"==typeof window?"undefined":n(window))&&(r=window)}e.exports=r},function(e,t){var n,r,i=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function u(){throw new Error("clearTimeout has not been defined")}function a(e){if(n===setTimeout)return setTimeout(e,0);if((n===o||!n)&&setTimeout)return n=setTimeout,setTimeout(e,0);try{return n(e,0)}catch(t){try{return n.call(null,e,0)}catch(t){return n.call(this,e,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:o}catch(e){n=o}try{r="function"==typeof clearTimeout?clearTimeout:u}catch(e){r=u}}();var l,s=[],c=!1,f=-1;function d(){c&&l&&(c=!1,l.length?s=l.concat(s):f=-1,s.length&&p())}function p(){if(!c){var e=a(d);c=!0;for(var t=s.length;t;){for(l=s,s=[];++f1)for(var n=1;nthis[u])return w(this,this[h].get(e)),!1;var o=this[h].get(e).value;return this[f]&&(this[d]||this[f](e,o.value)),o.now=r,o.maxAge=n,o.value=t,this[a]+=i-o.length,o.length=i,this.get(e),b(this),!0}var s=new E(e,t,i,r,n);return s.length>this[u]?(this[f]&&this[f](e,t),!1):(this[a]+=s.length,this[p].unshift(s),this[h].set(e,this[p].head),b(this),!0)}},{key:"has",value:function(e){if(!this[h].has(e))return!1;var t=this[h].get(e).value;return!_(this,t)}},{key:"get",value:function(e){return y(this,e,!0)}},{key:"peek",value:function(e){return y(this,e,!1)}},{key:"pop",value:function(){var e=this[p].tail;return e?(w(this,e),e.value):null}},{key:"del",value:function(e){w(this,this[h].get(e))}},{key:"load",value:function(e){this.reset();for(var t=Date.now(),n=e.length-1;n>=0;n--){var r=e[n],i=r.e||0;if(0===i)this.set(r.k,r.v);else{var o=i-t;o>0&&this.set(r.k,r.v,o)}}}},{key:"prune",value:function(){var e=this;this[h].forEach((function(t,n){return y(e,n,!1)}))}},{key:"max",set:function(e){if("number"!=typeof e||e<0)throw new TypeError("max must be a non-negative number");this[u]=e||1/0,b(this)},get:function(){return this[u]}},{key:"allowStale",set:function(e){this[s]=!!e},get:function(){return this[s]}},{key:"maxAge",set:function(e){if("number"!=typeof e)throw new TypeError("maxAge must be a non-negative number");this[c]=e,b(this)},get:function(){return this[c]}},{key:"lengthCalculator",set:function(e){var t=this;"function"!=typeof e&&(e=m),e!==this[l]&&(this[l]=e,this[a]=0,this[p].forEach((function(e){e.length=t[l](e.value,e.key),t[a]+=e.length}))),b(this)},get:function(){return this[l]}},{key:"length",get:function(){return this[a]}},{key:"itemCount",get:function(){return this[p].length}}])&&i(t.prototype,n),e}(),y=function(e,t,n){var r=e[h].get(t);if(r){var i=r.value;if(_(e,i)){if(w(e,r),!e[s])return}else n&&(e[v]&&(r.value.now=Date.now()),e[p].unshiftNode(r));return i.value}},_=function(e,t){if(!t||!t.maxAge&&!e[c])return!1;var n=Date.now()-t.now;return t.maxAge?n>t.maxAge:e[c]&&n>e[c]},b=function(e){if(e[a]>e[u])for(var t=e[p].tail;e[a]>e[u]&&null!==t;){var n=t.prev;w(e,t),t=n}},w=function(e,t){if(t){var n=t.value;e[f]&&e[f](n.key,n.value),e[a]-=n.length,e[h].delete(n.key),e[p].removeNode(t)}},E=function e(t,n,i,o,u){r(this,e),this.key=t,this.value=n,this.length=i,this.now=o,this.maxAge=u||0},D=function(e,t,n,r){var i=n.value;_(e,i)&&(w(e,n),e[s]||(i=void 0)),i&&t.call(r,i.value,i.key,e)};e.exports=g},function(e,t,n){(function(t){function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}e.exports=function(){if("undefined"==typeof document||!document.addEventListener)return null;var r,i,o,u={};return u.copy=function(){var e=!1,t=null,n=!1;function r(){e=!1,t=null,n&&window.getSelection().removeAllRanges(),n=!1}return document.addEventListener("copy",(function(n){if(e){for(var r in t)n.clipboardData.setData(r,t[r]);n.preventDefault()}})),function(i){return new Promise((function(o,u){e=!0,"string"==typeof i?t={"text/plain":i}:i instanceof Node?t={"text/html":(new XMLSerializer).serializeToString(i)}:i instanceof Object?t=i:u("Invalid data type. Must be string, DOM node, or an object mapping MIME types to strings."),function e(t){try{if(document.execCommand("copy"))r(),o();else{if(t)throw r(),new Error("Unable to copy. Perhaps it's not available in your browser?");!function(){var e=document.getSelection();if(!document.queryCommandEnabled("copy")&&e.isCollapsed){var t=document.createRange();t.selectNodeContents(document.body),e.removeAllRanges(),e.addRange(t),n=!0}}(),e(!0)}}catch(e){r(),u(e)}}(!1)}))}}(),u.paste=(o=!1,document.addEventListener("paste",(function(e){if(o){o=!1,e.preventDefault();var t=r;r=null,t(e.clipboardData.getData(i))}})),function(e){return new Promise((function(t,n){o=!0,r=t,i=e||"text/plain";try{document.execCommand("paste")||(o=!1,n(new Error("Unable to paste. Pasting only works in Internet Explorer at the moment.")))}catch(e){o=!1,n(new Error(e))}}))}),"undefined"==typeof ClipboardEvent&&void 0!==window.clipboardData&&void 0!==window.clipboardData.setData&&( /*! promise-polyfill 2.0.1 */ function(r){function i(e,t){return function(){e.apply(t,arguments)}}function o(e){if("object"!=n(this))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=null,this._value=null,this._deferreds=[],f(e,i(a,this),i(l,this))}function u(e){var t=this;return null===this._state?void this._deferreds.push(e):void d((function(){var n=t._state?e.onFulfilled:e.onRejected;if(null!==n){var r;try{r=n(t._value)}catch(t){return void e.reject(t)}e.resolve(r)}else(t._state?e.resolve:e.reject)(t._value)}))}function a(e){try{if(e===this)throw new TypeError("A promise cannot be resolved with itself.");if(e&&("object"==n(e)||"function"==typeof e)){var t=e.then;if("function"==typeof t)return void f(i(t,e),i(a,this),i(l,this))}this._state=!0,this._value=e,s.call(this)}catch(e){l.call(this,e)}}function l(e){this._state=!1,this._value=e,s.call(this)}function s(){for(var e=0,t=this._deferreds.length;t>e;e++)u.call(this,this._deferreds[e]);this._deferreds=null}function c(e,t,n,r){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof t?t:null,this.resolve=n,this.reject=r}function f(e,t,n){var r=!1;try{e((function(e){r||(r=!0,t(e))}),(function(e){r||(r=!0,n(e))}))}catch(e){if(r)return;r=!0,n(e)}}var d=o.immediateFn||"function"==typeof t&&t||function(e){setTimeout(e,1)},p=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)};o.prototype.catch=function(e){return this.then(null,e)},o.prototype.then=function(e,t){var n=this;return new o((function(r,i){u.call(n,new c(e,t,r,i))}))},o.all=function(){var e=Array.prototype.slice.call(1===arguments.length&&p(arguments[0])?arguments[0]:arguments);return new o((function(t,r){function i(u,a){try{if(a&&("object"==n(a)||"function"==typeof a)){var l=a.then;if("function"==typeof l)return void l.call(a,(function(e){i(u,e)}),r)}e[u]=a,0==--o&&t(e)}catch(e){r(e)}}if(0===e.length)return t([]);for(var o=e.length,u=0;ur;r++)e[r].then(t,n)}))},e.exports?e.exports=o:r.Promise||(r.Promise=o)}(this),u.copy=function(e){return new Promise((function(t,n){if("string"!=typeof e&&!("text/plain"in e))throw new Error("You must provide a text/plain type.");var r="string"==typeof e?e:e["text/plain"];window.clipboardData.setData("Text",r)?t():n(new Error("Copying was rejected."))}))},u.paste=function(){return new Promise((function(e,t){var n=window.clipboardData.getData("Text");n?e(n):t(new Error("Pasting was rejected."))}))}),u}()}).call(this,n(13).setImmediate)},function(e,t,n){"use strict";e.exports=n(15)},function(e,t,n){"use strict";n.r(t),t.default=":root {\n /**\n * IMPORTANT: When new theme variables are added below– also add them to SettingsContext updateThemeVariables()\n */\n\n /* Light theme */\n --light-color-attribute-name: #ef6632;\n --light-color-attribute-name-not-editable: #23272f;\n --light-color-attribute-name-inverted: rgba(255, 255, 255, 0.7);\n --light-color-attribute-value: #1a1aa6;\n --light-color-attribute-value-inverted: #ffffff;\n --light-color-attribute-editable-value: #1a1aa6;\n --light-color-background: #ffffff;\n --light-color-background-hover: rgba(0, 136, 250, 0.1);\n --light-color-background-inactive: #e5e5e5;\n --light-color-background-invalid: #fff0f0;\n --light-color-background-selected: #0088fa;\n --light-color-button-background: #ffffff;\n --light-color-button-background-focus: #ededed;\n --light-color-button: #5f6673;\n --light-color-button-disabled: #cfd1d5;\n --light-color-button-active: #0088fa;\n --light-color-button-focus: #23272f;\n --light-color-button-hover: #23272f;\n --light-color-border: #eeeeee;\n --light-color-commit-did-not-render-fill: #cfd1d5;\n --light-color-commit-did-not-render-fill-text: #000000;\n --light-color-commit-did-not-render-pattern: #cfd1d5;\n --light-color-commit-did-not-render-pattern-text: #333333;\n --light-color-commit-gradient-0: #37afa9;\n --light-color-commit-gradient-1: #63b19e;\n --light-color-commit-gradient-2: #80b393;\n --light-color-commit-gradient-3: #97b488;\n --light-color-commit-gradient-4: #abb67d;\n --light-color-commit-gradient-5: #beb771;\n --light-color-commit-gradient-6: #cfb965;\n --light-color-commit-gradient-7: #dfba57;\n --light-color-commit-gradient-8: #efbb49;\n --light-color-commit-gradient-9: #febc38;\n --light-color-commit-gradient-text: #000000;\n --light-color-component-name: #6a51b2;\n --light-color-component-name-inverted: #ffffff;\n --light-color-component-badge-background: rgba(0, 0, 0, 0.1);\n --light-color-component-badge-background-inverted: rgba(255, 255, 255, 0.25);\n --light-color-component-badge-count: #777d88;\n --light-color-component-badge-count-inverted: rgba(255, 255, 255, 0.7);\n --light-color-context-background: rgba(0,0,0,.9);\n --light-color-context-background-hover: rgba(255, 255, 255, 0.1);\n --light-color-context-background-selected: #178fb9;\n --light-color-context-border: #3d424a;\n --light-color-context-text: #ffffff;\n --light-color-context-text-selected: #ffffff;\n --light-color-dim: #777d88;\n --light-color-dimmer: #cfd1d5;\n --light-color-dimmest: #eff0f1;\n --light-color-error-background: hsl(0, 100%, 97%);\n --light-color-error-border: hsl(0, 100%, 92%);\n --light-color-error-text: #ff0000;\n --light-color-expand-collapse-toggle: #777d88;\n --light-color-link: #0000ff;\n --light-color-modal-background: rgba(255, 255, 255, 0.75);\n --light-color-record-active: #fc3a4b;\n --light-color-record-hover: #3578e5;\n --light-color-record-inactive: #0088fa;\n --light-color-scroll-thumb: #c2c2c2;\n --light-color-scroll-track: #fafafa;\n --light-color-search-match: yellow;\n --light-color-search-match-current: #f7923b;\n --light-color-selected-tree-highlight-active: rgba(0, 136, 250, 0.1);\n --light-color-selected-tree-highlight-inactive: rgba(0, 0, 0, 0.05);\n --light-color-shadow: rgba(0, 0, 0, 0.25);\n --light-color-tab-selected-border: #0088fa;\n --light-color-text: #000000;\n --light-color-text-invalid: #ff0000;\n --light-color-text-selected: #ffffff;\n --light-color-toggle-background-invalid: #fc3a4b;\n --light-color-toggle-background-on: #0088fa;\n --light-color-toggle-background-off: #cfd1d5;\n --light-color-toggle-text: #ffffff;\n --light-color-tooltip-background: rgba(0, 0, 0, 0.9);\n --light-color-tooltip-text: #ffffff;\n\n /* Dark theme */\n --dark-color-attribute-name: #9d87d2;\n --dark-color-attribute-name-not-editable: #ededed;\n --dark-color-attribute-name-inverted: #282828;\n --dark-color-attribute-value: #cedae0;\n --dark-color-attribute-value-inverted: #ffffff;\n --dark-color-attribute-editable-value: yellow;\n --dark-color-background: #282c34;\n --dark-color-background-hover: rgba(255, 255, 255, 0.1);\n --dark-color-background-inactive: #3d424a;\n --dark-color-background-invalid: #5c0000;\n --dark-color-background-selected: #178fb9;\n --dark-color-button-background: #282c34;\n --dark-color-button-background-focus: #3d424a;\n --dark-color-button: #afb3b9;\n --dark-color-button-active: #61dafb;\n --dark-color-button-disabled: #4f5766;\n --dark-color-button-focus: #a2e9fc;\n --dark-color-button-hover: #ededed;\n --dark-color-border: #3d424a;\n --dark-color-commit-did-not-render-fill: #777d88;\n --dark-color-commit-did-not-render-fill-text: #000000;\n --dark-color-commit-did-not-render-pattern: #666c77;\n --dark-color-commit-did-not-render-pattern-text: #ffffff;\n --dark-color-commit-gradient-0: #37afa9;\n --dark-color-commit-gradient-1: #63b19e;\n --dark-color-commit-gradient-2: #80b393;\n --dark-color-commit-gradient-3: #97b488;\n --dark-color-commit-gradient-4: #abb67d;\n --dark-color-commit-gradient-5: #beb771;\n --dark-color-commit-gradient-6: #cfb965;\n --dark-color-commit-gradient-7: #dfba57;\n --dark-color-commit-gradient-8: #efbb49;\n --dark-color-commit-gradient-9: #febc38;\n --dark-color-commit-gradient-text: #000000;\n --dark-color-component-name: #61dafb;\n --dark-color-component-name-inverted: #282828;\n --dark-color-component-badge-background: rgba(255, 255, 255, 0.25);\n --dark-color-component-badge-background-inverted: rgba(0, 0, 0, 0.25);\n --dark-color-component-badge-count: #8f949d;\n --dark-color-component-badge-count-inverted: rgba(255, 255, 255, 0.7);\n --dark-color-context-background: rgba(255,255,255,.9);\n --dark-color-context-background-hover: rgba(0, 136, 250, 0.1);\n --dark-color-context-background-selected: #0088fa;\n --dark-color-context-border: #eeeeee;\n --dark-color-context-text: #000000;\n --dark-color-context-text-selected: #ffffff;\n --dark-color-dim: #8f949d;\n --dark-color-dimmer: #777d88;\n --dark-color-dimmest: #4f5766;\n --dark-color-error-background: #200;\n --dark-color-error-border: #900;\n --dark-color-error-text: #f55;\n --dark-color-expand-collapse-toggle: #8f949d;\n --dark-color-link: #61dafb;\n --dark-color-modal-background: rgba(0, 0, 0, 0.75);\n --dark-color-record-active: #fc3a4b;\n --dark-color-record-hover: #a2e9fc;\n --dark-color-record-inactive: #61dafb;\n --dark-color-scroll-thumb: #afb3b9;\n --dark-color-scroll-track: #313640;\n --dark-color-search-match: yellow;\n --dark-color-search-match-current: #f7923b;\n --dark-color-selected-tree-highlight-active: rgba(23, 143, 185, 0.15);\n --dark-color-selected-tree-highlight-inactive: rgba(255, 255, 255, 0.05);\n --dark-color-shadow: rgba(0, 0, 0, 0.5);\n --dark-color-tab-selected-border: #178fb9;\n --dark-color-text: #ffffff;\n --dark-color-text-invalid: #ff8080;\n --dark-color-text-selected: #ffffff;\n --dark-color-toggle-background-invalid: #fc3a4b;\n --dark-color-toggle-background-on: #178fb9;\n --dark-color-toggle-background-off: #777d88;\n --dark-color-toggle-text: #ffffff;\n --dark-color-tooltip-background: rgba(255, 255, 255, 0.9);\n --dark-color-tooltip-text: #000000;\n\n /* Font smoothing */\n --light-font-smoothing: auto;\n --dark-font-smoothing: antialiased;\n --font-smoothing: auto;\n\n /* Compact density */\n --compact-font-size-monospace-small: 9px;\n --compact-font-size-monospace-normal: 11px;\n --compact-font-size-monospace-large: 15px;\n --compact-font-size-sans-small: 10px;\n --compact-font-size-sans-normal: 12px;\n --compact-font-size-sans-large: 14px;\n --compact-line-height-data: 18px;\n --compact-root-font-size: 16px;\n\n /* Comfortable density */\n --comfortable-font-size-monospace-small: 10px;\n --comfortable-font-size-monospace-normal: 13px;\n --comfortable-font-size-monospace-large: 17px;\n --comfortable-font-size-sans-small: 12px;\n --comfortable-font-size-sans-normal: 14px;\n --comfortable-font-size-sans-large: 16px;\n --comfortable-line-height-data: 22px;\n --comfortable-root-font-size: 20px;\n\n /* GitHub.com system fonts */\n --font-family-monospace: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo,\n Courier, monospace;\n --font-family-sans: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica,\n Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;\n\n /* Constant values shared between JS and CSS */\n --interaction-commit-size: 10px;\n --interaction-label-width: 200px;\n}\n"},function(e,t,n){"use strict";function r(e){var t=this;if(t instanceof r||(t=new r),t.tail=null,t.head=null,t.length=0,e&&"function"==typeof e.forEach)e.forEach((function(e){t.push(e)}));else if(arguments.length>0)for(var n=0,i=arguments.length;n1)n=t;else{if(!this.head)throw new TypeError("Reduce of empty list with no initial value");r=this.head.next,n=this.head.value}for(var i=0;null!==r;i++)n=e(n,r.value,i),r=r.next;return n},r.prototype.reduceReverse=function(e,t){var n,r=this.tail;if(arguments.length>1)n=t;else{if(!this.tail)throw new TypeError("Reduce of empty list with no initial value");r=this.tail.prev,n=this.tail.value}for(var i=this.length-1;null!==r;i--)n=e(n,r.value,i),r=r.prev;return n},r.prototype.toArray=function(){for(var e=new Array(this.length),t=0,n=this.head;null!==n;t++)e[t]=n.value,n=n.next;return e},r.prototype.toArrayReverse=function(){for(var e=new Array(this.length),t=0,n=this.tail;null!==n;t++)e[t]=n.value,n=n.prev;return e},r.prototype.slice=function(e,t){(t=t||this.length)<0&&(t+=this.length),(e=e||0)<0&&(e+=this.length);var n=new r;if(tthis.length&&(t=this.length);for(var i=0,o=this.head;null!==o&&ithis.length&&(t=this.length);for(var i=this.length,o=this.tail;null!==o&&i>t;i--)o=o.prev;for(;null!==o&&i>e;i--,o=o.prev)n.push(o.value);return n},r.prototype.splice=function(e,t){e>this.length&&(e=this.length-1),e<0&&(e=this.length+e);for(var n=0,r=this.head;null!==r&&n=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(14),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(4))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,i,o,u,a,l=1,s={},c=!1,f=e.document,d=Object.getPrototypeOf&&Object.getPrototypeOf(e);d=d&&d.setTimeout?d:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick((function(){h(e)}))}:function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?(u="setImmediate$"+Math.random()+"$",a=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(u)&&h(+t.data.slice(u.length))},e.addEventListener?e.addEventListener("message",a,!1):e.attachEvent("onmessage",a),r=function(t){e.postMessage(u+t,"*")}):e.MessageChannel?((o=new MessageChannel).port1.onmessage=function(e){h(e.data)},r=function(e){o.port2.postMessage(e)}):f&&"onreadystatechange"in f.createElement("script")?(i=f.documentElement,r=function(e){var t=f.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,i.removeChild(t),t=null},i.appendChild(t)}):r=function(e){setTimeout(h,0,e)},d.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;nv;v++)if(-1!==(h=g(p,c,v))){m=v,c=h;break e}c=-1}}e:{if(p=f,void 0!==(h=d().get(s.primitive)))for(v=0;vc-p?null:f.slice(p,c-1))){if(c=0,null!==r){for(;cc;r--)i=a.pop()}for(r=f.length-c-1;1<=r;r--)c=[],i.push({id:null,isStateEditable:!1,name:_(f[r-1].functionName),value:void 0,subHooks:c}),a.push(i),i=c;r=f}c="Context"===(f=s.primitive)||"DebugValue"===f?null:u++,i.push({id:c,isStateEditable:"Reducer"===f||"State"===f,name:f,value:s.value,subHooks:[]})}return function e(t,n){for(var r=[],i=0;i-1&&(t=t.replace(/eval code/g,"eval").replace(/(\(eval at [^()]*)|(\),.*$)/g,""));var n=t.replace(/^\s+/,"").replace(/\(eval code/g,"("),r=n.match(/ (\((.+):(\d+):(\d+)\)$)/),i=(n=r?n.replace(r[0],""):n).split(/\s+/).slice(1),o=this.extractLocation(r?r[1]:i.pop()),u=i.join(" ")||void 0,a=["eval",""].indexOf(o[0])>-1?void 0:o[0];return new e({functionName:u,fileName:a,lineNumber:o[1],columnNumber:o[2],source:t})}),this)},parseFFOrSafari:function(t){return t.stack.split("\n").filter((function(e){return!e.match(r)}),this).map((function(t){if(t.indexOf(" > eval")>-1&&(t=t.replace(/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g,":$1")),-1===t.indexOf("@")&&-1===t.indexOf(":"))return new e({functionName:t});var n=/((.*".+"[^@]*)?[^@]*)(?:@)/,r=t.match(n),i=r&&r[1]?r[1]:void 0,o=this.extractLocation(t.replace(n,""));return new e({functionName:i,fileName:o[0],lineNumber:o[1],columnNumber:o[2],source:t})}),this)},parseOpera:function(e){return!e.stacktrace||e.message.indexOf("\n")>-1&&e.message.split("\n").length>e.stacktrace.split("\n").length?this.parseOpera9(e):e.stack?this.parseOpera11(e):this.parseOpera10(e)},parseOpera9:function(t){for(var n=/Line (\d+).*script (?:in )?(\S+)/i,r=t.message.split("\n"),i=[],o=2,u=r.length;o/,"$2").replace(/\([^)]*\)/g,"")||void 0;o.match(/\(([^)]*)\)/)&&(n=o.replace(/^[^(]+\(([^)]*)\)$/,"$1"));var a=void 0===n||"[arguments not available]"===n?void 0:n.split(",");return new e({functionName:u,args:a,fileName:i[0],lineNumber:i[1],columnNumber:i[2],source:t})}),this)}}})?r.apply(t,i):r)||(e.exports=o)}()},function(e,t,n){var r,i,o;!function(n,u){"use strict";i=[],void 0===(o="function"==typeof(r=function(){function e(e){return e.charAt(0).toUpperCase()+e.substring(1)}function t(e){return function(){return this[e]}}var n=["isConstructor","isEval","isNative","isToplevel"],r=["columnNumber","lineNumber"],i=["fileName","functionName","source"],o=n.concat(r,i,["args"]);function u(t){if(t)for(var n=0;n1?n-1:0),i=1;i=0&&n.splice(r,1)}}}])&&r(t.prototype,n),e}(),o=n(2),u=n.n(o);try{var a=n(9).default,l=function(e){var t=new RegExp("".concat(e,": ([0-9]+)")),n=a.match(t);return parseInt(n[1],10)};l("comfortable-line-height-data"),l("compact-line-height-data")}catch(e){}function s(e){try{return sessionStorage.getItem(e)}catch(e){return null}}function c(e){try{sessionStorage.removeItem(e)}catch(e){}}function f(e,t){try{return sessionStorage.setItem(e,t)}catch(e){}}var d=function(e,t){return e===t},p=n(1),h=n.n(p);function v(e){return e.ownerDocument?e.ownerDocument.defaultView:null}function m(e){var t=v(e);return t?t.frameElement:null}function g(e){var t=b(e);return y([e.getBoundingClientRect(),{top:t.borderTop,left:t.borderLeft,bottom:t.borderBottom,right:t.borderRight,width:0,height:0}])}function y(e){return e.reduce((function(e,t){return null==e?t:{top:e.top+t.top,left:e.left+t.left,width:e.width,height:e.height,bottom:e.bottom+t.bottom,right:e.right+t.right}}))}function _(e,t){var n=m(e);if(n&&n!==t){for(var r=[e.getBoundingClientRect()],i=n,o=!1;i;){var u=g(i);if(r.push(u),i=m(i),o)break;i&&v(i)===t&&(o=!0)}return y(r)}return e.getBoundingClientRect()}function b(e){var t=window.getComputedStyle(e);return{borderLeft:parseInt(t.borderLeftWidth,10),borderRight:parseInt(t.borderRightWidth,10),borderTop:parseInt(t.borderTopWidth,10),borderBottom:parseInt(t.borderBottomWidth,10),marginLeft:parseInt(t.marginLeft,10),marginRight:parseInt(t.marginRight,10),marginTop:parseInt(t.marginTop,10),marginBottom:parseInt(t.marginBottom,10),paddingLeft:parseInt(t.paddingLeft,10),paddingRight:parseInt(t.paddingRight,10),paddingTop:parseInt(t.paddingTop,10),paddingBottom:parseInt(t.paddingBottom,10)}}function w(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nt.left+t.width&&(u=t.left+t.width-o-5),{style:{top:r+="px",left:u+="px"}}}(e,t,{width:n.width,height:n.height});h()(this.tip.style,r.style)}}]),e}(),T=function(){function e(){E(this,e);var t=window.__REACT_DEVTOOLS_TARGET_WINDOW__||window;this.window=t;var n=window.__REACT_DEVTOOLS_TARGET_WINDOW__||window;this.tipBoundsWindow=n;var r=t.document;this.container=r.createElement("div"),this.container.style.zIndex="10000000",this.tip=new k(r,this.container),this.rects=[],r.body.appendChild(this.container)}return S(e,[{key:"remove",value:function(){this.tip.remove(),this.rects.forEach((function(e){e.remove()})),this.rects.length=0,this.container.parentNode&&this.container.parentNode.removeChild(this.container)}},{key:"inspect",value:function(e,t){for(var n=this,r=e.filter((function(e){return e.nodeType===Node.ELEMENT_NODE}));this.rects.length>r.length;)this.rects.pop().remove();if(0!==r.length){for(;this.rects.length=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:i}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,u=!0,a=!1;return{s:function(){n=e[Symbol.iterator]()},n:function(){var e=n.next();return u=e.done,e},e:function(e){a=!0,o=e},f:function(){try{u||null==n.return||n.return()}finally{if(a)throw o}}}}(u.rendererInterfaces.values());try{for(s.s();!(a=s.n()).done;){var c=a.value,f=c.getFiberIDForNative(o,!0);if(null!==f){l=c.getDisplayNameForFiberID(f,!0);break}}}catch(e){s.e(e)}finally{s.f()}l&&(t+=" (in "+l+")")}}this.tip.updateText(t,i.right-i.left,i.bottom-i.top);var d=_(this.tipBoundsWindow.document.documentElement,this.window);this.tip.updatePosition({top:i.top,left:i.left,height:i.bottom-i.top,width:i.right-i.left},{top:d.top+this.tipBoundsWindow.scrollY,left:d.left+this.tipBoundsWindow.scrollX,height:this.tipBoundsWindow.innerHeight,width:this.tipBoundsWindow.innerWidth})}}}]),e}();function x(e,t,n){h()(n.style,{borderTopWidth:e[t+"Top"]+"px",borderLeftWidth:e[t+"Left"]+"px",borderRightWidth:e[t+"Right"]+"px",borderBottomWidth:e[t+"Bottom"]+"px",borderStyle:"solid"})}var A={background:"rgba(120, 170, 210, 0.7)",padding:"rgba(77, 200, 0, 0.3)",margin:"rgba(255, 155, 0, 0.3)",border:"rgba(255, 200, 50, 0.3)"},O=null,P=null;function I(){O=null,null!==P&&(P.remove(),P=null)}function N(e,t,n){null!=window.document&&(null!==O&&clearTimeout(O),null!=e&&(null===P&&(P=new T),P.inspect(e,t),n&&(O=setTimeout(I,2e3))))}var M=new Set,R=["#37afa9","#63b19e","#80b393","#97b488","#abb67d","#beb771","#cfb965","#dfba57","#efbb49","#febc38"],F=null;function L(e){return(L="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}var B="object"===("undefined"==typeof performance?"undefined":L(performance))&&"function"==typeof performance.now?function(){return performance.now()}:function(){return Date.now()},j=new Map,U=null,z=!1,W=null;function H(e){z&&(e.forEach((function(e){var t=j.get(e),n=B(),r=null!=t?t.lastMeasuredAt:0,i=null!=t?t.rect:null;(null===i||r+2505&&void 0!==arguments[5]?arguments[5]:0,a=me(e);switch(a){case"html_element":return t.push(r),{inspectable:!1,preview_short:_e(e,!1),preview_long:_e(e,!0),name:e.tagName,type:a};case"function":return t.push(r),{inspectable:!1,preview_short:_e(e,!1),preview_long:_e(e,!0),name:"function"!=typeof e.name&&e.name?e.name:"function",type:a};case"string":return e.length<=500?e:e.slice(0,500)+"...";case"bigint":case"symbol":return t.push(r),{inspectable:!1,preview_short:_e(e,!1),preview_long:_e(e,!0),name:e.toString(),type:a};case"react_element":return t.push(r),{inspectable:!1,preview_short:_e(e,!1),preview_long:_e(e,!0),name:ge(e)||"Unknown",type:a};case"array_buffer":case"data_view":return t.push(r),{inspectable:!1,preview_short:_e(e,!1),preview_long:_e(e,!0),name:"data_view"===a?"DataView":"ArrayBuffer",size:e.byteLength,type:a};case"array":return o=i(r),u>=2&&!o?Z(a,!0,e,t,r):e.map((function(e,a){return ee(e,t,n,r.concat([a]),i,o?1:u+1)}));case"html_all_collection":case"typed_array":case"iterator":if(o=i(r),u>=2&&!o)return Z(a,!0,e,t,r);var l={unserializable:!0,type:a,readonly:!0,size:"typed_array"===a?e.length:void 0,preview_short:_e(e,!1),preview_long:_e(e,!0),name:e.constructor&&"Object"!==e.constructor.name?e.constructor.name:""};return Q(e[Symbol.iterator])&&Array.from(e).forEach((function(e,a){return l[a]=ee(e,t,n,r.concat([a]),i,o?1:u+1)})),n.push(r),l;case"opaque_iterator":return t.push(r),{inspectable:!1,preview_short:_e(e,!1),preview_long:_e(e,!0),name:e[Symbol.toStringTag],type:a};case"date":case"regexp":return t.push(r),{inspectable:!1,preview_short:_e(e,!1),preview_long:_e(e,!0),name:e.toString(),type:a};case"object":if(o=i(r),u>=2&&!o)return Z(a,!0,e,t,r);var s={};return ae(e).forEach((function(a){var l=a.toString();s[l]=ee(e[a],t,n,r.concat([l]),i,o?1:u+1)})),s;case"infinity":case"nan":case"undefined":return t.push(r),{type:a};default:return e}}function te(e){return(te="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function ne(e){return function(e){if(Array.isArray(e))return re(e)}(e)||function(e){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||function(e,t){if(e){if("string"==typeof e)return re(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?re(e,t):void 0}}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function re(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);nt.toString()?1:t.toString()>e.toString()?-1:0}function ae(e){for(var t=[],n=e,r=function(){var e=[].concat(ne(Object.keys(n)),ne(Object.getOwnPropertySymbols(n))),r=Object.getOwnPropertyDescriptors(n);e.forEach((function(e){r[e].enumerable&&t.push(e)})),n=Object.getPrototypeOf(n)};null!=n;)r();return t}function le(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"Anonymous",n=ie.get(e);if(null!=n)return n;var r=t;return"string"==typeof e.displayName?r=e.displayName:"string"==typeof e.name&&""!==e.name&&(r=e.name),ie.set(e,r),r}var se=0;function ce(){return++se}function fe(e){var t=oe.get(e);if(void 0!==t)return t;for(var n=new Array(e.length),r=0;r1&&void 0!==arguments[1]?arguments[1]:50;return e.length>t?e.substr(0,t)+"…":e}function _e(e,t){if(null!=e&&hasOwnProperty.call(e,J.type))return t?e[J.preview_long]:e[J.preview_short];switch(me(e)){case"html_element":return"<".concat(ye(e.tagName.toLowerCase())," />");case"function":return ye("ƒ ".concat("function"==typeof e.name?"":e.name,"() {}"));case"string":return'"'.concat(e,'"');case"bigint":return ye(e.toString()+"n");case"regexp":case"symbol":return ye(e.toString());case"react_element":return"<".concat(ye(ge(e)||"Unknown")," />");case"array_buffer":return"ArrayBuffer(".concat(e.byteLength,")");case"data_view":return"DataView(".concat(e.buffer.byteLength,")");case"array":if(t){for(var n="",r=0;r0&&(n+=", "),!((n+=_e(e[r],!1)).length>50));r++);return"[".concat(ye(n),"]")}var i=hasOwnProperty.call(e,J.size)?e[J.size]:e.length;return"Array(".concat(i,")");case"typed_array":var o="".concat(e.constructor.name,"(").concat(e.length,")");if(t){for(var u="",a=0;a0&&(u+=", "),!((u+=e[a]).length>50));a++);return"".concat(o," [").concat(ye(u),"]")}return o;case"iterator":var l=e.constructor.name;if(t){for(var s=Array.from(e),c="",f=0;f0&&(c+=", "),Array.isArray(d)){var p=_e(d[0],!0),h=_e(d[1],!1);c+="".concat(p," => ").concat(h)}else c+=_e(d,!1);if(c.length>50)break}return"".concat(l,"(").concat(e.size,") {").concat(ye(c),"}")}return"".concat(l,"(").concat(e.size,")");case"opaque_iterator":return e[Symbol.toStringTag];case"date":return e.toString();case"object":if(t){for(var v=ae(e).sort(ue),m="",g=0;g0&&(m+=", "),(m+="".concat(y.toString(),": ").concat(_e(e[y],!1))).length>50)break}return"{".concat(ye(m),"}")}return"{…}";case"boolean":case"number":case"infinity":case"nan":case"null":case"undefined":return e;default:try{return ye(""+e)}catch(e){return"unserializable"}}}var be=n(7);function we(e){return(we="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function Ee(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function De(e){for(var t=1;t2&&void 0!==arguments[2]?arguments[2]:[];if(null!==e){var r=[],i=[],o=ee(e,r,i,n,t);return{data:o,cleaned:r,unserializable:i}}return null}function ke(e){var t,n,r=(t=e,n=new Set,JSON.stringify(t,(function(e,t){if("object"===we(t)&&null!==t){if(n.has(t))return;n.add(t)}return"bigint"==typeof t?t.toString()+"n":t}))),i=void 0===r?"undefined":r,o=window.__REACT_DEVTOOLS_GLOBAL_HOOK__.clipboardCopyText;"function"==typeof o?o(i).catch((function(e){})):Object(be.copy)(i)}function Te(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,r=t[n],i=Array.isArray(e)?e.slice():De({},e);return n+1===t.length?Array.isArray(i)?i.splice(r,1):delete i[r]:i[r]=Te(e[r],t,n+1),i}function xe(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0,i=t[r],o=Array.isArray(e)?e.slice():De({},e);if(r+1===t.length){var u=n[r];o[u]=o[i],Array.isArray(o)?o.splice(i,1):delete o[i]}else o[i]=xe(e[i],t,n,r+1);return o}function Ae(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:0;if(r>=t.length)return n;var i=t[r],o=Array.isArray(e)?e.slice():De({},e);return o[i]=Ae(e[i],t,n,r+1),o}var Oe=n(8);function Pe(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Ie(e){for(var t=1;t=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:i}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,u=!0,a=!1;return{s:function(){n=e[Symbol.iterator]()},n:function(){var e=n.next();return u=e.done,e},e:function(e){a=!0,o=e},f:function(){try{u||null==n.return||n.return()}finally{if(a)throw o}}}}function Le(e,t){if(e){if("string"==typeof e)return Be(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?Be(e,t):void 0}}function Be(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0){var a=o(e);if(null!=a){var l,s=Fe(Y);try{for(s.s();!(l=s.n()).done;)if(l.value.test(a))return!0}catch(e){s.e(e)}finally{s.f()}}}if(null!=t&&K.size>0){var c,f=t.fileName,d=Fe(K);try{for(d.s();!(c=d.n()).done;)if(c.value.test(f))return!0}catch(e){d.e(e)}finally{d.f()}}return!1}function te(e){var t=e.type;switch(e.tag){case v:case S:return 1;case h:case C:return 5;case _:return 6;case b:return 11;case E:return 7;case w:case D:case y:return 9;case k:case x:return 8;case A:return 12;case O:return 13;default:switch(u(t)){case 60111:case"Symbol(react.concurrent_mode)":case"Symbol(react.async_mode)":return 9;case 60109:case"Symbol(react.provider)":return 2;case 60110:case"Symbol(react.context)":return 2;case 60108:case"Symbol(react.strict_mode)":return 9;case 60114:case"Symbol(react.profiler)":return 10;default:return 9}}}function ne(e){if(oe.has(e))return e;var t=e.alternate;return null!=t&&oe.has(t)?t:(oe.add(e),e)}null!=window.__REACT_DEVTOOLS_COMPONENT_FILTERS__?Z(window.__REACT_DEVTOOLS_COMPONENT_FILTERS__):Z([{type:1,value:7,isEnabled:!0}]);var re=new Map,ie=new Map,oe=new Set,ue=new Map,ae=new Map,le=-1;function se(e){if(!re.has(e)){var t=ce();re.set(e,t),ie.set(t,e)}return re.get(e)}function me(e){switch(te(e)){case 1:if(null!==dt){var t=se(ne(e)),n=ye(e);null!==n&&dt.set(t,n)}}}var ge={};function ye(e){switch(te(e)){case 1:var t=e.stateNode,n=ge,r=ge;return null!=t&&(t.constructor&&null!=t.constructor.contextType?r=t.context:(n=t.context)&&0===Object.keys(n).length&&(n=ge)),[n,r];default:return null}}function _e(e){switch(te(e)){case 1:if(null!==dt){var t=se(ne(e)),n=dt.has(t)?dt.get(t):null,r=ye(e);if(null==n||null==r)return null;var i=Re(n,2),o=i[0],u=i[1],a=Re(r,2),l=a[0],s=a[1];if(l!==ge)return we(o,l);if(s!==ge)return u!==s}}return null}function be(e,t){if(null==e||null==t)return!1;if(t.hasOwnProperty("baseState")&&t.hasOwnProperty("memoizedState")&&t.hasOwnProperty("next")&&t.hasOwnProperty("queue"))for(;null!==t;){if(t.memoizedState!==e.memoizedState)return!0;t=t.next,e=e.next}return!1}function we(e,t){if(null==e||null==t)return null;if(t.hasOwnProperty("baseState")&&t.hasOwnProperty("memoizedState")&&t.hasOwnProperty("next")&&t.hasOwnProperty("queue"))return null;var n,r=[],i=Fe(new Set([].concat(Me(Object.keys(e)),Me(Object.keys(t)))));try{for(i.s();!(n=i.n()).done;){var o=n.value;e[o]!==t[o]&&r.push(o)}}catch(e){i.e(e)}finally{i.f()}return r}function Ee(e,t){switch(t.tag){case v:case h:case m:case k:case x:return(Ue(t)&d)===d;default:return e.memoizedProps!==t.memoizedProps||e.memoizedState!==t.memoizedState||e.ref!==t.ref}}var De=[],Se=[],Pe=[],Ne=[],Le=new Map,Be=0,je=null;function ze(e){De.push(e)}function Ve(n){if(0!==De.length||0!==Se.length||0!==Pe.length||null!==je||vt){var r=Se.length+Pe.length+(null===je?0:1),i=new Array(3+Be+(r>0?2+r:0)+De.length),o=0;if(i[o++]=t,i[o++]=le,i[o++]=Be,Le.forEach((function(e,t){i[o++]=t.length;for(var n=fe(t),r=0;r0){i[o++]=2,i[o++]=r;for(var u=Se.length-1;u>=0;u--)i[o++]=Se[u];for(var a=0;a0?n.forEach((function(t){e.emit("operations",t)})):(null!==Dt&&(kt=!0),e.getFiberRoots(t).forEach((function(e){Ot(le=se(ne(e.current)),e.current),vt&&null!=e.memoizedInteractions&&(st={changeDescriptions:gt?new Map:null,durations:[],commitTime:We()-mt,interactions:Array.from(e.memoizedInteractions).map((function(e){return Ie(Ie({},e),{},{timestamp:e.timestamp-mt})})),maxActualDuration:0,priorityLevel:null}),$e(e.current,null,!1,!1),Ve(),le=-1})))},getBestMatchForTrackedPath:function(){if(null===Dt)return null;if(null===St)return null;for(var e=St;null!==e&&ee(e);)e=e.return;return null===e?null:{id:se(ne(e)),isFullMatch:Ct===Dt.length-1}},getDisplayNameForFiberID:function(e){var t=ie.get(e);return null!=t?o(t):null},getFiberIDForNative:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],r=n.findFiberByHostInstance(e);if(null!=r){if(t)for(;null!==r&&ee(r);)r=r.return;return se(ne(r))}return null},getInstanceAndStyle:function(e){var t=null,n=null,r=et(e);return null!==r&&(t=r.stateNode,null!==r.memoizedProps&&(n=r.memoizedProps.style)),{instance:t,style:n}},getOwnersList:function(e){var t=et(e);if(null==t)return null;var n=t._debugOwner,r=[{displayName:o(t)||"Anonymous",id:e,type:te(t)}];if(n)for(var i=n;null!==i;)r.unshift({displayName:o(i)||"Anonymous",id:se(ne(i)),type:te(i)}),i=i._debugOwner||null;return r},getPathForElement:function(e){var t=ie.get(e);if(null==t)return null;for(var n=[];null!==t;)n.push(It(t)),t=t.return;return n.reverse(),n},getProfilingData:function(){var e=[];if(null===yt)throw Error("getProfilingData() called before any profiling data was recorded");return yt.forEach((function(t,n){var r=[],i=[],o=new Map,u=new Map,a=null!==ft&&ft.get(n)||"Unknown";null!=pt&&pt.forEach((function(e,t){null!=ht&&ht.get(t)===n&&i.push([t,e])})),t.forEach((function(e,t){var n=e.changeDescriptions,i=e.durations,a=e.interactions,l=e.maxActualDuration,s=e.priorityLevel,c=e.commitTime,f=[];a.forEach((function(e){o.has(e.id)||o.set(e.id,e),f.push(e.id);var n=u.get(e.id);null!=n?n.push(t):u.set(e.id,[t])}));for(var d=[],p=[],h=0;h1?At.set(n,r-1):At.delete(n),xt.delete(e)}(le),Ge(r,!1))}else Ot(le,r),$e(r,null,!1,!1);if(vt&&o){var l=yt.get(le);null!=l?l.push(st):yt.set(le,[st])}Ve(),Q&&e.emit("traceUpdates",J),le=-1},handleCommitFiberUnmount:function(e){Ge(e,!1)},inspectElement:function(e,t){if(ot(e)){if(null!=t){ut(t);var n=null;return"hooks"===t[0]&&(n="hooks"),{id:e,type:"hydrated-path",path:t,value:Ce(de(nt,t),at(null,n),t)}}return{id:e,type:"no-change"}}if(rt=!1,null!==nt&&nt.id===e||(it={}),null===(nt=tt(e)))return{id:e,type:"not-found"};null!=t&&ut(t),function(e){var t=e.hooks,n=e.id,i=e.props,o=ie.get(n);if(null!=o){var u=o.elementType,a=o.stateNode,l=o.tag,s=o.type;switch(l){case v:case S:case C:r.$r=a;break;case h:r.$r={hooks:t,props:i,type:s};break;case _:r.$r={props:i,type:s.render};break;case k:case x:r.$r={props:i,type:null!=u&&null!=u.type?u.type:s};break;default:r.$r=null}}else console.warn('Could not find Fiber with id "'.concat(n,'"'))}(nt);var i=Ie({},nt);return i.context=Ce(i.context,at("context",null)),i.hooks=Ce(i.hooks,at("hooks","hooks")),i.props=Ce(i.props,at("props",null)),i.state=Ce(i.state,at("state",null)),{id:e,type:"full-data",value:i}},logElementToConsole:function(e){var t=ot(e)?nt:tt(e);if(null!==t){var n="function"==typeof console.groupCollapsed;n&&console.groupCollapsed("[Click to expand] %c<".concat(t.displayName||"Component"," />"),"color: var(--dom-tag-name-color); font-weight: normal;"),null!==t.props&&console.log("Props:",t.props),null!==t.state&&console.log("State:",t.state),null!==t.hooks&&console.log("Hooks:",t.hooks);var r=Je(e);null!==r&&console.log("Nodes:",r),null!==t.source&&console.log("Location:",t.source),(window.chrome||/firefox/i.test(navigator.userAgent))&&console.log("Right-click any value to save it as a global variable for further inspection."),n&&console.groupEnd()}else console.warn('Could not find Fiber with id "'.concat(e,'"'))},prepareViewAttributeSource:function(e,t){ot(e)&&(window.$attribute=de(nt,t))},prepareViewElementSource:function(e){var t=ie.get(e);if(null!=t){var n=t.elementType,i=t.tag,o=t.type;switch(i){case v:case S:case C:case h:r.$type=o;break;case _:r.$type=o.render;break;case k:case x:r.$type=null!=n&&null!=n.type?n.type:o;break;default:r.$type=null}}else console.warn('Could not find Fiber with id "'.concat(e,'"'))},overrideSuspense:function(e,t){if("function"!=typeof H||"function"!=typeof V)throw new Error("Expected overrideSuspense() to not get called for earlier React versions.");t?(wt.add(e),1===wt.size&&H(Et)):(wt.delete(e),0===wt.size&&H(bt));var n=ie.get(e);null!=n&&V(n)},overrideValueAtPath:function(e,t,n,r,i){var o=et(t);if(null!==o){var u=o.stateNode;switch(e){case"context":switch(r=r.slice(1),o.tag){case v:0===r.length?u.context=i:ve(u.context,r,i),u.forceUpdate()}break;case"hooks":"function"==typeof L&&L(o,n,r,i);break;case"props":switch(o.tag){case v:o.pendingProps=Ae(u.props,r,i),u.forceUpdate();break;default:"function"==typeof U&&U(o,r,i)}break;case"state":switch(o.tag){case v:ve(u.state,r,i),u.forceUpdate()}}}},renamePath:function(e,t,n,r,i){var o=et(t);if(null!==o){var u=o.stateNode;switch(e){case"context":switch(r=r.slice(1),i=i.slice(1),o.tag){case v:0===r.length||he(u.context,r,i),u.forceUpdate()}break;case"hooks":"function"==typeof j&&j(o,n,r,i);break;case"props":null===u?"function"==typeof W&&W(o,r,i):(o.pendingProps=xe(u.props,r,i),u.forceUpdate());break;case"state":he(u.state,r,i),u.forceUpdate()}}},renderer:n,setTraceUpdatesEnabled:function(e){Q=e},setTrackedPath:Tt,startProfiling:_t,stopProfiling:function(){vt=!1,gt=!1},storeAsGlobal:function(e,t,n){if(ot(e)){var r=de(nt,t),i="$reactTemp".concat(n);window[i]=r,console.log(i),console.log(r)}},updateComponentFilters:function(n){if(vt)throw Error("Cannot modify filter preferences while profiling");e.getFiberRoots(t).forEach((function(e){le=se(ne(e.current)),Ye(e.current),Ge(e.current,!1),le=-1})),Z(n),At.clear(),e.getFiberRoots(t).forEach((function(e){Ot(le=se(ne(e.current)),e.current),$e(e.current,null,!1,!1),Ve(),le=-1}))}}}function qe(e){return(qe="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function Ge(e,t,n){if(void 0===ze)try{throw Error()}catch(e){var r=e.stack.trim().match(/\n( *(at )?)/);ze=r&&r[1]||""}return"\n"+ze+e}var $e=!1;function Ye(e,t,n){if(!e||$e)return"";var r,i=Error.prepareStackTrace;Error.prepareStackTrace=void 0,$e=!0;var o=n.current;n.current=null;try{if(t){var u=function(){throw Error()};if(Object.defineProperty(u.prototype,"props",{set:function(){throw Error()}}),"object"===("undefined"==typeof Reflect?"undefined":qe(Reflect))&&Reflect.construct){try{Reflect.construct(u,[])}catch(e){r=e}Reflect.construct(e,[],u)}else{try{u.call()}catch(e){r=e}e.call(u.prototype)}}else{try{throw Error()}catch(e){r=e}e()}}catch(e){if(e&&r&&"string"==typeof e.stack){for(var a=e.stack.split("\n"),l=r.stack.split("\n"),s=a.length-1,c=l.length-1;s>=1&&c>=0&&a[s]!==l[c];)c--;for(;s>=1&&c>=0;s--,c--)if(a[s]!==l[c]){if(1!==s||1!==c)do{if(s--,--c<0||a[s]!==l[c])return"\n"+a[s].replace(" at new "," at ")}while(s>=1&&c>=0);break}}}finally{$e=!1,Error.prepareStackTrace=i,n.current=o}var f=e?e.displayName||e.name:"";return f?Ge(f):""}function Ke(e,t,n,r){return Ye(e,!1,r)}function Xe(e,t,n){var r=e.HostComponent,i=e.LazyComponent,o=e.SuspenseComponent,u=e.SuspenseListComponent,a=e.FunctionComponent,l=e.IndeterminateComponent,s=e.SimpleMemoComponent,c=e.ForwardRef,f=e.Block,d=e.ClassComponent;switch(t.tag){case r:return Ge(t.type);case i:return Ge("Lazy");case o:return Ge("Suspense");case u:return Ge("SuspenseList");case a:case l:case s:return Ke(t.type,0,0,n);case c:return Ke(t.type.render,0,0,n);case f:return Ke(t.type._render,0,0,n);case d:return function(e,t,n,r){return Ye(e,!0,r)}(t.type,0,0,n);default:return""}}function Qe(e,t,n){try{var r="",i=t;do{r+=Xe(e,i,n),i=i.return}while(i);return r}catch(e){return"\nError generating stack: "+e.message+"\n"+e.stack}}function Je(e,t){var n;if("undefined"==typeof Symbol||null==e[Symbol.iterator]){if(Array.isArray(e)||(n=function(e,t){if(e){if("string"==typeof e)return Ze(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?Ze(e,t):void 0}}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0,i=function(){};return{s:i,n:function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:i}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,u=!0,a=!1;return{s:function(){n=e[Symbol.iterator]()},n:function(){var e=n.next();return u=e.done,e},e:function(e){a=!0,o=e},f:function(){try{u||null==n.return||n.return()}finally{if(a)throw o}}}}function Ze(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0?r[r.length-1]:null,u=null!==o&&(tt.test(o)||nt.test(o));if(!u){var a,l=Je(rt.values());try{for(l.s();!(a=l.n()).done;){var s=a.value,c=s.currentDispatcherRef,f=s.getCurrentFiber,d=s.workTagMap,p=f();if(null!=p){var h=Qe(d,p,c);""!==h&&r.push(h);break}}}catch(e){l.e(e)}finally{l.f()}}}catch(e){}t.apply(void 0,r)};n.__REACT_DEVTOOLS_ORIGINAL_METHOD__=t,it[e]=n}catch(e){}}))}}function ft(e){return(ft="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function dt(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:d,n=void 0,r=[],i=void 0,o=!1,u=function(e,n){return t(e,r[n])},a=function(){for(var t=arguments.length,a=Array(t),l=0;le.length)&&(t=e.length);for(var n=0,r=new Array(t);n1?t-1:0),r=1;r0?"development":"production";var t=Function.prototype.toString;if(e.Mount&&e.Mount._renderNewRootComponent){var n=t.call(e.Mount._renderNewRootComponent);return 0!==n.indexOf("function")?"production":-1!==n.indexOf("storedMeasure")?"development":-1!==n.indexOf("should be a pure function")?-1!==n.indexOf("NODE_ENV")||-1!==n.indexOf("development")||-1!==n.indexOf("true")?"development":-1!==n.indexOf("nextElement")||-1!==n.indexOf("nextComponent")?"unminified":"development":-1!==n.indexOf("nextElement")||-1!==n.indexOf("nextComponent")?"unminified":"outdated"}}catch(e){}return"production"}(r);try{var l=!1!==window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__,s=!0===window.__REACT_DEVTOOLS_BREAK_ON_CONSOLE_ERRORS__;(l||s)&&(lt(r),ct({appendComponentStack:l,breakOnConsoleErrors:s}))}catch(e){}var c=e.__REACT_DEVTOOLS_ATTACH__;if("function"==typeof c){var f=c(a,i,r,e);a.rendererInterfaces.set(i,f)}return a.emit("renderer",{id:i,renderer:r,reactBuildType:o}),i},on:function(e,t){o[e]||(o[e]=[]),o[e].push(t)},off:function(e,t){if(o[e]){var n=o[e].indexOf(t);-1!==n&&o[e].splice(n,1),o[e].length||delete o[e]}},sub:function(e,t){return a.on(e,t),function(){return a.off(e,t)}},supportsFiber:!0,checkDCE:function(e){try{Function.prototype.toString.call(e).indexOf("^_^")>-1&&(n=!0,setTimeout((function(){throw new Error("React is running in production mode, but dead code elimination has not been applied. Read how to correctly configure React for production: https://reactjs.org/link/perf-use-production-build")})))}catch(e){}},onCommitFiberUnmount:function(e,t){var n=i.get(e);null!=n&&n.handleCommitFiberUnmount(t)},onCommitFiberRoot:function(e,t,n){var r=a.getFiberRoots(e),o=t.current,u=r.has(t),l=null==o.memoizedState||null==o.memoizedState.element;u||l?u&&l&&r.delete(t):r.add(t);var s=i.get(e);null!=s&&s.handleCommitFiberRoot(t,n)}};Object.defineProperty(e,"__REACT_DEVTOOLS_GLOBAL_HOOK__",{configurable:!1,enumerable:!1,get:function(){return a}})}(window);var $t=window.__REACT_DEVTOOLS_GLOBAL_HOOK__,Yt=[{type:1,value:7,isEnabled:!0}];function Kt(e){if(null!=$t){var t=e||{},n=t.host,r=void 0===n?"localhost":n,i=t.nativeStyleEditorValidAttributes,o=t.useHttps,u=void 0!==o&&o,a=t.port,l=void 0===a?8097:a,s=t.websocket,c=t.resolveRNStyle,f=void 0===c?null:c,d=t.isAppActive,p=u?"wss":"ws",h=null;if((void 0===d?function(){return!0}:d)()){var v=null,m=[],g=p+"://"+r+":"+l,y=s||new window.WebSocket(g);y.onclose=function(){null!==v&&v.emit("shutdown"),_()},y.onerror=function(){_()},y.onmessage=function(e){var t;try{if("string"!=typeof e.data)throw Error();t=JSON.parse(e.data)}catch(t){return void console.error("[React DevTools] Failed to parse JSON: "+e.data)}m.forEach((function(e){try{e(t)}catch(e){throw console.log("[React DevTools] Error calling listener",t),console.log("error:",e),e}}))},y.onopen=function(){(v=new xt({listen:function(e){return m.push(e),function(){var t=m.indexOf(e);t>=0&&m.splice(t,1)}},send:function(e,t,n){y.readyState===y.OPEN?y.send(JSON.stringify({event:e,payload:t})):(null!==v&&v.shutdown(),_())}})).addListener("inspectElement",(function(t){var n=t.id,r=t.rendererID,i=e.rendererInterfaces[r];if(null!=i){var o=i.findNativeNodesForFiberID(n);null!=o&&null!=o[0]&&e.emit("showNativeHighlight",o[0])}})),v.addListener("updateComponentFilters",(function(e){Yt=e})),null==window.__REACT_DEVTOOLS_COMPONENT_FILTERS__&&v.send("overrideComponentFilters",Yt);var e=new yt(v);if(e.addListener("shutdown",(function(){$t.emit("shutdown")})),function(e,t,n){if(null==e)return function(){};var r=[e.sub("renderer-attached",(function(e){var n=e.id,r=(e.renderer,e.rendererInterface);t.setRendererInterface(n,r),r.flushInitialOperations()})),e.sub("unsupported-renderer-version",(function(e){t.onUnsupportedRenderer(e)})),e.sub("operations",t.onHookOperations),e.sub("traceUpdates",t.onTraceUpdates)],i=function(t,r){var i=e.rendererInterfaces.get(t);null==i&&("function"==typeof r.findFiberByHostInstance?i=Ve(e,t,r,n):r.ComponentTree&&(i=function(e,t,n,r){var i,o=new Map,u=new WeakMap,a=new WeakMap,l=null;function s(e){if("object"!==Rt(e)||null===e)throw new Error("Invalid internal instance: "+e);if(!u.has(e)){var t=ce();u.set(e,t),o.set(t,e)}return u.get(e)}function c(e,t){if(e.length!==t.length)return!1;for(var n=0;n0?f[f.length-1]:0),f.push(i),a.set(n,s(r._topLevelWrapper));try{var o=e.apply(this,t);return f.pop(),o}catch(e){throw f=[],e}finally{if(0===f.length){var u=a.get(n);if(void 0===u)throw new Error("Expected to find root ID.");w(u)}}},performUpdateIfNecessary:function(e,t){var n=t[0];if(9===Lt(n))return e.apply(this,t);var r=s(n);f.push(r);var i=Bt(n);try{var o=e.apply(this,t),u=Bt(n);return c(i,u)||h(0,r,u),f.pop(),o}catch(e){throw f=[],e}finally{if(0===f.length){var l=a.get(n);if(void 0===l)throw new Error("Expected to find root ID.");w(l)}}},receiveComponent:function(e,t){var n=t[0];if(9===Lt(n))return e.apply(this,t);var r=s(n);f.push(r);var i=Bt(n);try{var o=e.apply(this,t),u=Bt(n);return c(i,u)||h(0,r,u),f.pop(),o}catch(e){throw f=[],e}finally{if(0===f.length){var l=a.get(n);if(void 0===l)throw new Error("Expected to find root ID.");w(l)}}},unmountComponent:function(e,t){var n=t[0];if(9===Lt(n))return e.apply(this,t);var r=s(n);f.push(r);try{var i=e.apply(this,t);return f.pop(),function(e,t){y.push(t),o.delete(t)}(0,r),i}catch(e){throw f=[],e}finally{if(0===f.length){var u=a.get(n);if(void 0===u)throw new Error("Expected to find root ID.");w(u)}}}}));var m=[],g=new Map,y=[],_=0,b=null;function w(n){if(0!==m.length||0!==y.length||null!==b){var r=y.length+(null===b?0:1),i=new Array(3+_+(r>0?2+r:0)+m.length),o=0;if(i[o++]=t,i[o++]=n,i[o++]=_,g.forEach((function(e,t){i[o++]=t.length;for(var n=fe(t),r=0;r0){i[o++]=2,i[o++]=r;for(var u=0;u"),"color: var(--dom-tag-name-color); font-weight: normal;"),null!==t.props&&console.log("Props:",t.props),null!==t.state&&console.log("State:",t.state),null!==t.context&&console.log("Context:",t.context);var r=i(e);null!==r&&console.log("Node:",r),(window.chrome||/firefox/i.test(navigator.userAgent))&&console.log("Right-click any value to save it as a global variable for further inspection."),n&&console.groupEnd()}else console.warn('Could not find element with id "'.concat(e,'"'))},overrideSuspense:function(){throw new Error("overrideSuspense not supported by this renderer")},overrideValueAtPath:function(e,t,n,r,i){var u=o.get(t);if(null!=u){var a=u._instance;if(null!=a)switch(e){case"context":ve(a.context,r,i),Pt(a);break;case"hooks":throw new Error("Hooks not supported by this renderer");case"props":var l=u._currentElement;u._currentElement=Nt(Nt({},l),{},{props:Ae(l.props,r,i)}),Pt(a);break;case"state":ve(a.state,r,i),Pt(a)}}},renamePath:function(e,t,n,r,i){var u=o.get(t);if(null!=u){var a=u._instance;if(null!=a)switch(e){case"context":he(a.context,r,i),Pt(a);break;case"hooks":throw new Error("Hooks not supported by this renderer");case"props":var l=u._currentElement;u._currentElement=Nt(Nt({},l),{},{props:xe(l.props,r,i)}),Pt(a);break;case"state":he(a.state,r,i),Pt(a)}}},prepareViewAttributeSource:function(e,t){var n=T(e);null!==n&&(window.$attribute=de(n,t))},prepareViewElementSource:function(e){var t=o.get(e);if(null!=t){var n=t._currentElement;null!=n?r.$type=n.type:console.warn('Could not find element with id "'.concat(e,'"'))}else console.warn('Could not find instance with id "'.concat(e,'"'))},renderer:n,setTraceUpdatesEnabled:function(e){},setTrackedPath:function(e){},startProfiling:function(){},stopProfiling:function(){},storeAsGlobal:function(e,t,n){var r=T(e);if(null!==r){var i=de(r,t),o="$reactTemp".concat(n);window[o]=i,console.log(o),console.log(i)}},updateComponentFilters:function(e){}}}(e,t,r,n)),null!=i&&e.rendererInterfaces.set(t,i)),null!=i?e.emit("renderer-attached",{id:t,renderer:r,rendererInterface:i}):e.emit("unsupported-renderer-version",t)};e.renderers.forEach((function(e,t){i(t,e)})),r.push(e.sub("renderer",(function(e){var t=e.id,n=e.renderer;i(t,n)}))),e.emit("react-devtools",t),e.reactDevtoolsAgent=t;var o=function(){r.forEach((function(e){return e()})),e.rendererInterfaces.forEach((function(e){e.cleanup()})),e.reactDevtoolsAgent=null};t.addListener("shutdown",o),r.push((function(){t.removeListener("shutdown",o)}))}($t,e,window),null!=f||null!=$t.resolveRNStyle)Wt(v,e,f||$t.resolveRNStyle,i||$t.nativeStyleEditorValidAttributes||null);else{var t,n,r=function(){null!==v&&Wt(v,e,t,n)};$t.hasOwnProperty("resolveRNStyle")||Object.defineProperty($t,"resolveRNStyle",{enumerable:!1,get:function(){return t},set:function(e){t=e,r()}}),$t.hasOwnProperty("nativeStyleEditorValidAttributes")||Object.defineProperty($t,"nativeStyleEditorValidAttributes",{enumerable:!1,get:function(){return n},set:function(e){n=e,r()}})}}}else _()}function _(){null===h&&(h=setTimeout((function(){return Kt(e)}),2e3))}}}])},6099:(e,t,n)=>{"use strict"; /** @license React v16.13.1 * react.production.min.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */var r=n(9381),i="function"==typeof Symbol&&Symbol.for,o=i?Symbol.for("react.element"):60103,u=i?Symbol.for("react.portal"):60106,a=i?Symbol.for("react.fragment"):60107,l=i?Symbol.for("react.strict_mode"):60108,s=i?Symbol.for("react.profiler"):60114,c=i?Symbol.for("react.provider"):60109,f=i?Symbol.for("react.context"):60110,d=i?Symbol.for("react.forward_ref"):60112,p=i?Symbol.for("react.suspense"):60113,h=i?Symbol.for("react.memo"):60115,v=i?Symbol.for("react.lazy"):60116,m="function"==typeof Symbol&&Symbol.iterator;function g(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;nO.length&&O.push(e)}function N(e,t,n){return null==e?0:function e(t,n,r,i){var a=typeof t;"undefined"!==a&&"boolean"!==a||(t=null);var l=!1;if(null===t)l=!0;else switch(a){case"string":case"number":l=!0;break;case"object":switch(t.$$typeof){case o:case u:l=!0}}if(l)return r(i,t,""===n?"."+M(t,0):n),1;if(l=0,n=""===n?".":n+":",Array.isArray(t))for(var s=0;s{"use strict";e.exports=n(6099)},3390:(e,t,n)=>{"use strict";const r=n(834),i=n(6458);e.exports=r(()=>{i(()=>{process.stderr.write("[?25h")},{alwaysLast:!0})})},706:(e,t)=>{"use strict"; /** @license React v0.18.0 * scheduler.production.min.js * * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */var n,r,i,o,u;if(Object.defineProperty(t,"__esModule",{value:!0}),"undefined"==typeof window||"function"!=typeof MessageChannel){var a=null,l=null,s=function(){if(null!==a)try{var e=t.unstable_now();a(!0,e),a=null}catch(e){throw setTimeout(s,0),e}},c=Date.now();t.unstable_now=function(){return Date.now()-c},n=function(e){null!==a?setTimeout(n,0,e):(a=e,setTimeout(s,0))},r=function(e,t){l=setTimeout(e,t)},i=function(){clearTimeout(l)},o=function(){return!1},u=t.unstable_forceFrameRate=function(){}}else{var f=window.performance,d=window.Date,p=window.setTimeout,h=window.clearTimeout;if("undefined"!=typeof console){var v=window.cancelAnimationFrame;"function"!=typeof window.requestAnimationFrame&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),"function"!=typeof v&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills")}if("object"==typeof f&&"function"==typeof f.now)t.unstable_now=function(){return f.now()};else{var m=d.now();t.unstable_now=function(){return d.now()-m}}var g=!1,y=null,_=-1,b=5,w=0;o=function(){return t.unstable_now()>=w},u=function(){},t.unstable_forceFrameRate=function(e){0>e||125T(u,n))void 0!==l&&0>T(l,u)?(e[r]=l,e[a]=n,r=a):(e[r]=u,e[o]=n,r=o);else{if(!(void 0!==l&&0>T(l,n)))break e;e[r]=l,e[a]=n,r=a}}}return t}return null}function T(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}var x=[],A=[],O=1,P=null,I=3,N=!1,M=!1,R=!1;function F(e){for(var t=C(A);null!==t;){if(null===t.callback)k(A);else{if(!(t.startTime<=e))break;k(A),t.sortIndex=t.expirationTime,S(x,t)}t=C(A)}}function L(e){if(R=!1,F(e),!M)if(null!==C(x))M=!0,n(B);else{var t=C(A);null!==t&&r(L,t.startTime-e)}}function B(e,n){M=!1,R&&(R=!1,i()),N=!0;var u=I;try{for(F(n),P=C(x);null!==P&&(!(P.expirationTime>n)||e&&!o());){var a=P.callback;if(null!==a){P.callback=null,I=P.priorityLevel;var l=a(P.expirationTime<=n);n=t.unstable_now(),"function"==typeof l?P.callback=l:P===C(x)&&k(x),F(n)}else k(x);P=C(x)}if(null!==P)var s=!0;else{var c=C(A);null!==c&&r(L,c.startTime-n),s=!1}return s}finally{P=null,I=u,N=!1}}function j(e){switch(e){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1e4;default:return 5e3}}var U=u;t.unstable_ImmediatePriority=1,t.unstable_UserBlockingPriority=2,t.unstable_NormalPriority=3,t.unstable_IdlePriority=5,t.unstable_LowPriority=4,t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=I;I=e;try{return t()}finally{I=n}},t.unstable_next=function(e){switch(I){case 1:case 2:case 3:var t=3;break;default:t=I}var n=I;I=t;try{return e()}finally{I=n}},t.unstable_scheduleCallback=function(e,o,u){var a=t.unstable_now();if("object"==typeof u&&null!==u){var l=u.delay;l="number"==typeof l&&0a?(e.sortIndex=l,S(A,e),null===C(x)&&e===C(A)&&(R?i():R=!0,r(L,l-a))):(e.sortIndex=u,S(x,e),M||N||(M=!0,n(B))),e},t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_wrapCallback=function(e){var t=I;return function(){var n=I;I=t;try{return e.apply(this,arguments)}finally{I=n}}},t.unstable_getCurrentPriorityLevel=function(){return I},t.unstable_shouldYield=function(){var e=t.unstable_now();F(e);var n=C(x);return n!==P&&null!==P&&null!==n&&null!==n.callback&&n.startTime<=e&&n.expirationTime{"use strict";e.exports=n(706)},6458:(e,t,n)=>{var r,i=n(2357),o=n(8082),u=n(8614);function a(){c&&(c=!1,o.forEach((function(e){try{process.removeListener(e,s[e])}catch(e){}})),process.emit=h,process.reallyExit=d,r.count-=1)}function l(e,t,n){r.emitted[e]||(r.emitted[e]=!0,r.emit(e,t,n))}"function"!=typeof u&&(u=u.EventEmitter),process.__signal_exit_emitter__?r=process.__signal_exit_emitter__:((r=process.__signal_exit_emitter__=new u).count=0,r.emitted={}),r.infinite||(r.setMaxListeners(1/0),r.infinite=!0),e.exports=function(e,t){i.equal(typeof e,"function","a callback must be provided for exit handler"),!1===c&&f();var n="exit";t&&t.alwaysLast&&(n="afterexit");return r.on(n,e),function(){r.removeListener(n,e),0===r.listeners("exit").length&&0===r.listeners("afterexit").length&&a()}},e.exports.unload=a;var s={};o.forEach((function(e){s[e]=function(){process.listeners(e).length===r.count&&(a(),l("exit",null,e),l("afterexit",null,e),process.kill(process.pid,e))}})),e.exports.signals=function(){return o},e.exports.load=f;var c=!1;function f(){c||(c=!0,r.count+=1,o=o.filter((function(e){try{return process.on(e,s[e]),!0}catch(e){return!1}})),process.emit=v,process.reallyExit=p)}var d=process.reallyExit;function p(e){process.exitCode=e||0,l("exit",process.exitCode,null),l("afterexit",process.exitCode,null),d.call(process,process.exitCode)}var h=process.emit;function v(e,t){if("exit"===e){void 0!==t&&(process.exitCode=t);var n=h.apply(this,arguments);return l("exit",process.exitCode,null),l("afterexit",process.exitCode,null),n}return h.apply(this,arguments)}},8082:e=>{e.exports=["SIGABRT","SIGALRM","SIGHUP","SIGINT","SIGTERM"],"win32"!==process.platform&&e.exports.push("SIGVTALRM","SIGXCPU","SIGXFSZ","SIGUSR2","SIGTRAP","SIGSYS","SIGQUIT","SIGIOT"),"linux"===process.platform&&e.exports.push("SIGIO","SIGPOLL","SIGPWR","SIGSTKFLT","SIGUNUSED")},1566:(e,t,n)=>{"use strict";const r=n(7347),i=n(409),o=n(8483),u=["","›"],a=e=>`${u[0]}[${e}m`,l=(e,t,n)=>{let r=[];e=[...e];for(let n of e){const i=n;n.match(";")&&(n=n.split(";")[0][0]+"0");const u=o.codes.get(parseInt(n,10));if(u){const n=e.indexOf(u.toString());n>=0?e.splice(n,1):r.push(a(t?u:i))}else{if(t){r.push(a(0));break}r.push(a(i))}}if(t&&(r=r.filter((e,t)=>r.indexOf(e)===t),void 0!==n)){const e=a(o.codes.get(parseInt(n,10)));r=r.reduce((t,n)=>n===e?[n,...t]:[...t,n],[])}return r.join("")};e.exports=(e,t,n)=>{const o=[...e.normalize()],a=[];n="number"==typeof n?n:o.length;let s,c=!1,f=0,d="";for(const[p,h]of o.entries()){let o=!1;if(u.includes(h)){const t=/\d[^m]*/.exec(e.slice(p,p+18));s=t&&t.length>0?t[0]:void 0,ft&&f<=n)d+=h;else if(f!==t||c||void 0===s){if(f>=n){d+=l(a,!0,s);break}}else d=l(a)}return d}},9796:(e,t,n)=>{"use strict";const r=n(8759),i=[].concat(n(2282).builtinModules,"bootstrap_node","node").map(e=>new RegExp(`(?:\\(${e}\\.js:\\d+:\\d+\\)$|^\\s*at ${e}\\.js:\\d+:\\d+$)`));i.push(/\(internal\/[^:]+:\d+:\d+\)$/,/\s*at internal\/[^:]+:\d+:\d+$/,/\/\.node-spawn-wrap-\w+-\w+\/node:\d+:\d+\)?$/);class o{constructor(e){"internals"in(e={ignoredPackages:[],...e})==!1&&(e.internals=o.nodeInternals()),"cwd"in e==!1&&(e.cwd=process.cwd()),this._cwd=e.cwd.replace(/\\/g,"/"),this._internals=[].concat(e.internals,function(e){if(0===e.length)return[];const t=e.map(e=>r(e));return new RegExp(`[/\\\\]node_modules[/\\\\](?:${t.join("|")})[/\\\\][^:]+:\\d+:\\d+`)}(e.ignoredPackages)),this._wrapCallSite=e.wrapCallSite||!1}static nodeInternals(){return[...i]}clean(e,t=0){t=" ".repeat(t),Array.isArray(e)||(e=e.split("\n")),!/^\s*at /.test(e[0])&&/^\s*at /.test(e[1])&&(e=e.slice(1));let n=!1,r=null;const i=[];return e.forEach(e=>{if(e=e.replace(/\\/g,"/"),this._internals.some(t=>t.test(e)))return;const t=/^\s*at /.test(e);n?e=e.trimEnd().replace(/^(\s+)at /,"$1"):(e=e.trim(),t&&(e=e.slice(3))),(e=e.replace(this._cwd+"/",""))&&(t?(r&&(i.push(r),r=null),i.push(e)):(n=!0,r=e))}),i.map(e=>`${t}${e}\n`).join("")}captureString(e,t=this.captureString){"function"==typeof e&&(t=e,e=1/0);const{stackTraceLimit:n}=Error;e&&(Error.stackTraceLimit=e);const r={};Error.captureStackTrace(r,t);const{stack:i}=r;return Error.stackTraceLimit=n,this.clean(i)}capture(e,t=this.capture){"function"==typeof e&&(t=e,e=1/0);const{prepareStackTrace:n,stackTraceLimit:r}=Error;Error.prepareStackTrace=(e,t)=>this._wrapCallSite?t.map(this._wrapCallSite):t,e&&(Error.stackTraceLimit=e);const i={};Error.captureStackTrace(i,t);const{stack:o}=i;return Object.assign(Error,{prepareStackTrace:n,stackTraceLimit:r}),o}at(e=this.at){const[t]=this.capture(1,e);if(!t)return{};const n={line:t.getLineNumber(),column:t.getColumnNumber()};let r;u(n,t.getFileName(),this._cwd),t.isConstructor()&&(n.constructor=!0),t.isEval()&&(n.evalOrigin=t.getEvalOrigin()),t.isNative()&&(n.native=!0);try{r=t.getTypeName()}catch(e){}r&&"Object"!==r&&"[object Object]"!==r&&(n.type=r);const i=t.getFunctionName();i&&(n.function=i);const o=t.getMethodName();return o&&i!==o&&(n.method=o),n}parseLine(e){const t=e&&e.match(a);if(!t)return null;const n="new"===t[1];let r=t[2];const i=t[3],o=t[4],s=Number(t[5]),c=Number(t[6]);let f=t[7];const d=t[8],p=t[9],h="native"===t[10],v=")"===t[11];let m;const g={};if(d&&(g.line=Number(d)),p&&(g.column=Number(p)),v&&f){let e=0;for(let t=f.length-1;t>0;t--)if(")"===f.charAt(t))e++;else if("("===f.charAt(t)&&" "===f.charAt(t-1)&&(e--,-1===e&&" "===f.charAt(t-1))){const e=f.slice(0,t-1),n=f.slice(t+1);f=n,r+=" ("+e;break}}if(r){const e=r.match(l);e&&(r=e[1],m=e[2])}return u(g,f,this._cwd),n&&(g.constructor=!0),i&&(g.evalOrigin=i,g.evalLine=s,g.evalColumn=c,g.evalFile=o&&o.replace(/\\/g,"/")),h&&(g.native=!0),r&&(g.function=r),m&&r!==m&&(g.method=m),g}}function u(e,t,n){t&&((t=t.replace(/\\/g,"/")).startsWith(n+"/")&&(t=t.slice(n.length+1)),e.file=t)}const a=new RegExp("^(?:\\s*at )?(?:(new) )?(?:(.*?) \\()?(?:eval at ([^ ]+) \\((.+?):(\\d+):(\\d+)\\), )?(?:(.+?):(\\d+):(\\d+)|(native))(\\)?)$"),l=/^(.*?) \[as (.*?)\]$/;e.exports=o},3262:(e,t,n)=>{"use strict";const r=n(7402),i=n(5640),o=e=>r(e).replace(i()," ").length;e.exports=o,e.exports.default=o},5043:(e,t,n)=>{"use strict";const r=n(7915),i=n(7347),o=n(1013),u=e=>{if("string"!=typeof(e=e.replace(o()," "))||0===e.length)return 0;e=r(e);let t=0;for(let n=0;n=127&&r<=159||(r>=768&&r<=879||(r>65535&&n++,t+=i(r)?2:1))}return t};e.exports=u,e.exports.default=u},7402:(e,t,n)=>{"use strict";const r=n(5378),i=e=>"string"==typeof e?e.replace(r(),""):e;e.exports=i,e.exports.default=i},7915:(e,t,n)=>{"use strict";const r=n(1337);e.exports=e=>"string"==typeof e?e.replace(r(),""):e},9428:(e,t,n)=>{"use strict";const r=n(2087),i=n(3867),o=n(2918),{env:u}=process;let a;function l(e){return 0!==e&&{level:e,hasBasic:!0,has256:e>=2,has16m:e>=3}}function s(e,t){if(0===a)return 0;if(o("color=16m")||o("color=full")||o("color=truecolor"))return 3;if(o("color=256"))return 2;if(e&&!t&&void 0===a)return 0;const n=a||0;if("dumb"===u.TERM)return n;if("win32"===process.platform){const e=r.release().split(".");return Number(e[0])>=10&&Number(e[2])>=10586?Number(e[2])>=14931?3:2:1}if("CI"in u)return["TRAVIS","CIRCLECI","APPVEYOR","GITLAB_CI"].some(e=>e in u)||"codeship"===u.CI_NAME?1:n;if("TEAMCITY_VERSION"in u)return/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(u.TEAMCITY_VERSION)?1:0;if("GITHUB_ACTIONS"in u)return 1;if("truecolor"===u.COLORTERM)return 3;if("TERM_PROGRAM"in u){const e=parseInt((u.TERM_PROGRAM_VERSION||"").split(".")[0],10);switch(u.TERM_PROGRAM){case"iTerm.app":return e>=3?3:2;case"Apple_Terminal":return 2}}return/-256(color)?$/i.test(u.TERM)?2:/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(u.TERM)||"COLORTERM"in u?1:n}o("no-color")||o("no-colors")||o("color=false")||o("color=never")?a=0:(o("color")||o("colors")||o("color=true")||o("color=always"))&&(a=1),"FORCE_COLOR"in u&&(a="true"===u.FORCE_COLOR?1:"false"===u.FORCE_COLOR?0:0===u.FORCE_COLOR.length?1:Math.min(parseInt(u.FORCE_COLOR,10),3)),e.exports={supportsColor:function(e){return l(s(e,e&&e.isTTY))},stdout:l(s(!0,i.isatty(1))),stderr:l(s(!0,i.isatty(2)))}},8949:(e,t,n)=>{"use strict";const r=n(5043),i=e=>{let t=0;for(const n of e.split("\n"))t=Math.max(t,r(n));return t};e.exports=i,e.exports.default=i},4332:(e,t,n)=>{"use strict";const r=n(5043),i=n(7915),o=n(8483),u=new Set(["","›"]),a=e=>`${u.values().next().value}[${e}m`,l=(e,t,n)=>{const o=[...t];let a=!1,l=r(i(e[e.length-1]));for(const[t,i]of o.entries()){const s=r(i);if(l+s<=n?e[e.length-1]+=i:(e.push(i),l=0),u.has(i))a=!0;else if(a&&"m"===i){a=!1;continue}a||(l+=s,l===n&&t0&&e.length>1&&(e[e.length-2]+=e.pop())},s=e=>{const t=e.split(" ");let n=t.length;for(;n>0&&!(r(t[n-1])>0);)n--;return n===t.length?e:t.slice(0,n).join(" ")+t.slice(n).join("")},c=(e,t,n={})=>{if(!1!==n.trim&&""===e.trim())return"";let i,c="",f="";const d=(e=>e.split(" ").map(e=>r(e)))(e);let p=[""];for(const[i,o]of e.split(" ").entries()){!1!==n.trim&&(p[p.length-1]=p[p.length-1].trimLeft());let e=r(p[p.length-1]);if(0!==i&&(e>=t&&(!1===n.wordWrap||!1===n.trim)&&(p.push(""),e=0),(e>0||!1===n.trim)&&(p[p.length-1]+=" ",e++)),n.hard&&d[i]>t){const n=t-e,r=1+Math.floor((d[i]-n-1)/t);Math.floor((d[i]-1)/t)t&&e>0&&d[i]>0){if(!1===n.wordWrap&&et&&!1===n.wordWrap?l(p,o,t):p[p.length-1]+=o}}!1!==n.trim&&(p=p.map(s)),c=p.join("\n");for(const[e,t]of[...c].entries()){if(f+=t,u.has(t)){const t=parseFloat(/\d[^m]*/.exec(c.slice(e,e+4)));i=39===t?null:t}const n=o.codes.get(Number(i));i&&n&&("\n"===c[e+1]?f+=a(n):"\n"===t&&(f+=a(i)))}return f};e.exports=(e,t,n)=>String(e).normalize().replace(/\r\n/g,"\n").split("\n").map(e=>c(e,t,n)).join("\n")},3354:function(module,exports){var __WEBPACK_AMD_DEFINE_ARRAY__,__WEBPACK_AMD_DEFINE_RESULT__,wrapper;wrapper=function(Module,cb){var Module;"function"==typeof Module&&(cb=Module,Module={}),Module.onRuntimeInitialized=function(e,t){return function(){e&&e.apply(this,arguments);try{Module.ccall("nbind_init")}catch(e){return void t(e)}t(null,{bind:Module._nbind_value,reflect:Module.NBind.reflect,queryType:Module.NBind.queryType,toggleLightGC:Module.toggleLightGC,lib:Module})}}(Module.onRuntimeInitialized,cb),Module||(Module=(void 0!==Module?Module:null)||{});var moduleOverrides={};for(var key in Module)Module.hasOwnProperty(key)&&(moduleOverrides[key]=Module[key]);var ENVIRONMENT_IS_WEB=!1,ENVIRONMENT_IS_WORKER=!1,ENVIRONMENT_IS_NODE=!1,ENVIRONMENT_IS_SHELL=!1,nodeFS,nodePath;if(Module.ENVIRONMENT)if("WEB"===Module.ENVIRONMENT)ENVIRONMENT_IS_WEB=!0;else if("WORKER"===Module.ENVIRONMENT)ENVIRONMENT_IS_WORKER=!0;else if("NODE"===Module.ENVIRONMENT)ENVIRONMENT_IS_NODE=!0;else{if("SHELL"!==Module.ENVIRONMENT)throw new Error("The provided Module['ENVIRONMENT'] value is not valid. It must be one of: WEB|WORKER|NODE|SHELL.");ENVIRONMENT_IS_SHELL=!0}else ENVIRONMENT_IS_WEB="object"==typeof window,ENVIRONMENT_IS_WORKER="function"==typeof importScripts,ENVIRONMENT_IS_NODE="object"==typeof process&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER,ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;if(ENVIRONMENT_IS_NODE)Module.print||(Module.print=console.log),Module.printErr||(Module.printErr=console.warn),Module.read=function(e,t){nodeFS||(nodeFS={}("")),nodePath||(nodePath={}("")),e=nodePath.normalize(e);var n=nodeFS.readFileSync(e);return t?n:n.toString()},Module.readBinary=function(e){var t=Module.read(e,!0);return t.buffer||(t=new Uint8Array(t)),assert(t.buffer),t},Module.load=function(e){globalEval(read(e))},Module.thisProgram||(process.argv.length>1?Module.thisProgram=process.argv[1].replace(/\\/g,"/"):Module.thisProgram="unknown-program"),Module.arguments=process.argv.slice(2),module.exports=Module,Module.inspect=function(){return"[Emscripten Module object]"};else if(ENVIRONMENT_IS_SHELL)Module.print||(Module.print=print),"undefined"!=typeof printErr&&(Module.printErr=printErr),"undefined"!=typeof read?Module.read=read:Module.read=function(){throw"no read() available"},Module.readBinary=function(e){if("function"==typeof readbuffer)return new Uint8Array(readbuffer(e));var t=read(e,"binary");return assert("object"==typeof t),t},"undefined"!=typeof scriptArgs?Module.arguments=scriptArgs:void 0!==arguments&&(Module.arguments=arguments),"function"==typeof quit&&(Module.quit=function(e,t){quit(e)});else{if(!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER)throw"Unknown runtime environment. Where are we?";if(Module.read=function(e){var t=new XMLHttpRequest;return t.open("GET",e,!1),t.send(null),t.responseText},ENVIRONMENT_IS_WORKER&&(Module.readBinary=function(e){var t=new XMLHttpRequest;return t.open("GET",e,!1),t.responseType="arraybuffer",t.send(null),new Uint8Array(t.response)}),Module.readAsync=function(e,t,n){var r=new XMLHttpRequest;r.open("GET",e,!0),r.responseType="arraybuffer",r.onload=function(){200==r.status||0==r.status&&r.response?t(r.response):n()},r.onerror=n,r.send(null)},void 0!==arguments&&(Module.arguments=arguments),"undefined"!=typeof console)Module.print||(Module.print=function(e){console.log(e)}),Module.printErr||(Module.printErr=function(e){console.warn(e)});else{var TRY_USE_DUMP=!1;Module.print||(Module.print=TRY_USE_DUMP&&"undefined"!=typeof dump?function(e){dump(e)}:function(e){})}ENVIRONMENT_IS_WORKER&&(Module.load=importScripts),void 0===Module.setWindowTitle&&(Module.setWindowTitle=function(e){document.title=e})}function globalEval(e){eval.call(null,e)}for(var key in!Module.load&&Module.read&&(Module.load=function(e){globalEval(Module.read(e))}),Module.print||(Module.print=function(){}),Module.printErr||(Module.printErr=Module.print),Module.arguments||(Module.arguments=[]),Module.thisProgram||(Module.thisProgram="./this.program"),Module.quit||(Module.quit=function(e,t){throw t}),Module.print=Module.print,Module.printErr=Module.printErr,Module.preRun=[],Module.postRun=[],moduleOverrides)moduleOverrides.hasOwnProperty(key)&&(Module[key]=moduleOverrides[key]);moduleOverrides=void 0;var Runtime={setTempRet0:function(e){return tempRet0=e,e},getTempRet0:function(){return tempRet0},stackSave:function(){return STACKTOP},stackRestore:function(e){STACKTOP=e},getNativeTypeSize:function(e){switch(e){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:if("*"===e[e.length-1])return Runtime.QUANTUM_SIZE;if("i"===e[0]){var t=parseInt(e.substr(1));return assert(t%8==0),t/8}return 0}},getNativeFieldSize:function(e){return Math.max(Runtime.getNativeTypeSize(e),Runtime.QUANTUM_SIZE)},STACK_ALIGN:16,prepVararg:function(e,t){return"double"===t||"i64"===t?7&e&&(assert(4==(7&e)),e+=4):assert(0==(3&e)),e},getAlignSize:function(e,t,n){return n||"i64"!=e&&"double"!=e?e?Math.min(t||(e?Runtime.getNativeFieldSize(e):0),Runtime.QUANTUM_SIZE):Math.min(t,8):8},dynCall:function(e,t,n){return n&&n.length?Module["dynCall_"+e].apply(null,[t].concat(n)):Module["dynCall_"+e].call(null,t)},functionPointers:[],addFunction:function(e){for(var t=0;t>2],n=-16&(t+e+15|0);return HEAP32[DYNAMICTOP_PTR>>2]=n,n>=TOTAL_MEMORY&&!enlargeMemory()?(HEAP32[DYNAMICTOP_PTR>>2]=t,0):t},alignMemory:function(e,t){return e=Math.ceil(e/(t||16))*(t||16)},makeBigInt:function(e,t,n){return n?+(e>>>0)+4294967296*+(t>>>0):+(e>>>0)+4294967296*+(0|t)},GLOBAL_BASE:8,QUANTUM_SIZE:4,__dummy__:0};Module.Runtime=Runtime;var ABORT=0,EXITSTATUS=0,cwrap,ccall;function assert(e,t){e||abort("Assertion failed: "+t)}function getCFunc(ident){var func=Module["_"+ident];if(!func)try{func=eval("_"+ident)}catch(e){}return assert(func,"Cannot call unknown function "+ident+" (perhaps LLVM optimizations or closure removed it?)"),func}function setValue(e,t,n,r){switch("*"===(n=n||"i8").charAt(n.length-1)&&(n="i32"),n){case"i1":case"i8":HEAP8[e>>0]=t;break;case"i16":HEAP16[e>>1]=t;break;case"i32":HEAP32[e>>2]=t;break;case"i64":tempI64=[t>>>0,(tempDouble=t,+Math_abs(tempDouble)>=1?tempDouble>0?(0|Math_min(+Math_floor(tempDouble/4294967296),4294967295))>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[e>>2]=tempI64[0],HEAP32[e+4>>2]=tempI64[1];break;case"float":HEAPF32[e>>2]=t;break;case"double":HEAPF64[e>>3]=t;break;default:abort("invalid type for setValue: "+n)}}function getValue(e,t,n){switch("*"===(t=t||"i8").charAt(t.length-1)&&(t="i32"),t){case"i1":case"i8":return HEAP8[e>>0];case"i16":return HEAP16[e>>1];case"i32":case"i64":return HEAP32[e>>2];case"float":return HEAPF32[e>>2];case"double":return HEAPF64[e>>3];default:abort("invalid type for setValue: "+t)}return null}!function(){var JSfuncs={stackSave:function(){Runtime.stackSave()},stackRestore:function(){Runtime.stackRestore()},arrayToC:function(e){var t=Runtime.stackAlloc(e.length);return writeArrayToMemory(e,t),t},stringToC:function(e){var t=0;if(null!=e&&0!==e){var n=1+(e.length<<2);stringToUTF8(e,t=Runtime.stackAlloc(n),n)}return t}},toC={string:JSfuncs.stringToC,array:JSfuncs.arrayToC};ccall=function(e,t,n,r,i){var o=getCFunc(e),u=[],a=0;if(r)for(var l=0;l>2]=0;for(l=u+o;r>0]=0;return u}if("i8"===a)return e.subarray||e.slice?HEAPU8.set(e,u):HEAPU8.set(new Uint8Array(e),u),u;for(var s,c,f,d=0;d>0],(0!=n||t)&&(i++,!t||i!=t););t||(t=i);var o="";if(r<128){for(var u;t>0;)u=String.fromCharCode.apply(String,HEAPU8.subarray(e,e+Math.min(t,1024))),o=o?o+u:u,e+=1024,t-=1024;return o}return Module.UTF8ToString(e)}function AsciiToString(e){for(var t="";;){var n=HEAP8[e++>>0];if(!n)return t;t+=String.fromCharCode(n)}}function stringToAscii(e,t){return writeAsciiToMemory(e,t,!1)}Module.ALLOC_NORMAL=ALLOC_NORMAL,Module.ALLOC_STACK=ALLOC_STACK,Module.ALLOC_STATIC=ALLOC_STATIC,Module.ALLOC_DYNAMIC=ALLOC_DYNAMIC,Module.ALLOC_NONE=ALLOC_NONE,Module.allocate=allocate,Module.getMemory=getMemory,Module.Pointer_stringify=Pointer_stringify,Module.AsciiToString=AsciiToString,Module.stringToAscii=stringToAscii;var UTF8Decoder="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function UTF8ArrayToString(e,t){for(var n=t;e[n];)++n;if(n-t>16&&e.subarray&&UTF8Decoder)return UTF8Decoder.decode(e.subarray(t,n));for(var r,i,o,u,a,l="";;){if(!(r=e[t++]))return l;if(128&r)if(i=63&e[t++],192!=(224&r))if(o=63&e[t++],224==(240&r)?r=(15&r)<<12|i<<6|o:(u=63&e[t++],240==(248&r)?r=(7&r)<<18|i<<12|o<<6|u:(a=63&e[t++],r=248==(252&r)?(3&r)<<24|i<<18|o<<12|u<<6|a:(1&r)<<30|i<<24|o<<18|u<<12|a<<6|63&e[t++])),r<65536)l+=String.fromCharCode(r);else{var s=r-65536;l+=String.fromCharCode(55296|s>>10,56320|1023&s)}else l+=String.fromCharCode((31&r)<<6|i);else l+=String.fromCharCode(r)}}function UTF8ToString(e){return UTF8ArrayToString(HEAPU8,e)}function stringToUTF8Array(e,t,n,r){if(!(r>0))return 0;for(var i=n,o=n+r-1,u=0;u=55296&&a<=57343&&(a=65536+((1023&a)<<10)|1023&e.charCodeAt(++u)),a<=127){if(n>=o)break;t[n++]=a}else if(a<=2047){if(n+1>=o)break;t[n++]=192|a>>6,t[n++]=128|63&a}else if(a<=65535){if(n+2>=o)break;t[n++]=224|a>>12,t[n++]=128|a>>6&63,t[n++]=128|63&a}else if(a<=2097151){if(n+3>=o)break;t[n++]=240|a>>18,t[n++]=128|a>>12&63,t[n++]=128|a>>6&63,t[n++]=128|63&a}else if(a<=67108863){if(n+4>=o)break;t[n++]=248|a>>24,t[n++]=128|a>>18&63,t[n++]=128|a>>12&63,t[n++]=128|a>>6&63,t[n++]=128|63&a}else{if(n+5>=o)break;t[n++]=252|a>>30,t[n++]=128|a>>24&63,t[n++]=128|a>>18&63,t[n++]=128|a>>12&63,t[n++]=128|a>>6&63,t[n++]=128|63&a}}return t[n]=0,n-i}function stringToUTF8(e,t,n){return stringToUTF8Array(e,HEAPU8,t,n)}function lengthBytesUTF8(e){for(var t=0,n=0;n=55296&&r<=57343&&(r=65536+((1023&r)<<10)|1023&e.charCodeAt(++n)),r<=127?++t:t+=r<=2047?2:r<=65535?3:r<=2097151?4:r<=67108863?5:6}return t}Module.UTF8ArrayToString=UTF8ArrayToString,Module.UTF8ToString=UTF8ToString,Module.stringToUTF8Array=stringToUTF8Array,Module.stringToUTF8=stringToUTF8,Module.lengthBytesUTF8=lengthBytesUTF8;var UTF16Decoder="undefined"!=typeof TextDecoder?new TextDecoder("utf-16le"):void 0,HEAP,buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64,STATIC_BASE,STATICTOP,staticSealed,STACK_BASE,STACKTOP,STACK_MAX,DYNAMIC_BASE,DYNAMICTOP_PTR;function demangle(e){var t=Module.___cxa_demangle||Module.__cxa_demangle;if(t){try{var n=e.substr(1),r=lengthBytesUTF8(n)+1,i=_malloc(r);stringToUTF8(n,i,r);var o=_malloc(4),u=t(i,0,0,o);if(0===getValue(o,"i32")&&u)return Pointer_stringify(u)}catch(e){}finally{i&&_free(i),o&&_free(o),u&&_free(u)}return e}return Runtime.warnOnce("warning: build with -s DEMANGLE_SUPPORT=1 to link in libcxxabi demangling"),e}function demangleAll(e){return e.replace(/__Z[\w\d_]+/g,(function(e){var t=demangle(e);return e===t?e:e+" ["+t+"]"}))}function jsStackTrace(){var e=new Error;if(!e.stack){try{throw new Error(0)}catch(t){e=t}if(!e.stack)return"(no stack trace available)"}return e.stack.toString()}function stackTrace(){var e=jsStackTrace();return Module.extraStackTrace&&(e+="\n"+Module.extraStackTrace()),demangleAll(e)}function updateGlobalBufferViews(){Module.HEAP8=HEAP8=new Int8Array(buffer),Module.HEAP16=HEAP16=new Int16Array(buffer),Module.HEAP32=HEAP32=new Int32Array(buffer),Module.HEAPU8=HEAPU8=new Uint8Array(buffer),Module.HEAPU16=HEAPU16=new Uint16Array(buffer),Module.HEAPU32=HEAPU32=new Uint32Array(buffer),Module.HEAPF32=HEAPF32=new Float32Array(buffer),Module.HEAPF64=HEAPF64=new Float64Array(buffer)}function abortOnCannotGrowMemory(){abort("Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value "+TOTAL_MEMORY+", (2) compile with -s ALLOW_MEMORY_GROWTH=1 which allows increasing the size at runtime but prevents some optimizations, (3) set Module.TOTAL_MEMORY to a higher value before the program runs, or (4) if you want malloc to return NULL (0) instead of this abort, compile with -s ABORTING_MALLOC=0 ")}function enlargeMemory(){abortOnCannotGrowMemory()}Module.stackTrace=stackTrace,STATIC_BASE=STATICTOP=STACK_BASE=STACKTOP=STACK_MAX=DYNAMIC_BASE=DYNAMICTOP_PTR=0,staticSealed=!1;var TOTAL_STACK=Module.TOTAL_STACK||5242880,TOTAL_MEMORY=Module.TOTAL_MEMORY||134217728;function getTotalMemory(){return TOTAL_MEMORY}if(TOTAL_MEMORY0;){var t=e.shift();if("function"!=typeof t){var n=t.func;"number"==typeof n?void 0===t.arg?Module.dynCall_v(n):Module.dynCall_vi(n,t.arg):n(void 0===t.arg?null:t.arg)}else t()}}Module.HEAP=HEAP,Module.buffer=buffer,Module.HEAP8=HEAP8,Module.HEAP16=HEAP16,Module.HEAP32=HEAP32,Module.HEAPU8=HEAPU8,Module.HEAPU16=HEAPU16,Module.HEAPU32=HEAPU32,Module.HEAPF32=HEAPF32,Module.HEAPF64=HEAPF64;var __ATPRERUN__=[],__ATINIT__=[],__ATMAIN__=[],__ATEXIT__=[],__ATPOSTRUN__=[],runtimeInitialized=!1,runtimeExited=!1;function preRun(){if(Module.preRun)for("function"==typeof Module.preRun&&(Module.preRun=[Module.preRun]);Module.preRun.length;)addOnPreRun(Module.preRun.shift());callRuntimeCallbacks(__ATPRERUN__)}function ensureInitRuntime(){runtimeInitialized||(runtimeInitialized=!0,callRuntimeCallbacks(__ATINIT__))}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){callRuntimeCallbacks(__ATEXIT__),runtimeExited=!0}function postRun(){if(Module.postRun)for("function"==typeof Module.postRun&&(Module.postRun=[Module.postRun]);Module.postRun.length;)addOnPostRun(Module.postRun.shift());callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(e){__ATPRERUN__.unshift(e)}function addOnInit(e){__ATINIT__.unshift(e)}function addOnPreMain(e){__ATMAIN__.unshift(e)}function addOnExit(e){__ATEXIT__.unshift(e)}function addOnPostRun(e){__ATPOSTRUN__.unshift(e)}function intArrayFromString(e,t,n){var r=n>0?n:lengthBytesUTF8(e)+1,i=new Array(r),o=stringToUTF8Array(e,i,0,i.length);return t&&(i.length=o),i}function intArrayToString(e){for(var t=[],n=0;n255&&(r&=255),t.push(String.fromCharCode(r))}return t.join("")}function writeStringToMemory(e,t,n){var r,i;Runtime.warnOnce("writeStringToMemory is deprecated and should not be called! Use stringToUTF8() instead!"),n&&(i=t+lengthBytesUTF8(e),r=HEAP8[i]),stringToUTF8(e,t,1/0),n&&(HEAP8[i]=r)}function writeArrayToMemory(e,t){HEAP8.set(e,t)}function writeAsciiToMemory(e,t,n){for(var r=0;r>0]=e.charCodeAt(r);n||(HEAP8[t>>0]=0)}if(Module.addOnPreRun=addOnPreRun,Module.addOnInit=addOnInit,Module.addOnPreMain=addOnPreMain,Module.addOnExit=addOnExit,Module.addOnPostRun=addOnPostRun,Module.intArrayFromString=intArrayFromString,Module.intArrayToString=intArrayToString,Module.writeStringToMemory=writeStringToMemory,Module.writeArrayToMemory=writeArrayToMemory,Module.writeAsciiToMemory=writeAsciiToMemory,Math.imul&&-5===Math.imul(4294967295,5)||(Math.imul=function(e,t){var n=65535&e,r=65535&t;return n*r+((e>>>16)*r+n*(t>>>16)<<16)|0}),Math.imul=Math.imul,!Math.fround){var froundBuffer=new Float32Array(1);Math.fround=function(e){return froundBuffer[0]=e,froundBuffer[0]}}Math.fround=Math.fround,Math.clz32||(Math.clz32=function(e){e>>>=0;for(var t=0;t<32;t++)if(e&1<<31-t)return t;return 32}),Math.clz32=Math.clz32,Math.trunc||(Math.trunc=function(e){return e<0?Math.ceil(e):Math.floor(e)}),Math.trunc=Math.trunc;var Math_abs=Math.abs,Math_cos=Math.cos,Math_sin=Math.sin,Math_tan=Math.tan,Math_acos=Math.acos,Math_asin=Math.asin,Math_atan=Math.atan,Math_atan2=Math.atan2,Math_exp=Math.exp,Math_log=Math.log,Math_sqrt=Math.sqrt,Math_ceil=Math.ceil,Math_floor=Math.floor,Math_pow=Math.pow,Math_imul=Math.imul,Math_fround=Math.fround,Math_round=Math.round,Math_min=Math.min,Math_clz32=Math.clz32,Math_trunc=Math.trunc,runDependencies=0,runDependencyWatcher=null,dependenciesFulfilled=null;function getUniqueRunDependency(e){return e}function addRunDependency(e){runDependencies++,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies)}function removeRunDependency(e){if(runDependencies--,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies),0==runDependencies&&(null!==runDependencyWatcher&&(clearInterval(runDependencyWatcher),runDependencyWatcher=null),dependenciesFulfilled)){var t=dependenciesFulfilled;dependenciesFulfilled=null,t()}}Module.addRunDependency=addRunDependency,Module.removeRunDependency=removeRunDependency,Module.preloadedImages={},Module.preloadedAudios={};var ASM_CONSTS=[function(e,t,n,r,i,o,u,a){return _nbind.callbackSignatureList[e].apply(this,arguments)}];function _emscripten_asm_const_iiiiiiii(e,t,n,r,i,o,u,a){return ASM_CONSTS[e](t,n,r,i,o,u,a)}function _emscripten_asm_const_iiiii(e,t,n,r,i){return ASM_CONSTS[e](t,n,r,i)}function _emscripten_asm_const_iiidddddd(e,t,n,r,i,o,u,a,l){return ASM_CONSTS[e](t,n,r,i,o,u,a,l)}function _emscripten_asm_const_iiididi(e,t,n,r,i,o,u){return ASM_CONSTS[e](t,n,r,i,o,u)}function _emscripten_asm_const_iiii(e,t,n,r){return ASM_CONSTS[e](t,n,r)}function _emscripten_asm_const_iiiid(e,t,n,r,i){return ASM_CONSTS[e](t,n,r,i)}function _emscripten_asm_const_iiiiii(e,t,n,r,i,o){return ASM_CONSTS[e](t,n,r,i,o)}STATIC_BASE=Runtime.GLOBAL_BASE,STATICTOP=STATIC_BASE+12800,__ATINIT__.push({func:function(){__GLOBAL__sub_I_Yoga_cpp()}},{func:function(){__GLOBAL__sub_I_nbind_cc()}},{func:function(){__GLOBAL__sub_I_common_cc()}},{func:function(){__GLOBAL__sub_I_Binding_cc()}}),allocate([0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,192,127,0,0,192,127,3,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,3,0,0,0,0,0,192,127,3,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,0,0,128,191,0,0,128,191,0,0,192,127,0,0,0,0,0,0,0,0,0,0,128,63,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,3,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,190,12,0,0,200,12,0,0,208,12,0,0,216,12,0,0,230,12,0,0,242,12,0,0,1,0,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,192,127,3,0,0,0,180,45,0,0,181,45,0,0,182,45,0,0,181,45,0,0,182,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,3,0,0,0,1,0,0,0,4,0,0,0,183,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,184,45,0,0,185,45,0,0,181,45,0,0,181,45,0,0,182,45,0,0,186,45,0,0,185,45,0,0,148,4,0,0,3,0,0,0,187,45,0,0,164,4,0,0,188,45,0,0,2,0,0,0,189,45,0,0,164,4,0,0,188,45,0,0,185,45,0,0,164,4,0,0,185,45,0,0,164,4,0,0,188,45,0,0,181,45,0,0,182,45,0,0,181,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,5,0,0,0,6,0,0,0,1,0,0,0,7,0,0,0,183,45,0,0,182,45,0,0,181,45,0,0,190,45,0,0,190,45,0,0,182,45,0,0,182,45,0,0,185,45,0,0,181,45,0,0,185,45,0,0,182,45,0,0,181,45,0,0,185,45,0,0,182,45,0,0,185,45,0,0,48,5,0,0,3,0,0,0,56,5,0,0,1,0,0,0,189,45,0,0,185,45,0,0,164,4,0,0,76,5,0,0,2,0,0,0,191,45,0,0,186,45,0,0,182,45,0,0,185,45,0,0,192,45,0,0,185,45,0,0,182,45,0,0,186,45,0,0,185,45,0,0,76,5,0,0,76,5,0,0,136,5,0,0,182,45,0,0,181,45,0,0,2,0,0,0,190,45,0,0,136,5,0,0,56,19,0,0,156,5,0,0,2,0,0,0,184,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,8,0,0,0,9,0,0,0,1,0,0,0,10,0,0,0,204,5,0,0,181,45,0,0,181,45,0,0,2,0,0,0,180,45,0,0,204,5,0,0,2,0,0,0,195,45,0,0,236,5,0,0,97,19,0,0,198,45,0,0,211,45,0,0,212,45,0,0,213,45,0,0,214,45,0,0,215,45,0,0,188,45,0,0,182,45,0,0,216,45,0,0,217,45,0,0,218,45,0,0,219,45,0,0,192,45,0,0,181,45,0,0,0,0,0,0,185,45,0,0,110,19,0,0,186,45,0,0,115,19,0,0,221,45,0,0,120,19,0,0,148,4,0,0,132,19,0,0,96,6,0,0,145,19,0,0,222,45,0,0,164,19,0,0,223,45,0,0,173,19,0,0,0,0,0,0,3,0,0,0,104,6,0,0,1,0,0,0,187,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,11,0,0,0,12,0,0,0,1,0,0,0,13,0,0,0,185,45,0,0,224,45,0,0,164,6,0,0,188,45,0,0,172,6,0,0,180,6,0,0,2,0,0,0,188,6,0,0,7,0,0,0,224,45,0,0,7,0,0,0,164,6,0,0,1,0,0,0,213,45,0,0,185,45,0,0,224,45,0,0,172,6,0,0,185,45,0,0,224,45,0,0,164,6,0,0,185,45,0,0,224,45,0,0,211,45,0,0,211,45,0,0,222,45,0,0,211,45,0,0,224,45,0,0,222,45,0,0,211,45,0,0,224,45,0,0,172,6,0,0,222,45,0,0,211,45,0,0,224,45,0,0,188,45,0,0,222,45,0,0,211,45,0,0,40,7,0,0,188,45,0,0,2,0,0,0,224,45,0,0,185,45,0,0,188,45,0,0,188,45,0,0,188,45,0,0,188,45,0,0,222,45,0,0,224,45,0,0,148,4,0,0,185,45,0,0,148,4,0,0,148,4,0,0,148,4,0,0,148,4,0,0,148,4,0,0,185,45,0,0,164,6,0,0,148,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,14,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,148,7,0,0,2,0,0,0,225,45,0,0,183,45,0,0,188,45,0,0,168,7,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,234,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,148,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,9,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,2,0,0,0,242,45,0,0,0,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,67,111,117,108,100,32,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,32,102,111,114,32,110,111,100,101,0,67,97,110,110,111,116,32,114,101,115,101,116,32,97,32,110,111,100,101,32,119,104,105,99,104,32,115,116,105,108,108,32,104,97,115,32,99,104,105,108,100,114,101,110,32,97,116,116,97,99,104,101,100,0,67,97,110,110,111,116,32,114,101,115,101,116,32,97,32,110,111,100,101,32,115,116,105,108,108,32,97,116,116,97,99,104,101,100,32,116,111,32,97,32,112,97,114,101,110,116,0,67,111,117,108,100,32,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,32,102,111,114,32,99,111,110,102,105,103,0,67,97,110,110,111,116,32,115,101,116,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,58,32,78,111,100,101,115,32,119,105,116,104,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,32,99,97,110,110,111,116,32,104,97,118,101,32,99,104,105,108,100,114,101,110,46,0,67,104,105,108,100,32,97,108,114,101,97,100,121,32,104,97,115,32,97,32,112,97,114,101,110,116,44,32,105,116,32,109,117,115,116,32,98,101,32,114,101,109,111,118,101,100,32,102,105,114,115,116,46,0,67,97,110,110,111,116,32,97,100,100,32,99,104,105,108,100,58,32,78,111,100,101,115,32,119,105,116,104,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,32,99,97,110,110,111,116,32,104,97,118,101,32,99,104,105,108,100,114,101,110,46,0,79,110,108,121,32,108,101,97,102,32,110,111,100,101,115,32,119,105,116,104,32,99,117,115,116,111,109,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,115,104,111,117,108,100,32,109,97,110,117,97,108,108,121,32,109,97,114,107,32,116,104,101,109,115,101,108,118,101,115,32,97,115,32,100,105,114,116,121,0,67,97,110,110,111,116,32,103,101,116,32,108,97,121,111,117,116,32,112,114,111,112,101,114,116,105,101,115,32,111,102,32,109,117,108,116,105,45,101,100,103,101,32,115,104,111,114,116,104,97,110,100,115,0,37,115,37,100,46,123,91,115,107,105,112,112,101,100,93,32,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,97,119,58,32,37,102,32,97,104,58,32,37,102,32,61,62,32,100,58,32,40,37,102,44,32,37,102,41,32,37,115,10,0,37,115,37,100,46,123,37,115,0,42,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,97,119,58,32,37,102,32,97,104,58,32,37,102,32,37,115,10,0,37,115,37,100,46,125,37,115,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,100,58,32,40,37,102,44,32,37,102,41,32,37,115,10,0,79,117,116,32,111,102,32,99,97,99,104,101,32,101,110,116,114,105,101,115,33,10,0,83,99,97,108,101,32,102,97,99,116,111,114,32,115,104,111,117,108,100,32,110,111,116,32,98,101,32,108,101,115,115,32,116,104,97,110,32,122,101,114,111,0,105,110,105,116,105,97,108,0,37,115,10,0,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,0,85,78,68,69,70,73,78,69,68,0,69,88,65,67,84,76,89,0,65,84,95,77,79,83,84,0,76,65,89,95,85,78,68,69,70,73,78,69,68,0,76,65,89,95,69,88,65,67,84,76,89,0,76,65,89,95,65,84,95,77,79,83,84,0,97,118,97,105,108,97,98,108,101,87,105,100,116,104,32,105,115,32,105,110,100,101,102,105,110,105,116,101,32,115,111,32,119,105,100,116,104,77,101,97,115,117,114,101,77,111,100,101,32,109,117,115,116,32,98,101,32,89,71,77,101,97,115,117,114,101,77,111,100,101,85,110,100,101,102,105,110,101,100,0,97,118,97,105,108,97,98,108,101,72,101,105,103,104,116,32,105,115,32,105,110,100,101,102,105,110,105,116,101,32,115,111,32,104,101,105,103,104,116,77,101,97,115,117,114,101,77,111,100,101,32,109,117,115,116,32,98,101,32,89,71,77,101,97,115,117,114,101,77,111,100,101,85,110,100,101,102,105,110,101,100,0,102,108,101,120,0,115,116,114,101,116,99,104,0,109,117,108,116,105,108,105,110,101,45,115,116,114,101,116,99,104,0,69,120,112,101,99,116,101,100,32,110,111,100,101,32,116,111,32,104,97,118,101,32,99,117,115,116,111,109,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,0,109,101,97,115,117,114,101,0,69,120,112,101,99,116,32,99,117,115,116,111,109,32,98,97,115,101,108,105,110,101,32,102,117,110,99,116,105,111,110,32,116,111,32,110,111,116,32,114,101,116,117,114,110,32,78,97,78,0,97,98,115,45,109,101,97,115,117,114,101,0,97,98,115,45,108,97,121,111,117,116,0,78,111,100,101,0,99,114,101,97,116,101,68,101,102,97,117,108,116,0,99,114,101,97,116,101,87,105,116,104,67,111,110,102,105,103,0,100,101,115,116,114,111,121,0,114,101,115,101,116,0,99,111,112,121,83,116,121,108,101,0,115,101,116,80,111,115,105,116,105,111,110,84,121,112,101,0,115,101,116,80,111,115,105,116,105,111,110,0,115,101,116,80,111,115,105,116,105,111,110,80,101,114,99,101,110,116,0,115,101,116,65,108,105,103,110,67,111,110,116,101,110,116,0,115,101,116,65,108,105,103,110,73,116,101,109,115,0,115,101,116,65,108,105,103,110,83,101,108,102,0,115,101,116,70,108,101,120,68,105,114,101,99,116,105,111,110,0,115,101,116,70,108,101,120,87,114,97,112,0,115,101,116,74,117,115,116,105,102,121,67,111,110,116,101,110,116,0,115,101,116,77,97,114,103,105,110,0,115,101,116,77,97,114,103,105,110,80,101,114,99,101,110,116,0,115,101,116,77,97,114,103,105,110,65,117,116,111,0,115,101,116,79,118,101,114,102,108,111,119,0,115,101,116,68,105,115,112,108,97,121,0,115,101,116,70,108,101,120,0,115,101,116,70,108,101,120,66,97,115,105,115,0,115,101,116,70,108,101,120,66,97,115,105,115,80,101,114,99,101,110,116,0,115,101,116,70,108,101,120,71,114,111,119,0,115,101,116,70,108,101,120,83,104,114,105,110,107,0,115,101,116,87,105,100,116,104,0,115,101,116,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,87,105,100,116,104,65,117,116,111,0,115,101,116,72,101,105,103,104,116,0,115,101,116,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,72,101,105,103,104,116,65,117,116,111,0,115,101,116,77,105,110,87,105,100,116,104,0,115,101,116,77,105,110,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,77,105,110,72,101,105,103,104,116,0,115,101,116,77,105,110,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,77,97,120,87,105,100,116,104,0,115,101,116,77,97,120,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,77,97,120,72,101,105,103,104,116,0,115,101,116,77,97,120,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,65,115,112,101,99,116,82,97,116,105,111,0,115,101,116,66,111,114,100,101,114,0,115,101,116,80,97,100,100,105,110,103,0,115,101,116,80,97,100,100,105,110,103,80,101,114,99,101,110,116,0,103,101,116,80,111,115,105,116,105,111,110,84,121,112,101,0,103,101,116,80,111,115,105,116,105,111,110,0,103,101,116,65,108,105,103,110,67,111,110,116,101,110,116,0,103,101,116,65,108,105,103,110,73,116,101,109,115,0,103,101,116,65,108,105,103,110,83,101,108,102,0,103,101,116,70,108,101,120,68,105,114,101,99,116,105,111,110,0,103,101,116,70,108,101,120,87,114,97,112,0,103,101,116,74,117,115,116,105,102,121,67,111,110,116,101,110,116,0,103,101,116,77,97,114,103,105,110,0,103,101,116,70,108,101,120,66,97,115,105,115,0,103,101,116,70,108,101,120,71,114,111,119,0,103,101,116,70,108,101,120,83,104,114,105,110,107,0,103,101,116,87,105,100,116,104,0,103,101,116,72,101,105,103,104,116,0,103,101,116,77,105,110,87,105,100,116,104,0,103,101,116,77,105,110,72,101,105,103,104,116,0,103,101,116,77,97,120,87,105,100,116,104,0,103,101,116,77,97,120,72,101,105,103,104,116,0,103,101,116,65,115,112,101,99,116,82,97,116,105,111,0,103,101,116,66,111,114,100,101,114,0,103,101,116,79,118,101,114,102,108,111,119,0,103,101,116,68,105,115,112,108,97,121,0,103,101,116,80,97,100,100,105,110,103,0,105,110,115,101,114,116,67,104,105,108,100,0,114,101,109,111,118,101,67,104,105,108,100,0,103,101,116,67,104,105,108,100,67,111,117,110,116,0,103,101,116,80,97,114,101,110,116,0,103,101,116,67,104,105,108,100,0,115,101,116,77,101,97,115,117,114,101,70,117,110,99,0,117,110,115,101,116,77,101,97,115,117,114,101,70,117,110,99,0,109,97,114,107,68,105,114,116,121,0,105,115,68,105,114,116,121,0,99,97,108,99,117,108,97,116,101,76,97,121,111,117,116,0,103,101,116,67,111,109,112,117,116,101,100,76,101,102,116,0,103,101,116,67,111,109,112,117,116,101,100,82,105,103,104,116,0,103,101,116,67,111,109,112,117,116,101,100,84,111,112,0,103,101,116,67,111,109,112,117,116,101,100,66,111,116,116,111,109,0,103,101,116,67,111,109,112,117,116,101,100,87,105,100,116,104,0,103,101,116,67,111,109,112,117,116,101,100,72,101,105,103,104,116,0,103,101,116,67,111,109,112,117,116,101,100,76,97,121,111,117,116,0,103,101,116,67,111,109,112,117,116,101,100,77,97,114,103,105,110,0,103,101,116,67,111,109,112,117,116,101,100,66,111,114,100,101,114,0,103,101,116,67,111,109,112,117,116,101,100,80,97,100,100,105,110,103,0,67,111,110,102,105,103,0,99,114,101,97,116,101,0,115,101,116,69,120,112,101,114,105,109,101,110,116,97,108,70,101,97,116,117,114,101,69,110,97,98,108,101,100,0,115,101,116,80,111,105,110,116,83,99,97,108,101,70,97,99,116,111,114,0,105,115,69,120,112,101,114,105,109,101,110,116,97,108,70,101,97,116,117,114,101,69,110,97,98,108,101,100,0,86,97,108,117,101,0,76,97,121,111,117,116,0,83,105,122,101,0,103,101,116,73,110,115,116,97,110,99,101,67,111,117,110,116,0,73,110,116,54,52,0,1,1,1,2,2,4,4,4,4,8,8,4,8,118,111,105,100,0,98,111,111,108,0,115,116,100,58,58,115,116,114,105,110,103,0,99,98,70,117,110,99,116,105,111,110,32,38,0,99,111,110,115,116,32,99,98,70,117,110,99,116,105,111,110,32,38,0,69,120,116,101,114,110,97,108,0,66,117,102,102,101,114,0,78,66,105,110,100,73,68,0,78,66,105,110,100,0,98,105,110,100,95,118,97,108,117,101,0,114,101,102,108,101,99,116,0,113,117,101,114,121,84,121,112,101,0,108,97,108,108,111,99,0,108,114,101,115,101,116,0,123,114,101,116,117,114,110,40,95,110,98,105,110,100,46,99,97,108,108,98,97,99,107,83,105,103,110,97,116,117,114,101,76,105,115,116,91,36,48,93,46,97,112,112,108,121,40,116,104,105,115,44,97,114,103,117,109,101,110,116,115,41,41,59,125,0,95,110,98,105,110,100,95,110,101,119,0,17,0,10,0,17,17,17,0,0,0,0,5,0,0,0,0,0,0,9,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,15,10,17,17,17,3,10,7,0,1,19,9,11,11,0,0,9,6,11,0,0,11,0,6,17,0,0,0,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,10,10,17,17,17,0,10,0,0,2,0,9,11,0,0,0,9,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,13,0,0,0,4,13,0,0,0,0,9,14,0,0,0,0,0,14,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,15,0,0,0,0,9,16,0,0,0,0,0,16,0,0,16,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,10,0,0,0,0,9,11,0,0,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,45,43,32,32,32,48,88,48,120,0,40,110,117,108,108,41,0,45,48,88,43,48,88,32,48,88,45,48,120,43,48,120,32,48,120,0,105,110,102,0,73,78,70,0,110,97,110,0,78,65,78,0,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,46,0,84,33,34,25,13,1,2,3,17,75,28,12,16,4,11,29,18,30,39,104,110,111,112,113,98,32,5,6,15,19,20,21,26,8,22,7,40,36,23,24,9,10,14,27,31,37,35,131,130,125,38,42,43,60,61,62,63,67,71,74,77,88,89,90,91,92,93,94,95,96,97,99,100,101,102,103,105,106,107,108,114,115,116,121,122,123,124,0,73,108,108,101,103,97,108,32,98,121,116,101,32,115,101,113,117,101,110,99,101,0,68,111,109,97,105,110,32,101,114,114,111,114,0,82,101,115,117,108,116,32,110,111,116,32,114,101,112,114,101,115,101,110,116,97,98,108,101,0,78,111,116,32,97,32,116,116,121,0,80,101,114,109,105,115,115,105,111,110,32,100,101,110,105,101,100,0,79,112,101,114,97,116,105,111,110,32,110,111,116,32,112,101,114,109,105,116,116,101,100,0,78,111,32,115,117,99,104,32,102,105,108,101,32,111,114,32,100,105,114,101,99,116,111,114,121,0,78,111,32,115,117,99,104,32,112,114,111,99,101,115,115,0,70,105,108,101,32,101,120,105,115,116,115,0,86,97,108,117,101,32,116,111,111,32,108,97,114,103,101,32,102,111,114,32,100,97,116,97,32,116,121,112,101,0,78,111,32,115,112,97,99,101,32,108,101,102,116,32,111,110,32,100,101,118,105,99,101,0,79,117,116,32,111,102,32,109,101,109,111,114,121,0,82,101,115,111,117,114,99,101,32,98,117,115,121,0,73,110,116,101,114,114,117,112,116,101,100,32,115,121,115,116,101,109,32,99,97,108,108,0,82,101,115,111,117,114,99,101,32,116,101,109,112,111,114,97,114,105,108,121,32,117,110,97,118,97,105,108,97,98,108,101,0,73,110,118,97,108,105,100,32,115,101,101,107,0,67,114,111,115,115,45,100,101,118,105,99,101,32,108,105,110,107,0,82,101,97,100,45,111,110,108,121,32,102,105,108,101,32,115,121,115,116,101,109,0,68,105,114,101,99,116,111,114,121,32,110,111,116,32,101,109,112,116,121,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,112,101,101,114,0,79,112,101,114,97,116,105,111,110,32,116,105,109,101,100,32,111,117,116,0,67,111,110,110,101,99,116,105,111,110,32,114,101,102,117,115,101,100,0,72,111,115,116,32,105,115,32,100,111,119,110,0,72,111,115,116,32,105,115,32,117,110,114,101,97,99,104,97,98,108,101,0,65,100,100,114,101,115,115,32,105,110,32,117,115,101,0,66,114,111,107,101,110,32,112,105,112,101,0,73,47,79,32,101,114,114,111,114,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,32,111,114,32,97,100,100,114,101,115,115,0,66,108,111,99,107,32,100,101,118,105,99,101,32,114,101,113,117,105,114,101,100,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,0,78,111,116,32,97,32,100,105,114,101,99,116,111,114,121,0,73,115,32,97,32,100,105,114,101,99,116,111,114,121,0,84,101,120,116,32,102,105,108,101,32,98,117,115,121,0,69,120,101,99,32,102,111,114,109,97,116,32,101,114,114,111,114,0,73,110,118,97,108,105,100,32,97,114,103,117,109,101,110,116,0,65,114,103,117,109,101,110,116,32,108,105,115,116,32,116,111,111,32,108,111,110,103,0,83,121,109,98,111,108,105,99,32,108,105,110,107,32,108,111,111,112,0,70,105,108,101,110,97,109,101,32,116,111,111,32,108,111,110,103,0,84,111,111,32,109,97,110,121,32,111,112,101,110,32,102,105,108,101,115,32,105,110,32,115,121,115,116,101,109,0,78,111,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,115,32,97,118,97,105,108,97,98,108,101,0,66,97,100,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,0,78,111,32,99,104,105,108,100,32,112,114,111,99,101,115,115,0,66,97,100,32,97,100,100,114,101,115,115,0,70,105,108,101,32,116,111,111,32,108,97,114,103,101,0,84,111,111,32,109,97,110,121,32,108,105,110,107,115,0,78,111,32,108,111,99,107,115,32,97,118,97,105,108,97,98,108,101,0,82,101,115,111,117,114,99,101,32,100,101,97,100,108,111,99,107,32,119,111,117,108,100,32,111,99,99,117,114,0,83,116,97,116,101,32,110,111,116,32,114,101,99,111,118,101,114,97,98,108,101,0,80,114,101,118,105,111,117,115,32,111,119,110,101,114,32,100,105,101,100,0,79,112,101,114,97,116,105,111,110,32,99,97,110,99,101,108,101,100,0,70,117,110,99,116,105,111,110,32,110,111,116,32,105,109,112,108,101,109,101,110,116,101,100,0,78,111,32,109,101,115,115,97,103,101,32,111,102,32,100,101,115,105,114,101,100,32,116,121,112,101,0,73,100,101,110,116,105,102,105,101,114,32,114,101,109,111,118,101,100,0,68,101,118,105,99,101,32,110,111,116,32,97,32,115,116,114,101,97,109,0,78,111,32,100,97,116,97,32,97,118,97,105,108,97,98,108,101,0,68,101,118,105,99,101,32,116,105,109,101,111,117,116,0,79,117,116,32,111,102,32,115,116,114,101,97,109,115,32,114,101,115,111,117,114,99,101,115,0,76,105,110,107,32,104,97,115,32,98,101,101,110,32,115,101,118,101,114,101,100,0,80,114,111,116,111,99,111,108,32,101,114,114,111,114,0,66,97,100,32,109,101,115,115,97,103,101,0,70,105,108,101,32,100,101,115,99,114,105,112,116,111,114,32,105,110,32,98,97,100,32,115,116,97,116,101,0,78,111,116,32,97,32,115,111,99,107,101,116,0,68,101,115,116,105,110,97,116,105,111,110,32,97,100,100,114,101,115,115,32,114,101,113,117,105,114,101,100,0,77,101,115,115,97,103,101,32,116,111,111,32,108,97,114,103,101,0,80,114,111,116,111,99,111,108,32,119,114,111,110,103,32,116,121,112,101,32,102,111,114,32,115,111,99,107,101,116,0,80,114,111,116,111,99,111,108,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,80,114,111,116,111,99,111,108,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,83,111,99,107,101,116,32,116,121,112,101,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,78,111,116,32,115,117,112,112,111,114,116,101,100,0,80,114,111,116,111,99,111,108,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,65,100,100,114,101,115,115,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,32,98,121,32,112,114,111,116,111,99,111,108,0,65,100,100,114,101,115,115,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,78,101,116,119,111,114,107,32,105,115,32,100,111,119,110,0,78,101,116,119,111,114,107,32,117,110,114,101,97,99,104,97,98,108,101,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,110,101,116,119,111,114,107,0,67,111,110,110,101,99,116,105,111,110,32,97,98,111,114,116,101,100,0,78,111,32,98,117,102,102,101,114,32,115,112,97,99,101,32,97,118,97,105,108,97,98,108,101,0,83,111,99,107,101,116,32,105,115,32,99,111,110,110,101,99,116,101,100,0,83,111,99,107,101,116,32,110,111,116,32,99,111,110,110,101,99,116,101,100,0,67,97,110,110,111,116,32,115,101,110,100,32,97,102,116,101,114,32,115,111,99,107,101,116,32,115,104,117,116,100,111,119,110,0,79,112,101,114,97,116,105,111,110,32,97,108,114,101,97,100,121,32,105,110,32,112,114,111,103,114,101,115,115,0,79,112,101,114,97,116,105,111,110,32,105,110,32,112,114,111,103,114,101,115,115,0,83,116,97,108,101,32,102,105,108,101,32,104,97,110,100,108,101,0,82,101,109,111,116,101,32,73,47,79,32,101,114,114,111,114,0,81,117,111,116,97,32,101,120,99,101,101,100,101,100,0,78,111,32,109,101,100,105,117,109,32,102,111,117,110,100,0,87,114,111,110,103,32,109,101,100,105,117,109,32,116,121,112,101,0,78,111,32,101,114,114,111,114,32,105,110,102,111,114,109,97,116,105,111,110,0,0],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE);var tempDoublePtr=STATICTOP;function _atexit(e,t){__ATEXIT__.unshift({func:e,arg:t})}function ___cxa_atexit(){return _atexit.apply(null,arguments)}function _abort(){Module.abort()}function __ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj(){Module.printErr("missing function: _ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj"),abort(-1)}function __decorate(e,t,n,r){var i,o=arguments.length,u=o<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,n):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)u=Reflect.decorate(e,t,n,r);else for(var a=e.length-1;a>=0;a--)(i=e[a])&&(u=(o<3?i(u):o>3?i(t,n,u):i(t,n))||u);return o>3&&u&&Object.defineProperty(t,n,u),u}function _defineHidden(e){return function(t,n){Object.defineProperty(t,n,{configurable:!1,enumerable:!1,value:e,writable:!0})}}STATICTOP+=16;var _nbind={};function __nbind_free_external(e){_nbind.externalList[e].dereference(e)}function __nbind_reference_external(e){_nbind.externalList[e].reference()}function _llvm_stackrestore(e){var t=_llvm_stacksave,n=t.LLVM_SAVEDSTACKS[e];t.LLVM_SAVEDSTACKS.splice(e,1),Runtime.stackRestore(n)}function __nbind_register_pool(e,t,n,r){_nbind.Pool.pageSize=e,_nbind.Pool.usedPtr=t/4,_nbind.Pool.rootPtr=n,_nbind.Pool.pagePtr=r/4,HEAP32[t/4]=16909060,1==HEAP8[t]&&(_nbind.bigEndian=!0),HEAP32[t/4]=0,_nbind.makeTypeKindTbl=((i={})[1024]=_nbind.PrimitiveType,i[64]=_nbind.Int64Type,i[2048]=_nbind.BindClass,i[3072]=_nbind.BindClassPtr,i[4096]=_nbind.SharedClassPtr,i[5120]=_nbind.ArrayType,i[6144]=_nbind.ArrayType,i[7168]=_nbind.CStringType,i[9216]=_nbind.CallbackType,i[10240]=_nbind.BindType,i),_nbind.makeTypeNameTbl={Buffer:_nbind.BufferType,External:_nbind.ExternalType,Int64:_nbind.Int64Type,_nbind_new:_nbind.CreateValueType,bool:_nbind.BooleanType,"cbFunction &":_nbind.CallbackType,"const cbFunction &":_nbind.CallbackType,"const std::string &":_nbind.StringType,"std::string":_nbind.StringType},Module.toggleLightGC=_nbind.toggleLightGC,_nbind.callUpcast=Module.dynCall_ii;var i,o=_nbind.makeType(_nbind.constructType,{flags:2048,id:0,name:""});o.proto=Module,_nbind.BindClass.list.push(o)}function _emscripten_set_main_loop_timing(e,t){if(Browser.mainLoop.timingMode=e,Browser.mainLoop.timingValue=t,!Browser.mainLoop.func)return 1;if(0==e)Browser.mainLoop.scheduler=function(){var e=0|Math.max(0,Browser.mainLoop.tickStartTime+t-_emscripten_get_now());setTimeout(Browser.mainLoop.runner,e)},Browser.mainLoop.method="timeout";else if(1==e)Browser.mainLoop.scheduler=function(){Browser.requestAnimationFrame(Browser.mainLoop.runner)},Browser.mainLoop.method="rAF";else if(2==e){if(!window.setImmediate){var n=[];window.addEventListener("message",(function(e){e.source===window&&"setimmediate"===e.data&&(e.stopPropagation(),n.shift()())}),!0),window.setImmediate=function(e){n.push(e),ENVIRONMENT_IS_WORKER?(void 0===Module.setImmediates&&(Module.setImmediates=[]),Module.setImmediates.push(e),window.postMessage({target:"setimmediate"})):window.postMessage("setimmediate","*")}}Browser.mainLoop.scheduler=function(){window.setImmediate(Browser.mainLoop.runner)},Browser.mainLoop.method="immediate"}return 0}function _emscripten_get_now(){abort()}function _emscripten_set_main_loop(e,t,n,r,i){var o;Module.noExitRuntime=!0,assert(!Browser.mainLoop.func,"emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters."),Browser.mainLoop.func=e,Browser.mainLoop.arg=r,o=void 0!==r?function(){Module.dynCall_vi(e,r)}:function(){Module.dynCall_v(e)};var u=Browser.mainLoop.currentlyRunningMainloop;if(Browser.mainLoop.runner=function(){if(!ABORT)if(Browser.mainLoop.queue.length>0){var e=Date.now(),t=Browser.mainLoop.queue.shift();if(t.func(t.arg),Browser.mainLoop.remainingBlockers){var n=Browser.mainLoop.remainingBlockers,r=n%1==0?n-1:Math.floor(n);t.counted?Browser.mainLoop.remainingBlockers=r:(r+=.5,Browser.mainLoop.remainingBlockers=(8*n+r)/9)}if(console.log('main loop blocker "'+t.name+'" took '+(Date.now()-e)+" ms"),Browser.mainLoop.updateStatus(),u1&&Browser.mainLoop.currentFrameNumber%Browser.mainLoop.timingValue!=0?Browser.mainLoop.scheduler():(0==Browser.mainLoop.timingMode&&(Browser.mainLoop.tickStartTime=_emscripten_get_now()),"timeout"===Browser.mainLoop.method&&Module.ctx&&(Module.printErr("Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!"),Browser.mainLoop.method=""),Browser.mainLoop.runIter(o),u0?_emscripten_set_main_loop_timing(0,1e3/t):_emscripten_set_main_loop_timing(1,1),Browser.mainLoop.scheduler()),n)throw"SimulateInfiniteLoop"}var Browser={mainLoop:{scheduler:null,method:"",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:function(){Browser.mainLoop.scheduler=null,Browser.mainLoop.currentlyRunningMainloop++},resume:function(){Browser.mainLoop.currentlyRunningMainloop++;var e=Browser.mainLoop.timingMode,t=Browser.mainLoop.timingValue,n=Browser.mainLoop.func;Browser.mainLoop.func=null,_emscripten_set_main_loop(n,0,!1,Browser.mainLoop.arg,!0),_emscripten_set_main_loop_timing(e,t),Browser.mainLoop.scheduler()},updateStatus:function(){if(Module.setStatus){var e=Module.statusMessage||"Please wait...",t=Browser.mainLoop.remainingBlockers,n=Browser.mainLoop.expectedBlockers;t?t=6;){var u=r>>i-6&63;i-=6,n+=t[u]}return 2==i?(n+=t[(3&r)<<4],n+="=="):4==i&&(n+=t[(15&r)<<2],n+="="),n}(e),o(s))},s.src=l,Browser.safeSetTimeout((function(){o(s)}),1e4)}};Module.preloadPlugins.push(t);var n=Module.canvas;n&&(n.requestPointerLock=n.requestPointerLock||n.mozRequestPointerLock||n.webkitRequestPointerLock||n.msRequestPointerLock||function(){},n.exitPointerLock=document.exitPointerLock||document.mozExitPointerLock||document.webkitExitPointerLock||document.msExitPointerLock||function(){},n.exitPointerLock=n.exitPointerLock.bind(document),document.addEventListener("pointerlockchange",r,!1),document.addEventListener("mozpointerlockchange",r,!1),document.addEventListener("webkitpointerlockchange",r,!1),document.addEventListener("mspointerlockchange",r,!1),Module.elementPointerLock&&n.addEventListener("click",(function(e){!Browser.pointerLock&&Module.canvas.requestPointerLock&&(Module.canvas.requestPointerLock(),e.preventDefault())}),!1))}function r(){Browser.pointerLock=document.pointerLockElement===Module.canvas||document.mozPointerLockElement===Module.canvas||document.webkitPointerLockElement===Module.canvas||document.msPointerLockElement===Module.canvas}},createContext:function(e,t,n,r){if(t&&Module.ctx&&e==Module.canvas)return Module.ctx;var i,o;if(t){var u={antialias:!1,alpha:!1};if(r)for(var a in r)u[a]=r[a];(o=GL.createContext(e,u))&&(i=GL.getContext(o).GLctx)}else i=e.getContext("2d");return i?(n&&(t||assert("undefined"==typeof GLctx,"cannot set in module if GLctx is used, but we are a non-GL context that would replace it"),Module.ctx=i,t&&GL.makeContextCurrent(o),Module.useWebGL=t,Browser.moduleContextCreatedCallbacks.forEach((function(e){e()})),Browser.init()),i):null},destroyContext:function(e,t,n){},fullscreenHandlersInstalled:!1,lockPointer:void 0,resizeCanvas:void 0,requestFullscreen:function(e,t,n){Browser.lockPointer=e,Browser.resizeCanvas=t,Browser.vrDevice=n,void 0===Browser.lockPointer&&(Browser.lockPointer=!0),void 0===Browser.resizeCanvas&&(Browser.resizeCanvas=!1),void 0===Browser.vrDevice&&(Browser.vrDevice=null);var r=Module.canvas;function i(){Browser.isFullscreen=!1;var e=r.parentNode;(document.fullscreenElement||document.mozFullScreenElement||document.msFullscreenElement||document.webkitFullscreenElement||document.webkitCurrentFullScreenElement)===e?(r.exitFullscreen=document.exitFullscreen||document.cancelFullScreen||document.mozCancelFullScreen||document.msExitFullscreen||document.webkitCancelFullScreen||function(){},r.exitFullscreen=r.exitFullscreen.bind(document),Browser.lockPointer&&r.requestPointerLock(),Browser.isFullscreen=!0,Browser.resizeCanvas&&Browser.setFullscreenCanvasSize()):(e.parentNode.insertBefore(r,e),e.parentNode.removeChild(e),Browser.resizeCanvas&&Browser.setWindowedCanvasSize()),Module.onFullScreen&&Module.onFullScreen(Browser.isFullscreen),Module.onFullscreen&&Module.onFullscreen(Browser.isFullscreen),Browser.updateCanvasDimensions(r)}Browser.fullscreenHandlersInstalled||(Browser.fullscreenHandlersInstalled=!0,document.addEventListener("fullscreenchange",i,!1),document.addEventListener("mozfullscreenchange",i,!1),document.addEventListener("webkitfullscreenchange",i,!1),document.addEventListener("MSFullscreenChange",i,!1));var o=document.createElement("div");r.parentNode.insertBefore(o,r),o.appendChild(r),o.requestFullscreen=o.requestFullscreen||o.mozRequestFullScreen||o.msRequestFullscreen||(o.webkitRequestFullscreen?function(){o.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}:null)||(o.webkitRequestFullScreen?function(){o.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT)}:null),n?o.requestFullscreen({vrDisplay:n}):o.requestFullscreen()},requestFullScreen:function(e,t,n){return Module.printErr("Browser.requestFullScreen() is deprecated. Please call Browser.requestFullscreen instead."),Browser.requestFullScreen=function(e,t,n){return Browser.requestFullscreen(e,t,n)},Browser.requestFullscreen(e,t,n)},nextRAF:0,fakeRequestAnimationFrame:function(e){var t=Date.now();if(0===Browser.nextRAF)Browser.nextRAF=t+1e3/60;else for(;t+2>=Browser.nextRAF;)Browser.nextRAF+=1e3/60;var n=Math.max(Browser.nextRAF-t,0);setTimeout(e,n)},requestAnimationFrame:function(e){"undefined"==typeof window?Browser.fakeRequestAnimationFrame(e):(window.requestAnimationFrame||(window.requestAnimationFrame=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||window.oRequestAnimationFrame||Browser.fakeRequestAnimationFrame),window.requestAnimationFrame(e))},safeCallback:function(e){return function(){if(!ABORT)return e.apply(null,arguments)}},allowAsyncCallbacks:!0,queuedAsyncCallbacks:[],pauseAsyncCallbacks:function(){Browser.allowAsyncCallbacks=!1},resumeAsyncCallbacks:function(){if(Browser.allowAsyncCallbacks=!0,Browser.queuedAsyncCallbacks.length>0){var e=Browser.queuedAsyncCallbacks;Browser.queuedAsyncCallbacks=[],e.forEach((function(e){e()}))}},safeRequestAnimationFrame:function(e){return Browser.requestAnimationFrame((function(){ABORT||(Browser.allowAsyncCallbacks?e():Browser.queuedAsyncCallbacks.push(e))}))},safeSetTimeout:function(e,t){return Module.noExitRuntime=!0,setTimeout((function(){ABORT||(Browser.allowAsyncCallbacks?e():Browser.queuedAsyncCallbacks.push(e))}),t)},safeSetInterval:function(e,t){return Module.noExitRuntime=!0,setInterval((function(){ABORT||Browser.allowAsyncCallbacks&&e()}),t)},getMimetype:function(e){return{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",bmp:"image/bmp",ogg:"audio/ogg",wav:"audio/wav",mp3:"audio/mpeg"}[e.substr(e.lastIndexOf(".")+1)]},getUserMedia:function(e){window.getUserMedia||(window.getUserMedia=navigator.getUserMedia||navigator.mozGetUserMedia),window.getUserMedia(e)},getMovementX:function(e){return e.movementX||e.mozMovementX||e.webkitMovementX||0},getMovementY:function(e){return e.movementY||e.mozMovementY||e.webkitMovementY||0},getMouseWheelDelta:function(e){var t=0;switch(e.type){case"DOMMouseScroll":t=e.detail;break;case"mousewheel":t=e.wheelDelta;break;case"wheel":t=e.deltaY;break;default:throw"unrecognized mouse wheel event: "+e.type}return t},mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:function(e){if(Browser.pointerLock)"mousemove"!=e.type&&"mozMovementX"in e?Browser.mouseMovementX=Browser.mouseMovementY=0:(Browser.mouseMovementX=Browser.getMovementX(e),Browser.mouseMovementY=Browser.getMovementY(e)),"undefined"!=typeof SDL?(Browser.mouseX=SDL.mouseX+Browser.mouseMovementX,Browser.mouseY=SDL.mouseY+Browser.mouseMovementY):(Browser.mouseX+=Browser.mouseMovementX,Browser.mouseY+=Browser.mouseMovementY);else{var t=Module.canvas.getBoundingClientRect(),n=Module.canvas.width,r=Module.canvas.height,i=void 0!==window.scrollX?window.scrollX:window.pageXOffset,o=void 0!==window.scrollY?window.scrollY:window.pageYOffset;if("touchstart"===e.type||"touchend"===e.type||"touchmove"===e.type){var u=e.touch;if(void 0===u)return;var a=u.pageX-(i+t.left),l=u.pageY-(o+t.top),s={x:a*=n/t.width,y:l*=r/t.height};if("touchstart"===e.type)Browser.lastTouches[u.identifier]=s,Browser.touches[u.identifier]=s;else if("touchend"===e.type||"touchmove"===e.type){var c=Browser.touches[u.identifier];c||(c=s),Browser.lastTouches[u.identifier]=c,Browser.touches[u.identifier]=s}return}var f=e.pageX-(i+t.left),d=e.pageY-(o+t.top);f*=n/t.width,d*=r/t.height,Browser.mouseMovementX=f-Browser.mouseX,Browser.mouseMovementY=d-Browser.mouseY,Browser.mouseX=f,Browser.mouseY=d}},asyncLoad:function(e,t,n,r){var i=r?"":getUniqueRunDependency("al "+e);Module.readAsync(e,(function(n){assert(n,'Loading data file "'+e+'" failed (no arrayBuffer).'),t(new Uint8Array(n)),i&&removeRunDependency(i)}),(function(t){if(!n)throw'Loading data file "'+e+'" failed.';n()})),i&&addRunDependency(i)},resizeListeners:[],updateResizeListeners:function(){var e=Module.canvas;Browser.resizeListeners.forEach((function(t){t(e.width,e.height)}))},setCanvasSize:function(e,t,n){var r=Module.canvas;Browser.updateCanvasDimensions(r,e,t),n||Browser.updateResizeListeners()},windowedWidth:0,windowedHeight:0,setFullscreenCanvasSize:function(){if("undefined"!=typeof SDL){var e=HEAPU32[SDL.screen+0*Runtime.QUANTUM_SIZE>>2];e|=8388608,HEAP32[SDL.screen+0*Runtime.QUANTUM_SIZE>>2]=e}Browser.updateResizeListeners()},setWindowedCanvasSize:function(){if("undefined"!=typeof SDL){var e=HEAPU32[SDL.screen+0*Runtime.QUANTUM_SIZE>>2];e&=-8388609,HEAP32[SDL.screen+0*Runtime.QUANTUM_SIZE>>2]=e}Browser.updateResizeListeners()},updateCanvasDimensions:function(e,t,n){t&&n?(e.widthNative=t,e.heightNative=n):(t=e.widthNative,n=e.heightNative);var r=t,i=n;if(Module.forcedAspectRatio&&Module.forcedAspectRatio>0&&(r/i>2]},getStr:function(){return Pointer_stringify(SYSCALLS.get())},get64:function(){var e=SYSCALLS.get(),t=SYSCALLS.get();return assert(e>=0?0===t:-1===t),e},getZero:function(){assert(0===SYSCALLS.get())}};function ___syscall6(e,t){SYSCALLS.varargs=t;try{var n=SYSCALLS.getStreamFromFD();return FS.close(n),0}catch(e){return"undefined"!=typeof FS&&e instanceof FS.ErrnoError||abort(e),-e.errno}}function ___syscall54(e,t){SYSCALLS.varargs=t;try{return 0}catch(e){return"undefined"!=typeof FS&&e instanceof FS.ErrnoError||abort(e),-e.errno}}function _typeModule(e){var t=[[0,1,"X"],[1,1,"const X"],[128,1,"X *"],[256,1,"X &"],[384,1,"X &&"],[512,1,"std::shared_ptr"],[640,1,"std::unique_ptr"],[5120,1,"std::vector"],[6144,2,"std::array"],[9216,-1,"std::function"]];function n(e,t,n,r,i,o){if(1==t){var u=896&r;128!=u&&256!=u&&384!=u||(e="X const")}return(o?n.replace("X",e).replace("Y",i):e.replace("X",n).replace("Y",i)).replace(/([*&]) (?=[*&])/g,"$1")}function r(e,t){var n=t.flags,r=896&n,i=15360&n;return t.name||1024!=i||(1==t.ptrSize?t.name=(16&n?"":(8&n?"un":"")+"signed ")+"char":t.name=(8&n?"u":"")+(32&n?"float":"int")+8*t.ptrSize+"_t"),8!=t.ptrSize||32&n||(i=64),2048==i&&(512==r||640==r?i=4096:r&&(i=3072)),e(i,t)}var i={Type:function(){function e(e){this.id=e.id,this.name=e.name,this.flags=e.flags,this.spec=e}return e.prototype.toString=function(){return this.name},e}(),getComplexType:function e(i,o,u,a,l,s,c,f){void 0===s&&(s="X"),void 0===f&&(f=1);var d=u(i);if(d)return d;var p,h=a(i),v=h.placeholderFlag,m=t[v];c&&m&&(s=n(c[2],c[0],s,m[0],"?",!0)),0==v&&(p="Unbound"),v>=10&&(p="Corrupt"),f>20&&(p="Deeply nested"),p&&function(e,t,n,r,i){throw new Error(e+" type "+n.replace("X",t+"?")+(r?" with flag "+r:"")+" in "+i)}(p,i,s,v,l||"?");var g,y=e(h.paramList[0],o,u,a,l,s,m,f+1),_={flags:m[0],id:i,name:"",paramList:[y]},b=[],w="?";switch(h.placeholderFlag){case 1:g=y.spec;break;case 2:if(1024==(15360&y.flags)&&1==y.spec.ptrSize){_.flags=7168;break}case 3:case 6:case 5:g=y.spec,y.flags;break;case 8:w=""+h.paramList[1],_.paramList.push(h.paramList[1]);break;case 9:for(var E=0,D=h.paramList[1];E>2]=e),e}function _llvm_stacksave(){var e=_llvm_stacksave;return e.LLVM_SAVEDSTACKS||(e.LLVM_SAVEDSTACKS=[]),e.LLVM_SAVEDSTACKS.push(Runtime.stackSave()),e.LLVM_SAVEDSTACKS.length-1}function ___syscall140(e,t){SYSCALLS.varargs=t;try{var n=SYSCALLS.getStreamFromFD(),r=(SYSCALLS.get(),SYSCALLS.get()),i=SYSCALLS.get(),o=SYSCALLS.get(),u=r;return FS.llseek(n,u,o),HEAP32[i>>2]=n.position,n.getdents&&0===u&&0===o&&(n.getdents=null),0}catch(e){return"undefined"!=typeof FS&&e instanceof FS.ErrnoError||abort(e),-e.errno}}function ___syscall146(e,t){SYSCALLS.varargs=t;try{var n=SYSCALLS.get(),r=SYSCALLS.get(),i=SYSCALLS.get(),o=0;___syscall146.buffer||(___syscall146.buffers=[null,[],[]],___syscall146.printChar=function(e,t){var n=___syscall146.buffers[e];assert(n),0===t||10===t?((1===e?Module.print:Module.printErr)(UTF8ArrayToString(n,0)),n.length=0):n.push(t)});for(var u=0;u>2],l=HEAP32[r+(8*u+4)>>2],s=0;se.pageSize/2||t>e.pageSize-n?_nbind.typeNameTbl.NBind.proto.lalloc(t):(HEAPU32[e.usedPtr]=n+t,e.rootPtr+n)},e.lreset=function(t,n){HEAPU32[e.pagePtr]?_nbind.typeNameTbl.NBind.proto.lreset(t,n):HEAPU32[e.usedPtr]=t},e}();function constructType(e,t){var n=new(10240==e?_nbind.makeTypeNameTbl[t.name]||_nbind.BindType:_nbind.makeTypeKindTbl[e])(t);return typeIdTbl[t.id]=n,_nbind.typeNameTbl[t.name]=n,n}function getType(e){return typeIdTbl[e]}function queryType(e){var t=HEAPU8[e],n=_nbind.structureList[t][1];e/=4,n<0&&(++e,n=HEAPU32[e]+1);var r=Array.prototype.slice.call(HEAPU32.subarray(e+1,e+1+n));return 9==t&&(r=[r[0],r.slice(1)]),{paramList:r,placeholderFlag:t}}function getTypes(e,t){return e.map((function(e){return"number"==typeof e?_nbind.getComplexType(e,constructType,getType,queryType,t):_nbind.typeNameTbl[e]}))}function readTypeIdList(e,t){return Array.prototype.slice.call(HEAPU32,e/4,e/4+t)}function readAsciiString(e){for(var t=e;HEAPU8[t++];);return String.fromCharCode.apply("",HEAPU8.subarray(e,t-1))}function readPolicyList(e){var t={};if(e)for(;;){var n=HEAPU32[e/4];if(!n)break;t[readAsciiString(n)]=!0,e+=4}return t}function getDynCall(e,t){var n={float32_t:"d",float64_t:"d",int64_t:"d",uint64_t:"d",void:"v"},r=e.map((function(e){return n[e.name]||"i"})).join(""),i=Module["dynCall_"+r];if(!i)throw new Error("dynCall_"+r+" not found for "+t+"("+e.map((function(e){return e.name})).join(", ")+")");return i}function addMethod(e,t,n,r){var i=e[t];e.hasOwnProperty(t)&&i?((i.arity||0===i.arity)&&(i=_nbind.makeOverloader(i,i.arity),e[t]=i),i.addMethod(n,r)):(n.arity=r,e[t]=n)}function throwError(e){throw new Error(e)}_nbind.Pool=Pool,_nbind.constructType=constructType,_nbind.getType=getType,_nbind.queryType=queryType,_nbind.getTypes=getTypes,_nbind.readTypeIdList=readTypeIdList,_nbind.readAsciiString=readAsciiString,_nbind.readPolicyList=readPolicyList,_nbind.getDynCall=getDynCall,_nbind.addMethod=addMethod,_nbind.throwError=throwError,_nbind.bigEndian=!1,_a=_typeModule(_typeModule),_nbind.Type=_a.Type,_nbind.makeType=_a.makeType,_nbind.getComplexType=_a.getComplexType,_nbind.structureList=_a.structureList;var BindType=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.heap=HEAPU32,t.ptrSize=4,t}return __extends(t,e),t.prototype.needsWireRead=function(e){return!!this.wireRead||!!this.makeWireRead},t.prototype.needsWireWrite=function(e){return!!this.wireWrite||!!this.makeWireWrite},t}(_nbind.Type);_nbind.BindType=BindType;var PrimitiveType=function(e){function t(t){var n=e.call(this,t)||this,r=32&t.flags?{32:HEAPF32,64:HEAPF64}:8&t.flags?{8:HEAPU8,16:HEAPU16,32:HEAPU32}:{8:HEAP8,16:HEAP16,32:HEAP32};return n.heap=r[8*t.ptrSize],n.ptrSize=t.ptrSize,n}return __extends(t,e),t.prototype.needsWireWrite=function(e){return!!e&&!!e.Strict},t.prototype.makeWireWrite=function(e,t){return t&&t.Strict&&function(e){if("number"==typeof e)return e;throw new Error("Type mismatch")}},t}(BindType);function pushCString(e,t){if(null==e){if(t&&t.Nullable)return 0;throw new Error("Type mismatch")}if(t&&t.Strict){if("string"!=typeof e)throw new Error("Type mismatch")}else e=e.toString();var n=Module.lengthBytesUTF8(e)+1,r=_nbind.Pool.lalloc(n);return Module.stringToUTF8Array(e,HEAPU8,r,n),r}function popCString(e){return 0===e?null:Module.Pointer_stringify(e)}_nbind.PrimitiveType=PrimitiveType,_nbind.pushCString=pushCString,_nbind.popCString=popCString;var CStringType=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.wireRead=popCString,t.wireWrite=pushCString,t.readResources=[_nbind.resources.pool],t.writeResources=[_nbind.resources.pool],t}return __extends(t,e),t.prototype.makeWireWrite=function(e,t){return function(e){return pushCString(e,t)}},t}(BindType);_nbind.CStringType=CStringType;var BooleanType=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.wireRead=function(e){return!!e},t}return __extends(t,e),t.prototype.needsWireWrite=function(e){return!!e&&!!e.Strict},t.prototype.makeWireRead=function(e){return"!!("+e+")"},t.prototype.makeWireWrite=function(e,t){return t&&t.Strict&&function(e){if("boolean"==typeof e)return e;throw new Error("Type mismatch")}||e},t}(BindType);_nbind.BooleanType=BooleanType;var Wrapper=function(){function e(){}return e.prototype.persist=function(){this.__nbindState|=1},e}();function makeBound(e,t){var n=function(e){function n(t,r,i,o){var u=e.call(this)||this;if(!(u instanceof n))return new(Function.prototype.bind.apply(n,Array.prototype.concat.apply([null],arguments)));var a=r,l=i,s=o;if(t!==_nbind.ptrMarker){var c=u.__nbindConstructor.apply(u,arguments);a=4608,s=HEAPU32[c/4],l=HEAPU32[c/4+1]}var f={configurable:!0,enumerable:!1,value:null,writable:!1},d={__nbindFlags:a,__nbindPtr:l};s&&(d.__nbindShared=s,_nbind.mark(u));for(var p=0,h=Object.keys(d);p>=1;var n=_nbind.valueList[e];return _nbind.valueList[e]=firstFreeValue,firstFreeValue=e,n}if(t)return _nbind.popShared(e,t);throw new Error("Invalid value slot "+e)}_nbind.pushValue=pushValue,_nbind.popValue=popValue;var valueBase=0x10000000000000000;function push64(e){return"number"==typeof e?e:4096*pushValue(e)+valueBase}function pop64(e){return e=3?Buffer.from(o):new Buffer(o)).copy(r):getBuffer(r).set(o)}}_nbind.BufferType=BufferType,_nbind.commitBuffer=commitBuffer;var dirtyList=[],gcTimer=0;function sweep(){for(var e=0,t=dirtyList;e>2]=DYNAMIC_BASE,staticSealed=!0,Module.asmGlobalArg={Math,Int8Array,Int16Array,Int32Array,Uint8Array,Uint16Array,Uint32Array,Float32Array,Float64Array,NaN:NaN,Infinity:1/0},Module.asmLibraryArg={abort,assert,enlargeMemory,getTotalMemory,abortOnCannotGrowMemory,invoke_viiiii,invoke_vif,invoke_vid,invoke_fiff,invoke_vi,invoke_vii,invoke_ii,invoke_viddi,invoke_vidd,invoke_iiii,invoke_diii,invoke_di,invoke_iid,invoke_iii,invoke_viiddi,invoke_viiiiii,invoke_dii,invoke_i,invoke_iiiiii,invoke_viiid,invoke_viififi,invoke_viii,invoke_v,invoke_viid,invoke_idd,invoke_viiii,_emscripten_asm_const_iiiii,_emscripten_asm_const_iiidddddd,_emscripten_asm_const_iiiid,__nbind_reference_external,_emscripten_asm_const_iiiiiiii,_removeAccessorPrefix,_typeModule,__nbind_register_pool,__decorate,_llvm_stackrestore,___cxa_atexit,__extends,__nbind_get_value_object,__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj,_emscripten_set_main_loop_timing,__nbind_register_primitive,__nbind_register_type,_emscripten_memcpy_big,__nbind_register_function,___setErrNo,__nbind_register_class,__nbind_finish,_abort,_nbind_value,_llvm_stacksave,___syscall54,_defineHidden,_emscripten_set_main_loop,_emscripten_get_now,__nbind_register_callback_signature,_emscripten_asm_const_iiiiii,__nbind_free_external,_emscripten_asm_const_iiii,_emscripten_asm_const_iiididi,___syscall6,_atexit,___syscall140,___syscall146,DYNAMICTOP_PTR,tempDoublePtr,ABORT,STACKTOP,STACK_MAX,cttz_i8,___dso_handle};var asm=function(e,t,n){"use asm";var r=new e.Int8Array(n);var i=new e.Int16Array(n);var o=new e.Int32Array(n);var u=new e.Uint8Array(n);var a=new e.Uint16Array(n);var l=new e.Uint32Array(n);var s=new e.Float32Array(n);var c=new e.Float64Array(n);var f=t.DYNAMICTOP_PTR|0;var d=t.tempDoublePtr|0;var p=t.ABORT|0;var h=t.STACKTOP|0;var v=t.STACK_MAX|0;var m=t.cttz_i8|0;var g=t.___dso_handle|0;var y=0;var _=0;var b=0;var w=0;var E=e.NaN,D=e.Infinity;var S=0,C=0,k=0,T=0,x=0.0;var A=0;var O=e.Math.floor;var P=e.Math.abs;var I=e.Math.sqrt;var N=e.Math.pow;var M=e.Math.cos;var R=e.Math.sin;var F=e.Math.tan;var L=e.Math.acos;var B=e.Math.asin;var j=e.Math.atan;var U=e.Math.atan2;var z=e.Math.exp;var W=e.Math.log;var H=e.Math.ceil;var V=e.Math.imul;var q=e.Math.min;var G=e.Math.max;var $=e.Math.clz32;var Y=e.Math.fround;var K=t.abort;var X=t.assert;var Q=t.enlargeMemory;var J=t.getTotalMemory;var Z=t.abortOnCannotGrowMemory;var ee=t.invoke_viiiii;var te=t.invoke_vif;var ne=t.invoke_vid;var re=t.invoke_fiff;var ie=t.invoke_vi;var oe=t.invoke_vii;var ue=t.invoke_ii;var ae=t.invoke_viddi;var le=t.invoke_vidd;var se=t.invoke_iiii;var ce=t.invoke_diii;var fe=t.invoke_di;var de=t.invoke_iid;var pe=t.invoke_iii;var he=t.invoke_viiddi;var ve=t.invoke_viiiiii;var me=t.invoke_dii;var ge=t.invoke_i;var ye=t.invoke_iiiiii;var _e=t.invoke_viiid;var be=t.invoke_viififi;var we=t.invoke_viii;var Ee=t.invoke_v;var De=t.invoke_viid;var Se=t.invoke_idd;var Ce=t.invoke_viiii;var ke=t._emscripten_asm_const_iiiii;var Te=t._emscripten_asm_const_iiidddddd;var xe=t._emscripten_asm_const_iiiid;var Ae=t.__nbind_reference_external;var Oe=t._emscripten_asm_const_iiiiiiii;var Pe=t._removeAccessorPrefix;var Ie=t._typeModule;var Ne=t.__nbind_register_pool;var Me=t.__decorate;var Re=t._llvm_stackrestore;var Fe=t.___cxa_atexit;var Le=t.__extends;var Be=t.__nbind_get_value_object;var je=t.__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj;var Ue=t._emscripten_set_main_loop_timing;var ze=t.__nbind_register_primitive;var We=t.__nbind_register_type;var He=t._emscripten_memcpy_big;var Ve=t.__nbind_register_function;var qe=t.___setErrNo;var Ge=t.__nbind_register_class;var $e=t.__nbind_finish;var Ye=t._abort;var Ke=t._nbind_value;var Xe=t._llvm_stacksave;var Qe=t.___syscall54;var Je=t._defineHidden;var Ze=t._emscripten_set_main_loop;var et=t._emscripten_get_now;var tt=t.__nbind_register_callback_signature;var nt=t._emscripten_asm_const_iiiiii;var rt=t.__nbind_free_external;var it=t._emscripten_asm_const_iiii;var ot=t._emscripten_asm_const_iiididi;var ut=t.___syscall6;var at=t._atexit;var lt=t.___syscall140;var st=t.___syscall146;var ct=Y(0);const ft=Y(0);function dt(e){e=e|0;var t=0;t=h;h=h+e|0;h=h+15&-16;return t|0}function pt(){return h|0}function ht(e){e=e|0;h=e}function vt(e,t){e=e|0;t=t|0;h=e;v=t}function mt(e,t){e=e|0;t=t|0;if(!y){y=e;_=t}}function gt(e){e=e|0;A=e}function yt(){return A|0}function _t(){var e=0,t=0;ix(8104,8,400)|0;ix(8504,408,540)|0;e=9044;t=e+44|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));r[9088]=0;r[9089]=1;o[2273]=0;o[2274]=948;o[2275]=948;Fe(17,8104,g|0)|0;return}function bt(e){e=e|0;qt(e+948|0);return}function wt(e){e=Y(e);return((Ii(e)|0)&2147483647)>>>0>2139095040|0}function Et(e,t,n){e=e|0;t=t|0;n=n|0;e:do{if(!(o[e+(t<<3)+4>>2]|0)){if((t|2|0)==3?o[e+60>>2]|0:0){e=e+56|0;break}switch(t|0){case 0:case 2:case 4:case 5:{if(o[e+52>>2]|0){e=e+48|0;break e}break}default:{}}if(!(o[e+68>>2]|0)){e=(t|1|0)==5?948:n;break}else{e=e+64|0;break}}else e=e+(t<<3)|0}while(0);return e|0}function Dt(e){e=e|0;var t=0;t=qk(1e3)|0;St(e,(t|0)!=0,2456);o[2276]=(o[2276]|0)+1;ix(t|0,8104,1e3)|0;if(r[e+2>>0]|0){o[t+4>>2]=2;o[t+12>>2]=4}o[t+976>>2]=e;return t|0}function St(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0;i=h;h=h+16|0;r=i;if(!t){o[r>>2]=n;Lr(e,5,3197,r)}h=i;return}function Ct(){return Dt(956)|0}function kt(e){e=e|0;var t=0;t=$T(1e3)|0;Tt(t,e);St(o[e+976>>2]|0,1,2456);o[2276]=(o[2276]|0)+1;o[t+944>>2]=0;return t|0}function Tt(e,t){e=e|0;t=t|0;var n=0;ix(e|0,t|0,948)|0;Ur(e+948|0,t+948|0);n=e+960|0;e=t+960|0;t=n+40|0;do{o[n>>2]=o[e>>2];n=n+4|0;e=e+4|0}while((n|0)<(t|0));return}function xt(e){e=e|0;var t=0,n=0,r=0,i=0;t=e+944|0;n=o[t>>2]|0;if(n|0){At(n+948|0,e)|0;o[t>>2]=0}n=Ot(e)|0;if(n|0){t=0;do{o[(Pt(e,t)|0)+944>>2]=0;t=t+1|0}while((t|0)!=(n|0))}n=e+948|0;r=o[n>>2]|0;i=e+952|0;t=o[i>>2]|0;if((t|0)!=(r|0))o[i>>2]=t+(~((t+-4-r|0)>>>2)<<2);It(n);Gk(e);o[2276]=(o[2276]|0)+-1;return}function At(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0;r=o[e>>2]|0;l=e+4|0;n=o[l>>2]|0;u=n;e:do{if((r|0)==(n|0)){i=r;a=4}else{e=r;while(1){if((o[e>>2]|0)==(t|0)){i=e;a=4;break e}e=e+4|0;if((e|0)==(n|0)){e=0;break}}}}while(0);if((a|0)==4)if((i|0)!=(n|0)){r=i+4|0;e=u-r|0;t=e>>2;if(t){sx(i|0,r|0,e|0)|0;n=o[l>>2]|0}e=i+(t<<2)|0;if((n|0)==(e|0))e=1;else{o[l>>2]=n+(~((n+-4-e|0)>>>2)<<2);e=1}}else e=0;return e|0}function Ot(e){e=e|0;return(o[e+952>>2]|0)-(o[e+948>>2]|0)>>2|0}function Pt(e,t){e=e|0;t=t|0;var n=0;n=o[e+948>>2]|0;if((o[e+952>>2]|0)-n>>2>>>0>t>>>0)e=o[n+(t<<2)>>2]|0;else e=0;return e|0}function It(e){e=e|0;var t=0,n=0,r=0,i=0;r=h;h=h+32|0;t=r;i=o[e>>2]|0;n=(o[e+4>>2]|0)-i|0;if(((o[e+8>>2]|0)-i|0)>>>0>n>>>0){i=n>>2;Ni(t,i,i,e+8|0);Mi(e,t);Ri(t)}h=r;return}function Nt(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0;c=Ot(e)|0;do{if(c|0){if((o[(Pt(e,0)|0)+944>>2]|0)==(e|0)){if(!(At(e+948|0,t)|0))break;ix(t+400|0,8504,540)|0;o[t+944>>2]=0;Vt(e);break}a=o[(o[e+976>>2]|0)+12>>2]|0;l=e+948|0;s=(a|0)==0;n=0;u=0;do{r=o[(o[l>>2]|0)+(u<<2)>>2]|0;if((r|0)==(t|0))Vt(e);else{i=kt(r)|0;o[(o[l>>2]|0)+(n<<2)>>2]=i;o[i+944>>2]=e;if(!s)RA[a&15](r,i,e,n);n=n+1|0}u=u+1|0}while((u|0)!=(c|0));if(n>>>0>>0){s=e+948|0;l=e+952|0;a=n;n=o[l>>2]|0;do{u=(o[s>>2]|0)+(a<<2)|0;r=u+4|0;i=n-r|0;t=i>>2;if(!t)i=n;else{sx(u|0,r|0,i|0)|0;n=o[l>>2]|0;i=n}r=u+(t<<2)|0;if((i|0)!=(r|0)){n=i+(~((i+-4-r|0)>>>2)<<2)|0;o[l>>2]=n}a=a+1|0}while((a|0)!=(c|0))}}}while(0);return}function Mt(e){e=e|0;var t=0,n=0,i=0,u=0;Rt(e,(Ot(e)|0)==0,2491);Rt(e,(o[e+944>>2]|0)==0,2545);t=e+948|0;n=o[t>>2]|0;i=e+952|0;u=o[i>>2]|0;if((u|0)!=(n|0))o[i>>2]=u+(~((u+-4-n|0)>>>2)<<2);It(t);t=e+976|0;n=o[t>>2]|0;ix(e|0,8104,1e3)|0;if(r[n+2>>0]|0){o[e+4>>2]=2;o[e+12>>2]=4}o[t>>2]=n;return}function Rt(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0;i=h;h=h+16|0;r=i;if(!t){o[r>>2]=n;Cr(e,5,3197,r)}h=i;return}function Ft(){return o[2276]|0}function Lt(){var e=0;e=qk(20)|0;Bt((e|0)!=0,2592);o[2277]=(o[2277]|0)+1;o[e>>2]=o[239];o[e+4>>2]=o[240];o[e+8>>2]=o[241];o[e+12>>2]=o[242];o[e+16>>2]=o[243];return e|0}function Bt(e,t){e=e|0;t=t|0;var n=0,r=0;r=h;h=h+16|0;n=r;if(!e){o[n>>2]=t;Cr(0,5,3197,n)}h=r;return}function jt(e){e=e|0;Gk(e);o[2277]=(o[2277]|0)+-1;return}function Ut(e,t){e=e|0;t=t|0;var n=0;if(!t){n=0;t=0}else{Rt(e,(Ot(e)|0)==0,2629);n=1}o[e+964>>2]=t;o[e+988>>2]=n;return}function zt(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;u=r+8|0;i=r+4|0;a=r;o[i>>2]=t;Rt(e,(o[t+944>>2]|0)==0,2709);Rt(e,(o[e+964>>2]|0)==0,2763);Wt(e);t=e+948|0;o[a>>2]=(o[t>>2]|0)+(n<<2);o[u>>2]=o[a>>2];Ht(t,u,i)|0;o[(o[i>>2]|0)+944>>2]=e;Vt(e);h=r;return}function Wt(e){e=e|0;var t=0,n=0,r=0,i=0,u=0,a=0,l=0;n=Ot(e)|0;if(n|0?(o[(Pt(e,0)|0)+944>>2]|0)!=(e|0):0){r=o[(o[e+976>>2]|0)+12>>2]|0;i=e+948|0;u=(r|0)==0;t=0;do{a=o[(o[i>>2]|0)+(t<<2)>>2]|0;l=kt(a)|0;o[(o[i>>2]|0)+(t<<2)>>2]=l;o[l+944>>2]=e;if(!u)RA[r&15](a,l,e,t);t=t+1|0}while((t|0)!=(n|0))}return}function Ht(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0,v=0,m=0,g=0,y=0,_=0;y=h;h=h+64|0;d=y+52|0;l=y+48|0;p=y+28|0;v=y+24|0;m=y+20|0;g=y;r=o[e>>2]|0;u=r;t=r+((o[t>>2]|0)-u>>2<<2)|0;r=e+4|0;i=o[r>>2]|0;a=e+8|0;do{if(i>>>0<(o[a>>2]|0)>>>0){if((t|0)==(i|0)){o[t>>2]=o[n>>2];o[r>>2]=(o[r>>2]|0)+4;break}Fi(e,t,i,t+4|0);if(t>>>0<=n>>>0)n=(o[r>>2]|0)>>>0>n>>>0?n+4|0:n;o[t>>2]=o[n>>2]}else{r=(i-u>>2)+1|0;i=Hr(e)|0;if(i>>>0>>0)UT(e);f=o[e>>2]|0;c=(o[a>>2]|0)-f|0;u=c>>1;Ni(g,c>>2>>>0>>1>>>0?u>>>0>>0?r:u:i,t-f>>2,e+8|0);f=g+8|0;r=o[f>>2]|0;u=g+12|0;c=o[u>>2]|0;a=c;s=r;do{if((r|0)==(c|0)){c=g+4|0;r=o[c>>2]|0;_=o[g>>2]|0;i=_;if(r>>>0<=_>>>0){r=a-i>>1;r=(r|0)==0?1:r;Ni(p,r,r>>>2,o[g+16>>2]|0);o[v>>2]=o[c>>2];o[m>>2]=o[f>>2];o[l>>2]=o[v>>2];o[d>>2]=o[m>>2];Bi(p,l,d);r=o[g>>2]|0;o[g>>2]=o[p>>2];o[p>>2]=r;r=p+4|0;_=o[c>>2]|0;o[c>>2]=o[r>>2];o[r>>2]=_;r=p+8|0;_=o[f>>2]|0;o[f>>2]=o[r>>2];o[r>>2]=_;r=p+12|0;_=o[u>>2]|0;o[u>>2]=o[r>>2];o[r>>2]=_;Ri(p);r=o[f>>2]|0;break}u=r;a=((u-i>>2)+1|0)/-2|0;l=r+(a<<2)|0;i=s-u|0;u=i>>2;if(u){sx(l|0,r|0,i|0)|0;r=o[c>>2]|0}_=l+(u<<2)|0;o[f>>2]=_;o[c>>2]=r+(a<<2);r=_}}while(0);o[r>>2]=o[n>>2];o[f>>2]=(o[f>>2]|0)+4;t=Li(e,g,t)|0;Ri(g)}}while(0);h=y;return t|0}function Vt(e){e=e|0;var t=0;do{t=e+984|0;if(r[t>>0]|0)break;r[t>>0]=1;s[e+504>>2]=Y(E);e=o[e+944>>2]|0}while((e|0)!=0);return}function qt(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-4-r|0)>>>2)<<2);KT(n)}return}function Gt(e){e=e|0;return o[e+944>>2]|0}function $t(e){e=e|0;Rt(e,(o[e+964>>2]|0)!=0,2832);Vt(e);return}function Yt(e){e=e|0;return(r[e+984>>0]|0)!=0|0}function Kt(e,t){e=e|0;t=t|0;if(iT(e,t,400)|0){ix(e|0,t|0,400)|0;Vt(e)}return}function Xt(e){e=e|0;var t=ft;t=Y(s[e+44>>2]);e=wt(t)|0;return Y(e?Y(0.0):t)}function Qt(e){e=e|0;var t=ft;t=Y(s[e+48>>2]);if(wt(t)|0)t=r[(o[e+976>>2]|0)+2>>0]|0?Y(1.0):Y(0.0);return Y(t)}function Jt(e,t){e=e|0;t=t|0;o[e+980>>2]=t;return}function Zt(e){e=e|0;return o[e+980>>2]|0}function en(e,t){e=e|0;t=t|0;var n=0;n=e+4|0;if((o[n>>2]|0)!=(t|0)){o[n>>2]=t;Vt(e)}return}function tn(e){e=e|0;return o[e+4>>2]|0}function nn(e,t){e=e|0;t=t|0;var n=0;n=e+8|0;if((o[n>>2]|0)!=(t|0)){o[n>>2]=t;Vt(e)}return}function rn(e){e=e|0;return o[e+8>>2]|0}function on(e,t){e=e|0;t=t|0;var n=0;n=e+12|0;if((o[n>>2]|0)!=(t|0)){o[n>>2]=t;Vt(e)}return}function un(e){e=e|0;return o[e+12>>2]|0}function an(e,t){e=e|0;t=t|0;var n=0;n=e+16|0;if((o[n>>2]|0)!=(t|0)){o[n>>2]=t;Vt(e)}return}function ln(e){e=e|0;return o[e+16>>2]|0}function sn(e,t){e=e|0;t=t|0;var n=0;n=e+20|0;if((o[n>>2]|0)!=(t|0)){o[n>>2]=t;Vt(e)}return}function cn(e){e=e|0;return o[e+20>>2]|0}function fn(e,t){e=e|0;t=t|0;var n=0;n=e+24|0;if((o[n>>2]|0)!=(t|0)){o[n>>2]=t;Vt(e)}return}function dn(e){e=e|0;return o[e+24>>2]|0}function pn(e,t){e=e|0;t=t|0;var n=0;n=e+28|0;if((o[n>>2]|0)!=(t|0)){o[n>>2]=t;Vt(e)}return}function hn(e){e=e|0;return o[e+28>>2]|0}function vn(e,t){e=e|0;t=t|0;var n=0;n=e+32|0;if((o[n>>2]|0)!=(t|0)){o[n>>2]=t;Vt(e)}return}function mn(e){e=e|0;return o[e+32>>2]|0}function gn(e,t){e=e|0;t=t|0;var n=0;n=e+36|0;if((o[n>>2]|0)!=(t|0)){o[n>>2]=t;Vt(e)}return}function yn(e){e=e|0;return o[e+36>>2]|0}function _n(e,t){e=e|0;t=Y(t);var n=0;n=e+40|0;if(Y(s[n>>2])!=t){s[n>>2]=t;Vt(e)}return}function bn(e,t){e=e|0;t=Y(t);var n=0;n=e+44|0;if(Y(s[n>>2])!=t){s[n>>2]=t;Vt(e)}return}function wn(e,t){e=e|0;t=Y(t);var n=0;n=e+48|0;if(Y(s[n>>2])!=t){s[n>>2]=t;Vt(e)}return}function En(e,t){e=e|0;t=Y(t);var n=0,r=0,i=0,u=0;u=wt(t)|0;n=(u^1)&1;r=e+52|0;i=e+56|0;if(!(u|Y(s[r>>2])==t?(o[i>>2]|0)==(n|0):0)){s[r>>2]=t;o[i>>2]=n;Vt(e)}return}function Dn(e,t){e=e|0;t=Y(t);var n=0,r=0;r=e+52|0;n=e+56|0;if(!(!(Y(s[r>>2])!=t)?(o[n>>2]|0)==2:0)){s[r>>2]=t;r=wt(t)|0;o[n>>2]=r?3:2;Vt(e)}return}function Sn(e,t){e=e|0;t=t|0;var n=0,r=0;r=t+52|0;n=o[r+4>>2]|0;t=e;o[t>>2]=o[r>>2];o[t+4>>2]=n;return}function Cn(e,t,n){e=e|0;t=t|0;n=Y(n);var r=0,i=0,u=0;u=wt(n)|0;r=(u^1)&1;i=e+132+(t<<3)|0;t=e+132+(t<<3)+4|0;if(!(u|Y(s[i>>2])==n?(o[t>>2]|0)==(r|0):0)){s[i>>2]=n;o[t>>2]=r;Vt(e)}return}function kn(e,t,n){e=e|0;t=t|0;n=Y(n);var r=0,i=0,u=0;u=wt(n)|0;r=u?0:2;i=e+132+(t<<3)|0;t=e+132+(t<<3)+4|0;if(!(u|Y(s[i>>2])==n?(o[t>>2]|0)==(r|0):0)){s[i>>2]=n;o[t>>2]=r;Vt(e)}return}function Tn(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=t+132+(n<<3)|0;t=o[r+4>>2]|0;n=e;o[n>>2]=o[r>>2];o[n+4>>2]=t;return}function xn(e,t,n){e=e|0;t=t|0;n=Y(n);var r=0,i=0,u=0;u=wt(n)|0;r=(u^1)&1;i=e+60+(t<<3)|0;t=e+60+(t<<3)+4|0;if(!(u|Y(s[i>>2])==n?(o[t>>2]|0)==(r|0):0)){s[i>>2]=n;o[t>>2]=r;Vt(e)}return}function An(e,t,n){e=e|0;t=t|0;n=Y(n);var r=0,i=0,u=0;u=wt(n)|0;r=u?0:2;i=e+60+(t<<3)|0;t=e+60+(t<<3)+4|0;if(!(u|Y(s[i>>2])==n?(o[t>>2]|0)==(r|0):0)){s[i>>2]=n;o[t>>2]=r;Vt(e)}return}function On(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=t+60+(n<<3)|0;t=o[r+4>>2]|0;n=e;o[n>>2]=o[r>>2];o[n+4>>2]=t;return}function Pn(e,t){e=e|0;t=t|0;var n=0;n=e+60+(t<<3)+4|0;if((o[n>>2]|0)!=3){s[e+60+(t<<3)>>2]=Y(E);o[n>>2]=3;Vt(e)}return}function In(e,t,n){e=e|0;t=t|0;n=Y(n);var r=0,i=0,u=0;u=wt(n)|0;r=(u^1)&1;i=e+204+(t<<3)|0;t=e+204+(t<<3)+4|0;if(!(u|Y(s[i>>2])==n?(o[t>>2]|0)==(r|0):0)){s[i>>2]=n;o[t>>2]=r;Vt(e)}return}function Nn(e,t,n){e=e|0;t=t|0;n=Y(n);var r=0,i=0,u=0;u=wt(n)|0;r=u?0:2;i=e+204+(t<<3)|0;t=e+204+(t<<3)+4|0;if(!(u|Y(s[i>>2])==n?(o[t>>2]|0)==(r|0):0)){s[i>>2]=n;o[t>>2]=r;Vt(e)}return}function Mn(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=t+204+(n<<3)|0;t=o[r+4>>2]|0;n=e;o[n>>2]=o[r>>2];o[n+4>>2]=t;return}function Rn(e,t,n){e=e|0;t=t|0;n=Y(n);var r=0,i=0,u=0;u=wt(n)|0;r=(u^1)&1;i=e+276+(t<<3)|0;t=e+276+(t<<3)+4|0;if(!(u|Y(s[i>>2])==n?(o[t>>2]|0)==(r|0):0)){s[i>>2]=n;o[t>>2]=r;Vt(e)}return}function Fn(e,t){e=e|0;t=t|0;return Y(s[e+276+(t<<3)>>2])}function Ln(e,t){e=e|0;t=Y(t);var n=0,r=0,i=0,u=0;u=wt(t)|0;n=(u^1)&1;r=e+348|0;i=e+352|0;if(!(u|Y(s[r>>2])==t?(o[i>>2]|0)==(n|0):0)){s[r>>2]=t;o[i>>2]=n;Vt(e)}return}function Bn(e,t){e=e|0;t=Y(t);var n=0,r=0;r=e+348|0;n=e+352|0;if(!(!(Y(s[r>>2])!=t)?(o[n>>2]|0)==2:0)){s[r>>2]=t;r=wt(t)|0;o[n>>2]=r?3:2;Vt(e)}return}function jn(e){e=e|0;var t=0;t=e+352|0;if((o[t>>2]|0)!=3){s[e+348>>2]=Y(E);o[t>>2]=3;Vt(e)}return}function Un(e,t){e=e|0;t=t|0;var n=0,r=0;r=t+348|0;n=o[r+4>>2]|0;t=e;o[t>>2]=o[r>>2];o[t+4>>2]=n;return}function zn(e,t){e=e|0;t=Y(t);var n=0,r=0,i=0,u=0;u=wt(t)|0;n=(u^1)&1;r=e+356|0;i=e+360|0;if(!(u|Y(s[r>>2])==t?(o[i>>2]|0)==(n|0):0)){s[r>>2]=t;o[i>>2]=n;Vt(e)}return}function Wn(e,t){e=e|0;t=Y(t);var n=0,r=0;r=e+356|0;n=e+360|0;if(!(!(Y(s[r>>2])!=t)?(o[n>>2]|0)==2:0)){s[r>>2]=t;r=wt(t)|0;o[n>>2]=r?3:2;Vt(e)}return}function Hn(e){e=e|0;var t=0;t=e+360|0;if((o[t>>2]|0)!=3){s[e+356>>2]=Y(E);o[t>>2]=3;Vt(e)}return}function Vn(e,t){e=e|0;t=t|0;var n=0,r=0;r=t+356|0;n=o[r+4>>2]|0;t=e;o[t>>2]=o[r>>2];o[t+4>>2]=n;return}function qn(e,t){e=e|0;t=Y(t);var n=0,r=0,i=0,u=0;u=wt(t)|0;n=(u^1)&1;r=e+364|0;i=e+368|0;if(!(u|Y(s[r>>2])==t?(o[i>>2]|0)==(n|0):0)){s[r>>2]=t;o[i>>2]=n;Vt(e)}return}function Gn(e,t){e=e|0;t=Y(t);var n=0,r=0,i=0,u=0;u=wt(t)|0;n=u?0:2;r=e+364|0;i=e+368|0;if(!(u|Y(s[r>>2])==t?(o[i>>2]|0)==(n|0):0)){s[r>>2]=t;o[i>>2]=n;Vt(e)}return}function $n(e,t){e=e|0;t=t|0;var n=0,r=0;r=t+364|0;n=o[r+4>>2]|0;t=e;o[t>>2]=o[r>>2];o[t+4>>2]=n;return}function Yn(e,t){e=e|0;t=Y(t);var n=0,r=0,i=0,u=0;u=wt(t)|0;n=(u^1)&1;r=e+372|0;i=e+376|0;if(!(u|Y(s[r>>2])==t?(o[i>>2]|0)==(n|0):0)){s[r>>2]=t;o[i>>2]=n;Vt(e)}return}function Kn(e,t){e=e|0;t=Y(t);var n=0,r=0,i=0,u=0;u=wt(t)|0;n=u?0:2;r=e+372|0;i=e+376|0;if(!(u|Y(s[r>>2])==t?(o[i>>2]|0)==(n|0):0)){s[r>>2]=t;o[i>>2]=n;Vt(e)}return}function Xn(e,t){e=e|0;t=t|0;var n=0,r=0;r=t+372|0;n=o[r+4>>2]|0;t=e;o[t>>2]=o[r>>2];o[t+4>>2]=n;return}function Qn(e,t){e=e|0;t=Y(t);var n=0,r=0,i=0,u=0;u=wt(t)|0;n=(u^1)&1;r=e+380|0;i=e+384|0;if(!(u|Y(s[r>>2])==t?(o[i>>2]|0)==(n|0):0)){s[r>>2]=t;o[i>>2]=n;Vt(e)}return}function Jn(e,t){e=e|0;t=Y(t);var n=0,r=0,i=0,u=0;u=wt(t)|0;n=u?0:2;r=e+380|0;i=e+384|0;if(!(u|Y(s[r>>2])==t?(o[i>>2]|0)==(n|0):0)){s[r>>2]=t;o[i>>2]=n;Vt(e)}return}function Zn(e,t){e=e|0;t=t|0;var n=0,r=0;r=t+380|0;n=o[r+4>>2]|0;t=e;o[t>>2]=o[r>>2];o[t+4>>2]=n;return}function er(e,t){e=e|0;t=Y(t);var n=0,r=0,i=0,u=0;u=wt(t)|0;n=(u^1)&1;r=e+388|0;i=e+392|0;if(!(u|Y(s[r>>2])==t?(o[i>>2]|0)==(n|0):0)){s[r>>2]=t;o[i>>2]=n;Vt(e)}return}function tr(e,t){e=e|0;t=Y(t);var n=0,r=0,i=0,u=0;u=wt(t)|0;n=u?0:2;r=e+388|0;i=e+392|0;if(!(u|Y(s[r>>2])==t?(o[i>>2]|0)==(n|0):0)){s[r>>2]=t;o[i>>2]=n;Vt(e)}return}function nr(e,t){e=e|0;t=t|0;var n=0,r=0;r=t+388|0;n=o[r+4>>2]|0;t=e;o[t>>2]=o[r>>2];o[t+4>>2]=n;return}function rr(e,t){e=e|0;t=Y(t);var n=0;n=e+396|0;if(Y(s[n>>2])!=t){s[n>>2]=t;Vt(e)}return}function ir(e){e=e|0;return Y(s[e+396>>2])}function or(e){e=e|0;return Y(s[e+400>>2])}function ur(e){e=e|0;return Y(s[e+404>>2])}function ar(e){e=e|0;return Y(s[e+408>>2])}function lr(e){e=e|0;return Y(s[e+412>>2])}function sr(e){e=e|0;return Y(s[e+416>>2])}function cr(e){e=e|0;return Y(s[e+420>>2])}function fr(e,t){e=e|0;t=t|0;Rt(e,(t|0)<6,2918);switch(t|0){case 0:{t=(o[e+496>>2]|0)==2?5:4;break}case 2:{t=(o[e+496>>2]|0)==2?4:5;break}default:{}}return Y(s[e+424+(t<<2)>>2])}function dr(e,t){e=e|0;t=t|0;Rt(e,(t|0)<6,2918);switch(t|0){case 0:{t=(o[e+496>>2]|0)==2?5:4;break}case 2:{t=(o[e+496>>2]|0)==2?4:5;break}default:{}}return Y(s[e+448+(t<<2)>>2])}function pr(e,t){e=e|0;t=t|0;Rt(e,(t|0)<6,2918);switch(t|0){case 0:{t=(o[e+496>>2]|0)==2?5:4;break}case 2:{t=(o[e+496>>2]|0)==2?4:5;break}default:{}}return Y(s[e+472+(t<<2)>>2])}function hr(e,t){e=e|0;t=t|0;var n=0,r=ft;n=o[e+4>>2]|0;if((n|0)==(o[t+4>>2]|0)){if(!n)e=1;else{r=Y(s[e>>2]);e=Y(P(Y(r-Y(s[t>>2]))))>2]=0;o[i+4>>2]=0;o[i+8>>2]=0;je(i|0,e|0,t|0,0);Cr(e,3,(r[i+11>>0]|0)<0?o[i>>2]|0:i,n);XT(i);h=n;return}function yr(e,t,n,r){e=Y(e);t=Y(t);n=n|0;r=r|0;var i=ft;e=Y(e*t);i=Y(LT(e,Y(1.0)));do{if(!(vr(i,Y(0.0))|0)){e=Y(e-i);if(vr(i,Y(1.0))|0){e=Y(e+Y(1.0));break}if(n){e=Y(e+Y(1.0));break}if(!r){if(i>Y(.5))i=Y(1.0);else{r=vr(i,Y(.5))|0;i=r?Y(1.0):Y(0.0)}e=Y(e+i)}}else e=Y(e-i)}while(0);return Y(e/t)}function _r(e,t,n,r,i,o,u,a,l,c,f,d,p){e=e|0;t=Y(t);n=n|0;r=Y(r);i=i|0;o=Y(o);u=u|0;a=Y(a);l=Y(l);c=Y(c);f=Y(f);d=Y(d);p=p|0;var h=0,v=ft,m=ft,g=ft,y=ft,_=ft,b=ft;if(l>2]),v!=Y(0.0)):0){g=Y(yr(t,v,0,0));y=Y(yr(r,v,0,0));m=Y(yr(o,v,0,0));v=Y(yr(a,v,0,0))}else{m=o;g=t;v=a;y=r}if((i|0)==(e|0))h=vr(m,g)|0;else h=0;if((u|0)==(n|0))p=vr(v,y)|0;else p=0;if((!h?(_=Y(t-f),!(br(e,_,l)|0)):0)?!(wr(e,_,i,l)|0):0)h=Er(e,_,i,o,l)|0;else h=1;if((!p?(b=Y(r-d),!(br(n,b,c)|0)):0)?!(wr(n,b,u,c)|0):0)p=Er(n,b,u,a,c)|0;else p=1;p=h&p}return p|0}function br(e,t,n){e=e|0;t=Y(t);n=Y(n);if((e|0)==1)e=vr(t,n)|0;else e=0;return e|0}function wr(e,t,n,r){e=e|0;t=Y(t);n=n|0;r=Y(r);if((e|0)==2&(n|0)==0){if(!(t>=r))e=vr(t,r)|0;else e=1}else e=0;return e|0}function Er(e,t,n,r,i){e=e|0;t=Y(t);n=n|0;r=Y(r);i=Y(i);if((e|0)==2&(n|0)==2&r>t){if(!(i<=t))e=vr(t,i)|0;else e=1}else e=0;return e|0}function Dr(e,t,n,i,u,a,l,f,d,p,v){e=e|0;t=Y(t);n=Y(n);i=i|0;u=u|0;a=a|0;l=Y(l);f=Y(f);d=d|0;p=p|0;v=v|0;var m=0,g=0,y=0,_=0,b=ft,w=ft,E=0,D=0,S=0,C=0,k=0,T=0,x=0,A=0,O=0,P=0,I=0,N=ft,M=ft,R=ft,F=0.0,L=0.0;I=h;h=h+160|0;A=I+152|0;x=I+120|0;T=I+104|0;S=I+72|0;_=I+56|0;k=I+8|0;D=I;C=(o[2279]|0)+1|0;o[2279]=C;O=e+984|0;if((r[O>>0]|0)!=0?(o[e+512>>2]|0)!=(o[2278]|0):0)E=4;else if((o[e+516>>2]|0)==(i|0))P=0;else E=4;if((E|0)==4){o[e+520>>2]=0;o[e+924>>2]=-1;o[e+928>>2]=-1;s[e+932>>2]=Y(-1.0);s[e+936>>2]=Y(-1.0);P=1}e:do{if(!(o[e+964>>2]|0)){if(d){m=e+916|0;if(!(vr(Y(s[m>>2]),t)|0)){E=21;break}if(!(vr(Y(s[e+920>>2]),n)|0)){E=21;break}if((o[e+924>>2]|0)!=(u|0)){E=21;break}m=(o[e+928>>2]|0)==(a|0)?m:0;E=22;break}y=o[e+520>>2]|0;if(!y)E=21;else{g=0;while(1){m=e+524+(g*24|0)|0;if(((vr(Y(s[m>>2]),t)|0?vr(Y(s[e+524+(g*24|0)+4>>2]),n)|0:0)?(o[e+524+(g*24|0)+8>>2]|0)==(u|0):0)?(o[e+524+(g*24|0)+12>>2]|0)==(a|0):0){E=22;break e}g=g+1|0;if(g>>>0>=y>>>0){E=21;break}}}}else{b=Y(Sr(e,2,l));w=Y(Sr(e,0,l));m=e+916|0;R=Y(s[m>>2]);M=Y(s[e+920>>2]);N=Y(s[e+932>>2]);if(!(_r(u,t,a,n,o[e+924>>2]|0,R,o[e+928>>2]|0,M,N,Y(s[e+936>>2]),b,w,v)|0)){y=o[e+520>>2]|0;if(!y)E=21;else{g=0;while(1){m=e+524+(g*24|0)|0;N=Y(s[m>>2]);M=Y(s[e+524+(g*24|0)+4>>2]);R=Y(s[e+524+(g*24|0)+16>>2]);if(_r(u,t,a,n,o[e+524+(g*24|0)+8>>2]|0,N,o[e+524+(g*24|0)+12>>2]|0,M,R,Y(s[e+524+(g*24|0)+20>>2]),b,w,v)|0){E=22;break e}g=g+1|0;if(g>>>0>=y>>>0){E=21;break}}}}else E=22}}while(0);do{if((E|0)==21){if(!(r[11697]|0)){m=0;E=31}else{m=0;E=28}}else if((E|0)==22){g=(r[11697]|0)!=0;if(!((m|0)!=0&(P^1)))if(g){E=28;break}else{E=31;break}_=m+16|0;o[e+908>>2]=o[_>>2];y=m+20|0;o[e+912>>2]=o[y>>2];if(!((r[11698]|0)==0|g^1)){o[D>>2]=kr(C)|0;o[D+4>>2]=C;Cr(e,4,2972,D);g=o[e+972>>2]|0;if(g|0)hA[g&127](e);u=Tr(u,d)|0;a=Tr(a,d)|0;L=+Y(s[_>>2]);F=+Y(s[y>>2]);o[k>>2]=u;o[k+4>>2]=a;c[k+8>>3]=+t;c[k+16>>3]=+n;c[k+24>>3]=L;c[k+32>>3]=F;o[k+40>>2]=p;Cr(e,4,2989,k)}}}while(0);if((E|0)==28){g=kr(C)|0;o[_>>2]=g;o[_+4>>2]=C;o[_+8>>2]=P?3047:11699;Cr(e,4,3038,_);g=o[e+972>>2]|0;if(g|0)hA[g&127](e);k=Tr(u,d)|0;E=Tr(a,d)|0;o[S>>2]=k;o[S+4>>2]=E;c[S+8>>3]=+t;c[S+16>>3]=+n;o[S+24>>2]=p;Cr(e,4,3049,S);E=31}if((E|0)==31){xr(e,t,n,i,u,a,l,f,d,v);if(r[11697]|0){g=o[2279]|0;k=kr(g)|0;o[T>>2]=k;o[T+4>>2]=g;o[T+8>>2]=P?3047:11699;Cr(e,4,3083,T);g=o[e+972>>2]|0;if(g|0)hA[g&127](e);k=Tr(u,d)|0;T=Tr(a,d)|0;F=+Y(s[e+908>>2]);L=+Y(s[e+912>>2]);o[x>>2]=k;o[x+4>>2]=T;c[x+8>>3]=F;c[x+16>>3]=L;o[x+24>>2]=p;Cr(e,4,3092,x)}o[e+516>>2]=i;if(!m){g=e+520|0;m=o[g>>2]|0;if((m|0)==16){if(r[11697]|0)Cr(e,4,3124,A);o[g>>2]=0;m=0}if(d)m=e+916|0;else{o[g>>2]=m+1;m=e+524+(m*24|0)|0}s[m>>2]=t;s[m+4>>2]=n;o[m+8>>2]=u;o[m+12>>2]=a;o[m+16>>2]=o[e+908>>2];o[m+20>>2]=o[e+912>>2];m=0}}if(d){o[e+416>>2]=o[e+908>>2];o[e+420>>2]=o[e+912>>2];r[e+985>>0]=1;r[O>>0]=0}o[2279]=(o[2279]|0)+-1;o[e+512>>2]=o[2278];h=I;return P|(m|0)==0|0}function Sr(e,t,n){e=e|0;t=t|0;n=Y(n);var r=ft;r=Y(Vr(e,t,n));return Y(r+Y(qr(e,t,n)))}function Cr(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=h;h=h+16|0;i=u;o[i>>2]=r;if(!e)r=0;else r=o[e+976>>2]|0;Br(r,e,t,n,i);h=u;return}function kr(e){e=e|0;return(e>>>0>60?3201:3201+(60-e)|0)|0}function Tr(e,t){e=e|0;t=t|0;var n=0,r=0,i=0;i=h;h=h+32|0;n=i+12|0;r=i;o[n>>2]=o[254];o[n+4>>2]=o[255];o[n+8>>2]=o[256];o[r>>2]=o[257];o[r+4>>2]=o[258];o[r+8>>2]=o[259];if((e|0)>2)e=11699;else e=o[(t?r:n)+(e<<2)>>2]|0;h=i;return e|0}function xr(e,t,n,i,a,l,c,f,p,v){e=e|0;t=Y(t);n=Y(n);i=i|0;a=a|0;l=l|0;c=Y(c);f=Y(f);p=p|0;v=v|0;var m=0,g=0,y=0,_=0,b=ft,w=ft,E=ft,D=ft,S=ft,C=ft,k=ft,T=0,x=0,A=0,O=ft,P=ft,I=0,N=ft,M=0,R=0,F=0,L=0,B=0,j=0,U=0,z=0,W=0,H=0,V=0,q=0,G=0,$=0,K=0,X=0,Q=0,J=0,Z=ft,ee=ft,te=ft,ne=ft,re=ft,ie=0,oe=0,ue=0,ae=0,le=0,se=ft,ce=ft,fe=ft,de=ft,pe=ft,he=ft,ve=0,me=ft,ge=ft,ye=ft,_e=ft,be=ft,we=ft,Ee=0,De=0,Se=ft,Ce=ft,ke=0,Te=0,xe=0,Ae=0,Oe=ft,Pe=0,Ie=0,Ne=0,Me=0,Re=0,Fe=0,Le=0,Be=ft,je=0,Ue=0;Le=h;h=h+16|0;ie=Le+12|0;oe=Le+8|0;ue=Le+4|0;ae=Le;Rt(e,(a|0)==0|(wt(t)|0)^1,3326);Rt(e,(l|0)==0|(wt(n)|0)^1,3406);Ie=Yr(e,i)|0;o[e+496>>2]=Ie;Re=Kr(2,Ie)|0;Fe=Kr(0,Ie)|0;s[e+440>>2]=Y(Vr(e,Re,c));s[e+444>>2]=Y(qr(e,Re,c));s[e+428>>2]=Y(Vr(e,Fe,c));s[e+436>>2]=Y(qr(e,Fe,c));s[e+464>>2]=Y(Xr(e,Re));s[e+468>>2]=Y(Qr(e,Re));s[e+452>>2]=Y(Xr(e,Fe));s[e+460>>2]=Y(Qr(e,Fe));s[e+488>>2]=Y(Jr(e,Re,c));s[e+492>>2]=Y(Zr(e,Re,c));s[e+476>>2]=Y(Jr(e,Fe,c));s[e+484>>2]=Y(Zr(e,Fe,c));do{if(!(o[e+964>>2]|0)){Ne=e+948|0;Me=(o[e+952>>2]|0)-(o[Ne>>2]|0)>>2;if(!Me){ti(e,t,n,a,l,c,f);break}if(!p?ni(e,t,n,a,l,c,f)|0:0)break;Wt(e);X=e+508|0;r[X>>0]=0;Re=Kr(o[e+4>>2]|0,Ie)|0;Fe=ri(Re,Ie)|0;Pe=Gr(Re)|0;Q=o[e+8>>2]|0;Te=e+28|0;J=(o[Te>>2]|0)!=0;be=Pe?c:f;Se=Pe?f:c;Z=Y(ii(e,Re,c));ee=Y(oi(e,Re,c));b=Y(ii(e,Fe,c));we=Y(ui(e,Re,c));Ce=Y(ui(e,Fe,c));A=Pe?a:l;ke=Pe?l:a;Oe=Pe?we:Ce;S=Pe?Ce:we;_e=Y(Sr(e,2,c));D=Y(Sr(e,0,c));w=Y(Y(Nr(e+364|0,c))-Oe);E=Y(Y(Nr(e+380|0,c))-Oe);C=Y(Y(Nr(e+372|0,f))-S);k=Y(Y(Nr(e+388|0,f))-S);te=Pe?w:C;ne=Pe?E:k;_e=Y(t-_e);t=Y(_e-Oe);if(wt(t)|0)Oe=t;else Oe=Y(NT(Y(RT(t,E)),w));ge=Y(n-D);t=Y(ge-S);if(wt(t)|0)ye=t;else ye=Y(NT(Y(RT(t,k)),C));w=Pe?Oe:ye;me=Pe?ye:Oe;e:do{if((A|0)==1){i=0;g=0;while(1){m=Pt(e,g)|0;if(!i){if(Y(li(m))>Y(0.0)?Y(si(m))>Y(0.0):0)i=m;else i=0}else if(ai(m)|0){_=0;break e}g=g+1|0;if(g>>>0>=Me>>>0){_=i;break}}}else _=0}while(0);T=_+500|0;x=_+504|0;i=0;m=0;t=Y(0.0);y=0;do{g=o[(o[Ne>>2]|0)+(y<<2)>>2]|0;if((o[g+36>>2]|0)==1){ci(g);r[g+985>>0]=1;r[g+984>>0]=0}else{Pr(g);if(p)Mr(g,Yr(g,Ie)|0,w,me,Oe);do{if((o[g+24>>2]|0)!=1){if((g|0)==(_|0)){o[T>>2]=o[2278];s[x>>2]=Y(0.0);break}else{fi(e,g,Oe,a,ye,Oe,ye,l,Ie,v);break}}else{if(m|0)o[m+960>>2]=g;o[g+960>>2]=0;m=g;i=(i|0)==0?g:i}}while(0);he=Y(s[g+504>>2]);t=Y(t+Y(he+Y(Sr(g,Re,Oe))))}y=y+1|0}while((y|0)!=(Me|0));F=t>w;ve=J&((A|0)==2&F)?1:A;M=(ke|0)==1;B=M&(p^1);j=(ve|0)==1;U=(ve|0)==2;z=976+(Re<<2)|0;W=(ke|2|0)==2;$=M&(J^1);H=1040+(Fe<<2)|0;V=1040+(Re<<2)|0;q=976+(Fe<<2)|0;G=(ke|0)!=1;F=J&((A|0)!=0&F);R=e+976|0;M=M^1;t=w;I=0;L=0;he=Y(0.0);re=Y(0.0);while(1){e:do{if(I>>>0>>0){x=o[Ne>>2]|0;y=0;k=Y(0.0);C=Y(0.0);E=Y(0.0);w=Y(0.0);g=0;m=0;_=I;while(1){T=o[x+(_<<2)>>2]|0;if((o[T+36>>2]|0)!=1?(o[T+940>>2]=L,(o[T+24>>2]|0)!=1):0){D=Y(Sr(T,Re,Oe));K=o[z>>2]|0;n=Y(Nr(T+380+(K<<3)|0,be));S=Y(s[T+504>>2]);n=Y(RT(n,S));n=Y(NT(Y(Nr(T+364+(K<<3)|0,be)),n));if(J&(y|0)!=0&Y(D+Y(C+n))>t){l=y;D=k;A=_;break e}D=Y(D+n);n=Y(C+D);D=Y(k+D);if(ai(T)|0){E=Y(E+Y(li(T)));w=Y(w-Y(S*Y(si(T))))}if(m|0)o[m+960>>2]=T;o[T+960>>2]=0;y=y+1|0;m=T;g=(g|0)==0?T:g}else{D=k;n=C}_=_+1|0;if(_>>>0>>0){k=D;C=n}else{l=y;A=_;break}}}else{l=0;D=Y(0.0);E=Y(0.0);w=Y(0.0);g=0;A=I}}while(0);K=E>Y(0.0)&EY(0.0)&wne&((wt(ne)|0)^1))){if(!(r[(o[R>>2]|0)+3>>0]|0)){if(!(O==Y(0.0))?!(Y(li(e))==Y(0.0)):0){K=53;break}t=D;K=53}else K=51}else{t=ne;K=51}}else{t=te;K=51}}else K=51}while(0);if((K|0)==51){K=0;if(wt(t)|0)K=53;else{P=Y(t-D);N=t}}if((K|0)==53){K=0;if(D>2]|0;_=PY(0.0);C=Y(P/O);E=Y(0.0);D=Y(0.0);t=Y(0.0);m=g;do{n=Y(Nr(m+380+(y<<3)|0,be));w=Y(Nr(m+364+(y<<3)|0,be));w=Y(RT(n,Y(NT(w,Y(s[m+504>>2])))));if(_){n=Y(w*Y(si(m)));if(n!=Y(-0.0)?(Be=Y(w-Y(S*n)),se=Y(di(m,Re,Be,N,Oe)),Be!=se):0){E=Y(E-Y(se-w));t=Y(t+n)}}else if((T?(ce=Y(li(m)),ce!=Y(0.0)):0)?(Be=Y(w+Y(C*ce)),fe=Y(di(m,Re,Be,N,Oe)),Be!=fe):0){E=Y(E-Y(fe-w));D=Y(D-ce)}m=o[m+960>>2]|0}while((m|0)!=0);t=Y(k+t);w=Y(P+E);if(!le){S=Y(O+D);_=o[z>>2]|0;T=wY(0.0);S=Y(w/S);t=Y(0.0);do{Be=Y(Nr(g+380+(_<<3)|0,be));E=Y(Nr(g+364+(_<<3)|0,be));E=Y(RT(Be,Y(NT(E,Y(s[g+504>>2])))));if(T){Be=Y(E*Y(si(g)));w=Y(-Be);if(Be!=Y(-0.0)){Be=Y(C*w);w=Y(di(g,Re,Y(E+(x?w:Be)),N,Oe))}else w=E}else if(y?(de=Y(li(g)),de!=Y(0.0)):0)w=Y(di(g,Re,Y(E+Y(S*de)),N,Oe));else w=E;t=Y(t-Y(w-E));D=Y(Sr(g,Re,Oe));n=Y(Sr(g,Fe,Oe));w=Y(w+D);s[oe>>2]=w;o[ae>>2]=1;E=Y(s[g+396>>2]);e:do{if(wt(E)|0){m=wt(me)|0;do{if(!m){if(F|(Ir(g,Fe,me)|0|M))break;if((pi(e,g)|0)!=4)break;if((o[(hi(g,Fe)|0)+4>>2]|0)==3)break;if((o[(vi(g,Fe)|0)+4>>2]|0)==3)break;s[ie>>2]=me;o[ue>>2]=1;break e}}while(0);if(Ir(g,Fe,me)|0){m=o[g+992+(o[q>>2]<<2)>>2]|0;Be=Y(n+Y(Nr(m,me)));s[ie>>2]=Be;m=G&(o[m+4>>2]|0)==2;o[ue>>2]=((wt(Be)|0|m)^1)&1;break}else{s[ie>>2]=me;o[ue>>2]=m?0:2;break}}else{Be=Y(w-D);O=Y(Be/E);Be=Y(E*Be);o[ue>>2]=1;s[ie>>2]=Y(n+(Pe?O:Be))}}while(0);mi(g,Re,N,Oe,ae,oe);mi(g,Fe,me,Oe,ue,ie);do{if(!(Ir(g,Fe,me)|0)?(pi(e,g)|0)==4:0){if((o[(hi(g,Fe)|0)+4>>2]|0)==3){m=0;break}m=(o[(vi(g,Fe)|0)+4>>2]|0)!=3}else m=0}while(0);Be=Y(s[oe>>2]);O=Y(s[ie>>2]);je=o[ae>>2]|0;Ue=o[ue>>2]|0;Dr(g,Pe?Be:O,Pe?O:Be,Ie,Pe?je:Ue,Pe?Ue:je,Oe,ye,p&(m^1),3488,v)|0;r[X>>0]=r[X>>0]|r[g+508>>0];g=o[g+960>>2]|0}while((g|0)!=0)}else t=Y(0.0)}else t=Y(0.0);t=Y(P+t);Ue=t>0]=Ue|u[X>>0];if(U&t>Y(0.0)){m=o[z>>2]|0;if((o[e+364+(m<<3)+4>>2]|0)!=0?(pe=Y(Nr(e+364+(m<<3)|0,be)),pe>=Y(0.0)):0)w=Y(NT(Y(0.0),Y(pe-Y(N-t))));else w=Y(0.0)}else w=t;T=I>>>0>>0;if(T){_=o[Ne>>2]|0;y=I;m=0;do{g=o[_+(y<<2)>>2]|0;if(!(o[g+24>>2]|0)){m=((o[(hi(g,Re)|0)+4>>2]|0)==3&1)+m|0;m=m+((o[(vi(g,Re)|0)+4>>2]|0)==3&1)|0}y=y+1|0}while((y|0)!=(A|0));if(m){D=Y(0.0);n=Y(0.0)}else K=101}else K=101;e:do{if((K|0)==101){K=0;switch(Q|0){case 1:{m=0;D=Y(w*Y(.5));n=Y(0.0);break e}case 2:{m=0;D=w;n=Y(0.0);break e}case 3:{if(l>>>0<=1){m=0;D=Y(0.0);n=Y(0.0);break e}n=Y((l+-1|0)>>>0);m=0;D=Y(0.0);n=Y(Y(NT(w,Y(0.0)))/n);break e}case 5:{n=Y(w/Y((l+1|0)>>>0));m=0;D=n;break e}case 4:{n=Y(w/Y(l>>>0));m=0;D=Y(n*Y(.5));break e}default:{m=0;D=Y(0.0);n=Y(0.0);break e}}}}while(0);t=Y(Z+D);if(T){E=Y(w/Y(m|0));y=o[Ne>>2]|0;g=I;w=Y(0.0);do{m=o[y+(g<<2)>>2]|0;e:do{if((o[m+36>>2]|0)!=1){switch(o[m+24>>2]|0){case 1:{if(gi(m,Re)|0){if(!p)break e;Be=Y(yi(m,Re,N));Be=Y(Be+Y(Xr(e,Re)));Be=Y(Be+Y(Vr(m,Re,Oe)));s[m+400+(o[V>>2]<<2)>>2]=Be;break e}break}case 0:{Ue=(o[(hi(m,Re)|0)+4>>2]|0)==3;Be=Y(E+t);t=Ue?Be:t;if(p){Ue=m+400+(o[V>>2]<<2)|0;s[Ue>>2]=Y(t+Y(s[Ue>>2]))}Ue=(o[(vi(m,Re)|0)+4>>2]|0)==3;Be=Y(E+t);t=Ue?Be:t;if(B){Be=Y(n+Y(Sr(m,Re,Oe)));w=me;t=Y(t+Y(Be+Y(s[m+504>>2])));break e}else{t=Y(t+Y(n+Y(_i(m,Re,Oe))));w=Y(NT(w,Y(_i(m,Fe,Oe))));break e}}default:{}}if(p){Be=Y(D+Y(Xr(e,Re)));Ue=m+400+(o[V>>2]<<2)|0;s[Ue>>2]=Y(Be+Y(s[Ue>>2]))}}}while(0);g=g+1|0}while((g|0)!=(A|0))}else w=Y(0.0);n=Y(ee+t);if(W)D=Y(Y(di(e,Fe,Y(Ce+w),Se,c))-Ce);else D=me;E=Y(Y(di(e,Fe,Y(Ce+($?me:w)),Se,c))-Ce);if(T&p){g=I;do{y=o[(o[Ne>>2]|0)+(g<<2)>>2]|0;do{if((o[y+36>>2]|0)!=1){if((o[y+24>>2]|0)==1){if(gi(y,Fe)|0){Be=Y(yi(y,Fe,me));Be=Y(Be+Y(Xr(e,Fe)));Be=Y(Be+Y(Vr(y,Fe,Oe)));m=o[H>>2]|0;s[y+400+(m<<2)>>2]=Be;if(!(wt(Be)|0))break}else m=o[H>>2]|0;Be=Y(Xr(e,Fe));s[y+400+(m<<2)>>2]=Y(Be+Y(Vr(y,Fe,Oe)));break}m=pi(e,y)|0;do{if((m|0)==4){if((o[(hi(y,Fe)|0)+4>>2]|0)==3){K=139;break}if((o[(vi(y,Fe)|0)+4>>2]|0)==3){K=139;break}if(Ir(y,Fe,me)|0){t=b;break}je=o[y+908+(o[z>>2]<<2)>>2]|0;o[ie>>2]=je;t=Y(s[y+396>>2]);Ue=wt(t)|0;w=(o[d>>2]=je,Y(s[d>>2]));if(Ue)t=E;else{P=Y(Sr(y,Fe,Oe));Be=Y(w/t);t=Y(t*w);t=Y(P+(Pe?Be:t))}s[oe>>2]=t;s[ie>>2]=Y(Y(Sr(y,Re,Oe))+w);o[ue>>2]=1;o[ae>>2]=1;mi(y,Re,N,Oe,ue,ie);mi(y,Fe,me,Oe,ae,oe);t=Y(s[ie>>2]);P=Y(s[oe>>2]);Be=Pe?t:P;t=Pe?P:t;Ue=((wt(Be)|0)^1)&1;Dr(y,Be,t,Ie,Ue,((wt(t)|0)^1)&1,Oe,ye,1,3493,v)|0;t=b}else K=139}while(0);e:do{if((K|0)==139){K=0;t=Y(D-Y(_i(y,Fe,Oe)));do{if((o[(hi(y,Fe)|0)+4>>2]|0)==3){if((o[(vi(y,Fe)|0)+4>>2]|0)!=3)break;t=Y(b+Y(NT(Y(0.0),Y(t*Y(.5)))));break e}}while(0);if((o[(vi(y,Fe)|0)+4>>2]|0)==3){t=b;break}if((o[(hi(y,Fe)|0)+4>>2]|0)==3){t=Y(b+Y(NT(Y(0.0),t)));break}switch(m|0){case 1:{t=b;break e}case 2:{t=Y(b+Y(t*Y(.5)));break e}default:{t=Y(b+t);break e}}}}while(0);Be=Y(he+t);Ue=y+400+(o[H>>2]<<2)|0;s[Ue>>2]=Y(Be+Y(s[Ue>>2]))}}while(0);g=g+1|0}while((g|0)!=(A|0))}he=Y(he+E);re=Y(NT(re,n));l=L+1|0;if(A>>>0>=Me>>>0)break;else{t=N;I=A;L=l}}do{if(p){m=l>>>0>1;if(!m?!(bi(e)|0):0)break;if(!(wt(me)|0)){t=Y(me-he);e:do{switch(o[e+12>>2]|0){case 3:{b=Y(b+t);C=Y(0.0);break}case 2:{b=Y(b+Y(t*Y(.5)));C=Y(0.0);break}case 4:{if(me>he)C=Y(t/Y(l>>>0));else C=Y(0.0);break}case 7:if(me>he){b=Y(b+Y(t/Y(l<<1>>>0)));C=Y(t/Y(l>>>0));C=m?C:Y(0.0);break e}else{b=Y(b+Y(t*Y(.5)));C=Y(0.0);break e}case 6:{C=Y(t/Y(L>>>0));C=me>he&m?C:Y(0.0);break}default:C=Y(0.0)}}while(0);if(l|0){T=1040+(Fe<<2)|0;x=976+(Fe<<2)|0;_=0;g=0;while(1){e:do{if(g>>>0>>0){w=Y(0.0);E=Y(0.0);t=Y(0.0);y=g;while(1){m=o[(o[Ne>>2]|0)+(y<<2)>>2]|0;do{if((o[m+36>>2]|0)!=1?(o[m+24>>2]|0)==0:0){if((o[m+940>>2]|0)!=(_|0))break e;if(wi(m,Fe)|0){Be=Y(s[m+908+(o[x>>2]<<2)>>2]);t=Y(NT(t,Y(Be+Y(Sr(m,Fe,Oe)))))}if((pi(e,m)|0)!=5)break;pe=Y(Ei(m));pe=Y(pe+Y(Vr(m,0,Oe)));Be=Y(s[m+912>>2]);Be=Y(Y(Be+Y(Sr(m,0,Oe)))-pe);pe=Y(NT(E,pe));Be=Y(NT(w,Be));w=Be;E=pe;t=Y(NT(t,Y(pe+Be)))}}while(0);m=y+1|0;if(m>>>0>>0)y=m;else{y=m;break}}}else{E=Y(0.0);t=Y(0.0);y=g}}while(0);S=Y(C+t);n=b;b=Y(b+S);if(g>>>0>>0){D=Y(n+E);m=g;do{g=o[(o[Ne>>2]|0)+(m<<2)>>2]|0;e:do{if((o[g+36>>2]|0)!=1?(o[g+24>>2]|0)==0:0)switch(pi(e,g)|0){case 1:{Be=Y(n+Y(Vr(g,Fe,Oe)));s[g+400+(o[T>>2]<<2)>>2]=Be;break e}case 3:{Be=Y(Y(b-Y(qr(g,Fe,Oe)))-Y(s[g+908+(o[x>>2]<<2)>>2]));s[g+400+(o[T>>2]<<2)>>2]=Be;break e}case 2:{Be=Y(n+Y(Y(S-Y(s[g+908+(o[x>>2]<<2)>>2]))*Y(.5)));s[g+400+(o[T>>2]<<2)>>2]=Be;break e}case 4:{Be=Y(n+Y(Vr(g,Fe,Oe)));s[g+400+(o[T>>2]<<2)>>2]=Be;if(Ir(g,Fe,me)|0)break e;if(Pe){w=Y(s[g+908>>2]);t=Y(w+Y(Sr(g,Re,Oe)));E=S}else{E=Y(s[g+912>>2]);E=Y(E+Y(Sr(g,Fe,Oe)));t=S;w=Y(s[g+908>>2])}if(vr(t,w)|0?vr(E,Y(s[g+912>>2]))|0:0)break e;Dr(g,t,E,Ie,1,1,Oe,ye,1,3501,v)|0;break e}case 5:{s[g+404>>2]=Y(Y(D-Y(Ei(g)))+Y(yi(g,0,me)));break e}default:break e}}while(0);m=m+1|0}while((m|0)!=(y|0))}_=_+1|0;if((_|0)==(l|0))break;else g=y}}}}}while(0);s[e+908>>2]=Y(di(e,2,_e,c,c));s[e+912>>2]=Y(di(e,0,ge,f,c));if((ve|0)!=0?(Ee=o[e+32>>2]|0,De=(ve|0)==2,!(De&(Ee|0)!=2)):0){if(De&(Ee|0)==2){t=Y(we+N);t=Y(NT(Y(RT(t,Y(Di(e,Re,re,be)))),we));K=198}}else{t=Y(di(e,Re,re,be,c));K=198}if((K|0)==198)s[e+908+(o[976+(Re<<2)>>2]<<2)>>2]=t;if((ke|0)!=0?(xe=o[e+32>>2]|0,Ae=(ke|0)==2,!(Ae&(xe|0)!=2)):0){if(Ae&(xe|0)==2){t=Y(Ce+me);t=Y(NT(Y(RT(t,Y(Di(e,Fe,Y(Ce+he),Se)))),Ce));K=204}}else{t=Y(di(e,Fe,Y(Ce+he),Se,c));K=204}if((K|0)==204)s[e+908+(o[976+(Fe<<2)>>2]<<2)>>2]=t;if(p){if((o[Te>>2]|0)==2){g=976+(Fe<<2)|0;y=1040+(Fe<<2)|0;m=0;do{_=Pt(e,m)|0;if(!(o[_+24>>2]|0)){je=o[g>>2]|0;Be=Y(s[e+908+(je<<2)>>2]);Ue=_+400+(o[y>>2]<<2)|0;Be=Y(Be-Y(s[Ue>>2]));s[Ue>>2]=Y(Be-Y(s[_+908+(je<<2)>>2]))}m=m+1|0}while((m|0)!=(Me|0))}if(i|0){m=Pe?ve:a;do{Si(e,i,Oe,m,ye,Ie,v);i=o[i+960>>2]|0}while((i|0)!=0)}m=(Re|2|0)==3;g=(Fe|2|0)==3;if(m|g){i=0;do{y=o[(o[Ne>>2]|0)+(i<<2)>>2]|0;if((o[y+36>>2]|0)!=1){if(m)Ci(e,y,Re);if(g)Ci(e,y,Fe)}i=i+1|0}while((i|0)!=(Me|0))}}}else ei(e,t,n,a,l,c,f)}while(0);h=Le;return}function Ar(e,t){e=e|0;t=Y(t);var n=0;St(e,t>=Y(0.0),3147);n=t==Y(0.0);s[e+4>>2]=n?Y(0.0):t;return}function Or(e,t,n,i){e=e|0;t=Y(t);n=Y(n);i=i|0;var u=ft,a=ft,l=0,c=0,f=0;o[2278]=(o[2278]|0)+1;Pr(e);if(!(Ir(e,2,t)|0)){u=Y(Nr(e+380|0,t));if(!(u>=Y(0.0))){f=((wt(t)|0)^1)&1;u=t}else f=2}else{u=Y(Nr(o[e+992>>2]|0,t));f=1;u=Y(u+Y(Sr(e,2,t)))}if(!(Ir(e,0,n)|0)){a=Y(Nr(e+388|0,n));if(!(a>=Y(0.0))){c=((wt(n)|0)^1)&1;a=n}else c=2}else{a=Y(Nr(o[e+996>>2]|0,n));c=1;a=Y(a+Y(Sr(e,0,t)))}l=e+976|0;if(Dr(e,u,a,i,f,c,t,n,1,3189,o[l>>2]|0)|0?(Mr(e,o[e+496>>2]|0,t,n,t),Rr(e,Y(s[(o[l>>2]|0)+4>>2]),Y(0.0),Y(0.0)),r[11696]|0):0)mr(e,7);return}function Pr(e){e=e|0;var t=0,n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;a=l+24|0;u=l+16|0;r=l+8|0;i=l;n=0;do{t=e+380+(n<<3)|0;if(!((o[e+380+(n<<3)+4>>2]|0)!=0?(s=t,c=o[s+4>>2]|0,f=r,o[f>>2]=o[s>>2],o[f+4>>2]=c,f=e+364+(n<<3)|0,c=o[f+4>>2]|0,s=i,o[s>>2]=o[f>>2],o[s+4>>2]=c,o[u>>2]=o[r>>2],o[u+4>>2]=o[r+4>>2],o[a>>2]=o[i>>2],o[a+4>>2]=o[i+4>>2],hr(u,a)|0):0))t=e+348+(n<<3)|0;o[e+992+(n<<2)>>2]=t;n=n+1|0}while((n|0)!=2);h=l;return}function Ir(e,t,n){e=e|0;t=t|0;n=Y(n);var r=0;e=o[e+992+(o[976+(t<<2)>>2]<<2)>>2]|0;switch(o[e+4>>2]|0){case 0:case 3:{e=0;break}case 1:{if(Y(s[e>>2])>2])>2]|0){case 2:{t=Y(Y(Y(s[e>>2])*t)/Y(100.0));break}case 1:{t=Y(s[e>>2]);break}default:t=Y(E)}return Y(t)}function Mr(e,t,n,r,i){e=e|0;t=t|0;n=Y(n);r=Y(r);i=Y(i);var u=0,a=ft;t=o[e+944>>2]|0?t:1;u=Kr(o[e+4>>2]|0,t)|0;t=ri(u,t)|0;n=Y(Pi(e,u,n));r=Y(Pi(e,t,r));a=Y(n+Y(Vr(e,u,i)));s[e+400+(o[1040+(u<<2)>>2]<<2)>>2]=a;n=Y(n+Y(qr(e,u,i)));s[e+400+(o[1e3+(u<<2)>>2]<<2)>>2]=n;n=Y(r+Y(Vr(e,t,i)));s[e+400+(o[1040+(t<<2)>>2]<<2)>>2]=n;i=Y(r+Y(qr(e,t,i)));s[e+400+(o[1e3+(t<<2)>>2]<<2)>>2]=i;return}function Rr(e,t,n,r){e=e|0;t=Y(t);n=Y(n);r=Y(r);var i=0,u=0,a=ft,l=ft,c=0,f=0,d=ft,p=0,h=ft,v=ft,m=ft,g=ft;if(!(t==Y(0.0))){i=e+400|0;g=Y(s[i>>2]);u=e+404|0;m=Y(s[u>>2]);p=e+416|0;v=Y(s[p>>2]);f=e+420|0;a=Y(s[f>>2]);h=Y(g+n);d=Y(m+r);r=Y(h+v);l=Y(d+a);c=(o[e+988>>2]|0)==1;s[i>>2]=Y(yr(g,t,0,c));s[u>>2]=Y(yr(m,t,0,c));n=Y(LT(Y(v*t),Y(1.0)));if(vr(n,Y(0.0))|0)u=0;else u=(vr(n,Y(1.0))|0)^1;n=Y(LT(Y(a*t),Y(1.0)));if(vr(n,Y(0.0))|0)i=0;else i=(vr(n,Y(1.0))|0)^1;g=Y(yr(r,t,c&u,c&(u^1)));s[p>>2]=Y(g-Y(yr(h,t,0,c)));g=Y(yr(l,t,c&i,c&(i^1)));s[f>>2]=Y(g-Y(yr(d,t,0,c)));u=(o[e+952>>2]|0)-(o[e+948>>2]|0)>>2;if(u|0){i=0;do{Rr(Pt(e,i)|0,t,h,d);i=i+1|0}while((i|0)!=(u|0))}}return}function Fr(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;switch(n|0){case 5:case 0:{e=oT(o[489]|0,r,i)|0;break}default:e=jT(r,i)|0}return e|0}function Lr(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;i=h;h=h+16|0;u=i;o[u>>2]=r;Br(e,0,t,n,u);h=i;return}function Br(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;e=e|0?e:956;xA[o[e+8>>2]&1](e,t,n,r,i)|0;if((n|0)==5)Ye();else return}function jr(e,t,n){e=e|0;t=t|0;n=n|0;r[e+t>>0]=n&1;return}function Ur(e,t){e=e|0;t=t|0;var n=0,r=0;o[e>>2]=0;o[e+4>>2]=0;o[e+8>>2]=0;n=t+4|0;r=(o[n>>2]|0)-(o[t>>2]|0)>>2;if(r|0){zr(e,r);Wr(e,o[t>>2]|0,o[n>>2]|0,r)}return}function zr(e,t){e=e|0;t=t|0;var n=0;if((Hr(e)|0)>>>0>>0)UT(e);if(t>>>0>1073741823)Ye();else{n=$T(t<<2)|0;o[e+4>>2]=n;o[e>>2]=n;o[e+8>>2]=n+(t<<2);return}}function Wr(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;r=e+4|0;e=n-t|0;if((e|0)>0){ix(o[r>>2]|0,t|0,e|0)|0;o[r>>2]=(o[r>>2]|0)+(e>>>2<<2)}return}function Hr(e){e=e|0;return 1073741823}function Vr(e,t,n){e=e|0;t=t|0;n=Y(n);if(Gr(t)|0?(o[e+96>>2]|0)!=0:0)e=e+92|0;else e=Et(e+60|0,o[1040+(t<<2)>>2]|0,992)|0;return Y($r(e,n))}function qr(e,t,n){e=e|0;t=t|0;n=Y(n);if(Gr(t)|0?(o[e+104>>2]|0)!=0:0)e=e+100|0;else e=Et(e+60|0,o[1e3+(t<<2)>>2]|0,992)|0;return Y($r(e,n))}function Gr(e){e=e|0;return(e|1|0)==3|0}function $r(e,t){e=e|0;t=Y(t);if((o[e+4>>2]|0)==3)t=Y(0.0);else t=Y(Nr(e,t));return Y(t)}function Yr(e,t){e=e|0;t=t|0;e=o[e>>2]|0;return((e|0)==0?(t|0)>1?t:1:e)|0}function Kr(e,t){e=e|0;t=t|0;var n=0;e:do{if((t|0)==2){switch(e|0){case 2:{e=3;break e}case 3:break;default:{n=4;break e}}e=2}else n=4}while(0);return e|0}function Xr(e,t){e=e|0;t=t|0;var n=ft;if(!((Gr(t)|0?(o[e+312>>2]|0)!=0:0)?(n=Y(s[e+308>>2]),n>=Y(0.0)):0))n=Y(NT(Y(s[(Et(e+276|0,o[1040+(t<<2)>>2]|0,992)|0)>>2]),Y(0.0)));return Y(n)}function Qr(e,t){e=e|0;t=t|0;var n=ft;if(!((Gr(t)|0?(o[e+320>>2]|0)!=0:0)?(n=Y(s[e+316>>2]),n>=Y(0.0)):0))n=Y(NT(Y(s[(Et(e+276|0,o[1e3+(t<<2)>>2]|0,992)|0)>>2]),Y(0.0)));return Y(n)}function Jr(e,t,n){e=e|0;t=t|0;n=Y(n);var r=ft;if(!((Gr(t)|0?(o[e+240>>2]|0)!=0:0)?(r=Y(Nr(e+236|0,n)),r>=Y(0.0)):0))r=Y(NT(Y(Nr(Et(e+204|0,o[1040+(t<<2)>>2]|0,992)|0,n)),Y(0.0)));return Y(r)}function Zr(e,t,n){e=e|0;t=t|0;n=Y(n);var r=ft;if(!((Gr(t)|0?(o[e+248>>2]|0)!=0:0)?(r=Y(Nr(e+244|0,n)),r>=Y(0.0)):0))r=Y(NT(Y(Nr(Et(e+204|0,o[1e3+(t<<2)>>2]|0,992)|0,n)),Y(0.0)));return Y(r)}function ei(e,t,n,r,i,u,a){e=e|0;t=Y(t);n=Y(n);r=r|0;i=i|0;u=Y(u);a=Y(a);var l=ft,c=ft,f=ft,d=ft,p=ft,v=ft,m=0,g=0,y=0;y=h;h=h+16|0;m=y;g=e+964|0;Rt(e,(o[g>>2]|0)!=0,3519);l=Y(ui(e,2,t));c=Y(ui(e,0,t));f=Y(Sr(e,2,t));d=Y(Sr(e,0,t));if(wt(t)|0)p=t;else p=Y(NT(Y(0.0),Y(Y(t-f)-l)));if(wt(n)|0)v=n;else v=Y(NT(Y(0.0),Y(Y(n-d)-c)));if((r|0)==1&(i|0)==1){s[e+908>>2]=Y(di(e,2,Y(t-f),u,u));t=Y(di(e,0,Y(n-d),a,u))}else{OA[o[g>>2]&1](m,e,p,r,v,i);p=Y(l+Y(s[m>>2]));v=Y(t-f);s[e+908>>2]=Y(di(e,2,(r|2|0)==2?p:v,u,u));v=Y(c+Y(s[m+4>>2]));t=Y(n-d);t=Y(di(e,0,(i|2|0)==2?v:t,a,u))}s[e+912>>2]=t;h=y;return}function ti(e,t,n,r,i,o,u){e=e|0;t=Y(t);n=Y(n);r=r|0;i=i|0;o=Y(o);u=Y(u);var a=ft,l=ft,c=ft,f=ft;c=Y(ui(e,2,o));a=Y(ui(e,0,o));f=Y(Sr(e,2,o));l=Y(Sr(e,0,o));t=Y(t-f);s[e+908>>2]=Y(di(e,2,(r|2|0)==2?c:t,o,o));n=Y(n-l);s[e+912>>2]=Y(di(e,0,(i|2|0)==2?a:n,u,o));return}function ni(e,t,n,r,i,o,u){e=e|0;t=Y(t);n=Y(n);r=r|0;i=i|0;o=Y(o);u=Y(u);var a=0,l=ft,c=ft;a=(r|0)==2;if((!(t<=Y(0.0)&a)?!(n<=Y(0.0)&(i|0)==2):0)?!((r|0)==1&(i|0)==1):0)e=0;else{l=Y(Sr(e,0,o));c=Y(Sr(e,2,o));a=t>2]=Y(di(e,2,a?Y(0.0):t,o,o));t=Y(n-l);a=n>2]=Y(di(e,0,a?Y(0.0):t,u,o));e=1}return e|0}function ri(e,t){e=e|0;t=t|0;if(ki(e)|0)e=Kr(2,t)|0;else e=0;return e|0}function ii(e,t,n){e=e|0;t=t|0;n=Y(n);n=Y(Jr(e,t,n));return Y(n+Y(Xr(e,t)))}function oi(e,t,n){e=e|0;t=t|0;n=Y(n);n=Y(Zr(e,t,n));return Y(n+Y(Qr(e,t)))}function ui(e,t,n){e=e|0;t=t|0;n=Y(n);var r=ft;r=Y(ii(e,t,n));return Y(r+Y(oi(e,t,n)))}function ai(e){e=e|0;if(!(o[e+24>>2]|0)){if(Y(li(e))!=Y(0.0))e=1;else e=Y(si(e))!=Y(0.0)}else e=0;return e|0}function li(e){e=e|0;var t=ft;if(o[e+944>>2]|0){t=Y(s[e+44>>2]);if(wt(t)|0){t=Y(s[e+40>>2]);e=t>Y(0.0)&((wt(t)|0)^1);return Y(e?t:Y(0.0))}}else t=Y(0.0);return Y(t)}function si(e){e=e|0;var t=ft,n=0,i=ft;do{if(o[e+944>>2]|0){t=Y(s[e+48>>2]);if(wt(t)|0){n=r[(o[e+976>>2]|0)+2>>0]|0;if(n<<24>>24==0?(i=Y(s[e+40>>2]),i>24?Y(1.0):Y(0.0)}}else t=Y(0.0)}while(0);return Y(t)}function ci(e){e=e|0;var t=0,n=0;tx(e+400|0,0,540)|0;r[e+985>>0]=1;Wt(e);n=Ot(e)|0;if(n|0){t=e+948|0;e=0;do{ci(o[(o[t>>2]|0)+(e<<2)>>2]|0);e=e+1|0}while((e|0)!=(n|0))}return}function fi(e,t,n,r,i,u,a,l,c,f){e=e|0;t=t|0;n=Y(n);r=r|0;i=Y(i);u=Y(u);a=Y(a);l=l|0;c=c|0;f=f|0;var d=0,p=ft,v=0,m=0,g=ft,y=ft,_=0,b=ft,w=0,D=ft,S=0,C=0,k=0,T=0,x=0,A=0,O=0,P=0,I=0,N=0;I=h;h=h+16|0;k=I+12|0;T=I+8|0;x=I+4|0;A=I;P=Kr(o[e+4>>2]|0,c)|0;S=Gr(P)|0;p=Y(Nr(Ti(t)|0,S?u:a));C=Ir(t,2,u)|0;O=Ir(t,0,a)|0;do{if(!(wt(p)|0)?!(wt(S?n:i)|0):0){d=t+504|0;if(!(wt(Y(s[d>>2]))|0)){if(!(xi(o[t+976>>2]|0,0)|0))break;if((o[t+500>>2]|0)==(o[2278]|0))break}s[d>>2]=Y(NT(p,Y(ui(t,P,u))))}else v=7}while(0);do{if((v|0)==7){w=S^1;if(!(w|C^1)){a=Y(Nr(o[t+992>>2]|0,u));s[t+504>>2]=Y(NT(a,Y(ui(t,2,u))));break}if(!(S|O^1)){a=Y(Nr(o[t+996>>2]|0,a));s[t+504>>2]=Y(NT(a,Y(ui(t,0,u))));break}s[k>>2]=Y(E);s[T>>2]=Y(E);o[x>>2]=0;o[A>>2]=0;b=Y(Sr(t,2,u));D=Y(Sr(t,0,u));if(C){g=Y(b+Y(Nr(o[t+992>>2]|0,u)));s[k>>2]=g;o[x>>2]=1;m=1}else{m=0;g=Y(E)}if(O){p=Y(D+Y(Nr(o[t+996>>2]|0,a)));s[T>>2]=p;o[A>>2]=1;d=1}else{d=0;p=Y(E)}v=o[e+32>>2]|0;if(!(S&(v|0)==2)){if(wt(g)|0?!(wt(n)|0):0){s[k>>2]=n;o[x>>2]=2;m=2;g=n}}else v=2;if((!((v|0)==2&w)?wt(p)|0:0)?!(wt(i)|0):0){s[T>>2]=i;o[A>>2]=2;d=2;p=i}y=Y(s[t+396>>2]);_=wt(y)|0;do{if(!_){if((m|0)==1&w){s[T>>2]=Y(Y(g-b)/y);o[A>>2]=1;d=1;v=1;break}if(S&(d|0)==1){s[k>>2]=Y(y*Y(p-D));o[x>>2]=1;d=1;v=1}else v=m}else v=m}while(0);N=wt(n)|0;m=(pi(e,t)|0)!=4;if(!(S|C|((r|0)!=1|N)|(m|(v|0)==1))?(s[k>>2]=n,o[x>>2]=1,!_):0){s[T>>2]=Y(Y(n-b)/y);o[A>>2]=1;d=1}if(!(O|w|((l|0)!=1|(wt(i)|0))|(m|(d|0)==1))?(s[T>>2]=i,o[A>>2]=1,!_):0){s[k>>2]=Y(y*Y(i-D));o[x>>2]=1}mi(t,2,u,u,x,k);mi(t,0,a,u,A,T);n=Y(s[k>>2]);i=Y(s[T>>2]);Dr(t,n,i,c,o[x>>2]|0,o[A>>2]|0,u,a,0,3565,f)|0;a=Y(s[t+908+(o[976+(P<<2)>>2]<<2)>>2]);s[t+504>>2]=Y(NT(a,Y(ui(t,P,u))))}}while(0);o[t+500>>2]=o[2278];h=I;return}function di(e,t,n,r,i){e=e|0;t=t|0;n=Y(n);r=Y(r);i=Y(i);r=Y(Di(e,t,n,r));return Y(NT(r,Y(ui(e,t,i))))}function pi(e,t){e=e|0;t=t|0;t=t+20|0;t=o[((o[t>>2]|0)==0?e+16|0:t)>>2]|0;if((t|0)==5?ki(o[e+4>>2]|0)|0:0)t=1;return t|0}function hi(e,t){e=e|0;t=t|0;if(Gr(t)|0?(o[e+96>>2]|0)!=0:0)t=4;else t=o[1040+(t<<2)>>2]|0;return e+60+(t<<3)|0}function vi(e,t){e=e|0;t=t|0;if(Gr(t)|0?(o[e+104>>2]|0)!=0:0)t=5;else t=o[1e3+(t<<2)>>2]|0;return e+60+(t<<3)|0}function mi(e,t,n,r,i,u){e=e|0;t=t|0;n=Y(n);r=Y(r);i=i|0;u=u|0;n=Y(Nr(e+380+(o[976+(t<<2)>>2]<<3)|0,n));n=Y(n+Y(Sr(e,t,r)));switch(o[i>>2]|0){case 2:case 1:{i=wt(n)|0;r=Y(s[u>>2]);s[u>>2]=i|r>2]=2;s[u>>2]=n}break}default:{}}return}function gi(e,t){e=e|0;t=t|0;e=e+132|0;if(Gr(t)|0?(o[(Et(e,4,948)|0)+4>>2]|0)!=0:0)e=1;else e=(o[(Et(e,o[1040+(t<<2)>>2]|0,948)|0)+4>>2]|0)!=0;return e|0}function yi(e,t,n){e=e|0;t=t|0;n=Y(n);var r=0,i=0;e=e+132|0;if(Gr(t)|0?(r=Et(e,4,948)|0,(o[r+4>>2]|0)!=0):0)i=4;else{r=Et(e,o[1040+(t<<2)>>2]|0,948)|0;if(!(o[r+4>>2]|0))n=Y(0.0);else i=4}if((i|0)==4)n=Y(Nr(r,n));return Y(n)}function _i(e,t,n){e=e|0;t=t|0;n=Y(n);var r=ft;r=Y(s[e+908+(o[976+(t<<2)>>2]<<2)>>2]);r=Y(r+Y(Vr(e,t,n)));return Y(r+Y(qr(e,t,n)))}function bi(e){e=e|0;var t=0,n=0,r=0;e:do{if(!(ki(o[e+4>>2]|0)|0)){if((o[e+16>>2]|0)!=5){n=Ot(e)|0;if(!n)t=0;else{t=0;while(1){r=Pt(e,t)|0;if((o[r+24>>2]|0)==0?(o[r+20>>2]|0)==5:0){t=1;break e}t=t+1|0;if(t>>>0>=n>>>0){t=0;break}}}}else t=1}else t=0}while(0);return t|0}function wi(e,t){e=e|0;t=t|0;var n=ft;n=Y(s[e+908+(o[976+(t<<2)>>2]<<2)>>2]);return n>=Y(0.0)&((wt(n)|0)^1)|0}function Ei(e){e=e|0;var t=ft,n=0,r=0,i=0,u=0,a=0,l=0,c=ft;n=o[e+968>>2]|0;if(!n){u=Ot(e)|0;do{if(u|0){n=0;i=0;while(1){r=Pt(e,i)|0;if(o[r+940>>2]|0){a=8;break}if((o[r+24>>2]|0)!=1){l=(pi(e,r)|0)==5;if(l){n=r;break}else n=(n|0)==0?r:n}i=i+1|0;if(i>>>0>=u>>>0){a=8;break}}if((a|0)==8)if(!n)break;t=Y(Ei(n));return Y(t+Y(s[n+404>>2]))}}while(0);t=Y(s[e+912>>2])}else{c=Y(s[e+908>>2]);t=Y(s[e+912>>2]);t=Y(pA[n&0](e,c,t));Rt(e,(wt(t)|0)^1,3573)}return Y(t)}function Di(e,t,n,r){e=e|0;t=t|0;n=Y(n);r=Y(r);var i=ft,o=0;if(!(ki(t)|0)){if(Gr(t)|0){t=0;o=3}else{r=Y(E);i=Y(E)}}else{t=1;o=3}if((o|0)==3){i=Y(Nr(e+364+(t<<3)|0,r));r=Y(Nr(e+380+(t<<3)|0,r))}o=r=Y(0.0)&((wt(r)|0)^1));n=o?r:n;o=i>=Y(0.0)&((wt(i)|0)^1)&n>2]|0,u)|0;m=ri(y,u)|0;g=Gr(y)|0;p=Y(Sr(t,2,n));h=Y(Sr(t,0,n));if(!(Ir(t,2,n)|0)){if(gi(t,2)|0?Ai(t,2)|0:0){l=Y(s[e+908>>2]);c=Y(Xr(e,2));c=Y(l-Y(c+Y(Qr(e,2))));l=Y(yi(t,2,n));l=Y(di(t,2,Y(c-Y(l+Y(Oi(t,2,n)))),n,n))}else l=Y(E)}else l=Y(p+Y(Nr(o[t+992>>2]|0,n)));if(!(Ir(t,0,i)|0)){if(gi(t,0)|0?Ai(t,0)|0:0){c=Y(s[e+912>>2]);b=Y(Xr(e,0));b=Y(c-Y(b+Y(Qr(e,0))));c=Y(yi(t,0,i));c=Y(di(t,0,Y(b-Y(c+Y(Oi(t,0,i)))),i,n))}else c=Y(E)}else c=Y(h+Y(Nr(o[t+996>>2]|0,i)));f=wt(l)|0;d=wt(c)|0;do{if(f^d?(v=Y(s[t+396>>2]),!(wt(v)|0)):0)if(f){l=Y(p+Y(Y(c-h)*v));break}else{b=Y(h+Y(Y(l-p)/v));c=d?b:c;break}}while(0);d=wt(l)|0;f=wt(c)|0;if(d|f){w=(d^1)&1;r=n>Y(0.0)&((r|0)!=0&d);l=g?l:r?n:l;Dr(t,l,c,u,g?w:r?2:w,d&(f^1)&1,l,c,0,3623,a)|0;l=Y(s[t+908>>2]);l=Y(l+Y(Sr(t,2,n)));c=Y(s[t+912>>2]);c=Y(c+Y(Sr(t,0,n)))}Dr(t,l,c,u,1,1,l,c,1,3635,a)|0;if(Ai(t,y)|0?!(gi(t,y)|0):0){w=o[976+(y<<2)>>2]|0;b=Y(s[e+908+(w<<2)>>2]);b=Y(b-Y(s[t+908+(w<<2)>>2]));b=Y(b-Y(Qr(e,y)));b=Y(b-Y(qr(t,y,n)));b=Y(b-Y(Oi(t,y,g?n:i)));s[t+400+(o[1040+(y<<2)>>2]<<2)>>2]=b}else _=21;do{if((_|0)==21){if(!(gi(t,y)|0)?(o[e+8>>2]|0)==1:0){w=o[976+(y<<2)>>2]|0;b=Y(s[e+908+(w<<2)>>2]);b=Y(Y(b-Y(s[t+908+(w<<2)>>2]))*Y(.5));s[t+400+(o[1040+(y<<2)>>2]<<2)>>2]=b;break}if(!(gi(t,y)|0)?(o[e+8>>2]|0)==2:0){w=o[976+(y<<2)>>2]|0;b=Y(s[e+908+(w<<2)>>2]);b=Y(b-Y(s[t+908+(w<<2)>>2]));s[t+400+(o[1040+(y<<2)>>2]<<2)>>2]=b}}}while(0);if(Ai(t,m)|0?!(gi(t,m)|0):0){w=o[976+(m<<2)>>2]|0;b=Y(s[e+908+(w<<2)>>2]);b=Y(b-Y(s[t+908+(w<<2)>>2]));b=Y(b-Y(Qr(e,m)));b=Y(b-Y(qr(t,m,n)));b=Y(b-Y(Oi(t,m,g?i:n)));s[t+400+(o[1040+(m<<2)>>2]<<2)>>2]=b}else _=30;do{if((_|0)==30?!(gi(t,m)|0):0){if((pi(e,t)|0)==2){w=o[976+(m<<2)>>2]|0;b=Y(s[e+908+(w<<2)>>2]);b=Y(Y(b-Y(s[t+908+(w<<2)>>2]))*Y(.5));s[t+400+(o[1040+(m<<2)>>2]<<2)>>2]=b;break}w=(pi(e,t)|0)==3;if(w^(o[e+28>>2]|0)==2){w=o[976+(m<<2)>>2]|0;b=Y(s[e+908+(w<<2)>>2]);b=Y(b-Y(s[t+908+(w<<2)>>2]));s[t+400+(o[1040+(m<<2)>>2]<<2)>>2]=b}}}while(0);return}function Ci(e,t,n){e=e|0;t=t|0;n=n|0;var r=ft,i=0;i=o[976+(n<<2)>>2]|0;r=Y(s[t+908+(i<<2)>>2]);r=Y(Y(s[e+908+(i<<2)>>2])-r);r=Y(r-Y(s[t+400+(o[1040+(n<<2)>>2]<<2)>>2]));s[t+400+(o[1e3+(n<<2)>>2]<<2)>>2]=r;return}function ki(e){e=e|0;return(e|1|0)==1|0}function Ti(e){e=e|0;var t=ft;switch(o[e+56>>2]|0){case 0:case 3:{t=Y(s[e+40>>2]);if(t>Y(0.0)&((wt(t)|0)^1))e=r[(o[e+976>>2]|0)+2>>0]|0?1056:992;else e=1056;break}default:e=e+52|0}return e|0}function xi(e,t){e=e|0;t=t|0;return(r[e+t>>0]|0)!=0|0}function Ai(e,t){e=e|0;t=t|0;e=e+132|0;if(Gr(t)|0?(o[(Et(e,5,948)|0)+4>>2]|0)!=0:0)e=1;else e=(o[(Et(e,o[1e3+(t<<2)>>2]|0,948)|0)+4>>2]|0)!=0;return e|0}function Oi(e,t,n){e=e|0;t=t|0;n=Y(n);var r=0,i=0;e=e+132|0;if(Gr(t)|0?(r=Et(e,5,948)|0,(o[r+4>>2]|0)!=0):0)i=4;else{r=Et(e,o[1e3+(t<<2)>>2]|0,948)|0;if(!(o[r+4>>2]|0))n=Y(0.0);else i=4}if((i|0)==4)n=Y(Nr(r,n));return Y(n)}function Pi(e,t,n){e=e|0;t=t|0;n=Y(n);if(gi(e,t)|0)n=Y(yi(e,t,n));else n=Y(-Y(Oi(e,t,n)));return Y(n)}function Ii(e){e=Y(e);return(s[d>>2]=e,o[d>>2]|0)|0}function Ni(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>1073741823)Ye();else{i=$T(t<<2)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<2)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<2);return}function Mi(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>2)<<2)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Ri(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-4-t|0)>>>2)<<2);e=o[e>>2]|0;if(e|0)KT(e);return}function Fi(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0;a=e+4|0;l=o[a>>2]|0;i=l-r|0;u=i>>2;e=t+(u<<2)|0;if(e>>>0>>0){r=l;do{o[r>>2]=o[e>>2];e=e+4|0;r=(o[a>>2]|0)+4|0;o[a>>2]=r}while(e>>>0>>0)}if(u|0)sx(l+(0-u<<2)|0,t|0,i|0)|0;return}function Li(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0;l=t+4|0;s=o[l>>2]|0;i=o[e>>2]|0;a=n;u=a-i|0;r=s+(0-(u>>2)<<2)|0;o[l>>2]=r;if((u|0)>0)ix(r|0,i|0,u|0)|0;i=e+4|0;u=t+8|0;r=(o[i>>2]|0)-a|0;if((r|0)>0){ix(o[u>>2]|0,n|0,r|0)|0;o[u>>2]=(o[u>>2]|0)+(r>>>2<<2)}a=o[e>>2]|0;o[e>>2]=o[l>>2];o[l>>2]=a;a=o[i>>2]|0;o[i>>2]=o[u>>2];o[u>>2]=a;a=e+8|0;n=t+12|0;e=o[a>>2]|0;o[a>>2]=o[n>>2];o[n>>2]=e;o[t>>2]=o[l>>2];return s|0}function Bi(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;a=o[t>>2]|0;u=o[n>>2]|0;if((a|0)!=(u|0)){i=e+8|0;n=((u+-4-a|0)>>>2)+1|0;e=a;r=o[i>>2]|0;do{o[r>>2]=o[e>>2];r=(o[i>>2]|0)+4|0;o[i>>2]=r;e=e+4|0}while((e|0)!=(u|0));o[t>>2]=a+(n<<2)}return}function ji(){_t();return}function Ui(){var e=0;e=$T(4)|0;zi(e);return e|0}function zi(e){e=e|0;o[e>>2]=Lt()|0;return}function Wi(e){e=e|0;if(e|0){Hi(e);KT(e)}return}function Hi(e){e=e|0;jt(o[e>>2]|0);return}function Vi(e,t,n){e=e|0;t=t|0;n=n|0;jr(o[e>>2]|0,t,n);return}function qi(e,t){e=e|0;t=Y(t);Ar(o[e>>2]|0,t);return}function Gi(e,t){e=e|0;t=t|0;return xi(o[e>>2]|0,t)|0}function $i(){var e=0;e=$T(8)|0;Yi(e,0);return e|0}function Yi(e,t){e=e|0;t=t|0;if(!t)t=Ct()|0;else t=Dt(o[t>>2]|0)|0;o[e>>2]=t;o[e+4>>2]=0;Jt(t,e);return}function Ki(e){e=e|0;var t=0;t=$T(8)|0;Yi(t,e);return t|0}function Xi(e){e=e|0;if(e|0){Qi(e);KT(e)}return}function Qi(e){e=e|0;var t=0;xt(o[e>>2]|0);t=e+4|0;e=o[t>>2]|0;o[t>>2]=0;if(e|0){Ji(e);KT(e)}return}function Ji(e){e=e|0;Zi(e);return}function Zi(e){e=e|0;e=o[e>>2]|0;if(e|0)rt(e|0);return}function eo(e){e=e|0;return Zt(e)|0}function to(e){e=e|0;var t=0,n=0;n=e+4|0;t=o[n>>2]|0;o[n>>2]=0;if(t|0){Ji(t);KT(t)}Mt(o[e>>2]|0);return}function no(e,t){e=e|0;t=t|0;Kt(o[e>>2]|0,o[t>>2]|0);return}function ro(e,t){e=e|0;t=t|0;fn(o[e>>2]|0,t);return}function io(e,t,n){e=e|0;t=t|0;n=+n;Cn(o[e>>2]|0,t,Y(n));return}function oo(e,t,n){e=e|0;t=t|0;n=+n;kn(o[e>>2]|0,t,Y(n));return}function uo(e,t){e=e|0;t=t|0;on(o[e>>2]|0,t);return}function ao(e,t){e=e|0;t=t|0;an(o[e>>2]|0,t);return}function lo(e,t){e=e|0;t=t|0;sn(o[e>>2]|0,t);return}function so(e,t){e=e|0;t=t|0;en(o[e>>2]|0,t);return}function co(e,t){e=e|0;t=t|0;pn(o[e>>2]|0,t);return}function fo(e,t){e=e|0;t=t|0;nn(o[e>>2]|0,t);return}function po(e,t,n){e=e|0;t=t|0;n=+n;xn(o[e>>2]|0,t,Y(n));return}function ho(e,t,n){e=e|0;t=t|0;n=+n;An(o[e>>2]|0,t,Y(n));return}function vo(e,t){e=e|0;t=t|0;Pn(o[e>>2]|0,t);return}function mo(e,t){e=e|0;t=t|0;vn(o[e>>2]|0,t);return}function go(e,t){e=e|0;t=t|0;gn(o[e>>2]|0,t);return}function yo(e,t){e=e|0;t=+t;_n(o[e>>2]|0,Y(t));return}function _o(e,t){e=e|0;t=+t;En(o[e>>2]|0,Y(t));return}function bo(e,t){e=e|0;t=+t;Dn(o[e>>2]|0,Y(t));return}function wo(e,t){e=e|0;t=+t;bn(o[e>>2]|0,Y(t));return}function Eo(e,t){e=e|0;t=+t;wn(o[e>>2]|0,Y(t));return}function Do(e,t){e=e|0;t=+t;Ln(o[e>>2]|0,Y(t));return}function So(e,t){e=e|0;t=+t;Bn(o[e>>2]|0,Y(t));return}function Co(e){e=e|0;jn(o[e>>2]|0);return}function ko(e,t){e=e|0;t=+t;zn(o[e>>2]|0,Y(t));return}function To(e,t){e=e|0;t=+t;Wn(o[e>>2]|0,Y(t));return}function xo(e){e=e|0;Hn(o[e>>2]|0);return}function Ao(e,t){e=e|0;t=+t;qn(o[e>>2]|0,Y(t));return}function Oo(e,t){e=e|0;t=+t;Gn(o[e>>2]|0,Y(t));return}function Po(e,t){e=e|0;t=+t;Yn(o[e>>2]|0,Y(t));return}function Io(e,t){e=e|0;t=+t;Kn(o[e>>2]|0,Y(t));return}function No(e,t){e=e|0;t=+t;Qn(o[e>>2]|0,Y(t));return}function Mo(e,t){e=e|0;t=+t;Jn(o[e>>2]|0,Y(t));return}function Ro(e,t){e=e|0;t=+t;er(o[e>>2]|0,Y(t));return}function Fo(e,t){e=e|0;t=+t;tr(o[e>>2]|0,Y(t));return}function Lo(e,t){e=e|0;t=+t;rr(o[e>>2]|0,Y(t));return}function Bo(e,t,n){e=e|0;t=t|0;n=+n;Rn(o[e>>2]|0,t,Y(n));return}function jo(e,t,n){e=e|0;t=t|0;n=+n;In(o[e>>2]|0,t,Y(n));return}function Uo(e,t,n){e=e|0;t=t|0;n=+n;Nn(o[e>>2]|0,t,Y(n));return}function zo(e){e=e|0;return dn(o[e>>2]|0)|0}function Wo(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0;r=h;h=h+16|0;i=r;Tn(i,o[t>>2]|0,n);Ho(e,i);h=r;return}function Ho(e,t){e=e|0;t=t|0;Vo(e,o[t+4>>2]|0,+Y(s[t>>2]));return}function Vo(e,t,n){e=e|0;t=t|0;n=+n;o[e>>2]=t;c[e+8>>3]=n;return}function qo(e){e=e|0;return un(o[e>>2]|0)|0}function Go(e){e=e|0;return ln(o[e>>2]|0)|0}function $o(e){e=e|0;return cn(o[e>>2]|0)|0}function Yo(e){e=e|0;return tn(o[e>>2]|0)|0}function Ko(e){e=e|0;return hn(o[e>>2]|0)|0}function Xo(e){e=e|0;return rn(o[e>>2]|0)|0}function Qo(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0;r=h;h=h+16|0;i=r;On(i,o[t>>2]|0,n);Ho(e,i);h=r;return}function Jo(e){e=e|0;return mn(o[e>>2]|0)|0}function Zo(e){e=e|0;return yn(o[e>>2]|0)|0}function eu(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;Sn(r,o[t>>2]|0);Ho(e,r);h=n;return}function tu(e){e=e|0;return+ +Y(Xt(o[e>>2]|0))}function nu(e){e=e|0;return+ +Y(Qt(o[e>>2]|0))}function ru(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;Un(r,o[t>>2]|0);Ho(e,r);h=n;return}function iu(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;Vn(r,o[t>>2]|0);Ho(e,r);h=n;return}function ou(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;$n(r,o[t>>2]|0);Ho(e,r);h=n;return}function uu(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;Xn(r,o[t>>2]|0);Ho(e,r);h=n;return}function au(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;Zn(r,o[t>>2]|0);Ho(e,r);h=n;return}function lu(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;nr(r,o[t>>2]|0);Ho(e,r);h=n;return}function su(e){e=e|0;return+ +Y(ir(o[e>>2]|0))}function cu(e,t){e=e|0;t=t|0;return+ +Y(Fn(o[e>>2]|0,t))}function fu(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0;r=h;h=h+16|0;i=r;Mn(i,o[t>>2]|0,n);Ho(e,i);h=r;return}function du(e,t,n){e=e|0;t=t|0;n=n|0;zt(o[e>>2]|0,o[t>>2]|0,n);return}function pu(e,t){e=e|0;t=t|0;Nt(o[e>>2]|0,o[t>>2]|0);return}function hu(e){e=e|0;return Ot(o[e>>2]|0)|0}function vu(e){e=e|0;e=Gt(o[e>>2]|0)|0;if(!e)e=0;else e=eo(e)|0;return e|0}function mu(e,t){e=e|0;t=t|0;e=Pt(o[e>>2]|0,t)|0;if(!e)e=0;else e=eo(e)|0;return e|0}function gu(e,t){e=e|0;t=t|0;var n=0,r=0;r=$T(4)|0;yu(r,t);n=e+4|0;t=o[n>>2]|0;o[n>>2]=r;if(t|0){Ji(t);KT(t)}Ut(o[e>>2]|0,1);return}function yu(e,t){e=e|0;t=t|0;Bu(e,t);return}function _u(e,t,n,r,i,o){e=e|0;t=t|0;n=Y(n);r=r|0;i=Y(i);o=o|0;var u=0,a=0;u=h;h=h+16|0;a=u;bu(a,Zt(t)|0,+n,r,+i,o);s[e>>2]=Y(+c[a>>3]);s[e+4>>2]=Y(+c[a+8>>3]);h=u;return}function bu(e,t,n,r,i,u){e=e|0;t=t|0;n=+n;r=r|0;i=+i;u=u|0;var a=0,l=0,s=0,f=0,d=0;a=h;h=h+32|0;d=a+8|0;f=a+20|0;s=a;l=a+16|0;c[d>>3]=n;o[f>>2]=r;c[s>>3]=i;o[l>>2]=u;wu(e,o[t+4>>2]|0,d,f,s,l);h=a;return}function wu(e,t,n,r,i,u){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;u=u|0;var a=0,l=0;a=h;h=h+16|0;l=a;Dk(l);t=Eu(t)|0;Du(e,t,+c[n>>3],o[r>>2]|0,+c[i>>3],o[u>>2]|0);Ck(l);h=a;return}function Eu(e){e=e|0;return o[e>>2]|0}function Du(e,t,n,r,i,o){e=e|0;t=t|0;n=+n;r=r|0;i=+i;o=o|0;var u=0;u=Cu(Su()|0)|0;n=+ku(n);r=Tu(r)|0;i=+ku(i);xu(e,ot(0,u|0,t|0,+n,r|0,+i,Tu(o)|0)|0);return}function Su(){var e=0;if(!(r[7608]|0)){Ru(9120);e=7608;o[e>>2]=1;o[e+4>>2]=0}return 9120}function Cu(e){e=e|0;return o[e+8>>2]|0}function ku(e){e=+e;return+ +Mu(e)}function Tu(e){e=e|0;return Nu(e)|0}function xu(e,t){e=e|0;t=t|0;var n=0,r=0,i=0;i=h;h=h+32|0;n=i;r=t;if(!(r&1)){o[e>>2]=o[t>>2];o[e+4>>2]=o[t+4>>2];o[e+8>>2]=o[t+8>>2];o[e+12>>2]=o[t+12>>2]}else{Au(n,0);Be(r|0,n|0)|0;Ou(e,n);Pu(n)}h=i;return}function Au(e,t){e=e|0;t=t|0;Iu(e,t);o[e+8>>2]=0;r[e+24>>0]=0;return}function Ou(e,t){e=e|0;t=t|0;t=t+8|0;o[e>>2]=o[t>>2];o[e+4>>2]=o[t+4>>2];o[e+8>>2]=o[t+8>>2];o[e+12>>2]=o[t+12>>2];return}function Pu(e){e=e|0;r[e+24>>0]=0;return}function Iu(e,t){e=e|0;t=t|0;o[e>>2]=t;return}function Nu(e){e=e|0;return e|0}function Mu(e){e=+e;return+e}function Ru(e){e=e|0;Lu(e,Fu()|0,4);return}function Fu(){return 1064}function Lu(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;o[e+8>>2]=tt(t|0,n+1|0)|0;return}function Bu(e,t){e=e|0;t=t|0;t=o[t>>2]|0;o[e>>2]=t;Ae(t|0);return}function ju(e){e=e|0;var t=0,n=0;n=e+4|0;t=o[n>>2]|0;o[n>>2]=0;if(t|0){Ji(t);KT(t)}Ut(o[e>>2]|0,0);return}function Uu(e){e=e|0;$t(o[e>>2]|0);return}function zu(e){e=e|0;return Yt(o[e>>2]|0)|0}function Wu(e,t,n,r){e=e|0;t=+t;n=+n;r=r|0;Or(o[e>>2]|0,Y(t),Y(n),r);return}function Hu(e){e=e|0;return+ +Y(or(o[e>>2]|0))}function Vu(e){e=e|0;return+ +Y(ar(o[e>>2]|0))}function qu(e){e=e|0;return+ +Y(ur(o[e>>2]|0))}function Gu(e){e=e|0;return+ +Y(lr(o[e>>2]|0))}function $u(e){e=e|0;return+ +Y(sr(o[e>>2]|0))}function Yu(e){e=e|0;return+ +Y(cr(o[e>>2]|0))}function Ku(e,t){e=e|0;t=t|0;c[e>>3]=+Y(or(o[t>>2]|0));c[e+8>>3]=+Y(ar(o[t>>2]|0));c[e+16>>3]=+Y(ur(o[t>>2]|0));c[e+24>>3]=+Y(lr(o[t>>2]|0));c[e+32>>3]=+Y(sr(o[t>>2]|0));c[e+40>>3]=+Y(cr(o[t>>2]|0));return}function Xu(e,t){e=e|0;t=t|0;return+ +Y(fr(o[e>>2]|0,t))}function Qu(e,t){e=e|0;t=t|0;return+ +Y(dr(o[e>>2]|0,t))}function Ju(e,t){e=e|0;t=t|0;return+ +Y(pr(o[e>>2]|0,t))}function Zu(){return Ft()|0}function ea(){ta();na();ra();ia();oa();ua();return}function ta(){zb(11713,4938,1);return}function na(){tb(10448);return}function ra(){R_(10408);return}function ia(){Jy(10324);return}function oa(){qm(10096);return}function ua(){aa(9132);return}function aa(e){e=e|0;var t=0,n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0,v=0,m=0,g=0,y=0,_=0,b=0,w=0,E=0,D=0,S=0,C=0,k=0,T=0,x=0,A=0,O=0,P=0,I=0,N=0,M=0,R=0,F=0,L=0,B=0,j=0,U=0,z=0,W=0,H=0,V=0,q=0,G=0,$=0,Y=0,K=0,X=0,Q=0,J=0,Z=0,ee=0,te=0,ne=0,re=0,ie=0,oe=0,ue=0,ae=0,le=0,se=0,ce=0,fe=0,de=0,pe=0,he=0,ve=0,me=0,ge=0,ye=0,_e=0,be=0,we=0,Ee=0,De=0,Se=0,Ce=0,ke=0,Te=0,xe=0,Ae=0,Oe=0,Pe=0,Ie=0;t=h;h=h+672|0;n=t+656|0;Ie=t+648|0;Pe=t+640|0;Oe=t+632|0;Ae=t+624|0;xe=t+616|0;Te=t+608|0;ke=t+600|0;Ce=t+592|0;Se=t+584|0;De=t+576|0;Ee=t+568|0;we=t+560|0;be=t+552|0;_e=t+544|0;ye=t+536|0;ge=t+528|0;me=t+520|0;ve=t+512|0;he=t+504|0;pe=t+496|0;de=t+488|0;fe=t+480|0;ce=t+472|0;se=t+464|0;le=t+456|0;ae=t+448|0;ue=t+440|0;oe=t+432|0;ie=t+424|0;re=t+416|0;ne=t+408|0;te=t+400|0;ee=t+392|0;Z=t+384|0;J=t+376|0;Q=t+368|0;X=t+360|0;K=t+352|0;Y=t+344|0;$=t+336|0;G=t+328|0;q=t+320|0;V=t+312|0;H=t+304|0;W=t+296|0;z=t+288|0;U=t+280|0;j=t+272|0;B=t+264|0;L=t+256|0;F=t+248|0;R=t+240|0;M=t+232|0;N=t+224|0;I=t+216|0;P=t+208|0;O=t+200|0;A=t+192|0;x=t+184|0;T=t+176|0;k=t+168|0;C=t+160|0;S=t+152|0;D=t+144|0;E=t+136|0;w=t+128|0;b=t+120|0;_=t+112|0;y=t+104|0;g=t+96|0;m=t+88|0;v=t+80|0;p=t+72|0;d=t+64|0;f=t+56|0;c=t+48|0;s=t+40|0;l=t+32|0;a=t+24|0;u=t+16|0;i=t+8|0;r=t;la(e,3646);sa(e,3651,2)|0;ca(e,3665,2)|0;fa(e,3682,18)|0;o[Ie>>2]=19;o[Ie+4>>2]=0;o[n>>2]=o[Ie>>2];o[n+4>>2]=o[Ie+4>>2];da(e,3690,n)|0;o[Pe>>2]=1;o[Pe+4>>2]=0;o[n>>2]=o[Pe>>2];o[n+4>>2]=o[Pe+4>>2];pa(e,3696,n)|0;o[Oe>>2]=2;o[Oe+4>>2]=0;o[n>>2]=o[Oe>>2];o[n+4>>2]=o[Oe+4>>2];ha(e,3706,n)|0;o[Ae>>2]=1;o[Ae+4>>2]=0;o[n>>2]=o[Ae>>2];o[n+4>>2]=o[Ae+4>>2];va(e,3722,n)|0;o[xe>>2]=2;o[xe+4>>2]=0;o[n>>2]=o[xe>>2];o[n+4>>2]=o[xe+4>>2];va(e,3734,n)|0;o[Te>>2]=3;o[Te+4>>2]=0;o[n>>2]=o[Te>>2];o[n+4>>2]=o[Te+4>>2];ha(e,3753,n)|0;o[ke>>2]=4;o[ke+4>>2]=0;o[n>>2]=o[ke>>2];o[n+4>>2]=o[ke+4>>2];ha(e,3769,n)|0;o[Ce>>2]=5;o[Ce+4>>2]=0;o[n>>2]=o[Ce>>2];o[n+4>>2]=o[Ce+4>>2];ha(e,3783,n)|0;o[Se>>2]=6;o[Se+4>>2]=0;o[n>>2]=o[Se>>2];o[n+4>>2]=o[Se+4>>2];ha(e,3796,n)|0;o[De>>2]=7;o[De+4>>2]=0;o[n>>2]=o[De>>2];o[n+4>>2]=o[De+4>>2];ha(e,3813,n)|0;o[Ee>>2]=8;o[Ee+4>>2]=0;o[n>>2]=o[Ee>>2];o[n+4>>2]=o[Ee+4>>2];ha(e,3825,n)|0;o[we>>2]=3;o[we+4>>2]=0;o[n>>2]=o[we>>2];o[n+4>>2]=o[we+4>>2];va(e,3843,n)|0;o[be>>2]=4;o[be+4>>2]=0;o[n>>2]=o[be>>2];o[n+4>>2]=o[be+4>>2];va(e,3853,n)|0;o[_e>>2]=9;o[_e+4>>2]=0;o[n>>2]=o[_e>>2];o[n+4>>2]=o[_e+4>>2];ha(e,3870,n)|0;o[ye>>2]=10;o[ye+4>>2]=0;o[n>>2]=o[ye>>2];o[n+4>>2]=o[ye+4>>2];ha(e,3884,n)|0;o[ge>>2]=11;o[ge+4>>2]=0;o[n>>2]=o[ge>>2];o[n+4>>2]=o[ge+4>>2];ha(e,3896,n)|0;o[me>>2]=1;o[me+4>>2]=0;o[n>>2]=o[me>>2];o[n+4>>2]=o[me+4>>2];ma(e,3907,n)|0;o[ve>>2]=2;o[ve+4>>2]=0;o[n>>2]=o[ve>>2];o[n+4>>2]=o[ve+4>>2];ma(e,3915,n)|0;o[he>>2]=3;o[he+4>>2]=0;o[n>>2]=o[he>>2];o[n+4>>2]=o[he+4>>2];ma(e,3928,n)|0;o[pe>>2]=4;o[pe+4>>2]=0;o[n>>2]=o[pe>>2];o[n+4>>2]=o[pe+4>>2];ma(e,3948,n)|0;o[de>>2]=5;o[de+4>>2]=0;o[n>>2]=o[de>>2];o[n+4>>2]=o[de+4>>2];ma(e,3960,n)|0;o[fe>>2]=6;o[fe+4>>2]=0;o[n>>2]=o[fe>>2];o[n+4>>2]=o[fe+4>>2];ma(e,3974,n)|0;o[ce>>2]=7;o[ce+4>>2]=0;o[n>>2]=o[ce>>2];o[n+4>>2]=o[ce+4>>2];ma(e,3983,n)|0;o[se>>2]=20;o[se+4>>2]=0;o[n>>2]=o[se>>2];o[n+4>>2]=o[se+4>>2];da(e,3999,n)|0;o[le>>2]=8;o[le+4>>2]=0;o[n>>2]=o[le>>2];o[n+4>>2]=o[le+4>>2];ma(e,4012,n)|0;o[ae>>2]=9;o[ae+4>>2]=0;o[n>>2]=o[ae>>2];o[n+4>>2]=o[ae+4>>2];ma(e,4022,n)|0;o[ue>>2]=21;o[ue+4>>2]=0;o[n>>2]=o[ue>>2];o[n+4>>2]=o[ue+4>>2];da(e,4039,n)|0;o[oe>>2]=10;o[oe+4>>2]=0;o[n>>2]=o[oe>>2];o[n+4>>2]=o[oe+4>>2];ma(e,4053,n)|0;o[ie>>2]=11;o[ie+4>>2]=0;o[n>>2]=o[ie>>2];o[n+4>>2]=o[ie+4>>2];ma(e,4065,n)|0;o[re>>2]=12;o[re+4>>2]=0;o[n>>2]=o[re>>2];o[n+4>>2]=o[re+4>>2];ma(e,4084,n)|0;o[ne>>2]=13;o[ne+4>>2]=0;o[n>>2]=o[ne>>2];o[n+4>>2]=o[ne+4>>2];ma(e,4097,n)|0;o[te>>2]=14;o[te+4>>2]=0;o[n>>2]=o[te>>2];o[n+4>>2]=o[te+4>>2];ma(e,4117,n)|0;o[ee>>2]=15;o[ee+4>>2]=0;o[n>>2]=o[ee>>2];o[n+4>>2]=o[ee+4>>2];ma(e,4129,n)|0;o[Z>>2]=16;o[Z+4>>2]=0;o[n>>2]=o[Z>>2];o[n+4>>2]=o[Z+4>>2];ma(e,4148,n)|0;o[J>>2]=17;o[J+4>>2]=0;o[n>>2]=o[J>>2];o[n+4>>2]=o[J+4>>2];ma(e,4161,n)|0;o[Q>>2]=18;o[Q+4>>2]=0;o[n>>2]=o[Q>>2];o[n+4>>2]=o[Q+4>>2];ma(e,4181,n)|0;o[X>>2]=5;o[X+4>>2]=0;o[n>>2]=o[X>>2];o[n+4>>2]=o[X+4>>2];va(e,4196,n)|0;o[K>>2]=6;o[K+4>>2]=0;o[n>>2]=o[K>>2];o[n+4>>2]=o[K+4>>2];va(e,4206,n)|0;o[Y>>2]=7;o[Y+4>>2]=0;o[n>>2]=o[Y>>2];o[n+4>>2]=o[Y+4>>2];va(e,4217,n)|0;o[$>>2]=3;o[$+4>>2]=0;o[n>>2]=o[$>>2];o[n+4>>2]=o[$+4>>2];ga(e,4235,n)|0;o[G>>2]=1;o[G+4>>2]=0;o[n>>2]=o[G>>2];o[n+4>>2]=o[G+4>>2];ya(e,4251,n)|0;o[q>>2]=4;o[q+4>>2]=0;o[n>>2]=o[q>>2];o[n+4>>2]=o[q+4>>2];ga(e,4263,n)|0;o[V>>2]=5;o[V+4>>2]=0;o[n>>2]=o[V>>2];o[n+4>>2]=o[V+4>>2];ga(e,4279,n)|0;o[H>>2]=6;o[H+4>>2]=0;o[n>>2]=o[H>>2];o[n+4>>2]=o[H+4>>2];ga(e,4293,n)|0;o[W>>2]=7;o[W+4>>2]=0;o[n>>2]=o[W>>2];o[n+4>>2]=o[W+4>>2];ga(e,4306,n)|0;o[z>>2]=8;o[z+4>>2]=0;o[n>>2]=o[z>>2];o[n+4>>2]=o[z+4>>2];ga(e,4323,n)|0;o[U>>2]=9;o[U+4>>2]=0;o[n>>2]=o[U>>2];o[n+4>>2]=o[U+4>>2];ga(e,4335,n)|0;o[j>>2]=2;o[j+4>>2]=0;o[n>>2]=o[j>>2];o[n+4>>2]=o[j+4>>2];ya(e,4353,n)|0;o[B>>2]=12;o[B+4>>2]=0;o[n>>2]=o[B>>2];o[n+4>>2]=o[B+4>>2];_a(e,4363,n)|0;o[L>>2]=1;o[L+4>>2]=0;o[n>>2]=o[L>>2];o[n+4>>2]=o[L+4>>2];ba(e,4376,n)|0;o[F>>2]=2;o[F+4>>2]=0;o[n>>2]=o[F>>2];o[n+4>>2]=o[F+4>>2];ba(e,4388,n)|0;o[R>>2]=13;o[R+4>>2]=0;o[n>>2]=o[R>>2];o[n+4>>2]=o[R+4>>2];_a(e,4402,n)|0;o[M>>2]=14;o[M+4>>2]=0;o[n>>2]=o[M>>2];o[n+4>>2]=o[M+4>>2];_a(e,4411,n)|0;o[N>>2]=15;o[N+4>>2]=0;o[n>>2]=o[N>>2];o[n+4>>2]=o[N+4>>2];_a(e,4421,n)|0;o[I>>2]=16;o[I+4>>2]=0;o[n>>2]=o[I>>2];o[n+4>>2]=o[I+4>>2];_a(e,4433,n)|0;o[P>>2]=17;o[P+4>>2]=0;o[n>>2]=o[P>>2];o[n+4>>2]=o[P+4>>2];_a(e,4446,n)|0;o[O>>2]=18;o[O+4>>2]=0;o[n>>2]=o[O>>2];o[n+4>>2]=o[O+4>>2];_a(e,4458,n)|0;o[A>>2]=3;o[A+4>>2]=0;o[n>>2]=o[A>>2];o[n+4>>2]=o[A+4>>2];ba(e,4471,n)|0;o[x>>2]=1;o[x+4>>2]=0;o[n>>2]=o[x>>2];o[n+4>>2]=o[x+4>>2];wa(e,4486,n)|0;o[T>>2]=10;o[T+4>>2]=0;o[n>>2]=o[T>>2];o[n+4>>2]=o[T+4>>2];ga(e,4496,n)|0;o[k>>2]=11;o[k+4>>2]=0;o[n>>2]=o[k>>2];o[n+4>>2]=o[k+4>>2];ga(e,4508,n)|0;o[C>>2]=3;o[C+4>>2]=0;o[n>>2]=o[C>>2];o[n+4>>2]=o[C+4>>2];ya(e,4519,n)|0;o[S>>2]=4;o[S+4>>2]=0;o[n>>2]=o[S>>2];o[n+4>>2]=o[S+4>>2];Ea(e,4530,n)|0;o[D>>2]=19;o[D+4>>2]=0;o[n>>2]=o[D>>2];o[n+4>>2]=o[D+4>>2];Da(e,4542,n)|0;o[E>>2]=12;o[E+4>>2]=0;o[n>>2]=o[E>>2];o[n+4>>2]=o[E+4>>2];Sa(e,4554,n)|0;o[w>>2]=13;o[w+4>>2]=0;o[n>>2]=o[w>>2];o[n+4>>2]=o[w+4>>2];Ca(e,4568,n)|0;o[b>>2]=2;o[b+4>>2]=0;o[n>>2]=o[b>>2];o[n+4>>2]=o[b+4>>2];ka(e,4578,n)|0;o[_>>2]=20;o[_+4>>2]=0;o[n>>2]=o[_>>2];o[n+4>>2]=o[_+4>>2];Ta(e,4587,n)|0;o[y>>2]=22;o[y+4>>2]=0;o[n>>2]=o[y>>2];o[n+4>>2]=o[y+4>>2];da(e,4602,n)|0;o[g>>2]=23;o[g+4>>2]=0;o[n>>2]=o[g>>2];o[n+4>>2]=o[g+4>>2];da(e,4619,n)|0;o[m>>2]=14;o[m+4>>2]=0;o[n>>2]=o[m>>2];o[n+4>>2]=o[m+4>>2];xa(e,4629,n)|0;o[v>>2]=1;o[v+4>>2]=0;o[n>>2]=o[v>>2];o[n+4>>2]=o[v+4>>2];Aa(e,4637,n)|0;o[p>>2]=4;o[p+4>>2]=0;o[n>>2]=o[p>>2];o[n+4>>2]=o[p+4>>2];ba(e,4653,n)|0;o[d>>2]=5;o[d+4>>2]=0;o[n>>2]=o[d>>2];o[n+4>>2]=o[d+4>>2];ba(e,4669,n)|0;o[f>>2]=6;o[f+4>>2]=0;o[n>>2]=o[f>>2];o[n+4>>2]=o[f+4>>2];ba(e,4686,n)|0;o[c>>2]=7;o[c+4>>2]=0;o[n>>2]=o[c>>2];o[n+4>>2]=o[c+4>>2];ba(e,4701,n)|0;o[s>>2]=8;o[s+4>>2]=0;o[n>>2]=o[s>>2];o[n+4>>2]=o[s+4>>2];ba(e,4719,n)|0;o[l>>2]=9;o[l+4>>2]=0;o[n>>2]=o[l>>2];o[n+4>>2]=o[l+4>>2];ba(e,4736,n)|0;o[a>>2]=21;o[a+4>>2]=0;o[n>>2]=o[a>>2];o[n+4>>2]=o[a+4>>2];Oa(e,4754,n)|0;o[u>>2]=2;o[u+4>>2]=0;o[n>>2]=o[u>>2];o[n+4>>2]=o[u+4>>2];wa(e,4772,n)|0;o[i>>2]=3;o[i+4>>2]=0;o[n>>2]=o[i>>2];o[n+4>>2]=o[i+4>>2];wa(e,4790,n)|0;o[r>>2]=4;o[r+4>>2]=0;o[n>>2]=o[r>>2];o[n+4>>2]=o[r+4>>2];wa(e,4808,n)|0;h=t;return}function la(e,t){e=e|0;t=t|0;var n=0;n=Mm()|0;o[e>>2]=n;Rm(n,t);cw(o[e>>2]|0);return}function sa(e,t,n){e=e|0;t=t|0;n=n|0;gm(e,Ia(t)|0,n,0);return e|0}function ca(e,t,n){e=e|0;t=t|0;n=n|0;Xv(e,Ia(t)|0,n,0);return e|0}function fa(e,t,n){e=e|0;t=t|0;n=n|0;Nv(e,Ia(t)|0,n,0);return e|0}function da(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];hv(e,t,i);h=r;return e|0}function pa(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Gh(e,t,i);h=r;return e|0}function ha(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Th(e,t,i);h=r;return e|0}function va(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];lh(e,t,i);h=r;return e|0}function ma(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Hp(e,t,i);h=r;return e|0}function ga(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Sp(e,t,i);h=r;return e|0}function ya(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];op(e,t,i);h=r;return e|0}function _a(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Cd(e,t,i);h=r;return e|0}function ba(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];ud(e,t,i);h=r;return e|0}function wa(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];zf(e,t,i);h=r;return e|0}function Ea(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Ef(e,t,i);h=r;return e|0}function Da(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Zc(e,t,i);h=r;return e|0}function Sa(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Nc(e,t,i);h=r;return e|0}function Ca(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];hc(e,t,i);h=r;return e|0}function ka(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];qs(e,t,i);h=r;return e|0}function Ta(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];ws(e,t,i);h=r;return e|0}function xa(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];ts(e,t,i);h=r;return e|0}function Aa(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Ol(e,t,i);h=r;return e|0}function Oa(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Pa(e,t,i);h=r;return e|0}function Pa(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Na(e,n,i,1);h=r;return}function Ia(e){e=e|0;return e|0}function Na(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=Ma()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Ra(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,Fa(u,r)|0,r);h=i;return}function Ma(){var e=0,t=0;if(!(r[7616]|0)){Ya(9136);Fe(24,9136,g|0)|0;t=7616;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9136)|0)){e=9136;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Ya(9136)}return 9136}function Ra(e){e=e|0;return 0}function Fa(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=Ma()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Wa(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{Ha(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function La(e,t,n,r,i,u){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;u=u|0;var a=0,l=0,s=0,c=0,f=0,d=0,p=0,v=0;a=h;h=h+32|0;p=a+24|0;d=a+20|0;s=a+16|0;f=a+12|0;c=a+8|0;l=a+4|0;v=a;o[d>>2]=t;o[s>>2]=n;o[f>>2]=r;o[c>>2]=i;o[l>>2]=u;u=e+28|0;o[v>>2]=o[u>>2];o[p>>2]=o[v>>2];Ba(e+24|0,p,d,f,c,s,l)|0;o[u>>2]=o[o[u>>2]>>2];h=a;return}function Ba(e,t,n,r,i,u,a){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;u=u|0;a=a|0;e=ja(t)|0;t=$T(24)|0;Ua(t+4|0,o[n>>2]|0,o[r>>2]|0,o[i>>2]|0,o[u>>2]|0,o[a>>2]|0);o[t>>2]=o[e>>2];o[e>>2]=t;return t|0}function ja(e){e=e|0;return o[e>>2]|0}function Ua(e,t,n,r,i,u){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;u=u|0;o[e>>2]=t;o[e+4>>2]=n;o[e+8>>2]=r;o[e+12>>2]=i;o[e+16>>2]=u;return}function za(e,t){e=e|0;t=t|0;return t|e|0}function Wa(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function Ha(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Va(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;qa(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Wa(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Ga(e,l);$a(l);h=c;return}}function Va(e){e=e|0;return 357913941}function qa(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Ga(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function $a(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function Ya(e){e=e|0;Ja(e);return}function Ka(e){e=e|0;Qa(e+24|0);return}function Xa(e){e=e|0;return o[e>>2]|0}function Qa(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function Ja(e){e=e|0;var t=0;t=Za()|0;nl(e,2,3,t,el()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Za(){return 9228}function el(){return 1140}function tl(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0;n=h;h=h+16|0;r=n+8|0;i=n;u=rl(e)|0;e=o[u+4>>2]|0;o[i>>2]=o[u>>2];o[i+4>>2]=e;o[r>>2]=o[i>>2];o[r+4>>2]=o[i+4>>2];t=il(t,r)|0;h=n;return t|0}function nl(e,t,n,r,i,u){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;u=u|0;o[e>>2]=t;o[e+4>>2]=n;o[e+8>>2]=r;o[e+12>>2]=i;o[e+16>>2]=u;return}function rl(e){e=e|0;return(o[(Ma()|0)+24>>2]|0)+(e*12|0)|0}function il(e,t){e=e|0;t=t|0;var n=0,r=0,i=0;i=h;h=h+48|0;r=i;n=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)n=o[(o[e>>2]|0)+n>>2]|0;vA[n&31](r,e);r=ol(r)|0;h=i;return r|0}function ol(e){e=e|0;var t=0,n=0,r=0,i=0;i=h;h=h+32|0;t=i+12|0;n=i;r=al(ul()|0)|0;if(!r)e=dl(e)|0;else{ll(t,r);sl(n,t);cl(e,n);e=fl(t)|0}h=i;return e|0}function ul(){var e=0;if(!(r[7632]|0)){Dl(9184);Fe(25,9184,g|0)|0;e=7632;o[e>>2]=1;o[e+4>>2]=0}return 9184}function al(e){e=e|0;return o[e+36>>2]|0}function ll(e,t){e=e|0;t=t|0;o[e>>2]=t;o[e+4>>2]=e;o[e+8>>2]=0;return}function sl(e,t){e=e|0;t=t|0;o[e>>2]=o[t>>2];o[e+4>>2]=o[t+4>>2];o[e+8>>2]=0;return}function cl(e,t){e=e|0;t=t|0;gl(t,e,e+8|0,e+16|0,e+24|0,e+32|0,e+40|0)|0;return}function fl(e){e=e|0;return o[(o[e+4>>2]|0)+8>>2]|0}function dl(e){e=e|0;var t=0,n=0,r=0,i=0,u=0,a=0,l=0,s=0;s=h;h=h+16|0;n=s+4|0;r=s;i=UD(8)|0;u=i;a=$T(48)|0;l=a;t=l+48|0;do{o[l>>2]=o[e>>2];l=l+4|0;e=e+4|0}while((l|0)<(t|0));t=u+4|0;o[t>>2]=a;l=$T(8)|0;a=o[t>>2]|0;o[r>>2]=0;o[n>>2]=o[r>>2];pl(l,a,n);o[i>>2]=l;h=s;return u|0}function pl(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;n=$T(16)|0;o[n+4>>2]=0;o[n+8>>2]=0;o[n>>2]=1092;o[n+12>>2]=t;o[e+4>>2]=n;return}function hl(e){e=e|0;zT(e);KT(e);return}function vl(e){e=e|0;e=o[e+12>>2]|0;if(e|0)KT(e);return}function ml(e){e=e|0;KT(e);return}function gl(e,t,n,r,i,u,a){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;u=u|0;a=a|0;u=yl(o[e>>2]|0,t,n,r,i,u,a)|0;a=e+4|0;o[(o[a>>2]|0)+8>>2]=u;return o[(o[a>>2]|0)+8>>2]|0}function yl(e,t,n,r,i,o,u){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;o=o|0;u=u|0;var a=0,l=0;a=h;h=h+16|0;l=a;Dk(l);e=Eu(e)|0;u=_l(e,+c[t>>3],+c[n>>3],+c[r>>3],+c[i>>3],+c[o>>3],+c[u>>3])|0;Ck(l);h=a;return u|0}function _l(e,t,n,r,i,o,u){e=e|0;t=+t;n=+n;r=+r;i=+i;o=+o;u=+u;var a=0;a=Cu(bl()|0)|0;t=+ku(t);n=+ku(n);r=+ku(r);i=+ku(i);o=+ku(o);return Te(0,a|0,e|0,+t,+n,+r,+i,+o,+ +ku(u))|0}function bl(){var e=0;if(!(r[7624]|0)){wl(9172);e=7624;o[e>>2]=1;o[e+4>>2]=0}return 9172}function wl(e){e=e|0;Lu(e,El()|0,6);return}function El(){return 1112}function Dl(e){e=e|0;Al(e);return}function Sl(e){e=e|0;Cl(e+24|0);kl(e+16|0);return}function Cl(e){e=e|0;xl(e);return}function kl(e){e=e|0;Tl(e);return}function Tl(e){e=e|0;var t=0,n=0;t=o[e>>2]|0;if(t|0)do{n=t;t=o[t>>2]|0;KT(n)}while((t|0)!=0);o[e>>2]=0;return}function xl(e){e=e|0;var t=0,n=0;t=o[e>>2]|0;if(t|0)do{n=t;t=o[t>>2]|0;KT(n)}while((t|0)!=0);o[e>>2]=0;return}function Al(e){e=e|0;var t=0;o[e+16>>2]=0;o[e+20>>2]=0;t=e+24|0;o[t>>2]=0;o[e+28>>2]=t;o[e+36>>2]=0;r[e+40>>0]=0;r[e+41>>0]=0;return}function Ol(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Pl(e,n,i,0);h=r;return}function Pl(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=Il()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Nl(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,Ml(u,r)|0,r);h=i;return}function Il(){var e=0,t=0;if(!(r[7640]|0)){zl(9232);Fe(26,9232,g|0)|0;t=7640;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9232)|0)){e=9232;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));zl(9232)}return 9232}function Nl(e){e=e|0;return 0}function Ml(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=Il()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Rl(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{Fl(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Rl(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function Fl(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Ll(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;Bl(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Rl(u,r,n);o[s>>2]=(o[s>>2]|0)+12;jl(e,l);Ul(l);h=c;return}}function Ll(e){e=e|0;return 357913941}function Bl(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function jl(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Ul(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function zl(e){e=e|0;Vl(e);return}function Wl(e){e=e|0;Hl(e+24|0);return}function Hl(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function Vl(e){e=e|0;var t=0;t=Za()|0;nl(e,2,1,t,ql()|0,3);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function ql(){return 1144}function Gl(e,t,n,r,i){e=e|0;t=t|0;n=+n;r=+r;i=i|0;var u=0,a=0,l=0,s=0;u=h;h=h+16|0;a=u+8|0;l=u;s=$l(e)|0;e=o[s+4>>2]|0;o[l>>2]=o[s>>2];o[l+4>>2]=e;o[a>>2]=o[l>>2];o[a+4>>2]=o[l+4>>2];Yl(t,a,n,r,i);h=u;return}function $l(e){e=e|0;return(o[(Il()|0)+24>>2]|0)+(e*12|0)|0}function Yl(e,t,n,r,i){e=e|0;t=t|0;n=+n;r=+r;i=i|0;var u=0,a=0,l=0,s=0,c=0;c=h;h=h+16|0;a=c+2|0;l=c+1|0;s=c;u=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)u=o[(o[e>>2]|0)+u>>2]|0;Kl(a,n);n=+Xl(a,n);Kl(l,r);r=+Xl(l,r);Ql(s,i);s=Jl(s,i)|0;gA[u&1](e,n,r,s);h=c;return}function Kl(e,t){e=e|0;t=+t;return}function Xl(e,t){e=e|0;t=+t;return+ +es(t)}function Ql(e,t){e=e|0;t=t|0;return}function Jl(e,t){e=e|0;t=t|0;return Zl(t)|0}function Zl(e){e=e|0;return e|0}function es(e){e=+e;return+e}function ts(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];ns(e,n,i,1);h=r;return}function ns(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=rs()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=is(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,os(u,r)|0,r);h=i;return}function rs(){var e=0,t=0;if(!(r[7648]|0)){ds(9268);Fe(27,9268,g|0)|0;t=7648;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9268)|0)){e=9268;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));ds(9268)}return 9268}function is(e){e=e|0;return 0}function os(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=rs()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];us(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{as(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function us(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function as(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=ls(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;ss(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];us(u,r,n);o[s>>2]=(o[s>>2]|0)+12;cs(e,l);fs(l);h=c;return}}function ls(e){e=e|0;return 357913941}function ss(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function cs(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function fs(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function ds(e){e=e|0;vs(e);return}function ps(e){e=e|0;hs(e+24|0);return}function hs(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function vs(e){e=e|0;var t=0;t=Za()|0;nl(e,2,4,t,ms()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function ms(){return 1160}function gs(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0;n=h;h=h+16|0;r=n+8|0;i=n;u=ys(e)|0;e=o[u+4>>2]|0;o[i>>2]=o[u>>2];o[i+4>>2]=e;o[r>>2]=o[i>>2];o[r+4>>2]=o[i+4>>2];t=_s(t,r)|0;h=n;return t|0}function ys(e){e=e|0;return(o[(rs()|0)+24>>2]|0)+(e*12|0)|0}function _s(e,t){e=e|0;t=t|0;var n=0;n=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)n=o[(o[e>>2]|0)+n>>2]|0;return bs(mA[n&31](e)|0)|0}function bs(e){e=e|0;return e&1|0}function ws(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Es(e,n,i,0);h=r;return}function Es(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=Ds()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Ss(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,Cs(u,r)|0,r);h=i;return}function Ds(){var e=0,t=0;if(!(r[7656]|0)){Is(9304);Fe(28,9304,g|0)|0;t=7656;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9304)|0)){e=9304;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Is(9304)}return 9304}function Ss(e){e=e|0;return 0}function Cs(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=Ds()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];ks(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{Ts(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function ks(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function Ts(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=xs(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;As(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];ks(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Os(e,l);Ps(l);h=c;return}}function xs(e){e=e|0;return 357913941}function As(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Os(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Ps(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function Is(e){e=e|0;Rs(e);return}function Ns(e){e=e|0;Ms(e+24|0);return}function Ms(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function Rs(e){e=e|0;var t=0;t=Za()|0;nl(e,2,5,t,Fs()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Fs(){return 1164}function Ls(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=Bs(e)|0;e=o[a+4>>2]|0;o[u>>2]=o[a>>2];o[u+4>>2]=e;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];js(t,i,n);h=r;return}function Bs(e){e=e|0;return(o[(Ds()|0)+24>>2]|0)+(e*12|0)|0}function js(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0;u=h;h=h+16|0;i=u;r=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)r=o[(o[e>>2]|0)+r>>2]|0;Us(i,n);n=zs(i,n)|0;vA[r&31](e,n);Ws(i);h=u;return}function Us(e,t){e=e|0;t=t|0;Hs(e,t);return}function zs(e,t){e=e|0;t=t|0;return e|0}function Ws(e){e=e|0;Ji(e);return}function Hs(e,t){e=e|0;t=t|0;Vs(e,t);return}function Vs(e,t){e=e|0;t=t|0;o[e>>2]=t;return}function qs(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Gs(e,n,i,0);h=r;return}function Gs(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=$s()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Ys(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,Ks(u,r)|0,r);h=i;return}function $s(){var e=0,t=0;if(!(r[7664]|0)){nc(9340);Fe(29,9340,g|0)|0;t=7664;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9340)|0)){e=9340;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));nc(9340)}return 9340}function Ys(e){e=e|0;return 0}function Ks(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=$s()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Xs(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{Qs(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Xs(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function Qs(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Js(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;Zs(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Xs(u,r,n);o[s>>2]=(o[s>>2]|0)+12;ec(e,l);tc(l);h=c;return}}function Js(e){e=e|0;return 357913941}function Zs(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function ec(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function tc(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function nc(e){e=e|0;oc(e);return}function rc(e){e=e|0;ic(e+24|0);return}function ic(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function oc(e){e=e|0;var t=0;t=Za()|0;nl(e,2,4,t,uc()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function uc(){return 1180}function ac(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=lc(e)|0;e=o[a+4>>2]|0;o[u>>2]=o[a>>2];o[u+4>>2]=e;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];n=sc(t,i,n)|0;h=r;return n|0}function lc(e){e=e|0;return(o[($s()|0)+24>>2]|0)+(e*12|0)|0}function sc(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0;u=h;h=h+16|0;i=u;r=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)r=o[(o[e>>2]|0)+r>>2]|0;cc(i,n);i=fc(i,n)|0;i=dc(DA[r&15](e,i)|0)|0;h=u;return i|0}function cc(e,t){e=e|0;t=t|0;return}function fc(e,t){e=e|0;t=t|0;return pc(t)|0}function dc(e){e=e|0;return e|0}function pc(e){e=e|0;return e|0}function hc(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];vc(e,n,i,0);h=r;return}function vc(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=mc()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=gc(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,yc(u,r)|0,r);h=i;return}function mc(){var e=0,t=0;if(!(r[7672]|0)){Cc(9376);Fe(30,9376,g|0)|0;t=7672;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9376)|0)){e=9376;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Cc(9376)}return 9376}function gc(e){e=e|0;return 0}function yc(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=mc()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];_c(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{bc(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function _c(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function bc(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=wc(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;Ec(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];_c(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Dc(e,l);Sc(l);h=c;return}}function wc(e){e=e|0;return 357913941}function Ec(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Dc(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Sc(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function Cc(e){e=e|0;xc(e);return}function kc(e){e=e|0;Tc(e+24|0);return}function Tc(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function xc(e){e=e|0;var t=0;t=Za()|0;nl(e,2,5,t,Ac()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Ac(){return 1196}function Oc(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0;n=h;h=h+16|0;r=n+8|0;i=n;u=Pc(e)|0;e=o[u+4>>2]|0;o[i>>2]=o[u>>2];o[i+4>>2]=e;o[r>>2]=o[i>>2];o[r+4>>2]=o[i+4>>2];t=Ic(t,r)|0;h=n;return t|0}function Pc(e){e=e|0;return(o[(mc()|0)+24>>2]|0)+(e*12|0)|0}function Ic(e,t){e=e|0;t=t|0;var n=0;n=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)n=o[(o[e>>2]|0)+n>>2]|0;return dc(mA[n&31](e)|0)|0}function Nc(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Mc(e,n,i,1);h=r;return}function Mc(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=Rc()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Fc(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,Lc(u,r)|0,r);h=i;return}function Rc(){var e=0,t=0;if(!(r[7680]|0)){Vc(9412);Fe(31,9412,g|0)|0;t=7680;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9412)|0)){e=9412;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Vc(9412)}return 9412}function Fc(e){e=e|0;return 0}function Lc(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=Rc()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Bc(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{jc(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Bc(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function jc(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Uc(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;zc(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Bc(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Wc(e,l);Hc(l);h=c;return}}function Uc(e){e=e|0;return 357913941}function zc(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Wc(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Hc(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function Vc(e){e=e|0;$c(e);return}function qc(e){e=e|0;Gc(e+24|0);return}function Gc(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function $c(e){e=e|0;var t=0;t=Za()|0;nl(e,2,6,t,Yc()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Yc(){return 1200}function Kc(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0;n=h;h=h+16|0;r=n+8|0;i=n;u=Xc(e)|0;e=o[u+4>>2]|0;o[i>>2]=o[u>>2];o[i+4>>2]=e;o[r>>2]=o[i>>2];o[r+4>>2]=o[i+4>>2];t=Qc(t,r)|0;h=n;return t|0}function Xc(e){e=e|0;return(o[(Rc()|0)+24>>2]|0)+(e*12|0)|0}function Qc(e,t){e=e|0;t=t|0;var n=0;n=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)n=o[(o[e>>2]|0)+n>>2]|0;return Jc(mA[n&31](e)|0)|0}function Jc(e){e=e|0;return e|0}function Zc(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];ef(e,n,i,0);h=r;return}function ef(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=tf()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=nf(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,rf(u,r)|0,r);h=i;return}function tf(){var e=0,t=0;if(!(r[7688]|0)){ff(9448);Fe(32,9448,g|0)|0;t=7688;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9448)|0)){e=9448;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));ff(9448)}return 9448}function nf(e){e=e|0;return 0}function rf(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=tf()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];of(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{uf(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function of(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function uf(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=af(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;lf(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];of(u,r,n);o[s>>2]=(o[s>>2]|0)+12;sf(e,l);cf(l);h=c;return}}function af(e){e=e|0;return 357913941}function lf(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function sf(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function cf(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function ff(e){e=e|0;hf(e);return}function df(e){e=e|0;pf(e+24|0);return}function pf(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function hf(e){e=e|0;var t=0;t=Za()|0;nl(e,2,6,t,vf()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function vf(){return 1204}function mf(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=gf(e)|0;e=o[a+4>>2]|0;o[u>>2]=o[a>>2];o[u+4>>2]=e;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];yf(t,i,n);h=r;return}function gf(e){e=e|0;return(o[(tf()|0)+24>>2]|0)+(e*12|0)|0}function yf(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0;u=h;h=h+16|0;i=u;r=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)r=o[(o[e>>2]|0)+r>>2]|0;_f(i,n);i=bf(i,n)|0;vA[r&31](e,i);h=u;return}function _f(e,t){e=e|0;t=t|0;return}function bf(e,t){e=e|0;t=t|0;return wf(t)|0}function wf(e){e=e|0;return e|0}function Ef(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Df(e,n,i,0);h=r;return}function Df(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=Sf()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Cf(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,kf(u,r)|0,r);h=i;return}function Sf(){var e=0,t=0;if(!(r[7696]|0)){Nf(9484);Fe(33,9484,g|0)|0;t=7696;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9484)|0)){e=9484;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Nf(9484)}return 9484}function Cf(e){e=e|0;return 0}function kf(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=Sf()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Tf(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{xf(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Tf(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function xf(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Af(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;Of(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Tf(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Pf(e,l);If(l);h=c;return}}function Af(e){e=e|0;return 357913941}function Of(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Pf(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function If(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function Nf(e){e=e|0;Ff(e);return}function Mf(e){e=e|0;Rf(e+24|0);return}function Rf(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function Ff(e){e=e|0;var t=0;t=Za()|0;nl(e,2,1,t,Lf()|0,2);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Lf(){return 1212}function Bf(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0;i=h;h=h+16|0;u=i+8|0;a=i;l=jf(e)|0;e=o[l+4>>2]|0;o[a>>2]=o[l>>2];o[a+4>>2]=e;o[u>>2]=o[a>>2];o[u+4>>2]=o[a+4>>2];Uf(t,u,n,r);h=i;return}function jf(e){e=e|0;return(o[(Sf()|0)+24>>2]|0)+(e*12|0)|0}function Uf(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0;l=h;h=h+16|0;u=l+1|0;a=l;i=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)i=o[(o[e>>2]|0)+i>>2]|0;_f(u,n);u=bf(u,n)|0;cc(a,r);a=fc(a,r)|0;PA[i&15](e,u,a);h=l;return}function zf(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Wf(e,n,i,1);h=r;return}function Wf(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=Hf()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Vf(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,qf(u,r)|0,r);h=i;return}function Hf(){var e=0,t=0;if(!(r[7704]|0)){Jf(9520);Fe(34,9520,g|0)|0;t=7704;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9520)|0)){e=9520;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Jf(9520)}return 9520}function Vf(e){e=e|0;return 0}function qf(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=Hf()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Gf(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{$f(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Gf(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function $f(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Yf(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;Kf(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Gf(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Xf(e,l);Qf(l);h=c;return}}function Yf(e){e=e|0;return 357913941}function Kf(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Xf(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Qf(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function Jf(e){e=e|0;td(e);return}function Zf(e){e=e|0;ed(e+24|0);return}function ed(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function td(e){e=e|0;var t=0;t=Za()|0;nl(e,2,1,t,nd()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function nd(){return 1224}function rd(e,t,n){e=e|0;t=t|0;n=n|0;var r=0.0,i=0,u=0,a=0,l=0;i=h;h=h+16|0;u=i+8|0;a=i;l=id(e)|0;e=o[l+4>>2]|0;o[a>>2]=o[l>>2];o[a+4>>2]=e;o[u>>2]=o[a>>2];o[u+4>>2]=o[a+4>>2];r=+od(t,u,n);h=i;return+r}function id(e){e=e|0;return(o[(Hf()|0)+24>>2]|0)+(e*12|0)|0}function od(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0.0;u=h;h=h+16|0;i=u;r=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)r=o[(o[e>>2]|0)+r>>2]|0;Ql(i,n);i=Jl(i,n)|0;a=+Mu(+kA[r&7](e,i));h=u;return+a}function ud(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];ad(e,n,i,1);h=r;return}function ad(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=ld()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=sd(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,cd(u,r)|0,r);h=i;return}function ld(){var e=0,t=0;if(!(r[7712]|0)){gd(9556);Fe(35,9556,g|0)|0;t=7712;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9556)|0)){e=9556;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));gd(9556)}return 9556}function sd(e){e=e|0;return 0}function cd(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=ld()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];fd(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{dd(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function fd(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function dd(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=pd(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;hd(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];fd(u,r,n);o[s>>2]=(o[s>>2]|0)+12;vd(e,l);md(l);h=c;return}}function pd(e){e=e|0;return 357913941}function hd(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function vd(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function md(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function gd(e){e=e|0;bd(e);return}function yd(e){e=e|0;_d(e+24|0);return}function _d(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function bd(e){e=e|0;var t=0;t=Za()|0;nl(e,2,5,t,wd()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function wd(){return 1232}function Ed(e,t){e=e|0;t=t|0;var n=0.0,r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=Dd(e)|0;e=o[a+4>>2]|0;o[u>>2]=o[a>>2];o[u+4>>2]=e;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];n=+Sd(t,i);h=r;return+n}function Dd(e){e=e|0;return(o[(ld()|0)+24>>2]|0)+(e*12|0)|0}function Sd(e,t){e=e|0;t=t|0;var n=0;n=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)n=o[(o[e>>2]|0)+n>>2]|0;return+ +Mu(+wA[n&15](e))}function Cd(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];kd(e,n,i,1);h=r;return}function kd(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=Td()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=xd(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,Ad(u,r)|0,r);h=i;return}function Td(){var e=0,t=0;if(!(r[7720]|0)){Fd(9592);Fe(36,9592,g|0)|0;t=7720;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9592)|0)){e=9592;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Fd(9592)}return 9592}function xd(e){e=e|0;return 0}function Ad(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=Td()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Od(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{Pd(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Od(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function Pd(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Id(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;Nd(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Od(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Md(e,l);Rd(l);h=c;return}}function Id(e){e=e|0;return 357913941}function Nd(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Md(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Rd(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function Fd(e){e=e|0;jd(e);return}function Ld(e){e=e|0;Bd(e+24|0);return}function Bd(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function jd(e){e=e|0;var t=0;t=Za()|0;nl(e,2,7,t,Ud()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Ud(){return 1276}function zd(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0;n=h;h=h+16|0;r=n+8|0;i=n;u=Wd(e)|0;e=o[u+4>>2]|0;o[i>>2]=o[u>>2];o[i+4>>2]=e;o[r>>2]=o[i>>2];o[r+4>>2]=o[i+4>>2];t=Hd(t,r)|0;h=n;return t|0}function Wd(e){e=e|0;return(o[(Td()|0)+24>>2]|0)+(e*12|0)|0}function Hd(e,t){e=e|0;t=t|0;var n=0,r=0,i=0;i=h;h=h+16|0;r=i;n=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)n=o[(o[e>>2]|0)+n>>2]|0;vA[n&31](r,e);r=Vd(r)|0;h=i;return r|0}function Vd(e){e=e|0;var t=0,n=0,r=0,i=0;i=h;h=h+32|0;t=i+12|0;n=i;r=al(qd()|0)|0;if(!r)e=$d(e)|0;else{ll(t,r);sl(n,t);Gd(e,n);e=fl(t)|0}h=i;return e|0}function qd(){var e=0;if(!(r[7736]|0)){ip(9640);Fe(25,9640,g|0)|0;e=7736;o[e>>2]=1;o[e+4>>2]=0}return 9640}function Gd(e,t){e=e|0;t=t|0;Jd(t,e,e+8|0)|0;return}function $d(e){e=e|0;var t=0,n=0,r=0,i=0,u=0,a=0,l=0;n=h;h=h+16|0;i=n+4|0;a=n;r=UD(8)|0;t=r;l=$T(16)|0;o[l>>2]=o[e>>2];o[l+4>>2]=o[e+4>>2];o[l+8>>2]=o[e+8>>2];o[l+12>>2]=o[e+12>>2];u=t+4|0;o[u>>2]=l;e=$T(8)|0;u=o[u>>2]|0;o[a>>2]=0;o[i>>2]=o[a>>2];Yd(e,u,i);o[r>>2]=e;h=n;return t|0}function Yd(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;n=$T(16)|0;o[n+4>>2]=0;o[n+8>>2]=0;o[n>>2]=1244;o[n+12>>2]=t;o[e+4>>2]=n;return}function Kd(e){e=e|0;zT(e);KT(e);return}function Xd(e){e=e|0;e=o[e+12>>2]|0;if(e|0)KT(e);return}function Qd(e){e=e|0;KT(e);return}function Jd(e,t,n){e=e|0;t=t|0;n=n|0;t=Zd(o[e>>2]|0,t,n)|0;n=e+4|0;o[(o[n>>2]|0)+8>>2]=t;return o[(o[n>>2]|0)+8>>2]|0}function Zd(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0;r=h;h=h+16|0;i=r;Dk(i);e=Eu(e)|0;n=ep(e,o[t>>2]|0,+c[n>>3])|0;Ck(i);h=r;return n|0}function ep(e,t,n){e=e|0;t=t|0;n=+n;var r=0;r=Cu(tp()|0)|0;t=Tu(t)|0;return xe(0,r|0,e|0,t|0,+ +ku(n))|0}function tp(){var e=0;if(!(r[7728]|0)){np(9628);e=7728;o[e>>2]=1;o[e+4>>2]=0}return 9628}function np(e){e=e|0;Lu(e,rp()|0,2);return}function rp(){return 1264}function ip(e){e=e|0;Al(e);return}function op(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];up(e,n,i,1);h=r;return}function up(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=ap()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=lp(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,sp(u,r)|0,r);h=i;return}function ap(){var e=0,t=0;if(!(r[7744]|0)){mp(9684);Fe(37,9684,g|0)|0;t=7744;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9684)|0)){e=9684;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));mp(9684)}return 9684}function lp(e){e=e|0;return 0}function sp(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=ap()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];cp(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{fp(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function cp(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function fp(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=dp(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;pp(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];cp(u,r,n);o[s>>2]=(o[s>>2]|0)+12;hp(e,l);vp(l);h=c;return}}function dp(e){e=e|0;return 357913941}function pp(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function hp(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function vp(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function mp(e){e=e|0;_p(e);return}function gp(e){e=e|0;yp(e+24|0);return}function yp(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function _p(e){e=e|0;var t=0;t=Za()|0;nl(e,2,5,t,bp()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function bp(){return 1280}function wp(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=Ep(e)|0;e=o[a+4>>2]|0;o[u>>2]=o[a>>2];o[u+4>>2]=e;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];n=Dp(t,i,n)|0;h=r;return n|0}function Ep(e){e=e|0;return(o[(ap()|0)+24>>2]|0)+(e*12|0)|0}function Dp(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;a=h;h=h+32|0;i=a;u=a+16|0;r=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)r=o[(o[e>>2]|0)+r>>2]|0;Ql(u,n);u=Jl(u,n)|0;PA[r&15](i,e,u);u=Vd(i)|0;h=a;return u|0}function Sp(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Cp(e,n,i,1);h=r;return}function Cp(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=kp()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Tp(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,xp(u,r)|0,r);h=i;return}function kp(){var e=0,t=0;if(!(r[7752]|0)){Rp(9720);Fe(38,9720,g|0)|0;t=7752;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9720)|0)){e=9720;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Rp(9720)}return 9720}function Tp(e){e=e|0;return 0}function xp(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=kp()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Ap(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{Op(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Ap(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function Op(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Pp(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;Ip(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Ap(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Np(e,l);Mp(l);h=c;return}}function Pp(e){e=e|0;return 357913941}function Ip(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Np(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Mp(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function Rp(e){e=e|0;Bp(e);return}function Fp(e){e=e|0;Lp(e+24|0);return}function Lp(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function Bp(e){e=e|0;var t=0;t=Za()|0;nl(e,2,8,t,jp()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function jp(){return 1288}function Up(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0;n=h;h=h+16|0;r=n+8|0;i=n;u=zp(e)|0;e=o[u+4>>2]|0;o[i>>2]=o[u>>2];o[i+4>>2]=e;o[r>>2]=o[i>>2];o[r+4>>2]=o[i+4>>2];t=Wp(t,r)|0;h=n;return t|0}function zp(e){e=e|0;return(o[(kp()|0)+24>>2]|0)+(e*12|0)|0}function Wp(e,t){e=e|0;t=t|0;var n=0;n=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)n=o[(o[e>>2]|0)+n>>2]|0;return Nu(mA[n&31](e)|0)|0}function Hp(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Vp(e,n,i,0);h=r;return}function Vp(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=qp()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Gp(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,$p(u,r)|0,r);h=i;return}function qp(){var e=0,t=0;if(!(r[7760]|0)){eh(9756);Fe(39,9756,g|0)|0;t=7760;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9756)|0)){e=9756;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));eh(9756)}return 9756}function Gp(e){e=e|0;return 0}function $p(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=qp()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Yp(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{Kp(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Yp(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function Kp(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Xp(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;Qp(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Yp(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Jp(e,l);Zp(l);h=c;return}}function Xp(e){e=e|0;return 357913941}function Qp(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Jp(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Zp(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function eh(e){e=e|0;rh(e);return}function th(e){e=e|0;nh(e+24|0);return}function nh(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function rh(e){e=e|0;var t=0;t=Za()|0;nl(e,2,8,t,ih()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function ih(){return 1292}function oh(e,t,n){e=e|0;t=t|0;n=+n;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=uh(e)|0;e=o[a+4>>2]|0;o[u>>2]=o[a>>2];o[u+4>>2]=e;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];ah(t,i,n);h=r;return}function uh(e){e=e|0;return(o[(qp()|0)+24>>2]|0)+(e*12|0)|0}function ah(e,t,n){e=e|0;t=t|0;n=+n;var r=0,i=0,u=0;u=h;h=h+16|0;i=u;r=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)r=o[(o[e>>2]|0)+r>>2]|0;Kl(i,n);n=+Xl(i,n);dA[r&31](e,n);h=u;return}function lh(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];sh(e,n,i,0);h=r;return}function sh(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=ch()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=fh(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,dh(u,r)|0,r);h=i;return}function ch(){var e=0,t=0;if(!(r[7768]|0)){_h(9792);Fe(40,9792,g|0)|0;t=7768;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9792)|0)){e=9792;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));_h(9792)}return 9792}function fh(e){e=e|0;return 0}function dh(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=ch()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];ph(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{hh(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function ph(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function hh(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=vh(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;mh(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];ph(u,r,n);o[s>>2]=(o[s>>2]|0)+12;gh(e,l);yh(l);h=c;return}}function vh(e){e=e|0;return 357913941}function mh(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function gh(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function yh(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function _h(e){e=e|0;Eh(e);return}function bh(e){e=e|0;wh(e+24|0);return}function wh(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function Eh(e){e=e|0;var t=0;t=Za()|0;nl(e,2,1,t,Dh()|0,2);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Dh(){return 1300}function Sh(e,t,n,r){e=e|0;t=t|0;n=n|0;r=+r;var i=0,u=0,a=0,l=0;i=h;h=h+16|0;u=i+8|0;a=i;l=Ch(e)|0;e=o[l+4>>2]|0;o[a>>2]=o[l>>2];o[a+4>>2]=e;o[u>>2]=o[a>>2];o[u+4>>2]=o[a+4>>2];kh(t,u,n,r);h=i;return}function Ch(e){e=e|0;return(o[(ch()|0)+24>>2]|0)+(e*12|0)|0}function kh(e,t,n,r){e=e|0;t=t|0;n=n|0;r=+r;var i=0,u=0,a=0,l=0;l=h;h=h+16|0;u=l+1|0;a=l;i=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)i=o[(o[e>>2]|0)+i>>2]|0;Ql(u,n);u=Jl(u,n)|0;Kl(a,r);r=+Xl(a,r);NA[i&15](e,u,r);h=l;return}function Th(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];xh(e,n,i,0);h=r;return}function xh(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=Ah()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Oh(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,Ph(u,r)|0,r);h=i;return}function Ah(){var e=0,t=0;if(!(r[7776]|0)){Bh(9828);Fe(41,9828,g|0)|0;t=7776;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9828)|0)){e=9828;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Bh(9828)}return 9828}function Oh(e){e=e|0;return 0}function Ph(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=Ah()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Ih(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{Nh(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Ih(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function Nh(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Mh(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;Rh(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Ih(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Fh(e,l);Lh(l);h=c;return}}function Mh(e){e=e|0;return 357913941}function Rh(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Fh(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Lh(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function Bh(e){e=e|0;zh(e);return}function jh(e){e=e|0;Uh(e+24|0);return}function Uh(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function zh(e){e=e|0;var t=0;t=Za()|0;nl(e,2,7,t,Wh()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Wh(){return 1312}function Hh(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=Vh(e)|0;e=o[a+4>>2]|0;o[u>>2]=o[a>>2];o[u+4>>2]=e;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];qh(t,i,n);h=r;return}function Vh(e){e=e|0;return(o[(Ah()|0)+24>>2]|0)+(e*12|0)|0}function qh(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0;u=h;h=h+16|0;i=u;r=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)r=o[(o[e>>2]|0)+r>>2]|0;Ql(i,n);i=Jl(i,n)|0;vA[r&31](e,i);h=u;return}function Gh(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];$h(e,n,i,0);h=r;return}function $h(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=Yh()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Kh(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,Xh(u,r)|0,r);h=i;return}function Yh(){var e=0,t=0;if(!(r[7784]|0)){rv(9864);Fe(42,9864,g|0)|0;t=7784;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9864)|0)){e=9864;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));rv(9864)}return 9864}function Kh(e){e=e|0;return 0}function Xh(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=Yh()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Qh(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{Jh(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Qh(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function Jh(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Zh(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;ev(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Qh(u,r,n);o[s>>2]=(o[s>>2]|0)+12;tv(e,l);nv(l);h=c;return}}function Zh(e){e=e|0;return 357913941}function ev(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function tv(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function nv(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function rv(e){e=e|0;uv(e);return}function iv(e){e=e|0;ov(e+24|0);return}function ov(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function uv(e){e=e|0;var t=0;t=Za()|0;nl(e,2,8,t,av()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function av(){return 1320}function lv(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=sv(e)|0;e=o[a+4>>2]|0;o[u>>2]=o[a>>2];o[u+4>>2]=e;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];cv(t,i,n);h=r;return}function sv(e){e=e|0;return(o[(Yh()|0)+24>>2]|0)+(e*12|0)|0}function cv(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0;u=h;h=h+16|0;i=u;r=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)r=o[(o[e>>2]|0)+r>>2]|0;fv(i,n);i=dv(i,n)|0;vA[r&31](e,i);h=u;return}function fv(e,t){e=e|0;t=t|0;return}function dv(e,t){e=e|0;t=t|0;return pv(t)|0}function pv(e){e=e|0;return e|0}function hv(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];vv(e,n,i,0);h=r;return}function vv(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=mv()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=gv(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,yv(u,r)|0,r);h=i;return}function mv(){var e=0,t=0;if(!(r[7792]|0)){Cv(9900);Fe(43,9900,g|0)|0;t=7792;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9900)|0)){e=9900;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Cv(9900)}return 9900}function gv(e){e=e|0;return 0}function yv(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=mv()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];_v(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{bv(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function _v(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function bv(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=wv(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;Ev(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];_v(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Dv(e,l);Sv(l);h=c;return}}function wv(e){e=e|0;return 357913941}function Ev(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Dv(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Sv(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function Cv(e){e=e|0;xv(e);return}function kv(e){e=e|0;Tv(e+24|0);return}function Tv(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function xv(e){e=e|0;var t=0;t=Za()|0;nl(e,2,22,t,Av()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Av(){return 1344}function Ov(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0;n=h;h=h+16|0;r=n+8|0;i=n;u=Pv(e)|0;e=o[u+4>>2]|0;o[i>>2]=o[u>>2];o[i+4>>2]=e;o[r>>2]=o[i>>2];o[r+4>>2]=o[i+4>>2];Iv(t,r);h=n;return}function Pv(e){e=e|0;return(o[(mv()|0)+24>>2]|0)+(e*12|0)|0}function Iv(e,t){e=e|0;t=t|0;var n=0;n=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)n=o[(o[e>>2]|0)+n>>2]|0;hA[n&127](e);return}function Nv(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=o[e>>2]|0;i=Mv()|0;e=Rv(n)|0;La(u,t,i,e,Fv(n,r)|0,r);return}function Mv(){var e=0,t=0;if(!(r[7800]|0)){Hv(9936);Fe(44,9936,g|0)|0;t=7800;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9936)|0)){e=9936;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Hv(9936)}return 9936}function Rv(e){e=e|0;return e|0}function Fv(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;l=h;h=h+16|0;i=l;u=l+4|0;o[i>>2]=e;s=Mv()|0;a=s+24|0;t=za(t,4)|0;o[u>>2]=t;n=s+28|0;r=o[n>>2]|0;if(r>>>0<(o[s+32>>2]|0)>>>0){Lv(r,e,t);t=(o[n>>2]|0)+8|0;o[n>>2]=t}else{Bv(a,i,u);t=o[n>>2]|0}h=l;return(t-(o[a>>2]|0)>>3)+-1|0}function Lv(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;return}function Bv(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;i=l;u=e+4|0;a=((o[u>>2]|0)-(o[e>>2]|0)>>3)+1|0;r=jv(e)|0;if(r>>>0>>0)UT(e);else{s=o[e>>2]|0;f=(o[e+8>>2]|0)-s|0;c=f>>2;Uv(i,f>>3>>>0>>1>>>0?c>>>0>>0?a:c:r,(o[u>>2]|0)-s>>3,e+8|0);a=i+8|0;Lv(o[a>>2]|0,o[t>>2]|0,o[n>>2]|0);o[a>>2]=(o[a>>2]|0)+8;zv(e,i);Wv(i);h=l;return}}function jv(e){e=e|0;return 536870911}function Uv(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>536870911)Ye();else{i=$T(t<<3)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<3)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<3);return}function zv(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>3)<<3)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Wv(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-8-t|0)>>>3)<<3);e=o[e>>2]|0;if(e|0)KT(e);return}function Hv(e){e=e|0;Gv(e);return}function Vv(e){e=e|0;qv(e+24|0);return}function qv(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function Gv(e){e=e|0;var t=0;t=Za()|0;nl(e,1,23,t,vf()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function $v(e,t){e=e|0;t=t|0;Kv(o[(Yv(e)|0)>>2]|0,t);return}function Yv(e){e=e|0;return(o[(Mv()|0)+24>>2]|0)+(e<<3)|0}function Kv(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;_f(r,t);t=bf(r,t)|0;hA[e&127](t);h=n;return}function Xv(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=o[e>>2]|0;i=Qv()|0;e=Jv(n)|0;La(u,t,i,e,Zv(n,r)|0,r);return}function Qv(){var e=0,t=0;if(!(r[7808]|0)){um(9972);Fe(45,9972,g|0)|0;t=7808;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(9972)|0)){e=9972;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));um(9972)}return 9972}function Jv(e){e=e|0;return e|0}function Zv(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;l=h;h=h+16|0;i=l;u=l+4|0;o[i>>2]=e;s=Qv()|0;a=s+24|0;t=za(t,4)|0;o[u>>2]=t;n=s+28|0;r=o[n>>2]|0;if(r>>>0<(o[s+32>>2]|0)>>>0){em(r,e,t);t=(o[n>>2]|0)+8|0;o[n>>2]=t}else{tm(a,i,u);t=o[n>>2]|0}h=l;return(t-(o[a>>2]|0)>>3)+-1|0}function em(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;return}function tm(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;i=l;u=e+4|0;a=((o[u>>2]|0)-(o[e>>2]|0)>>3)+1|0;r=nm(e)|0;if(r>>>0>>0)UT(e);else{s=o[e>>2]|0;f=(o[e+8>>2]|0)-s|0;c=f>>2;rm(i,f>>3>>>0>>1>>>0?c>>>0>>0?a:c:r,(o[u>>2]|0)-s>>3,e+8|0);a=i+8|0;em(o[a>>2]|0,o[t>>2]|0,o[n>>2]|0);o[a>>2]=(o[a>>2]|0)+8;im(e,i);om(i);h=l;return}}function nm(e){e=e|0;return 536870911}function rm(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>536870911)Ye();else{i=$T(t<<3)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<3)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<3);return}function im(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>3)<<3)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function om(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-8-t|0)>>>3)<<3);e=o[e>>2]|0;if(e|0)KT(e);return}function um(e){e=e|0;sm(e);return}function am(e){e=e|0;lm(e+24|0);return}function lm(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function sm(e){e=e|0;var t=0;t=Za()|0;nl(e,1,9,t,cm()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function cm(){return 1348}function fm(e,t){e=e|0;t=t|0;return pm(o[(dm(e)|0)>>2]|0,t)|0}function dm(e){e=e|0;return(o[(Qv()|0)+24>>2]|0)+(e<<3)|0}function pm(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;hm(r,t);t=vm(r,t)|0;t=dc(mA[e&31](t)|0)|0;h=n;return t|0}function hm(e,t){e=e|0;t=t|0;return}function vm(e,t){e=e|0;t=t|0;return mm(t)|0}function mm(e){e=e|0;return e|0}function gm(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=o[e>>2]|0;i=ym()|0;e=_m(n)|0;La(u,t,i,e,bm(n,r)|0,r);return}function ym(){var e=0,t=0;if(!(r[7816]|0)){Tm(10008);Fe(46,10008,g|0)|0;t=7816;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(10008)|0)){e=10008;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Tm(10008)}return 10008}function _m(e){e=e|0;return e|0}function bm(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;l=h;h=h+16|0;i=l;u=l+4|0;o[i>>2]=e;s=ym()|0;a=s+24|0;t=za(t,4)|0;o[u>>2]=t;n=s+28|0;r=o[n>>2]|0;if(r>>>0<(o[s+32>>2]|0)>>>0){wm(r,e,t);t=(o[n>>2]|0)+8|0;o[n>>2]=t}else{Em(a,i,u);t=o[n>>2]|0}h=l;return(t-(o[a>>2]|0)>>3)+-1|0}function wm(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;return}function Em(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;i=l;u=e+4|0;a=((o[u>>2]|0)-(o[e>>2]|0)>>3)+1|0;r=Dm(e)|0;if(r>>>0>>0)UT(e);else{s=o[e>>2]|0;f=(o[e+8>>2]|0)-s|0;c=f>>2;Sm(i,f>>3>>>0>>1>>>0?c>>>0>>0?a:c:r,(o[u>>2]|0)-s>>3,e+8|0);a=i+8|0;wm(o[a>>2]|0,o[t>>2]|0,o[n>>2]|0);o[a>>2]=(o[a>>2]|0)+8;Cm(e,i);km(i);h=l;return}}function Dm(e){e=e|0;return 536870911}function Sm(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>536870911)Ye();else{i=$T(t<<3)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<3)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<3);return}function Cm(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>3)<<3)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function km(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-8-t|0)>>>3)<<3);e=o[e>>2]|0;if(e|0)KT(e);return}function Tm(e){e=e|0;Om(e);return}function xm(e){e=e|0;Am(e+24|0);return}function Am(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function Om(e){e=e|0;var t=0;t=Za()|0;nl(e,1,15,t,Ac()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Pm(e){e=e|0;return Nm(o[(Im(e)|0)>>2]|0)|0}function Im(e){e=e|0;return(o[(ym()|0)+24>>2]|0)+(e<<3)|0}function Nm(e){e=e|0;return dc(TA[e&7]()|0)|0}function Mm(){var e=0;if(!(r[7832]|0)){Vm(10052);Fe(25,10052,g|0)|0;e=7832;o[e>>2]=1;o[e+4>>2]=0}return 10052}function Rm(e,t){e=e|0;t=t|0;o[e>>2]=Fm()|0;o[e+4>>2]=Lm()|0;o[e+12>>2]=t;o[e+8>>2]=Bm()|0;o[e+32>>2]=2;return}function Fm(){return 11709}function Lm(){return 1188}function Bm(){return Wm()|0}function jm(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;if((Um(r,896)|0)==512){if(n|0){zm(n);KT(n)}}else if(t|0){Qi(t);KT(t)}return}function Um(e,t){e=e|0;t=t|0;return t&e|0}function zm(e){e=e|0;e=o[e+4>>2]|0;if(e|0)qT(e);return}function Wm(){var e=0;if(!(r[7824]|0)){o[2511]=Hm()|0;o[2512]=0;e=7824;o[e>>2]=1;o[e+4>>2]=0}return 10044}function Hm(){return 0}function Vm(e){e=e|0;Al(e);return}function qm(e){e=e|0;var t=0,n=0,r=0,i=0,u=0;t=h;h=h+32|0;n=t+24|0;u=t+16|0;i=t+8|0;r=t;Gm(e,4827);$m(e,4834,3)|0;Ym(e,3682,47)|0;o[u>>2]=9;o[u+4>>2]=0;o[n>>2]=o[u>>2];o[n+4>>2]=o[u+4>>2];Km(e,4841,n)|0;o[i>>2]=1;o[i+4>>2]=0;o[n>>2]=o[i>>2];o[n+4>>2]=o[i+4>>2];Xm(e,4871,n)|0;o[r>>2]=10;o[r+4>>2]=0;o[n>>2]=o[r>>2];o[n+4>>2]=o[r+4>>2];Qm(e,4891,n)|0;h=t;return}function Gm(e,t){e=e|0;t=t|0;var n=0;n=Vy()|0;o[e>>2]=n;qy(n,t);cw(o[e>>2]|0);return}function $m(e,t,n){e=e|0;t=t|0;n=n|0;Cy(e,Ia(t)|0,n,0);return e|0}function Ym(e,t,n){e=e|0;t=t|0;n=n|0;ay(e,Ia(t)|0,n,0);return e|0}function Km(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];jg(e,t,i);h=r;return e|0}function Xm(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];gg(e,t,i);h=r;return e|0}function Qm(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=o[n+4>>2]|0;o[u>>2]=o[n>>2];o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Jm(e,t,i);h=r;return e|0}function Jm(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Zm(e,n,i,1);h=r;return}function Zm(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=eg()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=tg(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,ng(u,r)|0,r);h=i;return}function eg(){var e=0,t=0;if(!(r[7840]|0)){sg(10100);Fe(48,10100,g|0)|0;t=7840;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(10100)|0)){e=10100;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));sg(10100)}return 10100}function tg(e){e=e|0;return 0}function ng(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=eg()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];rg(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{ig(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function rg(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function ig(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=og(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;ug(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];rg(u,r,n);o[s>>2]=(o[s>>2]|0)+12;ag(e,l);lg(l);h=c;return}}function og(e){e=e|0;return 357913941}function ug(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function ag(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function lg(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function sg(e){e=e|0;dg(e);return}function cg(e){e=e|0;fg(e+24|0);return}function fg(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function dg(e){e=e|0;var t=0;t=Za()|0;nl(e,2,6,t,pg()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function pg(){return 1364}function hg(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=vg(e)|0;e=o[a+4>>2]|0;o[u>>2]=o[a>>2];o[u+4>>2]=e;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];n=mg(t,i,n)|0;h=r;return n|0}function vg(e){e=e|0;return(o[(eg()|0)+24>>2]|0)+(e*12|0)|0}function mg(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0;u=h;h=h+16|0;i=u;r=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)r=o[(o[e>>2]|0)+r>>2]|0;Ql(i,n);i=Jl(i,n)|0;i=bs(DA[r&15](e,i)|0)|0;h=u;return i|0}function gg(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];yg(e,n,i,0);h=r;return}function yg(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=_g()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=bg(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,wg(u,r)|0,r);h=i;return}function _g(){var e=0,t=0;if(!(r[7848]|0)){xg(10136);Fe(49,10136,g|0)|0;t=7848;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(10136)|0)){e=10136;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));xg(10136)}return 10136}function bg(e){e=e|0;return 0}function wg(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=_g()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Eg(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{Dg(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Eg(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function Dg(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Sg(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;Cg(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Eg(u,r,n);o[s>>2]=(o[s>>2]|0)+12;kg(e,l);Tg(l);h=c;return}}function Sg(e){e=e|0;return 357913941}function Cg(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function kg(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Tg(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function xg(e){e=e|0;Pg(e);return}function Ag(e){e=e|0;Og(e+24|0);return}function Og(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function Pg(e){e=e|0;var t=0;t=Za()|0;nl(e,2,9,t,Ig()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Ig(){return 1372}function Ng(e,t,n){e=e|0;t=t|0;n=+n;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;i=r+8|0;u=r;a=Mg(e)|0;e=o[a+4>>2]|0;o[u>>2]=o[a>>2];o[u+4>>2]=e;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Rg(t,i,n);h=r;return}function Mg(e){e=e|0;return(o[(_g()|0)+24>>2]|0)+(e*12|0)|0}function Rg(e,t,n){e=e|0;t=t|0;n=+n;var r=0,i=0,u=0,a=ft;u=h;h=h+16|0;i=u;r=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)r=o[(o[e>>2]|0)+r>>2]|0;Fg(i,n);a=Y(Lg(i,n));fA[r&1](e,a);h=u;return}function Fg(e,t){e=e|0;t=+t;return}function Lg(e,t){e=e|0;t=+t;return Y(Bg(t))}function Bg(e){e=+e;return Y(e)}function jg(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;i=r+8|0;u=r;l=o[n>>2]|0;a=o[n+4>>2]|0;n=Ia(t)|0;o[u>>2]=l;o[u+4>>2]=a;o[i>>2]=o[u>>2];o[i+4>>2]=o[u+4>>2];Ug(e,n,i,0);h=r;return}function Ug(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0;i=h;h=h+32|0;u=i+16|0;f=i+8|0;l=i;c=o[n>>2]|0;s=o[n+4>>2]|0;a=o[e>>2]|0;e=zg()|0;o[f>>2]=c;o[f+4>>2]=s;o[u>>2]=o[f>>2];o[u+4>>2]=o[f+4>>2];n=Wg(u)|0;o[l>>2]=c;o[l+4>>2]=s;o[u>>2]=o[l>>2];o[u+4>>2]=o[l+4>>2];La(a,t,e,n,Hg(u,r)|0,r);h=i;return}function zg(){var e=0,t=0;if(!(r[7856]|0)){Xg(10172);Fe(50,10172,g|0)|0;t=7856;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(10172)|0)){e=10172;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Xg(10172)}return 10172}function Wg(e){e=e|0;return 0}function Hg(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0;f=h;h=h+32|0;i=f+24|0;a=f+16|0;l=f;s=f+8|0;u=o[e>>2]|0;r=o[e+4>>2]|0;o[l>>2]=u;o[l+4>>2]=r;d=zg()|0;c=d+24|0;e=za(t,4)|0;o[s>>2]=e;t=d+28|0;n=o[t>>2]|0;if(n>>>0<(o[d+32>>2]|0)>>>0){o[a>>2]=u;o[a+4>>2]=r;o[i>>2]=o[a>>2];o[i+4>>2]=o[a+4>>2];Vg(n,i,e);e=(o[t>>2]|0)+12|0;o[t>>2]=e}else{qg(c,l,s);e=o[t>>2]|0}h=f;return((e-(o[c>>2]|0)|0)/12|0)+-1|0}function Vg(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=o[t+4>>2]|0;o[e>>2]=o[t>>2];o[e+4>>2]=r;o[e+8>>2]=n;return}function qg(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0;c=h;h=h+48|0;r=c+32|0;a=c+24|0;l=c;s=e+4|0;i=(((o[s>>2]|0)-(o[e>>2]|0)|0)/12|0)+1|0;u=Gg(e)|0;if(u>>>0>>0)UT(e);else{f=o[e>>2]|0;p=((o[e+8>>2]|0)-f|0)/12|0;d=p<<1;$g(l,p>>>0>>1>>>0?d>>>0>>0?i:d:u,((o[s>>2]|0)-f|0)/12|0,e+8|0);s=l+8|0;u=o[s>>2]|0;i=o[t+4>>2]|0;n=o[n>>2]|0;o[a>>2]=o[t>>2];o[a+4>>2]=i;o[r>>2]=o[a>>2];o[r+4>>2]=o[a+4>>2];Vg(u,r,n);o[s>>2]=(o[s>>2]|0)+12;Yg(e,l);Kg(l);h=c;return}}function Gg(e){e=e|0;return 357913941}function $g(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>357913941)Ye();else{i=$T(t*12|0)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n*12|0)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t*12|0);return}function Yg(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(((i|0)/-12|0)*12|0)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function Kg(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~(((r+-12-t|0)>>>0)/12|0)*12|0);e=o[e>>2]|0;if(e|0)KT(e);return}function Xg(e){e=e|0;Zg(e);return}function Qg(e){e=e|0;Jg(e+24|0);return}function Jg(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~(((t+-12-r|0)>>>0)/12|0)*12|0);KT(n)}return}function Zg(e){e=e|0;var t=0;t=Za()|0;nl(e,2,3,t,ey()|0,2);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function ey(){return 1380}function ty(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0;i=h;h=h+16|0;u=i+8|0;a=i;l=ny(e)|0;e=o[l+4>>2]|0;o[a>>2]=o[l>>2];o[a+4>>2]=e;o[u>>2]=o[a>>2];o[u+4>>2]=o[a+4>>2];ry(t,u,n,r);h=i;return}function ny(e){e=e|0;return(o[(zg()|0)+24>>2]|0)+(e*12|0)|0}function ry(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0;l=h;h=h+16|0;u=l+1|0;a=l;i=o[t>>2]|0;t=o[t+4>>2]|0;e=e+(t>>1)|0;if(t&1)i=o[(o[e>>2]|0)+i>>2]|0;Ql(u,n);u=Jl(u,n)|0;iy(a,r);a=oy(a,r)|0;PA[i&15](e,u,a);h=l;return}function iy(e,t){e=e|0;t=t|0;return}function oy(e,t){e=e|0;t=t|0;return uy(t)|0}function uy(e){e=e|0;return(e|0)!=0|0}function ay(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=o[e>>2]|0;i=ly()|0;e=sy(n)|0;La(u,t,i,e,cy(n,r)|0,r);return}function ly(){var e=0,t=0;if(!(r[7864]|0)){gy(10208);Fe(51,10208,g|0)|0;t=7864;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(10208)|0)){e=10208;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));gy(10208)}return 10208}function sy(e){e=e|0;return e|0}function cy(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;l=h;h=h+16|0;i=l;u=l+4|0;o[i>>2]=e;s=ly()|0;a=s+24|0;t=za(t,4)|0;o[u>>2]=t;n=s+28|0;r=o[n>>2]|0;if(r>>>0<(o[s+32>>2]|0)>>>0){fy(r,e,t);t=(o[n>>2]|0)+8|0;o[n>>2]=t}else{dy(a,i,u);t=o[n>>2]|0}h=l;return(t-(o[a>>2]|0)>>3)+-1|0}function fy(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;return}function dy(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;i=l;u=e+4|0;a=((o[u>>2]|0)-(o[e>>2]|0)>>3)+1|0;r=py(e)|0;if(r>>>0>>0)UT(e);else{s=o[e>>2]|0;f=(o[e+8>>2]|0)-s|0;c=f>>2;hy(i,f>>3>>>0>>1>>>0?c>>>0>>0?a:c:r,(o[u>>2]|0)-s>>3,e+8|0);a=i+8|0;fy(o[a>>2]|0,o[t>>2]|0,o[n>>2]|0);o[a>>2]=(o[a>>2]|0)+8;vy(e,i);my(i);h=l;return}}function py(e){e=e|0;return 536870911}function hy(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>536870911)Ye();else{i=$T(t<<3)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<3)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<3);return}function vy(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>3)<<3)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function my(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-8-t|0)>>>3)<<3);e=o[e>>2]|0;if(e|0)KT(e);return}function gy(e){e=e|0;by(e);return}function yy(e){e=e|0;_y(e+24|0);return}function _y(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function by(e){e=e|0;var t=0;t=Za()|0;nl(e,1,24,t,wy()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function wy(){return 1392}function Ey(e,t){e=e|0;t=t|0;Sy(o[(Dy(e)|0)>>2]|0,t);return}function Dy(e){e=e|0;return(o[(ly()|0)+24>>2]|0)+(e<<3)|0}function Sy(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;hm(r,t);t=vm(r,t)|0;hA[e&127](t);h=n;return}function Cy(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=o[e>>2]|0;i=ky()|0;e=Ty(n)|0;La(u,t,i,e,xy(n,r)|0,r);return}function ky(){var e=0,t=0;if(!(r[7872]|0)){Ry(10244);Fe(52,10244,g|0)|0;t=7872;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(10244)|0)){e=10244;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));Ry(10244)}return 10244}function Ty(e){e=e|0;return e|0}function xy(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;l=h;h=h+16|0;i=l;u=l+4|0;o[i>>2]=e;s=ky()|0;a=s+24|0;t=za(t,4)|0;o[u>>2]=t;n=s+28|0;r=o[n>>2]|0;if(r>>>0<(o[s+32>>2]|0)>>>0){Ay(r,e,t);t=(o[n>>2]|0)+8|0;o[n>>2]=t}else{Oy(a,i,u);t=o[n>>2]|0}h=l;return(t-(o[a>>2]|0)>>3)+-1|0}function Ay(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;return}function Oy(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;i=l;u=e+4|0;a=((o[u>>2]|0)-(o[e>>2]|0)>>3)+1|0;r=Py(e)|0;if(r>>>0>>0)UT(e);else{s=o[e>>2]|0;f=(o[e+8>>2]|0)-s|0;c=f>>2;Iy(i,f>>3>>>0>>1>>>0?c>>>0>>0?a:c:r,(o[u>>2]|0)-s>>3,e+8|0);a=i+8|0;Ay(o[a>>2]|0,o[t>>2]|0,o[n>>2]|0);o[a>>2]=(o[a>>2]|0)+8;Ny(e,i);My(i);h=l;return}}function Py(e){e=e|0;return 536870911}function Iy(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>536870911)Ye();else{i=$T(t<<3)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<3)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<3);return}function Ny(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>3)<<3)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function My(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-8-t|0)>>>3)<<3);e=o[e>>2]|0;if(e|0)KT(e);return}function Ry(e){e=e|0;By(e);return}function Fy(e){e=e|0;Ly(e+24|0);return}function Ly(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function By(e){e=e|0;var t=0;t=Za()|0;nl(e,1,16,t,jy()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function jy(){return 1400}function Uy(e){e=e|0;return Wy(o[(zy(e)|0)>>2]|0)|0}function zy(e){e=e|0;return(o[(ky()|0)+24>>2]|0)+(e<<3)|0}function Wy(e){e=e|0;return Hy(TA[e&7]()|0)|0}function Hy(e){e=e|0;return e|0}function Vy(){var e=0;if(!(r[7880]|0)){Qy(10280);Fe(25,10280,g|0)|0;e=7880;o[e>>2]=1;o[e+4>>2]=0}return 10280}function qy(e,t){e=e|0;t=t|0;o[e>>2]=Gy()|0;o[e+4>>2]=$y()|0;o[e+12>>2]=t;o[e+8>>2]=Yy()|0;o[e+32>>2]=4;return}function Gy(){return 11711}function $y(){return 1356}function Yy(){return Wm()|0}function Ky(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;if((Um(r,896)|0)==512){if(n|0){Xy(n);KT(n)}}else if(t|0){Hi(t);KT(t)}return}function Xy(e){e=e|0;e=o[e+4>>2]|0;if(e|0)qT(e);return}function Qy(e){e=e|0;Al(e);return}function Jy(e){e=e|0;Zy(e,4920);e_(e)|0;t_(e)|0;return}function Zy(e,t){e=e|0;t=t|0;var n=0;n=qd()|0;o[e>>2]=n;T_(n,t);cw(o[e>>2]|0);return}function e_(e){e=e|0;var t=0;t=o[e>>2]|0;r_(t,v_()|0);return e|0}function t_(e){e=e|0;var t=0;t=o[e>>2]|0;r_(t,n_()|0);return e|0}function n_(){var e=0;if(!(r[7888]|0)){i_(10328);Fe(53,10328,g|0)|0;e=7888;o[e>>2]=1;o[e+4>>2]=0}if(!(Xa(10328)|0))i_(10328);return 10328}function r_(e,t){e=e|0;t=t|0;La(e,0,t,0,0,0);return}function i_(e){e=e|0;a_(e);s_(e,10);return}function o_(e){e=e|0;u_(e+24|0);return}function u_(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function a_(e){e=e|0;var t=0;t=Za()|0;nl(e,5,1,t,d_()|0,2);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function l_(e,t,n){e=e|0;t=t|0;n=+n;c_(e,t,n);return}function s_(e,t){e=e|0;t=t|0;o[e+20>>2]=t;return}function c_(e,t,n){e=e|0;t=t|0;n=+n;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+16|0;u=r+8|0;l=r+13|0;i=r;a=r+12|0;Ql(l,t);o[u>>2]=Jl(l,t)|0;Kl(a,n);c[i>>3]=+Xl(a,n);f_(e,u,i);h=r;return}function f_(e,t,n){e=e|0;t=t|0;n=n|0;Vo(e+8|0,o[t>>2]|0,+c[n>>3]);r[e+24>>0]=1;return}function d_(){return 1404}function p_(e,t){e=e|0;t=+t;return h_(e,t)|0}function h_(e,t){e=e|0;t=+t;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;r=h;h=h+16|0;u=r+4|0;a=r+8|0;l=r;i=UD(8)|0;n=i;s=$T(16)|0;Ql(u,e);e=Jl(u,e)|0;Kl(a,t);Vo(s,e,+Xl(a,t));a=n+4|0;o[a>>2]=s;e=$T(8)|0;a=o[a>>2]|0;o[l>>2]=0;o[u>>2]=o[l>>2];Yd(e,a,u);o[i>>2]=e;h=r;return n|0}function v_(){var e=0;if(!(r[7896]|0)){m_(10364);Fe(54,10364,g|0)|0;e=7896;o[e>>2]=1;o[e+4>>2]=0}if(!(Xa(10364)|0))m_(10364);return 10364}function m_(e){e=e|0;__(e);s_(e,55);return}function g_(e){e=e|0;y_(e+24|0);return}function y_(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function __(e){e=e|0;var t=0;t=Za()|0;nl(e,5,4,t,S_()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function b_(e){e=e|0;w_(e);return}function w_(e){e=e|0;E_(e);return}function E_(e){e=e|0;D_(e+8|0);r[e+24>>0]=1;return}function D_(e){e=e|0;o[e>>2]=0;c[e+8>>3]=0.0;return}function S_(){return 1424}function C_(){return k_()|0}function k_(){var e=0,t=0,n=0,r=0,i=0,u=0,a=0;t=h;h=h+16|0;i=t+4|0;a=t;n=UD(8)|0;e=n;r=$T(16)|0;D_(r);u=e+4|0;o[u>>2]=r;r=$T(8)|0;u=o[u>>2]|0;o[a>>2]=0;o[i>>2]=o[a>>2];Yd(r,u,i);o[n>>2]=r;h=t;return e|0}function T_(e,t){e=e|0;t=t|0;o[e>>2]=x_()|0;o[e+4>>2]=A_()|0;o[e+12>>2]=t;o[e+8>>2]=O_()|0;o[e+32>>2]=5;return}function x_(){return 11710}function A_(){return 1416}function O_(){return N_()|0}function P_(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;if((Um(r,896)|0)==512){if(n|0){I_(n);KT(n)}}else if(t|0)KT(t);return}function I_(e){e=e|0;e=o[e+4>>2]|0;if(e|0)qT(e);return}function N_(){var e=0;if(!(r[7904]|0)){o[2600]=M_()|0;o[2601]=0;e=7904;o[e>>2]=1;o[e+4>>2]=0}return 10400}function M_(){return o[357]|0}function R_(e){e=e|0;F_(e,4926);L_(e)|0;return}function F_(e,t){e=e|0;t=t|0;var n=0;n=ul()|0;o[e>>2]=n;K_(n,t);cw(o[e>>2]|0);return}function L_(e){e=e|0;var t=0;t=o[e>>2]|0;r_(t,B_()|0);return e|0}function B_(){var e=0;if(!(r[7912]|0)){j_(10412);Fe(56,10412,g|0)|0;e=7912;o[e>>2]=1;o[e+4>>2]=0}if(!(Xa(10412)|0))j_(10412);return 10412}function j_(e){e=e|0;W_(e);s_(e,57);return}function U_(e){e=e|0;z_(e+24|0);return}function z_(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function W_(e){e=e|0;var t=0;t=Za()|0;nl(e,5,5,t,G_()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function H_(e){e=e|0;V_(e);return}function V_(e){e=e|0;q_(e);return}function q_(e){e=e|0;var t=0,n=0;t=e+8|0;n=t+48|0;do{o[t>>2]=0;t=t+4|0}while((t|0)<(n|0));r[e+56>>0]=1;return}function G_(){return 1432}function $_(){return Y_()|0}function Y_(){var e=0,t=0,n=0,r=0,i=0,u=0,a=0,l=0;a=h;h=h+16|0;e=a+4|0;t=a;n=UD(8)|0;r=n;i=$T(48)|0;u=i;l=u+48|0;do{o[u>>2]=0;u=u+4|0}while((u|0)<(l|0));u=r+4|0;o[u>>2]=i;l=$T(8)|0;u=o[u>>2]|0;o[t>>2]=0;o[e>>2]=o[t>>2];pl(l,u,e);o[n>>2]=l;h=a;return r|0}function K_(e,t){e=e|0;t=t|0;o[e>>2]=X_()|0;o[e+4>>2]=Q_()|0;o[e+12>>2]=t;o[e+8>>2]=J_()|0;o[e+32>>2]=6;return}function X_(){return 11704}function Q_(){return 1436}function J_(){return N_()|0}function Z_(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;if((Um(r,896)|0)==512){if(n|0){eb(n);KT(n)}}else if(t|0)KT(t);return}function eb(e){e=e|0;e=o[e+4>>2]|0;if(e|0)qT(e);return}function tb(e){e=e|0;nb(e,4933);rb(e)|0;ib(e)|0;return}function nb(e,t){e=e|0;t=t|0;var n=0;n=Nb()|0;o[e>>2]=n;Mb(n,t);cw(o[e>>2]|0);return}function rb(e){e=e|0;var t=0;t=o[e>>2]|0;r_(t,wb()|0);return e|0}function ib(e){e=e|0;var t=0;t=o[e>>2]|0;r_(t,ob()|0);return e|0}function ob(){var e=0;if(!(r[7920]|0)){ub(10452);Fe(58,10452,g|0)|0;e=7920;o[e>>2]=1;o[e+4>>2]=0}if(!(Xa(10452)|0))ub(10452);return 10452}function ub(e){e=e|0;sb(e);s_(e,1);return}function ab(e){e=e|0;lb(e+24|0);return}function lb(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function sb(e){e=e|0;var t=0;t=Za()|0;nl(e,5,1,t,hb()|0,2);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function cb(e,t,n){e=e|0;t=+t;n=+n;fb(e,t,n);return}function fb(e,t,n){e=e|0;t=+t;n=+n;var r=0,i=0,o=0,u=0,a=0;r=h;h=h+32|0;o=r+8|0;a=r+17|0;i=r;u=r+16|0;Kl(a,t);c[o>>3]=+Xl(a,t);Kl(u,n);c[i>>3]=+Xl(u,n);db(e,o,i);h=r;return}function db(e,t,n){e=e|0;t=t|0;n=n|0;pb(e+8|0,+c[t>>3],+c[n>>3]);r[e+24>>0]=1;return}function pb(e,t,n){e=e|0;t=+t;n=+n;c[e>>3]=t;c[e+8>>3]=n;return}function hb(){return 1472}function vb(e,t){e=+e;t=+t;return mb(e,t)|0}function mb(e,t){e=+e;t=+t;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;r=h;h=h+16|0;a=r+4|0;l=r+8|0;s=r;i=UD(8)|0;n=i;u=$T(16)|0;Kl(a,e);e=+Xl(a,e);Kl(l,t);pb(u,e,+Xl(l,t));l=n+4|0;o[l>>2]=u;u=$T(8)|0;l=o[l>>2]|0;o[s>>2]=0;o[a>>2]=o[s>>2];gb(u,l,a);o[i>>2]=u;h=r;return n|0}function gb(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;n=$T(16)|0;o[n+4>>2]=0;o[n+8>>2]=0;o[n>>2]=1452;o[n+12>>2]=t;o[e+4>>2]=n;return}function yb(e){e=e|0;zT(e);KT(e);return}function _b(e){e=e|0;e=o[e+12>>2]|0;if(e|0)KT(e);return}function bb(e){e=e|0;KT(e);return}function wb(){var e=0;if(!(r[7928]|0)){Eb(10488);Fe(59,10488,g|0)|0;e=7928;o[e>>2]=1;o[e+4>>2]=0}if(!(Xa(10488)|0))Eb(10488);return 10488}function Eb(e){e=e|0;Cb(e);s_(e,60);return}function Db(e){e=e|0;Sb(e+24|0);return}function Sb(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function Cb(e){e=e|0;var t=0;t=Za()|0;nl(e,5,6,t,Ob()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function kb(e){e=e|0;Tb(e);return}function Tb(e){e=e|0;xb(e);return}function xb(e){e=e|0;Ab(e+8|0);r[e+24>>0]=1;return}function Ab(e){e=e|0;o[e>>2]=0;o[e+4>>2]=0;o[e+8>>2]=0;o[e+12>>2]=0;return}function Ob(){return 1492}function Pb(){return Ib()|0}function Ib(){var e=0,t=0,n=0,r=0,i=0,u=0,a=0;t=h;h=h+16|0;i=t+4|0;a=t;n=UD(8)|0;e=n;r=$T(16)|0;Ab(r);u=e+4|0;o[u>>2]=r;r=$T(8)|0;u=o[u>>2]|0;o[a>>2]=0;o[i>>2]=o[a>>2];gb(r,u,i);o[n>>2]=r;h=t;return e|0}function Nb(){var e=0;if(!(r[7936]|0)){Ub(10524);Fe(25,10524,g|0)|0;e=7936;o[e>>2]=1;o[e+4>>2]=0}return 10524}function Mb(e,t){e=e|0;t=t|0;o[e>>2]=Rb()|0;o[e+4>>2]=Fb()|0;o[e+12>>2]=t;o[e+8>>2]=Lb()|0;o[e+32>>2]=7;return}function Rb(){return 11700}function Fb(){return 1484}function Lb(){return N_()|0}function Bb(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;if((Um(r,896)|0)==512){if(n|0){jb(n);KT(n)}}else if(t|0)KT(t);return}function jb(e){e=e|0;e=o[e+4>>2]|0;if(e|0)qT(e);return}function Ub(e){e=e|0;Al(e);return}function zb(e,t,n){e=e|0;t=t|0;n=n|0;e=Ia(t)|0;t=Wb(n)|0;n=Hb(n,0)|0;xw(e,t,n,Vb()|0,0);return}function Wb(e){e=e|0;return e|0}function Hb(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;l=h;h=h+16|0;i=l;u=l+4|0;o[i>>2]=e;s=Vb()|0;a=s+24|0;t=za(t,4)|0;o[u>>2]=t;n=s+28|0;r=o[n>>2]|0;if(r>>>0<(o[s+32>>2]|0)>>>0){Jb(r,e,t);t=(o[n>>2]|0)+8|0;o[n>>2]=t}else{Zb(a,i,u);t=o[n>>2]|0}h=l;return(t-(o[a>>2]|0)>>3)+-1|0}function Vb(){var e=0,t=0;if(!(r[7944]|0)){qb(10568);Fe(61,10568,g|0)|0;t=7944;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(10568)|0)){e=10568;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));qb(10568)}return 10568}function qb(e){e=e|0;Yb(e);return}function Gb(e){e=e|0;$b(e+24|0);return}function $b(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function Yb(e){e=e|0;var t=0;t=Za()|0;nl(e,1,17,t,Yc()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function Kb(e){e=e|0;return Qb(o[(Xb(e)|0)>>2]|0)|0}function Xb(e){e=e|0;return(o[(Vb()|0)+24>>2]|0)+(e<<3)|0}function Qb(e){e=e|0;return Jc(TA[e&7]()|0)|0}function Jb(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;return}function Zb(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;i=l;u=e+4|0;a=((o[u>>2]|0)-(o[e>>2]|0)>>3)+1|0;r=ew(e)|0;if(r>>>0>>0)UT(e);else{s=o[e>>2]|0;f=(o[e+8>>2]|0)-s|0;c=f>>2;tw(i,f>>3>>>0>>1>>>0?c>>>0>>0?a:c:r,(o[u>>2]|0)-s>>3,e+8|0);a=i+8|0;Jb(o[a>>2]|0,o[t>>2]|0,o[n>>2]|0);o[a>>2]=(o[a>>2]|0)+8;nw(e,i);rw(i);h=l;return}}function ew(e){e=e|0;return 536870911}function tw(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>536870911)Ye();else{i=$T(t<<3)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<3)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<3);return}function nw(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>3)<<3)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function rw(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-8-t|0)>>>3)<<3);e=o[e>>2]|0;if(e|0)KT(e);return}function iw(){ow();return}function ow(){uw(10604);return}function uw(e){e=e|0;aw(e,4955);return}function aw(e,t){e=e|0;t=t|0;var n=0;n=lw()|0;o[e>>2]=n;sw(n,t);cw(o[e>>2]|0);return}function lw(){var e=0;if(!(r[7952]|0)){bw(10612);Fe(25,10612,g|0)|0;e=7952;o[e>>2]=1;o[e+4>>2]=0}return 10612}function sw(e,t){e=e|0;t=t|0;o[e>>2]=vw()|0;o[e+4>>2]=mw()|0;o[e+12>>2]=t;o[e+8>>2]=gw()|0;o[e+32>>2]=8;return}function cw(e){e=e|0;var t=0,n=0;t=h;h=h+16|0;n=t;fw()|0;o[n>>2]=e;dw(10608,n);h=t;return}function fw(){if(!(r[11714]|0)){o[2652]=0;Fe(62,10608,g|0)|0;r[11714]=1}return 10608}function dw(e,t){e=e|0;t=t|0;var n=0;n=$T(8)|0;o[n+4>>2]=o[t>>2];o[n>>2]=o[e>>2];o[e>>2]=n;return}function pw(e){e=e|0;hw(e);return}function hw(e){e=e|0;var t=0,n=0;t=o[e>>2]|0;if(t|0)do{n=t;t=o[t>>2]|0;KT(n)}while((t|0)!=0);o[e>>2]=0;return}function vw(){return 11715}function mw(){return 1496}function gw(){return Wm()|0}function yw(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;if((Um(r,896)|0)==512){if(n|0){_w(n);KT(n)}}else if(t|0)KT(t);return}function _w(e){e=e|0;e=o[e+4>>2]|0;if(e|0)qT(e);return}function bw(e){e=e|0;Al(e);return}function ww(e,t){e=e|0;t=t|0;var n=0,r=0;fw()|0;n=o[2652]|0;e:do{if(n|0){while(1){r=o[n+4>>2]|0;if(r|0?(rT(Ew(r)|0,e)|0)==0:0)break;n=o[n>>2]|0;if(!n)break e}Dw(r,t)}}while(0);return}function Ew(e){e=e|0;return o[e+12>>2]|0}function Dw(e,t){e=e|0;t=t|0;var n=0;e=e+36|0;n=o[e>>2]|0;if(n|0){Ji(n);KT(n)}n=$T(4)|0;yu(n,t);o[e>>2]=n;return}function Sw(){if(!(r[11716]|0)){o[2664]=0;Fe(63,10656,g|0)|0;r[11716]=1}return 10656}function Cw(){var e=0;if(!(r[11717]|0)){kw();o[2665]=1504;r[11717]=1;e=1504}else e=o[2665]|0;return e|0}function kw(){if(!(r[11740]|0)){r[11718]=za(za(8,0)|0,0)|0;r[11719]=za(za(0,0)|0,0)|0;r[11720]=za(za(0,16)|0,0)|0;r[11721]=za(za(8,0)|0,0)|0;r[11722]=za(za(0,0)|0,0)|0;r[11723]=za(za(8,0)|0,0)|0;r[11724]=za(za(0,0)|0,0)|0;r[11725]=za(za(8,0)|0,0)|0;r[11726]=za(za(0,0)|0,0)|0;r[11727]=za(za(8,0)|0,0)|0;r[11728]=za(za(0,0)|0,0)|0;r[11729]=za(za(0,0)|0,32)|0;r[11730]=za(za(0,0)|0,32)|0;r[11740]=1}return}function Tw(){return 1572}function xw(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;var u=0,a=0,l=0,s=0,c=0,f=0;u=h;h=h+32|0;f=u+16|0;c=u+12|0;s=u+8|0;l=u+4|0;a=u;o[f>>2]=e;o[c>>2]=t;o[s>>2]=n;o[l>>2]=r;o[a>>2]=i;Sw()|0;Aw(10656,f,c,s,l,a);h=u;return}function Aw(e,t,n,r,i,u){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;u=u|0;var a=0;a=$T(24)|0;Ua(a+4|0,o[t>>2]|0,o[n>>2]|0,o[r>>2]|0,o[i>>2]|0,o[u>>2]|0);o[a>>2]=o[e>>2];o[e>>2]=a;return}function Ow(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0,v=0,m=0,g=0,y=0,_=0,b=0;b=h;h=h+32|0;m=b+20|0;g=b+8|0;y=b+4|0;_=b;t=o[t>>2]|0;if(t|0){v=m+4|0;s=m+8|0;c=g+4|0;f=g+8|0;d=g+8|0;p=m+8|0;do{a=t+4|0;l=Pw(a)|0;if(l|0){i=Iw(l)|0;o[m>>2]=0;o[v>>2]=0;o[s>>2]=0;r=(Nw(l)|0)+1|0;Mw(m,r);if(r|0)while(1){r=r+-1|0;gk(g,o[i>>2]|0);u=o[v>>2]|0;if(u>>>0<(o[p>>2]|0)>>>0){o[u>>2]=o[g>>2];o[v>>2]=(o[v>>2]|0)+4}else Rw(m,g);if(!r)break;else i=i+4|0}r=Fw(l)|0;o[g>>2]=0;o[c>>2]=0;o[f>>2]=0;e:do{if(o[r>>2]|0){i=0;u=0;while(1){if((i|0)==(u|0))Lw(g,r);else{o[i>>2]=o[r>>2];o[c>>2]=(o[c>>2]|0)+4}r=r+4|0;if(!(o[r>>2]|0))break e;i=o[c>>2]|0;u=o[d>>2]|0}}}while(0);o[y>>2]=Bw(a)|0;o[_>>2]=Xa(l)|0;jw(n,e,y,_,m,g);Uw(g);zw(m)}t=o[t>>2]|0}while((t|0)!=0)}h=b;return}function Pw(e){e=e|0;return o[e+12>>2]|0}function Iw(e){e=e|0;return o[e+12>>2]|0}function Nw(e){e=e|0;return o[e+16>>2]|0}function Mw(e,t){e=e|0;t=t|0;var n=0,r=0,i=0;i=h;h=h+32|0;n=i;r=o[e>>2]|0;if((o[e+8>>2]|0)-r>>2>>>0>>0){bE(n,t,(o[e+4>>2]|0)-r>>2,e+8|0);wE(e,n);EE(n)}h=i;return}function Rw(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0;a=h;h=h+32|0;n=a;r=e+4|0;i=((o[r>>2]|0)-(o[e>>2]|0)>>2)+1|0;u=mE(e)|0;if(u>>>0>>0)UT(e);else{l=o[e>>2]|0;c=(o[e+8>>2]|0)-l|0;s=c>>1;bE(n,c>>2>>>0>>1>>>0?s>>>0>>0?i:s:u,(o[r>>2]|0)-l>>2,e+8|0);u=n+8|0;o[o[u>>2]>>2]=o[t>>2];o[u>>2]=(o[u>>2]|0)+4;wE(e,n);EE(n);h=a;return}}function Fw(e){e=e|0;return o[e+8>>2]|0}function Lw(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0;a=h;h=h+32|0;n=a;r=e+4|0;i=((o[r>>2]|0)-(o[e>>2]|0)>>2)+1|0;u=pE(e)|0;if(u>>>0>>0)UT(e);else{l=o[e>>2]|0;c=(o[e+8>>2]|0)-l|0;s=c>>1;gE(n,c>>2>>>0>>1>>>0?s>>>0>>0?i:s:u,(o[r>>2]|0)-l>>2,e+8|0);u=n+8|0;o[o[u>>2]>>2]=o[t>>2];o[u>>2]=(o[u>>2]|0)+4;yE(e,n);_E(n);h=a;return}}function Bw(e){e=e|0;return o[e>>2]|0}function jw(e,t,n,r,i,o){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;o=o|0;Ww(e,t,n,r,i,o);return}function Uw(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-4-r|0)>>>2)<<2);KT(n)}return}function zw(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-4-r|0)>>>2)<<2);KT(n)}return}function Ww(e,t,n,r,i,u){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;u=u|0;var a=0,l=0,s=0,c=0,f=0,d=0;a=h;h=h+48|0;f=a+40|0;l=a+32|0;d=a+24|0;s=a+12|0;c=a;Dk(l);e=Eu(e)|0;o[d>>2]=o[t>>2];n=o[n>>2]|0;r=o[r>>2]|0;Hw(s,i);Vw(c,u);o[f>>2]=o[d>>2];qw(e,f,n,r,s,c);Uw(c);zw(s);Ck(l);h=a;return}function Hw(e,t){e=e|0;t=t|0;var n=0,r=0;o[e>>2]=0;o[e+4>>2]=0;o[e+8>>2]=0;n=t+4|0;r=(o[n>>2]|0)-(o[t>>2]|0)>>2;if(r|0){hE(e,r);vE(e,o[t>>2]|0,o[n>>2]|0,r)}return}function Vw(e,t){e=e|0;t=t|0;var n=0,r=0;o[e>>2]=0;o[e+4>>2]=0;o[e+8>>2]=0;n=t+4|0;r=(o[n>>2]|0)-(o[t>>2]|0)>>2;if(r|0){fE(e,r);dE(e,o[t>>2]|0,o[n>>2]|0,r)}return}function qw(e,t,n,r,i,u){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;u=u|0;var a=0,l=0,s=0,c=0,f=0,d=0;a=h;h=h+32|0;f=a+28|0;d=a+24|0;l=a+12|0;s=a;c=Cu(Gw()|0)|0;o[d>>2]=o[t>>2];o[f>>2]=o[d>>2];t=$w(f)|0;n=Yw(n)|0;r=Kw(r)|0;o[l>>2]=o[i>>2];f=i+4|0;o[l+4>>2]=o[f>>2];d=i+8|0;o[l+8>>2]=o[d>>2];o[d>>2]=0;o[f>>2]=0;o[i>>2]=0;i=Xw(l)|0;o[s>>2]=o[u>>2];f=u+4|0;o[s+4>>2]=o[f>>2];d=u+8|0;o[s+8>>2]=o[d>>2];o[d>>2]=0;o[f>>2]=0;o[u>>2]=0;Oe(0,c|0,e|0,t|0,n|0,r|0,i|0,Qw(s)|0)|0;Uw(s);zw(l);h=a;return}function Gw(){var e=0;if(!(r[7968]|0)){sE(10708);e=7968;o[e>>2]=1;o[e+4>>2]=0}return 10708}function $w(e){e=e|0;return tE(e)|0}function Yw(e){e=e|0;return Zw(e)|0}function Kw(e){e=e|0;return Jc(e)|0}function Xw(e){e=e|0;return eE(e)|0}function Qw(e){e=e|0;return Jw(e)|0}function Jw(e){e=e|0;var t=0,n=0,r=0;r=(o[e+4>>2]|0)-(o[e>>2]|0)|0;n=r>>2;r=UD(r+4|0)|0;o[r>>2]=n;if(n|0){t=0;do{o[r+4+(t<<2)>>2]=Zw(o[(o[e>>2]|0)+(t<<2)>>2]|0)|0;t=t+1|0}while((t|0)!=(n|0))}return r|0}function Zw(e){e=e|0;return e|0}function eE(e){e=e|0;var t=0,n=0,r=0;r=(o[e+4>>2]|0)-(o[e>>2]|0)|0;n=r>>2;r=UD(r+4|0)|0;o[r>>2]=n;if(n|0){t=0;do{o[r+4+(t<<2)>>2]=tE((o[e>>2]|0)+(t<<2)|0)|0;t=t+1|0}while((t|0)!=(n|0))}return r|0}function tE(e){e=e|0;var t=0,n=0,r=0,i=0;i=h;h=h+32|0;t=i+12|0;n=i;r=al(nE()|0)|0;if(!r)e=rE(e)|0;else{ll(t,r);sl(n,t);bk(e,n);e=fl(t)|0}h=i;return e|0}function nE(){var e=0;if(!(r[7960]|0)){lE(10664);Fe(25,10664,g|0)|0;e=7960;o[e>>2]=1;o[e+4>>2]=0}return 10664}function rE(e){e=e|0;var t=0,n=0,r=0,i=0,u=0,a=0,l=0;n=h;h=h+16|0;i=n+4|0;a=n;r=UD(8)|0;t=r;l=$T(4)|0;o[l>>2]=o[e>>2];u=t+4|0;o[u>>2]=l;e=$T(8)|0;u=o[u>>2]|0;o[a>>2]=0;o[i>>2]=o[a>>2];iE(e,u,i);o[r>>2]=e;h=n;return t|0}function iE(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;n=$T(16)|0;o[n+4>>2]=0;o[n+8>>2]=0;o[n>>2]=1656;o[n+12>>2]=t;o[e+4>>2]=n;return}function oE(e){e=e|0;zT(e);KT(e);return}function uE(e){e=e|0;e=o[e+12>>2]|0;if(e|0)KT(e);return}function aE(e){e=e|0;KT(e);return}function lE(e){e=e|0;Al(e);return}function sE(e){e=e|0;Lu(e,cE()|0,5);return}function cE(){return 1676}function fE(e,t){e=e|0;t=t|0;var n=0;if((pE(e)|0)>>>0>>0)UT(e);if(t>>>0>1073741823)Ye();else{n=$T(t<<2)|0;o[e+4>>2]=n;o[e>>2]=n;o[e+8>>2]=n+(t<<2);return}}function dE(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;r=e+4|0;e=n-t|0;if((e|0)>0){ix(o[r>>2]|0,t|0,e|0)|0;o[r>>2]=(o[r>>2]|0)+(e>>>2<<2)}return}function pE(e){e=e|0;return 1073741823}function hE(e,t){e=e|0;t=t|0;var n=0;if((mE(e)|0)>>>0>>0)UT(e);if(t>>>0>1073741823)Ye();else{n=$T(t<<2)|0;o[e+4>>2]=n;o[e>>2]=n;o[e+8>>2]=n+(t<<2);return}}function vE(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;r=e+4|0;e=n-t|0;if((e|0)>0){ix(o[r>>2]|0,t|0,e|0)|0;o[r>>2]=(o[r>>2]|0)+(e>>>2<<2)}return}function mE(e){e=e|0;return 1073741823}function gE(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>1073741823)Ye();else{i=$T(t<<2)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<2)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<2);return}function yE(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>2)<<2)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function _E(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-4-t|0)>>>2)<<2);e=o[e>>2]|0;if(e|0)KT(e);return}function bE(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>1073741823)Ye();else{i=$T(t<<2)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<2)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<2);return}function wE(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>2)<<2)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function EE(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-4-t|0)>>>2)<<2);e=o[e>>2]|0;if(e|0)KT(e);return}function DE(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;var u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0,v=0,m=0,g=0;g=h;h=h+32|0;f=g+20|0;d=g+12|0;c=g+16|0;p=g+4|0;v=g;m=g+8|0;l=Cw()|0;u=o[l>>2]|0;a=o[u>>2]|0;if(a|0){s=o[l+8>>2]|0;l=o[l+4>>2]|0;while(1){gk(f,a);SE(e,f,l,s);u=u+4|0;a=o[u>>2]|0;if(!a)break;else{s=s+1|0;l=l+1|0}}}u=Tw()|0;a=o[u>>2]|0;if(a|0)do{gk(f,a);o[d>>2]=o[u+4>>2];CE(t,f,d);u=u+8|0;a=o[u>>2]|0}while((a|0)!=0);u=o[(fw()|0)>>2]|0;if(u|0)do{t=o[u+4>>2]|0;gk(f,o[(kE(t)|0)>>2]|0);o[d>>2]=Ew(t)|0;TE(n,f,d);u=o[u>>2]|0}while((u|0)!=0);gk(c,0);u=Sw()|0;o[f>>2]=o[c>>2];Ow(f,u,i);u=o[(fw()|0)>>2]|0;if(u|0){e=f+4|0;t=f+8|0;n=f+8|0;do{s=o[u+4>>2]|0;gk(d,o[(kE(s)|0)>>2]|0);AE(p,xE(s)|0);a=o[p>>2]|0;if(a|0){o[f>>2]=0;o[e>>2]=0;o[t>>2]=0;do{gk(v,o[(kE(o[a+4>>2]|0)|0)>>2]|0);l=o[e>>2]|0;if(l>>>0<(o[n>>2]|0)>>>0){o[l>>2]=o[v>>2];o[e>>2]=(o[e>>2]|0)+4}else Rw(f,v);a=o[a>>2]|0}while((a|0)!=0);OE(r,d,f);zw(f)}o[m>>2]=o[d>>2];c=PE(s)|0;o[f>>2]=o[m>>2];Ow(f,c,i);kl(p);u=o[u>>2]|0}while((u|0)!=0)}h=g;return}function SE(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;qE(e,t,n,r);return}function CE(e,t,n){e=e|0;t=t|0;n=n|0;VE(e,t,n);return}function kE(e){e=e|0;return e|0}function TE(e,t,n){e=e|0;t=t|0;n=n|0;jE(e,t,n);return}function xE(e){e=e|0;return e+16|0}function AE(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;u=h;h=h+16|0;i=u+8|0;n=u;o[e>>2]=0;r=o[t>>2]|0;o[i>>2]=r;o[n>>2]=e;n=LE(n)|0;if(r|0){r=$T(12)|0;a=(BE(i)|0)+4|0;e=o[a+4>>2]|0;t=r+4|0;o[t>>2]=o[a>>2];o[t+4>>2]=e;t=o[o[i>>2]>>2]|0;o[i>>2]=t;if(!t)e=r;else{t=r;while(1){e=$T(12)|0;s=(BE(i)|0)+4|0;l=o[s+4>>2]|0;a=e+4|0;o[a>>2]=o[s>>2];o[a+4>>2]=l;o[t>>2]=e;a=o[o[i>>2]>>2]|0;o[i>>2]=a;if(!a)break;else t=e}}o[e>>2]=o[n>>2];o[n>>2]=r}h=u;return}function OE(e,t,n){e=e|0;t=t|0;n=n|0;IE(e,t,n);return}function PE(e){e=e|0;return e+24|0}function IE(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+32|0;a=r+24|0;i=r+16|0;l=r+12|0;u=r;Dk(i);e=Eu(e)|0;o[l>>2]=o[t>>2];Hw(u,n);o[a>>2]=o[l>>2];NE(e,a,u);zw(u);Ck(i);h=r;return}function NE(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0;r=h;h=h+32|0;a=r+16|0;l=r+12|0;i=r;u=Cu(ME()|0)|0;o[l>>2]=o[t>>2];o[a>>2]=o[l>>2];t=$w(a)|0;o[i>>2]=o[n>>2];a=n+4|0;o[i+4>>2]=o[a>>2];l=n+8|0;o[i+8>>2]=o[l>>2];o[l>>2]=0;o[a>>2]=0;o[n>>2]=0;ke(0,u|0,e|0,t|0,Xw(i)|0)|0;zw(i);h=r;return}function ME(){var e=0;if(!(r[7976]|0)){RE(10720);e=7976;o[e>>2]=1;o[e+4>>2]=0}return 10720}function RE(e){e=e|0;Lu(e,FE()|0,2);return}function FE(){return 1732}function LE(e){e=e|0;return o[e>>2]|0}function BE(e){e=e|0;return o[e>>2]|0}function jE(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+32|0;u=r+16|0;i=r+8|0;a=r;Dk(i);e=Eu(e)|0;o[a>>2]=o[t>>2];n=o[n>>2]|0;o[u>>2]=o[a>>2];UE(e,u,n);Ck(i);h=r;return}function UE(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+16|0;u=r+4|0;a=r;i=Cu(zE()|0)|0;o[a>>2]=o[t>>2];o[u>>2]=o[a>>2];t=$w(u)|0;ke(0,i|0,e|0,t|0,Yw(n)|0)|0;h=r;return}function zE(){var e=0;if(!(r[7984]|0)){WE(10732);e=7984;o[e>>2]=1;o[e+4>>2]=0}return 10732}function WE(e){e=e|0;Lu(e,HE()|0,2);return}function HE(){return 1744}function VE(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0;r=h;h=h+32|0;u=r+16|0;i=r+8|0;a=r;Dk(i);e=Eu(e)|0;o[a>>2]=o[t>>2];n=o[n>>2]|0;o[u>>2]=o[a>>2];UE(e,u,n);Ck(i);h=r;return}function qE(e,t,n,i){e=e|0;t=t|0;n=n|0;i=i|0;var u=0,a=0,l=0,s=0;u=h;h=h+32|0;l=u+16|0;a=u+8|0;s=u;Dk(a);e=Eu(e)|0;o[s>>2]=o[t>>2];n=r[n>>0]|0;i=r[i>>0]|0;o[l>>2]=o[s>>2];GE(e,l,n,i);Ck(a);h=u;return}function GE(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0;i=h;h=h+16|0;a=i+4|0;l=i;u=Cu($E()|0)|0;o[l>>2]=o[t>>2];o[a>>2]=o[l>>2];t=$w(a)|0;n=YE(n)|0;nt(0,u|0,e|0,t|0,n|0,YE(r)|0)|0;h=i;return}function $E(){var e=0;if(!(r[7992]|0)){XE(10744);e=7992;o[e>>2]=1;o[e+4>>2]=0}return 10744}function YE(e){e=e|0;return KE(e)|0}function KE(e){e=e|0;return e&255|0}function XE(e){e=e|0;Lu(e,QE()|0,3);return}function QE(){return 1756}function JE(e,t,n){e=e|0;t=t|0;n=n|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0,v=0;v=h;h=h+32|0;s=v+8|0;c=v+4|0;f=v+20|0;d=v;Vs(e,0);i=_k(t)|0;o[s>>2]=0;p=s+4|0;o[p>>2]=0;o[s+8>>2]=0;switch(i<<24>>24){case 0:{r[f>>0]=0;ZE(c,n,f);eD(e,c)|0;Zi(c);break}case 8:{p=yk(t)|0;r[f>>0]=8;gk(d,o[p+4>>2]|0);tD(c,n,f,d,p+8|0);eD(e,c)|0;Zi(c);break}case 9:{a=yk(t)|0;t=o[a+4>>2]|0;if(t|0){l=s+8|0;u=a+12|0;while(1){t=t+-1|0;gk(c,o[u>>2]|0);i=o[p>>2]|0;if(i>>>0<(o[l>>2]|0)>>>0){o[i>>2]=o[c>>2];o[p>>2]=(o[p>>2]|0)+4}else Rw(s,c);if(!t)break;else u=u+4|0}}r[f>>0]=9;gk(d,o[a+8>>2]|0);nD(c,n,f,d,s);eD(e,c)|0;Zi(c);break}default:{p=yk(t)|0;r[f>>0]=i;gk(d,o[p+4>>2]|0);rD(c,n,f,d);eD(e,c)|0;Zi(c)}}zw(s);h=v;return}function ZE(e,t,n){e=e|0;t=t|0;n=n|0;var i=0,o=0;i=h;h=h+16|0;o=i;Dk(o);t=Eu(t)|0;gD(e,t,r[n>>0]|0);Ck(o);h=i;return}function eD(e,t){e=e|0;t=t|0;var n=0;n=o[e>>2]|0;if(n|0)rt(n|0);o[e>>2]=o[t>>2];o[t>>2]=0;return e|0}function tD(e,t,n,i,u){e=e|0;t=t|0;n=n|0;i=i|0;u=u|0;var a=0,l=0,s=0,c=0;a=h;h=h+32|0;s=a+16|0;l=a+8|0;c=a;Dk(l);t=Eu(t)|0;n=r[n>>0]|0;o[c>>2]=o[i>>2];u=o[u>>2]|0;o[s>>2]=o[c>>2];pD(e,t,n,s,u);Ck(l);h=a;return}function nD(e,t,n,i,u){e=e|0;t=t|0;n=n|0;i=i|0;u=u|0;var a=0,l=0,s=0,c=0,f=0;a=h;h=h+32|0;c=a+24|0;l=a+16|0;f=a+12|0;s=a;Dk(l);t=Eu(t)|0;n=r[n>>0]|0;o[f>>2]=o[i>>2];Hw(s,u);o[c>>2]=o[f>>2];sD(e,t,n,c,s);zw(s);Ck(l);h=a;return}function rD(e,t,n,i){e=e|0;t=t|0;n=n|0;i=i|0;var u=0,a=0,l=0,s=0;u=h;h=h+32|0;l=u+16|0;a=u+8|0;s=u;Dk(a);t=Eu(t)|0;n=r[n>>0]|0;o[s>>2]=o[i>>2];o[l>>2]=o[s>>2];iD(e,t,n,l);Ck(a);h=u;return}function iD(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0,a=0,l=0;i=h;h=h+16|0;u=i+4|0;l=i;a=Cu(oD()|0)|0;n=YE(n)|0;o[l>>2]=o[r>>2];o[u>>2]=o[l>>2];uD(e,ke(0,a|0,t|0,n|0,$w(u)|0)|0);h=i;return}function oD(){var e=0;if(!(r[8e3]|0)){aD(10756);e=8e3;o[e>>2]=1;o[e+4>>2]=0}return 10756}function uD(e,t){e=e|0;t=t|0;Vs(e,t);return}function aD(e){e=e|0;Lu(e,lD()|0,2);return}function lD(){return 1772}function sD(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;var u=0,a=0,l=0,s=0,c=0;u=h;h=h+32|0;s=u+16|0;c=u+12|0;a=u;l=Cu(cD()|0)|0;n=YE(n)|0;o[c>>2]=o[r>>2];o[s>>2]=o[c>>2];r=$w(s)|0;o[a>>2]=o[i>>2];s=i+4|0;o[a+4>>2]=o[s>>2];c=i+8|0;o[a+8>>2]=o[c>>2];o[c>>2]=0;o[s>>2]=0;o[i>>2]=0;uD(e,nt(0,l|0,t|0,n|0,r|0,Xw(a)|0)|0);zw(a);h=u;return}function cD(){var e=0;if(!(r[8008]|0)){fD(10768);e=8008;o[e>>2]=1;o[e+4>>2]=0}return 10768}function fD(e){e=e|0;Lu(e,dD()|0,3);return}function dD(){return 1784}function pD(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;var u=0,a=0,l=0,s=0;u=h;h=h+16|0;l=u+4|0;s=u;a=Cu(hD()|0)|0;n=YE(n)|0;o[s>>2]=o[r>>2];o[l>>2]=o[s>>2];r=$w(l)|0;uD(e,nt(0,a|0,t|0,n|0,r|0,Kw(i)|0)|0);h=u;return}function hD(){var e=0;if(!(r[8016]|0)){vD(10780);e=8016;o[e>>2]=1;o[e+4>>2]=0}return 10780}function vD(e){e=e|0;Lu(e,mD()|0,3);return}function mD(){return 1800}function gD(e,t,n){e=e|0;t=t|0;n=n|0;var r=0;r=Cu(yD()|0)|0;uD(e,it(0,r|0,t|0,YE(n)|0)|0);return}function yD(){var e=0;if(!(r[8024]|0)){_D(10792);e=8024;o[e>>2]=1;o[e+4>>2]=0}return 10792}function _D(e){e=e|0;Lu(e,bD()|0,1);return}function bD(){return 1816}function wD(){ED();DD();SD();return}function ED(){o[2702]=YT(65536)|0;return}function DD(){$D(10856);return}function SD(){CD(10816);return}function CD(e){e=e|0;kD(e,5044);TD(e)|0;return}function kD(e,t){e=e|0;t=t|0;var n=0;n=nE()|0;o[e>>2]=n;zD(n,t);cw(o[e>>2]|0);return}function TD(e){e=e|0;var t=0;t=o[e>>2]|0;r_(t,xD()|0);return e|0}function xD(){var e=0;if(!(r[8032]|0)){AD(10820);Fe(64,10820,g|0)|0;e=8032;o[e>>2]=1;o[e+4>>2]=0}if(!(Xa(10820)|0))AD(10820);return 10820}function AD(e){e=e|0;ID(e);s_(e,25);return}function OD(e){e=e|0;PD(e+24|0);return}function PD(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function ID(e){e=e|0;var t=0;t=Za()|0;nl(e,5,18,t,LD()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function ND(e,t){e=e|0;t=t|0;MD(e,t);return}function MD(e,t){e=e|0;t=t|0;var n=0,r=0,i=0;n=h;h=h+16|0;r=n;i=n+4|0;cc(i,t);o[r>>2]=fc(i,t)|0;RD(e,r);h=n;return}function RD(e,t){e=e|0;t=t|0;FD(e+4|0,o[t>>2]|0);r[e+8>>0]=1;return}function FD(e,t){e=e|0;t=t|0;o[e>>2]=t;return}function LD(){return 1824}function BD(e){e=e|0;return jD(e)|0}function jD(e){e=e|0;var t=0,n=0,r=0,i=0,u=0,a=0,l=0;n=h;h=h+16|0;i=n+4|0;a=n;r=UD(8)|0;t=r;l=$T(4)|0;cc(i,e);FD(l,fc(i,e)|0);u=t+4|0;o[u>>2]=l;e=$T(8)|0;u=o[u>>2]|0;o[a>>2]=0;o[i>>2]=o[a>>2];iE(e,u,i);o[r>>2]=e;h=n;return t|0}function UD(e){e=e|0;var t=0,n=0;e=e+7&-8;if(e>>>0<=32768?(t=o[2701]|0,e>>>0<=(65536-t|0)>>>0):0){n=(o[2702]|0)+t|0;o[2701]=t+e;e=n}else{e=YT(e+8|0)|0;o[e>>2]=o[2703];o[2703]=e;e=e+8|0}return e|0}function zD(e,t){e=e|0;t=t|0;o[e>>2]=WD()|0;o[e+4>>2]=HD()|0;o[e+12>>2]=t;o[e+8>>2]=VD()|0;o[e+32>>2]=9;return}function WD(){return 11744}function HD(){return 1832}function VD(){return N_()|0}function qD(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;if((Um(r,896)|0)==512){if(n|0){GD(n);KT(n)}}else if(t|0)KT(t);return}function GD(e){e=e|0;e=o[e+4>>2]|0;if(e|0)qT(e);return}function $D(e){e=e|0;YD(e,5052);KD(e)|0;XD(e,5058,26)|0;QD(e,5069,1)|0;JD(e,5077,10)|0;ZD(e,5087,19)|0;tS(e,5094,27)|0;return}function YD(e,t){e=e|0;t=t|0;var n=0;n=sk()|0;o[e>>2]=n;ck(n,t);cw(o[e>>2]|0);return}function KD(e){e=e|0;var t=0;t=o[e>>2]|0;r_(t,YC()|0);return e|0}function XD(e,t,n){e=e|0;t=t|0;n=n|0;TC(e,Ia(t)|0,n,0);return e|0}function QD(e,t,n){e=e|0;t=t|0;n=n|0;sC(e,Ia(t)|0,n,0);return e|0}function JD(e,t,n){e=e|0;t=t|0;n=n|0;BS(e,Ia(t)|0,n,0);return e|0}function ZD(e,t,n){e=e|0;t=t|0;n=n|0;bS(e,Ia(t)|0,n,0);return e|0}function eS(e,t){e=e|0;t=t|0;var n=0,r=0;e:while(1){n=o[2703]|0;while(1){if((n|0)==(t|0))break e;r=o[n>>2]|0;o[2703]=r;if(!n)n=r;else break}KT(n)}o[2701]=e;return}function tS(e,t,n){e=e|0;t=t|0;n=n|0;nS(e,Ia(t)|0,n,0);return e|0}function nS(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=o[e>>2]|0;i=rS()|0;e=iS(n)|0;La(u,t,i,e,oS(n,r)|0,r);return}function rS(){var e=0,t=0;if(!(r[8040]|0)){dS(10860);Fe(65,10860,g|0)|0;t=8040;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(10860)|0)){e=10860;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));dS(10860)}return 10860}function iS(e){e=e|0;return e|0}function oS(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;l=h;h=h+16|0;i=l;u=l+4|0;o[i>>2]=e;s=rS()|0;a=s+24|0;t=za(t,4)|0;o[u>>2]=t;n=s+28|0;r=o[n>>2]|0;if(r>>>0<(o[s+32>>2]|0)>>>0){uS(r,e,t);t=(o[n>>2]|0)+8|0;o[n>>2]=t}else{aS(a,i,u);t=o[n>>2]|0}h=l;return(t-(o[a>>2]|0)>>3)+-1|0}function uS(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;return}function aS(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;i=l;u=e+4|0;a=((o[u>>2]|0)-(o[e>>2]|0)>>3)+1|0;r=lS(e)|0;if(r>>>0>>0)UT(e);else{s=o[e>>2]|0;f=(o[e+8>>2]|0)-s|0;c=f>>2;sS(i,f>>3>>>0>>1>>>0?c>>>0>>0?a:c:r,(o[u>>2]|0)-s>>3,e+8|0);a=i+8|0;uS(o[a>>2]|0,o[t>>2]|0,o[n>>2]|0);o[a>>2]=(o[a>>2]|0)+8;cS(e,i);fS(i);h=l;return}}function lS(e){e=e|0;return 536870911}function sS(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>536870911)Ye();else{i=$T(t<<3)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<3)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<3);return}function cS(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>3)<<3)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function fS(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-8-t|0)>>>3)<<3);e=o[e>>2]|0;if(e|0)KT(e);return}function dS(e){e=e|0;vS(e);return}function pS(e){e=e|0;hS(e+24|0);return}function hS(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function vS(e){e=e|0;var t=0;t=Za()|0;nl(e,1,11,t,mS()|0,2);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function mS(){return 1840}function gS(e,t,n){e=e|0;t=t|0;n=n|0;_S(o[(yS(e)|0)>>2]|0,t,n);return}function yS(e){e=e|0;return(o[(rS()|0)+24>>2]|0)+(e<<3)|0}function _S(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,o=0;r=h;h=h+16|0;o=r+1|0;i=r;cc(o,t);t=fc(o,t)|0;cc(i,n);n=fc(i,n)|0;vA[e&31](t,n);h=r;return}function bS(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=o[e>>2]|0;i=wS()|0;e=ES(n)|0;La(u,t,i,e,DS(n,r)|0,r);return}function wS(){var e=0,t=0;if(!(r[8048]|0)){OS(10896);Fe(66,10896,g|0)|0;t=8048;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(10896)|0)){e=10896;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));OS(10896)}return 10896}function ES(e){e=e|0;return e|0}function DS(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;l=h;h=h+16|0;i=l;u=l+4|0;o[i>>2]=e;s=wS()|0;a=s+24|0;t=za(t,4)|0;o[u>>2]=t;n=s+28|0;r=o[n>>2]|0;if(r>>>0<(o[s+32>>2]|0)>>>0){SS(r,e,t);t=(o[n>>2]|0)+8|0;o[n>>2]=t}else{CS(a,i,u);t=o[n>>2]|0}h=l;return(t-(o[a>>2]|0)>>3)+-1|0}function SS(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;return}function CS(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;i=l;u=e+4|0;a=((o[u>>2]|0)-(o[e>>2]|0)>>3)+1|0;r=kS(e)|0;if(r>>>0>>0)UT(e);else{s=o[e>>2]|0;f=(o[e+8>>2]|0)-s|0;c=f>>2;TS(i,f>>3>>>0>>1>>>0?c>>>0>>0?a:c:r,(o[u>>2]|0)-s>>3,e+8|0);a=i+8|0;SS(o[a>>2]|0,o[t>>2]|0,o[n>>2]|0);o[a>>2]=(o[a>>2]|0)+8;xS(e,i);AS(i);h=l;return}}function kS(e){e=e|0;return 536870911}function TS(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>536870911)Ye();else{i=$T(t<<3)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<3)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<3);return}function xS(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>3)<<3)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function AS(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-8-t|0)>>>3)<<3);e=o[e>>2]|0;if(e|0)KT(e);return}function OS(e){e=e|0;NS(e);return}function PS(e){e=e|0;IS(e+24|0);return}function IS(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function NS(e){e=e|0;var t=0;t=Za()|0;nl(e,1,11,t,MS()|0,1);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function MS(){return 1852}function RS(e,t){e=e|0;t=t|0;return LS(o[(FS(e)|0)>>2]|0,t)|0}function FS(e){e=e|0;return(o[(wS()|0)+24>>2]|0)+(e<<3)|0}function LS(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;cc(r,t);t=fc(r,t)|0;t=Jc(mA[e&31](t)|0)|0;h=n;return t|0}function BS(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=o[e>>2]|0;i=jS()|0;e=US(n)|0;La(u,t,i,e,zS(n,r)|0,r);return}function jS(){var e=0,t=0;if(!(r[8056]|0)){YS(10932);Fe(67,10932,g|0)|0;t=8056;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(10932)|0)){e=10932;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));YS(10932)}return 10932}function US(e){e=e|0;return e|0}function zS(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;l=h;h=h+16|0;i=l;u=l+4|0;o[i>>2]=e;s=jS()|0;a=s+24|0;t=za(t,4)|0;o[u>>2]=t;n=s+28|0;r=o[n>>2]|0;if(r>>>0<(o[s+32>>2]|0)>>>0){WS(r,e,t);t=(o[n>>2]|0)+8|0;o[n>>2]=t}else{HS(a,i,u);t=o[n>>2]|0}h=l;return(t-(o[a>>2]|0)>>3)+-1|0}function WS(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;return}function HS(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;i=l;u=e+4|0;a=((o[u>>2]|0)-(o[e>>2]|0)>>3)+1|0;r=VS(e)|0;if(r>>>0>>0)UT(e);else{s=o[e>>2]|0;f=(o[e+8>>2]|0)-s|0;c=f>>2;qS(i,f>>3>>>0>>1>>>0?c>>>0>>0?a:c:r,(o[u>>2]|0)-s>>3,e+8|0);a=i+8|0;WS(o[a>>2]|0,o[t>>2]|0,o[n>>2]|0);o[a>>2]=(o[a>>2]|0)+8;GS(e,i);$S(i);h=l;return}}function VS(e){e=e|0;return 536870911}function qS(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>536870911)Ye();else{i=$T(t<<3)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<3)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<3);return}function GS(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>3)<<3)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function $S(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-8-t|0)>>>3)<<3);e=o[e>>2]|0;if(e|0)KT(e);return}function YS(e){e=e|0;QS(e);return}function KS(e){e=e|0;XS(e+24|0);return}function XS(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function QS(e){e=e|0;var t=0;t=Za()|0;nl(e,1,7,t,JS()|0,2);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function JS(){return 1860}function ZS(e,t,n){e=e|0;t=t|0;n=n|0;return tC(o[(eC(e)|0)>>2]|0,t,n)|0}function eC(e){e=e|0;return(o[(jS()|0)+24>>2]|0)+(e<<3)|0}function tC(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0;r=h;h=h+32|0;a=r+12|0;u=r+8|0;l=r;s=r+16|0;i=r+4|0;nC(s,t);rC(l,s,t);Us(i,n);n=zs(i,n)|0;o[a>>2]=o[l>>2];PA[e&15](u,a,n);n=iC(u)|0;Zi(u);Ws(i);h=r;return n|0}function nC(e,t){e=e|0;t=t|0;return}function rC(e,t,n){e=e|0;t=t|0;n=n|0;oC(e,n);return}function iC(e){e=e|0;return Eu(e)|0}function oC(e,t){e=e|0;t=t|0;var n=0,r=0,i=0;i=h;h=h+16|0;n=i;r=t;if(!(r&1))o[e>>2]=o[t>>2];else{uC(n,0);Be(r|0,n|0)|0;aC(e,n);lC(n)}h=i;return}function uC(e,t){e=e|0;t=t|0;Iu(e,t);o[e+4>>2]=0;r[e+8>>0]=0;return}function aC(e,t){e=e|0;t=t|0;o[e>>2]=o[t+4>>2];return}function lC(e){e=e|0;r[e+8>>0]=0;return}function sC(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=o[e>>2]|0;i=cC()|0;e=fC(n)|0;La(u,t,i,e,dC(n,r)|0,r);return}function cC(){var e=0,t=0;if(!(r[8064]|0)){_C(10968);Fe(68,10968,g|0)|0;t=8064;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(10968)|0)){e=10968;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));_C(10968)}return 10968}function fC(e){e=e|0;return e|0}function dC(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;l=h;h=h+16|0;i=l;u=l+4|0;o[i>>2]=e;s=cC()|0;a=s+24|0;t=za(t,4)|0;o[u>>2]=t;n=s+28|0;r=o[n>>2]|0;if(r>>>0<(o[s+32>>2]|0)>>>0){pC(r,e,t);t=(o[n>>2]|0)+8|0;o[n>>2]=t}else{hC(a,i,u);t=o[n>>2]|0}h=l;return(t-(o[a>>2]|0)>>3)+-1|0}function pC(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;return}function hC(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;i=l;u=e+4|0;a=((o[u>>2]|0)-(o[e>>2]|0)>>3)+1|0;r=vC(e)|0;if(r>>>0>>0)UT(e);else{s=o[e>>2]|0;f=(o[e+8>>2]|0)-s|0;c=f>>2;mC(i,f>>3>>>0>>1>>>0?c>>>0>>0?a:c:r,(o[u>>2]|0)-s>>3,e+8|0);a=i+8|0;pC(o[a>>2]|0,o[t>>2]|0,o[n>>2]|0);o[a>>2]=(o[a>>2]|0)+8;gC(e,i);yC(i);h=l;return}}function vC(e){e=e|0;return 536870911}function mC(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>536870911)Ye();else{i=$T(t<<3)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<3)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<3);return}function gC(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>3)<<3)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function yC(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-8-t|0)>>>3)<<3);e=o[e>>2]|0;if(e|0)KT(e);return}function _C(e){e=e|0;EC(e);return}function bC(e){e=e|0;wC(e+24|0);return}function wC(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function EC(e){e=e|0;var t=0;t=Za()|0;nl(e,1,1,t,DC()|0,5);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function DC(){return 1872}function SC(e,t,n,r,i,u){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;u=u|0;kC(o[(CC(e)|0)>>2]|0,t,n,r,i,u);return}function CC(e){e=e|0;return(o[(cC()|0)+24>>2]|0)+(e<<3)|0}function kC(e,t,n,r,i,o){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;o=o|0;var u=0,a=0,l=0,s=0,c=0,f=0;u=h;h=h+32|0;a=u+16|0;l=u+12|0;s=u+8|0;c=u+4|0;f=u;Us(a,t);t=zs(a,t)|0;Us(l,n);n=zs(l,n)|0;Us(s,r);r=zs(s,r)|0;Us(c,i);i=zs(c,i)|0;Us(f,o);o=zs(f,o)|0;cA[e&1](t,n,r,i,o);Ws(f);Ws(c);Ws(s);Ws(l);Ws(a);h=u;return}function TC(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=o[e>>2]|0;i=xC()|0;e=AC(n)|0;La(u,t,i,e,OC(n,r)|0,r);return}function xC(){var e=0,t=0;if(!(r[8072]|0)){LC(11004);Fe(69,11004,g|0)|0;t=8072;o[t>>2]=1;o[t+4>>2]=0}if(!(Xa(11004)|0)){e=11004;t=e+36|0;do{o[e>>2]=0;e=e+4|0}while((e|0)<(t|0));LC(11004)}return 11004}function AC(e){e=e|0;return e|0}function OC(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0,l=0,s=0;l=h;h=h+16|0;i=l;u=l+4|0;o[i>>2]=e;s=xC()|0;a=s+24|0;t=za(t,4)|0;o[u>>2]=t;n=s+28|0;r=o[n>>2]|0;if(r>>>0<(o[s+32>>2]|0)>>>0){PC(r,e,t);t=(o[n>>2]|0)+8|0;o[n>>2]=t}else{IC(a,i,u);t=o[n>>2]|0}h=l;return(t-(o[a>>2]|0)>>3)+-1|0}function PC(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;o[e+4>>2]=n;return}function IC(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0;l=h;h=h+32|0;i=l;u=e+4|0;a=((o[u>>2]|0)-(o[e>>2]|0)>>3)+1|0;r=NC(e)|0;if(r>>>0>>0)UT(e);else{s=o[e>>2]|0;f=(o[e+8>>2]|0)-s|0;c=f>>2;MC(i,f>>3>>>0>>1>>>0?c>>>0>>0?a:c:r,(o[u>>2]|0)-s>>3,e+8|0);a=i+8|0;PC(o[a>>2]|0,o[t>>2]|0,o[n>>2]|0);o[a>>2]=(o[a>>2]|0)+8;RC(e,i);FC(i);h=l;return}}function NC(e){e=e|0;return 536870911}function MC(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0;o[e+12>>2]=0;o[e+16>>2]=r;do{if(t){if(t>>>0>536870911)Ye();else{i=$T(t<<3)|0;break}}else i=0}while(0);o[e>>2]=i;r=i+(n<<3)|0;o[e+8>>2]=r;o[e+4>>2]=r;o[e+12>>2]=i+(t<<3);return}function RC(e,t){e=e|0;t=t|0;var n=0,r=0,i=0,u=0,a=0;r=o[e>>2]|0;a=e+4|0;u=t+4|0;i=(o[a>>2]|0)-r|0;n=(o[u>>2]|0)+(0-(i>>3)<<3)|0;o[u>>2]=n;if((i|0)>0){ix(n|0,r|0,i|0)|0;r=u;n=o[u>>2]|0}else r=u;u=o[e>>2]|0;o[e>>2]=n;o[r>>2]=u;u=t+8|0;i=o[a>>2]|0;o[a>>2]=o[u>>2];o[u>>2]=i;u=e+8|0;a=t+12|0;e=o[u>>2]|0;o[u>>2]=o[a>>2];o[a>>2]=e;o[t>>2]=o[r>>2];return}function FC(e){e=e|0;var t=0,n=0,r=0;t=o[e+4>>2]|0;n=e+8|0;r=o[n>>2]|0;if((r|0)!=(t|0))o[n>>2]=r+(~((r+-8-t|0)>>>3)<<3);e=o[e>>2]|0;if(e|0)KT(e);return}function LC(e){e=e|0;UC(e);return}function BC(e){e=e|0;jC(e+24|0);return}function jC(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function UC(e){e=e|0;var t=0;t=Za()|0;nl(e,1,12,t,zC()|0,2);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function zC(){return 1896}function WC(e,t,n){e=e|0;t=t|0;n=n|0;VC(o[(HC(e)|0)>>2]|0,t,n);return}function HC(e){e=e|0;return(o[(xC()|0)+24>>2]|0)+(e<<3)|0}function VC(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,o=0;r=h;h=h+16|0;o=r+4|0;i=r;qC(o,t);t=GC(o,t)|0;Us(i,n);n=zs(i,n)|0;vA[e&31](t,n);Ws(i);h=r;return}function qC(e,t){e=e|0;t=t|0;return}function GC(e,t){e=e|0;t=t|0;return $C(t)|0}function $C(e){e=e|0;return e|0}function YC(){var e=0;if(!(r[8080]|0)){KC(11040);Fe(70,11040,g|0)|0;e=8080;o[e>>2]=1;o[e+4>>2]=0}if(!(Xa(11040)|0))KC(11040);return 11040}function KC(e){e=e|0;JC(e);s_(e,71);return}function XC(e){e=e|0;QC(e+24|0);return}function QC(e){e=e|0;var t=0,n=0,r=0;n=o[e>>2]|0;r=n;if(n|0){e=e+4|0;t=o[e>>2]|0;if((t|0)!=(n|0))o[e>>2]=t+(~((t+-8-r|0)>>>3)<<3);KT(n)}return}function JC(e){e=e|0;var t=0;t=Za()|0;nl(e,5,7,t,nk()|0,0);o[e+24>>2]=0;o[e+28>>2]=0;o[e+32>>2]=0;return}function ZC(e){e=e|0;ek(e);return}function ek(e){e=e|0;tk(e);return}function tk(e){e=e|0;r[e+8>>0]=1;return}function nk(){return 1936}function rk(){return ik()|0}function ik(){var e=0,t=0,n=0,r=0,i=0,u=0,a=0;t=h;h=h+16|0;i=t+4|0;a=t;n=UD(8)|0;e=n;u=e+4|0;o[u>>2]=$T(1)|0;r=$T(8)|0;u=o[u>>2]|0;o[a>>2]=0;o[i>>2]=o[a>>2];ok(r,u,i);o[n>>2]=r;h=t;return e|0}function ok(e,t,n){e=e|0;t=t|0;n=n|0;o[e>>2]=t;n=$T(16)|0;o[n+4>>2]=0;o[n+8>>2]=0;o[n>>2]=1916;o[n+12>>2]=t;o[e+4>>2]=n;return}function uk(e){e=e|0;zT(e);KT(e);return}function ak(e){e=e|0;e=o[e+12>>2]|0;if(e|0)KT(e);return}function lk(e){e=e|0;KT(e);return}function sk(){var e=0;if(!(r[8088]|0)){mk(11076);Fe(25,11076,g|0)|0;e=8088;o[e>>2]=1;o[e+4>>2]=0}return 11076}function ck(e,t){e=e|0;t=t|0;o[e>>2]=fk()|0;o[e+4>>2]=dk()|0;o[e+12>>2]=t;o[e+8>>2]=pk()|0;o[e+32>>2]=10;return}function fk(){return 11745}function dk(){return 1940}function pk(){return Wm()|0}function hk(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;if((Um(r,896)|0)==512){if(n|0){vk(n);KT(n)}}else if(t|0)KT(t);return}function vk(e){e=e|0;e=o[e+4>>2]|0;if(e|0)qT(e);return}function mk(e){e=e|0;Al(e);return}function gk(e,t){e=e|0;t=t|0;o[e>>2]=t;return}function yk(e){e=e|0;return o[e>>2]|0}function _k(e){e=e|0;return r[o[e>>2]>>0]|0}function bk(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;o[r>>2]=o[e>>2];wk(t,r)|0;h=n;return}function wk(e,t){e=e|0;t=t|0;var n=0;n=Ek(o[e>>2]|0,t)|0;t=e+4|0;o[(o[t>>2]|0)+8>>2]=n;return o[(o[t>>2]|0)+8>>2]|0}function Ek(e,t){e=e|0;t=t|0;var n=0,r=0;n=h;h=h+16|0;r=n;Dk(r);e=Eu(e)|0;t=Sk(e,o[t>>2]|0)|0;Ck(r);h=n;return t|0}function Dk(e){e=e|0;o[e>>2]=o[2701];o[e+4>>2]=o[2703];return}function Sk(e,t){e=e|0;t=t|0;var n=0;n=Cu(kk()|0)|0;return it(0,n|0,e|0,Kw(t)|0)|0}function Ck(e){e=e|0;eS(o[e>>2]|0,o[e+4>>2]|0);return}function kk(){var e=0;if(!(r[8096]|0)){Tk(11120);e=8096;o[e>>2]=1;o[e+4>>2]=0}return 11120}function Tk(e){e=e|0;Lu(e,xk()|0,1);return}function xk(){return 1948}function Ak(){Ok();return}function Ok(){var e=0,t=0,n=0,i=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0,v=0,m=0,g=0,y=0,_=0;y=h;h=h+16|0;p=y+4|0;v=y;Ne(65536,10804,o[2702]|0,10812);n=Cw()|0;t=o[n>>2]|0;e=o[t>>2]|0;if(e|0){i=o[n+8>>2]|0;n=o[n+4>>2]|0;while(1){ze(e|0,u[n>>0]|0|0,r[i>>0]|0);t=t+4|0;e=o[t>>2]|0;if(!e)break;else{i=i+1|0;n=n+1|0}}}e=Tw()|0;t=o[e>>2]|0;if(t|0)do{We(t|0,o[e+4>>2]|0);e=e+8|0;t=o[e>>2]|0}while((t|0)!=0);We(Pk()|0,5167);d=fw()|0;e=o[d>>2]|0;e:do{if(e|0){do{Ik(o[e+4>>2]|0);e=o[e>>2]|0}while((e|0)!=0);e=o[d>>2]|0;if(e|0){f=d;do{while(1){a=e;e=o[e>>2]|0;a=o[a+4>>2]|0;if(!(Nk(a)|0))break;o[v>>2]=f;o[p>>2]=o[v>>2];Mk(d,p)|0;if(!e)break e}Rk(a);f=o[f>>2]|0;t=Fk(a)|0;l=Xe()|0;s=h;h=h+((1*(t<<2)|0)+15&-16)|0;c=h;h=h+((1*(t<<2)|0)+15&-16)|0;t=o[(xE(a)|0)>>2]|0;if(t|0){n=s;i=c;while(1){o[n>>2]=o[(kE(o[t+4>>2]|0)|0)>>2];o[i>>2]=o[t+8>>2];t=o[t>>2]|0;if(!t)break;else{n=n+4|0;i=i+4|0}}}_=kE(a)|0;t=Lk(a)|0;n=Fk(a)|0;i=Bk(a)|0;Ge(_|0,t|0,s|0,c|0,n|0,i|0,Ew(a)|0);Re(l|0)}while((e|0)!=0)}}}while(0);e=o[(Sw()|0)>>2]|0;if(e|0)do{_=e+4|0;d=Pw(_)|0;a=Fw(d)|0;l=Iw(d)|0;s=(Nw(d)|0)+1|0;c=jk(d)|0;f=Uk(_)|0;d=Xa(d)|0;p=Bw(_)|0;v=zk(_)|0;Ve(0,a|0,l|0,s|0,c|0,f|0,d|0,p|0,v|0,Wk(_)|0);e=o[e>>2]|0}while((e|0)!=0);e=o[(fw()|0)>>2]|0;e:do{if(e|0){t:while(1){t=o[e+4>>2]|0;if(t|0?(m=o[(kE(t)|0)>>2]|0,g=o[(PE(t)|0)>>2]|0,g|0):0){n=g;do{t=n+4|0;i=Pw(t)|0;n:do{if(i|0)switch(Xa(i)|0){case 0:break t;case 4:case 3:case 2:{c=Fw(i)|0;f=Iw(i)|0;d=(Nw(i)|0)+1|0;p=jk(i)|0;v=Xa(i)|0;_=Bw(t)|0;Ve(m|0,c|0,f|0,d|0,p|0,0,v|0,_|0,zk(t)|0,Wk(t)|0);break n}case 1:{s=Fw(i)|0;c=Iw(i)|0;f=(Nw(i)|0)+1|0;d=jk(i)|0;p=Uk(t)|0;v=Xa(i)|0;_=Bw(t)|0;Ve(m|0,s|0,c|0,f|0,d|0,p|0,v|0,_|0,zk(t)|0,Wk(t)|0);break n}case 5:{d=Fw(i)|0;p=Iw(i)|0;v=(Nw(i)|0)+1|0;_=jk(i)|0;Ve(m|0,d|0,p|0,v|0,_|0,Hk(i)|0,Xa(i)|0,0,0,0);break n}default:break n}}while(0);n=o[n>>2]|0}while((n|0)!=0)}e=o[e>>2]|0;if(!e)break e}Ye()}}while(0);$e();h=y;return}function Pk(){return 11703}function Ik(e){e=e|0;r[e+40>>0]=0;return}function Nk(e){e=e|0;return(r[e+40>>0]|0)!=0|0}function Mk(e,t){e=e|0;t=t|0;t=Vk(t)|0;e=o[t>>2]|0;o[t>>2]=o[e>>2];KT(e);return o[t>>2]|0}function Rk(e){e=e|0;r[e+40>>0]=1;return}function Fk(e){e=e|0;return o[e+20>>2]|0}function Lk(e){e=e|0;return o[e+8>>2]|0}function Bk(e){e=e|0;return o[e+32>>2]|0}function jk(e){e=e|0;return o[e+4>>2]|0}function Uk(e){e=e|0;return o[e+4>>2]|0}function zk(e){e=e|0;return o[e+8>>2]|0}function Wk(e){e=e|0;return o[e+16>>2]|0}function Hk(e){e=e|0;return o[e+20>>2]|0}function Vk(e){e=e|0;return o[e>>2]|0}function qk(e){e=e|0;var t=0,n=0,r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0,v=0,m=0,g=0,y=0,_=0,b=0,w=0,E=0,D=0;D=h;h=h+16|0;p=D;do{if(e>>>0<245){c=e>>>0<11?16:e+11&-8;e=c>>>3;d=o[2783]|0;n=d>>>e;if(n&3|0){t=(n&1^1)+e|0;e=11172+(t<<1<<2)|0;n=e+8|0;r=o[n>>2]|0;i=r+8|0;u=o[i>>2]|0;if((e|0)==(u|0))o[2783]=d&~(1<>2]=e;o[n>>2]=u}E=t<<3;o[r+4>>2]=E|3;E=r+E+4|0;o[E>>2]=o[E>>2]|1;E=i;h=D;return E|0}f=o[2785]|0;if(c>>>0>f>>>0){if(n|0){t=2<>>12&16;t=t>>>a;n=t>>>5&8;t=t>>>n;i=t>>>2&4;t=t>>>i;e=t>>>1&2;t=t>>>e;r=t>>>1&1;r=(n|a|i|e|r)+(t>>>r)|0;t=11172+(r<<1<<2)|0;e=t+8|0;i=o[e>>2]|0;a=i+8|0;n=o[a>>2]|0;if((t|0)==(n|0)){e=d&~(1<>2]=t;o[e>>2]=n;e=d}u=(r<<3)-c|0;o[i+4>>2]=c|3;r=i+c|0;o[r+4>>2]=u|1;o[r+u>>2]=u;if(f|0){i=o[2788]|0;t=f>>>3;n=11172+(t<<1<<2)|0;t=1<>2]|0}o[e>>2]=i;o[t+12>>2]=i;o[i+8>>2]=t;o[i+12>>2]=n}o[2785]=u;o[2788]=r;E=a;h=D;return E|0}l=o[2784]|0;if(l){n=(l&0-l)+-1|0;a=n>>>12&16;n=n>>>a;u=n>>>5&8;n=n>>>u;s=n>>>2&4;n=n>>>s;r=n>>>1&2;n=n>>>r;e=n>>>1&1;e=o[11436+((u|a|s|r|e)+(n>>>e)<<2)>>2]|0;n=(o[e+4>>2]&-8)-c|0;r=o[e+16+(((o[e+16>>2]|0)==0&1)<<2)>>2]|0;if(!r){s=e;u=n}else{do{a=(o[r+4>>2]&-8)-c|0;s=a>>>0>>0;n=s?a:n;e=s?r:e;r=o[r+16+(((o[r+16>>2]|0)==0&1)<<2)>>2]|0}while((r|0)!=0);s=e;u=n}a=s+c|0;if(s>>>0>>0){i=o[s+24>>2]|0;t=o[s+12>>2]|0;do{if((t|0)==(s|0)){e=s+20|0;t=o[e>>2]|0;if(!t){e=s+16|0;t=o[e>>2]|0;if(!t){n=0;break}}while(1){n=t+20|0;r=o[n>>2]|0;if(r|0){t=r;e=n;continue}n=t+16|0;r=o[n>>2]|0;if(!r)break;else{t=r;e=n}}o[e>>2]=0;n=t}else{n=o[s+8>>2]|0;o[n+12>>2]=t;o[t+8>>2]=n;n=t}}while(0);do{if(i|0){t=o[s+28>>2]|0;e=11436+(t<<2)|0;if((s|0)==(o[e>>2]|0)){o[e>>2]=n;if(!n){o[2784]=l&~(1<>2]|0)!=(s|0)&1)<<2)>>2]=n;if(!n)break}o[n+24>>2]=i;t=o[s+16>>2]|0;if(t|0){o[n+16>>2]=t;o[t+24>>2]=n}t=o[s+20>>2]|0;if(t|0){o[n+20>>2]=t;o[t+24>>2]=n}}}while(0);if(u>>>0<16){E=u+c|0;o[s+4>>2]=E|3;E=s+E+4|0;o[E>>2]=o[E>>2]|1}else{o[s+4>>2]=c|3;o[a+4>>2]=u|1;o[a+u>>2]=u;if(f|0){r=o[2788]|0;t=f>>>3;n=11172+(t<<1<<2)|0;t=1<>2]|0}o[e>>2]=r;o[t+12>>2]=r;o[r+8>>2]=t;o[r+12>>2]=n}o[2785]=u;o[2788]=a}E=s+8|0;h=D;return E|0}else d=c}else d=c}else d=c}else if(e>>>0<=4294967231){e=e+11|0;c=e&-8;s=o[2784]|0;if(s){r=0-c|0;e=e>>>8;if(e){if(c>>>0>16777215)l=31;else{d=(e+1048320|0)>>>16&8;w=e<>>16&4;w=w<>>16&2;l=14-(f|d|l)+(w<>>15)|0;l=c>>>(l+7|0)&1|l<<1}}else l=0;n=o[11436+(l<<2)>>2]|0;e:do{if(!n){n=0;e=0;w=57}else{e=0;a=c<<((l|0)==31?0:25-(l>>>1)|0);u=0;while(1){i=(o[n+4>>2]&-8)-c|0;if(i>>>0>>0)if(!i){e=n;r=0;i=n;w=61;break e}else{e=n;r=i}i=o[n+20>>2]|0;n=o[n+16+(a>>>31<<2)>>2]|0;u=(i|0)==0|(i|0)==(n|0)?u:i;i=(n|0)==0;if(i){n=u;w=57;break}else a=a<<((i^1)&1)}}}while(0);if((w|0)==57){if((n|0)==0&(e|0)==0){e=2<>>12&16;d=d>>>a;u=d>>>5&8;d=d>>>u;l=d>>>2&4;d=d>>>l;f=d>>>1&2;d=d>>>f;n=d>>>1&1;e=0;n=o[11436+((u|a|l|f|n)+(d>>>n)<<2)>>2]|0}if(!n){l=e;a=r}else{i=n;w=61}}if((w|0)==61)while(1){w=0;n=(o[i+4>>2]&-8)-c|0;d=n>>>0>>0;n=d?n:r;e=d?i:e;i=o[i+16+(((o[i+16>>2]|0)==0&1)<<2)>>2]|0;if(!i){l=e;a=n;break}else{r=n;w=61}}if((l|0)!=0?a>>>0<((o[2785]|0)-c|0)>>>0:0){u=l+c|0;if(l>>>0>=u>>>0){E=0;h=D;return E|0}i=o[l+24>>2]|0;t=o[l+12>>2]|0;do{if((t|0)==(l|0)){e=l+20|0;t=o[e>>2]|0;if(!t){e=l+16|0;t=o[e>>2]|0;if(!t){t=0;break}}while(1){n=t+20|0;r=o[n>>2]|0;if(r|0){t=r;e=n;continue}n=t+16|0;r=o[n>>2]|0;if(!r)break;else{t=r;e=n}}o[e>>2]=0}else{E=o[l+8>>2]|0;o[E+12>>2]=t;o[t+8>>2]=E}}while(0);do{if(i){e=o[l+28>>2]|0;n=11436+(e<<2)|0;if((l|0)==(o[n>>2]|0)){o[n>>2]=t;if(!t){r=s&~(1<>2]|0)!=(l|0)&1)<<2)>>2]=t;if(!t){r=s;break}}o[t+24>>2]=i;e=o[l+16>>2]|0;if(e|0){o[t+16>>2]=e;o[e+24>>2]=t}e=o[l+20>>2]|0;if(e){o[t+20>>2]=e;o[e+24>>2]=t;r=s}else r=s}else r=s}while(0);do{if(a>>>0>=16){o[l+4>>2]=c|3;o[u+4>>2]=a|1;o[u+a>>2]=a;t=a>>>3;if(a>>>0<256){n=11172+(t<<1<<2)|0;e=o[2783]|0;t=1<>2]|0}o[e>>2]=u;o[t+12>>2]=u;o[u+8>>2]=t;o[u+12>>2]=n;break}t=a>>>8;if(t){if(a>>>0>16777215)t=31;else{w=(t+1048320|0)>>>16&8;E=t<>>16&4;E=E<>>16&2;t=14-(b|w|t)+(E<>>15)|0;t=a>>>(t+7|0)&1|t<<1}}else t=0;n=11436+(t<<2)|0;o[u+28>>2]=t;e=u+16|0;o[e+4>>2]=0;o[e>>2]=0;e=1<>2]=u;o[u+24>>2]=n;o[u+12>>2]=u;o[u+8>>2]=u;break}e=a<<((t|0)==31?0:25-(t>>>1)|0);n=o[n>>2]|0;while(1){if((o[n+4>>2]&-8|0)==(a|0)){w=97;break}r=n+16+(e>>>31<<2)|0;t=o[r>>2]|0;if(!t){w=96;break}else{e=e<<1;n=t}}if((w|0)==96){o[r>>2]=u;o[u+24>>2]=n;o[u+12>>2]=u;o[u+8>>2]=u;break}else if((w|0)==97){w=n+8|0;E=o[w>>2]|0;o[E+12>>2]=u;o[w>>2]=u;o[u+8>>2]=E;o[u+12>>2]=n;o[u+24>>2]=0;break}}else{E=a+c|0;o[l+4>>2]=E|3;E=l+E+4|0;o[E>>2]=o[E>>2]|1}}while(0);E=l+8|0;h=D;return E|0}else d=c}else d=c}else d=-1}while(0);n=o[2785]|0;if(n>>>0>=d>>>0){t=n-d|0;e=o[2788]|0;if(t>>>0>15){E=e+d|0;o[2788]=E;o[2785]=t;o[E+4>>2]=t|1;o[E+t>>2]=t;o[e+4>>2]=d|3}else{o[2785]=0;o[2788]=0;o[e+4>>2]=n|3;E=e+n+4|0;o[E>>2]=o[E>>2]|1}E=e+8|0;h=D;return E|0}a=o[2786]|0;if(a>>>0>d>>>0){b=a-d|0;o[2786]=b;E=o[2789]|0;w=E+d|0;o[2789]=w;o[w+4>>2]=b|1;o[E+4>>2]=d|3;E=E+8|0;h=D;return E|0}if(!(o[2901]|0)){o[2903]=4096;o[2902]=4096;o[2904]=-1;o[2905]=-1;o[2906]=0;o[2894]=0;e=p&-16^1431655768;o[p>>2]=e;o[2901]=e;e=4096}else e=o[2903]|0;l=d+48|0;s=d+47|0;u=e+s|0;i=0-e|0;c=u&i;if(c>>>0<=d>>>0){E=0;h=D;return E|0}e=o[2893]|0;if(e|0?(f=o[2891]|0,p=f+c|0,p>>>0<=f>>>0|p>>>0>e>>>0):0){E=0;h=D;return E|0}e:do{if(!(o[2894]&4)){n=o[2789]|0;t:do{if(n){r=11580;while(1){e=o[r>>2]|0;if(e>>>0<=n>>>0?(g=r+4|0,(e+(o[g>>2]|0)|0)>>>0>n>>>0):0)break;e=o[r+8>>2]|0;if(!e){w=118;break t}else r=e}t=u-a&i;if(t>>>0<2147483647){e=lx(t|0)|0;if((e|0)==((o[r>>2]|0)+(o[g>>2]|0)|0)){if((e|0)!=(-1|0)){a=t;u=e;w=135;break e}}else{r=e;w=126}}else t=0}else w=118}while(0);do{if((w|0)==118){n=lx(0)|0;if((n|0)!=(-1|0)?(t=n,v=o[2902]|0,m=v+-1|0,t=((m&t|0)==0?0:(m+t&0-v)-t|0)+c|0,v=o[2891]|0,m=t+v|0,t>>>0>d>>>0&t>>>0<2147483647):0){g=o[2893]|0;if(g|0?m>>>0<=v>>>0|m>>>0>g>>>0:0){t=0;break}e=lx(t|0)|0;if((e|0)==(n|0)){a=t;u=n;w=135;break e}else{r=e;w=126}}else t=0}}while(0);do{if((w|0)==126){n=0-t|0;if(!(l>>>0>t>>>0&(t>>>0<2147483647&(r|0)!=(-1|0))))if((r|0)==(-1|0)){t=0;break}else{a=t;u=r;w=135;break e}e=o[2903]|0;e=s-t+e&0-e;if(e>>>0>=2147483647){a=t;u=r;w=135;break e}if((lx(e|0)|0)==(-1|0)){lx(n|0)|0;t=0;break}else{a=e+t|0;u=r;w=135;break e}}}while(0);o[2894]=o[2894]|4;w=133}else{t=0;w=133}}while(0);if(((w|0)==133?c>>>0<2147483647:0)?(b=lx(c|0)|0,g=lx(0)|0,y=g-b|0,_=y>>>0>(d+40|0)>>>0,!((b|0)==(-1|0)|_^1|b>>>0>>0&((b|0)!=(-1|0)&(g|0)!=(-1|0))^1)):0){a=_?y:t;u=b;w=135}if((w|0)==135){t=(o[2891]|0)+a|0;o[2891]=t;if(t>>>0>(o[2892]|0)>>>0)o[2892]=t;s=o[2789]|0;do{if(s){t=11580;while(1){e=o[t>>2]|0;n=t+4|0;r=o[n>>2]|0;if((u|0)==(e+r|0)){w=145;break}i=o[t+8>>2]|0;if(!i)break;else t=i}if(((w|0)==145?(o[t+12>>2]&8|0)==0:0)?s>>>0>>0&s>>>0>=e>>>0:0){o[n>>2]=r+a;E=s+8|0;E=(E&7|0)==0?0:0-E&7;w=s+E|0;E=(o[2786]|0)+(a-E)|0;o[2789]=w;o[2786]=E;o[w+4>>2]=E|1;o[w+E+4>>2]=40;o[2790]=o[2905];break}if(u>>>0<(o[2787]|0)>>>0)o[2787]=u;n=u+a|0;t=11580;while(1){if((o[t>>2]|0)==(n|0)){w=153;break}e=o[t+8>>2]|0;if(!e)break;else t=e}if((w|0)==153?(o[t+12>>2]&8|0)==0:0){o[t>>2]=u;f=t+4|0;o[f>>2]=(o[f>>2]|0)+a;f=u+8|0;f=u+((f&7|0)==0?0:0-f&7)|0;t=n+8|0;t=n+((t&7|0)==0?0:0-t&7)|0;c=f+d|0;l=t-f-d|0;o[f+4>>2]=d|3;do{if((t|0)!=(s|0)){if((t|0)==(o[2788]|0)){E=(o[2785]|0)+l|0;o[2785]=E;o[2788]=c;o[c+4>>2]=E|1;o[c+E>>2]=E;break}e=o[t+4>>2]|0;if((e&3|0)==1){a=e&-8;r=e>>>3;e:do{if(e>>>0<256){e=o[t+8>>2]|0;n=o[t+12>>2]|0;if((n|0)==(e|0)){o[2783]=o[2783]&~(1<>2]=n;o[n+8>>2]=e;break}}else{u=o[t+24>>2]|0;e=o[t+12>>2]|0;do{if((e|0)==(t|0)){r=t+16|0;n=r+4|0;e=o[n>>2]|0;if(!e){e=o[r>>2]|0;if(!e){e=0;break}else n=r}while(1){r=e+20|0;i=o[r>>2]|0;if(i|0){e=i;n=r;continue}r=e+16|0;i=o[r>>2]|0;if(!i)break;else{e=i;n=r}}o[n>>2]=0}else{E=o[t+8>>2]|0;o[E+12>>2]=e;o[e+8>>2]=E}}while(0);if(!u)break;n=o[t+28>>2]|0;r=11436+(n<<2)|0;do{if((t|0)!=(o[r>>2]|0)){o[u+16+(((o[u+16>>2]|0)!=(t|0)&1)<<2)>>2]=e;if(!e)break e}else{o[r>>2]=e;if(e|0)break;o[2784]=o[2784]&~(1<>2]=u;n=t+16|0;r=o[n>>2]|0;if(r|0){o[e+16>>2]=r;o[r+24>>2]=e}n=o[n+4>>2]|0;if(!n)break;o[e+20>>2]=n;o[n+24>>2]=e}}while(0);t=t+a|0;i=a+l|0}else i=l;t=t+4|0;o[t>>2]=o[t>>2]&-2;o[c+4>>2]=i|1;o[c+i>>2]=i;t=i>>>3;if(i>>>0<256){n=11172+(t<<1<<2)|0;e=o[2783]|0;t=1<>2]|0}o[e>>2]=c;o[t+12>>2]=c;o[c+8>>2]=t;o[c+12>>2]=n;break}t=i>>>8;do{if(!t)t=0;else{if(i>>>0>16777215){t=31;break}w=(t+1048320|0)>>>16&8;E=t<>>16&4;E=E<>>16&2;t=14-(b|w|t)+(E<>>15)|0;t=i>>>(t+7|0)&1|t<<1}}while(0);r=11436+(t<<2)|0;o[c+28>>2]=t;e=c+16|0;o[e+4>>2]=0;o[e>>2]=0;e=o[2784]|0;n=1<>2]=c;o[c+24>>2]=r;o[c+12>>2]=c;o[c+8>>2]=c;break}e=i<<((t|0)==31?0:25-(t>>>1)|0);n=o[r>>2]|0;while(1){if((o[n+4>>2]&-8|0)==(i|0)){w=194;break}r=n+16+(e>>>31<<2)|0;t=o[r>>2]|0;if(!t){w=193;break}else{e=e<<1;n=t}}if((w|0)==193){o[r>>2]=c;o[c+24>>2]=n;o[c+12>>2]=c;o[c+8>>2]=c;break}else if((w|0)==194){w=n+8|0;E=o[w>>2]|0;o[E+12>>2]=c;o[w>>2]=c;o[c+8>>2]=E;o[c+12>>2]=n;o[c+24>>2]=0;break}}else{E=(o[2786]|0)+l|0;o[2786]=E;o[2789]=c;o[c+4>>2]=E|1}}while(0);E=f+8|0;h=D;return E|0}t=11580;while(1){e=o[t>>2]|0;if(e>>>0<=s>>>0?(E=e+(o[t+4>>2]|0)|0,E>>>0>s>>>0):0)break;t=o[t+8>>2]|0}i=E+-47|0;e=i+8|0;e=i+((e&7|0)==0?0:0-e&7)|0;i=s+16|0;e=e>>>0>>0?s:e;t=e+8|0;n=u+8|0;n=(n&7|0)==0?0:0-n&7;w=u+n|0;n=a+-40-n|0;o[2789]=w;o[2786]=n;o[w+4>>2]=n|1;o[w+n+4>>2]=40;o[2790]=o[2905];n=e+4|0;o[n>>2]=27;o[t>>2]=o[2895];o[t+4>>2]=o[2896];o[t+8>>2]=o[2897];o[t+12>>2]=o[2898];o[2895]=u;o[2896]=a;o[2898]=0;o[2897]=t;t=e+24|0;do{w=t;t=t+4|0;o[t>>2]=7}while((w+8|0)>>>0>>0);if((e|0)!=(s|0)){u=e-s|0;o[n>>2]=o[n>>2]&-2;o[s+4>>2]=u|1;o[e>>2]=u;t=u>>>3;if(u>>>0<256){n=11172+(t<<1<<2)|0;e=o[2783]|0;t=1<>2]|0}o[e>>2]=s;o[t+12>>2]=s;o[s+8>>2]=t;o[s+12>>2]=n;break}t=u>>>8;if(t){if(u>>>0>16777215)n=31;else{w=(t+1048320|0)>>>16&8;E=t<>>16&4;E=E<>>16&2;n=14-(b|w|n)+(E<>>15)|0;n=u>>>(n+7|0)&1|n<<1}}else n=0;r=11436+(n<<2)|0;o[s+28>>2]=n;o[s+20>>2]=0;o[i>>2]=0;t=o[2784]|0;e=1<>2]=s;o[s+24>>2]=r;o[s+12>>2]=s;o[s+8>>2]=s;break}e=u<<((n|0)==31?0:25-(n>>>1)|0);n=o[r>>2]|0;while(1){if((o[n+4>>2]&-8|0)==(u|0)){w=216;break}r=n+16+(e>>>31<<2)|0;t=o[r>>2]|0;if(!t){w=215;break}else{e=e<<1;n=t}}if((w|0)==215){o[r>>2]=s;o[s+24>>2]=n;o[s+12>>2]=s;o[s+8>>2]=s;break}else if((w|0)==216){w=n+8|0;E=o[w>>2]|0;o[E+12>>2]=s;o[w>>2]=s;o[s+8>>2]=E;o[s+12>>2]=n;o[s+24>>2]=0;break}}}else{E=o[2787]|0;if((E|0)==0|u>>>0>>0)o[2787]=u;o[2895]=u;o[2896]=a;o[2898]=0;o[2792]=o[2901];o[2791]=-1;t=0;do{E=11172+(t<<1<<2)|0;o[E+12>>2]=E;o[E+8>>2]=E;t=t+1|0}while((t|0)!=32);E=u+8|0;E=(E&7|0)==0?0:0-E&7;w=u+E|0;E=a+-40-E|0;o[2789]=w;o[2786]=E;o[w+4>>2]=E|1;o[w+E+4>>2]=40;o[2790]=o[2905]}}while(0);t=o[2786]|0;if(t>>>0>d>>>0){b=t-d|0;o[2786]=b;E=o[2789]|0;w=E+d|0;o[2789]=w;o[w+4>>2]=b|1;o[E+4>>2]=d|3;E=E+8|0;h=D;return E|0}}o[(Jk()|0)>>2]=12;E=0;h=D;return E|0}function Gk(e){e=e|0;var t=0,n=0,r=0,i=0,u=0,a=0,l=0,s=0;if(!e)return;n=e+-8|0;i=o[2787]|0;e=o[e+-4>>2]|0;t=e&-8;s=n+t|0;do{if(!(e&1)){r=o[n>>2]|0;if(!(e&3))return;a=n+(0-r)|0;u=r+t|0;if(a>>>0>>0)return;if((a|0)==(o[2788]|0)){e=s+4|0;t=o[e>>2]|0;if((t&3|0)!=3){l=a;t=u;break}o[2785]=u;o[e>>2]=t&-2;o[a+4>>2]=u|1;o[a+u>>2]=u;return}n=r>>>3;if(r>>>0<256){e=o[a+8>>2]|0;t=o[a+12>>2]|0;if((t|0)==(e|0)){o[2783]=o[2783]&~(1<>2]=t;o[t+8>>2]=e;l=a;t=u;break}}i=o[a+24>>2]|0;e=o[a+12>>2]|0;do{if((e|0)==(a|0)){n=a+16|0;t=n+4|0;e=o[t>>2]|0;if(!e){e=o[n>>2]|0;if(!e){e=0;break}else t=n}while(1){n=e+20|0;r=o[n>>2]|0;if(r|0){e=r;t=n;continue}n=e+16|0;r=o[n>>2]|0;if(!r)break;else{e=r;t=n}}o[t>>2]=0}else{l=o[a+8>>2]|0;o[l+12>>2]=e;o[e+8>>2]=l}}while(0);if(i){t=o[a+28>>2]|0;n=11436+(t<<2)|0;if((a|0)==(o[n>>2]|0)){o[n>>2]=e;if(!e){o[2784]=o[2784]&~(1<>2]|0)!=(a|0)&1)<<2)>>2]=e;if(!e){l=a;t=u;break}}o[e+24>>2]=i;t=a+16|0;n=o[t>>2]|0;if(n|0){o[e+16>>2]=n;o[n+24>>2]=e}t=o[t+4>>2]|0;if(t){o[e+20>>2]=t;o[t+24>>2]=e;l=a;t=u}else{l=a;t=u}}else{l=a;t=u}}else{l=n;a=n}}while(0);if(a>>>0>=s>>>0)return;e=s+4|0;r=o[e>>2]|0;if(!(r&1))return;if(!(r&2)){e=o[2788]|0;if((s|0)==(o[2789]|0)){s=(o[2786]|0)+t|0;o[2786]=s;o[2789]=l;o[l+4>>2]=s|1;if((l|0)!=(e|0))return;o[2788]=0;o[2785]=0;return}if((s|0)==(e|0)){s=(o[2785]|0)+t|0;o[2785]=s;o[2788]=a;o[l+4>>2]=s|1;o[a+s>>2]=s;return}i=(r&-8)+t|0;n=r>>>3;do{if(r>>>0<256){t=o[s+8>>2]|0;e=o[s+12>>2]|0;if((e|0)==(t|0)){o[2783]=o[2783]&~(1<>2]=e;o[e+8>>2]=t;break}}else{u=o[s+24>>2]|0;e=o[s+12>>2]|0;do{if((e|0)==(s|0)){n=s+16|0;t=n+4|0;e=o[t>>2]|0;if(!e){e=o[n>>2]|0;if(!e){n=0;break}else t=n}while(1){n=e+20|0;r=o[n>>2]|0;if(r|0){e=r;t=n;continue}n=e+16|0;r=o[n>>2]|0;if(!r)break;else{e=r;t=n}}o[t>>2]=0;n=e}else{n=o[s+8>>2]|0;o[n+12>>2]=e;o[e+8>>2]=n;n=e}}while(0);if(u|0){e=o[s+28>>2]|0;t=11436+(e<<2)|0;if((s|0)==(o[t>>2]|0)){o[t>>2]=n;if(!n){o[2784]=o[2784]&~(1<>2]|0)!=(s|0)&1)<<2)>>2]=n;if(!n)break}o[n+24>>2]=u;e=s+16|0;t=o[e>>2]|0;if(t|0){o[n+16>>2]=t;o[t+24>>2]=n}e=o[e+4>>2]|0;if(e|0){o[n+20>>2]=e;o[e+24>>2]=n}}}}while(0);o[l+4>>2]=i|1;o[a+i>>2]=i;if((l|0)==(o[2788]|0)){o[2785]=i;return}}else{o[e>>2]=r&-2;o[l+4>>2]=t|1;o[a+t>>2]=t;i=t}e=i>>>3;if(i>>>0<256){n=11172+(e<<1<<2)|0;t=o[2783]|0;e=1<>2]|0}o[t>>2]=l;o[e+12>>2]=l;o[l+8>>2]=e;o[l+12>>2]=n;return}e=i>>>8;if(e){if(i>>>0>16777215)e=31;else{a=(e+1048320|0)>>>16&8;s=e<>>16&4;s=s<>>16&2;e=14-(u|a|e)+(s<>>15)|0;e=i>>>(e+7|0)&1|e<<1}}else e=0;r=11436+(e<<2)|0;o[l+28>>2]=e;o[l+20>>2]=0;o[l+16>>2]=0;t=o[2784]|0;n=1<>>1)|0);n=o[r>>2]|0;while(1){if((o[n+4>>2]&-8|0)==(i|0)){e=73;break}r=n+16+(t>>>31<<2)|0;e=o[r>>2]|0;if(!e){e=72;break}else{t=t<<1;n=e}}if((e|0)==72){o[r>>2]=l;o[l+24>>2]=n;o[l+12>>2]=l;o[l+8>>2]=l;break}else if((e|0)==73){a=n+8|0;s=o[a>>2]|0;o[s+12>>2]=l;o[a>>2]=l;o[l+8>>2]=s;o[l+12>>2]=n;o[l+24>>2]=0;break}}else{o[2784]=t|n;o[r>>2]=l;o[l+24>>2]=r;o[l+12>>2]=l;o[l+8>>2]=l}}while(0);s=(o[2791]|0)+-1|0;o[2791]=s;if(!s)e=11588;else return;while(1){e=o[e>>2]|0;if(!e)break;else e=e+8|0}o[2791]=-1;return}function $k(){return 11628}function Yk(e){e=e|0;var t=0,n=0;t=h;h=h+16|0;n=t;o[n>>2]=tT(o[e+60>>2]|0)|0;e=Qk(ut(6,n|0)|0)|0;h=t;return e|0}function Kk(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0,v=0;d=h;h=h+48|0;c=d+16|0;u=d;i=d+32|0;l=e+28|0;r=o[l>>2]|0;o[i>>2]=r;s=e+20|0;r=(o[s>>2]|0)-r|0;o[i+4>>2]=r;o[i+8>>2]=t;o[i+12>>2]=n;r=r+n|0;a=e+60|0;o[u>>2]=o[a>>2];o[u+4>>2]=i;o[u+8>>2]=2;u=Qk(st(146,u|0)|0)|0;e:do{if((r|0)!=(u|0)){t=2;while(1){if((u|0)<0)break;r=r-u|0;v=o[i+4>>2]|0;p=u>>>0>v>>>0;i=p?i+8|0:i;t=(p<<31>>31)+t|0;v=u-(p?v:0)|0;o[i>>2]=(o[i>>2]|0)+v;p=i+4|0;o[p>>2]=(o[p>>2]|0)-v;o[c>>2]=o[a>>2];o[c+4>>2]=i;o[c+8>>2]=t;u=Qk(st(146,c|0)|0)|0;if((r|0)==(u|0)){f=3;break e}}o[e+16>>2]=0;o[l>>2]=0;o[s>>2]=0;o[e>>2]=o[e>>2]|32;if((t|0)==2)n=0;else n=n-(o[i+4>>2]|0)|0}else f=3}while(0);if((f|0)==3){v=o[e+44>>2]|0;o[e+16>>2]=v+(o[e+48>>2]|0);o[l>>2]=v;o[s>>2]=v}h=d;return n|0}function Xk(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0;i=h;h=h+32|0;u=i;r=i+20|0;o[u>>2]=o[e+60>>2];o[u+4>>2]=0;o[u+8>>2]=t;o[u+12>>2]=r;o[u+16>>2]=n;if((Qk(lt(140,u|0)|0)|0)<0){o[r>>2]=-1;e=-1}else e=o[r>>2]|0;h=i;return e|0}function Qk(e){e=e|0;if(e>>>0>4294963200){o[(Jk()|0)>>2]=0-e;e=-1}return e|0}function Jk(){return(Zk()|0)+64|0}function Zk(){return eT()|0}function eT(){return 2084}function tT(e){e=e|0;return e|0}function nT(e,t,n){e=e|0;t=t|0;n=n|0;var i=0,u=0;u=h;h=h+32|0;i=u;o[e+36>>2]=1;if((o[e>>2]&64|0)==0?(o[i>>2]=o[e+60>>2],o[i+4>>2]=21523,o[i+8>>2]=u+16,Qe(54,i|0)|0):0)r[e+75>>0]=-1;i=Kk(e,t,n)|0;h=u;return i|0}function rT(e,t){e=e|0;t=t|0;var n=0,i=0;n=r[e>>0]|0;i=r[t>>0]|0;if(n<<24>>24==0?1:n<<24>>24!=i<<24>>24)e=i;else{do{e=e+1|0;t=t+1|0;n=r[e>>0]|0;i=r[t>>0]|0}while(!(n<<24>>24==0?1:n<<24>>24!=i<<24>>24));e=i}return(n&255)-(e&255)|0}function iT(e,t,n){e=e|0;t=t|0;n=n|0;var i=0,o=0;e:do{if(!n)e=0;else{while(1){i=r[e>>0]|0;o=r[t>>0]|0;if(i<<24>>24!=o<<24>>24)break;n=n+-1|0;if(!n){e=0;break e}else{e=e+1|0;t=t+1|0}}e=(i&255)-(o&255)|0}}while(0);return e|0}function oT(e,t,n){e=e|0;t=t|0;n=n|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0,v=0,m=0,g=0,y=0;y=h;h=h+224|0;d=y+120|0;p=y+80|0;m=y;g=y+136|0;i=p;u=i+40|0;do{o[i>>2]=0;i=i+4|0}while((i|0)<(u|0));o[d>>2]=o[n>>2];if((uT(0,t,d,m,p)|0)<0)n=-1;else{if((o[e+76>>2]|0)>-1)v=aT(e)|0;else v=0;n=o[e>>2]|0;f=n&32;if((r[e+74>>0]|0)<1)o[e>>2]=n&-33;i=e+48|0;if(!(o[i>>2]|0)){u=e+44|0;a=o[u>>2]|0;o[u>>2]=g;l=e+28|0;o[l>>2]=g;s=e+20|0;o[s>>2]=g;o[i>>2]=80;c=e+16|0;o[c>>2]=g+80;n=uT(e,t,d,m,p)|0;if(a){_A[o[e+36>>2]&7](e,0,0)|0;n=(o[s>>2]|0)==0?-1:n;o[u>>2]=a;o[i>>2]=0;o[c>>2]=0;o[l>>2]=0;o[s>>2]=0}}else n=uT(e,t,d,m,p)|0;i=o[e>>2]|0;o[e>>2]=i|f;if(v|0)lT(e);n=(i&32|0)==0?n:-1}h=y;return n|0}function uT(e,t,n,u,a){e=e|0;t=t|0;n=n|0;u=u|0;a=a|0;var l=0,s=0,f=0,d=0,p=0,v=0,m=0,g=0,y=0,_=0,b=0,w=0,E=0,D=0,S=0,C=0,k=0,T=0,x=0,O=0,P=0,I=0,N=0;N=h;h=h+64|0;x=N+16|0;O=N;k=N+24|0;P=N+8|0;I=N+20|0;o[x>>2]=t;D=(e|0)!=0;S=k+40|0;C=S;k=k+39|0;T=P+4|0;s=0;l=0;v=0;e:while(1){do{if((l|0)>-1)if((s|0)>(2147483647-l|0)){o[(Jk()|0)>>2]=75;l=-1;break}else{l=s+l|0;break}}while(0);s=r[t>>0]|0;if(!(s<<24>>24)){E=87;break}else f=t;t:while(1){switch(s<<24>>24){case 37:{s=f;E=9;break t}case 0:{s=f;break t}default:{}}w=f+1|0;o[x>>2]=w;s=r[w>>0]|0;f=w}t:do{if((E|0)==9)while(1){E=0;if((r[f+1>>0]|0)!=37)break t;s=s+1|0;f=f+2|0;o[x>>2]=f;if((r[f>>0]|0)==37)E=9;else break}}while(0);s=s-t|0;if(D)sT(e,t,s);if(s|0){t=f;continue}d=f+1|0;s=(r[d>>0]|0)+-48|0;if(s>>>0<10){w=(r[f+2>>0]|0)==36;b=w?s:-1;v=w?1:v;d=w?f+3|0:d}else b=-1;o[x>>2]=d;s=r[d>>0]|0;f=(s<<24>>24)+-32|0;t:do{if(f>>>0<32){p=0;m=s;while(1){s=1<>2]=d;s=r[d>>0]|0;f=(s<<24>>24)+-32|0;if(f>>>0>=32)break;else m=s}}else p=0}while(0);if(s<<24>>24==42){f=d+1|0;s=(r[f>>0]|0)+-48|0;if(s>>>0<10?(r[d+2>>0]|0)==36:0){o[a+(s<<2)>>2]=10;s=o[u+((r[f>>0]|0)+-48<<3)>>2]|0;v=1;d=d+3|0}else{if(v|0){l=-1;break}if(D){v=(o[n>>2]|0)+(4-1)&~(4-1);s=o[v>>2]|0;o[n>>2]=v+4;v=0;d=f}else{s=0;v=0;d=f}}o[x>>2]=d;w=(s|0)<0;s=w?0-s|0:s;p=w?p|8192:p}else{s=cT(x)|0;if((s|0)<0){l=-1;break}d=o[x>>2]|0}do{if((r[d>>0]|0)==46){if((r[d+1>>0]|0)!=42){o[x>>2]=d+1;f=cT(x)|0;d=o[x>>2]|0;break}m=d+2|0;f=(r[m>>0]|0)+-48|0;if(f>>>0<10?(r[d+3>>0]|0)==36:0){o[a+(f<<2)>>2]=10;f=o[u+((r[m>>0]|0)+-48<<3)>>2]|0;d=d+4|0;o[x>>2]=d;break}if(v|0){l=-1;break e}if(D){w=(o[n>>2]|0)+(4-1)&~(4-1);f=o[w>>2]|0;o[n>>2]=w+4}else f=0;o[x>>2]=m;d=m}else f=-1}while(0);_=0;while(1){if(((r[d>>0]|0)+-65|0)>>>0>57){l=-1;break e}w=d+1|0;o[x>>2]=w;m=r[(r[d>>0]|0)+-65+(5178+(_*58|0))>>0]|0;g=m&255;if((g+-1|0)>>>0<8){_=g;d=w}else break}if(!(m<<24>>24)){l=-1;break}y=(b|0)>-1;do{if(m<<24>>24==19){if(y){l=-1;break e}else E=49}else{if(y){o[a+(b<<2)>>2]=g;y=u+(b<<3)|0;b=o[y+4>>2]|0;E=O;o[E>>2]=o[y>>2];o[E+4>>2]=b;E=49;break}if(!D){l=0;break e}fT(O,g,n)}}while(0);if((E|0)==49?(E=0,!D):0){s=0;t=w;continue}d=r[d>>0]|0;d=(_|0)!=0&(d&15|0)==3?d&-33:d;y=p&-65537;b=(p&8192|0)==0?p:y;t:do{switch(d|0){case 110:switch((_&255)<<24>>24){case 0:{o[o[O>>2]>>2]=l;s=0;t=w;continue e}case 1:{o[o[O>>2]>>2]=l;s=0;t=w;continue e}case 2:{s=o[O>>2]|0;o[s>>2]=l;o[s+4>>2]=((l|0)<0)<<31>>31;s=0;t=w;continue e}case 3:{i[o[O>>2]>>1]=l;s=0;t=w;continue e}case 4:{r[o[O>>2]>>0]=l;s=0;t=w;continue e}case 6:{o[o[O>>2]>>2]=l;s=0;t=w;continue e}case 7:{s=o[O>>2]|0;o[s>>2]=l;o[s+4>>2]=((l|0)<0)<<31>>31;s=0;t=w;continue e}default:{s=0;t=w;continue e}}case 112:{d=120;f=f>>>0>8?f:8;t=b|8;E=61;break}case 88:case 120:{t=b;E=61;break}case 111:{d=O;t=o[d>>2]|0;d=o[d+4>>2]|0;g=pT(t,d,S)|0;y=C-g|0;p=0;m=5642;f=(b&8|0)==0|(f|0)>(y|0)?f:y+1|0;y=b;E=67;break}case 105:case 100:{d=O;t=o[d>>2]|0;d=o[d+4>>2]|0;if((d|0)<0){t=ZT(0,0,t|0,d|0)|0;d=A;p=O;o[p>>2]=t;o[p+4>>2]=d;p=1;m=5642;E=66;break t}else{p=(b&2049|0)!=0&1;m=(b&2048|0)==0?(b&1|0)==0?5642:5644:5643;E=66;break t}}case 117:{d=O;p=0;m=5642;t=o[d>>2]|0;d=o[d+4>>2]|0;E=66;break}case 99:{r[k>>0]=o[O>>2];t=k;p=0;m=5642;g=S;d=1;f=y;break}case 109:{d=vT(o[(Jk()|0)>>2]|0)|0;E=71;break}case 115:{d=o[O>>2]|0;d=d|0?d:5652;E=71;break}case 67:{o[P>>2]=o[O>>2];o[T>>2]=0;o[O>>2]=P;g=-1;d=P;E=75;break}case 83:{t=o[O>>2]|0;if(!f){gT(e,32,s,0,b);t=0;E=84}else{g=f;d=t;E=75}break}case 65:case 71:case 70:case 69:case 97:case 103:case 102:case 101:{s=_T(e,+c[O>>3],s,f,b,d)|0;t=w;continue e}default:{p=0;m=5642;g=S;d=f;f=b}}}while(0);t:do{if((E|0)==61){b=O;_=o[b>>2]|0;b=o[b+4>>2]|0;g=dT(_,b,S,d&32)|0;m=(t&8|0)==0|(_|0)==0&(b|0)==0;p=m?0:2;m=m?5642:5642+(d>>4)|0;y=t;t=_;d=b;E=67}else if((E|0)==66){g=hT(t,d,S)|0;y=b;E=67}else if((E|0)==71){E=0;b=mT(d,0,f)|0;_=(b|0)==0;t=d;p=0;m=5642;g=_?d+f|0:b;d=_?f:b-d|0;f=y}else if((E|0)==75){E=0;m=d;t=0;f=0;while(1){p=o[m>>2]|0;if(!p)break;f=yT(I,p)|0;if((f|0)<0|f>>>0>(g-t|0)>>>0)break;t=f+t|0;if(g>>>0>t>>>0)m=m+4|0;else break}if((f|0)<0){l=-1;break e}gT(e,32,s,t,b);if(!t){t=0;E=84}else{p=0;while(1){f=o[d>>2]|0;if(!f){E=84;break t}f=yT(I,f)|0;p=f+p|0;if((p|0)>(t|0)){E=84;break t}sT(e,I,f);if(p>>>0>=t>>>0){E=84;break}else d=d+4|0}}}}while(0);if((E|0)==67){E=0;d=(t|0)!=0|(d|0)!=0;b=(f|0)!=0|d;d=((d^1)&1)+(C-g)|0;t=b?g:S;g=S;d=b?(f|0)>(d|0)?f:d:f;f=(f|0)>-1?y&-65537:y}else if((E|0)==84){E=0;gT(e,32,s,t,b^8192);s=(s|0)>(t|0)?s:t;t=w;continue}_=g-t|0;y=(d|0)<(_|0)?_:d;b=y+p|0;s=(s|0)<(b|0)?b:s;gT(e,32,s,b,f);sT(e,m,p);gT(e,48,s,b,f^65536);gT(e,48,y,_,0);sT(e,t,_);gT(e,32,s,b,f^8192);t=w}e:do{if((E|0)==87)if(!e)if(!v)l=0;else{l=1;while(1){t=o[a+(l<<2)>>2]|0;if(!t)break;fT(u+(l<<3)|0,t,n);l=l+1|0;if((l|0)>=10){l=1;break e}}while(1){if(o[a+(l<<2)>>2]|0){l=-1;break e}l=l+1|0;if((l|0)>=10){l=1;break}}}}while(0);h=N;return l|0}function aT(e){e=e|0;return 0}function lT(e){e=e|0;return}function sT(e,t,n){e=e|0;t=t|0;n=n|0;if(!(o[e>>2]&32))PT(t,n,e)|0;return}function cT(e){e=e|0;var t=0,n=0,i=0;n=o[e>>2]|0;i=(r[n>>0]|0)+-48|0;if(i>>>0<10){t=0;do{t=i+(t*10|0)|0;n=n+1|0;o[e>>2]=n;i=(r[n>>0]|0)+-48|0}while(i>>>0<10)}else t=0;return t|0}function fT(e,t,n){e=e|0;t=t|0;n=n|0;var r=0,i=0,u=0.0;e:do{if(t>>>0<=20)do{switch(t|0){case 9:{r=(o[n>>2]|0)+(4-1)&~(4-1);t=o[r>>2]|0;o[n>>2]=r+4;o[e>>2]=t;break e}case 10:{r=(o[n>>2]|0)+(4-1)&~(4-1);t=o[r>>2]|0;o[n>>2]=r+4;r=e;o[r>>2]=t;o[r+4>>2]=((t|0)<0)<<31>>31;break e}case 11:{r=(o[n>>2]|0)+(4-1)&~(4-1);t=o[r>>2]|0;o[n>>2]=r+4;r=e;o[r>>2]=t;o[r+4>>2]=0;break e}case 12:{r=(o[n>>2]|0)+(8-1)&~(8-1);t=r;i=o[t>>2]|0;t=o[t+4>>2]|0;o[n>>2]=r+8;r=e;o[r>>2]=i;o[r+4>>2]=t;break e}case 13:{i=(o[n>>2]|0)+(4-1)&~(4-1);r=o[i>>2]|0;o[n>>2]=i+4;r=(r&65535)<<16>>16;i=e;o[i>>2]=r;o[i+4>>2]=((r|0)<0)<<31>>31;break e}case 14:{i=(o[n>>2]|0)+(4-1)&~(4-1);r=o[i>>2]|0;o[n>>2]=i+4;i=e;o[i>>2]=r&65535;o[i+4>>2]=0;break e}case 15:{i=(o[n>>2]|0)+(4-1)&~(4-1);r=o[i>>2]|0;o[n>>2]=i+4;r=(r&255)<<24>>24;i=e;o[i>>2]=r;o[i+4>>2]=((r|0)<0)<<31>>31;break e}case 16:{i=(o[n>>2]|0)+(4-1)&~(4-1);r=o[i>>2]|0;o[n>>2]=i+4;i=e;o[i>>2]=r&255;o[i+4>>2]=0;break e}case 17:{i=(o[n>>2]|0)+(8-1)&~(8-1);u=+c[i>>3];o[n>>2]=i+8;c[e>>3]=u;break e}case 18:{i=(o[n>>2]|0)+(8-1)&~(8-1);u=+c[i>>3];o[n>>2]=i+8;c[e>>3]=u;break e}default:break e}}while(0)}while(0);return}function dT(e,t,n,i){e=e|0;t=t|0;n=n|0;i=i|0;if(!((e|0)==0&(t|0)==0))do{n=n+-1|0;r[n>>0]=u[5694+(e&15)>>0]|0|i;e=rx(e|0,t|0,4)|0;t=A}while(!((e|0)==0&(t|0)==0));return n|0}function pT(e,t,n){e=e|0;t=t|0;n=n|0;if(!((e|0)==0&(t|0)==0))do{n=n+-1|0;r[n>>0]=e&7|48;e=rx(e|0,t|0,3)|0;t=A}while(!((e|0)==0&(t|0)==0));return n|0}function hT(e,t,n){e=e|0;t=t|0;n=n|0;var i=0;if(t>>>0>0|(t|0)==0&e>>>0>4294967295){while(1){i=cx(e|0,t|0,10,0)|0;n=n+-1|0;r[n>>0]=i&255|48;i=e;e=ax(e|0,t|0,10,0)|0;if(!(t>>>0>9|(t|0)==9&i>>>0>4294967295))break;else t=A}t=e}else t=e;if(t)while(1){n=n+-1|0;r[n>>0]=(t>>>0)%10|0|48;if(t>>>0<10)break;else t=(t>>>0)/10|0}return n|0}function vT(e){e=e|0;return kT(e,o[(CT()|0)+188>>2]|0)|0}function mT(e,t,n){e=e|0;t=t|0;n=n|0;var i=0,u=0,a=0,l=0;a=t&255;i=(n|0)!=0;e:do{if(i&(e&3|0)!=0){u=t&255;while(1){if((r[e>>0]|0)==u<<24>>24){l=6;break e}e=e+1|0;n=n+-1|0;i=(n|0)!=0;if(!(i&(e&3|0)!=0)){l=5;break}}}else l=5}while(0);if((l|0)==5)if(i)l=6;else n=0;e:do{if((l|0)==6){u=t&255;if((r[e>>0]|0)!=u<<24>>24){i=V(a,16843009)|0;t:do{if(n>>>0>3)while(1){a=o[e>>2]^i;if((a&-2139062144^-2139062144)&a+-16843009|0)break;e=e+4|0;n=n+-4|0;if(n>>>0<=3){l=11;break t}}else l=11}while(0);if((l|0)==11)if(!n){n=0;break}while(1){if((r[e>>0]|0)==u<<24>>24)break e;e=e+1|0;n=n+-1|0;if(!n){n=0;break}}}}}while(0);return(n|0?e:0)|0}function gT(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;var o=0,u=0;u=h;h=h+256|0;o=u;if((n|0)>(r|0)&(i&73728|0)==0){i=n-r|0;tx(o|0,t|0,(i>>>0<256?i:256)|0)|0;if(i>>>0>255){t=n-r|0;do{sT(e,o,256);i=i+-256|0}while(i>>>0>255);i=t&255}sT(e,o,i)}h=u;return}function yT(e,t){e=e|0;t=t|0;if(!e)e=0;else e=DT(e,t,0)|0;return e|0}function _T(e,t,n,i,a,l){e=e|0;t=+t;n=n|0;i=i|0;a=a|0;l=l|0;var s=0,c=0,f=0,d=0,p=0,v=0,m=0,g=0.0,y=0,_=0,b=0,w=0,E=0,D=0,S=0,C=0,k=0,T=0,x=0,O=0,P=0,I=0,N=0;N=h;h=h+560|0;f=N+8|0;b=N;I=N+524|0;P=I;d=N+512|0;o[b>>2]=0;O=d+12|0;bT(t)|0;if((A|0)<0){t=-t;T=1;k=5659}else{T=(a&2049|0)!=0&1;k=(a&2048|0)==0?(a&1|0)==0?5660:5665:5662}bT(t)|0;x=A&2146435072;do{if(x>>>0<2146435072|(x|0)==2146435072&0<0){g=+wT(t,b)*2.0;s=g!=0.0;if(s)o[b>>2]=(o[b>>2]|0)+-1;E=l|32;if((E|0)==97){y=l&32;m=(y|0)==0?k:k+9|0;v=T|2;s=12-i|0;do{if(!(i>>>0>11|(s|0)==0)){t=8.0;do{s=s+-1|0;t=t*16.0}while((s|0)!=0);if((r[m>>0]|0)==45){t=-(t+(-g-t));break}else{t=g+t-t;break}}else t=g}while(0);c=o[b>>2]|0;s=(c|0)<0?0-c|0:c;s=hT(s,((s|0)<0)<<31>>31,O)|0;if((s|0)==(O|0)){s=d+11|0;r[s>>0]=48}r[s+-1>>0]=(c>>31&2)+43;p=s+-2|0;r[p>>0]=l+15;d=(i|0)<1;f=(a&8|0)==0;s=I;do{x=~~t;c=s+1|0;r[s>>0]=u[5694+x>>0]|y;t=(t-+(x|0))*16.0;if((c-P|0)==1?!(f&(d&t==0.0)):0){r[c>>0]=46;s=s+2|0}else s=c}while(t!=0.0);x=s-P|0;P=O-p|0;O=(i|0)!=0&(x+-2|0)<(i|0)?i+2|0:x;s=P+v+O|0;gT(e,32,n,s,a);sT(e,m,v);gT(e,48,n,s,a^65536);sT(e,I,x);gT(e,48,O-x|0,0,0);sT(e,p,P);gT(e,32,n,s,a^8192);break}c=(i|0)<0?6:i;if(s){s=(o[b>>2]|0)+-28|0;o[b>>2]=s;t=g*268435456.0}else{t=g;s=o[b>>2]|0}x=(s|0)<0?f:f+288|0;f=x;do{S=~~t>>>0;o[f>>2]=S;f=f+4|0;t=(t-+(S>>>0))*1.0e9}while(t!=0.0);if((s|0)>0){d=x;v=f;while(1){p=(s|0)<29?s:29;s=v+-4|0;if(s>>>0>=d>>>0){f=0;do{D=nx(o[s>>2]|0,0,p|0)|0;D=ex(D|0,A|0,f|0,0)|0;S=A;w=cx(D|0,S|0,1e9,0)|0;o[s>>2]=w;f=ax(D|0,S|0,1e9,0)|0;s=s+-4|0}while(s>>>0>=d>>>0);if(f){d=d+-4|0;o[d>>2]=f}}f=v;while(1){if(f>>>0<=d>>>0)break;s=f+-4|0;if(!(o[s>>2]|0))f=s;else break}s=(o[b>>2]|0)-p|0;o[b>>2]=s;if((s|0)>0)v=f;else break}}else d=x;if((s|0)<0){i=((c+25|0)/9|0)+1|0;_=(E|0)==102;do{y=0-s|0;y=(y|0)<9?y:9;if(d>>>0>>0){p=(1<>>y;m=0;s=d;do{S=o[s>>2]|0;o[s>>2]=(S>>>y)+m;m=V(S&p,v)|0;s=s+4|0}while(s>>>0>>0);s=(o[d>>2]|0)==0?d+4|0:d;if(!m){d=s;s=f}else{o[f>>2]=m;d=s;s=f+4|0}}else{d=(o[d>>2]|0)==0?d+4|0:d;s=f}f=_?x:d;f=(s-f>>2|0)>(i|0)?f+(i<<2)|0:s;s=(o[b>>2]|0)+y|0;o[b>>2]=s}while((s|0)<0);s=d;i=f}else{s=d;i=f}S=x;if(s>>>0>>0){f=(S-s>>2)*9|0;p=o[s>>2]|0;if(p>>>0>=10){d=10;do{d=d*10|0;f=f+1|0}while(p>>>0>=d>>>0)}}else f=0;_=(E|0)==103;w=(c|0)!=0;d=c-((E|0)!=102?f:0)+((w&_)<<31>>31)|0;if((d|0)<(((i-S>>2)*9|0)+-9|0)){d=d+9216|0;y=x+4+(((d|0)/9|0)+-1024<<2)|0;d=((d|0)%9|0)+1|0;if((d|0)<9){p=10;do{p=p*10|0;d=d+1|0}while((d|0)!=9)}else p=10;v=o[y>>2]|0;m=(v>>>0)%(p>>>0)|0;d=(y+4|0)==(i|0);if(!(d&(m|0)==0)){g=(((v>>>0)/(p>>>0)|0)&1|0)==0?9007199254740992.0:9007199254740994.0;D=(p|0)/2|0;t=m>>>0>>0?.5:d&(m|0)==(D|0)?1.0:1.5;if(T){D=(r[k>>0]|0)==45;t=D?-t:t;g=D?-g:g}d=v-m|0;o[y>>2]=d;if(g+t!=g){D=d+p|0;o[y>>2]=D;if(D>>>0>999999999){f=y;while(1){d=f+-4|0;o[f>>2]=0;if(d>>>0>>0){s=s+-4|0;o[s>>2]=0}D=(o[d>>2]|0)+1|0;o[d>>2]=D;if(D>>>0>999999999)f=d;else break}}else d=y;f=(S-s>>2)*9|0;v=o[s>>2]|0;if(v>>>0>=10){p=10;do{p=p*10|0;f=f+1|0}while(v>>>0>=p>>>0)}}else d=y}else d=y;d=d+4|0;d=i>>>0>d>>>0?d:i;D=s}else{d=i;D=s}E=d;while(1){if(E>>>0<=D>>>0){b=0;break}s=E+-4|0;if(!(o[s>>2]|0))E=s;else{b=1;break}}i=0-f|0;do{if(_){s=((w^1)&1)+c|0;if((s|0)>(f|0)&(f|0)>-5){p=l+-1|0;c=s+-1-f|0}else{p=l+-2|0;c=s+-1|0}s=a&8;if(!s){if(b?(C=o[E+-4>>2]|0,(C|0)!=0):0){if(!((C>>>0)%10|0)){d=0;s=10;do{s=s*10|0;d=d+1|0}while(!((C>>>0)%(s>>>0)|0|0))}else d=0}else d=9;s=((E-S>>2)*9|0)+-9|0;if((p|32|0)==102){y=s-d|0;y=(y|0)>0?y:0;c=(c|0)<(y|0)?c:y;y=0;break}else{y=s+f-d|0;y=(y|0)>0?y:0;c=(c|0)<(y|0)?c:y;y=0;break}}else y=s}else{p=l;y=a&8}}while(0);_=c|y;v=(_|0)!=0&1;m=(p|32|0)==102;if(m){w=0;s=(f|0)>0?f:0}else{s=(f|0)<0?i:f;s=hT(s,((s|0)<0)<<31>>31,O)|0;d=O;if((d-s|0)<2)do{s=s+-1|0;r[s>>0]=48}while((d-s|0)<2);r[s+-1>>0]=(f>>31&2)+43;s=s+-2|0;r[s>>0]=p;w=s;s=d-s|0}s=T+1+c+v+s|0;gT(e,32,n,s,a);sT(e,k,T);gT(e,48,n,s,a^65536);if(m){p=D>>>0>x>>>0?x:D;y=I+9|0;v=y;m=I+8|0;d=p;do{f=hT(o[d>>2]|0,0,y)|0;if((d|0)==(p|0)){if((f|0)==(y|0)){r[m>>0]=48;f=m}}else if(f>>>0>I>>>0){tx(I|0,48,f-P|0)|0;do{f=f+-1|0}while(f>>>0>I>>>0)}sT(e,f,v-f|0);d=d+4|0}while(d>>>0<=x>>>0);if(_|0)sT(e,5710,1);if(d>>>0>>0&(c|0)>0)while(1){f=hT(o[d>>2]|0,0,y)|0;if(f>>>0>I>>>0){tx(I|0,48,f-P|0)|0;do{f=f+-1|0}while(f>>>0>I>>>0)}sT(e,f,(c|0)<9?c:9);d=d+4|0;f=c+-9|0;if(!(d>>>0>>0&(c|0)>9)){c=f;break}else c=f}gT(e,48,c+9|0,9,0)}else{_=b?E:D+4|0;if((c|0)>-1){b=I+9|0;y=(y|0)==0;i=b;v=0-P|0;m=I+8|0;p=D;do{f=hT(o[p>>2]|0,0,b)|0;if((f|0)==(b|0)){r[m>>0]=48;f=m}do{if((p|0)==(D|0)){d=f+1|0;sT(e,f,1);if(y&(c|0)<1){f=d;break}sT(e,5710,1);f=d}else{if(f>>>0<=I>>>0)break;tx(I|0,48,f+v|0)|0;do{f=f+-1|0}while(f>>>0>I>>>0)}}while(0);P=i-f|0;sT(e,f,(c|0)>(P|0)?P:c);c=c-P|0;p=p+4|0}while(p>>>0<_>>>0&(c|0)>-1)}gT(e,48,c+18|0,18,0);sT(e,w,O-w|0)}gT(e,32,n,s,a^8192)}else{I=(l&32|0)!=0;s=T+3|0;gT(e,32,n,s,a&-65537);sT(e,k,T);sT(e,t!=t|0.0!=0.0?I?5686:5690:I?5678:5682,3);gT(e,32,n,s,a^8192)}}while(0);h=N;return((s|0)<(n|0)?n:s)|0}function bT(e){e=+e;var t=0;c[d>>3]=e;t=o[d>>2]|0;A=o[d+4>>2]|0;return t|0}function wT(e,t){e=+e;t=t|0;return+ +ET(e,t)}function ET(e,t){e=+e;t=t|0;var n=0,r=0,i=0;c[d>>3]=e;n=o[d>>2]|0;r=o[d+4>>2]|0;i=rx(n|0,r|0,52)|0;switch(i&2047){case 0:{if(e!=0.0){e=+ET(e*18446744073709551616.0,t);n=(o[t>>2]|0)+-64|0}else n=0;o[t>>2]=n;break}case 2047:break;default:{o[t>>2]=(i&2047)+-1022;o[d>>2]=n;o[d+4>>2]=r&-2146435073|1071644672;e=+c[d>>3]}}return+e}function DT(e,t,n){e=e|0;t=t|0;n=n|0;do{if(e){if(t>>>0<128){r[e>>0]=t;e=1;break}if(!(o[o[(ST()|0)+188>>2]>>2]|0))if((t&-128|0)==57216){r[e>>0]=t;e=1;break}else{o[(Jk()|0)>>2]=84;e=-1;break}if(t>>>0<2048){r[e>>0]=t>>>6|192;r[e+1>>0]=t&63|128;e=2;break}if(t>>>0<55296|(t&-8192|0)==57344){r[e>>0]=t>>>12|224;r[e+1>>0]=t>>>6&63|128;r[e+2>>0]=t&63|128;e=3;break}if((t+-65536|0)>>>0<1048576){r[e>>0]=t>>>18|240;r[e+1>>0]=t>>>12&63|128;r[e+2>>0]=t>>>6&63|128;r[e+3>>0]=t&63|128;e=4;break}else{o[(Jk()|0)>>2]=84;e=-1;break}}else e=1}while(0);return e|0}function ST(){return eT()|0}function CT(){return eT()|0}function kT(e,t){e=e|0;t=t|0;var n=0,i=0;i=0;while(1){if((u[5712+i>>0]|0)==(e|0)){e=2;break}n=i+1|0;if((n|0)==87){n=5800;i=87;e=5;break}else i=n}if((e|0)==2)if(!i)n=5800;else{n=5800;e=5}if((e|0)==5)while(1){do{e=n;n=n+1|0}while((r[e>>0]|0)!=0);i=i+-1|0;if(!i)break;else e=5}return TT(n,o[t+20>>2]|0)|0}function TT(e,t){e=e|0;t=t|0;return xT(e,t)|0}function xT(e,t){e=e|0;t=t|0;if(!t)t=0;else t=AT(o[t>>2]|0,o[t+4>>2]|0,e)|0;return(t|0?t:e)|0}function AT(e,t,n){e=e|0;t=t|0;n=n|0;var i=0,u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0,h=0;h=(o[e>>2]|0)+1794895138|0;a=OT(o[e+8>>2]|0,h)|0;i=OT(o[e+12>>2]|0,h)|0;u=OT(o[e+16>>2]|0,h)|0;e:do{if((a>>>0>>2>>>0?(p=t-(a<<2)|0,i>>>0

>>0&u>>>0

>>0):0)?((u|i)&3|0)==0:0){p=i>>>2;d=u>>>2;f=0;while(1){s=a>>>1;c=f+s|0;l=c<<1;u=l+p|0;i=OT(o[e+(u<<2)>>2]|0,h)|0;u=OT(o[e+(u+1<<2)>>2]|0,h)|0;if(!(u>>>0>>0&i>>>0<(t-u|0)>>>0)){i=0;break e}if(r[e+(u+i)>>0]|0){i=0;break e}i=rT(n,e+u|0)|0;if(!i)break;i=(i|0)<0;if((a|0)==1){i=0;break e}else{f=i?f:c;a=i?s:a-s|0}}i=l+d|0;u=OT(o[e+(i<<2)>>2]|0,h)|0;i=OT(o[e+(i+1<<2)>>2]|0,h)|0;if(i>>>0>>0&u>>>0<(t-i|0)>>>0)i=(r[e+(i+u)>>0]|0)==0?e+i|0:0;else i=0}else i=0}while(0);return i|0}function OT(e,t){e=e|0;t=t|0;var n=0;n=fx(e|0)|0;return((t|0)==0?e:n)|0}function PT(e,t,n){e=e|0;t=t|0;n=n|0;var i=0,u=0,a=0,l=0,s=0;i=n+16|0;u=o[i>>2]|0;if(!u){if(!(IT(n)|0)){u=o[i>>2]|0;a=5}else i=0}else a=5;e:do{if((a|0)==5){s=n+20|0;l=o[s>>2]|0;i=l;if((u-l|0)>>>0>>0){i=_A[o[n+36>>2]&7](n,e,t)|0;break}t:do{if((r[n+75>>0]|0)>-1){l=t;while(1){if(!l){a=0;u=e;break t}u=l+-1|0;if((r[e+u>>0]|0)==10)break;else l=u}i=_A[o[n+36>>2]&7](n,e,l)|0;if(i>>>0>>0)break e;a=l;u=e+l|0;t=t-l|0;i=o[s>>2]|0}else{a=0;u=e}}while(0);ix(i|0,u|0,t|0)|0;o[s>>2]=(o[s>>2]|0)+t;i=a+t|0}}while(0);return i|0}function IT(e){e=e|0;var t=0,n=0;t=e+74|0;n=r[t>>0]|0;r[t>>0]=n+255|n;t=o[e>>2]|0;if(!(t&8)){o[e+8>>2]=0;o[e+4>>2]=0;n=o[e+44>>2]|0;o[e+28>>2]=n;o[e+20>>2]=n;o[e+16>>2]=n+(o[e+48>>2]|0);e=0}else{o[e>>2]=t|32;e=-1}return e|0}function NT(e,t){e=Y(e);t=Y(t);var n=0,r=0;n=MT(e)|0;do{if((n&2147483647)>>>0<=2139095040){r=MT(t)|0;if((r&2147483647)>>>0<=2139095040)if((r^n|0)<0){e=(n|0)<0?t:e;break}else{e=e>2]=e,o[d>>2]|0)|0}function RT(e,t){e=Y(e);t=Y(t);var n=0,r=0;n=FT(e)|0;do{if((n&2147483647)>>>0<=2139095040){r=FT(t)|0;if((r&2147483647)>>>0<=2139095040)if((r^n|0)<0){e=(n|0)<0?e:t;break}else{e=e>2]=e,o[d>>2]|0)|0}function LT(e,t){e=Y(e);t=Y(t);var n=0,r=0,i=0,u=0,a=0,l=0,c=0,f=0;u=(s[d>>2]=e,o[d>>2]|0);l=(s[d>>2]=t,o[d>>2]|0);n=u>>>23&255;a=l>>>23&255;c=u&-2147483648;i=l<<1;e:do{if((i|0)!=0?!((n|0)==255|((BT(t)|0)&2147483647)>>>0>2139095040):0){r=u<<1;if(r>>>0<=i>>>0){t=Y(e*Y(0.0));return Y((r|0)==(i|0)?t:e)}if(!n){n=u<<9;if((n|0)>-1){r=n;n=0;do{n=n+-1|0;r=r<<1}while((r|0)>-1)}else n=0;r=u<<1-n}else r=u&8388607|8388608;if(!a){u=l<<9;if((u|0)>-1){i=0;do{i=i+-1|0;u=u<<1}while((u|0)>-1)}else i=0;a=i;l=l<<1-i}else l=l&8388607|8388608;i=r-l|0;u=(i|0)>-1;t:do{if((n|0)>(a|0)){while(1){if(u)if(!i)break;else r=i;r=r<<1;n=n+-1|0;i=r-l|0;u=(i|0)>-1;if((n|0)<=(a|0))break t}t=Y(e*Y(0.0));break e}}while(0);if(u)if(!i){t=Y(e*Y(0.0));break}else r=i;if(r>>>0<8388608)do{r=r<<1;n=n+-1|0}while(r>>>0<8388608);if((n|0)>0)n=r+-8388608|n<<23;else n=r>>>(1-n|0);t=(o[d>>2]=n|c,Y(s[d>>2]))}else f=3}while(0);if((f|0)==3){t=Y(e*t);t=Y(t/t)}return Y(t)}function BT(e){e=Y(e);return(s[d>>2]=e,o[d>>2]|0)|0}function jT(e,t){e=e|0;t=t|0;return oT(o[582]|0,e,t)|0}function UT(e){e=e|0;Ye()}function zT(e){e=e|0;return}function WT(e,t){e=e|0;t=t|0;return 0}function HT(e){e=e|0;if((VT(e+4|0)|0)==-1){hA[o[(o[e>>2]|0)+8>>2]&127](e);e=1}else e=0;return e|0}function VT(e){e=e|0;var t=0;t=o[e>>2]|0;o[e>>2]=t+-1;return t+-1|0}function qT(e){e=e|0;if(HT(e)|0)GT(e);return}function GT(e){e=e|0;var t=0;t=e+8|0;if(!((o[t>>2]|0)!=0?(VT(t)|0)!=-1:0))hA[o[(o[e>>2]|0)+16>>2]&127](e);return}function $T(e){e=e|0;var t=0;t=(e|0)==0?1:e;while(1){e=qk(t)|0;if(e|0)break;e=QT()|0;if(!e){e=0;break}IA[e&0]()}return e|0}function YT(e){e=e|0;return $T(e)|0}function KT(e){e=e|0;Gk(e);return}function XT(e){e=e|0;if((r[e+11>>0]|0)<0)KT(o[e>>2]|0);return}function QT(){var e=0;e=o[2923]|0;o[2923]=e+0;return e|0}function JT(){}function ZT(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;r=t-r-(n>>>0>e>>>0|0)>>>0;return(A=r,e-n>>>0|0)|0}function ex(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;n=e+n>>>0;return(A=t+r+(n>>>0>>0|0)>>>0,n|0)|0}function tx(e,t,n){e=e|0;t=t|0;n=n|0;var i=0,u=0,a=0,l=0;a=e+n|0;t=t&255;if((n|0)>=67){while(e&3){r[e>>0]=t;e=e+1|0}i=a&-4|0;u=i-64|0;l=t|t<<8|t<<16|t<<24;while((e|0)<=(u|0)){o[e>>2]=l;o[e+4>>2]=l;o[e+8>>2]=l;o[e+12>>2]=l;o[e+16>>2]=l;o[e+20>>2]=l;o[e+24>>2]=l;o[e+28>>2]=l;o[e+32>>2]=l;o[e+36>>2]=l;o[e+40>>2]=l;o[e+44>>2]=l;o[e+48>>2]=l;o[e+52>>2]=l;o[e+56>>2]=l;o[e+60>>2]=l;e=e+64|0}while((e|0)<(i|0)){o[e>>2]=l;e=e+4|0}}while((e|0)<(a|0)){r[e>>0]=t;e=e+1|0}return a-n|0}function nx(e,t,n){e=e|0;t=t|0;n=n|0;if((n|0)<32){A=t<>>32-n;return e<>>n;return e>>>n|(t&(1<>>n-32|0}function ix(e,t,n){e=e|0;t=t|0;n=n|0;var i=0,u=0,a=0;if((n|0)>=8192)return He(e|0,t|0,n|0)|0;a=e|0;u=e+n|0;if((e&3)==(t&3)){while(e&3){if(!n)return a|0;r[e>>0]=r[t>>0]|0;e=e+1|0;t=t+1|0;n=n-1|0}n=u&-4|0;i=n-64|0;while((e|0)<=(i|0)){o[e>>2]=o[t>>2];o[e+4>>2]=o[t+4>>2];o[e+8>>2]=o[t+8>>2];o[e+12>>2]=o[t+12>>2];o[e+16>>2]=o[t+16>>2];o[e+20>>2]=o[t+20>>2];o[e+24>>2]=o[t+24>>2];o[e+28>>2]=o[t+28>>2];o[e+32>>2]=o[t+32>>2];o[e+36>>2]=o[t+36>>2];o[e+40>>2]=o[t+40>>2];o[e+44>>2]=o[t+44>>2];o[e+48>>2]=o[t+48>>2];o[e+52>>2]=o[t+52>>2];o[e+56>>2]=o[t+56>>2];o[e+60>>2]=o[t+60>>2];e=e+64|0;t=t+64|0}while((e|0)<(n|0)){o[e>>2]=o[t>>2];e=e+4|0;t=t+4|0}}else{n=u-4|0;while((e|0)<(n|0)){r[e>>0]=r[t>>0]|0;r[e+1>>0]=r[t+1>>0]|0;r[e+2>>0]=r[t+2>>0]|0;r[e+3>>0]=r[t+3>>0]|0;e=e+4|0;t=t+4|0}}while((e|0)<(u|0)){r[e>>0]=r[t>>0]|0;e=e+1|0;t=t+1|0}return a|0}function ox(e){e=e|0;var t=0;t=r[m+(e&255)>>0]|0;if((t|0)<8)return t|0;t=r[m+(e>>8&255)>>0]|0;if((t|0)<8)return t+8|0;t=r[m+(e>>16&255)>>0]|0;if((t|0)<8)return t+16|0;return(r[m+(e>>>24)>>0]|0)+24|0}function ux(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;var u=0,a=0,l=0,s=0,c=0,f=0,d=0,p=0,h=0,v=0;f=e;s=t;c=s;a=n;p=r;l=p;if(!c){u=(i|0)!=0;if(!l){if(u){o[i>>2]=(f>>>0)%(a>>>0);o[i+4>>2]=0}p=0;i=(f>>>0)/(a>>>0)>>>0;return(A=p,i)|0}else{if(!u){p=0;i=0;return(A=p,i)|0}o[i>>2]=e|0;o[i+4>>2]=t&0;p=0;i=0;return(A=p,i)|0}}u=(l|0)==0;do{if(a){if(!u){u=($(l|0)|0)-($(c|0)|0)|0;if(u>>>0<=31){d=u+1|0;l=31-u|0;t=u-31>>31;a=d;e=f>>>(d>>>0)&t|c<>>(d>>>0)&t;u=0;l=f<>2]=e|0;o[i+4>>2]=s|t&0;p=0;i=0;return(A=p,i)|0}u=a-1|0;if(u&a|0){l=($(a|0)|0)+33-($(c|0)|0)|0;v=64-l|0;d=32-l|0;s=d>>31;h=l-32|0;t=h>>31;a=l;e=d-1>>31&c>>>(h>>>0)|(c<>>(l>>>0))&t;t=t&c>>>(l>>>0);u=f<>>(h>>>0))&s|f<>31;break}if(i|0){o[i>>2]=u&f;o[i+4>>2]=0}if((a|0)==1){h=s|t&0;v=e|0|0;return(A=h,v)|0}else{v=ox(a|0)|0;h=c>>>(v>>>0)|0;v=c<<32-v|f>>>(v>>>0)|0;return(A=h,v)|0}}else{if(u){if(i|0){o[i>>2]=(c>>>0)%(a>>>0);o[i+4>>2]=0}h=0;v=(c>>>0)/(a>>>0)>>>0;return(A=h,v)|0}if(!f){if(i|0){o[i>>2]=0;o[i+4>>2]=(c>>>0)%(l>>>0)}h=0;v=(c>>>0)/(l>>>0)>>>0;return(A=h,v)|0}u=l-1|0;if(!(u&l)){if(i|0){o[i>>2]=e|0;o[i+4>>2]=u&c|t&0}h=0;v=c>>>((ox(l|0)|0)>>>0);return(A=h,v)|0}u=($(l|0)|0)-($(c|0)|0)|0;if(u>>>0<=30){t=u+1|0;l=31-u|0;a=t;e=c<>>(t>>>0);t=c>>>(t>>>0);u=0;l=f<>2]=e|0;o[i+4>>2]=s|t&0;h=0;v=0;return(A=h,v)|0}}while(0);if(!a){c=l;s=0;l=0}else{d=n|0|0;f=p|r&0;c=ex(d|0,f|0,-1,-1)|0;n=A;s=l;l=0;do{r=s;s=u>>>31|s<<1;u=l|u<<1;r=e<<1|r>>>31|0;p=e>>>31|t<<1|0;ZT(c|0,n|0,r|0,p|0)|0;v=A;h=v>>31|((v|0)<0?-1:0)<<1;l=h&1;e=ZT(r|0,p|0,h&d|0,(((v|0)<0?-1:0)>>31|((v|0)<0?-1:0)<<1)&f|0)|0;t=A;a=a-1|0}while((a|0)!=0);c=s;s=0}a=0;if(i|0){o[i>>2]=e;o[i+4>>2]=t}h=(u|0)>>>31|(c|a)<<1|(a<<1|u>>>31)&0|s;v=(u<<1|0>>>31)&-2|l;return(A=h,v)|0}function ax(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;return ux(e,t,n,r,0)|0}function lx(e){e=e|0;var t=0,n=0;n=e+15&-16|0;t=o[f>>2]|0;e=t+n|0;if((n|0)>0&(e|0)<(t|0)|(e|0)<0){Z()|0;qe(12);return-1}o[f>>2]=e;if((e|0)>(J()|0)?(Q()|0)==0:0){o[f>>2]=t;qe(12);return-1}return t|0}function sx(e,t,n){e=e|0;t=t|0;n=n|0;var i=0;if((t|0)<(e|0)&(e|0)<(t+n|0)){i=e;t=t+n|0;e=e+n|0;while((n|0)>0){e=e-1|0;t=t-1|0;n=n-1|0;r[e>>0]=r[t>>0]|0}e=i}else ix(e,t,n)|0;return e|0}function cx(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;var i=0,u=0;u=h;h=h+16|0;i=u|0;ux(e,t,n,r,i)|0;h=u;return(A=o[i+4>>2]|0,o[i>>2]|0)|0}function fx(e){e=e|0;return(e&255)<<24|(e>>8&255)<<16|(e>>16&255)<<8|e>>>24|0}function dx(e,t,n,r,i,o){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;o=o|0;cA[e&1](t|0,n|0,r|0,i|0,o|0)}function px(e,t,n){e=e|0;t=t|0;n=Y(n);fA[e&1](t|0,Y(n))}function hx(e,t,n){e=e|0;t=t|0;n=+n;dA[e&31](t|0,+n)}function vx(e,t,n,r){e=e|0;t=t|0;n=Y(n);r=Y(r);return Y(pA[e&0](t|0,Y(n),Y(r)))}function mx(e,t){e=e|0;t=t|0;hA[e&127](t|0)}function gx(e,t,n){e=e|0;t=t|0;n=n|0;vA[e&31](t|0,n|0)}function yx(e,t){e=e|0;t=t|0;return mA[e&31](t|0)|0}function _x(e,t,n,r,i){e=e|0;t=t|0;n=+n;r=+r;i=i|0;gA[e&1](t|0,+n,+r,i|0)}function bx(e,t,n,r){e=e|0;t=t|0;n=+n;r=+r;yA[e&1](t|0,+n,+r)}function wx(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;return _A[e&7](t|0,n|0,r|0)|0}function Ex(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;return+bA[e&1](t|0,n|0,r|0)}function Dx(e,t){e=e|0;t=t|0;return+wA[e&15](t|0)}function Sx(e,t,n){e=e|0;t=t|0;n=+n;return EA[e&1](t|0,+n)|0}function Cx(e,t,n){e=e|0;t=t|0;n=n|0;return DA[e&15](t|0,n|0)|0}function kx(e,t,n,r,i,o){e=e|0;t=t|0;n=n|0;r=+r;i=+i;o=o|0;SA[e&1](t|0,n|0,+r,+i,o|0)}function Tx(e,t,n,r,i,o,u){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;o=o|0;u=u|0;CA[e&1](t|0,n|0,r|0,i|0,o|0,u|0)}function xx(e,t,n){e=e|0;t=t|0;n=n|0;return+kA[e&7](t|0,n|0)}function Ax(e){e=e|0;return TA[e&7]()|0}function Ox(e,t,n,r,i,o){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;o=o|0;return xA[e&1](t|0,n|0,r|0,i|0,o|0)|0}function Px(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=+i;AA[e&1](t|0,n|0,r|0,+i)}function Ix(e,t,n,r,i,o,u){e=e|0;t=t|0;n=n|0;r=Y(r);i=i|0;o=Y(o);u=u|0;OA[e&1](t|0,n|0,Y(r),i|0,Y(o),u|0)}function Nx(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;PA[e&15](t|0,n|0,r|0)}function Mx(e){e=e|0;IA[e&0]()}function Rx(e,t,n,r){e=e|0;t=t|0;n=n|0;r=+r;NA[e&15](t|0,n|0,+r)}function Fx(e,t,n){e=e|0;t=+t;n=+n;return MA[e&1](+t,+n)|0}function Lx(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;RA[e&15](t|0,n|0,r|0,i|0)}function Bx(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;K(0)}function jx(e,t){e=e|0;t=Y(t);K(1)}function Ux(e,t){e=e|0;t=+t;K(2)}function zx(e,t,n){e=e|0;t=Y(t);n=Y(n);K(3);return ft}function Wx(e){e=e|0;K(4)}function Hx(e,t){e=e|0;t=t|0;K(5)}function Vx(e){e=e|0;K(6);return 0}function qx(e,t,n,r){e=e|0;t=+t;n=+n;r=r|0;K(7)}function Gx(e,t,n){e=e|0;t=+t;n=+n;K(8)}function $x(e,t,n){e=e|0;t=t|0;n=n|0;K(9);return 0}function Yx(e,t,n){e=e|0;t=t|0;n=n|0;K(10);return 0.0}function Kx(e){e=e|0;K(11);return 0.0}function Xx(e,t){e=e|0;t=+t;K(12);return 0}function Qx(e,t){e=e|0;t=t|0;K(13);return 0}function Jx(e,t,n,r,i){e=e|0;t=t|0;n=+n;r=+r;i=i|0;K(14)}function Zx(e,t,n,r,i,o){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;o=o|0;K(15)}function eA(e,t){e=e|0;t=t|0;K(16);return 0.0}function tA(){K(17);return 0}function nA(e,t,n,r,i){e=e|0;t=t|0;n=n|0;r=r|0;i=i|0;K(18);return 0}function rA(e,t,n,r){e=e|0;t=t|0;n=n|0;r=+r;K(19)}function iA(e,t,n,r,i,o){e=e|0;t=t|0;n=Y(n);r=r|0;i=Y(i);o=o|0;K(20)}function oA(e,t,n){e=e|0;t=t|0;n=n|0;K(21)}function uA(){K(22)}function aA(e,t,n){e=e|0;t=t|0;n=+n;K(23)}function lA(e,t){e=+e;t=+t;K(24);return 0}function sA(e,t,n,r){e=e|0;t=t|0;n=n|0;r=r|0;K(25)}var cA=[Bx,DE];var fA=[jx,qi];var dA=[Ux,yo,_o,bo,wo,Eo,Do,So,ko,To,Ao,Oo,Po,Io,No,Mo,Ro,Fo,Lo,Ux,Ux,Ux,Ux,Ux,Ux,Ux,Ux,Ux,Ux,Ux,Ux,Ux];var pA=[zx];var hA=[Wx,zT,hl,vl,ml,Kd,Xd,Qd,yb,_b,bb,oE,uE,aE,uk,ak,lk,bt,Xi,to,Co,xo,ju,Uu,Ka,Sl,Wl,ps,Ns,rc,kc,qc,df,Mf,Zf,yd,Ld,gp,Fp,th,bh,jh,iv,kv,Vv,am,xm,Wi,cg,Ag,Qg,yy,Fy,o_,g_,b_,U_,H_,ab,Db,kb,Gb,pw,Cl,OD,pS,PS,KS,bC,BC,XC,ZC,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx,Wx];var vA=[Hx,no,ro,uo,ao,lo,so,co,fo,vo,mo,go,eu,ru,iu,ou,uu,au,lu,pu,gu,Ku,Ov,$v,Ey,ND,ww,eS,Hx,Hx,Hx,Hx];var mA=[Vx,Yk,Ki,zo,qo,Go,$o,Yo,Ko,Xo,Jo,Zo,hu,vu,zu,Pm,Uy,Kb,BD,UD,Vx,Vx,Vx,Vx,Vx,Vx,Vx,Vx,Vx,Vx,Vx,Vx];var gA=[qx,Wu];var yA=[Gx,cb];var _A=[$x,Kk,Xk,nT,ac,wp,hg,ZS];var bA=[Yx,rd];var wA=[Kx,tu,nu,su,Hu,Vu,qu,Gu,$u,Yu,Kx,Kx,Kx,Kx,Kx,Kx];var EA=[Xx,p_];var DA=[Qx,WT,mu,tl,gs,Oc,Kc,zd,Up,fm,Gi,RS,Qx,Qx,Qx,Qx];var SA=[Jx,Gl];var CA=[Zx,SC];var kA=[eA,cu,Xu,Qu,Ju,Ed,eA,eA];var TA=[tA,Zu,$i,Ui,C_,$_,Pb,rk];var xA=[nA,Fr];var AA=[rA,Sh];var OA=[iA,_u];var PA=[oA,Wo,Qo,fu,du,Ls,mf,Hh,lv,Vi,JE,gS,WC,oA,oA,oA];var IA=[uA];var NA=[aA,io,oo,po,ho,Bo,jo,Uo,oh,Ng,l_,aA,aA,aA,aA,aA];var MA=[lA,vb];var RA=[sA,Bf,jm,ty,Ky,P_,Z_,Bb,yw,qD,hk,sA,sA,sA,sA,sA];return{_llvm_bswap_i32:fx,dynCall_idd:Fx,dynCall_i:Ax,_i64Subtract:ZT,___udivdi3:ax,dynCall_vif:px,setThrew:mt,dynCall_viii:Nx,_bitshift64Lshr:rx,_bitshift64Shl:nx,dynCall_vi:mx,dynCall_viiddi:kx,dynCall_diii:Ex,dynCall_iii:Cx,_memset:tx,_sbrk:lx,_memcpy:ix,__GLOBAL__sub_I_Yoga_cpp:ji,dynCall_vii:gx,___uremdi3:cx,dynCall_vid:hx,stackAlloc:dt,_nbind_init:Ak,getTempRet0:yt,dynCall_di:Dx,dynCall_iid:Sx,setTempRet0:gt,_i64Add:ex,dynCall_fiff:vx,dynCall_iiii:wx,_emscripten_get_global_libc:$k,dynCall_viid:Rx,dynCall_viiid:Px,dynCall_viififi:Ix,dynCall_ii:yx,__GLOBAL__sub_I_Binding_cc:wD,dynCall_viiii:Lx,dynCall_iiiiii:Ox,stackSave:pt,dynCall_viiiii:dx,__GLOBAL__sub_I_nbind_cc:ea,dynCall_vidd:bx,_free:Gk,runPostSets:JT,dynCall_viiiiii:Tx,establishStackSpace:vt,_memmove:sx,stackRestore:ht,_malloc:qk,__GLOBAL__sub_I_common_cc:iw,dynCall_viddi:_x,dynCall_dii:xx,dynCall_v:Mx}}(Module.asmGlobalArg,Module.asmLibraryArg,buffer),_llvm_bswap_i32=Module._llvm_bswap_i32=asm._llvm_bswap_i32,getTempRet0=Module.getTempRet0=asm.getTempRet0,___udivdi3=Module.___udivdi3=asm.___udivdi3,setThrew=Module.setThrew=asm.setThrew,_bitshift64Lshr=Module._bitshift64Lshr=asm._bitshift64Lshr,_bitshift64Shl=Module._bitshift64Shl=asm._bitshift64Shl,_memset=Module._memset=asm._memset,_sbrk=Module._sbrk=asm._sbrk,_memcpy=Module._memcpy=asm._memcpy,stackAlloc=Module.stackAlloc=asm.stackAlloc,___uremdi3=Module.___uremdi3=asm.___uremdi3,_nbind_init=Module._nbind_init=asm._nbind_init,_i64Subtract=Module._i64Subtract=asm._i64Subtract,setTempRet0=Module.setTempRet0=asm.setTempRet0,_i64Add=Module._i64Add=asm._i64Add,_emscripten_get_global_libc=Module._emscripten_get_global_libc=asm._emscripten_get_global_libc,__GLOBAL__sub_I_Yoga_cpp=Module.__GLOBAL__sub_I_Yoga_cpp=asm.__GLOBAL__sub_I_Yoga_cpp,__GLOBAL__sub_I_Binding_cc=Module.__GLOBAL__sub_I_Binding_cc=asm.__GLOBAL__sub_I_Binding_cc,stackSave=Module.stackSave=asm.stackSave,__GLOBAL__sub_I_nbind_cc=Module.__GLOBAL__sub_I_nbind_cc=asm.__GLOBAL__sub_I_nbind_cc,_free=Module._free=asm._free,runPostSets=Module.runPostSets=asm.runPostSets,establishStackSpace=Module.establishStackSpace=asm.establishStackSpace,_memmove=Module._memmove=asm._memmove,stackRestore=Module.stackRestore=asm.stackRestore,_malloc=Module._malloc=asm._malloc,__GLOBAL__sub_I_common_cc=Module.__GLOBAL__sub_I_common_cc=asm.__GLOBAL__sub_I_common_cc,dynCall_viiiii=Module.dynCall_viiiii=asm.dynCall_viiiii,dynCall_vif=Module.dynCall_vif=asm.dynCall_vif,dynCall_vid=Module.dynCall_vid=asm.dynCall_vid,dynCall_fiff=Module.dynCall_fiff=asm.dynCall_fiff,dynCall_vi=Module.dynCall_vi=asm.dynCall_vi,dynCall_vii=Module.dynCall_vii=asm.dynCall_vii,dynCall_ii=Module.dynCall_ii=asm.dynCall_ii,dynCall_viddi=Module.dynCall_viddi=asm.dynCall_viddi,dynCall_vidd=Module.dynCall_vidd=asm.dynCall_vidd,dynCall_iiii=Module.dynCall_iiii=asm.dynCall_iiii,dynCall_diii=Module.dynCall_diii=asm.dynCall_diii,dynCall_di=Module.dynCall_di=asm.dynCall_di,dynCall_iid=Module.dynCall_iid=asm.dynCall_iid,dynCall_iii=Module.dynCall_iii=asm.dynCall_iii,dynCall_viiddi=Module.dynCall_viiddi=asm.dynCall_viiddi,dynCall_viiiiii=Module.dynCall_viiiiii=asm.dynCall_viiiiii,dynCall_dii=Module.dynCall_dii=asm.dynCall_dii,dynCall_i=Module.dynCall_i=asm.dynCall_i,dynCall_iiiiii=Module.dynCall_iiiiii=asm.dynCall_iiiiii,dynCall_viiid=Module.dynCall_viiid=asm.dynCall_viiid,dynCall_viififi=Module.dynCall_viififi=asm.dynCall_viififi,dynCall_viii=Module.dynCall_viii=asm.dynCall_viii,dynCall_v=Module.dynCall_v=asm.dynCall_v,dynCall_viid=Module.dynCall_viid=asm.dynCall_viid,dynCall_idd=Module.dynCall_idd=asm.dynCall_idd,dynCall_viiii=Module.dynCall_viiii=asm.dynCall_viiii,initialStackTop;function ExitStatus(e){this.name="ExitStatus",this.message="Program terminated with exit("+e+")",this.status=e}Runtime.stackAlloc=Module.stackAlloc,Runtime.stackSave=Module.stackSave,Runtime.stackRestore=Module.stackRestore,Runtime.establishStackSpace=Module.establishStackSpace,Runtime.setTempRet0=Module.setTempRet0,Runtime.getTempRet0=Module.getTempRet0,Module.asm=asm,ExitStatus.prototype=new Error,ExitStatus.prototype.constructor=ExitStatus;var preloadStartTime=null,calledMain=!1;function run(e){function t(){Module.calledRun||(Module.calledRun=!0,ABORT||(ensureInitRuntime(),preMain(),Module.onRuntimeInitialized&&Module.onRuntimeInitialized(),Module._main&&shouldRunNow&&Module.callMain(e),postRun()))}e=e||Module.arguments,null===preloadStartTime&&(preloadStartTime=Date.now()),runDependencies>0||(preRun(),runDependencies>0||Module.calledRun||(Module.setStatus?(Module.setStatus("Running..."),setTimeout((function(){setTimeout((function(){Module.setStatus("")}),1),t()}),1)):t()))}function exit(e,t){t&&Module.noExitRuntime||(Module.noExitRuntime||(ABORT=!0,EXITSTATUS=e,STACKTOP=initialStackTop,exitRuntime(),Module.onExit&&Module.onExit(e)),ENVIRONMENT_IS_NODE&&process.exit(e),Module.quit(e,new ExitStatus(e)))}dependenciesFulfilled=function e(){Module.calledRun||run(),Module.calledRun||(dependenciesFulfilled=e)},Module.callMain=Module.callMain=function(e){e=e||[],ensureInitRuntime();var t=e.length+1;function n(){for(var e=0;e<3;e++)r.push(0)}var r=[allocate(intArrayFromString(Module.thisProgram),"i8",ALLOC_NORMAL)];n();for(var i=0;i0;)Module.preInit.pop()();var shouldRunNow=!0;Module.noInitialRun&&(shouldRunNow=!1),run()},void 0===(__WEBPACK_AMD_DEFINE_RESULT__=function(){return wrapper}.apply(exports,__WEBPACK_AMD_DEFINE_ARRAY__=[]))||(module.exports=__WEBPACK_AMD_DEFINE_RESULT__)},3019:e=>{"use strict";e.exports={ALIGN_COUNT:8,ALIGN_AUTO:0,ALIGN_FLEX_START:1,ALIGN_CENTER:2,ALIGN_FLEX_END:3,ALIGN_STRETCH:4,ALIGN_BASELINE:5,ALIGN_SPACE_BETWEEN:6,ALIGN_SPACE_AROUND:7,DIMENSION_COUNT:2,DIMENSION_WIDTH:0,DIMENSION_HEIGHT:1,DIRECTION_COUNT:3,DIRECTION_INHERIT:0,DIRECTION_LTR:1,DIRECTION_RTL:2,DISPLAY_COUNT:2,DISPLAY_FLEX:0,DISPLAY_NONE:1,EDGE_COUNT:9,EDGE_LEFT:0,EDGE_TOP:1,EDGE_RIGHT:2,EDGE_BOTTOM:3,EDGE_START:4,EDGE_END:5,EDGE_HORIZONTAL:6,EDGE_VERTICAL:7,EDGE_ALL:8,EXPERIMENTAL_FEATURE_COUNT:1,EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS:0,FLEX_DIRECTION_COUNT:4,FLEX_DIRECTION_COLUMN:0,FLEX_DIRECTION_COLUMN_REVERSE:1,FLEX_DIRECTION_ROW:2,FLEX_DIRECTION_ROW_REVERSE:3,JUSTIFY_COUNT:6,JUSTIFY_FLEX_START:0,JUSTIFY_CENTER:1,JUSTIFY_FLEX_END:2,JUSTIFY_SPACE_BETWEEN:3,JUSTIFY_SPACE_AROUND:4,JUSTIFY_SPACE_EVENLY:5,LOG_LEVEL_COUNT:6,LOG_LEVEL_ERROR:0,LOG_LEVEL_WARN:1,LOG_LEVEL_INFO:2,LOG_LEVEL_DEBUG:3,LOG_LEVEL_VERBOSE:4,LOG_LEVEL_FATAL:5,MEASURE_MODE_COUNT:3,MEASURE_MODE_UNDEFINED:0,MEASURE_MODE_EXACTLY:1,MEASURE_MODE_AT_MOST:2,NODE_TYPE_COUNT:2,NODE_TYPE_DEFAULT:0,NODE_TYPE_TEXT:1,OVERFLOW_COUNT:3,OVERFLOW_VISIBLE:0,OVERFLOW_HIDDEN:1,OVERFLOW_SCROLL:2,POSITION_TYPE_COUNT:2,POSITION_TYPE_RELATIVE:0,POSITION_TYPE_ABSOLUTE:1,PRINT_OPTIONS_COUNT:3,PRINT_OPTIONS_LAYOUT:1,PRINT_OPTIONS_STYLE:2,PRINT_OPTIONS_CHILDREN:4,UNIT_COUNT:4,UNIT_UNDEFINED:0,UNIT_POINT:1,UNIT_PERCENT:2,UNIT_AUTO:3,WRAP_COUNT:3,WRAP_NO_WRAP:0,WRAP_WRAP:1,WRAP_WRAP_REVERSE:2}},6401:(e,t,n)=>{"use strict";var r=n(7180),i=n(3354),o=!1,u=null;if(i({},(function(e,t){if(!o){if(o=!0,e)throw e;u=t}})),!o)throw new Error("Failed to load the yoga module - it needed to be loaded synchronously, but didn't");e.exports=r(u.bind,u.lib)},7180:(e,t,n)=>{"use strict";var r=Object.assign||function(e){for(var t=1;t"}}]),e}(),s=function(){function e(t,n){u(this,e),this.width=t,this.height=n}return i(e,null,[{key:"fromJS",value:function(t){return new e(t.width,t.height)}}]),i(e,[{key:"fromJS",value:function(e){e(this.width,this.height)}},{key:"toString",value:function(){return""}}]),e}(),c=function(){function e(t,n){u(this,e),this.unit=t,this.value=n}return i(e,[{key:"fromJS",value:function(e){e(this.unit,this.value)}},{key:"toString",value:function(){switch(this.unit){case a.UNIT_POINT:return String(this.value);case a.UNIT_PERCENT:return this.value+"%";case a.UNIT_AUTO:return"auto";default:return this.value+"?"}}},{key:"valueOf",value:function(){return this.value}}]),e}();e.exports=function(e,t){function n(e,t,n){var r=e[t];e[t]=function(){for(var e=arguments.length,t=Array(e),i=0;i1?t-1:0),i=1;i1&&void 0!==arguments[1]?arguments[1]:NaN,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:NaN,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:a.DIRECTION_LTR;return e.call(this,t,n,r)})),r({Config:t.Config,Node:t.Node,Layout:e("Layout",l),Size:e("Size",s),Value:e("Value",c),getInstanceCount:function(){return t.getInstanceCount.apply(t,arguments)}},a)}},2357:e=>{"use strict";e.exports=require("assert")},6417:e=>{"use strict";e.exports=require("crypto")},8614:e=>{"use strict";e.exports=require("events")},5747:e=>{"use strict";e.exports=require("fs")},8605:e=>{"use strict";e.exports=require("http")},7211:e=>{"use strict";e.exports=require("https")},2282:e=>{"use strict";e.exports=require("module")},1631:e=>{"use strict";e.exports=require("net")},2087:e=>{"use strict";e.exports=require("os")},2413:e=>{"use strict";e.exports=require("stream")},4016:e=>{"use strict";e.exports=require("tls")},3867:e=>{"use strict";e.exports=require("tty")},8835:e=>{"use strict";e.exports=require("url")},8761:e=>{"use strict";e.exports=require("zlib")}},__webpack_module_cache__={};function __webpack_require__(e){if(__webpack_module_cache__[e])return __webpack_module_cache__[e].exports;var t=__webpack_module_cache__[e]={id:e,loaded:!1,exports:{}};return __webpack_modules__[e].call(t.exports,t,t.exports,__webpack_require__),t.loaded=!0,t.exports}return __webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},__webpack_require__.d=(e,t)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),__webpack_require__(7560)})(); return plugin; } }; ================================================ FILE: .yarn/releases/yarn-3.6.3.cjs ================================================ #!/usr/bin/env node /* eslint-disable */ //prettier-ignore (()=>{var Dge=Object.create;var lS=Object.defineProperty;var kge=Object.getOwnPropertyDescriptor;var Rge=Object.getOwnPropertyNames;var Fge=Object.getPrototypeOf,Nge=Object.prototype.hasOwnProperty;var J=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(e,t)=>(typeof require<"u"?require:e)[t]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+r+'" is not supported')});var Tge=(r,e)=>()=>(r&&(e=r(r=0)),e);var w=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports),ut=(r,e)=>{for(var t in e)lS(r,t,{get:e[t],enumerable:!0})},Lge=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Rge(e))!Nge.call(r,n)&&n!==t&&lS(r,n,{get:()=>e[n],enumerable:!(i=kge(e,n))||i.enumerable});return r};var Pe=(r,e,t)=>(t=r!=null?Dge(Fge(r)):{},Lge(e||!r||!r.__esModule?lS(t,"default",{value:r,enumerable:!0}):t,r));var PK=w((z7e,xK)=>{xK.exports=vK;vK.sync=ife;var QK=J("fs");function rfe(r,e){var t=e.pathExt!==void 0?e.pathExt:process.env.PATHEXT;if(!t||(t=t.split(";"),t.indexOf("")!==-1))return!0;for(var i=0;i{FK.exports=kK;kK.sync=nfe;var DK=J("fs");function kK(r,e,t){DK.stat(r,function(i,n){t(i,i?!1:RK(n,e))})}function nfe(r,e){return RK(DK.statSync(r),e)}function RK(r,e){return r.isFile()&&sfe(r,e)}function sfe(r,e){var t=r.mode,i=r.uid,n=r.gid,s=e.uid!==void 0?e.uid:process.getuid&&process.getuid(),o=e.gid!==void 0?e.gid:process.getgid&&process.getgid(),a=parseInt("100",8),l=parseInt("010",8),c=parseInt("001",8),u=a|l,g=t&c||t&l&&n===o||t&a&&i===s||t&u&&s===0;return g}});var LK=w((Z7e,TK)=>{var X7e=J("fs"),lI;process.platform==="win32"||global.TESTING_WINDOWS?lI=PK():lI=NK();TK.exports=SS;SS.sync=ofe;function SS(r,e,t){if(typeof e=="function"&&(t=e,e={}),!t){if(typeof Promise!="function")throw new TypeError("callback not provided");return new Promise(function(i,n){SS(r,e||{},function(s,o){s?n(s):i(o)})})}lI(r,e||{},function(i,n){i&&(i.code==="EACCES"||e&&e.ignoreErrors)&&(i=null,n=!1),t(i,n)})}function ofe(r,e){try{return lI.sync(r,e||{})}catch(t){if(e&&e.ignoreErrors||t.code==="EACCES")return!1;throw t}}});var YK=w((_7e,GK)=>{var Dg=process.platform==="win32"||process.env.OSTYPE==="cygwin"||process.env.OSTYPE==="msys",MK=J("path"),afe=Dg?";":":",OK=LK(),KK=r=>Object.assign(new Error(`not found: ${r}`),{code:"ENOENT"}),UK=(r,e)=>{let t=e.colon||afe,i=r.match(/\//)||Dg&&r.match(/\\/)?[""]:[...Dg?[process.cwd()]:[],...(e.path||process.env.PATH||"").split(t)],n=Dg?e.pathExt||process.env.PATHEXT||".EXE;.CMD;.BAT;.COM":"",s=Dg?n.split(t):[""];return Dg&&r.indexOf(".")!==-1&&s[0]!==""&&s.unshift(""),{pathEnv:i,pathExt:s,pathExtExe:n}},HK=(r,e,t)=>{typeof e=="function"&&(t=e,e={}),e||(e={});let{pathEnv:i,pathExt:n,pathExtExe:s}=UK(r,e),o=[],a=c=>new Promise((u,g)=>{if(c===i.length)return e.all&&o.length?u(o):g(KK(r));let f=i[c],h=/^".*"$/.test(f)?f.slice(1,-1):f,p=MK.join(h,r),C=!h&&/^\.[\\\/]/.test(r)?r.slice(0,2)+p:p;u(l(C,c,0))}),l=(c,u,g)=>new Promise((f,h)=>{if(g===n.length)return f(a(u+1));let p=n[g];OK(c+p,{pathExt:s},(C,y)=>{if(!C&&y)if(e.all)o.push(c+p);else return f(c+p);return f(l(c,u,g+1))})});return t?a(0).then(c=>t(null,c),t):a(0)},Afe=(r,e)=>{e=e||{};let{pathEnv:t,pathExt:i,pathExtExe:n}=UK(r,e),s=[];for(let o=0;o{"use strict";var jK=(r={})=>{let e=r.env||process.env;return(r.platform||process.platform)!=="win32"?"PATH":Object.keys(e).reverse().find(i=>i.toUpperCase()==="PATH")||"Path"};vS.exports=jK;vS.exports.default=jK});var VK=w((eZe,zK)=>{"use strict";var JK=J("path"),lfe=YK(),cfe=qK();function WK(r,e){let t=r.options.env||process.env,i=process.cwd(),n=r.options.cwd!=null,s=n&&process.chdir!==void 0&&!process.chdir.disabled;if(s)try{process.chdir(r.options.cwd)}catch{}let o;try{o=lfe.sync(r.command,{path:t[cfe({env:t})],pathExt:e?JK.delimiter:void 0})}catch{}finally{s&&process.chdir(i)}return o&&(o=JK.resolve(n?r.options.cwd:"",o)),o}function ufe(r){return WK(r)||WK(r,!0)}zK.exports=ufe});var XK=w((tZe,PS)=>{"use strict";var xS=/([()\][%!^"`<>&|;, *?])/g;function gfe(r){return r=r.replace(xS,"^$1"),r}function ffe(r,e){return r=`${r}`,r=r.replace(/(\\*)"/g,'$1$1\\"'),r=r.replace(/(\\*)$/,"$1$1"),r=`"${r}"`,r=r.replace(xS,"^$1"),e&&(r=r.replace(xS,"^$1")),r}PS.exports.command=gfe;PS.exports.argument=ffe});var _K=w((rZe,ZK)=>{"use strict";ZK.exports=/^#!(.*)/});var eU=w((iZe,$K)=>{"use strict";var hfe=_K();$K.exports=(r="")=>{let e=r.match(hfe);if(!e)return null;let[t,i]=e[0].replace(/#! ?/,"").split(" "),n=t.split("/").pop();return n==="env"?i:i?`${n} ${i}`:n}});var rU=w((nZe,tU)=>{"use strict";var DS=J("fs"),pfe=eU();function dfe(r){let t=Buffer.alloc(150),i;try{i=DS.openSync(r,"r"),DS.readSync(i,t,0,150,0),DS.closeSync(i)}catch{}return pfe(t.toString())}tU.exports=dfe});var oU=w((sZe,sU)=>{"use strict";var Cfe=J("path"),iU=VK(),nU=XK(),mfe=rU(),Efe=process.platform==="win32",Ife=/\.(?:com|exe)$/i,yfe=/node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;function wfe(r){r.file=iU(r);let e=r.file&&mfe(r.file);return e?(r.args.unshift(r.file),r.command=e,iU(r)):r.file}function Bfe(r){if(!Efe)return r;let e=wfe(r),t=!Ife.test(e);if(r.options.forceShell||t){let i=yfe.test(e);r.command=Cfe.normalize(r.command),r.command=nU.command(r.command),r.args=r.args.map(s=>nU.argument(s,i));let n=[r.command].concat(r.args).join(" ");r.args=["/d","/s","/c",`"${n}"`],r.command=process.env.comspec||"cmd.exe",r.options.windowsVerbatimArguments=!0}return r}function bfe(r,e,t){e&&!Array.isArray(e)&&(t=e,e=null),e=e?e.slice(0):[],t=Object.assign({},t);let i={command:r,args:e,options:t,file:void 0,original:{command:r,args:e}};return t.shell?i:Bfe(i)}sU.exports=bfe});var lU=w((oZe,AU)=>{"use strict";var kS=process.platform==="win32";function RS(r,e){return Object.assign(new Error(`${e} ${r.command} ENOENT`),{code:"ENOENT",errno:"ENOENT",syscall:`${e} ${r.command}`,path:r.command,spawnargs:r.args})}function Qfe(r,e){if(!kS)return;let t=r.emit;r.emit=function(i,n){if(i==="exit"){let s=aU(n,e,"spawn");if(s)return t.call(r,"error",s)}return t.apply(r,arguments)}}function aU(r,e){return kS&&r===1&&!e.file?RS(e.original,"spawn"):null}function Sfe(r,e){return kS&&r===1&&!e.file?RS(e.original,"spawnSync"):null}AU.exports={hookChildProcess:Qfe,verifyENOENT:aU,verifyENOENTSync:Sfe,notFoundError:RS}});var TS=w((aZe,kg)=>{"use strict";var cU=J("child_process"),FS=oU(),NS=lU();function uU(r,e,t){let i=FS(r,e,t),n=cU.spawn(i.command,i.args,i.options);return NS.hookChildProcess(n,i),n}function vfe(r,e,t){let i=FS(r,e,t),n=cU.spawnSync(i.command,i.args,i.options);return n.error=n.error||NS.verifyENOENTSync(n.status,i),n}kg.exports=uU;kg.exports.spawn=uU;kg.exports.sync=vfe;kg.exports._parse=FS;kg.exports._enoent=NS});var fU=w((AZe,gU)=>{"use strict";function xfe(r,e){function t(){this.constructor=r}t.prototype=e.prototype,r.prototype=new t}function Zl(r,e,t,i){this.message=r,this.expected=e,this.found=t,this.location=i,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Zl)}xfe(Zl,Error);Zl.buildMessage=function(r,e){var t={literal:function(c){return'"'+n(c.text)+'"'},class:function(c){var u="",g;for(g=0;g0){for(g=1,f=1;g>",ie=me(">>",!1),de=">&",_e=me(">&",!1),Pt=">",It=me(">",!1),Mr="<<<",ii=me("<<<",!1),gi="<&",hr=me("<&",!1),fi="<",ni=me("<",!1),Ks=function(m){return{type:"argument",segments:[].concat(...m)}},pr=function(m){return m},Ii="$'",rs=me("$'",!1),fa="'",dA=me("'",!1),cg=function(m){return[{type:"text",text:m}]},is='""',CA=me('""',!1),ha=function(){return{type:"text",text:""}},wp='"',mA=me('"',!1),EA=function(m){return m},wr=function(m){return{type:"arithmetic",arithmetic:m,quoted:!0}},Tl=function(m){return{type:"shell",shell:m,quoted:!0}},ug=function(m){return{type:"variable",...m,quoted:!0}},yo=function(m){return{type:"text",text:m}},gg=function(m){return{type:"arithmetic",arithmetic:m,quoted:!1}},Bp=function(m){return{type:"shell",shell:m,quoted:!1}},bp=function(m){return{type:"variable",...m,quoted:!1}},vr=function(m){return{type:"glob",pattern:m}},se=/^[^']/,wo=Je(["'"],!0,!1),Fn=function(m){return m.join("")},fg=/^[^$"]/,bt=Je(["$",'"'],!0,!1),Ll=`\\ `,Nn=me(`\\ `,!1),ns=function(){return""},ss="\\",gt=me("\\",!1),Bo=/^[\\$"`]/,At=Je(["\\","$",'"',"`"],!1,!1),ln=function(m){return m},S="\\a",Lt=me("\\a",!1),hg=function(){return"a"},Ml="\\b",Qp=me("\\b",!1),Sp=function(){return"\b"},vp=/^[Ee]/,xp=Je(["E","e"],!1,!1),Pp=function(){return"\x1B"},G="\\f",yt=me("\\f",!1),IA=function(){return"\f"},zi="\\n",Ol=me("\\n",!1),Xe=function(){return` `},pa="\\r",pg=me("\\r",!1),ME=function(){return"\r"},Dp="\\t",OE=me("\\t",!1),ar=function(){return" "},Tn="\\v",Kl=me("\\v",!1),kp=function(){return"\v"},Us=/^[\\'"?]/,da=Je(["\\","'",'"',"?"],!1,!1),cn=function(m){return String.fromCharCode(parseInt(m,16))},Le="\\x",dg=me("\\x",!1),Ul="\\u",Hs=me("\\u",!1),Hl="\\U",yA=me("\\U",!1),Cg=function(m){return String.fromCodePoint(parseInt(m,16))},mg=/^[0-7]/,Ca=Je([["0","7"]],!1,!1),ma=/^[0-9a-fA-f]/,rt=Je([["0","9"],["a","f"],["A","f"]],!1,!1),bo=nt(),wA="-",Gl=me("-",!1),Gs="+",Yl=me("+",!1),KE=".",Rp=me(".",!1),Eg=function(m,Q,N){return{type:"number",value:(m==="-"?-1:1)*parseFloat(Q.join("")+"."+N.join(""))}},Fp=function(m,Q){return{type:"number",value:(m==="-"?-1:1)*parseInt(Q.join(""))}},UE=function(m){return{type:"variable",...m}},jl=function(m){return{type:"variable",name:m}},HE=function(m){return m},Ig="*",BA=me("*",!1),Rr="/",GE=me("/",!1),Ys=function(m,Q,N){return{type:Q==="*"?"multiplication":"division",right:N}},js=function(m,Q){return Q.reduce((N,U)=>({left:N,...U}),m)},yg=function(m,Q,N){return{type:Q==="+"?"addition":"subtraction",right:N}},bA="$((",R=me("$((",!1),q="))",Ce=me("))",!1),Ke=function(m){return m},Re="$(",ze=me("$(",!1),dt=function(m){return m},Ft="${",Ln=me("${",!1),JQ=":-",k1=me(":-",!1),R1=function(m,Q){return{name:m,defaultValue:Q}},WQ=":-}",F1=me(":-}",!1),N1=function(m){return{name:m,defaultValue:[]}},zQ=":+",T1=me(":+",!1),L1=function(m,Q){return{name:m,alternativeValue:Q}},VQ=":+}",M1=me(":+}",!1),O1=function(m){return{name:m,alternativeValue:[]}},XQ=function(m){return{name:m}},K1="$",U1=me("$",!1),H1=function(m){return e.isGlobPattern(m)},G1=function(m){return m},ZQ=/^[a-zA-Z0-9_]/,_Q=Je([["a","z"],["A","Z"],["0","9"],"_"],!1,!1),$Q=function(){return L()},eS=/^[$@*?#a-zA-Z0-9_\-]/,tS=Je(["$","@","*","?","#",["a","z"],["A","Z"],["0","9"],"_","-"],!1,!1),Y1=/^[(){}<>$|&; \t"']/,wg=Je(["(",")","{","}","<",">","$","|","&",";"," "," ",'"',"'"],!1,!1),rS=/^[<>&; \t"']/,iS=Je(["<",">","&",";"," "," ",'"',"'"],!1,!1),YE=/^[ \t]/,jE=Je([" "," "],!1,!1),b=0,Oe=0,QA=[{line:1,column:1}],d=0,E=[],I=0,k;if("startRule"in e){if(!(e.startRule in i))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');n=i[e.startRule]}function L(){return r.substring(Oe,b)}function Z(){return Et(Oe,b)}function te(m,Q){throw Q=Q!==void 0?Q:Et(Oe,b),Ri([lt(m)],r.substring(Oe,b),Q)}function we(m,Q){throw Q=Q!==void 0?Q:Et(Oe,b),Mn(m,Q)}function me(m,Q){return{type:"literal",text:m,ignoreCase:Q}}function Je(m,Q,N){return{type:"class",parts:m,inverted:Q,ignoreCase:N}}function nt(){return{type:"any"}}function wt(){return{type:"end"}}function lt(m){return{type:"other",description:m}}function it(m){var Q=QA[m],N;if(Q)return Q;for(N=m-1;!QA[N];)N--;for(Q=QA[N],Q={line:Q.line,column:Q.column};Nd&&(d=b,E=[]),E.push(m))}function Mn(m,Q){return new Zl(m,null,null,Q)}function Ri(m,Q,N){return new Zl(Zl.buildMessage(m,Q),m,Q,N)}function SA(){var m,Q;return m=b,Q=Or(),Q===t&&(Q=null),Q!==t&&(Oe=m,Q=s(Q)),m=Q,m}function Or(){var m,Q,N,U,ce;if(m=b,Q=Kr(),Q!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();N!==t?(U=Ea(),U!==t?(ce=os(),ce===t&&(ce=null),ce!==t?(Oe=m,Q=o(Q,U,ce),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)}else b=m,m=t;if(m===t)if(m=b,Q=Kr(),Q!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();N!==t?(U=Ea(),U===t&&(U=null),U!==t?(Oe=m,Q=a(Q,U),m=Q):(b=m,m=t)):(b=m,m=t)}else b=m,m=t;return m}function os(){var m,Q,N,U,ce;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t)if(N=Or(),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Oe=m,Q=l(N),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t;return m}function Ea(){var m;return r.charCodeAt(b)===59?(m=c,b++):(m=t,I===0&&be(u)),m===t&&(r.charCodeAt(b)===38?(m=g,b++):(m=t,I===0&&be(f))),m}function Kr(){var m,Q,N;return m=b,Q=j1(),Q!==t?(N=fge(),N===t&&(N=null),N!==t?(Oe=m,Q=h(Q,N),m=Q):(b=m,m=t)):(b=m,m=t),m}function fge(){var m,Q,N,U,ce,Se,ht;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t)if(N=hge(),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=Kr(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();Se!==t?(Oe=m,Q=p(N,ce),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t;return m}function hge(){var m;return r.substr(b,2)===C?(m=C,b+=2):(m=t,I===0&&be(y)),m===t&&(r.substr(b,2)===B?(m=B,b+=2):(m=t,I===0&&be(v))),m}function j1(){var m,Q,N;return m=b,Q=Cge(),Q!==t?(N=pge(),N===t&&(N=null),N!==t?(Oe=m,Q=D(Q,N),m=Q):(b=m,m=t)):(b=m,m=t),m}function pge(){var m,Q,N,U,ce,Se,ht;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t)if(N=dge(),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=j1(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();Se!==t?(Oe=m,Q=T(N,ce),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t;return m}function dge(){var m;return r.substr(b,2)===H?(m=H,b+=2):(m=t,I===0&&be(j)),m===t&&(r.charCodeAt(b)===124?(m=$,b++):(m=t,I===0&&be(V))),m}function qE(){var m,Q,N,U,ce,Se;if(m=b,Q=rK(),Q!==t)if(r.charCodeAt(b)===61?(N=W,b++):(N=t,I===0&&be(_)),N!==t)if(U=W1(),U!==t){for(ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();ce!==t?(Oe=m,Q=A(Q,U),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t;else b=m,m=t;if(m===t)if(m=b,Q=rK(),Q!==t)if(r.charCodeAt(b)===61?(N=W,b++):(N=t,I===0&&be(_)),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Oe=m,Q=Ae(Q),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t;return m}function Cge(){var m,Q,N,U,ce,Se,ht,Bt,qr,hi,as;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t)if(r.charCodeAt(b)===40?(N=ge,b++):(N=t,I===0&&be(re)),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=Or(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();if(Se!==t)if(r.charCodeAt(b)===41?(ht=M,b++):(ht=t,I===0&&be(F)),ht!==t){for(Bt=[],qr=He();qr!==t;)Bt.push(qr),qr=He();if(Bt!==t){for(qr=[],hi=Np();hi!==t;)qr.push(hi),hi=Np();if(qr!==t){for(hi=[],as=He();as!==t;)hi.push(as),as=He();hi!==t?(Oe=m,Q=ue(ce,qr),m=Q):(b=m,m=t)}else b=m,m=t}else b=m,m=t}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t;if(m===t){for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t)if(r.charCodeAt(b)===123?(N=pe,b++):(N=t,I===0&&be(ke)),N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t)if(ce=Or(),ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();if(Se!==t)if(r.charCodeAt(b)===125?(ht=Fe,b++):(ht=t,I===0&&be(Ne)),ht!==t){for(Bt=[],qr=He();qr!==t;)Bt.push(qr),qr=He();if(Bt!==t){for(qr=[],hi=Np();hi!==t;)qr.push(hi),hi=Np();if(qr!==t){for(hi=[],as=He();as!==t;)hi.push(as),as=He();hi!==t?(Oe=m,Q=oe(ce,qr),m=Q):(b=m,m=t)}else b=m,m=t}else b=m,m=t}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t}else b=m,m=t;else b=m,m=t;if(m===t){for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t){for(N=[],U=qE();U!==t;)N.push(U),U=qE();if(N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();if(U!==t){if(ce=[],Se=J1(),Se!==t)for(;Se!==t;)ce.push(Se),Se=J1();else ce=t;if(ce!==t){for(Se=[],ht=He();ht!==t;)Se.push(ht),ht=He();Se!==t?(Oe=m,Q=le(N,ce),m=Q):(b=m,m=t)}else b=m,m=t}else b=m,m=t}else b=m,m=t}else b=m,m=t;if(m===t){for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t){if(N=[],U=qE(),U!==t)for(;U!==t;)N.push(U),U=qE();else N=t;if(N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Oe=m,Q=Be(N),m=Q):(b=m,m=t)}else b=m,m=t}else b=m,m=t}}}return m}function q1(){var m,Q,N,U,ce;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t){if(N=[],U=JE(),U!==t)for(;U!==t;)N.push(U),U=JE();else N=t;if(N!==t){for(U=[],ce=He();ce!==t;)U.push(ce),ce=He();U!==t?(Oe=m,Q=fe(N),m=Q):(b=m,m=t)}else b=m,m=t}else b=m,m=t;return m}function J1(){var m,Q,N;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();if(Q!==t?(N=Np(),N!==t?(Oe=m,Q=ae(N),m=Q):(b=m,m=t)):(b=m,m=t),m===t){for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();Q!==t?(N=JE(),N!==t?(Oe=m,Q=ae(N),m=Q):(b=m,m=t)):(b=m,m=t)}return m}function Np(){var m,Q,N,U,ce;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();return Q!==t?(qe.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(ne)),N===t&&(N=null),N!==t?(U=mge(),U!==t?(ce=JE(),ce!==t?(Oe=m,Q=Y(N,U,ce),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m}function mge(){var m;return r.substr(b,2)===he?(m=he,b+=2):(m=t,I===0&&be(ie)),m===t&&(r.substr(b,2)===de?(m=de,b+=2):(m=t,I===0&&be(_e)),m===t&&(r.charCodeAt(b)===62?(m=Pt,b++):(m=t,I===0&&be(It)),m===t&&(r.substr(b,3)===Mr?(m=Mr,b+=3):(m=t,I===0&&be(ii)),m===t&&(r.substr(b,2)===gi?(m=gi,b+=2):(m=t,I===0&&be(hr)),m===t&&(r.charCodeAt(b)===60?(m=fi,b++):(m=t,I===0&&be(ni))))))),m}function JE(){var m,Q,N;for(m=b,Q=[],N=He();N!==t;)Q.push(N),N=He();return Q!==t?(N=W1(),N!==t?(Oe=m,Q=ae(N),m=Q):(b=m,m=t)):(b=m,m=t),m}function W1(){var m,Q,N;if(m=b,Q=[],N=z1(),N!==t)for(;N!==t;)Q.push(N),N=z1();else Q=t;return Q!==t&&(Oe=m,Q=Ks(Q)),m=Q,m}function z1(){var m,Q;return m=b,Q=Ege(),Q!==t&&(Oe=m,Q=pr(Q)),m=Q,m===t&&(m=b,Q=Ige(),Q!==t&&(Oe=m,Q=pr(Q)),m=Q,m===t&&(m=b,Q=yge(),Q!==t&&(Oe=m,Q=pr(Q)),m=Q,m===t&&(m=b,Q=wge(),Q!==t&&(Oe=m,Q=pr(Q)),m=Q))),m}function Ege(){var m,Q,N,U;return m=b,r.substr(b,2)===Ii?(Q=Ii,b+=2):(Q=t,I===0&&be(rs)),Q!==t?(N=Qge(),N!==t?(r.charCodeAt(b)===39?(U=fa,b++):(U=t,I===0&&be(dA)),U!==t?(Oe=m,Q=cg(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m}function Ige(){var m,Q,N,U;return m=b,r.charCodeAt(b)===39?(Q=fa,b++):(Q=t,I===0&&be(dA)),Q!==t?(N=Bge(),N!==t?(r.charCodeAt(b)===39?(U=fa,b++):(U=t,I===0&&be(dA)),U!==t?(Oe=m,Q=cg(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m}function yge(){var m,Q,N,U;if(m=b,r.substr(b,2)===is?(Q=is,b+=2):(Q=t,I===0&&be(CA)),Q!==t&&(Oe=m,Q=ha()),m=Q,m===t)if(m=b,r.charCodeAt(b)===34?(Q=wp,b++):(Q=t,I===0&&be(mA)),Q!==t){for(N=[],U=V1();U!==t;)N.push(U),U=V1();N!==t?(r.charCodeAt(b)===34?(U=wp,b++):(U=t,I===0&&be(mA)),U!==t?(Oe=m,Q=EA(N),m=Q):(b=m,m=t)):(b=m,m=t)}else b=m,m=t;return m}function wge(){var m,Q,N;if(m=b,Q=[],N=X1(),N!==t)for(;N!==t;)Q.push(N),N=X1();else Q=t;return Q!==t&&(Oe=m,Q=EA(Q)),m=Q,m}function V1(){var m,Q;return m=b,Q=eK(),Q!==t&&(Oe=m,Q=wr(Q)),m=Q,m===t&&(m=b,Q=tK(),Q!==t&&(Oe=m,Q=Tl(Q)),m=Q,m===t&&(m=b,Q=aS(),Q!==t&&(Oe=m,Q=ug(Q)),m=Q,m===t&&(m=b,Q=bge(),Q!==t&&(Oe=m,Q=yo(Q)),m=Q))),m}function X1(){var m,Q;return m=b,Q=eK(),Q!==t&&(Oe=m,Q=gg(Q)),m=Q,m===t&&(m=b,Q=tK(),Q!==t&&(Oe=m,Q=Bp(Q)),m=Q,m===t&&(m=b,Q=aS(),Q!==t&&(Oe=m,Q=bp(Q)),m=Q,m===t&&(m=b,Q=xge(),Q!==t&&(Oe=m,Q=vr(Q)),m=Q,m===t&&(m=b,Q=vge(),Q!==t&&(Oe=m,Q=yo(Q)),m=Q)))),m}function Bge(){var m,Q,N;for(m=b,Q=[],se.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(wo));N!==t;)Q.push(N),se.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(wo));return Q!==t&&(Oe=m,Q=Fn(Q)),m=Q,m}function bge(){var m,Q,N;if(m=b,Q=[],N=Z1(),N===t&&(fg.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(bt))),N!==t)for(;N!==t;)Q.push(N),N=Z1(),N===t&&(fg.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(bt)));else Q=t;return Q!==t&&(Oe=m,Q=Fn(Q)),m=Q,m}function Z1(){var m,Q,N;return m=b,r.substr(b,2)===Ll?(Q=Ll,b+=2):(Q=t,I===0&&be(Nn)),Q!==t&&(Oe=m,Q=ns()),m=Q,m===t&&(m=b,r.charCodeAt(b)===92?(Q=ss,b++):(Q=t,I===0&&be(gt)),Q!==t?(Bo.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(At)),N!==t?(Oe=m,Q=ln(N),m=Q):(b=m,m=t)):(b=m,m=t)),m}function Qge(){var m,Q,N;for(m=b,Q=[],N=_1(),N===t&&(se.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(wo)));N!==t;)Q.push(N),N=_1(),N===t&&(se.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(wo)));return Q!==t&&(Oe=m,Q=Fn(Q)),m=Q,m}function _1(){var m,Q,N;return m=b,r.substr(b,2)===S?(Q=S,b+=2):(Q=t,I===0&&be(Lt)),Q!==t&&(Oe=m,Q=hg()),m=Q,m===t&&(m=b,r.substr(b,2)===Ml?(Q=Ml,b+=2):(Q=t,I===0&&be(Qp)),Q!==t&&(Oe=m,Q=Sp()),m=Q,m===t&&(m=b,r.charCodeAt(b)===92?(Q=ss,b++):(Q=t,I===0&&be(gt)),Q!==t?(vp.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(xp)),N!==t?(Oe=m,Q=Pp(),m=Q):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===G?(Q=G,b+=2):(Q=t,I===0&&be(yt)),Q!==t&&(Oe=m,Q=IA()),m=Q,m===t&&(m=b,r.substr(b,2)===zi?(Q=zi,b+=2):(Q=t,I===0&&be(Ol)),Q!==t&&(Oe=m,Q=Xe()),m=Q,m===t&&(m=b,r.substr(b,2)===pa?(Q=pa,b+=2):(Q=t,I===0&&be(pg)),Q!==t&&(Oe=m,Q=ME()),m=Q,m===t&&(m=b,r.substr(b,2)===Dp?(Q=Dp,b+=2):(Q=t,I===0&&be(OE)),Q!==t&&(Oe=m,Q=ar()),m=Q,m===t&&(m=b,r.substr(b,2)===Tn?(Q=Tn,b+=2):(Q=t,I===0&&be(Kl)),Q!==t&&(Oe=m,Q=kp()),m=Q,m===t&&(m=b,r.charCodeAt(b)===92?(Q=ss,b++):(Q=t,I===0&&be(gt)),Q!==t?(Us.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(da)),N!==t?(Oe=m,Q=ln(N),m=Q):(b=m,m=t)):(b=m,m=t),m===t&&(m=Sge()))))))))),m}function Sge(){var m,Q,N,U,ce,Se,ht,Bt,qr,hi,as,AS;return m=b,r.charCodeAt(b)===92?(Q=ss,b++):(Q=t,I===0&&be(gt)),Q!==t?(N=nS(),N!==t?(Oe=m,Q=cn(N),m=Q):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Le?(Q=Le,b+=2):(Q=t,I===0&&be(dg)),Q!==t?(N=b,U=b,ce=nS(),ce!==t?(Se=On(),Se!==t?(ce=[ce,Se],U=ce):(b=U,U=t)):(b=U,U=t),U===t&&(U=nS()),U!==t?N=r.substring(N,b):N=U,N!==t?(Oe=m,Q=cn(N),m=Q):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Ul?(Q=Ul,b+=2):(Q=t,I===0&&be(Hs)),Q!==t?(N=b,U=b,ce=On(),ce!==t?(Se=On(),Se!==t?(ht=On(),ht!==t?(Bt=On(),Bt!==t?(ce=[ce,Se,ht,Bt],U=ce):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t),U!==t?N=r.substring(N,b):N=U,N!==t?(Oe=m,Q=cn(N),m=Q):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Hl?(Q=Hl,b+=2):(Q=t,I===0&&be(yA)),Q!==t?(N=b,U=b,ce=On(),ce!==t?(Se=On(),Se!==t?(ht=On(),ht!==t?(Bt=On(),Bt!==t?(qr=On(),qr!==t?(hi=On(),hi!==t?(as=On(),as!==t?(AS=On(),AS!==t?(ce=[ce,Se,ht,Bt,qr,hi,as,AS],U=ce):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t)):(b=U,U=t),U!==t?N=r.substring(N,b):N=U,N!==t?(Oe=m,Q=Cg(N),m=Q):(b=m,m=t)):(b=m,m=t)))),m}function nS(){var m;return mg.test(r.charAt(b))?(m=r.charAt(b),b++):(m=t,I===0&&be(Ca)),m}function On(){var m;return ma.test(r.charAt(b))?(m=r.charAt(b),b++):(m=t,I===0&&be(rt)),m}function vge(){var m,Q,N,U,ce;if(m=b,Q=[],N=b,r.charCodeAt(b)===92?(U=ss,b++):(U=t,I===0&&be(gt)),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Oe=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t),N===t&&(N=b,U=b,I++,ce=iK(),I--,ce===t?U=void 0:(b=U,U=t),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Oe=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t)),N!==t)for(;N!==t;)Q.push(N),N=b,r.charCodeAt(b)===92?(U=ss,b++):(U=t,I===0&&be(gt)),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Oe=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t),N===t&&(N=b,U=b,I++,ce=iK(),I--,ce===t?U=void 0:(b=U,U=t),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Oe=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t));else Q=t;return Q!==t&&(Oe=m,Q=Fn(Q)),m=Q,m}function sS(){var m,Q,N,U,ce,Se;if(m=b,r.charCodeAt(b)===45?(Q=wA,b++):(Q=t,I===0&&be(Gl)),Q===t&&(r.charCodeAt(b)===43?(Q=Gs,b++):(Q=t,I===0&&be(Yl))),Q===t&&(Q=null),Q!==t){if(N=[],qe.test(r.charAt(b))?(U=r.charAt(b),b++):(U=t,I===0&&be(ne)),U!==t)for(;U!==t;)N.push(U),qe.test(r.charAt(b))?(U=r.charAt(b),b++):(U=t,I===0&&be(ne));else N=t;if(N!==t)if(r.charCodeAt(b)===46?(U=KE,b++):(U=t,I===0&&be(Rp)),U!==t){if(ce=[],qe.test(r.charAt(b))?(Se=r.charAt(b),b++):(Se=t,I===0&&be(ne)),Se!==t)for(;Se!==t;)ce.push(Se),qe.test(r.charAt(b))?(Se=r.charAt(b),b++):(Se=t,I===0&&be(ne));else ce=t;ce!==t?(Oe=m,Q=Eg(Q,N,ce),m=Q):(b=m,m=t)}else b=m,m=t;else b=m,m=t}else b=m,m=t;if(m===t){if(m=b,r.charCodeAt(b)===45?(Q=wA,b++):(Q=t,I===0&&be(Gl)),Q===t&&(r.charCodeAt(b)===43?(Q=Gs,b++):(Q=t,I===0&&be(Yl))),Q===t&&(Q=null),Q!==t){if(N=[],qe.test(r.charAt(b))?(U=r.charAt(b),b++):(U=t,I===0&&be(ne)),U!==t)for(;U!==t;)N.push(U),qe.test(r.charAt(b))?(U=r.charAt(b),b++):(U=t,I===0&&be(ne));else N=t;N!==t?(Oe=m,Q=Fp(Q,N),m=Q):(b=m,m=t)}else b=m,m=t;if(m===t&&(m=b,Q=aS(),Q!==t&&(Oe=m,Q=UE(Q)),m=Q,m===t&&(m=b,Q=ql(),Q!==t&&(Oe=m,Q=jl(Q)),m=Q,m===t)))if(m=b,r.charCodeAt(b)===40?(Q=ge,b++):(Q=t,I===0&&be(re)),Q!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();if(N!==t)if(U=$1(),U!==t){for(ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();ce!==t?(r.charCodeAt(b)===41?(Se=M,b++):(Se=t,I===0&&be(F)),Se!==t?(Oe=m,Q=HE(U),m=Q):(b=m,m=t)):(b=m,m=t)}else b=m,m=t;else b=m,m=t}else b=m,m=t}return m}function oS(){var m,Q,N,U,ce,Se,ht,Bt;if(m=b,Q=sS(),Q!==t){for(N=[],U=b,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(b)===42?(Se=Ig,b++):(Se=t,I===0&&be(BA)),Se===t&&(r.charCodeAt(b)===47?(Se=Rr,b++):(Se=t,I===0&&be(GE))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=sS(),Bt!==t?(Oe=U,ce=Ys(Q,Se,Bt),U=ce):(b=U,U=t)):(b=U,U=t)}else b=U,U=t;else b=U,U=t;for(;U!==t;){for(N.push(U),U=b,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(b)===42?(Se=Ig,b++):(Se=t,I===0&&be(BA)),Se===t&&(r.charCodeAt(b)===47?(Se=Rr,b++):(Se=t,I===0&&be(GE))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=sS(),Bt!==t?(Oe=U,ce=Ys(Q,Se,Bt),U=ce):(b=U,U=t)):(b=U,U=t)}else b=U,U=t;else b=U,U=t}N!==t?(Oe=m,Q=js(Q,N),m=Q):(b=m,m=t)}else b=m,m=t;return m}function $1(){var m,Q,N,U,ce,Se,ht,Bt;if(m=b,Q=oS(),Q!==t){for(N=[],U=b,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(b)===43?(Se=Gs,b++):(Se=t,I===0&&be(Yl)),Se===t&&(r.charCodeAt(b)===45?(Se=wA,b++):(Se=t,I===0&&be(Gl))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=oS(),Bt!==t?(Oe=U,ce=yg(Q,Se,Bt),U=ce):(b=U,U=t)):(b=U,U=t)}else b=U,U=t;else b=U,U=t;for(;U!==t;){for(N.push(U),U=b,ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();if(ce!==t)if(r.charCodeAt(b)===43?(Se=Gs,b++):(Se=t,I===0&&be(Yl)),Se===t&&(r.charCodeAt(b)===45?(Se=wA,b++):(Se=t,I===0&&be(Gl))),Se!==t){for(ht=[],Bt=He();Bt!==t;)ht.push(Bt),Bt=He();ht!==t?(Bt=oS(),Bt!==t?(Oe=U,ce=yg(Q,Se,Bt),U=ce):(b=U,U=t)):(b=U,U=t)}else b=U,U=t;else b=U,U=t}N!==t?(Oe=m,Q=js(Q,N),m=Q):(b=m,m=t)}else b=m,m=t;return m}function eK(){var m,Q,N,U,ce,Se;if(m=b,r.substr(b,3)===bA?(Q=bA,b+=3):(Q=t,I===0&&be(R)),Q!==t){for(N=[],U=He();U!==t;)N.push(U),U=He();if(N!==t)if(U=$1(),U!==t){for(ce=[],Se=He();Se!==t;)ce.push(Se),Se=He();ce!==t?(r.substr(b,2)===q?(Se=q,b+=2):(Se=t,I===0&&be(Ce)),Se!==t?(Oe=m,Q=Ke(U),m=Q):(b=m,m=t)):(b=m,m=t)}else b=m,m=t;else b=m,m=t}else b=m,m=t;return m}function tK(){var m,Q,N,U;return m=b,r.substr(b,2)===Re?(Q=Re,b+=2):(Q=t,I===0&&be(ze)),Q!==t?(N=Or(),N!==t?(r.charCodeAt(b)===41?(U=M,b++):(U=t,I===0&&be(F)),U!==t?(Oe=m,Q=dt(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m}function aS(){var m,Q,N,U,ce,Se;return m=b,r.substr(b,2)===Ft?(Q=Ft,b+=2):(Q=t,I===0&&be(Ln)),Q!==t?(N=ql(),N!==t?(r.substr(b,2)===JQ?(U=JQ,b+=2):(U=t,I===0&&be(k1)),U!==t?(ce=q1(),ce!==t?(r.charCodeAt(b)===125?(Se=Fe,b++):(Se=t,I===0&&be(Ne)),Se!==t?(Oe=m,Q=R1(N,ce),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Ft?(Q=Ft,b+=2):(Q=t,I===0&&be(Ln)),Q!==t?(N=ql(),N!==t?(r.substr(b,3)===WQ?(U=WQ,b+=3):(U=t,I===0&&be(F1)),U!==t?(Oe=m,Q=N1(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Ft?(Q=Ft,b+=2):(Q=t,I===0&&be(Ln)),Q!==t?(N=ql(),N!==t?(r.substr(b,2)===zQ?(U=zQ,b+=2):(U=t,I===0&&be(T1)),U!==t?(ce=q1(),ce!==t?(r.charCodeAt(b)===125?(Se=Fe,b++):(Se=t,I===0&&be(Ne)),Se!==t?(Oe=m,Q=L1(N,ce),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Ft?(Q=Ft,b+=2):(Q=t,I===0&&be(Ln)),Q!==t?(N=ql(),N!==t?(r.substr(b,3)===VQ?(U=VQ,b+=3):(U=t,I===0&&be(M1)),U!==t?(Oe=m,Q=O1(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.substr(b,2)===Ft?(Q=Ft,b+=2):(Q=t,I===0&&be(Ln)),Q!==t?(N=ql(),N!==t?(r.charCodeAt(b)===125?(U=Fe,b++):(U=t,I===0&&be(Ne)),U!==t?(Oe=m,Q=XQ(N),m=Q):(b=m,m=t)):(b=m,m=t)):(b=m,m=t),m===t&&(m=b,r.charCodeAt(b)===36?(Q=K1,b++):(Q=t,I===0&&be(U1)),Q!==t?(N=ql(),N!==t?(Oe=m,Q=XQ(N),m=Q):(b=m,m=t)):(b=m,m=t)))))),m}function xge(){var m,Q,N;return m=b,Q=Pge(),Q!==t?(Oe=b,N=H1(Q),N?N=void 0:N=t,N!==t?(Oe=m,Q=G1(Q),m=Q):(b=m,m=t)):(b=m,m=t),m}function Pge(){var m,Q,N,U,ce;if(m=b,Q=[],N=b,U=b,I++,ce=nK(),I--,ce===t?U=void 0:(b=U,U=t),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Oe=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t),N!==t)for(;N!==t;)Q.push(N),N=b,U=b,I++,ce=nK(),I--,ce===t?U=void 0:(b=U,U=t),U!==t?(r.length>b?(ce=r.charAt(b),b++):(ce=t,I===0&&be(bo)),ce!==t?(Oe=N,U=ln(ce),N=U):(b=N,N=t)):(b=N,N=t);else Q=t;return Q!==t&&(Oe=m,Q=Fn(Q)),m=Q,m}function rK(){var m,Q,N;if(m=b,Q=[],ZQ.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(_Q)),N!==t)for(;N!==t;)Q.push(N),ZQ.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(_Q));else Q=t;return Q!==t&&(Oe=m,Q=$Q()),m=Q,m}function ql(){var m,Q,N;if(m=b,Q=[],eS.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(tS)),N!==t)for(;N!==t;)Q.push(N),eS.test(r.charAt(b))?(N=r.charAt(b),b++):(N=t,I===0&&be(tS));else Q=t;return Q!==t&&(Oe=m,Q=$Q()),m=Q,m}function iK(){var m;return Y1.test(r.charAt(b))?(m=r.charAt(b),b++):(m=t,I===0&&be(wg)),m}function nK(){var m;return rS.test(r.charAt(b))?(m=r.charAt(b),b++):(m=t,I===0&&be(iS)),m}function He(){var m,Q;if(m=[],YE.test(r.charAt(b))?(Q=r.charAt(b),b++):(Q=t,I===0&&be(jE)),Q!==t)for(;Q!==t;)m.push(Q),YE.test(r.charAt(b))?(Q=r.charAt(b),b++):(Q=t,I===0&&be(jE));else m=t;return m}if(k=n(),k!==t&&b===r.length)return k;throw k!==t&&b{"use strict";function Dfe(r,e){function t(){this.constructor=r}t.prototype=e.prototype,r.prototype=new t}function $l(r,e,t,i){this.message=r,this.expected=e,this.found=t,this.location=i,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,$l)}Dfe($l,Error);$l.buildMessage=function(r,e){var t={literal:function(c){return'"'+n(c.text)+'"'},class:function(c){var u="",g;for(g=0;g0){for(g=1,f=1;gH&&(H=v,j=[]),j.push(ne))}function Ne(ne,Y){return new $l(ne,null,null,Y)}function oe(ne,Y,he){return new $l($l.buildMessage(ne,Y),ne,Y,he)}function le(){var ne,Y,he,ie;return ne=v,Y=Be(),Y!==t?(r.charCodeAt(v)===47?(he=s,v++):(he=t,$===0&&Fe(o)),he!==t?(ie=Be(),ie!==t?(D=ne,Y=a(Y,ie),ne=Y):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t),ne===t&&(ne=v,Y=Be(),Y!==t&&(D=ne,Y=l(Y)),ne=Y),ne}function Be(){var ne,Y,he,ie;return ne=v,Y=fe(),Y!==t?(r.charCodeAt(v)===64?(he=c,v++):(he=t,$===0&&Fe(u)),he!==t?(ie=qe(),ie!==t?(D=ne,Y=g(Y,ie),ne=Y):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t),ne===t&&(ne=v,Y=fe(),Y!==t&&(D=ne,Y=f(Y)),ne=Y),ne}function fe(){var ne,Y,he,ie,de;return ne=v,r.charCodeAt(v)===64?(Y=c,v++):(Y=t,$===0&&Fe(u)),Y!==t?(he=ae(),he!==t?(r.charCodeAt(v)===47?(ie=s,v++):(ie=t,$===0&&Fe(o)),ie!==t?(de=ae(),de!==t?(D=ne,Y=h(),ne=Y):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t)):(v=ne,ne=t),ne===t&&(ne=v,Y=ae(),Y!==t&&(D=ne,Y=h()),ne=Y),ne}function ae(){var ne,Y,he;if(ne=v,Y=[],p.test(r.charAt(v))?(he=r.charAt(v),v++):(he=t,$===0&&Fe(C)),he!==t)for(;he!==t;)Y.push(he),p.test(r.charAt(v))?(he=r.charAt(v),v++):(he=t,$===0&&Fe(C));else Y=t;return Y!==t&&(D=ne,Y=h()),ne=Y,ne}function qe(){var ne,Y,he;if(ne=v,Y=[],y.test(r.charAt(v))?(he=r.charAt(v),v++):(he=t,$===0&&Fe(B)),he!==t)for(;he!==t;)Y.push(he),y.test(r.charAt(v))?(he=r.charAt(v),v++):(he=t,$===0&&Fe(B));else Y=t;return Y!==t&&(D=ne,Y=h()),ne=Y,ne}if(V=n(),V!==t&&v===r.length)return V;throw V!==t&&v{"use strict";function mU(r){return typeof r>"u"||r===null}function Rfe(r){return typeof r=="object"&&r!==null}function Ffe(r){return Array.isArray(r)?r:mU(r)?[]:[r]}function Nfe(r,e){var t,i,n,s;if(e)for(s=Object.keys(e),t=0,i=s.length;t{"use strict";function Vp(r,e){Error.call(this),this.name="YAMLException",this.reason=r,this.mark=e,this.message=(this.reason||"(unknown reason)")+(this.mark?" "+this.mark.toString():""),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack||""}Vp.prototype=Object.create(Error.prototype);Vp.prototype.constructor=Vp;Vp.prototype.toString=function(e){var t=this.name+": ";return t+=this.reason||"(unknown reason)",!e&&this.mark&&(t+=" "+this.mark.toString()),t};EU.exports=Vp});var wU=w((SZe,yU)=>{"use strict";var IU=tc();function HS(r,e,t,i,n){this.name=r,this.buffer=e,this.position=t,this.line=i,this.column=n}HS.prototype.getSnippet=function(e,t){var i,n,s,o,a;if(!this.buffer)return null;for(e=e||4,t=t||75,i="",n=this.position;n>0&&`\0\r \x85\u2028\u2029`.indexOf(this.buffer.charAt(n-1))===-1;)if(n-=1,this.position-n>t/2-1){i=" ... ",n+=5;break}for(s="",o=this.position;ot/2-1){s=" ... ",o-=5;break}return a=this.buffer.slice(n,o),IU.repeat(" ",e)+i+a+s+` `+IU.repeat(" ",e+this.position-n+i.length)+"^"};HS.prototype.toString=function(e){var t,i="";return this.name&&(i+='in "'+this.name+'" '),i+="at line "+(this.line+1)+", column "+(this.column+1),e||(t=this.getSnippet(),t&&(i+=`: `+t)),i};yU.exports=HS});var si=w((vZe,bU)=>{"use strict";var BU=Ng(),Mfe=["kind","resolve","construct","instanceOf","predicate","represent","defaultStyle","styleAliases"],Ofe=["scalar","sequence","mapping"];function Kfe(r){var e={};return r!==null&&Object.keys(r).forEach(function(t){r[t].forEach(function(i){e[String(i)]=t})}),e}function Ufe(r,e){if(e=e||{},Object.keys(e).forEach(function(t){if(Mfe.indexOf(t)===-1)throw new BU('Unknown option "'+t+'" is met in definition of "'+r+'" YAML type.')}),this.tag=r,this.kind=e.kind||null,this.resolve=e.resolve||function(){return!0},this.construct=e.construct||function(t){return t},this.instanceOf=e.instanceOf||null,this.predicate=e.predicate||null,this.represent=e.represent||null,this.defaultStyle=e.defaultStyle||null,this.styleAliases=Kfe(e.styleAliases||null),Ofe.indexOf(this.kind)===-1)throw new BU('Unknown kind "'+this.kind+'" is specified for "'+r+'" YAML type.')}bU.exports=Ufe});var rc=w((xZe,SU)=>{"use strict";var QU=tc(),dI=Ng(),Hfe=si();function GS(r,e,t){var i=[];return r.include.forEach(function(n){t=GS(n,e,t)}),r[e].forEach(function(n){t.forEach(function(s,o){s.tag===n.tag&&s.kind===n.kind&&i.push(o)}),t.push(n)}),t.filter(function(n,s){return i.indexOf(s)===-1})}function Gfe(){var r={scalar:{},sequence:{},mapping:{},fallback:{}},e,t;function i(n){r[n.kind][n.tag]=r.fallback[n.tag]=n}for(e=0,t=arguments.length;e{"use strict";var Yfe=si();vU.exports=new Yfe("tag:yaml.org,2002:str",{kind:"scalar",construct:function(r){return r!==null?r:""}})});var DU=w((DZe,PU)=>{"use strict";var jfe=si();PU.exports=new jfe("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(r){return r!==null?r:[]}})});var RU=w((kZe,kU)=>{"use strict";var qfe=si();kU.exports=new qfe("tag:yaml.org,2002:map",{kind:"mapping",construct:function(r){return r!==null?r:{}}})});var CI=w((RZe,FU)=>{"use strict";var Jfe=rc();FU.exports=new Jfe({explicit:[xU(),DU(),RU()]})});var TU=w((FZe,NU)=>{"use strict";var Wfe=si();function zfe(r){if(r===null)return!0;var e=r.length;return e===1&&r==="~"||e===4&&(r==="null"||r==="Null"||r==="NULL")}function Vfe(){return null}function Xfe(r){return r===null}NU.exports=new Wfe("tag:yaml.org,2002:null",{kind:"scalar",resolve:zfe,construct:Vfe,predicate:Xfe,represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})});var MU=w((NZe,LU)=>{"use strict";var Zfe=si();function _fe(r){if(r===null)return!1;var e=r.length;return e===4&&(r==="true"||r==="True"||r==="TRUE")||e===5&&(r==="false"||r==="False"||r==="FALSE")}function $fe(r){return r==="true"||r==="True"||r==="TRUE"}function ehe(r){return Object.prototype.toString.call(r)==="[object Boolean]"}LU.exports=new Zfe("tag:yaml.org,2002:bool",{kind:"scalar",resolve:_fe,construct:$fe,predicate:ehe,represent:{lowercase:function(r){return r?"true":"false"},uppercase:function(r){return r?"TRUE":"FALSE"},camelcase:function(r){return r?"True":"False"}},defaultStyle:"lowercase"})});var KU=w((TZe,OU)=>{"use strict";var the=tc(),rhe=si();function ihe(r){return 48<=r&&r<=57||65<=r&&r<=70||97<=r&&r<=102}function nhe(r){return 48<=r&&r<=55}function she(r){return 48<=r&&r<=57}function ohe(r){if(r===null)return!1;var e=r.length,t=0,i=!1,n;if(!e)return!1;if(n=r[t],(n==="-"||n==="+")&&(n=r[++t]),n==="0"){if(t+1===e)return!0;if(n=r[++t],n==="b"){for(t++;t=0?"0b"+r.toString(2):"-0b"+r.toString(2).slice(1)},octal:function(r){return r>=0?"0"+r.toString(8):"-0"+r.toString(8).slice(1)},decimal:function(r){return r.toString(10)},hexadecimal:function(r){return r>=0?"0x"+r.toString(16).toUpperCase():"-0x"+r.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})});var GU=w((LZe,HU)=>{"use strict";var UU=tc(),lhe=si(),che=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");function uhe(r){return!(r===null||!che.test(r)||r[r.length-1]==="_")}function ghe(r){var e,t,i,n;return e=r.replace(/_/g,"").toLowerCase(),t=e[0]==="-"?-1:1,n=[],"+-".indexOf(e[0])>=0&&(e=e.slice(1)),e===".inf"?t===1?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:e===".nan"?NaN:e.indexOf(":")>=0?(e.split(":").forEach(function(s){n.unshift(parseFloat(s,10))}),e=0,i=1,n.forEach(function(s){e+=s*i,i*=60}),t*e):t*parseFloat(e,10)}var fhe=/^[-+]?[0-9]+e/;function hhe(r,e){var t;if(isNaN(r))switch(e){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===r)switch(e){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===r)switch(e){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(UU.isNegativeZero(r))return"-0.0";return t=r.toString(10),fhe.test(t)?t.replace("e",".e"):t}function phe(r){return Object.prototype.toString.call(r)==="[object Number]"&&(r%1!==0||UU.isNegativeZero(r))}HU.exports=new lhe("tag:yaml.org,2002:float",{kind:"scalar",resolve:uhe,construct:ghe,predicate:phe,represent:hhe,defaultStyle:"lowercase"})});var YS=w((MZe,YU)=>{"use strict";var dhe=rc();YU.exports=new dhe({include:[CI()],implicit:[TU(),MU(),KU(),GU()]})});var jS=w((OZe,jU)=>{"use strict";var Che=rc();jU.exports=new Che({include:[YS()]})});var zU=w((KZe,WU)=>{"use strict";var mhe=si(),qU=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),JU=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");function Ehe(r){return r===null?!1:qU.exec(r)!==null||JU.exec(r)!==null}function Ihe(r){var e,t,i,n,s,o,a,l=0,c=null,u,g,f;if(e=qU.exec(r),e===null&&(e=JU.exec(r)),e===null)throw new Error("Date resolve error");if(t=+e[1],i=+e[2]-1,n=+e[3],!e[4])return new Date(Date.UTC(t,i,n));if(s=+e[4],o=+e[5],a=+e[6],e[7]){for(l=e[7].slice(0,3);l.length<3;)l+="0";l=+l}return e[9]&&(u=+e[10],g=+(e[11]||0),c=(u*60+g)*6e4,e[9]==="-"&&(c=-c)),f=new Date(Date.UTC(t,i,n,s,o,a,l)),c&&f.setTime(f.getTime()-c),f}function yhe(r){return r.toISOString()}WU.exports=new mhe("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:Ehe,construct:Ihe,instanceOf:Date,represent:yhe})});var XU=w((UZe,VU)=>{"use strict";var whe=si();function Bhe(r){return r==="<<"||r===null}VU.exports=new whe("tag:yaml.org,2002:merge",{kind:"scalar",resolve:Bhe})});var $U=w((HZe,_U)=>{"use strict";var ic;try{ZU=J,ic=ZU("buffer").Buffer}catch{}var ZU,bhe=si(),qS=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= \r`;function Qhe(r){if(r===null)return!1;var e,t,i=0,n=r.length,s=qS;for(t=0;t64)){if(e<0)return!1;i+=6}return i%8===0}function She(r){var e,t,i=r.replace(/[\r\n=]/g,""),n=i.length,s=qS,o=0,a=[];for(e=0;e>16&255),a.push(o>>8&255),a.push(o&255)),o=o<<6|s.indexOf(i.charAt(e));return t=n%4*6,t===0?(a.push(o>>16&255),a.push(o>>8&255),a.push(o&255)):t===18?(a.push(o>>10&255),a.push(o>>2&255)):t===12&&a.push(o>>4&255),ic?ic.from?ic.from(a):new ic(a):a}function vhe(r){var e="",t=0,i,n,s=r.length,o=qS;for(i=0;i>18&63],e+=o[t>>12&63],e+=o[t>>6&63],e+=o[t&63]),t=(t<<8)+r[i];return n=s%3,n===0?(e+=o[t>>18&63],e+=o[t>>12&63],e+=o[t>>6&63],e+=o[t&63]):n===2?(e+=o[t>>10&63],e+=o[t>>4&63],e+=o[t<<2&63],e+=o[64]):n===1&&(e+=o[t>>2&63],e+=o[t<<4&63],e+=o[64],e+=o[64]),e}function xhe(r){return ic&&ic.isBuffer(r)}_U.exports=new bhe("tag:yaml.org,2002:binary",{kind:"scalar",resolve:Qhe,construct:She,predicate:xhe,represent:vhe})});var t2=w((YZe,e2)=>{"use strict";var Phe=si(),Dhe=Object.prototype.hasOwnProperty,khe=Object.prototype.toString;function Rhe(r){if(r===null)return!0;var e=[],t,i,n,s,o,a=r;for(t=0,i=a.length;t{"use strict";var Nhe=si(),The=Object.prototype.toString;function Lhe(r){if(r===null)return!0;var e,t,i,n,s,o=r;for(s=new Array(o.length),e=0,t=o.length;e{"use strict";var Ohe=si(),Khe=Object.prototype.hasOwnProperty;function Uhe(r){if(r===null)return!0;var e,t=r;for(e in t)if(Khe.call(t,e)&&t[e]!==null)return!1;return!0}function Hhe(r){return r!==null?r:{}}n2.exports=new Ohe("tag:yaml.org,2002:set",{kind:"mapping",resolve:Uhe,construct:Hhe})});var Lg=w((JZe,o2)=>{"use strict";var Ghe=rc();o2.exports=new Ghe({include:[jS()],implicit:[zU(),XU()],explicit:[$U(),t2(),i2(),s2()]})});var A2=w((WZe,a2)=>{"use strict";var Yhe=si();function jhe(){return!0}function qhe(){}function Jhe(){return""}function Whe(r){return typeof r>"u"}a2.exports=new Yhe("tag:yaml.org,2002:js/undefined",{kind:"scalar",resolve:jhe,construct:qhe,predicate:Whe,represent:Jhe})});var c2=w((zZe,l2)=>{"use strict";var zhe=si();function Vhe(r){if(r===null||r.length===0)return!1;var e=r,t=/\/([gim]*)$/.exec(r),i="";return!(e[0]==="/"&&(t&&(i=t[1]),i.length>3||e[e.length-i.length-1]!=="/"))}function Xhe(r){var e=r,t=/\/([gim]*)$/.exec(r),i="";return e[0]==="/"&&(t&&(i=t[1]),e=e.slice(1,e.length-i.length-1)),new RegExp(e,i)}function Zhe(r){var e="/"+r.source+"/";return r.global&&(e+="g"),r.multiline&&(e+="m"),r.ignoreCase&&(e+="i"),e}function _he(r){return Object.prototype.toString.call(r)==="[object RegExp]"}l2.exports=new zhe("tag:yaml.org,2002:js/regexp",{kind:"scalar",resolve:Vhe,construct:Xhe,predicate:_he,represent:Zhe})});var f2=w((VZe,g2)=>{"use strict";var mI;try{u2=J,mI=u2("esprima")}catch{typeof window<"u"&&(mI=window.esprima)}var u2,$he=si();function epe(r){if(r===null)return!1;try{var e="("+r+")",t=mI.parse(e,{range:!0});return!(t.type!=="Program"||t.body.length!==1||t.body[0].type!=="ExpressionStatement"||t.body[0].expression.type!=="ArrowFunctionExpression"&&t.body[0].expression.type!=="FunctionExpression")}catch{return!1}}function tpe(r){var e="("+r+")",t=mI.parse(e,{range:!0}),i=[],n;if(t.type!=="Program"||t.body.length!==1||t.body[0].type!=="ExpressionStatement"||t.body[0].expression.type!=="ArrowFunctionExpression"&&t.body[0].expression.type!=="FunctionExpression")throw new Error("Failed to resolve function");return t.body[0].expression.params.forEach(function(s){i.push(s.name)}),n=t.body[0].expression.body.range,t.body[0].expression.body.type==="BlockStatement"?new Function(i,e.slice(n[0]+1,n[1]-1)):new Function(i,"return "+e.slice(n[0],n[1]))}function rpe(r){return r.toString()}function ipe(r){return Object.prototype.toString.call(r)==="[object Function]"}g2.exports=new $he("tag:yaml.org,2002:js/function",{kind:"scalar",resolve:epe,construct:tpe,predicate:ipe,represent:rpe})});var Xp=w((ZZe,p2)=>{"use strict";var h2=rc();p2.exports=h2.DEFAULT=new h2({include:[Lg()],explicit:[A2(),c2(),f2()]})});var N2=w((_Ze,Zp)=>{"use strict";var Ba=tc(),w2=Ng(),npe=wU(),B2=Lg(),spe=Xp(),kA=Object.prototype.hasOwnProperty,EI=1,b2=2,Q2=3,II=4,JS=1,ope=2,d2=3,ape=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,Ape=/[\x85\u2028\u2029]/,lpe=/[,\[\]\{\}]/,S2=/^(?:!|!!|![a-z\-]+!)$/i,v2=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function C2(r){return Object.prototype.toString.call(r)}function xo(r){return r===10||r===13}function sc(r){return r===9||r===32}function fn(r){return r===9||r===32||r===10||r===13}function Mg(r){return r===44||r===91||r===93||r===123||r===125}function cpe(r){var e;return 48<=r&&r<=57?r-48:(e=r|32,97<=e&&e<=102?e-97+10:-1)}function upe(r){return r===120?2:r===117?4:r===85?8:0}function gpe(r){return 48<=r&&r<=57?r-48:-1}function m2(r){return r===48?"\0":r===97?"\x07":r===98?"\b":r===116||r===9?" ":r===110?` `:r===118?"\v":r===102?"\f":r===114?"\r":r===101?"\x1B":r===32?" ":r===34?'"':r===47?"/":r===92?"\\":r===78?"\x85":r===95?"\xA0":r===76?"\u2028":r===80?"\u2029":""}function fpe(r){return r<=65535?String.fromCharCode(r):String.fromCharCode((r-65536>>10)+55296,(r-65536&1023)+56320)}var x2=new Array(256),P2=new Array(256);for(nc=0;nc<256;nc++)x2[nc]=m2(nc)?1:0,P2[nc]=m2(nc);var nc;function hpe(r,e){this.input=r,this.filename=e.filename||null,this.schema=e.schema||spe,this.onWarning=e.onWarning||null,this.legacy=e.legacy||!1,this.json=e.json||!1,this.listener=e.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=r.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function D2(r,e){return new w2(e,new npe(r.filename,r.input,r.position,r.line,r.position-r.lineStart))}function ft(r,e){throw D2(r,e)}function yI(r,e){r.onWarning&&r.onWarning.call(null,D2(r,e))}var E2={YAML:function(e,t,i){var n,s,o;e.version!==null&&ft(e,"duplication of %YAML directive"),i.length!==1&&ft(e,"YAML directive accepts exactly one argument"),n=/^([0-9]+)\.([0-9]+)$/.exec(i[0]),n===null&&ft(e,"ill-formed argument of the YAML directive"),s=parseInt(n[1],10),o=parseInt(n[2],10),s!==1&&ft(e,"unacceptable YAML version of the document"),e.version=i[0],e.checkLineBreaks=o<2,o!==1&&o!==2&&yI(e,"unsupported YAML version of the document")},TAG:function(e,t,i){var n,s;i.length!==2&&ft(e,"TAG directive accepts exactly two arguments"),n=i[0],s=i[1],S2.test(n)||ft(e,"ill-formed tag handle (first argument) of the TAG directive"),kA.call(e.tagMap,n)&&ft(e,'there is a previously declared suffix for "'+n+'" tag handle'),v2.test(s)||ft(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[n]=s}};function DA(r,e,t,i){var n,s,o,a;if(e1&&(r.result+=Ba.repeat(` `,e-1))}function ppe(r,e,t){var i,n,s,o,a,l,c,u,g=r.kind,f=r.result,h;if(h=r.input.charCodeAt(r.position),fn(h)||Mg(h)||h===35||h===38||h===42||h===33||h===124||h===62||h===39||h===34||h===37||h===64||h===96||(h===63||h===45)&&(n=r.input.charCodeAt(r.position+1),fn(n)||t&&Mg(n)))return!1;for(r.kind="scalar",r.result="",s=o=r.position,a=!1;h!==0;){if(h===58){if(n=r.input.charCodeAt(r.position+1),fn(n)||t&&Mg(n))break}else if(h===35){if(i=r.input.charCodeAt(r.position-1),fn(i))break}else{if(r.position===r.lineStart&&wI(r)||t&&Mg(h))break;if(xo(h))if(l=r.line,c=r.lineStart,u=r.lineIndent,zr(r,!1,-1),r.lineIndent>=e){a=!0,h=r.input.charCodeAt(r.position);continue}else{r.position=o,r.line=l,r.lineStart=c,r.lineIndent=u;break}}a&&(DA(r,s,o,!1),zS(r,r.line-l),s=o=r.position,a=!1),sc(h)||(o=r.position+1),h=r.input.charCodeAt(++r.position)}return DA(r,s,o,!1),r.result?!0:(r.kind=g,r.result=f,!1)}function dpe(r,e){var t,i,n;if(t=r.input.charCodeAt(r.position),t!==39)return!1;for(r.kind="scalar",r.result="",r.position++,i=n=r.position;(t=r.input.charCodeAt(r.position))!==0;)if(t===39)if(DA(r,i,r.position,!0),t=r.input.charCodeAt(++r.position),t===39)i=r.position,r.position++,n=r.position;else return!0;else xo(t)?(DA(r,i,n,!0),zS(r,zr(r,!1,e)),i=n=r.position):r.position===r.lineStart&&wI(r)?ft(r,"unexpected end of the document within a single quoted scalar"):(r.position++,n=r.position);ft(r,"unexpected end of the stream within a single quoted scalar")}function Cpe(r,e){var t,i,n,s,o,a;if(a=r.input.charCodeAt(r.position),a!==34)return!1;for(r.kind="scalar",r.result="",r.position++,t=i=r.position;(a=r.input.charCodeAt(r.position))!==0;){if(a===34)return DA(r,t,r.position,!0),r.position++,!0;if(a===92){if(DA(r,t,r.position,!0),a=r.input.charCodeAt(++r.position),xo(a))zr(r,!1,e);else if(a<256&&x2[a])r.result+=P2[a],r.position++;else if((o=upe(a))>0){for(n=o,s=0;n>0;n--)a=r.input.charCodeAt(++r.position),(o=cpe(a))>=0?s=(s<<4)+o:ft(r,"expected hexadecimal character");r.result+=fpe(s),r.position++}else ft(r,"unknown escape sequence");t=i=r.position}else xo(a)?(DA(r,t,i,!0),zS(r,zr(r,!1,e)),t=i=r.position):r.position===r.lineStart&&wI(r)?ft(r,"unexpected end of the document within a double quoted scalar"):(r.position++,i=r.position)}ft(r,"unexpected end of the stream within a double quoted scalar")}function mpe(r,e){var t=!0,i,n=r.tag,s,o=r.anchor,a,l,c,u,g,f={},h,p,C,y;if(y=r.input.charCodeAt(r.position),y===91)l=93,g=!1,s=[];else if(y===123)l=125,g=!0,s={};else return!1;for(r.anchor!==null&&(r.anchorMap[r.anchor]=s),y=r.input.charCodeAt(++r.position);y!==0;){if(zr(r,!0,e),y=r.input.charCodeAt(r.position),y===l)return r.position++,r.tag=n,r.anchor=o,r.kind=g?"mapping":"sequence",r.result=s,!0;t||ft(r,"missed comma between flow collection entries"),p=h=C=null,c=u=!1,y===63&&(a=r.input.charCodeAt(r.position+1),fn(a)&&(c=u=!0,r.position++,zr(r,!0,e))),i=r.line,Kg(r,e,EI,!1,!0),p=r.tag,h=r.result,zr(r,!0,e),y=r.input.charCodeAt(r.position),(u||r.line===i)&&y===58&&(c=!0,y=r.input.charCodeAt(++r.position),zr(r,!0,e),Kg(r,e,EI,!1,!0),C=r.result),g?Og(r,s,f,p,h,C):c?s.push(Og(r,null,f,p,h,C)):s.push(h),zr(r,!0,e),y=r.input.charCodeAt(r.position),y===44?(t=!0,y=r.input.charCodeAt(++r.position)):t=!1}ft(r,"unexpected end of the stream within a flow collection")}function Epe(r,e){var t,i,n=JS,s=!1,o=!1,a=e,l=0,c=!1,u,g;if(g=r.input.charCodeAt(r.position),g===124)i=!1;else if(g===62)i=!0;else return!1;for(r.kind="scalar",r.result="";g!==0;)if(g=r.input.charCodeAt(++r.position),g===43||g===45)JS===n?n=g===43?d2:ope:ft(r,"repeat of a chomping mode identifier");else if((u=gpe(g))>=0)u===0?ft(r,"bad explicit indentation width of a block scalar; it cannot be less than one"):o?ft(r,"repeat of an indentation width identifier"):(a=e+u-1,o=!0);else break;if(sc(g)){do g=r.input.charCodeAt(++r.position);while(sc(g));if(g===35)do g=r.input.charCodeAt(++r.position);while(!xo(g)&&g!==0)}for(;g!==0;){for(WS(r),r.lineIndent=0,g=r.input.charCodeAt(r.position);(!o||r.lineIndenta&&(a=r.lineIndent),xo(g)){l++;continue}if(r.lineIndente)&&l!==0)ft(r,"bad indentation of a sequence entry");else if(r.lineIndente)&&(Kg(r,e,II,!0,n)&&(p?f=r.result:h=r.result),p||(Og(r,c,u,g,f,h,s,o),g=f=h=null),zr(r,!0,-1),y=r.input.charCodeAt(r.position)),r.lineIndent>e&&y!==0)ft(r,"bad indentation of a mapping entry");else if(r.lineIndente?l=1:r.lineIndent===e?l=0:r.lineIndente?l=1:r.lineIndent===e?l=0:r.lineIndent tag; it should be "scalar", not "'+r.kind+'"'),g=0,f=r.implicitTypes.length;g tag; it should be "'+h.kind+'", not "'+r.kind+'"'),h.resolve(r.result)?(r.result=h.construct(r.result),r.anchor!==null&&(r.anchorMap[r.anchor]=r.result)):ft(r,"cannot resolve a node with !<"+r.tag+"> explicit tag")):ft(r,"unknown tag !<"+r.tag+">");return r.listener!==null&&r.listener("close",r),r.tag!==null||r.anchor!==null||u}function bpe(r){var e=r.position,t,i,n,s=!1,o;for(r.version=null,r.checkLineBreaks=r.legacy,r.tagMap={},r.anchorMap={};(o=r.input.charCodeAt(r.position))!==0&&(zr(r,!0,-1),o=r.input.charCodeAt(r.position),!(r.lineIndent>0||o!==37));){for(s=!0,o=r.input.charCodeAt(++r.position),t=r.position;o!==0&&!fn(o);)o=r.input.charCodeAt(++r.position);for(i=r.input.slice(t,r.position),n=[],i.length<1&&ft(r,"directive name must not be less than one character in length");o!==0;){for(;sc(o);)o=r.input.charCodeAt(++r.position);if(o===35){do o=r.input.charCodeAt(++r.position);while(o!==0&&!xo(o));break}if(xo(o))break;for(t=r.position;o!==0&&!fn(o);)o=r.input.charCodeAt(++r.position);n.push(r.input.slice(t,r.position))}o!==0&&WS(r),kA.call(E2,i)?E2[i](r,i,n):yI(r,'unknown document directive "'+i+'"')}if(zr(r,!0,-1),r.lineIndent===0&&r.input.charCodeAt(r.position)===45&&r.input.charCodeAt(r.position+1)===45&&r.input.charCodeAt(r.position+2)===45?(r.position+=3,zr(r,!0,-1)):s&&ft(r,"directives end mark is expected"),Kg(r,r.lineIndent-1,II,!1,!0),zr(r,!0,-1),r.checkLineBreaks&&Ape.test(r.input.slice(e,r.position))&&yI(r,"non-ASCII line breaks are interpreted as content"),r.documents.push(r.result),r.position===r.lineStart&&wI(r)){r.input.charCodeAt(r.position)===46&&(r.position+=3,zr(r,!0,-1));return}if(r.position"u"&&(t=e,e=null);var i=k2(r,t);if(typeof e!="function")return i;for(var n=0,s=i.length;n"u"&&(t=e,e=null),R2(r,e,Ba.extend({schema:B2},t))}function Spe(r,e){return F2(r,Ba.extend({schema:B2},e))}Zp.exports.loadAll=R2;Zp.exports.load=F2;Zp.exports.safeLoadAll=Qpe;Zp.exports.safeLoad=Spe});var iH=w(($Ze,_S)=>{"use strict";var $p=tc(),ed=Ng(),vpe=Xp(),xpe=Lg(),G2=Object.prototype.toString,Y2=Object.prototype.hasOwnProperty,Ppe=9,_p=10,Dpe=13,kpe=32,Rpe=33,Fpe=34,j2=35,Npe=37,Tpe=38,Lpe=39,Mpe=42,q2=44,Ope=45,J2=58,Kpe=61,Upe=62,Hpe=63,Gpe=64,W2=91,z2=93,Ype=96,V2=123,jpe=124,X2=125,Ni={};Ni[0]="\\0";Ni[7]="\\a";Ni[8]="\\b";Ni[9]="\\t";Ni[10]="\\n";Ni[11]="\\v";Ni[12]="\\f";Ni[13]="\\r";Ni[27]="\\e";Ni[34]='\\"';Ni[92]="\\\\";Ni[133]="\\N";Ni[160]="\\_";Ni[8232]="\\L";Ni[8233]="\\P";var qpe=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"];function Jpe(r,e){var t,i,n,s,o,a,l;if(e===null)return{};for(t={},i=Object.keys(e),n=0,s=i.length;n0?r.charCodeAt(s-1):null,f=f&&M2(o,a)}else{for(s=0;si&&r[g+1]!==" ",g=s);else if(!Ug(o))return BI;a=s>0?r.charCodeAt(s-1):null,f=f&&M2(o,a)}c=c||u&&s-g-1>i&&r[g+1]!==" "}return!l&&!c?f&&!n(r)?_2:$2:t>9&&Z2(r)?BI:c?tH:eH}function _pe(r,e,t,i){r.dump=function(){if(e.length===0)return"''";if(!r.noCompatMode&&qpe.indexOf(e)!==-1)return"'"+e+"'";var n=r.indent*Math.max(1,t),s=r.lineWidth===-1?-1:Math.max(Math.min(r.lineWidth,40),r.lineWidth-n),o=i||r.flowLevel>-1&&t>=r.flowLevel;function a(l){return zpe(r,l)}switch(Zpe(e,o,r.indent,s,a)){case _2:return e;case $2:return"'"+e.replace(/'/g,"''")+"'";case eH:return"|"+O2(e,r.indent)+K2(L2(e,n));case tH:return">"+O2(e,r.indent)+K2(L2($pe(e,s),n));case BI:return'"'+ede(e,s)+'"';default:throw new ed("impossible error: invalid scalar style")}}()}function O2(r,e){var t=Z2(r)?String(e):"",i=r[r.length-1]===` `,n=i&&(r[r.length-2]===` `||r===` `),s=n?"+":i?"":"-";return t+s+` `}function K2(r){return r[r.length-1]===` `?r.slice(0,-1):r}function $pe(r,e){for(var t=/(\n+)([^\n]*)/g,i=function(){var c=r.indexOf(` `);return c=c!==-1?c:r.length,t.lastIndex=c,U2(r.slice(0,c),e)}(),n=r[0]===` `||r[0]===" ",s,o;o=t.exec(r);){var a=o[1],l=o[2];s=l[0]===" ",i+=a+(!n&&!s&&l!==""?` `:"")+U2(l,e),n=s}return i}function U2(r,e){if(r===""||r[0]===" ")return r;for(var t=/ [^ ]/g,i,n=0,s,o=0,a=0,l="";i=t.exec(r);)a=i.index,a-n>e&&(s=o>n?o:a,l+=` `+r.slice(n,s),n=s+1),o=a;return l+=` `,r.length-n>e&&o>n?l+=r.slice(n,o)+` `+r.slice(o+1):l+=r.slice(n),l.slice(1)}function ede(r){for(var e="",t,i,n,s=0;s=55296&&t<=56319&&(i=r.charCodeAt(s+1),i>=56320&&i<=57343)){e+=T2((t-55296)*1024+i-56320+65536),s++;continue}n=Ni[t],e+=!n&&Ug(t)?r[s]:n||T2(t)}return e}function tde(r,e,t){var i="",n=r.tag,s,o;for(s=0,o=t.length;s1024&&(u+="? "),u+=r.dump+(r.condenseFlow?'"':"")+":"+(r.condenseFlow?"":" "),oc(r,e,c,!1,!1)&&(u+=r.dump,i+=u));r.tag=n,r.dump="{"+i+"}"}function nde(r,e,t,i){var n="",s=r.tag,o=Object.keys(t),a,l,c,u,g,f;if(r.sortKeys===!0)o.sort();else if(typeof r.sortKeys=="function")o.sort(r.sortKeys);else if(r.sortKeys)throw new ed("sortKeys must be a boolean or a function");for(a=0,l=o.length;a1024,g&&(r.dump&&_p===r.dump.charCodeAt(0)?f+="?":f+="? "),f+=r.dump,g&&(f+=VS(r,e)),oc(r,e+1,u,!0,g)&&(r.dump&&_p===r.dump.charCodeAt(0)?f+=":":f+=": ",f+=r.dump,n+=f));r.tag=s,r.dump=n||"{}"}function H2(r,e,t){var i,n,s,o,a,l;for(n=t?r.explicitTypes:r.implicitTypes,s=0,o=n.length;s tag resolver accepts not "'+l+'" style');r.dump=i}return!0}return!1}function oc(r,e,t,i,n,s){r.tag=null,r.dump=t,H2(r,t,!1)||H2(r,t,!0);var o=G2.call(r.dump);i&&(i=r.flowLevel<0||r.flowLevel>e);var a=o==="[object Object]"||o==="[object Array]",l,c;if(a&&(l=r.duplicates.indexOf(t),c=l!==-1),(r.tag!==null&&r.tag!=="?"||c||r.indent!==2&&e>0)&&(n=!1),c&&r.usedDuplicates[l])r.dump="*ref_"+l;else{if(a&&c&&!r.usedDuplicates[l]&&(r.usedDuplicates[l]=!0),o==="[object Object]")i&&Object.keys(r.dump).length!==0?(nde(r,e,r.dump,n),c&&(r.dump="&ref_"+l+r.dump)):(ide(r,e,r.dump),c&&(r.dump="&ref_"+l+" "+r.dump));else if(o==="[object Array]"){var u=r.noArrayIndent&&e>0?e-1:e;i&&r.dump.length!==0?(rde(r,u,r.dump,n),c&&(r.dump="&ref_"+l+r.dump)):(tde(r,u,r.dump),c&&(r.dump="&ref_"+l+" "+r.dump))}else if(o==="[object String]")r.tag!=="?"&&_pe(r,r.dump,e,s);else{if(r.skipInvalid)return!1;throw new ed("unacceptable kind of an object to dump "+o)}r.tag!==null&&r.tag!=="?"&&(r.dump="!<"+r.tag+"> "+r.dump)}return!0}function sde(r,e){var t=[],i=[],n,s;for(XS(r,t,i),n=0,s=i.length;n{"use strict";var bI=N2(),nH=iH();function QI(r){return function(){throw new Error("Function "+r+" is deprecated and cannot be used.")}}Fr.exports.Type=si();Fr.exports.Schema=rc();Fr.exports.FAILSAFE_SCHEMA=CI();Fr.exports.JSON_SCHEMA=YS();Fr.exports.CORE_SCHEMA=jS();Fr.exports.DEFAULT_SAFE_SCHEMA=Lg();Fr.exports.DEFAULT_FULL_SCHEMA=Xp();Fr.exports.load=bI.load;Fr.exports.loadAll=bI.loadAll;Fr.exports.safeLoad=bI.safeLoad;Fr.exports.safeLoadAll=bI.safeLoadAll;Fr.exports.dump=nH.dump;Fr.exports.safeDump=nH.safeDump;Fr.exports.YAMLException=Ng();Fr.exports.MINIMAL_SCHEMA=CI();Fr.exports.SAFE_SCHEMA=Lg();Fr.exports.DEFAULT_SCHEMA=Xp();Fr.exports.scan=QI("scan");Fr.exports.parse=QI("parse");Fr.exports.compose=QI("compose");Fr.exports.addConstructor=QI("addConstructor")});var aH=w((t_e,oH)=>{"use strict";var ade=sH();oH.exports=ade});var lH=w((r_e,AH)=>{"use strict";function Ade(r,e){function t(){this.constructor=r}t.prototype=e.prototype,r.prototype=new t}function ac(r,e,t,i){this.message=r,this.expected=e,this.found=t,this.location=i,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,ac)}Ade(ac,Error);ac.buildMessage=function(r,e){var t={literal:function(c){return'"'+n(c.text)+'"'},class:function(c){var u="",g;for(g=0;g0){for(g=1,f=1;g({[Ke]:Ce})))},H=function(R){return R},j=function(R){return R},$=Us("correct indentation"),V=" ",W=ar(" ",!1),_=function(R){return R.length===bA*yg},A=function(R){return R.length===(bA+1)*yg},Ae=function(){return bA++,!0},ge=function(){return bA--,!0},re=function(){return pg()},M=Us("pseudostring"),F=/^[^\r\n\t ?:,\][{}#&*!|>'"%@`\-]/,ue=Tn(["\r",` `," "," ","?",":",",","]","[","{","}","#","&","*","!","|",">","'",'"',"%","@","`","-"],!0,!1),pe=/^[^\r\n\t ,\][{}:#"']/,ke=Tn(["\r",` `," "," ",",","]","[","{","}",":","#",'"',"'"],!0,!1),Fe=function(){return pg().replace(/^ *| *$/g,"")},Ne="--",oe=ar("--",!1),le=/^[a-zA-Z\/0-9]/,Be=Tn([["a","z"],["A","Z"],"/",["0","9"]],!1,!1),fe=/^[^\r\n\t :,]/,ae=Tn(["\r",` `," "," ",":",","],!0,!1),qe="null",ne=ar("null",!1),Y=function(){return null},he="true",ie=ar("true",!1),de=function(){return!0},_e="false",Pt=ar("false",!1),It=function(){return!1},Mr=Us("string"),ii='"',gi=ar('"',!1),hr=function(){return""},fi=function(R){return R},ni=function(R){return R.join("")},Ks=/^[^"\\\0-\x1F\x7F]/,pr=Tn(['"',"\\",["\0",""],"\x7F"],!0,!1),Ii='\\"',rs=ar('\\"',!1),fa=function(){return'"'},dA="\\\\",cg=ar("\\\\",!1),is=function(){return"\\"},CA="\\/",ha=ar("\\/",!1),wp=function(){return"/"},mA="\\b",EA=ar("\\b",!1),wr=function(){return"\b"},Tl="\\f",ug=ar("\\f",!1),yo=function(){return"\f"},gg="\\n",Bp=ar("\\n",!1),bp=function(){return` `},vr="\\r",se=ar("\\r",!1),wo=function(){return"\r"},Fn="\\t",fg=ar("\\t",!1),bt=function(){return" "},Ll="\\u",Nn=ar("\\u",!1),ns=function(R,q,Ce,Ke){return String.fromCharCode(parseInt(`0x${R}${q}${Ce}${Ke}`))},ss=/^[0-9a-fA-F]/,gt=Tn([["0","9"],["a","f"],["A","F"]],!1,!1),Bo=Us("blank space"),At=/^[ \t]/,ln=Tn([" "," "],!1,!1),S=Us("white space"),Lt=/^[ \t\n\r]/,hg=Tn([" "," ",` `,"\r"],!1,!1),Ml=`\r `,Qp=ar(`\r `,!1),Sp=` `,vp=ar(` `,!1),xp="\r",Pp=ar("\r",!1),G=0,yt=0,IA=[{line:1,column:1}],zi=0,Ol=[],Xe=0,pa;if("startRule"in e){if(!(e.startRule in i))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');n=i[e.startRule]}function pg(){return r.substring(yt,G)}function ME(){return cn(yt,G)}function Dp(R,q){throw q=q!==void 0?q:cn(yt,G),Ul([Us(R)],r.substring(yt,G),q)}function OE(R,q){throw q=q!==void 0?q:cn(yt,G),dg(R,q)}function ar(R,q){return{type:"literal",text:R,ignoreCase:q}}function Tn(R,q,Ce){return{type:"class",parts:R,inverted:q,ignoreCase:Ce}}function Kl(){return{type:"any"}}function kp(){return{type:"end"}}function Us(R){return{type:"other",description:R}}function da(R){var q=IA[R],Ce;if(q)return q;for(Ce=R-1;!IA[Ce];)Ce--;for(q=IA[Ce],q={line:q.line,column:q.column};Cezi&&(zi=G,Ol=[]),Ol.push(R))}function dg(R,q){return new ac(R,null,null,q)}function Ul(R,q,Ce){return new ac(ac.buildMessage(R,q),R,q,Ce)}function Hs(){var R;return R=Cg(),R}function Hl(){var R,q,Ce;for(R=G,q=[],Ce=yA();Ce!==t;)q.push(Ce),Ce=yA();return q!==t&&(yt=R,q=s(q)),R=q,R}function yA(){var R,q,Ce,Ke,Re;return R=G,q=ma(),q!==t?(r.charCodeAt(G)===45?(Ce=o,G++):(Ce=t,Xe===0&&Le(a)),Ce!==t?(Ke=Rr(),Ke!==t?(Re=Ca(),Re!==t?(yt=R,q=l(Re),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R}function Cg(){var R,q,Ce;for(R=G,q=[],Ce=mg();Ce!==t;)q.push(Ce),Ce=mg();return q!==t&&(yt=R,q=c(q)),R=q,R}function mg(){var R,q,Ce,Ke,Re,ze,dt,Ft,Ln;if(R=G,q=Rr(),q===t&&(q=null),q!==t){if(Ce=G,r.charCodeAt(G)===35?(Ke=u,G++):(Ke=t,Xe===0&&Le(g)),Ke!==t){if(Re=[],ze=G,dt=G,Xe++,Ft=js(),Xe--,Ft===t?dt=void 0:(G=dt,dt=t),dt!==t?(r.length>G?(Ft=r.charAt(G),G++):(Ft=t,Xe===0&&Le(f)),Ft!==t?(dt=[dt,Ft],ze=dt):(G=ze,ze=t)):(G=ze,ze=t),ze!==t)for(;ze!==t;)Re.push(ze),ze=G,dt=G,Xe++,Ft=js(),Xe--,Ft===t?dt=void 0:(G=dt,dt=t),dt!==t?(r.length>G?(Ft=r.charAt(G),G++):(Ft=t,Xe===0&&Le(f)),Ft!==t?(dt=[dt,Ft],ze=dt):(G=ze,ze=t)):(G=ze,ze=t);else Re=t;Re!==t?(Ke=[Ke,Re],Ce=Ke):(G=Ce,Ce=t)}else G=Ce,Ce=t;if(Ce===t&&(Ce=null),Ce!==t){if(Ke=[],Re=Ys(),Re!==t)for(;Re!==t;)Ke.push(Re),Re=Ys();else Ke=t;Ke!==t?(yt=R,q=h(),R=q):(G=R,R=t)}else G=R,R=t}else G=R,R=t;if(R===t&&(R=G,q=ma(),q!==t?(Ce=Gl(),Ce!==t?(Ke=Rr(),Ke===t&&(Ke=null),Ke!==t?(r.charCodeAt(G)===58?(Re=p,G++):(Re=t,Xe===0&&Le(C)),Re!==t?(ze=Rr(),ze===t&&(ze=null),ze!==t?(dt=Ca(),dt!==t?(yt=R,q=y(Ce,dt),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t&&(R=G,q=ma(),q!==t?(Ce=Gs(),Ce!==t?(Ke=Rr(),Ke===t&&(Ke=null),Ke!==t?(r.charCodeAt(G)===58?(Re=p,G++):(Re=t,Xe===0&&Le(C)),Re!==t?(ze=Rr(),ze===t&&(ze=null),ze!==t?(dt=Ca(),dt!==t?(yt=R,q=y(Ce,dt),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t))){if(R=G,q=ma(),q!==t)if(Ce=Gs(),Ce!==t)if(Ke=Rr(),Ke!==t)if(Re=KE(),Re!==t){if(ze=[],dt=Ys(),dt!==t)for(;dt!==t;)ze.push(dt),dt=Ys();else ze=t;ze!==t?(yt=R,q=y(Ce,Re),R=q):(G=R,R=t)}else G=R,R=t;else G=R,R=t;else G=R,R=t;else G=R,R=t;if(R===t)if(R=G,q=ma(),q!==t)if(Ce=Gs(),Ce!==t){if(Ke=[],Re=G,ze=Rr(),ze===t&&(ze=null),ze!==t?(r.charCodeAt(G)===44?(dt=B,G++):(dt=t,Xe===0&&Le(v)),dt!==t?(Ft=Rr(),Ft===t&&(Ft=null),Ft!==t?(Ln=Gs(),Ln!==t?(yt=Re,ze=D(Ce,Ln),Re=ze):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t),Re!==t)for(;Re!==t;)Ke.push(Re),Re=G,ze=Rr(),ze===t&&(ze=null),ze!==t?(r.charCodeAt(G)===44?(dt=B,G++):(dt=t,Xe===0&&Le(v)),dt!==t?(Ft=Rr(),Ft===t&&(Ft=null),Ft!==t?(Ln=Gs(),Ln!==t?(yt=Re,ze=D(Ce,Ln),Re=ze):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t)):(G=Re,Re=t);else Ke=t;Ke!==t?(Re=Rr(),Re===t&&(Re=null),Re!==t?(r.charCodeAt(G)===58?(ze=p,G++):(ze=t,Xe===0&&Le(C)),ze!==t?(dt=Rr(),dt===t&&(dt=null),dt!==t?(Ft=Ca(),Ft!==t?(yt=R,q=T(Ce,Ke,Ft),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)}else G=R,R=t;else G=R,R=t}return R}function Ca(){var R,q,Ce,Ke,Re,ze,dt;if(R=G,q=G,Xe++,Ce=G,Ke=js(),Ke!==t?(Re=rt(),Re!==t?(r.charCodeAt(G)===45?(ze=o,G++):(ze=t,Xe===0&&Le(a)),ze!==t?(dt=Rr(),dt!==t?(Ke=[Ke,Re,ze,dt],Ce=Ke):(G=Ce,Ce=t)):(G=Ce,Ce=t)):(G=Ce,Ce=t)):(G=Ce,Ce=t),Xe--,Ce!==t?(G=q,q=void 0):q=t,q!==t?(Ce=Ys(),Ce!==t?(Ke=bo(),Ke!==t?(Re=Hl(),Re!==t?(ze=wA(),ze!==t?(yt=R,q=H(Re),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t&&(R=G,q=js(),q!==t?(Ce=bo(),Ce!==t?(Ke=Cg(),Ke!==t?(Re=wA(),Re!==t?(yt=R,q=H(Ke),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t),R===t))if(R=G,q=Yl(),q!==t){if(Ce=[],Ke=Ys(),Ke!==t)for(;Ke!==t;)Ce.push(Ke),Ke=Ys();else Ce=t;Ce!==t?(yt=R,q=j(q),R=q):(G=R,R=t)}else G=R,R=t;return R}function ma(){var R,q,Ce;for(Xe++,R=G,q=[],r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Le(W));Ce!==t;)q.push(Ce),r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Le(W));return q!==t?(yt=G,Ce=_(q),Ce?Ce=void 0:Ce=t,Ce!==t?(q=[q,Ce],R=q):(G=R,R=t)):(G=R,R=t),Xe--,R===t&&(q=t,Xe===0&&Le($)),R}function rt(){var R,q,Ce;for(R=G,q=[],r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Le(W));Ce!==t;)q.push(Ce),r.charCodeAt(G)===32?(Ce=V,G++):(Ce=t,Xe===0&&Le(W));return q!==t?(yt=G,Ce=A(q),Ce?Ce=void 0:Ce=t,Ce!==t?(q=[q,Ce],R=q):(G=R,R=t)):(G=R,R=t),R}function bo(){var R;return yt=G,R=Ae(),R?R=void 0:R=t,R}function wA(){var R;return yt=G,R=ge(),R?R=void 0:R=t,R}function Gl(){var R;return R=jl(),R===t&&(R=Rp()),R}function Gs(){var R,q,Ce;if(R=jl(),R===t){if(R=G,q=[],Ce=Eg(),Ce!==t)for(;Ce!==t;)q.push(Ce),Ce=Eg();else q=t;q!==t&&(yt=R,q=re()),R=q}return R}function Yl(){var R;return R=Fp(),R===t&&(R=UE(),R===t&&(R=jl(),R===t&&(R=Rp()))),R}function KE(){var R;return R=Fp(),R===t&&(R=jl(),R===t&&(R=Eg())),R}function Rp(){var R,q,Ce,Ke,Re,ze;if(Xe++,R=G,F.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Le(ue)),q!==t){for(Ce=[],Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(pe.test(r.charAt(G))?(ze=r.charAt(G),G++):(ze=t,Xe===0&&Le(ke)),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ke!==t;)Ce.push(Ke),Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(pe.test(r.charAt(G))?(ze=r.charAt(G),G++):(ze=t,Xe===0&&Le(ke)),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ce!==t?(yt=R,q=Fe(),R=q):(G=R,R=t)}else G=R,R=t;return Xe--,R===t&&(q=t,Xe===0&&Le(M)),R}function Eg(){var R,q,Ce,Ke,Re;if(R=G,r.substr(G,2)===Ne?(q=Ne,G+=2):(q=t,Xe===0&&Le(oe)),q===t&&(q=null),q!==t)if(le.test(r.charAt(G))?(Ce=r.charAt(G),G++):(Ce=t,Xe===0&&Le(Be)),Ce!==t){for(Ke=[],fe.test(r.charAt(G))?(Re=r.charAt(G),G++):(Re=t,Xe===0&&Le(ae));Re!==t;)Ke.push(Re),fe.test(r.charAt(G))?(Re=r.charAt(G),G++):(Re=t,Xe===0&&Le(ae));Ke!==t?(yt=R,q=Fe(),R=q):(G=R,R=t)}else G=R,R=t;else G=R,R=t;return R}function Fp(){var R,q;return R=G,r.substr(G,4)===qe?(q=qe,G+=4):(q=t,Xe===0&&Le(ne)),q!==t&&(yt=R,q=Y()),R=q,R}function UE(){var R,q;return R=G,r.substr(G,4)===he?(q=he,G+=4):(q=t,Xe===0&&Le(ie)),q!==t&&(yt=R,q=de()),R=q,R===t&&(R=G,r.substr(G,5)===_e?(q=_e,G+=5):(q=t,Xe===0&&Le(Pt)),q!==t&&(yt=R,q=It()),R=q),R}function jl(){var R,q,Ce,Ke;return Xe++,R=G,r.charCodeAt(G)===34?(q=ii,G++):(q=t,Xe===0&&Le(gi)),q!==t?(r.charCodeAt(G)===34?(Ce=ii,G++):(Ce=t,Xe===0&&Le(gi)),Ce!==t?(yt=R,q=hr(),R=q):(G=R,R=t)):(G=R,R=t),R===t&&(R=G,r.charCodeAt(G)===34?(q=ii,G++):(q=t,Xe===0&&Le(gi)),q!==t?(Ce=HE(),Ce!==t?(r.charCodeAt(G)===34?(Ke=ii,G++):(Ke=t,Xe===0&&Le(gi)),Ke!==t?(yt=R,q=fi(Ce),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)),Xe--,R===t&&(q=t,Xe===0&&Le(Mr)),R}function HE(){var R,q,Ce;if(R=G,q=[],Ce=Ig(),Ce!==t)for(;Ce!==t;)q.push(Ce),Ce=Ig();else q=t;return q!==t&&(yt=R,q=ni(q)),R=q,R}function Ig(){var R,q,Ce,Ke,Re,ze;return Ks.test(r.charAt(G))?(R=r.charAt(G),G++):(R=t,Xe===0&&Le(pr)),R===t&&(R=G,r.substr(G,2)===Ii?(q=Ii,G+=2):(q=t,Xe===0&&Le(rs)),q!==t&&(yt=R,q=fa()),R=q,R===t&&(R=G,r.substr(G,2)===dA?(q=dA,G+=2):(q=t,Xe===0&&Le(cg)),q!==t&&(yt=R,q=is()),R=q,R===t&&(R=G,r.substr(G,2)===CA?(q=CA,G+=2):(q=t,Xe===0&&Le(ha)),q!==t&&(yt=R,q=wp()),R=q,R===t&&(R=G,r.substr(G,2)===mA?(q=mA,G+=2):(q=t,Xe===0&&Le(EA)),q!==t&&(yt=R,q=wr()),R=q,R===t&&(R=G,r.substr(G,2)===Tl?(q=Tl,G+=2):(q=t,Xe===0&&Le(ug)),q!==t&&(yt=R,q=yo()),R=q,R===t&&(R=G,r.substr(G,2)===gg?(q=gg,G+=2):(q=t,Xe===0&&Le(Bp)),q!==t&&(yt=R,q=bp()),R=q,R===t&&(R=G,r.substr(G,2)===vr?(q=vr,G+=2):(q=t,Xe===0&&Le(se)),q!==t&&(yt=R,q=wo()),R=q,R===t&&(R=G,r.substr(G,2)===Fn?(q=Fn,G+=2):(q=t,Xe===0&&Le(fg)),q!==t&&(yt=R,q=bt()),R=q,R===t&&(R=G,r.substr(G,2)===Ll?(q=Ll,G+=2):(q=t,Xe===0&&Le(Nn)),q!==t?(Ce=BA(),Ce!==t?(Ke=BA(),Ke!==t?(Re=BA(),Re!==t?(ze=BA(),ze!==t?(yt=R,q=ns(Ce,Ke,Re,ze),R=q):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)):(G=R,R=t)))))))))),R}function BA(){var R;return ss.test(r.charAt(G))?(R=r.charAt(G),G++):(R=t,Xe===0&&Le(gt)),R}function Rr(){var R,q;if(Xe++,R=[],At.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Le(ln)),q!==t)for(;q!==t;)R.push(q),At.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Le(ln));else R=t;return Xe--,R===t&&(q=t,Xe===0&&Le(Bo)),R}function GE(){var R,q;if(Xe++,R=[],Lt.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Le(hg)),q!==t)for(;q!==t;)R.push(q),Lt.test(r.charAt(G))?(q=r.charAt(G),G++):(q=t,Xe===0&&Le(hg));else R=t;return Xe--,R===t&&(q=t,Xe===0&&Le(S)),R}function Ys(){var R,q,Ce,Ke,Re,ze;if(R=G,q=js(),q!==t){for(Ce=[],Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(ze=js(),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ke!==t;)Ce.push(Ke),Ke=G,Re=Rr(),Re===t&&(Re=null),Re!==t?(ze=js(),ze!==t?(Re=[Re,ze],Ke=Re):(G=Ke,Ke=t)):(G=Ke,Ke=t);Ce!==t?(q=[q,Ce],R=q):(G=R,R=t)}else G=R,R=t;return R}function js(){var R;return r.substr(G,2)===Ml?(R=Ml,G+=2):(R=t,Xe===0&&Le(Qp)),R===t&&(r.charCodeAt(G)===10?(R=Sp,G++):(R=t,Xe===0&&Le(vp)),R===t&&(r.charCodeAt(G)===13?(R=xp,G++):(R=t,Xe===0&&Le(Pp)))),R}let yg=2,bA=0;if(pa=n(),pa!==t&&G===r.length)return pa;throw pa!==t&&G{"use strict";var hde=r=>{let e=!1,t=!1,i=!1;for(let n=0;n{if(!(typeof r=="string"||Array.isArray(r)))throw new TypeError("Expected the input to be `string | string[]`");e=Object.assign({pascalCase:!1},e);let t=n=>e.pascalCase?n.charAt(0).toUpperCase()+n.slice(1):n;return Array.isArray(r)?r=r.map(n=>n.trim()).filter(n=>n.length).join("-"):r=r.trim(),r.length===0?"":r.length===1?e.pascalCase?r.toUpperCase():r.toLowerCase():(r!==r.toLowerCase()&&(r=hde(r)),r=r.replace(/^[_.\- ]+/,"").toLowerCase().replace(/[_.\- ]+(\w|$)/g,(n,s)=>s.toUpperCase()).replace(/\d+(\w|$)/g,n=>n.toUpperCase()),t(r))};ev.exports=hH;ev.exports.default=hH});var dH=w((A_e,pde)=>{pde.exports=[{name:"AppVeyor",constant:"APPVEYOR",env:"APPVEYOR",pr:"APPVEYOR_PULL_REQUEST_NUMBER"},{name:"Azure Pipelines",constant:"AZURE_PIPELINES",env:"SYSTEM_TEAMFOUNDATIONCOLLECTIONURI",pr:"SYSTEM_PULLREQUEST_PULLREQUESTID"},{name:"Appcircle",constant:"APPCIRCLE",env:"AC_APPCIRCLE"},{name:"Bamboo",constant:"BAMBOO",env:"bamboo_planKey"},{name:"Bitbucket Pipelines",constant:"BITBUCKET",env:"BITBUCKET_COMMIT",pr:"BITBUCKET_PR_ID"},{name:"Bitrise",constant:"BITRISE",env:"BITRISE_IO",pr:"BITRISE_PULL_REQUEST"},{name:"Buddy",constant:"BUDDY",env:"BUDDY_WORKSPACE_ID",pr:"BUDDY_EXECUTION_PULL_REQUEST_ID"},{name:"Buildkite",constant:"BUILDKITE",env:"BUILDKITE",pr:{env:"BUILDKITE_PULL_REQUEST",ne:"false"}},{name:"CircleCI",constant:"CIRCLE",env:"CIRCLECI",pr:"CIRCLE_PULL_REQUEST"},{name:"Cirrus CI",constant:"CIRRUS",env:"CIRRUS_CI",pr:"CIRRUS_PR"},{name:"AWS CodeBuild",constant:"CODEBUILD",env:"CODEBUILD_BUILD_ARN"},{name:"Codefresh",constant:"CODEFRESH",env:"CF_BUILD_ID",pr:{any:["CF_PULL_REQUEST_NUMBER","CF_PULL_REQUEST_ID"]}},{name:"Codeship",constant:"CODESHIP",env:{CI_NAME:"codeship"}},{name:"Drone",constant:"DRONE",env:"DRONE",pr:{DRONE_BUILD_EVENT:"pull_request"}},{name:"dsari",constant:"DSARI",env:"DSARI"},{name:"GitHub Actions",constant:"GITHUB_ACTIONS",env:"GITHUB_ACTIONS",pr:{GITHUB_EVENT_NAME:"pull_request"}},{name:"GitLab CI",constant:"GITLAB",env:"GITLAB_CI",pr:"CI_MERGE_REQUEST_ID"},{name:"GoCD",constant:"GOCD",env:"GO_PIPELINE_LABEL"},{name:"LayerCI",constant:"LAYERCI",env:"LAYERCI",pr:"LAYERCI_PULL_REQUEST"},{name:"Hudson",constant:"HUDSON",env:"HUDSON_URL"},{name:"Jenkins",constant:"JENKINS",env:["JENKINS_URL","BUILD_ID"],pr:{any:["ghprbPullId","CHANGE_ID"]}},{name:"Magnum CI",constant:"MAGNUM",env:"MAGNUM"},{name:"Netlify CI",constant:"NETLIFY",env:"NETLIFY",pr:{env:"PULL_REQUEST",ne:"false"}},{name:"Nevercode",constant:"NEVERCODE",env:"NEVERCODE",pr:{env:"NEVERCODE_PULL_REQUEST",ne:"false"}},{name:"Render",constant:"RENDER",env:"RENDER",pr:{IS_PULL_REQUEST:"true"}},{name:"Sail CI",constant:"SAIL",env:"SAILCI",pr:"SAIL_PULL_REQUEST_NUMBER"},{name:"Semaphore",constant:"SEMAPHORE",env:"SEMAPHORE",pr:"PULL_REQUEST_NUMBER"},{name:"Screwdriver",constant:"SCREWDRIVER",env:"SCREWDRIVER",pr:{env:"SD_PULL_REQUEST",ne:"false"}},{name:"Shippable",constant:"SHIPPABLE",env:"SHIPPABLE",pr:{IS_PULL_REQUEST:"true"}},{name:"Solano CI",constant:"SOLANO",env:"TDDIUM",pr:"TDDIUM_PR_ID"},{name:"Strider CD",constant:"STRIDER",env:"STRIDER"},{name:"TaskCluster",constant:"TASKCLUSTER",env:["TASK_ID","RUN_ID"]},{name:"TeamCity",constant:"TEAMCITY",env:"TEAMCITY_VERSION"},{name:"Travis CI",constant:"TRAVIS",env:"TRAVIS",pr:{env:"TRAVIS_PULL_REQUEST",ne:"false"}},{name:"Vercel",constant:"VERCEL",env:"NOW_BUILDER"},{name:"Visual Studio App Center",constant:"APPCENTER",env:"APPCENTER_BUILD_ID"}]});var Ac=w(Un=>{"use strict";var mH=dH(),Po=process.env;Object.defineProperty(Un,"_vendors",{value:mH.map(function(r){return r.constant})});Un.name=null;Un.isPR=null;mH.forEach(function(r){let t=(Array.isArray(r.env)?r.env:[r.env]).every(function(i){return CH(i)});if(Un[r.constant]=t,t)switch(Un.name=r.name,typeof r.pr){case"string":Un.isPR=!!Po[r.pr];break;case"object":"env"in r.pr?Un.isPR=r.pr.env in Po&&Po[r.pr.env]!==r.pr.ne:"any"in r.pr?Un.isPR=r.pr.any.some(function(i){return!!Po[i]}):Un.isPR=CH(r.pr);break;default:Un.isPR=null}});Un.isCI=!!(Po.CI||Po.CONTINUOUS_INTEGRATION||Po.BUILD_NUMBER||Po.RUN_ID||Un.name);function CH(r){return typeof r=="string"?!!Po[r]:Object.keys(r).every(function(e){return Po[e]===r[e]})}});var hn={};ut(hn,{KeyRelationship:()=>lc,applyCascade:()=>od,base64RegExp:()=>BH,colorStringAlphaRegExp:()=>wH,colorStringRegExp:()=>yH,computeKey:()=>RA,getPrintable:()=>Vr,hasExactLength:()=>xH,hasForbiddenKeys:()=>Wde,hasKeyRelationship:()=>av,hasMaxLength:()=>Dde,hasMinLength:()=>Pde,hasMutuallyExclusiveKeys:()=>zde,hasRequiredKeys:()=>Jde,hasUniqueItems:()=>kde,isArray:()=>yde,isAtLeast:()=>Nde,isAtMost:()=>Tde,isBase64:()=>jde,isBoolean:()=>mde,isDate:()=>Ide,isDict:()=>Bde,isEnum:()=>Zi,isHexColor:()=>Yde,isISO8601:()=>Gde,isInExclusiveRange:()=>Mde,isInInclusiveRange:()=>Lde,isInstanceOf:()=>Qde,isInteger:()=>Ode,isJSON:()=>qde,isLiteral:()=>dde,isLowerCase:()=>Kde,isNegative:()=>Rde,isNullable:()=>xde,isNumber:()=>Ede,isObject:()=>bde,isOneOf:()=>Sde,isOptional:()=>vde,isPositive:()=>Fde,isString:()=>sd,isTuple:()=>wde,isUUID4:()=>Hde,isUnknown:()=>vH,isUpperCase:()=>Ude,iso8601RegExp:()=>ov,makeCoercionFn:()=>cc,makeSetter:()=>SH,makeTrait:()=>QH,makeValidator:()=>Qt,matchesRegExp:()=>ad,plural:()=>kI,pushError:()=>pt,simpleKeyRegExp:()=>IH,uuid4RegExp:()=>bH});function Qt({test:r}){return QH(r)()}function Vr(r){return r===null?"null":r===void 0?"undefined":r===""?"an empty string":JSON.stringify(r)}function RA(r,e){var t,i,n;return typeof e=="number"?`${(t=r==null?void 0:r.p)!==null&&t!==void 0?t:"."}[${e}]`:IH.test(e)?`${(i=r==null?void 0:r.p)!==null&&i!==void 0?i:""}.${e}`:`${(n=r==null?void 0:r.p)!==null&&n!==void 0?n:"."}[${JSON.stringify(e)}]`}function cc(r,e){return t=>{let i=r[e];return r[e]=t,cc(r,e).bind(null,i)}}function SH(r,e){return t=>{r[e]=t}}function kI(r,e,t){return r===1?e:t}function pt({errors:r,p:e}={},t){return r==null||r.push(`${e!=null?e:"."}: ${t}`),!1}function dde(r){return Qt({test:(e,t)=>e!==r?pt(t,`Expected a literal (got ${Vr(r)})`):!0})}function Zi(r){let e=Array.isArray(r)?r:Object.values(r),t=new Set(e);return Qt({test:(i,n)=>t.has(i)?!0:pt(n,`Expected a valid enumeration value (got ${Vr(i)})`)})}var IH,yH,wH,BH,bH,ov,QH,vH,sd,Cde,mde,Ede,Ide,yde,wde,Bde,bde,Qde,Sde,od,vde,xde,Pde,Dde,xH,kde,Rde,Fde,Nde,Tde,Lde,Mde,Ode,ad,Kde,Ude,Hde,Gde,Yde,jde,qde,Jde,Wde,zde,lc,Vde,av,ls=Tge(()=>{IH=/^[a-zA-Z_][a-zA-Z0-9_]*$/,yH=/^#[0-9a-f]{6}$/i,wH=/^#[0-9a-f]{6}([0-9a-f]{2})?$/i,BH=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,bH=/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$/i,ov=/^(?:[1-9]\d{3}(-?)(?:(?:0[1-9]|1[0-2])\1(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])\1(?:29|30)|(?:0[13578]|1[02])(?:\1)31|00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[0-5]))|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)(?:(-?)02(?:\2)29|-?366))T(?:[01]\d|2[0-3])(:?)[0-5]\d(?:\3[0-5]\d)?(?:Z|[+-][01]\d(?:\3[0-5]\d)?)$/,QH=r=>()=>r;vH=()=>Qt({test:(r,e)=>!0});sd=()=>Qt({test:(r,e)=>typeof r!="string"?pt(e,`Expected a string (got ${Vr(r)})`):!0});Cde=new Map([["true",!0],["True",!0],["1",!0],[1,!0],["false",!1],["False",!1],["0",!1],[0,!1]]),mde=()=>Qt({test:(r,e)=>{var t;if(typeof r!="boolean"){if(typeof(e==null?void 0:e.coercions)<"u"){if(typeof(e==null?void 0:e.coercion)>"u")return pt(e,"Unbound coercion result");let i=Cde.get(r);if(typeof i<"u")return e.coercions.push([(t=e.p)!==null&&t!==void 0?t:".",e.coercion.bind(null,i)]),!0}return pt(e,`Expected a boolean (got ${Vr(r)})`)}return!0}}),Ede=()=>Qt({test:(r,e)=>{var t;if(typeof r!="number"){if(typeof(e==null?void 0:e.coercions)<"u"){if(typeof(e==null?void 0:e.coercion)>"u")return pt(e,"Unbound coercion result");let i;if(typeof r=="string"){let n;try{n=JSON.parse(r)}catch{}if(typeof n=="number")if(JSON.stringify(n)===r)i=n;else return pt(e,`Received a number that can't be safely represented by the runtime (${r})`)}if(typeof i<"u")return e.coercions.push([(t=e.p)!==null&&t!==void 0?t:".",e.coercion.bind(null,i)]),!0}return pt(e,`Expected a number (got ${Vr(r)})`)}return!0}}),Ide=()=>Qt({test:(r,e)=>{var t;if(!(r instanceof Date)){if(typeof(e==null?void 0:e.coercions)<"u"){if(typeof(e==null?void 0:e.coercion)>"u")return pt(e,"Unbound coercion result");let i;if(typeof r=="string"&&ov.test(r))i=new Date(r);else{let n;if(typeof r=="string"){let s;try{s=JSON.parse(r)}catch{}typeof s=="number"&&(n=s)}else typeof r=="number"&&(n=r);if(typeof n<"u")if(Number.isSafeInteger(n)||!Number.isSafeInteger(n*1e3))i=new Date(n*1e3);else return pt(e,`Received a timestamp that can't be safely represented by the runtime (${r})`)}if(typeof i<"u")return e.coercions.push([(t=e.p)!==null&&t!==void 0?t:".",e.coercion.bind(null,i)]),!0}return pt(e,`Expected a date (got ${Vr(r)})`)}return!0}}),yde=(r,{delimiter:e}={})=>Qt({test:(t,i)=>{var n;if(typeof t=="string"&&typeof e<"u"&&typeof(i==null?void 0:i.coercions)<"u"){if(typeof(i==null?void 0:i.coercion)>"u")return pt(i,"Unbound coercion result");t=t.split(e),i.coercions.push([(n=i.p)!==null&&n!==void 0?n:".",i.coercion.bind(null,t)])}if(!Array.isArray(t))return pt(i,`Expected an array (got ${Vr(t)})`);let s=!0;for(let o=0,a=t.length;o{let t=xH(r.length);return Qt({test:(i,n)=>{var s;if(typeof i=="string"&&typeof e<"u"&&typeof(n==null?void 0:n.coercions)<"u"){if(typeof(n==null?void 0:n.coercion)>"u")return pt(n,"Unbound coercion result");i=i.split(e),n.coercions.push([(s=n.p)!==null&&s!==void 0?s:".",n.coercion.bind(null,i)])}if(!Array.isArray(i))return pt(n,`Expected a tuple (got ${Vr(i)})`);let o=t(i,Object.assign({},n));for(let a=0,l=i.length;aQt({test:(t,i)=>{if(typeof t!="object"||t===null)return pt(i,`Expected an object (got ${Vr(t)})`);let n=Object.keys(t),s=!0;for(let o=0,a=n.length;o{let t=Object.keys(r);return Qt({test:(i,n)=>{if(typeof i!="object"||i===null)return pt(n,`Expected an object (got ${Vr(i)})`);let s=new Set([...t,...Object.keys(i)]),o={},a=!0;for(let l of s){if(l==="constructor"||l==="__proto__")a=pt(Object.assign(Object.assign({},n),{p:RA(n,l)}),"Unsafe property name");else{let c=Object.prototype.hasOwnProperty.call(r,l)?r[l]:void 0,u=Object.prototype.hasOwnProperty.call(i,l)?i[l]:void 0;typeof c<"u"?a=c(u,Object.assign(Object.assign({},n),{p:RA(n,l),coercion:cc(i,l)}))&&a:e===null?a=pt(Object.assign(Object.assign({},n),{p:RA(n,l)}),`Extraneous property (got ${Vr(u)})`):Object.defineProperty(o,l,{enumerable:!0,get:()=>u,set:SH(i,l)})}if(!a&&(n==null?void 0:n.errors)==null)break}return e!==null&&(a||(n==null?void 0:n.errors)!=null)&&(a=e(o,n)&&a),a}})},Qde=r=>Qt({test:(e,t)=>e instanceof r?!0:pt(t,`Expected an instance of ${r.name} (got ${Vr(e)})`)}),Sde=(r,{exclusive:e=!1}={})=>Qt({test:(t,i)=>{var n,s,o;let a=[],l=typeof(i==null?void 0:i.errors)<"u"?[]:void 0;for(let c=0,u=r.length;c1?pt(i,`Expected to match exactly a single predicate (matched ${a.join(", ")})`):(o=i==null?void 0:i.errors)===null||o===void 0||o.push(...l),!1}}),od=(r,e)=>Qt({test:(t,i)=>{var n,s;let o={value:t},a=typeof(i==null?void 0:i.coercions)<"u"?cc(o,"value"):void 0,l=typeof(i==null?void 0:i.coercions)<"u"?[]:void 0;if(!r(t,Object.assign(Object.assign({},i),{coercion:a,coercions:l})))return!1;let c=[];if(typeof l<"u")for(let[,u]of l)c.push(u());try{if(typeof(i==null?void 0:i.coercions)<"u"){if(o.value!==t){if(typeof(i==null?void 0:i.coercion)>"u")return pt(i,"Unbound coercion result");i.coercions.push([(n=i.p)!==null&&n!==void 0?n:".",i.coercion.bind(null,o.value)])}(s=i==null?void 0:i.coercions)===null||s===void 0||s.push(...l)}return e.every(u=>u(o.value,i))}finally{for(let u of c)u()}}}),vde=r=>Qt({test:(e,t)=>typeof e>"u"?!0:r(e,t)}),xde=r=>Qt({test:(e,t)=>e===null?!0:r(e,t)}),Pde=r=>Qt({test:(e,t)=>e.length>=r?!0:pt(t,`Expected to have a length of at least ${r} elements (got ${e.length})`)}),Dde=r=>Qt({test:(e,t)=>e.length<=r?!0:pt(t,`Expected to have a length of at most ${r} elements (got ${e.length})`)}),xH=r=>Qt({test:(e,t)=>e.length!==r?pt(t,`Expected to have a length of exactly ${r} elements (got ${e.length})`):!0}),kde=({map:r}={})=>Qt({test:(e,t)=>{let i=new Set,n=new Set;for(let s=0,o=e.length;sQt({test:(r,e)=>r<=0?!0:pt(e,`Expected to be negative (got ${r})`)}),Fde=()=>Qt({test:(r,e)=>r>=0?!0:pt(e,`Expected to be positive (got ${r})`)}),Nde=r=>Qt({test:(e,t)=>e>=r?!0:pt(t,`Expected to be at least ${r} (got ${e})`)}),Tde=r=>Qt({test:(e,t)=>e<=r?!0:pt(t,`Expected to be at most ${r} (got ${e})`)}),Lde=(r,e)=>Qt({test:(t,i)=>t>=r&&t<=e?!0:pt(i,`Expected to be in the [${r}; ${e}] range (got ${t})`)}),Mde=(r,e)=>Qt({test:(t,i)=>t>=r&&tQt({test:(e,t)=>e!==Math.round(e)?pt(t,`Expected to be an integer (got ${e})`):Number.isSafeInteger(e)?!0:pt(t,`Expected to be a safe integer (got ${e})`)}),ad=r=>Qt({test:(e,t)=>r.test(e)?!0:pt(t,`Expected to match the pattern ${r.toString()} (got ${Vr(e)})`)}),Kde=()=>Qt({test:(r,e)=>r!==r.toLowerCase()?pt(e,`Expected to be all-lowercase (got ${r})`):!0}),Ude=()=>Qt({test:(r,e)=>r!==r.toUpperCase()?pt(e,`Expected to be all-uppercase (got ${r})`):!0}),Hde=()=>Qt({test:(r,e)=>bH.test(r)?!0:pt(e,`Expected to be a valid UUID v4 (got ${Vr(r)})`)}),Gde=()=>Qt({test:(r,e)=>ov.test(r)?!1:pt(e,`Expected to be a valid ISO 8601 date string (got ${Vr(r)})`)}),Yde=({alpha:r=!1})=>Qt({test:(e,t)=>(r?yH.test(e):wH.test(e))?!0:pt(t,`Expected to be a valid hexadecimal color string (got ${Vr(e)})`)}),jde=()=>Qt({test:(r,e)=>BH.test(r)?!0:pt(e,`Expected to be a valid base 64 string (got ${Vr(r)})`)}),qde=(r=vH())=>Qt({test:(e,t)=>{let i;try{i=JSON.parse(e)}catch{return pt(t,`Expected to be a valid JSON string (got ${Vr(e)})`)}return r(i,t)}}),Jde=r=>{let e=new Set(r);return Qt({test:(t,i)=>{let n=new Set(Object.keys(t)),s=[];for(let o of e)n.has(o)||s.push(o);return s.length>0?pt(i,`Missing required ${kI(s.length,"property","properties")} ${s.map(o=>`"${o}"`).join(", ")}`):!0}})},Wde=r=>{let e=new Set(r);return Qt({test:(t,i)=>{let n=new Set(Object.keys(t)),s=[];for(let o of e)n.has(o)&&s.push(o);return s.length>0?pt(i,`Forbidden ${kI(s.length,"property","properties")} ${s.map(o=>`"${o}"`).join(", ")}`):!0}})},zde=r=>{let e=new Set(r);return Qt({test:(t,i)=>{let n=new Set(Object.keys(t)),s=[];for(let o of e)n.has(o)&&s.push(o);return s.length>1?pt(i,`Mutually exclusive properties ${s.map(o=>`"${o}"`).join(", ")}`):!0}})};(function(r){r.Forbids="Forbids",r.Requires="Requires"})(lc||(lc={}));Vde={[lc.Forbids]:{expect:!1,message:"forbids using"},[lc.Requires]:{expect:!0,message:"requires using"}},av=(r,e,t,{ignore:i=[]}={})=>{let n=new Set(i),s=new Set(t),o=Vde[e];return Qt({test:(a,l)=>{let c=new Set(Object.keys(a));if(!c.has(r)||n.has(a[r]))return!0;let u=[];for(let g of s)(c.has(g)&&!n.has(a[g]))!==o.expect&&u.push(g);return u.length>=1?pt(l,`Property "${r}" ${o.message} ${kI(u.length,"property","properties")} ${u.map(g=>`"${g}"`).join(", ")}`):!0}})}});var qH=w((A$e,jH)=>{"use strict";jH.exports=(r,...e)=>new Promise(t=>{t(r(...e))})});var Jg=w((l$e,pv)=>{"use strict";var gCe=qH(),JH=r=>{if(r<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let e=[],t=0,i=()=>{t--,e.length>0&&e.shift()()},n=(a,l,...c)=>{t++;let u=gCe(a,...c);l(u),u.then(i,i)},s=(a,l,...c)=>{tnew Promise(c=>s(a,c,...l));return Object.defineProperties(o,{activeCount:{get:()=>t},pendingCount:{get:()=>e.length}}),o};pv.exports=JH;pv.exports.default=JH});var gd=w((u$e,WH)=>{var fCe="2.0.0",hCe=Number.MAX_SAFE_INTEGER||9007199254740991,pCe=16;WH.exports={SEMVER_SPEC_VERSION:fCe,MAX_LENGTH:256,MAX_SAFE_INTEGER:hCe,MAX_SAFE_COMPONENT_LENGTH:pCe}});var fd=w((g$e,zH)=>{var dCe=typeof process=="object"&&process.env&&process.env.NODE_DEBUG&&/\bsemver\b/i.test(process.env.NODE_DEBUG)?(...r)=>console.error("SEMVER",...r):()=>{};zH.exports=dCe});var uc=w((NA,VH)=>{var{MAX_SAFE_COMPONENT_LENGTH:dv}=gd(),CCe=fd();NA=VH.exports={};var mCe=NA.re=[],et=NA.src=[],tt=NA.t={},ECe=0,St=(r,e,t)=>{let i=ECe++;CCe(i,e),tt[r]=i,et[i]=e,mCe[i]=new RegExp(e,t?"g":void 0)};St("NUMERICIDENTIFIER","0|[1-9]\\d*");St("NUMERICIDENTIFIERLOOSE","[0-9]+");St("NONNUMERICIDENTIFIER","\\d*[a-zA-Z-][a-zA-Z0-9-]*");St("MAINVERSION",`(${et[tt.NUMERICIDENTIFIER]})\\.(${et[tt.NUMERICIDENTIFIER]})\\.(${et[tt.NUMERICIDENTIFIER]})`);St("MAINVERSIONLOOSE",`(${et[tt.NUMERICIDENTIFIERLOOSE]})\\.(${et[tt.NUMERICIDENTIFIERLOOSE]})\\.(${et[tt.NUMERICIDENTIFIERLOOSE]})`);St("PRERELEASEIDENTIFIER",`(?:${et[tt.NUMERICIDENTIFIER]}|${et[tt.NONNUMERICIDENTIFIER]})`);St("PRERELEASEIDENTIFIERLOOSE",`(?:${et[tt.NUMERICIDENTIFIERLOOSE]}|${et[tt.NONNUMERICIDENTIFIER]})`);St("PRERELEASE",`(?:-(${et[tt.PRERELEASEIDENTIFIER]}(?:\\.${et[tt.PRERELEASEIDENTIFIER]})*))`);St("PRERELEASELOOSE",`(?:-?(${et[tt.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${et[tt.PRERELEASEIDENTIFIERLOOSE]})*))`);St("BUILDIDENTIFIER","[0-9A-Za-z-]+");St("BUILD",`(?:\\+(${et[tt.BUILDIDENTIFIER]}(?:\\.${et[tt.BUILDIDENTIFIER]})*))`);St("FULLPLAIN",`v?${et[tt.MAINVERSION]}${et[tt.PRERELEASE]}?${et[tt.BUILD]}?`);St("FULL",`^${et[tt.FULLPLAIN]}$`);St("LOOSEPLAIN",`[v=\\s]*${et[tt.MAINVERSIONLOOSE]}${et[tt.PRERELEASELOOSE]}?${et[tt.BUILD]}?`);St("LOOSE",`^${et[tt.LOOSEPLAIN]}$`);St("GTLT","((?:<|>)?=?)");St("XRANGEIDENTIFIERLOOSE",`${et[tt.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`);St("XRANGEIDENTIFIER",`${et[tt.NUMERICIDENTIFIER]}|x|X|\\*`);St("XRANGEPLAIN",`[v=\\s]*(${et[tt.XRANGEIDENTIFIER]})(?:\\.(${et[tt.XRANGEIDENTIFIER]})(?:\\.(${et[tt.XRANGEIDENTIFIER]})(?:${et[tt.PRERELEASE]})?${et[tt.BUILD]}?)?)?`);St("XRANGEPLAINLOOSE",`[v=\\s]*(${et[tt.XRANGEIDENTIFIERLOOSE]})(?:\\.(${et[tt.XRANGEIDENTIFIERLOOSE]})(?:\\.(${et[tt.XRANGEIDENTIFIERLOOSE]})(?:${et[tt.PRERELEASELOOSE]})?${et[tt.BUILD]}?)?)?`);St("XRANGE",`^${et[tt.GTLT]}\\s*${et[tt.XRANGEPLAIN]}$`);St("XRANGELOOSE",`^${et[tt.GTLT]}\\s*${et[tt.XRANGEPLAINLOOSE]}$`);St("COERCE",`(^|[^\\d])(\\d{1,${dv}})(?:\\.(\\d{1,${dv}}))?(?:\\.(\\d{1,${dv}}))?(?:$|[^\\d])`);St("COERCERTL",et[tt.COERCE],!0);St("LONETILDE","(?:~>?)");St("TILDETRIM",`(\\s*)${et[tt.LONETILDE]}\\s+`,!0);NA.tildeTrimReplace="$1~";St("TILDE",`^${et[tt.LONETILDE]}${et[tt.XRANGEPLAIN]}$`);St("TILDELOOSE",`^${et[tt.LONETILDE]}${et[tt.XRANGEPLAINLOOSE]}$`);St("LONECARET","(?:\\^)");St("CARETTRIM",`(\\s*)${et[tt.LONECARET]}\\s+`,!0);NA.caretTrimReplace="$1^";St("CARET",`^${et[tt.LONECARET]}${et[tt.XRANGEPLAIN]}$`);St("CARETLOOSE",`^${et[tt.LONECARET]}${et[tt.XRANGEPLAINLOOSE]}$`);St("COMPARATORLOOSE",`^${et[tt.GTLT]}\\s*(${et[tt.LOOSEPLAIN]})$|^$`);St("COMPARATOR",`^${et[tt.GTLT]}\\s*(${et[tt.FULLPLAIN]})$|^$`);St("COMPARATORTRIM",`(\\s*)${et[tt.GTLT]}\\s*(${et[tt.LOOSEPLAIN]}|${et[tt.XRANGEPLAIN]})`,!0);NA.comparatorTrimReplace="$1$2$3";St("HYPHENRANGE",`^\\s*(${et[tt.XRANGEPLAIN]})\\s+-\\s+(${et[tt.XRANGEPLAIN]})\\s*$`);St("HYPHENRANGELOOSE",`^\\s*(${et[tt.XRANGEPLAINLOOSE]})\\s+-\\s+(${et[tt.XRANGEPLAINLOOSE]})\\s*$`);St("STAR","(<|>)?=?\\s*\\*");St("GTE0","^\\s*>=\\s*0.0.0\\s*$");St("GTE0PRE","^\\s*>=\\s*0.0.0-0\\s*$")});var hd=w((f$e,XH)=>{var ICe=["includePrerelease","loose","rtl"],yCe=r=>r?typeof r!="object"?{loose:!0}:ICe.filter(e=>r[e]).reduce((e,t)=>(e[t]=!0,e),{}):{};XH.exports=yCe});var MI=w((h$e,$H)=>{var ZH=/^[0-9]+$/,_H=(r,e)=>{let t=ZH.test(r),i=ZH.test(e);return t&&i&&(r=+r,e=+e),r===e?0:t&&!i?-1:i&&!t?1:r_H(e,r);$H.exports={compareIdentifiers:_H,rcompareIdentifiers:wCe}});var Li=w((p$e,iG)=>{var OI=fd(),{MAX_LENGTH:eG,MAX_SAFE_INTEGER:KI}=gd(),{re:tG,t:rG}=uc(),BCe=hd(),{compareIdentifiers:pd}=MI(),Yn=class{constructor(e,t){if(t=BCe(t),e instanceof Yn){if(e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease)return e;e=e.version}else if(typeof e!="string")throw new TypeError(`Invalid Version: ${e}`);if(e.length>eG)throw new TypeError(`version is longer than ${eG} characters`);OI("SemVer",e,t),this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease;let i=e.trim().match(t.loose?tG[rG.LOOSE]:tG[rG.FULL]);if(!i)throw new TypeError(`Invalid Version: ${e}`);if(this.raw=e,this.major=+i[1],this.minor=+i[2],this.patch=+i[3],this.major>KI||this.major<0)throw new TypeError("Invalid major version");if(this.minor>KI||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>KI||this.patch<0)throw new TypeError("Invalid patch version");i[4]?this.prerelease=i[4].split(".").map(n=>{if(/^[0-9]+$/.test(n)){let s=+n;if(s>=0&&s=0;)typeof this.prerelease[i]=="number"&&(this.prerelease[i]++,i=-2);i===-1&&this.prerelease.push(0)}t&&(this.prerelease[0]===t?isNaN(this.prerelease[1])&&(this.prerelease=[t,0]):this.prerelease=[t,0]);break;default:throw new Error(`invalid increment argument: ${e}`)}return this.format(),this.raw=this.version,this}};iG.exports=Yn});var gc=w((d$e,aG)=>{var{MAX_LENGTH:bCe}=gd(),{re:nG,t:sG}=uc(),oG=Li(),QCe=hd(),SCe=(r,e)=>{if(e=QCe(e),r instanceof oG)return r;if(typeof r!="string"||r.length>bCe||!(e.loose?nG[sG.LOOSE]:nG[sG.FULL]).test(r))return null;try{return new oG(r,e)}catch{return null}};aG.exports=SCe});var lG=w((C$e,AG)=>{var vCe=gc(),xCe=(r,e)=>{let t=vCe(r,e);return t?t.version:null};AG.exports=xCe});var uG=w((m$e,cG)=>{var PCe=gc(),DCe=(r,e)=>{let t=PCe(r.trim().replace(/^[=v]+/,""),e);return t?t.version:null};cG.exports=DCe});var fG=w((E$e,gG)=>{var kCe=Li(),RCe=(r,e,t,i)=>{typeof t=="string"&&(i=t,t=void 0);try{return new kCe(r,t).inc(e,i).version}catch{return null}};gG.exports=RCe});var cs=w((I$e,pG)=>{var hG=Li(),FCe=(r,e,t)=>new hG(r,t).compare(new hG(e,t));pG.exports=FCe});var UI=w((y$e,dG)=>{var NCe=cs(),TCe=(r,e,t)=>NCe(r,e,t)===0;dG.exports=TCe});var EG=w((w$e,mG)=>{var CG=gc(),LCe=UI(),MCe=(r,e)=>{if(LCe(r,e))return null;{let t=CG(r),i=CG(e),n=t.prerelease.length||i.prerelease.length,s=n?"pre":"",o=n?"prerelease":"";for(let a in t)if((a==="major"||a==="minor"||a==="patch")&&t[a]!==i[a])return s+a;return o}};mG.exports=MCe});var yG=w((B$e,IG)=>{var OCe=Li(),KCe=(r,e)=>new OCe(r,e).major;IG.exports=KCe});var BG=w((b$e,wG)=>{var UCe=Li(),HCe=(r,e)=>new UCe(r,e).minor;wG.exports=HCe});var QG=w((Q$e,bG)=>{var GCe=Li(),YCe=(r,e)=>new GCe(r,e).patch;bG.exports=YCe});var vG=w((S$e,SG)=>{var jCe=gc(),qCe=(r,e)=>{let t=jCe(r,e);return t&&t.prerelease.length?t.prerelease:null};SG.exports=qCe});var PG=w((v$e,xG)=>{var JCe=cs(),WCe=(r,e,t)=>JCe(e,r,t);xG.exports=WCe});var kG=w((x$e,DG)=>{var zCe=cs(),VCe=(r,e)=>zCe(r,e,!0);DG.exports=VCe});var HI=w((P$e,FG)=>{var RG=Li(),XCe=(r,e,t)=>{let i=new RG(r,t),n=new RG(e,t);return i.compare(n)||i.compareBuild(n)};FG.exports=XCe});var TG=w((D$e,NG)=>{var ZCe=HI(),_Ce=(r,e)=>r.sort((t,i)=>ZCe(t,i,e));NG.exports=_Ce});var MG=w((k$e,LG)=>{var $Ce=HI(),eme=(r,e)=>r.sort((t,i)=>$Ce(i,t,e));LG.exports=eme});var dd=w((R$e,OG)=>{var tme=cs(),rme=(r,e,t)=>tme(r,e,t)>0;OG.exports=rme});var GI=w((F$e,KG)=>{var ime=cs(),nme=(r,e,t)=>ime(r,e,t)<0;KG.exports=nme});var Cv=w((N$e,UG)=>{var sme=cs(),ome=(r,e,t)=>sme(r,e,t)!==0;UG.exports=ome});var YI=w((T$e,HG)=>{var ame=cs(),Ame=(r,e,t)=>ame(r,e,t)>=0;HG.exports=Ame});var jI=w((L$e,GG)=>{var lme=cs(),cme=(r,e,t)=>lme(r,e,t)<=0;GG.exports=cme});var mv=w((M$e,YG)=>{var ume=UI(),gme=Cv(),fme=dd(),hme=YI(),pme=GI(),dme=jI(),Cme=(r,e,t,i)=>{switch(e){case"===":return typeof r=="object"&&(r=r.version),typeof t=="object"&&(t=t.version),r===t;case"!==":return typeof r=="object"&&(r=r.version),typeof t=="object"&&(t=t.version),r!==t;case"":case"=":case"==":return ume(r,t,i);case"!=":return gme(r,t,i);case">":return fme(r,t,i);case">=":return hme(r,t,i);case"<":return pme(r,t,i);case"<=":return dme(r,t,i);default:throw new TypeError(`Invalid operator: ${e}`)}};YG.exports=Cme});var qG=w((O$e,jG)=>{var mme=Li(),Eme=gc(),{re:qI,t:JI}=uc(),Ime=(r,e)=>{if(r instanceof mme)return r;if(typeof r=="number"&&(r=String(r)),typeof r!="string")return null;e=e||{};let t=null;if(!e.rtl)t=r.match(qI[JI.COERCE]);else{let i;for(;(i=qI[JI.COERCERTL].exec(r))&&(!t||t.index+t[0].length!==r.length);)(!t||i.index+i[0].length!==t.index+t[0].length)&&(t=i),qI[JI.COERCERTL].lastIndex=i.index+i[1].length+i[2].length;qI[JI.COERCERTL].lastIndex=-1}return t===null?null:Eme(`${t[2]}.${t[3]||"0"}.${t[4]||"0"}`,e)};jG.exports=Ime});var WG=w((K$e,JG)=>{"use strict";JG.exports=function(r){r.prototype[Symbol.iterator]=function*(){for(let e=this.head;e;e=e.next)yield e.value}}});var WI=w((U$e,zG)=>{"use strict";zG.exports=Ht;Ht.Node=fc;Ht.create=Ht;function Ht(r){var e=this;if(e instanceof Ht||(e=new Ht),e.tail=null,e.head=null,e.length=0,r&&typeof r.forEach=="function")r.forEach(function(n){e.push(n)});else if(arguments.length>0)for(var t=0,i=arguments.length;t1)t=e;else if(this.head)i=this.head.next,t=this.head.value;else throw new TypeError("Reduce of empty list with no initial value");for(var n=0;i!==null;n++)t=r(t,i.value,n),i=i.next;return t};Ht.prototype.reduceReverse=function(r,e){var t,i=this.tail;if(arguments.length>1)t=e;else if(this.tail)i=this.tail.prev,t=this.tail.value;else throw new TypeError("Reduce of empty list with no initial value");for(var n=this.length-1;i!==null;n--)t=r(t,i.value,n),i=i.prev;return t};Ht.prototype.toArray=function(){for(var r=new Array(this.length),e=0,t=this.head;t!==null;e++)r[e]=t.value,t=t.next;return r};Ht.prototype.toArrayReverse=function(){for(var r=new Array(this.length),e=0,t=this.tail;t!==null;e++)r[e]=t.value,t=t.prev;return r};Ht.prototype.slice=function(r,e){e=e||this.length,e<0&&(e+=this.length),r=r||0,r<0&&(r+=this.length);var t=new Ht;if(ethis.length&&(e=this.length);for(var i=0,n=this.head;n!==null&&ithis.length&&(e=this.length);for(var i=this.length,n=this.tail;n!==null&&i>e;i--)n=n.prev;for(;n!==null&&i>r;i--,n=n.prev)t.push(n.value);return t};Ht.prototype.splice=function(r,e,...t){r>this.length&&(r=this.length-1),r<0&&(r=this.length+r);for(var i=0,n=this.head;n!==null&&i{"use strict";var bme=WI(),hc=Symbol("max"),va=Symbol("length"),Wg=Symbol("lengthCalculator"),md=Symbol("allowStale"),pc=Symbol("maxAge"),Sa=Symbol("dispose"),VG=Symbol("noDisposeOnSet"),di=Symbol("lruList"),Zs=Symbol("cache"),ZG=Symbol("updateAgeOnGet"),Ev=()=>1,yv=class{constructor(e){if(typeof e=="number"&&(e={max:e}),e||(e={}),e.max&&(typeof e.max!="number"||e.max<0))throw new TypeError("max must be a non-negative number");let t=this[hc]=e.max||1/0,i=e.length||Ev;if(this[Wg]=typeof i!="function"?Ev:i,this[md]=e.stale||!1,e.maxAge&&typeof e.maxAge!="number")throw new TypeError("maxAge must be a number");this[pc]=e.maxAge||0,this[Sa]=e.dispose,this[VG]=e.noDisposeOnSet||!1,this[ZG]=e.updateAgeOnGet||!1,this.reset()}set max(e){if(typeof e!="number"||e<0)throw new TypeError("max must be a non-negative number");this[hc]=e||1/0,Cd(this)}get max(){return this[hc]}set allowStale(e){this[md]=!!e}get allowStale(){return this[md]}set maxAge(e){if(typeof e!="number")throw new TypeError("maxAge must be a non-negative number");this[pc]=e,Cd(this)}get maxAge(){return this[pc]}set lengthCalculator(e){typeof e!="function"&&(e=Ev),e!==this[Wg]&&(this[Wg]=e,this[va]=0,this[di].forEach(t=>{t.length=this[Wg](t.value,t.key),this[va]+=t.length})),Cd(this)}get lengthCalculator(){return this[Wg]}get length(){return this[va]}get itemCount(){return this[di].length}rforEach(e,t){t=t||this;for(let i=this[di].tail;i!==null;){let n=i.prev;XG(this,e,i,t),i=n}}forEach(e,t){t=t||this;for(let i=this[di].head;i!==null;){let n=i.next;XG(this,e,i,t),i=n}}keys(){return this[di].toArray().map(e=>e.key)}values(){return this[di].toArray().map(e=>e.value)}reset(){this[Sa]&&this[di]&&this[di].length&&this[di].forEach(e=>this[Sa](e.key,e.value)),this[Zs]=new Map,this[di]=new bme,this[va]=0}dump(){return this[di].map(e=>zI(this,e)?!1:{k:e.key,v:e.value,e:e.now+(e.maxAge||0)}).toArray().filter(e=>e)}dumpLru(){return this[di]}set(e,t,i){if(i=i||this[pc],i&&typeof i!="number")throw new TypeError("maxAge must be a number");let n=i?Date.now():0,s=this[Wg](t,e);if(this[Zs].has(e)){if(s>this[hc])return zg(this,this[Zs].get(e)),!1;let l=this[Zs].get(e).value;return this[Sa]&&(this[VG]||this[Sa](e,l.value)),l.now=n,l.maxAge=i,l.value=t,this[va]+=s-l.length,l.length=s,this.get(e),Cd(this),!0}let o=new wv(e,t,s,n,i);return o.length>this[hc]?(this[Sa]&&this[Sa](e,t),!1):(this[va]+=o.length,this[di].unshift(o),this[Zs].set(e,this[di].head),Cd(this),!0)}has(e){if(!this[Zs].has(e))return!1;let t=this[Zs].get(e).value;return!zI(this,t)}get(e){return Iv(this,e,!0)}peek(e){return Iv(this,e,!1)}pop(){let e=this[di].tail;return e?(zg(this,e),e.value):null}del(e){zg(this,this[Zs].get(e))}load(e){this.reset();let t=Date.now();for(let i=e.length-1;i>=0;i--){let n=e[i],s=n.e||0;if(s===0)this.set(n.k,n.v);else{let o=s-t;o>0&&this.set(n.k,n.v,o)}}}prune(){this[Zs].forEach((e,t)=>Iv(this,t,!1))}},Iv=(r,e,t)=>{let i=r[Zs].get(e);if(i){let n=i.value;if(zI(r,n)){if(zg(r,i),!r[md])return}else t&&(r[ZG]&&(i.value.now=Date.now()),r[di].unshiftNode(i));return n.value}},zI=(r,e)=>{if(!e||!e.maxAge&&!r[pc])return!1;let t=Date.now()-e.now;return e.maxAge?t>e.maxAge:r[pc]&&t>r[pc]},Cd=r=>{if(r[va]>r[hc])for(let e=r[di].tail;r[va]>r[hc]&&e!==null;){let t=e.prev;zg(r,e),e=t}},zg=(r,e)=>{if(e){let t=e.value;r[Sa]&&r[Sa](t.key,t.value),r[va]-=t.length,r[Zs].delete(t.key),r[di].removeNode(e)}},wv=class{constructor(e,t,i,n,s){this.key=e,this.value=t,this.length=i,this.now=n,this.maxAge=s||0}},XG=(r,e,t,i)=>{let n=t.value;zI(r,n)&&(zg(r,t),r[md]||(n=void 0)),n&&e.call(i,n.value,n.key,r)};_G.exports=yv});var us=w((G$e,iY)=>{var dc=class{constructor(e,t){if(t=Sme(t),e instanceof dc)return e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease?e:new dc(e.raw,t);if(e instanceof Bv)return this.raw=e.value,this.set=[[e]],this.format(),this;if(this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease,this.raw=e,this.set=e.split(/\s*\|\|\s*/).map(i=>this.parseRange(i.trim())).filter(i=>i.length),!this.set.length)throw new TypeError(`Invalid SemVer Range: ${e}`);if(this.set.length>1){let i=this.set[0];if(this.set=this.set.filter(n=>!tY(n[0])),this.set.length===0)this.set=[i];else if(this.set.length>1){for(let n of this.set)if(n.length===1&&kme(n[0])){this.set=[n];break}}}this.format()}format(){return this.range=this.set.map(e=>e.join(" ").trim()).join("||").trim(),this.range}toString(){return this.range}parseRange(e){e=e.trim();let i=`parseRange:${Object.keys(this.options).join(",")}:${e}`,n=eY.get(i);if(n)return n;let s=this.options.loose,o=s?Mi[bi.HYPHENRANGELOOSE]:Mi[bi.HYPHENRANGE];e=e.replace(o,Hme(this.options.includePrerelease)),Hr("hyphen replace",e),e=e.replace(Mi[bi.COMPARATORTRIM],xme),Hr("comparator trim",e,Mi[bi.COMPARATORTRIM]),e=e.replace(Mi[bi.TILDETRIM],Pme),e=e.replace(Mi[bi.CARETTRIM],Dme),e=e.split(/\s+/).join(" ");let a=s?Mi[bi.COMPARATORLOOSE]:Mi[bi.COMPARATOR],l=e.split(" ").map(f=>Rme(f,this.options)).join(" ").split(/\s+/).map(f=>Ume(f,this.options)).filter(this.options.loose?f=>!!f.match(a):()=>!0).map(f=>new Bv(f,this.options)),c=l.length,u=new Map;for(let f of l){if(tY(f))return[f];u.set(f.value,f)}u.size>1&&u.has("")&&u.delete("");let g=[...u.values()];return eY.set(i,g),g}intersects(e,t){if(!(e instanceof dc))throw new TypeError("a Range is required");return this.set.some(i=>rY(i,t)&&e.set.some(n=>rY(n,t)&&i.every(s=>n.every(o=>s.intersects(o,t)))))}test(e){if(!e)return!1;if(typeof e=="string")try{e=new vme(e,this.options)}catch{return!1}for(let t=0;tr.value==="<0.0.0-0",kme=r=>r.value==="",rY=(r,e)=>{let t=!0,i=r.slice(),n=i.pop();for(;t&&i.length;)t=i.every(s=>n.intersects(s,e)),n=i.pop();return t},Rme=(r,e)=>(Hr("comp",r,e),r=Tme(r,e),Hr("caret",r),r=Fme(r,e),Hr("tildes",r),r=Mme(r,e),Hr("xrange",r),r=Kme(r,e),Hr("stars",r),r),$i=r=>!r||r.toLowerCase()==="x"||r==="*",Fme=(r,e)=>r.trim().split(/\s+/).map(t=>Nme(t,e)).join(" "),Nme=(r,e)=>{let t=e.loose?Mi[bi.TILDELOOSE]:Mi[bi.TILDE];return r.replace(t,(i,n,s,o,a)=>{Hr("tilde",r,i,n,s,o,a);let l;return $i(n)?l="":$i(s)?l=`>=${n}.0.0 <${+n+1}.0.0-0`:$i(o)?l=`>=${n}.${s}.0 <${n}.${+s+1}.0-0`:a?(Hr("replaceTilde pr",a),l=`>=${n}.${s}.${o}-${a} <${n}.${+s+1}.0-0`):l=`>=${n}.${s}.${o} <${n}.${+s+1}.0-0`,Hr("tilde return",l),l})},Tme=(r,e)=>r.trim().split(/\s+/).map(t=>Lme(t,e)).join(" "),Lme=(r,e)=>{Hr("caret",r,e);let t=e.loose?Mi[bi.CARETLOOSE]:Mi[bi.CARET],i=e.includePrerelease?"-0":"";return r.replace(t,(n,s,o,a,l)=>{Hr("caret",r,n,s,o,a,l);let c;return $i(s)?c="":$i(o)?c=`>=${s}.0.0${i} <${+s+1}.0.0-0`:$i(a)?s==="0"?c=`>=${s}.${o}.0${i} <${s}.${+o+1}.0-0`:c=`>=${s}.${o}.0${i} <${+s+1}.0.0-0`:l?(Hr("replaceCaret pr",l),s==="0"?o==="0"?c=`>=${s}.${o}.${a}-${l} <${s}.${o}.${+a+1}-0`:c=`>=${s}.${o}.${a}-${l} <${s}.${+o+1}.0-0`:c=`>=${s}.${o}.${a}-${l} <${+s+1}.0.0-0`):(Hr("no pr"),s==="0"?o==="0"?c=`>=${s}.${o}.${a}${i} <${s}.${o}.${+a+1}-0`:c=`>=${s}.${o}.${a}${i} <${s}.${+o+1}.0-0`:c=`>=${s}.${o}.${a} <${+s+1}.0.0-0`),Hr("caret return",c),c})},Mme=(r,e)=>(Hr("replaceXRanges",r,e),r.split(/\s+/).map(t=>Ome(t,e)).join(" ")),Ome=(r,e)=>{r=r.trim();let t=e.loose?Mi[bi.XRANGELOOSE]:Mi[bi.XRANGE];return r.replace(t,(i,n,s,o,a,l)=>{Hr("xRange",r,i,n,s,o,a,l);let c=$i(s),u=c||$i(o),g=u||$i(a),f=g;return n==="="&&f&&(n=""),l=e.includePrerelease?"-0":"",c?n===">"||n==="<"?i="<0.0.0-0":i="*":n&&f?(u&&(o=0),a=0,n===">"?(n=">=",u?(s=+s+1,o=0,a=0):(o=+o+1,a=0)):n==="<="&&(n="<",u?s=+s+1:o=+o+1),n==="<"&&(l="-0"),i=`${n+s}.${o}.${a}${l}`):u?i=`>=${s}.0.0${l} <${+s+1}.0.0-0`:g&&(i=`>=${s}.${o}.0${l} <${s}.${+o+1}.0-0`),Hr("xRange return",i),i})},Kme=(r,e)=>(Hr("replaceStars",r,e),r.trim().replace(Mi[bi.STAR],"")),Ume=(r,e)=>(Hr("replaceGTE0",r,e),r.trim().replace(Mi[e.includePrerelease?bi.GTE0PRE:bi.GTE0],"")),Hme=r=>(e,t,i,n,s,o,a,l,c,u,g,f,h)=>($i(i)?t="":$i(n)?t=`>=${i}.0.0${r?"-0":""}`:$i(s)?t=`>=${i}.${n}.0${r?"-0":""}`:o?t=`>=${t}`:t=`>=${t}${r?"-0":""}`,$i(c)?l="":$i(u)?l=`<${+c+1}.0.0-0`:$i(g)?l=`<${c}.${+u+1}.0-0`:f?l=`<=${c}.${u}.${g}-${f}`:r?l=`<${c}.${u}.${+g+1}-0`:l=`<=${l}`,`${t} ${l}`.trim()),Gme=(r,e,t)=>{for(let i=0;i0){let n=r[i].semver;if(n.major===e.major&&n.minor===e.minor&&n.patch===e.patch)return!0}return!1}return!0}});var Ed=w((Y$e,AY)=>{var Id=Symbol("SemVer ANY"),Vg=class{static get ANY(){return Id}constructor(e,t){if(t=Yme(t),e instanceof Vg){if(e.loose===!!t.loose)return e;e=e.value}Qv("comparator",e,t),this.options=t,this.loose=!!t.loose,this.parse(e),this.semver===Id?this.value="":this.value=this.operator+this.semver.version,Qv("comp",this)}parse(e){let t=this.options.loose?nY[sY.COMPARATORLOOSE]:nY[sY.COMPARATOR],i=e.match(t);if(!i)throw new TypeError(`Invalid comparator: ${e}`);this.operator=i[1]!==void 0?i[1]:"",this.operator==="="&&(this.operator=""),i[2]?this.semver=new oY(i[2],this.options.loose):this.semver=Id}toString(){return this.value}test(e){if(Qv("Comparator.test",e,this.options.loose),this.semver===Id||e===Id)return!0;if(typeof e=="string")try{e=new oY(e,this.options)}catch{return!1}return bv(e,this.operator,this.semver,this.options)}intersects(e,t){if(!(e instanceof Vg))throw new TypeError("a Comparator is required");if((!t||typeof t!="object")&&(t={loose:!!t,includePrerelease:!1}),this.operator==="")return this.value===""?!0:new aY(e.value,t).test(this.value);if(e.operator==="")return e.value===""?!0:new aY(this.value,t).test(e.semver);let i=(this.operator===">="||this.operator===">")&&(e.operator===">="||e.operator===">"),n=(this.operator==="<="||this.operator==="<")&&(e.operator==="<="||e.operator==="<"),s=this.semver.version===e.semver.version,o=(this.operator===">="||this.operator==="<=")&&(e.operator===">="||e.operator==="<="),a=bv(this.semver,"<",e.semver,t)&&(this.operator===">="||this.operator===">")&&(e.operator==="<="||e.operator==="<"),l=bv(this.semver,">",e.semver,t)&&(this.operator==="<="||this.operator==="<")&&(e.operator===">="||e.operator===">");return i||n||s&&o||a||l}};AY.exports=Vg;var Yme=hd(),{re:nY,t:sY}=uc(),bv=mv(),Qv=fd(),oY=Li(),aY=us()});var yd=w((j$e,lY)=>{var jme=us(),qme=(r,e,t)=>{try{e=new jme(e,t)}catch{return!1}return e.test(r)};lY.exports=qme});var uY=w((q$e,cY)=>{var Jme=us(),Wme=(r,e)=>new Jme(r,e).set.map(t=>t.map(i=>i.value).join(" ").trim().split(" "));cY.exports=Wme});var fY=w((J$e,gY)=>{var zme=Li(),Vme=us(),Xme=(r,e,t)=>{let i=null,n=null,s=null;try{s=new Vme(e,t)}catch{return null}return r.forEach(o=>{s.test(o)&&(!i||n.compare(o)===-1)&&(i=o,n=new zme(i,t))}),i};gY.exports=Xme});var pY=w((W$e,hY)=>{var Zme=Li(),_me=us(),$me=(r,e,t)=>{let i=null,n=null,s=null;try{s=new _me(e,t)}catch{return null}return r.forEach(o=>{s.test(o)&&(!i||n.compare(o)===1)&&(i=o,n=new Zme(i,t))}),i};hY.exports=$me});var mY=w((z$e,CY)=>{var Sv=Li(),eEe=us(),dY=dd(),tEe=(r,e)=>{r=new eEe(r,e);let t=new Sv("0.0.0");if(r.test(t)||(t=new Sv("0.0.0-0"),r.test(t)))return t;t=null;for(let i=0;i{let a=new Sv(o.semver.version);switch(o.operator){case">":a.prerelease.length===0?a.patch++:a.prerelease.push(0),a.raw=a.format();case"":case">=":(!s||dY(a,s))&&(s=a);break;case"<":case"<=":break;default:throw new Error(`Unexpected operation: ${o.operator}`)}}),s&&(!t||dY(t,s))&&(t=s)}return t&&r.test(t)?t:null};CY.exports=tEe});var IY=w((V$e,EY)=>{var rEe=us(),iEe=(r,e)=>{try{return new rEe(r,e).range||"*"}catch{return null}};EY.exports=iEe});var VI=w((X$e,bY)=>{var nEe=Li(),BY=Ed(),{ANY:sEe}=BY,oEe=us(),aEe=yd(),yY=dd(),wY=GI(),AEe=jI(),lEe=YI(),cEe=(r,e,t,i)=>{r=new nEe(r,i),e=new oEe(e,i);let n,s,o,a,l;switch(t){case">":n=yY,s=AEe,o=wY,a=">",l=">=";break;case"<":n=wY,s=lEe,o=yY,a="<",l="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(aEe(r,e,i))return!1;for(let c=0;c{h.semver===sEe&&(h=new BY(">=0.0.0")),g=g||h,f=f||h,n(h.semver,g.semver,i)?g=h:o(h.semver,f.semver,i)&&(f=h)}),g.operator===a||g.operator===l||(!f.operator||f.operator===a)&&s(r,f.semver))return!1;if(f.operator===l&&o(r,f.semver))return!1}return!0};bY.exports=cEe});var SY=w((Z$e,QY)=>{var uEe=VI(),gEe=(r,e,t)=>uEe(r,e,">",t);QY.exports=gEe});var xY=w((_$e,vY)=>{var fEe=VI(),hEe=(r,e,t)=>fEe(r,e,"<",t);vY.exports=hEe});var kY=w(($$e,DY)=>{var PY=us(),pEe=(r,e,t)=>(r=new PY(r,t),e=new PY(e,t),r.intersects(e));DY.exports=pEe});var FY=w((eet,RY)=>{var dEe=yd(),CEe=cs();RY.exports=(r,e,t)=>{let i=[],n=null,s=null,o=r.sort((u,g)=>CEe(u,g,t));for(let u of o)dEe(u,e,t)?(s=u,n||(n=u)):(s&&i.push([n,s]),s=null,n=null);n&&i.push([n,null]);let a=[];for(let[u,g]of i)u===g?a.push(u):!g&&u===o[0]?a.push("*"):g?u===o[0]?a.push(`<=${g}`):a.push(`${u} - ${g}`):a.push(`>=${u}`);let l=a.join(" || "),c=typeof e.raw=="string"?e.raw:String(e);return l.length{var NY=us(),XI=Ed(),{ANY:vv}=XI,wd=yd(),xv=cs(),mEe=(r,e,t={})=>{if(r===e)return!0;r=new NY(r,t),e=new NY(e,t);let i=!1;e:for(let n of r.set){for(let s of e.set){let o=EEe(n,s,t);if(i=i||o!==null,o)continue e}if(i)return!1}return!0},EEe=(r,e,t)=>{if(r===e)return!0;if(r.length===1&&r[0].semver===vv){if(e.length===1&&e[0].semver===vv)return!0;t.includePrerelease?r=[new XI(">=0.0.0-0")]:r=[new XI(">=0.0.0")]}if(e.length===1&&e[0].semver===vv){if(t.includePrerelease)return!0;e=[new XI(">=0.0.0")]}let i=new Set,n,s;for(let h of r)h.operator===">"||h.operator===">="?n=TY(n,h,t):h.operator==="<"||h.operator==="<="?s=LY(s,h,t):i.add(h.semver);if(i.size>1)return null;let o;if(n&&s){if(o=xv(n.semver,s.semver,t),o>0)return null;if(o===0&&(n.operator!==">="||s.operator!=="<="))return null}for(let h of i){if(n&&!wd(h,String(n),t)||s&&!wd(h,String(s),t))return null;for(let p of e)if(!wd(h,String(p),t))return!1;return!0}let a,l,c,u,g=s&&!t.includePrerelease&&s.semver.prerelease.length?s.semver:!1,f=n&&!t.includePrerelease&&n.semver.prerelease.length?n.semver:!1;g&&g.prerelease.length===1&&s.operator==="<"&&g.prerelease[0]===0&&(g=!1);for(let h of e){if(u=u||h.operator===">"||h.operator===">=",c=c||h.operator==="<"||h.operator==="<=",n){if(f&&h.semver.prerelease&&h.semver.prerelease.length&&h.semver.major===f.major&&h.semver.minor===f.minor&&h.semver.patch===f.patch&&(f=!1),h.operator===">"||h.operator===">="){if(a=TY(n,h,t),a===h&&a!==n)return!1}else if(n.operator===">="&&!wd(n.semver,String(h),t))return!1}if(s){if(g&&h.semver.prerelease&&h.semver.prerelease.length&&h.semver.major===g.major&&h.semver.minor===g.minor&&h.semver.patch===g.patch&&(g=!1),h.operator==="<"||h.operator==="<="){if(l=LY(s,h,t),l===h&&l!==s)return!1}else if(s.operator==="<="&&!wd(s.semver,String(h),t))return!1}if(!h.operator&&(s||n)&&o!==0)return!1}return!(n&&c&&!s&&o!==0||s&&u&&!n&&o!==0||f||g)},TY=(r,e,t)=>{if(!r)return e;let i=xv(r.semver,e.semver,t);return i>0?r:i<0||e.operator===">"&&r.operator===">="?e:r},LY=(r,e,t)=>{if(!r)return e;let i=xv(r.semver,e.semver,t);return i<0?r:i>0||e.operator==="<"&&r.operator==="<="?e:r};MY.exports=mEe});var Xr=w((ret,KY)=>{var Pv=uc();KY.exports={re:Pv.re,src:Pv.src,tokens:Pv.t,SEMVER_SPEC_VERSION:gd().SEMVER_SPEC_VERSION,SemVer:Li(),compareIdentifiers:MI().compareIdentifiers,rcompareIdentifiers:MI().rcompareIdentifiers,parse:gc(),valid:lG(),clean:uG(),inc:fG(),diff:EG(),major:yG(),minor:BG(),patch:QG(),prerelease:vG(),compare:cs(),rcompare:PG(),compareLoose:kG(),compareBuild:HI(),sort:TG(),rsort:MG(),gt:dd(),lt:GI(),eq:UI(),neq:Cv(),gte:YI(),lte:jI(),cmp:mv(),coerce:qG(),Comparator:Ed(),Range:us(),satisfies:yd(),toComparators:uY(),maxSatisfying:fY(),minSatisfying:pY(),minVersion:mY(),validRange:IY(),outside:VI(),gtr:SY(),ltr:xY(),intersects:kY(),simplifyRange:FY(),subset:OY()}});var Dv=w(ZI=>{"use strict";Object.defineProperty(ZI,"__esModule",{value:!0});ZI.VERSION=void 0;ZI.VERSION="9.1.0"});var Gt=w((exports,module)=>{"use strict";var __spreadArray=exports&&exports.__spreadArray||function(r,e,t){if(t||arguments.length===2)for(var i=0,n=e.length,s;i{(function(r,e){typeof define=="function"&&define.amd?define([],e):typeof _I=="object"&&_I.exports?_I.exports=e():r.regexpToAst=e()})(typeof self<"u"?self:UY,function(){function r(){}r.prototype.saveState=function(){return{idx:this.idx,input:this.input,groupIdx:this.groupIdx}},r.prototype.restoreState=function(p){this.idx=p.idx,this.input=p.input,this.groupIdx=p.groupIdx},r.prototype.pattern=function(p){this.idx=0,this.input=p,this.groupIdx=0,this.consumeChar("/");var C=this.disjunction();this.consumeChar("/");for(var y={type:"Flags",loc:{begin:this.idx,end:p.length},global:!1,ignoreCase:!1,multiLine:!1,unicode:!1,sticky:!1};this.isRegExpFlag();)switch(this.popChar()){case"g":o(y,"global");break;case"i":o(y,"ignoreCase");break;case"m":o(y,"multiLine");break;case"u":o(y,"unicode");break;case"y":o(y,"sticky");break}if(this.idx!==this.input.length)throw Error("Redundant input: "+this.input.substring(this.idx));return{type:"Pattern",flags:y,value:C,loc:this.loc(0)}},r.prototype.disjunction=function(){var p=[],C=this.idx;for(p.push(this.alternative());this.peekChar()==="|";)this.consumeChar("|"),p.push(this.alternative());return{type:"Disjunction",value:p,loc:this.loc(C)}},r.prototype.alternative=function(){for(var p=[],C=this.idx;this.isTerm();)p.push(this.term());return{type:"Alternative",value:p,loc:this.loc(C)}},r.prototype.term=function(){return this.isAssertion()?this.assertion():this.atom()},r.prototype.assertion=function(){var p=this.idx;switch(this.popChar()){case"^":return{type:"StartAnchor",loc:this.loc(p)};case"$":return{type:"EndAnchor",loc:this.loc(p)};case"\\":switch(this.popChar()){case"b":return{type:"WordBoundary",loc:this.loc(p)};case"B":return{type:"NonWordBoundary",loc:this.loc(p)}}throw Error("Invalid Assertion Escape");case"(":this.consumeChar("?");var C;switch(this.popChar()){case"=":C="Lookahead";break;case"!":C="NegativeLookahead";break}a(C);var y=this.disjunction();return this.consumeChar(")"),{type:C,value:y,loc:this.loc(p)}}l()},r.prototype.quantifier=function(p){var C,y=this.idx;switch(this.popChar()){case"*":C={atLeast:0,atMost:1/0};break;case"+":C={atLeast:1,atMost:1/0};break;case"?":C={atLeast:0,atMost:1};break;case"{":var B=this.integerIncludingZero();switch(this.popChar()){case"}":C={atLeast:B,atMost:B};break;case",":var v;this.isDigit()?(v=this.integerIncludingZero(),C={atLeast:B,atMost:v}):C={atLeast:B,atMost:1/0},this.consumeChar("}");break}if(p===!0&&C===void 0)return;a(C);break}if(!(p===!0&&C===void 0))return a(C),this.peekChar(0)==="?"?(this.consumeChar("?"),C.greedy=!1):C.greedy=!0,C.type="Quantifier",C.loc=this.loc(y),C},r.prototype.atom=function(){var p,C=this.idx;switch(this.peekChar()){case".":p=this.dotAll();break;case"\\":p=this.atomEscape();break;case"[":p=this.characterClass();break;case"(":p=this.group();break}return p===void 0&&this.isPatternCharacter()&&(p=this.patternCharacter()),a(p),p.loc=this.loc(C),this.isQuantifier()&&(p.quantifier=this.quantifier()),p},r.prototype.dotAll=function(){return this.consumeChar("."),{type:"Set",complement:!0,value:[n(` `),n("\r"),n("\u2028"),n("\u2029")]}},r.prototype.atomEscape=function(){switch(this.consumeChar("\\"),this.peekChar()){case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":return this.decimalEscapeAtom();case"d":case"D":case"s":case"S":case"w":case"W":return this.characterClassEscape();case"f":case"n":case"r":case"t":case"v":return this.controlEscapeAtom();case"c":return this.controlLetterEscapeAtom();case"0":return this.nulCharacterAtom();case"x":return this.hexEscapeSequenceAtom();case"u":return this.regExpUnicodeEscapeSequenceAtom();default:return this.identityEscapeAtom()}},r.prototype.decimalEscapeAtom=function(){var p=this.positiveInteger();return{type:"GroupBackReference",value:p}},r.prototype.characterClassEscape=function(){var p,C=!1;switch(this.popChar()){case"d":p=u;break;case"D":p=u,C=!0;break;case"s":p=f;break;case"S":p=f,C=!0;break;case"w":p=g;break;case"W":p=g,C=!0;break}return a(p),{type:"Set",value:p,complement:C}},r.prototype.controlEscapeAtom=function(){var p;switch(this.popChar()){case"f":p=n("\f");break;case"n":p=n(` `);break;case"r":p=n("\r");break;case"t":p=n(" ");break;case"v":p=n("\v");break}return a(p),{type:"Character",value:p}},r.prototype.controlLetterEscapeAtom=function(){this.consumeChar("c");var p=this.popChar();if(/[a-zA-Z]/.test(p)===!1)throw Error("Invalid ");var C=p.toUpperCase().charCodeAt(0)-64;return{type:"Character",value:C}},r.prototype.nulCharacterAtom=function(){return this.consumeChar("0"),{type:"Character",value:n("\0")}},r.prototype.hexEscapeSequenceAtom=function(){return this.consumeChar("x"),this.parseHexDigits(2)},r.prototype.regExpUnicodeEscapeSequenceAtom=function(){return this.consumeChar("u"),this.parseHexDigits(4)},r.prototype.identityEscapeAtom=function(){var p=this.popChar();return{type:"Character",value:n(p)}},r.prototype.classPatternCharacterAtom=function(){switch(this.peekChar()){case` `:case"\r":case"\u2028":case"\u2029":case"\\":case"]":throw Error("TBD");default:var p=this.popChar();return{type:"Character",value:n(p)}}},r.prototype.characterClass=function(){var p=[],C=!1;for(this.consumeChar("["),this.peekChar(0)==="^"&&(this.consumeChar("^"),C=!0);this.isClassAtom();){var y=this.classAtom(),B=y.type==="Character";if(B&&this.isRangeDash()){this.consumeChar("-");var v=this.classAtom(),D=v.type==="Character";if(D){if(v.value=this.input.length)throw Error("Unexpected end of input");this.idx++},r.prototype.loc=function(p){return{begin:p,end:this.idx}};var e=/[0-9a-fA-F]/,t=/[0-9]/,i=/[1-9]/;function n(p){return p.charCodeAt(0)}function s(p,C){p.length!==void 0?p.forEach(function(y){C.push(y)}):C.push(p)}function o(p,C){if(p[C]===!0)throw"duplicate flag "+C;p[C]=!0}function a(p){if(p===void 0)throw Error("Internal Error - Should never get here!")}function l(){throw Error("Internal Error - Should never get here!")}var c,u=[];for(c=n("0");c<=n("9");c++)u.push(c);var g=[n("_")].concat(u);for(c=n("a");c<=n("z");c++)g.push(c);for(c=n("A");c<=n("Z");c++)g.push(c);var f=[n(" "),n("\f"),n(` `),n("\r"),n(" "),n("\v"),n(" "),n("\xA0"),n("\u1680"),n("\u2000"),n("\u2001"),n("\u2002"),n("\u2003"),n("\u2004"),n("\u2005"),n("\u2006"),n("\u2007"),n("\u2008"),n("\u2009"),n("\u200A"),n("\u2028"),n("\u2029"),n("\u202F"),n("\u205F"),n("\u3000"),n("\uFEFF")];function h(){}return h.prototype.visitChildren=function(p){for(var C in p){var y=p[C];p.hasOwnProperty(C)&&(y.type!==void 0?this.visit(y):Array.isArray(y)&&y.forEach(function(B){this.visit(B)},this))}},h.prototype.visit=function(p){switch(p.type){case"Pattern":this.visitPattern(p);break;case"Flags":this.visitFlags(p);break;case"Disjunction":this.visitDisjunction(p);break;case"Alternative":this.visitAlternative(p);break;case"StartAnchor":this.visitStartAnchor(p);break;case"EndAnchor":this.visitEndAnchor(p);break;case"WordBoundary":this.visitWordBoundary(p);break;case"NonWordBoundary":this.visitNonWordBoundary(p);break;case"Lookahead":this.visitLookahead(p);break;case"NegativeLookahead":this.visitNegativeLookahead(p);break;case"Character":this.visitCharacter(p);break;case"Set":this.visitSet(p);break;case"Group":this.visitGroup(p);break;case"GroupBackReference":this.visitGroupBackReference(p);break;case"Quantifier":this.visitQuantifier(p);break}this.visitChildren(p)},h.prototype.visitPattern=function(p){},h.prototype.visitFlags=function(p){},h.prototype.visitDisjunction=function(p){},h.prototype.visitAlternative=function(p){},h.prototype.visitStartAnchor=function(p){},h.prototype.visitEndAnchor=function(p){},h.prototype.visitWordBoundary=function(p){},h.prototype.visitNonWordBoundary=function(p){},h.prototype.visitLookahead=function(p){},h.prototype.visitNegativeLookahead=function(p){},h.prototype.visitCharacter=function(p){},h.prototype.visitSet=function(p){},h.prototype.visitGroup=function(p){},h.prototype.visitGroupBackReference=function(p){},h.prototype.visitQuantifier=function(p){},{RegExpParser:r,BaseRegExpVisitor:h,VERSION:"0.5.0"}})});var ty=w(Xg=>{"use strict";Object.defineProperty(Xg,"__esModule",{value:!0});Xg.clearRegExpParserCache=Xg.getRegExpAst=void 0;var IEe=$I(),ey={},yEe=new IEe.RegExpParser;function wEe(r){var e=r.toString();if(ey.hasOwnProperty(e))return ey[e];var t=yEe.pattern(e);return ey[e]=t,t}Xg.getRegExpAst=wEe;function BEe(){ey={}}Xg.clearRegExpParserCache=BEe});var qY=w(Cn=>{"use strict";var bEe=Cn&&Cn.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Cn,"__esModule",{value:!0});Cn.canMatchCharCode=Cn.firstCharOptimizedIndices=Cn.getOptimizedStartCodesIndices=Cn.failedOptimizationPrefixMsg=void 0;var GY=$I(),gs=Gt(),YY=ty(),xa=Rv(),jY="Complement Sets are not supported for first char optimization";Cn.failedOptimizationPrefixMsg=`Unable to use "first char" lexer optimizations: `;function QEe(r,e){e===void 0&&(e=!1);try{var t=(0,YY.getRegExpAst)(r),i=iy(t.value,{},t.flags.ignoreCase);return i}catch(s){if(s.message===jY)e&&(0,gs.PRINT_WARNING)(""+Cn.failedOptimizationPrefixMsg+(" Unable to optimize: < "+r.toString()+` > `)+` Complement Sets cannot be automatically optimized. This will disable the lexer's first char optimizations. See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#COMPLEMENT for details.`);else{var n="";e&&(n=` This will disable the lexer's first char optimizations. See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#REGEXP_PARSING for details.`),(0,gs.PRINT_ERROR)(Cn.failedOptimizationPrefixMsg+` `+(" Failed parsing: < "+r.toString()+` > `)+(" Using the regexp-to-ast library version: "+GY.VERSION+` `)+" Please open an issue at: https://github.com/bd82/regexp-to-ast/issues"+n)}}return[]}Cn.getOptimizedStartCodesIndices=QEe;function iy(r,e,t){switch(r.type){case"Disjunction":for(var i=0;i=xa.minOptimizationVal)for(var f=u.from>=xa.minOptimizationVal?u.from:xa.minOptimizationVal,h=u.to,p=(0,xa.charCodeToOptimizedIndex)(f),C=(0,xa.charCodeToOptimizedIndex)(h),y=p;y<=C;y++)e[y]=y}}});break;case"Group":iy(o.value,e,t);break;default:throw Error("Non Exhaustive Match")}var a=o.quantifier!==void 0&&o.quantifier.atLeast===0;if(o.type==="Group"&&kv(o)===!1||o.type!=="Group"&&a===!1)break}break;default:throw Error("non exhaustive match!")}return(0,gs.values)(e)}Cn.firstCharOptimizedIndices=iy;function ry(r,e,t){var i=(0,xa.charCodeToOptimizedIndex)(r);e[i]=i,t===!0&&SEe(r,e)}function SEe(r,e){var t=String.fromCharCode(r),i=t.toUpperCase();if(i!==t){var n=(0,xa.charCodeToOptimizedIndex)(i.charCodeAt(0));e[n]=n}else{var s=t.toLowerCase();if(s!==t){var n=(0,xa.charCodeToOptimizedIndex)(s.charCodeAt(0));e[n]=n}}}function HY(r,e){return(0,gs.find)(r.value,function(t){if(typeof t=="number")return(0,gs.contains)(e,t);var i=t;return(0,gs.find)(e,function(n){return i.from<=n&&n<=i.to})!==void 0})}function kv(r){return r.quantifier&&r.quantifier.atLeast===0?!0:r.value?(0,gs.isArray)(r.value)?(0,gs.every)(r.value,kv):kv(r.value):!1}var vEe=function(r){bEe(e,r);function e(t){var i=r.call(this)||this;return i.targetCharCodes=t,i.found=!1,i}return e.prototype.visitChildren=function(t){if(this.found!==!0){switch(t.type){case"Lookahead":this.visitLookahead(t);return;case"NegativeLookahead":this.visitNegativeLookahead(t);return}r.prototype.visitChildren.call(this,t)}},e.prototype.visitCharacter=function(t){(0,gs.contains)(this.targetCharCodes,t.value)&&(this.found=!0)},e.prototype.visitSet=function(t){t.complement?HY(t,this.targetCharCodes)===void 0&&(this.found=!0):HY(t,this.targetCharCodes)!==void 0&&(this.found=!0)},e}(GY.BaseRegExpVisitor);function xEe(r,e){if(e instanceof RegExp){var t=(0,YY.getRegExpAst)(e),i=new vEe(r);return i.visit(t),i.found}else return(0,gs.find)(e,function(n){return(0,gs.contains)(r,n.charCodeAt(0))})!==void 0}Cn.canMatchCharCode=xEe});var Rv=w(Ve=>{"use strict";var JY=Ve&&Ve.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Ve,"__esModule",{value:!0});Ve.charCodeToOptimizedIndex=Ve.minOptimizationVal=Ve.buildLineBreakIssueMessage=Ve.LineTerminatorOptimizedTester=Ve.isShortPattern=Ve.isCustomPattern=Ve.cloneEmptyGroups=Ve.performWarningRuntimeChecks=Ve.performRuntimeChecks=Ve.addStickyFlag=Ve.addStartOfInput=Ve.findUnreachablePatterns=Ve.findModesThatDoNotExist=Ve.findInvalidGroupType=Ve.findDuplicatePatterns=Ve.findUnsupportedFlags=Ve.findStartOfInputAnchor=Ve.findEmptyMatchRegExps=Ve.findEndOfInputAnchor=Ve.findInvalidPatterns=Ve.findMissingPatterns=Ve.validatePatterns=Ve.analyzeTokenTypes=Ve.enableSticky=Ve.disableSticky=Ve.SUPPORT_STICKY=Ve.MODES=Ve.DEFAULT_MODE=void 0;var WY=$I(),ir=Bd(),xe=Gt(),Zg=qY(),zY=ty(),ko="PATTERN";Ve.DEFAULT_MODE="defaultMode";Ve.MODES="modes";Ve.SUPPORT_STICKY=typeof new RegExp("(?:)").sticky=="boolean";function PEe(){Ve.SUPPORT_STICKY=!1}Ve.disableSticky=PEe;function DEe(){Ve.SUPPORT_STICKY=!0}Ve.enableSticky=DEe;function kEe(r,e){e=(0,xe.defaults)(e,{useSticky:Ve.SUPPORT_STICKY,debug:!1,safeMode:!1,positionTracking:"full",lineTerminatorCharacters:["\r",` `],tracer:function(v,D){return D()}});var t=e.tracer;t("initCharCodeToOptimizedIndexMap",function(){HEe()});var i;t("Reject Lexer.NA",function(){i=(0,xe.reject)(r,function(v){return v[ko]===ir.Lexer.NA})});var n=!1,s;t("Transform Patterns",function(){n=!1,s=(0,xe.map)(i,function(v){var D=v[ko];if((0,xe.isRegExp)(D)){var T=D.source;return T.length===1&&T!=="^"&&T!=="$"&&T!=="."&&!D.ignoreCase?T:T.length===2&&T[0]==="\\"&&!(0,xe.contains)(["d","D","s","S","t","r","n","t","0","c","b","B","f","v","w","W"],T[1])?T[1]:e.useSticky?Tv(D):Nv(D)}else{if((0,xe.isFunction)(D))return n=!0,{exec:D};if((0,xe.has)(D,"exec"))return n=!0,D;if(typeof D=="string"){if(D.length===1)return D;var H=D.replace(/[\\^$.*+?()[\]{}|]/g,"\\$&"),j=new RegExp(H);return e.useSticky?Tv(j):Nv(j)}else throw Error("non exhaustive match")}})});var o,a,l,c,u;t("misc mapping",function(){o=(0,xe.map)(i,function(v){return v.tokenTypeIdx}),a=(0,xe.map)(i,function(v){var D=v.GROUP;if(D!==ir.Lexer.SKIPPED){if((0,xe.isString)(D))return D;if((0,xe.isUndefined)(D))return!1;throw Error("non exhaustive match")}}),l=(0,xe.map)(i,function(v){var D=v.LONGER_ALT;if(D){var T=(0,xe.isArray)(D)?(0,xe.map)(D,function(H){return(0,xe.indexOf)(i,H)}):[(0,xe.indexOf)(i,D)];return T}}),c=(0,xe.map)(i,function(v){return v.PUSH_MODE}),u=(0,xe.map)(i,function(v){return(0,xe.has)(v,"POP_MODE")})});var g;t("Line Terminator Handling",function(){var v=Aj(e.lineTerminatorCharacters);g=(0,xe.map)(i,function(D){return!1}),e.positionTracking!=="onlyOffset"&&(g=(0,xe.map)(i,function(D){if((0,xe.has)(D,"LINE_BREAKS"))return D.LINE_BREAKS;if(oj(D,v)===!1)return(0,Zg.canMatchCharCode)(v,D.PATTERN)}))});var f,h,p,C;t("Misc Mapping #2",function(){f=(0,xe.map)(i,Mv),h=(0,xe.map)(s,sj),p=(0,xe.reduce)(i,function(v,D){var T=D.GROUP;return(0,xe.isString)(T)&&T!==ir.Lexer.SKIPPED&&(v[T]=[]),v},{}),C=(0,xe.map)(s,function(v,D){return{pattern:s[D],longerAlt:l[D],canLineTerminator:g[D],isCustom:f[D],short:h[D],group:a[D],push:c[D],pop:u[D],tokenTypeIdx:o[D],tokenType:i[D]}})});var y=!0,B=[];return e.safeMode||t("First Char Optimization",function(){B=(0,xe.reduce)(i,function(v,D,T){if(typeof D.PATTERN=="string"){var H=D.PATTERN.charCodeAt(0),j=Lv(H);Fv(v,j,C[T])}else if((0,xe.isArray)(D.START_CHARS_HINT)){var $;(0,xe.forEach)(D.START_CHARS_HINT,function(W){var _=typeof W=="string"?W.charCodeAt(0):W,A=Lv(_);$!==A&&($=A,Fv(v,A,C[T]))})}else if((0,xe.isRegExp)(D.PATTERN))if(D.PATTERN.unicode)y=!1,e.ensureOptimizations&&(0,xe.PRINT_ERROR)(""+Zg.failedOptimizationPrefixMsg+(" Unable to analyze < "+D.PATTERN.toString()+` > pattern. `)+` The regexp unicode flag is not currently supported by the regexp-to-ast library. This will disable the lexer's first char optimizations. For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNICODE_OPTIMIZE`);else{var V=(0,Zg.getOptimizedStartCodesIndices)(D.PATTERN,e.ensureOptimizations);(0,xe.isEmpty)(V)&&(y=!1),(0,xe.forEach)(V,function(W){Fv(v,W,C[T])})}else e.ensureOptimizations&&(0,xe.PRINT_ERROR)(""+Zg.failedOptimizationPrefixMsg+(" TokenType: <"+D.name+`> is using a custom token pattern without providing parameter. `)+` This will disable the lexer's first char optimizations. For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_OPTIMIZE`),y=!1;return v},[])}),t("ArrayPacking",function(){B=(0,xe.packArray)(B)}),{emptyGroups:p,patternIdxToConfig:C,charCodeToPatternIdxToConfig:B,hasCustom:n,canBeOptimized:y}}Ve.analyzeTokenTypes=kEe;function REe(r,e){var t=[],i=VY(r);t=t.concat(i.errors);var n=XY(i.valid),s=n.valid;return t=t.concat(n.errors),t=t.concat(FEe(s)),t=t.concat(rj(s)),t=t.concat(ij(s,e)),t=t.concat(nj(s)),t}Ve.validatePatterns=REe;function FEe(r){var e=[],t=(0,xe.filter)(r,function(i){return(0,xe.isRegExp)(i[ko])});return e=e.concat(ZY(t)),e=e.concat($Y(t)),e=e.concat(ej(t)),e=e.concat(tj(t)),e=e.concat(_Y(t)),e}function VY(r){var e=(0,xe.filter)(r,function(n){return!(0,xe.has)(n,ko)}),t=(0,xe.map)(e,function(n){return{message:"Token Type: ->"+n.name+"<- missing static 'PATTERN' property",type:ir.LexerDefinitionErrorType.MISSING_PATTERN,tokenTypes:[n]}}),i=(0,xe.difference)(r,e);return{errors:t,valid:i}}Ve.findMissingPatterns=VY;function XY(r){var e=(0,xe.filter)(r,function(n){var s=n[ko];return!(0,xe.isRegExp)(s)&&!(0,xe.isFunction)(s)&&!(0,xe.has)(s,"exec")&&!(0,xe.isString)(s)}),t=(0,xe.map)(e,function(n){return{message:"Token Type: ->"+n.name+"<- static 'PATTERN' can only be a RegExp, a Function matching the {CustomPatternMatcherFunc} type or an Object matching the {ICustomPattern} interface.",type:ir.LexerDefinitionErrorType.INVALID_PATTERN,tokenTypes:[n]}}),i=(0,xe.difference)(r,e);return{errors:t,valid:i}}Ve.findInvalidPatterns=XY;var NEe=/[^\\][\$]/;function ZY(r){var e=function(n){JY(s,n);function s(){var o=n!==null&&n.apply(this,arguments)||this;return o.found=!1,o}return s.prototype.visitEndAnchor=function(o){this.found=!0},s}(WY.BaseRegExpVisitor),t=(0,xe.filter)(r,function(n){var s=n[ko];try{var o=(0,zY.getRegExpAst)(s),a=new e;return a.visit(o),a.found}catch{return NEe.test(s.source)}}),i=(0,xe.map)(t,function(n){return{message:`Unexpected RegExp Anchor Error: Token Type: ->`+n.name+`<- static 'PATTERN' cannot contain end of input anchor '$' See chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:ir.LexerDefinitionErrorType.EOI_ANCHOR_FOUND,tokenTypes:[n]}});return i}Ve.findEndOfInputAnchor=ZY;function _Y(r){var e=(0,xe.filter)(r,function(i){var n=i[ko];return n.test("")}),t=(0,xe.map)(e,function(i){return{message:"Token Type: ->"+i.name+"<- static 'PATTERN' must not match an empty string",type:ir.LexerDefinitionErrorType.EMPTY_MATCH_PATTERN,tokenTypes:[i]}});return t}Ve.findEmptyMatchRegExps=_Y;var TEe=/[^\\[][\^]|^\^/;function $Y(r){var e=function(n){JY(s,n);function s(){var o=n!==null&&n.apply(this,arguments)||this;return o.found=!1,o}return s.prototype.visitStartAnchor=function(o){this.found=!0},s}(WY.BaseRegExpVisitor),t=(0,xe.filter)(r,function(n){var s=n[ko];try{var o=(0,zY.getRegExpAst)(s),a=new e;return a.visit(o),a.found}catch{return TEe.test(s.source)}}),i=(0,xe.map)(t,function(n){return{message:`Unexpected RegExp Anchor Error: Token Type: ->`+n.name+`<- static 'PATTERN' cannot contain start of input anchor '^' See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:ir.LexerDefinitionErrorType.SOI_ANCHOR_FOUND,tokenTypes:[n]}});return i}Ve.findStartOfInputAnchor=$Y;function ej(r){var e=(0,xe.filter)(r,function(i){var n=i[ko];return n instanceof RegExp&&(n.multiline||n.global)}),t=(0,xe.map)(e,function(i){return{message:"Token Type: ->"+i.name+"<- static 'PATTERN' may NOT contain global('g') or multiline('m')",type:ir.LexerDefinitionErrorType.UNSUPPORTED_FLAGS_FOUND,tokenTypes:[i]}});return t}Ve.findUnsupportedFlags=ej;function tj(r){var e=[],t=(0,xe.map)(r,function(s){return(0,xe.reduce)(r,function(o,a){return s.PATTERN.source===a.PATTERN.source&&!(0,xe.contains)(e,a)&&a.PATTERN!==ir.Lexer.NA&&(e.push(a),o.push(a)),o},[])});t=(0,xe.compact)(t);var i=(0,xe.filter)(t,function(s){return s.length>1}),n=(0,xe.map)(i,function(s){var o=(0,xe.map)(s,function(l){return l.name}),a=(0,xe.first)(s).PATTERN;return{message:"The same RegExp pattern ->"+a+"<-"+("has been used in all of the following Token Types: "+o.join(", ")+" <-"),type:ir.LexerDefinitionErrorType.DUPLICATE_PATTERNS_FOUND,tokenTypes:s}});return n}Ve.findDuplicatePatterns=tj;function rj(r){var e=(0,xe.filter)(r,function(i){if(!(0,xe.has)(i,"GROUP"))return!1;var n=i.GROUP;return n!==ir.Lexer.SKIPPED&&n!==ir.Lexer.NA&&!(0,xe.isString)(n)}),t=(0,xe.map)(e,function(i){return{message:"Token Type: ->"+i.name+"<- static 'GROUP' can only be Lexer.SKIPPED/Lexer.NA/A String",type:ir.LexerDefinitionErrorType.INVALID_GROUP_TYPE_FOUND,tokenTypes:[i]}});return t}Ve.findInvalidGroupType=rj;function ij(r,e){var t=(0,xe.filter)(r,function(n){return n.PUSH_MODE!==void 0&&!(0,xe.contains)(e,n.PUSH_MODE)}),i=(0,xe.map)(t,function(n){var s="Token Type: ->"+n.name+"<- static 'PUSH_MODE' value cannot refer to a Lexer Mode ->"+n.PUSH_MODE+"<-which does not exist";return{message:s,type:ir.LexerDefinitionErrorType.PUSH_MODE_DOES_NOT_EXIST,tokenTypes:[n]}});return i}Ve.findModesThatDoNotExist=ij;function nj(r){var e=[],t=(0,xe.reduce)(r,function(i,n,s){var o=n.PATTERN;return o===ir.Lexer.NA||((0,xe.isString)(o)?i.push({str:o,idx:s,tokenType:n}):(0,xe.isRegExp)(o)&&MEe(o)&&i.push({str:o.source,idx:s,tokenType:n})),i},[]);return(0,xe.forEach)(r,function(i,n){(0,xe.forEach)(t,function(s){var o=s.str,a=s.idx,l=s.tokenType;if(n"+i.name+"<-")+`in the lexer's definition. See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNREACHABLE`;e.push({message:c,type:ir.LexerDefinitionErrorType.UNREACHABLE_PATTERN,tokenTypes:[i,l]})}})}),e}Ve.findUnreachablePatterns=nj;function LEe(r,e){if((0,xe.isRegExp)(e)){var t=e.exec(r);return t!==null&&t.index===0}else{if((0,xe.isFunction)(e))return e(r,0,[],{});if((0,xe.has)(e,"exec"))return e.exec(r,0,[],{});if(typeof e=="string")return e===r;throw Error("non exhaustive match")}}function MEe(r){var e=[".","\\","[","]","|","^","$","(",")","?","*","+","{"];return(0,xe.find)(e,function(t){return r.source.indexOf(t)!==-1})===void 0}function Nv(r){var e=r.ignoreCase?"i":"";return new RegExp("^(?:"+r.source+")",e)}Ve.addStartOfInput=Nv;function Tv(r){var e=r.ignoreCase?"iy":"y";return new RegExp(""+r.source,e)}Ve.addStickyFlag=Tv;function OEe(r,e,t){var i=[];return(0,xe.has)(r,Ve.DEFAULT_MODE)||i.push({message:"A MultiMode Lexer cannot be initialized without a <"+Ve.DEFAULT_MODE+`> property in its definition `,type:ir.LexerDefinitionErrorType.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE}),(0,xe.has)(r,Ve.MODES)||i.push({message:"A MultiMode Lexer cannot be initialized without a <"+Ve.MODES+`> property in its definition `,type:ir.LexerDefinitionErrorType.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY}),(0,xe.has)(r,Ve.MODES)&&(0,xe.has)(r,Ve.DEFAULT_MODE)&&!(0,xe.has)(r.modes,r.defaultMode)&&i.push({message:"A MultiMode Lexer cannot be initialized with a "+Ve.DEFAULT_MODE+": <"+r.defaultMode+`>which does not exist `,type:ir.LexerDefinitionErrorType.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST}),(0,xe.has)(r,Ve.MODES)&&(0,xe.forEach)(r.modes,function(n,s){(0,xe.forEach)(n,function(o,a){(0,xe.isUndefined)(o)&&i.push({message:"A Lexer cannot be initialized using an undefined Token Type. Mode:"+("<"+s+"> at index: <"+a+`> `),type:ir.LexerDefinitionErrorType.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED})})}),i}Ve.performRuntimeChecks=OEe;function KEe(r,e,t){var i=[],n=!1,s=(0,xe.compact)((0,xe.flatten)((0,xe.mapValues)(r.modes,function(l){return l}))),o=(0,xe.reject)(s,function(l){return l[ko]===ir.Lexer.NA}),a=Aj(t);return e&&(0,xe.forEach)(o,function(l){var c=oj(l,a);if(c!==!1){var u=aj(l,c),g={message:u,type:c.issue,tokenType:l};i.push(g)}else(0,xe.has)(l,"LINE_BREAKS")?l.LINE_BREAKS===!0&&(n=!0):(0,Zg.canMatchCharCode)(a,l.PATTERN)&&(n=!0)}),e&&!n&&i.push({message:`Warning: No LINE_BREAKS Found. This Lexer has been defined to track line and column information, But none of the Token Types can be identified as matching a line terminator. See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#LINE_BREAKS for details.`,type:ir.LexerDefinitionErrorType.NO_LINE_BREAKS_FLAGS}),i}Ve.performWarningRuntimeChecks=KEe;function UEe(r){var e={},t=(0,xe.keys)(r);return(0,xe.forEach)(t,function(i){var n=r[i];if((0,xe.isArray)(n))e[i]=[];else throw Error("non exhaustive match")}),e}Ve.cloneEmptyGroups=UEe;function Mv(r){var e=r.PATTERN;if((0,xe.isRegExp)(e))return!1;if((0,xe.isFunction)(e))return!0;if((0,xe.has)(e,"exec"))return!0;if((0,xe.isString)(e))return!1;throw Error("non exhaustive match")}Ve.isCustomPattern=Mv;function sj(r){return(0,xe.isString)(r)&&r.length===1?r.charCodeAt(0):!1}Ve.isShortPattern=sj;Ve.LineTerminatorOptimizedTester={test:function(r){for(var e=r.length,t=this.lastIndex;t Token Type `)+(" Root cause: "+e.errMsg+`. `)+" For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#IDENTIFY_TERMINATOR";if(e.issue===ir.LexerDefinitionErrorType.CUSTOM_LINE_BREAK)return`Warning: A Custom Token Pattern should specify the option. `+(" The problem is in the <"+r.name+`> Token Type `)+" For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_LINE_BREAK";throw Error("non exhaustive match")}Ve.buildLineBreakIssueMessage=aj;function Aj(r){var e=(0,xe.map)(r,function(t){return(0,xe.isString)(t)&&t.length>0?t.charCodeAt(0):t});return e}function Fv(r,e,t){r[e]===void 0?r[e]=[t]:r[e].push(t)}Ve.minOptimizationVal=256;var ny=[];function Lv(r){return r255?255+~~(r/255):r}}});var _g=w(Nt=>{"use strict";Object.defineProperty(Nt,"__esModule",{value:!0});Nt.isTokenType=Nt.hasExtendingTokensTypesMapProperty=Nt.hasExtendingTokensTypesProperty=Nt.hasCategoriesProperty=Nt.hasShortKeyProperty=Nt.singleAssignCategoriesToksMap=Nt.assignCategoriesMapProp=Nt.assignCategoriesTokensProp=Nt.assignTokenDefaultProps=Nt.expandCategories=Nt.augmentTokenTypes=Nt.tokenIdxToClass=Nt.tokenShortNameIdx=Nt.tokenStructuredMatcherNoCategories=Nt.tokenStructuredMatcher=void 0;var Zr=Gt();function GEe(r,e){var t=r.tokenTypeIdx;return t===e.tokenTypeIdx?!0:e.isParent===!0&&e.categoryMatchesMap[t]===!0}Nt.tokenStructuredMatcher=GEe;function YEe(r,e){return r.tokenTypeIdx===e.tokenTypeIdx}Nt.tokenStructuredMatcherNoCategories=YEe;Nt.tokenShortNameIdx=1;Nt.tokenIdxToClass={};function jEe(r){var e=lj(r);cj(e),gj(e),uj(e),(0,Zr.forEach)(e,function(t){t.isParent=t.categoryMatches.length>0})}Nt.augmentTokenTypes=jEe;function lj(r){for(var e=(0,Zr.cloneArr)(r),t=r,i=!0;i;){t=(0,Zr.compact)((0,Zr.flatten)((0,Zr.map)(t,function(s){return s.CATEGORIES})));var n=(0,Zr.difference)(t,e);e=e.concat(n),(0,Zr.isEmpty)(n)?i=!1:t=n}return e}Nt.expandCategories=lj;function cj(r){(0,Zr.forEach)(r,function(e){fj(e)||(Nt.tokenIdxToClass[Nt.tokenShortNameIdx]=e,e.tokenTypeIdx=Nt.tokenShortNameIdx++),Ov(e)&&!(0,Zr.isArray)(e.CATEGORIES)&&(e.CATEGORIES=[e.CATEGORIES]),Ov(e)||(e.CATEGORIES=[]),hj(e)||(e.categoryMatches=[]),pj(e)||(e.categoryMatchesMap={})})}Nt.assignTokenDefaultProps=cj;function uj(r){(0,Zr.forEach)(r,function(e){e.categoryMatches=[],(0,Zr.forEach)(e.categoryMatchesMap,function(t,i){e.categoryMatches.push(Nt.tokenIdxToClass[i].tokenTypeIdx)})})}Nt.assignCategoriesTokensProp=uj;function gj(r){(0,Zr.forEach)(r,function(e){Kv([],e)})}Nt.assignCategoriesMapProp=gj;function Kv(r,e){(0,Zr.forEach)(r,function(t){e.categoryMatchesMap[t.tokenTypeIdx]=!0}),(0,Zr.forEach)(e.CATEGORIES,function(t){var i=r.concat(e);(0,Zr.contains)(i,t)||Kv(i,t)})}Nt.singleAssignCategoriesToksMap=Kv;function fj(r){return(0,Zr.has)(r,"tokenTypeIdx")}Nt.hasShortKeyProperty=fj;function Ov(r){return(0,Zr.has)(r,"CATEGORIES")}Nt.hasCategoriesProperty=Ov;function hj(r){return(0,Zr.has)(r,"categoryMatches")}Nt.hasExtendingTokensTypesProperty=hj;function pj(r){return(0,Zr.has)(r,"categoryMatchesMap")}Nt.hasExtendingTokensTypesMapProperty=pj;function qEe(r){return(0,Zr.has)(r,"tokenTypeIdx")}Nt.isTokenType=qEe});var Uv=w(sy=>{"use strict";Object.defineProperty(sy,"__esModule",{value:!0});sy.defaultLexerErrorProvider=void 0;sy.defaultLexerErrorProvider={buildUnableToPopLexerModeMessage:function(r){return"Unable to pop Lexer Mode after encountering Token ->"+r.image+"<- The Mode Stack is empty"},buildUnexpectedCharactersMessage:function(r,e,t,i,n){return"unexpected character: ->"+r.charAt(e)+"<- at offset: "+e+","+(" skipped "+t+" characters.")}}});var Bd=w(Cc=>{"use strict";Object.defineProperty(Cc,"__esModule",{value:!0});Cc.Lexer=Cc.LexerDefinitionErrorType=void 0;var _s=Rv(),nr=Gt(),JEe=_g(),WEe=Uv(),zEe=ty(),VEe;(function(r){r[r.MISSING_PATTERN=0]="MISSING_PATTERN",r[r.INVALID_PATTERN=1]="INVALID_PATTERN",r[r.EOI_ANCHOR_FOUND=2]="EOI_ANCHOR_FOUND",r[r.UNSUPPORTED_FLAGS_FOUND=3]="UNSUPPORTED_FLAGS_FOUND",r[r.DUPLICATE_PATTERNS_FOUND=4]="DUPLICATE_PATTERNS_FOUND",r[r.INVALID_GROUP_TYPE_FOUND=5]="INVALID_GROUP_TYPE_FOUND",r[r.PUSH_MODE_DOES_NOT_EXIST=6]="PUSH_MODE_DOES_NOT_EXIST",r[r.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE=7]="MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE",r[r.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY=8]="MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY",r[r.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST=9]="MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST",r[r.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED=10]="LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED",r[r.SOI_ANCHOR_FOUND=11]="SOI_ANCHOR_FOUND",r[r.EMPTY_MATCH_PATTERN=12]="EMPTY_MATCH_PATTERN",r[r.NO_LINE_BREAKS_FLAGS=13]="NO_LINE_BREAKS_FLAGS",r[r.UNREACHABLE_PATTERN=14]="UNREACHABLE_PATTERN",r[r.IDENTIFY_TERMINATOR=15]="IDENTIFY_TERMINATOR",r[r.CUSTOM_LINE_BREAK=16]="CUSTOM_LINE_BREAK"})(VEe=Cc.LexerDefinitionErrorType||(Cc.LexerDefinitionErrorType={}));var bd={deferDefinitionErrorsHandling:!1,positionTracking:"full",lineTerminatorsPattern:/\n|\r\n?/g,lineTerminatorCharacters:[` `,"\r"],ensureOptimizations:!1,safeMode:!1,errorMessageProvider:WEe.defaultLexerErrorProvider,traceInitPerf:!1,skipValidations:!1};Object.freeze(bd);var XEe=function(){function r(e,t){var i=this;if(t===void 0&&(t=bd),this.lexerDefinition=e,this.lexerDefinitionErrors=[],this.lexerDefinitionWarning=[],this.patternIdxToConfig={},this.charCodeToPatternIdxToConfig={},this.modes=[],this.emptyGroups={},this.config=void 0,this.trackStartLines=!0,this.trackEndLines=!0,this.hasCustom=!1,this.canModeBeOptimized={},typeof t=="boolean")throw Error(`The second argument to the Lexer constructor is now an ILexerConfig Object. a boolean 2nd argument is no longer supported`);this.config=(0,nr.merge)(bd,t);var n=this.config.traceInitPerf;n===!0?(this.traceInitMaxIdent=1/0,this.traceInitPerf=!0):typeof n=="number"&&(this.traceInitMaxIdent=n,this.traceInitPerf=!0),this.traceInitIndent=-1,this.TRACE_INIT("Lexer Constructor",function(){var s,o=!0;i.TRACE_INIT("Lexer Config handling",function(){if(i.config.lineTerminatorsPattern===bd.lineTerminatorsPattern)i.config.lineTerminatorsPattern=_s.LineTerminatorOptimizedTester;else if(i.config.lineTerminatorCharacters===bd.lineTerminatorCharacters)throw Error(`Error: Missing property on the Lexer config. For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#MISSING_LINE_TERM_CHARS`);if(t.safeMode&&t.ensureOptimizations)throw Error('"safeMode" and "ensureOptimizations" flags are mutually exclusive.');i.trackStartLines=/full|onlyStart/i.test(i.config.positionTracking),i.trackEndLines=/full/i.test(i.config.positionTracking),(0,nr.isArray)(e)?(s={modes:{}},s.modes[_s.DEFAULT_MODE]=(0,nr.cloneArr)(e),s[_s.DEFAULT_MODE]=_s.DEFAULT_MODE):(o=!1,s=(0,nr.cloneObj)(e))}),i.config.skipValidations===!1&&(i.TRACE_INIT("performRuntimeChecks",function(){i.lexerDefinitionErrors=i.lexerDefinitionErrors.concat((0,_s.performRuntimeChecks)(s,i.trackStartLines,i.config.lineTerminatorCharacters))}),i.TRACE_INIT("performWarningRuntimeChecks",function(){i.lexerDefinitionWarning=i.lexerDefinitionWarning.concat((0,_s.performWarningRuntimeChecks)(s,i.trackStartLines,i.config.lineTerminatorCharacters))})),s.modes=s.modes?s.modes:{},(0,nr.forEach)(s.modes,function(u,g){s.modes[g]=(0,nr.reject)(u,function(f){return(0,nr.isUndefined)(f)})});var a=(0,nr.keys)(s.modes);if((0,nr.forEach)(s.modes,function(u,g){i.TRACE_INIT("Mode: <"+g+"> processing",function(){if(i.modes.push(g),i.config.skipValidations===!1&&i.TRACE_INIT("validatePatterns",function(){i.lexerDefinitionErrors=i.lexerDefinitionErrors.concat((0,_s.validatePatterns)(u,a))}),(0,nr.isEmpty)(i.lexerDefinitionErrors)){(0,JEe.augmentTokenTypes)(u);var f;i.TRACE_INIT("analyzeTokenTypes",function(){f=(0,_s.analyzeTokenTypes)(u,{lineTerminatorCharacters:i.config.lineTerminatorCharacters,positionTracking:t.positionTracking,ensureOptimizations:t.ensureOptimizations,safeMode:t.safeMode,tracer:i.TRACE_INIT.bind(i)})}),i.patternIdxToConfig[g]=f.patternIdxToConfig,i.charCodeToPatternIdxToConfig[g]=f.charCodeToPatternIdxToConfig,i.emptyGroups=(0,nr.merge)(i.emptyGroups,f.emptyGroups),i.hasCustom=f.hasCustom||i.hasCustom,i.canModeBeOptimized[g]=f.canBeOptimized}})}),i.defaultMode=s.defaultMode,!(0,nr.isEmpty)(i.lexerDefinitionErrors)&&!i.config.deferDefinitionErrorsHandling){var l=(0,nr.map)(i.lexerDefinitionErrors,function(u){return u.message}),c=l.join(`----------------------- `);throw new Error(`Errors detected in definition of Lexer: `+c)}(0,nr.forEach)(i.lexerDefinitionWarning,function(u){(0,nr.PRINT_WARNING)(u.message)}),i.TRACE_INIT("Choosing sub-methods implementations",function(){if(_s.SUPPORT_STICKY?(i.chopInput=nr.IDENTITY,i.match=i.matchWithTest):(i.updateLastIndex=nr.NOOP,i.match=i.matchWithExec),o&&(i.handleModes=nr.NOOP),i.trackStartLines===!1&&(i.computeNewColumn=nr.IDENTITY),i.trackEndLines===!1&&(i.updateTokenEndLineColumnLocation=nr.NOOP),/full/i.test(i.config.positionTracking))i.createTokenInstance=i.createFullToken;else if(/onlyStart/i.test(i.config.positionTracking))i.createTokenInstance=i.createStartOnlyToken;else if(/onlyOffset/i.test(i.config.positionTracking))i.createTokenInstance=i.createOffsetOnlyToken;else throw Error('Invalid config option: "'+i.config.positionTracking+'"');i.hasCustom?(i.addToken=i.addTokenUsingPush,i.handlePayload=i.handlePayloadWithCustom):(i.addToken=i.addTokenUsingMemberAccess,i.handlePayload=i.handlePayloadNoCustom)}),i.TRACE_INIT("Failed Optimization Warnings",function(){var u=(0,nr.reduce)(i.canModeBeOptimized,function(g,f,h){return f===!1&&g.push(h),g},[]);if(t.ensureOptimizations&&!(0,nr.isEmpty)(u))throw Error("Lexer Modes: < "+u.join(", ")+` > cannot be optimized. Disable the "ensureOptimizations" lexer config flag to silently ignore this and run the lexer in an un-optimized mode. Or inspect the console log for details on how to resolve these issues.`)}),i.TRACE_INIT("clearRegExpParserCache",function(){(0,zEe.clearRegExpParserCache)()}),i.TRACE_INIT("toFastProperties",function(){(0,nr.toFastProperties)(i)})})}return r.prototype.tokenize=function(e,t){if(t===void 0&&(t=this.defaultMode),!(0,nr.isEmpty)(this.lexerDefinitionErrors)){var i=(0,nr.map)(this.lexerDefinitionErrors,function(o){return o.message}),n=i.join(`----------------------- `);throw new Error(`Unable to Tokenize because Errors detected in definition of Lexer: `+n)}var s=this.tokenizeInternal(e,t);return s},r.prototype.tokenizeInternal=function(e,t){var i=this,n,s,o,a,l,c,u,g,f,h,p,C,y,B,v,D,T=e,H=T.length,j=0,$=0,V=this.hasCustom?0:Math.floor(e.length/10),W=new Array(V),_=[],A=this.trackStartLines?1:void 0,Ae=this.trackStartLines?1:void 0,ge=(0,_s.cloneEmptyGroups)(this.emptyGroups),re=this.trackStartLines,M=this.config.lineTerminatorsPattern,F=0,ue=[],pe=[],ke=[],Fe=[];Object.freeze(Fe);var Ne=void 0;function oe(){return ue}function le(pr){var Ii=(0,_s.charCodeToOptimizedIndex)(pr),rs=pe[Ii];return rs===void 0?Fe:rs}var Be=function(pr){if(ke.length===1&&pr.tokenType.PUSH_MODE===void 0){var Ii=i.config.errorMessageProvider.buildUnableToPopLexerModeMessage(pr);_.push({offset:pr.startOffset,line:pr.startLine!==void 0?pr.startLine:void 0,column:pr.startColumn!==void 0?pr.startColumn:void 0,length:pr.image.length,message:Ii})}else{ke.pop();var rs=(0,nr.last)(ke);ue=i.patternIdxToConfig[rs],pe=i.charCodeToPatternIdxToConfig[rs],F=ue.length;var fa=i.canModeBeOptimized[rs]&&i.config.safeMode===!1;pe&&fa?Ne=le:Ne=oe}};function fe(pr){ke.push(pr),pe=this.charCodeToPatternIdxToConfig[pr],ue=this.patternIdxToConfig[pr],F=ue.length,F=ue.length;var Ii=this.canModeBeOptimized[pr]&&this.config.safeMode===!1;pe&&Ii?Ne=le:Ne=oe}fe.call(this,t);for(var ae;jc.length){c=a,u=g,ae=_e;break}}}break}}if(c!==null){if(f=c.length,h=ae.group,h!==void 0&&(p=ae.tokenTypeIdx,C=this.createTokenInstance(c,j,p,ae.tokenType,A,Ae,f),this.handlePayload(C,u),h===!1?$=this.addToken(W,$,C):ge[h].push(C)),e=this.chopInput(e,f),j=j+f,Ae=this.computeNewColumn(Ae,f),re===!0&&ae.canLineTerminator===!0){var It=0,Mr=void 0,ii=void 0;M.lastIndex=0;do Mr=M.test(c),Mr===!0&&(ii=M.lastIndex-1,It++);while(Mr===!0);It!==0&&(A=A+It,Ae=f-ii,this.updateTokenEndLineColumnLocation(C,h,ii,It,A,Ae,f))}this.handleModes(ae,Be,fe,C)}else{for(var gi=j,hr=A,fi=Ae,ni=!1;!ni&&j <"+e+">");var n=(0,nr.timer)(t),s=n.time,o=n.value,a=s>10?console.warn:console.log;return this.traceInitIndent time: "+s+"ms"),this.traceInitIndent--,o}else return t()},r.SKIPPED="This marks a skipped Token pattern, this means each token identified by it willbe consumed and then thrown into oblivion, this can be used to for example to completely ignore whitespace.",r.NA=/NOT_APPLICABLE/,r}();Cc.Lexer=XEe});var TA=w(Qi=>{"use strict";Object.defineProperty(Qi,"__esModule",{value:!0});Qi.tokenMatcher=Qi.createTokenInstance=Qi.EOF=Qi.createToken=Qi.hasTokenLabel=Qi.tokenName=Qi.tokenLabel=void 0;var $s=Gt(),ZEe=Bd(),Hv=_g();function _Ee(r){return bj(r)?r.LABEL:r.name}Qi.tokenLabel=_Ee;function $Ee(r){return r.name}Qi.tokenName=$Ee;function bj(r){return(0,$s.isString)(r.LABEL)&&r.LABEL!==""}Qi.hasTokenLabel=bj;var eIe="parent",dj="categories",Cj="label",mj="group",Ej="push_mode",Ij="pop_mode",yj="longer_alt",wj="line_breaks",Bj="start_chars_hint";function Qj(r){return tIe(r)}Qi.createToken=Qj;function tIe(r){var e=r.pattern,t={};if(t.name=r.name,(0,$s.isUndefined)(e)||(t.PATTERN=e),(0,$s.has)(r,eIe))throw`The parent property is no longer supported. See: https://github.com/chevrotain/chevrotain/issues/564#issuecomment-349062346 for details.`;return(0,$s.has)(r,dj)&&(t.CATEGORIES=r[dj]),(0,Hv.augmentTokenTypes)([t]),(0,$s.has)(r,Cj)&&(t.LABEL=r[Cj]),(0,$s.has)(r,mj)&&(t.GROUP=r[mj]),(0,$s.has)(r,Ij)&&(t.POP_MODE=r[Ij]),(0,$s.has)(r,Ej)&&(t.PUSH_MODE=r[Ej]),(0,$s.has)(r,yj)&&(t.LONGER_ALT=r[yj]),(0,$s.has)(r,wj)&&(t.LINE_BREAKS=r[wj]),(0,$s.has)(r,Bj)&&(t.START_CHARS_HINT=r[Bj]),t}Qi.EOF=Qj({name:"EOF",pattern:ZEe.Lexer.NA});(0,Hv.augmentTokenTypes)([Qi.EOF]);function rIe(r,e,t,i,n,s,o,a){return{image:e,startOffset:t,endOffset:i,startLine:n,endLine:s,startColumn:o,endColumn:a,tokenTypeIdx:r.tokenTypeIdx,tokenType:r}}Qi.createTokenInstance=rIe;function iIe(r,e){return(0,Hv.tokenStructuredMatcher)(r,e)}Qi.tokenMatcher=iIe});var mn=w(zt=>{"use strict";var Pa=zt&&zt.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(zt,"__esModule",{value:!0});zt.serializeProduction=zt.serializeGrammar=zt.Terminal=zt.Alternation=zt.RepetitionWithSeparator=zt.Repetition=zt.RepetitionMandatoryWithSeparator=zt.RepetitionMandatory=zt.Option=zt.Alternative=zt.Rule=zt.NonTerminal=zt.AbstractProduction=void 0;var Ar=Gt(),nIe=TA(),Ro=function(){function r(e){this._definition=e}return Object.defineProperty(r.prototype,"definition",{get:function(){return this._definition},set:function(e){this._definition=e},enumerable:!1,configurable:!0}),r.prototype.accept=function(e){e.visit(this),(0,Ar.forEach)(this.definition,function(t){t.accept(e)})},r}();zt.AbstractProduction=Ro;var Sj=function(r){Pa(e,r);function e(t){var i=r.call(this,[])||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return Object.defineProperty(e.prototype,"definition",{get:function(){return this.referencedRule!==void 0?this.referencedRule.definition:[]},set:function(t){},enumerable:!1,configurable:!0}),e.prototype.accept=function(t){t.visit(this)},e}(Ro);zt.NonTerminal=Sj;var vj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.orgText="",(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.Rule=vj;var xj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.ignoreAmbiguities=!1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.Alternative=xj;var Pj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.Option=Pj;var Dj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.RepetitionMandatory=Dj;var kj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.RepetitionMandatoryWithSeparator=kj;var Rj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.Repetition=Rj;var Fj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return e}(Ro);zt.RepetitionWithSeparator=Fj;var Nj=function(r){Pa(e,r);function e(t){var i=r.call(this,t.definition)||this;return i.idx=1,i.ignoreAmbiguities=!1,i.hasPredicates=!1,(0,Ar.assign)(i,(0,Ar.pick)(t,function(n){return n!==void 0})),i}return Object.defineProperty(e.prototype,"definition",{get:function(){return this._definition},set:function(t){this._definition=t},enumerable:!1,configurable:!0}),e}(Ro);zt.Alternation=Nj;var oy=function(){function r(e){this.idx=1,(0,Ar.assign)(this,(0,Ar.pick)(e,function(t){return t!==void 0}))}return r.prototype.accept=function(e){e.visit(this)},r}();zt.Terminal=oy;function sIe(r){return(0,Ar.map)(r,Qd)}zt.serializeGrammar=sIe;function Qd(r){function e(s){return(0,Ar.map)(s,Qd)}if(r instanceof Sj){var t={type:"NonTerminal",name:r.nonTerminalName,idx:r.idx};return(0,Ar.isString)(r.label)&&(t.label=r.label),t}else{if(r instanceof xj)return{type:"Alternative",definition:e(r.definition)};if(r instanceof Pj)return{type:"Option",idx:r.idx,definition:e(r.definition)};if(r instanceof Dj)return{type:"RepetitionMandatory",idx:r.idx,definition:e(r.definition)};if(r instanceof kj)return{type:"RepetitionMandatoryWithSeparator",idx:r.idx,separator:Qd(new oy({terminalType:r.separator})),definition:e(r.definition)};if(r instanceof Fj)return{type:"RepetitionWithSeparator",idx:r.idx,separator:Qd(new oy({terminalType:r.separator})),definition:e(r.definition)};if(r instanceof Rj)return{type:"Repetition",idx:r.idx,definition:e(r.definition)};if(r instanceof Nj)return{type:"Alternation",idx:r.idx,definition:e(r.definition)};if(r instanceof oy){var i={type:"Terminal",name:r.terminalType.name,label:(0,nIe.tokenLabel)(r.terminalType),idx:r.idx};(0,Ar.isString)(r.label)&&(i.terminalLabel=r.label);var n=r.terminalType.PATTERN;return r.terminalType.PATTERN&&(i.pattern=(0,Ar.isRegExp)(n)?n.source:n),i}else{if(r instanceof vj)return{type:"Rule",name:r.name,orgText:r.orgText,definition:e(r.definition)};throw Error("non exhaustive match")}}}zt.serializeProduction=Qd});var Ay=w(ay=>{"use strict";Object.defineProperty(ay,"__esModule",{value:!0});ay.RestWalker=void 0;var Gv=Gt(),En=mn(),oIe=function(){function r(){}return r.prototype.walk=function(e,t){var i=this;t===void 0&&(t=[]),(0,Gv.forEach)(e.definition,function(n,s){var o=(0,Gv.drop)(e.definition,s+1);if(n instanceof En.NonTerminal)i.walkProdRef(n,o,t);else if(n instanceof En.Terminal)i.walkTerminal(n,o,t);else if(n instanceof En.Alternative)i.walkFlat(n,o,t);else if(n instanceof En.Option)i.walkOption(n,o,t);else if(n instanceof En.RepetitionMandatory)i.walkAtLeastOne(n,o,t);else if(n instanceof En.RepetitionMandatoryWithSeparator)i.walkAtLeastOneSep(n,o,t);else if(n instanceof En.RepetitionWithSeparator)i.walkManySep(n,o,t);else if(n instanceof En.Repetition)i.walkMany(n,o,t);else if(n instanceof En.Alternation)i.walkOr(n,o,t);else throw Error("non exhaustive match")})},r.prototype.walkTerminal=function(e,t,i){},r.prototype.walkProdRef=function(e,t,i){},r.prototype.walkFlat=function(e,t,i){var n=t.concat(i);this.walk(e,n)},r.prototype.walkOption=function(e,t,i){var n=t.concat(i);this.walk(e,n)},r.prototype.walkAtLeastOne=function(e,t,i){var n=[new En.Option({definition:e.definition})].concat(t,i);this.walk(e,n)},r.prototype.walkAtLeastOneSep=function(e,t,i){var n=Tj(e,t,i);this.walk(e,n)},r.prototype.walkMany=function(e,t,i){var n=[new En.Option({definition:e.definition})].concat(t,i);this.walk(e,n)},r.prototype.walkManySep=function(e,t,i){var n=Tj(e,t,i);this.walk(e,n)},r.prototype.walkOr=function(e,t,i){var n=this,s=t.concat(i);(0,Gv.forEach)(e.definition,function(o){var a=new En.Alternative({definition:[o]});n.walk(a,s)})},r}();ay.RestWalker=oIe;function Tj(r,e,t){var i=[new En.Option({definition:[new En.Terminal({terminalType:r.separator})].concat(r.definition)})],n=i.concat(e,t);return n}});var $g=w(ly=>{"use strict";Object.defineProperty(ly,"__esModule",{value:!0});ly.GAstVisitor=void 0;var Fo=mn(),aIe=function(){function r(){}return r.prototype.visit=function(e){var t=e;switch(t.constructor){case Fo.NonTerminal:return this.visitNonTerminal(t);case Fo.Alternative:return this.visitAlternative(t);case Fo.Option:return this.visitOption(t);case Fo.RepetitionMandatory:return this.visitRepetitionMandatory(t);case Fo.RepetitionMandatoryWithSeparator:return this.visitRepetitionMandatoryWithSeparator(t);case Fo.RepetitionWithSeparator:return this.visitRepetitionWithSeparator(t);case Fo.Repetition:return this.visitRepetition(t);case Fo.Alternation:return this.visitAlternation(t);case Fo.Terminal:return this.visitTerminal(t);case Fo.Rule:return this.visitRule(t);default:throw Error("non exhaustive match")}},r.prototype.visitNonTerminal=function(e){},r.prototype.visitAlternative=function(e){},r.prototype.visitOption=function(e){},r.prototype.visitRepetition=function(e){},r.prototype.visitRepetitionMandatory=function(e){},r.prototype.visitRepetitionMandatoryWithSeparator=function(e){},r.prototype.visitRepetitionWithSeparator=function(e){},r.prototype.visitAlternation=function(e){},r.prototype.visitTerminal=function(e){},r.prototype.visitRule=function(e){},r}();ly.GAstVisitor=aIe});var vd=w(Oi=>{"use strict";var AIe=Oi&&Oi.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Oi,"__esModule",{value:!0});Oi.collectMethods=Oi.DslMethodsCollectorVisitor=Oi.getProductionDslName=Oi.isBranchingProd=Oi.isOptionalProd=Oi.isSequenceProd=void 0;var Sd=Gt(),br=mn(),lIe=$g();function cIe(r){return r instanceof br.Alternative||r instanceof br.Option||r instanceof br.Repetition||r instanceof br.RepetitionMandatory||r instanceof br.RepetitionMandatoryWithSeparator||r instanceof br.RepetitionWithSeparator||r instanceof br.Terminal||r instanceof br.Rule}Oi.isSequenceProd=cIe;function Yv(r,e){e===void 0&&(e=[]);var t=r instanceof br.Option||r instanceof br.Repetition||r instanceof br.RepetitionWithSeparator;return t?!0:r instanceof br.Alternation?(0,Sd.some)(r.definition,function(i){return Yv(i,e)}):r instanceof br.NonTerminal&&(0,Sd.contains)(e,r)?!1:r instanceof br.AbstractProduction?(r instanceof br.NonTerminal&&e.push(r),(0,Sd.every)(r.definition,function(i){return Yv(i,e)})):!1}Oi.isOptionalProd=Yv;function uIe(r){return r instanceof br.Alternation}Oi.isBranchingProd=uIe;function gIe(r){if(r instanceof br.NonTerminal)return"SUBRULE";if(r instanceof br.Option)return"OPTION";if(r instanceof br.Alternation)return"OR";if(r instanceof br.RepetitionMandatory)return"AT_LEAST_ONE";if(r instanceof br.RepetitionMandatoryWithSeparator)return"AT_LEAST_ONE_SEP";if(r instanceof br.RepetitionWithSeparator)return"MANY_SEP";if(r instanceof br.Repetition)return"MANY";if(r instanceof br.Terminal)return"CONSUME";throw Error("non exhaustive match")}Oi.getProductionDslName=gIe;var Lj=function(r){AIe(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.separator="-",t.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]},t}return e.prototype.reset=function(){this.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]}},e.prototype.visitTerminal=function(t){var i=t.terminalType.name+this.separator+"Terminal";(0,Sd.has)(this.dslMethods,i)||(this.dslMethods[i]=[]),this.dslMethods[i].push(t)},e.prototype.visitNonTerminal=function(t){var i=t.nonTerminalName+this.separator+"Terminal";(0,Sd.has)(this.dslMethods,i)||(this.dslMethods[i]=[]),this.dslMethods[i].push(t)},e.prototype.visitOption=function(t){this.dslMethods.option.push(t)},e.prototype.visitRepetitionWithSeparator=function(t){this.dslMethods.repetitionWithSeparator.push(t)},e.prototype.visitRepetitionMandatory=function(t){this.dslMethods.repetitionMandatory.push(t)},e.prototype.visitRepetitionMandatoryWithSeparator=function(t){this.dslMethods.repetitionMandatoryWithSeparator.push(t)},e.prototype.visitRepetition=function(t){this.dslMethods.repetition.push(t)},e.prototype.visitAlternation=function(t){this.dslMethods.alternation.push(t)},e}(lIe.GAstVisitor);Oi.DslMethodsCollectorVisitor=Lj;var cy=new Lj;function fIe(r){cy.reset(),r.accept(cy);var e=cy.dslMethods;return cy.reset(),e}Oi.collectMethods=fIe});var qv=w(No=>{"use strict";Object.defineProperty(No,"__esModule",{value:!0});No.firstForTerminal=No.firstForBranching=No.firstForSequence=No.first=void 0;var uy=Gt(),Mj=mn(),jv=vd();function gy(r){if(r instanceof Mj.NonTerminal)return gy(r.referencedRule);if(r instanceof Mj.Terminal)return Uj(r);if((0,jv.isSequenceProd)(r))return Oj(r);if((0,jv.isBranchingProd)(r))return Kj(r);throw Error("non exhaustive match")}No.first=gy;function Oj(r){for(var e=[],t=r.definition,i=0,n=t.length>i,s,o=!0;n&&o;)s=t[i],o=(0,jv.isOptionalProd)(s),e=e.concat(gy(s)),i=i+1,n=t.length>i;return(0,uy.uniq)(e)}No.firstForSequence=Oj;function Kj(r){var e=(0,uy.map)(r.definition,function(t){return gy(t)});return(0,uy.uniq)((0,uy.flatten)(e))}No.firstForBranching=Kj;function Uj(r){return[r.terminalType]}No.firstForTerminal=Uj});var Jv=w(fy=>{"use strict";Object.defineProperty(fy,"__esModule",{value:!0});fy.IN=void 0;fy.IN="_~IN~_"});var qj=w(fs=>{"use strict";var hIe=fs&&fs.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(fs,"__esModule",{value:!0});fs.buildInProdFollowPrefix=fs.buildBetweenProdsFollowPrefix=fs.computeAllProdsFollows=fs.ResyncFollowsWalker=void 0;var pIe=Ay(),dIe=qv(),Hj=Gt(),Gj=Jv(),CIe=mn(),Yj=function(r){hIe(e,r);function e(t){var i=r.call(this)||this;return i.topProd=t,i.follows={},i}return e.prototype.startWalking=function(){return this.walk(this.topProd),this.follows},e.prototype.walkTerminal=function(t,i,n){},e.prototype.walkProdRef=function(t,i,n){var s=jj(t.referencedRule,t.idx)+this.topProd.name,o=i.concat(n),a=new CIe.Alternative({definition:o}),l=(0,dIe.first)(a);this.follows[s]=l},e}(pIe.RestWalker);fs.ResyncFollowsWalker=Yj;function mIe(r){var e={};return(0,Hj.forEach)(r,function(t){var i=new Yj(t).startWalking();(0,Hj.assign)(e,i)}),e}fs.computeAllProdsFollows=mIe;function jj(r,e){return r.name+e+Gj.IN}fs.buildBetweenProdsFollowPrefix=jj;function EIe(r){var e=r.terminalType.name;return e+r.idx+Gj.IN}fs.buildInProdFollowPrefix=EIe});var xd=w(Da=>{"use strict";Object.defineProperty(Da,"__esModule",{value:!0});Da.defaultGrammarValidatorErrorProvider=Da.defaultGrammarResolverErrorProvider=Da.defaultParserErrorProvider=void 0;var ef=TA(),IIe=Gt(),eo=Gt(),Wv=mn(),Jj=vd();Da.defaultParserErrorProvider={buildMismatchTokenMessage:function(r){var e=r.expected,t=r.actual,i=r.previous,n=r.ruleName,s=(0,ef.hasTokenLabel)(e),o=s?"--> "+(0,ef.tokenLabel)(e)+" <--":"token of type --> "+e.name+" <--",a="Expecting "+o+" but found --> '"+t.image+"' <--";return a},buildNotAllInputParsedMessage:function(r){var e=r.firstRedundant,t=r.ruleName;return"Redundant input, expecting EOF but found: "+e.image},buildNoViableAltMessage:function(r){var e=r.expectedPathsPerAlt,t=r.actual,i=r.previous,n=r.customUserDescription,s=r.ruleName,o="Expecting: ",a=(0,eo.first)(t).image,l=` but found: '`+a+"'";if(n)return o+n+l;var c=(0,eo.reduce)(e,function(h,p){return h.concat(p)},[]),u=(0,eo.map)(c,function(h){return"["+(0,eo.map)(h,function(p){return(0,ef.tokenLabel)(p)}).join(", ")+"]"}),g=(0,eo.map)(u,function(h,p){return" "+(p+1)+". "+h}),f=`one of these possible Token sequences: `+g.join(` `);return o+f+l},buildEarlyExitMessage:function(r){var e=r.expectedIterationPaths,t=r.actual,i=r.customUserDescription,n=r.ruleName,s="Expecting: ",o=(0,eo.first)(t).image,a=` but found: '`+o+"'";if(i)return s+i+a;var l=(0,eo.map)(e,function(u){return"["+(0,eo.map)(u,function(g){return(0,ef.tokenLabel)(g)}).join(",")+"]"}),c=`expecting at least one iteration which starts with one of these possible Token sequences:: `+("<"+l.join(" ,")+">");return s+c+a}};Object.freeze(Da.defaultParserErrorProvider);Da.defaultGrammarResolverErrorProvider={buildRuleNotFoundError:function(r,e){var t="Invalid grammar, reference to a rule which is not defined: ->"+e.nonTerminalName+`<- inside top level rule: ->`+r.name+"<-";return t}};Da.defaultGrammarValidatorErrorProvider={buildDuplicateFoundError:function(r,e){function t(u){return u instanceof Wv.Terminal?u.terminalType.name:u instanceof Wv.NonTerminal?u.nonTerminalName:""}var i=r.name,n=(0,eo.first)(e),s=n.idx,o=(0,Jj.getProductionDslName)(n),a=t(n),l=s>0,c="->"+o+(l?s:"")+"<- "+(a?"with argument: ->"+a+"<-":"")+` appears more than once (`+e.length+" times) in the top level rule: ->"+i+`<-. For further details see: https://chevrotain.io/docs/FAQ.html#NUMERICAL_SUFFIXES `;return c=c.replace(/[ \t]+/g," "),c=c.replace(/\s\s+/g,` `),c},buildNamespaceConflictError:function(r){var e=`Namespace conflict found in grammar. `+("The grammar has both a Terminal(Token) and a Non-Terminal(Rule) named: <"+r.name+`>. `)+`To resolve this make sure each Terminal and Non-Terminal names are unique This is easy to accomplish by using the convention that Terminal names start with an uppercase letter and Non-Terminal names start with a lower case letter.`;return e},buildAlternationPrefixAmbiguityError:function(r){var e=(0,eo.map)(r.prefixPath,function(n){return(0,ef.tokenLabel)(n)}).join(", "),t=r.alternation.idx===0?"":r.alternation.idx,i="Ambiguous alternatives: <"+r.ambiguityIndices.join(" ,")+`> due to common lookahead prefix `+("in inside <"+r.topLevelRule.name+`> Rule, `)+("<"+e+`> may appears as a prefix path in all these alternatives. `)+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#COMMON_PREFIX For Further details.`;return i},buildAlternationAmbiguityError:function(r){var e=(0,eo.map)(r.prefixPath,function(n){return(0,ef.tokenLabel)(n)}).join(", "),t=r.alternation.idx===0?"":r.alternation.idx,i="Ambiguous Alternatives Detected: <"+r.ambiguityIndices.join(" ,")+"> in "+(" inside <"+r.topLevelRule.name+`> Rule, `)+("<"+e+`> may appears as a prefix path in all these alternatives. `);return i=i+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#AMBIGUOUS_ALTERNATIVES For Further details.`,i},buildEmptyRepetitionError:function(r){var e=(0,Jj.getProductionDslName)(r.repetition);r.repetition.idx!==0&&(e+=r.repetition.idx);var t="The repetition <"+e+"> within Rule <"+r.topLevelRule.name+`> can never consume any tokens. This could lead to an infinite loop.`;return t},buildTokenNameError:function(r){return"deprecated"},buildEmptyAlternationError:function(r){var e="Ambiguous empty alternative: <"+(r.emptyChoiceIdx+1)+">"+(" in inside <"+r.topLevelRule.name+`> Rule. `)+"Only the last alternative may be an empty alternative.";return e},buildTooManyAlternativesError:function(r){var e=`An Alternation cannot have more than 256 alternatives: `+(" inside <"+r.topLevelRule.name+`> Rule. has `+(r.alternation.definition.length+1)+" alternatives.");return e},buildLeftRecursionError:function(r){var e=r.topLevelRule.name,t=IIe.map(r.leftRecursionPath,function(s){return s.name}),i=e+" --> "+t.concat([e]).join(" --> "),n=`Left Recursion found in grammar. `+("rule: <"+e+`> can be invoked from itself (directly or indirectly) `)+(`without consuming any Tokens. The grammar path that causes this is: `+i+` `)+` To fix this refactor your grammar to remove the left recursion. see: https://en.wikipedia.org/wiki/LL_parser#Left_Factoring.`;return n},buildInvalidRuleNameError:function(r){return"deprecated"},buildDuplicateRuleNameError:function(r){var e;r.topLevelRule instanceof Wv.Rule?e=r.topLevelRule.name:e=r.topLevelRule;var t="Duplicate definition, rule: ->"+e+"<- is already defined in the grammar: ->"+r.grammarName+"<-";return t}}});var Vj=w(LA=>{"use strict";var yIe=LA&&LA.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(LA,"__esModule",{value:!0});LA.GastRefResolverVisitor=LA.resolveGrammar=void 0;var wIe=jn(),Wj=Gt(),BIe=$g();function bIe(r,e){var t=new zj(r,e);return t.resolveRefs(),t.errors}LA.resolveGrammar=bIe;var zj=function(r){yIe(e,r);function e(t,i){var n=r.call(this)||this;return n.nameToTopRule=t,n.errMsgProvider=i,n.errors=[],n}return e.prototype.resolveRefs=function(){var t=this;(0,Wj.forEach)((0,Wj.values)(this.nameToTopRule),function(i){t.currTopLevel=i,i.accept(t)})},e.prototype.visitNonTerminal=function(t){var i=this.nameToTopRule[t.nonTerminalName];if(i)t.referencedRule=i;else{var n=this.errMsgProvider.buildRuleNotFoundError(this.currTopLevel,t);this.errors.push({message:n,type:wIe.ParserDefinitionErrorType.UNRESOLVED_SUBRULE_REF,ruleName:this.currTopLevel.name,unresolvedRefName:t.nonTerminalName})}},e}(BIe.GAstVisitor);LA.GastRefResolverVisitor=zj});var Dd=w(Nr=>{"use strict";var mc=Nr&&Nr.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Nr,"__esModule",{value:!0});Nr.nextPossibleTokensAfter=Nr.possiblePathsFrom=Nr.NextTerminalAfterAtLeastOneSepWalker=Nr.NextTerminalAfterAtLeastOneWalker=Nr.NextTerminalAfterManySepWalker=Nr.NextTerminalAfterManyWalker=Nr.AbstractNextTerminalAfterProductionWalker=Nr.NextAfterTokenWalker=Nr.AbstractNextPossibleTokensWalker=void 0;var Xj=Ay(),Kt=Gt(),QIe=qv(),kt=mn(),Zj=function(r){mc(e,r);function e(t,i){var n=r.call(this)||this;return n.topProd=t,n.path=i,n.possibleTokTypes=[],n.nextProductionName="",n.nextProductionOccurrence=0,n.found=!1,n.isAtEndOfPath=!1,n}return e.prototype.startWalking=function(){if(this.found=!1,this.path.ruleStack[0]!==this.topProd.name)throw Error("The path does not start with the walker's top Rule!");return this.ruleStack=(0,Kt.cloneArr)(this.path.ruleStack).reverse(),this.occurrenceStack=(0,Kt.cloneArr)(this.path.occurrenceStack).reverse(),this.ruleStack.pop(),this.occurrenceStack.pop(),this.updateExpectedNext(),this.walk(this.topProd),this.possibleTokTypes},e.prototype.walk=function(t,i){i===void 0&&(i=[]),this.found||r.prototype.walk.call(this,t,i)},e.prototype.walkProdRef=function(t,i,n){if(t.referencedRule.name===this.nextProductionName&&t.idx===this.nextProductionOccurrence){var s=i.concat(n);this.updateExpectedNext(),this.walk(t.referencedRule,s)}},e.prototype.updateExpectedNext=function(){(0,Kt.isEmpty)(this.ruleStack)?(this.nextProductionName="",this.nextProductionOccurrence=0,this.isAtEndOfPath=!0):(this.nextProductionName=this.ruleStack.pop(),this.nextProductionOccurrence=this.occurrenceStack.pop())},e}(Xj.RestWalker);Nr.AbstractNextPossibleTokensWalker=Zj;var SIe=function(r){mc(e,r);function e(t,i){var n=r.call(this,t,i)||this;return n.path=i,n.nextTerminalName="",n.nextTerminalOccurrence=0,n.nextTerminalName=n.path.lastTok.name,n.nextTerminalOccurrence=n.path.lastTokOccurrence,n}return e.prototype.walkTerminal=function(t,i,n){if(this.isAtEndOfPath&&t.terminalType.name===this.nextTerminalName&&t.idx===this.nextTerminalOccurrence&&!this.found){var s=i.concat(n),o=new kt.Alternative({definition:s});this.possibleTokTypes=(0,QIe.first)(o),this.found=!0}},e}(Zj);Nr.NextAfterTokenWalker=SIe;var Pd=function(r){mc(e,r);function e(t,i){var n=r.call(this)||this;return n.topRule=t,n.occurrence=i,n.result={token:void 0,occurrence:void 0,isEndOfRule:void 0},n}return e.prototype.startWalking=function(){return this.walk(this.topRule),this.result},e}(Xj.RestWalker);Nr.AbstractNextTerminalAfterProductionWalker=Pd;var vIe=function(r){mc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkMany=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkMany.call(this,t,i,n)},e}(Pd);Nr.NextTerminalAfterManyWalker=vIe;var xIe=function(r){mc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkManySep=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkManySep.call(this,t,i,n)},e}(Pd);Nr.NextTerminalAfterManySepWalker=xIe;var PIe=function(r){mc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkAtLeastOne=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkAtLeastOne.call(this,t,i,n)},e}(Pd);Nr.NextTerminalAfterAtLeastOneWalker=PIe;var DIe=function(r){mc(e,r);function e(){return r!==null&&r.apply(this,arguments)||this}return e.prototype.walkAtLeastOneSep=function(t,i,n){if(t.idx===this.occurrence){var s=(0,Kt.first)(i.concat(n));this.result.isEndOfRule=s===void 0,s instanceof kt.Terminal&&(this.result.token=s.terminalType,this.result.occurrence=s.idx)}else r.prototype.walkAtLeastOneSep.call(this,t,i,n)},e}(Pd);Nr.NextTerminalAfterAtLeastOneSepWalker=DIe;function _j(r,e,t){t===void 0&&(t=[]),t=(0,Kt.cloneArr)(t);var i=[],n=0;function s(c){return c.concat((0,Kt.drop)(r,n+1))}function o(c){var u=_j(s(c),e,t);return i.concat(u)}for(;t.length=0;ge--){var re=B.definition[ge],M={idx:p,def:re.definition.concat((0,Kt.drop)(h)),ruleStack:C,occurrenceStack:y};g.push(M),g.push(o)}else if(B instanceof kt.Alternative)g.push({idx:p,def:B.definition.concat((0,Kt.drop)(h)),ruleStack:C,occurrenceStack:y});else if(B instanceof kt.Rule)g.push(RIe(B,p,C,y));else throw Error("non exhaustive match")}}return u}Nr.nextPossibleTokensAfter=kIe;function RIe(r,e,t,i){var n=(0,Kt.cloneArr)(t);n.push(r.name);var s=(0,Kt.cloneArr)(i);return s.push(1),{idx:e,def:r.definition,ruleStack:n,occurrenceStack:s}}});var kd=w(Zt=>{"use strict";var tq=Zt&&Zt.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Zt,"__esModule",{value:!0});Zt.areTokenCategoriesNotUsed=Zt.isStrictPrefixOfPath=Zt.containsPath=Zt.getLookaheadPathsForOptionalProd=Zt.getLookaheadPathsForOr=Zt.lookAheadSequenceFromAlternatives=Zt.buildSingleAlternativeLookaheadFunction=Zt.buildAlternativesLookAheadFunc=Zt.buildLookaheadFuncForOptionalProd=Zt.buildLookaheadFuncForOr=Zt.getProdType=Zt.PROD_TYPE=void 0;var sr=Gt(),$j=Dd(),FIe=Ay(),hy=_g(),MA=mn(),NIe=$g(),oi;(function(r){r[r.OPTION=0]="OPTION",r[r.REPETITION=1]="REPETITION",r[r.REPETITION_MANDATORY=2]="REPETITION_MANDATORY",r[r.REPETITION_MANDATORY_WITH_SEPARATOR=3]="REPETITION_MANDATORY_WITH_SEPARATOR",r[r.REPETITION_WITH_SEPARATOR=4]="REPETITION_WITH_SEPARATOR",r[r.ALTERNATION=5]="ALTERNATION"})(oi=Zt.PROD_TYPE||(Zt.PROD_TYPE={}));function TIe(r){if(r instanceof MA.Option)return oi.OPTION;if(r instanceof MA.Repetition)return oi.REPETITION;if(r instanceof MA.RepetitionMandatory)return oi.REPETITION_MANDATORY;if(r instanceof MA.RepetitionMandatoryWithSeparator)return oi.REPETITION_MANDATORY_WITH_SEPARATOR;if(r instanceof MA.RepetitionWithSeparator)return oi.REPETITION_WITH_SEPARATOR;if(r instanceof MA.Alternation)return oi.ALTERNATION;throw Error("non exhaustive match")}Zt.getProdType=TIe;function LIe(r,e,t,i,n,s){var o=iq(r,e,t),a=Xv(o)?hy.tokenStructuredMatcherNoCategories:hy.tokenStructuredMatcher;return s(o,i,a,n)}Zt.buildLookaheadFuncForOr=LIe;function MIe(r,e,t,i,n,s){var o=nq(r,e,n,t),a=Xv(o)?hy.tokenStructuredMatcherNoCategories:hy.tokenStructuredMatcher;return s(o[0],a,i)}Zt.buildLookaheadFuncForOptionalProd=MIe;function OIe(r,e,t,i){var n=r.length,s=(0,sr.every)(r,function(l){return(0,sr.every)(l,function(c){return c.length===1})});if(e)return function(l){for(var c=(0,sr.map)(l,function(D){return D.GATE}),u=0;u{"use strict";var Zv=Vt&&Vt.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(Vt,"__esModule",{value:!0});Vt.checkPrefixAlternativesAmbiguities=Vt.validateSomeNonEmptyLookaheadPath=Vt.validateTooManyAlts=Vt.RepetionCollector=Vt.validateAmbiguousAlternationAlternatives=Vt.validateEmptyOrAlternative=Vt.getFirstNoneTerminal=Vt.validateNoLeftRecursion=Vt.validateRuleIsOverridden=Vt.validateRuleDoesNotAlreadyExist=Vt.OccurrenceValidationCollector=Vt.identifyProductionForDuplicates=Vt.validateGrammar=void 0;var er=Gt(),Qr=Gt(),To=jn(),_v=vd(),tf=kd(),YIe=Dd(),to=mn(),$v=$g();function jIe(r,e,t,i,n){var s=er.map(r,function(h){return qIe(h,i)}),o=er.map(r,function(h){return ex(h,h,i)}),a=[],l=[],c=[];(0,Qr.every)(o,Qr.isEmpty)&&(a=(0,Qr.map)(r,function(h){return cq(h,i)}),l=(0,Qr.map)(r,function(h){return uq(h,e,i)}),c=hq(r,e,i));var u=zIe(r,t,i),g=(0,Qr.map)(r,function(h){return fq(h,i)}),f=(0,Qr.map)(r,function(h){return lq(h,r,n,i)});return er.flatten(s.concat(c,o,a,l,u,g,f))}Vt.validateGrammar=jIe;function qIe(r,e){var t=new Aq;r.accept(t);var i=t.allProductions,n=er.groupBy(i,oq),s=er.pick(n,function(a){return a.length>1}),o=er.map(er.values(s),function(a){var l=er.first(a),c=e.buildDuplicateFoundError(r,a),u=(0,_v.getProductionDslName)(l),g={message:c,type:To.ParserDefinitionErrorType.DUPLICATE_PRODUCTIONS,ruleName:r.name,dslName:u,occurrence:l.idx},f=aq(l);return f&&(g.parameter=f),g});return o}function oq(r){return(0,_v.getProductionDslName)(r)+"_#_"+r.idx+"_#_"+aq(r)}Vt.identifyProductionForDuplicates=oq;function aq(r){return r instanceof to.Terminal?r.terminalType.name:r instanceof to.NonTerminal?r.nonTerminalName:""}var Aq=function(r){Zv(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.allProductions=[],t}return e.prototype.visitNonTerminal=function(t){this.allProductions.push(t)},e.prototype.visitOption=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatory=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatoryWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetition=function(t){this.allProductions.push(t)},e.prototype.visitAlternation=function(t){this.allProductions.push(t)},e.prototype.visitTerminal=function(t){this.allProductions.push(t)},e}($v.GAstVisitor);Vt.OccurrenceValidationCollector=Aq;function lq(r,e,t,i){var n=[],s=(0,Qr.reduce)(e,function(a,l){return l.name===r.name?a+1:a},0);if(s>1){var o=i.buildDuplicateRuleNameError({topLevelRule:r,grammarName:t});n.push({message:o,type:To.ParserDefinitionErrorType.DUPLICATE_RULE_NAME,ruleName:r.name})}return n}Vt.validateRuleDoesNotAlreadyExist=lq;function JIe(r,e,t){var i=[],n;return er.contains(e,r)||(n="Invalid rule override, rule: ->"+r+"<- cannot be overridden in the grammar: ->"+t+"<-as it is not defined in any of the super grammars ",i.push({message:n,type:To.ParserDefinitionErrorType.INVALID_RULE_OVERRIDE,ruleName:r})),i}Vt.validateRuleIsOverridden=JIe;function ex(r,e,t,i){i===void 0&&(i=[]);var n=[],s=Rd(e.definition);if(er.isEmpty(s))return[];var o=r.name,a=er.contains(s,r);a&&n.push({message:t.buildLeftRecursionError({topLevelRule:r,leftRecursionPath:i}),type:To.ParserDefinitionErrorType.LEFT_RECURSION,ruleName:o});var l=er.difference(s,i.concat([r])),c=er.map(l,function(u){var g=er.cloneArr(i);return g.push(u),ex(r,u,t,g)});return n.concat(er.flatten(c))}Vt.validateNoLeftRecursion=ex;function Rd(r){var e=[];if(er.isEmpty(r))return e;var t=er.first(r);if(t instanceof to.NonTerminal)e.push(t.referencedRule);else if(t instanceof to.Alternative||t instanceof to.Option||t instanceof to.RepetitionMandatory||t instanceof to.RepetitionMandatoryWithSeparator||t instanceof to.RepetitionWithSeparator||t instanceof to.Repetition)e=e.concat(Rd(t.definition));else if(t instanceof to.Alternation)e=er.flatten(er.map(t.definition,function(o){return Rd(o.definition)}));else if(!(t instanceof to.Terminal))throw Error("non exhaustive match");var i=(0,_v.isOptionalProd)(t),n=r.length>1;if(i&&n){var s=er.drop(r);return e.concat(Rd(s))}else return e}Vt.getFirstNoneTerminal=Rd;var tx=function(r){Zv(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.alternations=[],t}return e.prototype.visitAlternation=function(t){this.alternations.push(t)},e}($v.GAstVisitor);function cq(r,e){var t=new tx;r.accept(t);var i=t.alternations,n=er.reduce(i,function(s,o){var a=er.dropRight(o.definition),l=er.map(a,function(c,u){var g=(0,YIe.nextPossibleTokensAfter)([c],[],null,1);return er.isEmpty(g)?{message:e.buildEmptyAlternationError({topLevelRule:r,alternation:o,emptyChoiceIdx:u}),type:To.ParserDefinitionErrorType.NONE_LAST_EMPTY_ALT,ruleName:r.name,occurrence:o.idx,alternative:u+1}:null});return s.concat(er.compact(l))},[]);return n}Vt.validateEmptyOrAlternative=cq;function uq(r,e,t){var i=new tx;r.accept(i);var n=i.alternations;n=(0,Qr.reject)(n,function(o){return o.ignoreAmbiguities===!0});var s=er.reduce(n,function(o,a){var l=a.idx,c=a.maxLookahead||e,u=(0,tf.getLookaheadPathsForOr)(l,r,c,a),g=WIe(u,a,r,t),f=pq(u,a,r,t);return o.concat(g,f)},[]);return s}Vt.validateAmbiguousAlternationAlternatives=uq;var gq=function(r){Zv(e,r);function e(){var t=r!==null&&r.apply(this,arguments)||this;return t.allProductions=[],t}return e.prototype.visitRepetitionWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatory=function(t){this.allProductions.push(t)},e.prototype.visitRepetitionMandatoryWithSeparator=function(t){this.allProductions.push(t)},e.prototype.visitRepetition=function(t){this.allProductions.push(t)},e}($v.GAstVisitor);Vt.RepetionCollector=gq;function fq(r,e){var t=new tx;r.accept(t);var i=t.alternations,n=er.reduce(i,function(s,o){return o.definition.length>255&&s.push({message:e.buildTooManyAlternativesError({topLevelRule:r,alternation:o}),type:To.ParserDefinitionErrorType.TOO_MANY_ALTS,ruleName:r.name,occurrence:o.idx}),s},[]);return n}Vt.validateTooManyAlts=fq;function hq(r,e,t){var i=[];return(0,Qr.forEach)(r,function(n){var s=new gq;n.accept(s);var o=s.allProductions;(0,Qr.forEach)(o,function(a){var l=(0,tf.getProdType)(a),c=a.maxLookahead||e,u=a.idx,g=(0,tf.getLookaheadPathsForOptionalProd)(u,n,l,c),f=g[0];if((0,Qr.isEmpty)((0,Qr.flatten)(f))){var h=t.buildEmptyRepetitionError({topLevelRule:n,repetition:a});i.push({message:h,type:To.ParserDefinitionErrorType.NO_NON_EMPTY_LOOKAHEAD,ruleName:n.name})}})}),i}Vt.validateSomeNonEmptyLookaheadPath=hq;function WIe(r,e,t,i){var n=[],s=(0,Qr.reduce)(r,function(a,l,c){return e.definition[c].ignoreAmbiguities===!0||(0,Qr.forEach)(l,function(u){var g=[c];(0,Qr.forEach)(r,function(f,h){c!==h&&(0,tf.containsPath)(f,u)&&e.definition[h].ignoreAmbiguities!==!0&&g.push(h)}),g.length>1&&!(0,tf.containsPath)(n,u)&&(n.push(u),a.push({alts:g,path:u}))}),a},[]),o=er.map(s,function(a){var l=(0,Qr.map)(a.alts,function(u){return u+1}),c=i.buildAlternationAmbiguityError({topLevelRule:t,alternation:e,ambiguityIndices:l,prefixPath:a.path});return{message:c,type:To.ParserDefinitionErrorType.AMBIGUOUS_ALTS,ruleName:t.name,occurrence:e.idx,alternatives:[a.alts]}});return o}function pq(r,e,t,i){var n=[],s=(0,Qr.reduce)(r,function(o,a,l){var c=(0,Qr.map)(a,function(u){return{idx:l,path:u}});return o.concat(c)},[]);return(0,Qr.forEach)(s,function(o){var a=e.definition[o.idx];if(a.ignoreAmbiguities!==!0){var l=o.idx,c=o.path,u=(0,Qr.findAll)(s,function(f){return e.definition[f.idx].ignoreAmbiguities!==!0&&f.idx{"use strict";Object.defineProperty(rf,"__esModule",{value:!0});rf.validateGrammar=rf.resolveGrammar=void 0;var ix=Gt(),VIe=Vj(),XIe=rx(),dq=xd();function ZIe(r){r=(0,ix.defaults)(r,{errMsgProvider:dq.defaultGrammarResolverErrorProvider});var e={};return(0,ix.forEach)(r.rules,function(t){e[t.name]=t}),(0,VIe.resolveGrammar)(e,r.errMsgProvider)}rf.resolveGrammar=ZIe;function _Ie(r){return r=(0,ix.defaults)(r,{errMsgProvider:dq.defaultGrammarValidatorErrorProvider}),(0,XIe.validateGrammar)(r.rules,r.maxLookahead,r.tokenTypes,r.errMsgProvider,r.grammarName)}rf.validateGrammar=_Ie});var nf=w(In=>{"use strict";var Fd=In&&In.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(In,"__esModule",{value:!0});In.EarlyExitException=In.NotAllInputParsedException=In.NoViableAltException=In.MismatchedTokenException=In.isRecognitionException=void 0;var $Ie=Gt(),mq="MismatchedTokenException",Eq="NoViableAltException",Iq="EarlyExitException",yq="NotAllInputParsedException",wq=[mq,Eq,Iq,yq];Object.freeze(wq);function eye(r){return(0,$Ie.contains)(wq,r.name)}In.isRecognitionException=eye;var py=function(r){Fd(e,r);function e(t,i){var n=this.constructor,s=r.call(this,t)||this;return s.token=i,s.resyncedTokens=[],Object.setPrototypeOf(s,n.prototype),Error.captureStackTrace&&Error.captureStackTrace(s,s.constructor),s}return e}(Error),tye=function(r){Fd(e,r);function e(t,i,n){var s=r.call(this,t,i)||this;return s.previousToken=n,s.name=mq,s}return e}(py);In.MismatchedTokenException=tye;var rye=function(r){Fd(e,r);function e(t,i,n){var s=r.call(this,t,i)||this;return s.previousToken=n,s.name=Eq,s}return e}(py);In.NoViableAltException=rye;var iye=function(r){Fd(e,r);function e(t,i){var n=r.call(this,t,i)||this;return n.name=yq,n}return e}(py);In.NotAllInputParsedException=iye;var nye=function(r){Fd(e,r);function e(t,i,n){var s=r.call(this,t,i)||this;return s.previousToken=n,s.name=Iq,s}return e}(py);In.EarlyExitException=nye});var sx=w(Ki=>{"use strict";Object.defineProperty(Ki,"__esModule",{value:!0});Ki.attemptInRepetitionRecovery=Ki.Recoverable=Ki.InRuleRecoveryException=Ki.IN_RULE_RECOVERY_EXCEPTION=Ki.EOF_FOLLOW_KEY=void 0;var dy=TA(),hs=Gt(),sye=nf(),oye=Jv(),aye=jn();Ki.EOF_FOLLOW_KEY={};Ki.IN_RULE_RECOVERY_EXCEPTION="InRuleRecoveryException";function nx(r){this.name=Ki.IN_RULE_RECOVERY_EXCEPTION,this.message=r}Ki.InRuleRecoveryException=nx;nx.prototype=Error.prototype;var Aye=function(){function r(){}return r.prototype.initRecoverable=function(e){this.firstAfterRepMap={},this.resyncFollows={},this.recoveryEnabled=(0,hs.has)(e,"recoveryEnabled")?e.recoveryEnabled:aye.DEFAULT_PARSER_CONFIG.recoveryEnabled,this.recoveryEnabled&&(this.attemptInRepetitionRecovery=Bq)},r.prototype.getTokenToInsert=function(e){var t=(0,dy.createTokenInstance)(e,"",NaN,NaN,NaN,NaN,NaN,NaN);return t.isInsertedInRecovery=!0,t},r.prototype.canTokenTypeBeInsertedInRecovery=function(e){return!0},r.prototype.tryInRepetitionRecovery=function(e,t,i,n){for(var s=this,o=this.findReSyncTokenType(),a=this.exportLexerState(),l=[],c=!1,u=this.LA(1),g=this.LA(1),f=function(){var h=s.LA(0),p=s.errorMessageProvider.buildMismatchTokenMessage({expected:n,actual:u,previous:h,ruleName:s.getCurrRuleFullName()}),C=new sye.MismatchedTokenException(p,u,s.LA(0));C.resyncedTokens=(0,hs.dropRight)(l),s.SAVE_ERROR(C)};!c;)if(this.tokenMatcher(g,n)){f();return}else if(i.call(this)){f(),e.apply(this,t);return}else this.tokenMatcher(g,o)?c=!0:(g=this.SKIP_TOKEN(),this.addToResyncTokens(g,l));this.importLexerState(a)},r.prototype.shouldInRepetitionRecoveryBeTried=function(e,t,i){return!(i===!1||e===void 0||t===void 0||this.tokenMatcher(this.LA(1),e)||this.isBackTracking()||this.canPerformInRuleRecovery(e,this.getFollowsForInRuleRecovery(e,t)))},r.prototype.getFollowsForInRuleRecovery=function(e,t){var i=this.getCurrentGrammarPath(e,t),n=this.getNextPossibleTokenTypes(i);return n},r.prototype.tryInRuleRecovery=function(e,t){if(this.canRecoverWithSingleTokenInsertion(e,t)){var i=this.getTokenToInsert(e);return i}if(this.canRecoverWithSingleTokenDeletion(e)){var n=this.SKIP_TOKEN();return this.consumeToken(),n}throw new nx("sad sad panda")},r.prototype.canPerformInRuleRecovery=function(e,t){return this.canRecoverWithSingleTokenInsertion(e,t)||this.canRecoverWithSingleTokenDeletion(e)},r.prototype.canRecoverWithSingleTokenInsertion=function(e,t){var i=this;if(!this.canTokenTypeBeInsertedInRecovery(e)||(0,hs.isEmpty)(t))return!1;var n=this.LA(1),s=(0,hs.find)(t,function(o){return i.tokenMatcher(n,o)})!==void 0;return s},r.prototype.canRecoverWithSingleTokenDeletion=function(e){var t=this.tokenMatcher(this.LA(2),e);return t},r.prototype.isInCurrentRuleReSyncSet=function(e){var t=this.getCurrFollowKey(),i=this.getFollowSetFromFollowKey(t);return(0,hs.contains)(i,e)},r.prototype.findReSyncTokenType=function(){for(var e=this.flattenFollowSet(),t=this.LA(1),i=2;;){var n=t.tokenType;if((0,hs.contains)(e,n))return n;t=this.LA(i),i++}},r.prototype.getCurrFollowKey=function(){if(this.RULE_STACK.length===1)return Ki.EOF_FOLLOW_KEY;var e=this.getLastExplicitRuleShortName(),t=this.getLastExplicitRuleOccurrenceIndex(),i=this.getPreviousExplicitRuleShortName();return{ruleName:this.shortRuleNameToFullName(e),idxInCallingRule:t,inRule:this.shortRuleNameToFullName(i)}},r.prototype.buildFullFollowKeyStack=function(){var e=this,t=this.RULE_STACK,i=this.RULE_OCCURRENCE_STACK;return(0,hs.map)(t,function(n,s){return s===0?Ki.EOF_FOLLOW_KEY:{ruleName:e.shortRuleNameToFullName(n),idxInCallingRule:i[s],inRule:e.shortRuleNameToFullName(t[s-1])}})},r.prototype.flattenFollowSet=function(){var e=this,t=(0,hs.map)(this.buildFullFollowKeyStack(),function(i){return e.getFollowSetFromFollowKey(i)});return(0,hs.flatten)(t)},r.prototype.getFollowSetFromFollowKey=function(e){if(e===Ki.EOF_FOLLOW_KEY)return[dy.EOF];var t=e.ruleName+e.idxInCallingRule+oye.IN+e.inRule;return this.resyncFollows[t]},r.prototype.addToResyncTokens=function(e,t){return this.tokenMatcher(e,dy.EOF)||t.push(e),t},r.prototype.reSyncTo=function(e){for(var t=[],i=this.LA(1);this.tokenMatcher(i,e)===!1;)i=this.SKIP_TOKEN(),this.addToResyncTokens(i,t);return(0,hs.dropRight)(t)},r.prototype.attemptInRepetitionRecovery=function(e,t,i,n,s,o,a){},r.prototype.getCurrentGrammarPath=function(e,t){var i=this.getHumanReadableRuleStack(),n=(0,hs.cloneArr)(this.RULE_OCCURRENCE_STACK),s={ruleStack:i,occurrenceStack:n,lastTok:e,lastTokOccurrence:t};return s},r.prototype.getHumanReadableRuleStack=function(){var e=this;return(0,hs.map)(this.RULE_STACK,function(t){return e.shortRuleNameToFullName(t)})},r}();Ki.Recoverable=Aye;function Bq(r,e,t,i,n,s,o){var a=this.getKeyForAutomaticLookahead(i,n),l=this.firstAfterRepMap[a];if(l===void 0){var c=this.getCurrRuleFullName(),u=this.getGAstProductions()[c],g=new s(u,n);l=g.startWalking(),this.firstAfterRepMap[a]=l}var f=l.token,h=l.occurrence,p=l.isEndOfRule;this.RULE_STACK.length===1&&p&&f===void 0&&(f=dy.EOF,h=1),this.shouldInRepetitionRecoveryBeTried(f,h,o)&&this.tryInRepetitionRecovery(r,e,t,f)}Ki.attemptInRepetitionRecovery=Bq});var Cy=w(Jt=>{"use strict";Object.defineProperty(Jt,"__esModule",{value:!0});Jt.getKeyForAutomaticLookahead=Jt.AT_LEAST_ONE_SEP_IDX=Jt.MANY_SEP_IDX=Jt.AT_LEAST_ONE_IDX=Jt.MANY_IDX=Jt.OPTION_IDX=Jt.OR_IDX=Jt.BITS_FOR_ALT_IDX=Jt.BITS_FOR_RULE_IDX=Jt.BITS_FOR_OCCURRENCE_IDX=Jt.BITS_FOR_METHOD_TYPE=void 0;Jt.BITS_FOR_METHOD_TYPE=4;Jt.BITS_FOR_OCCURRENCE_IDX=8;Jt.BITS_FOR_RULE_IDX=12;Jt.BITS_FOR_ALT_IDX=8;Jt.OR_IDX=1<{"use strict";Object.defineProperty(my,"__esModule",{value:!0});my.LooksAhead=void 0;var ka=kd(),ro=Gt(),bq=jn(),Ra=Cy(),Ec=vd(),cye=function(){function r(){}return r.prototype.initLooksAhead=function(e){this.dynamicTokensEnabled=(0,ro.has)(e,"dynamicTokensEnabled")?e.dynamicTokensEnabled:bq.DEFAULT_PARSER_CONFIG.dynamicTokensEnabled,this.maxLookahead=(0,ro.has)(e,"maxLookahead")?e.maxLookahead:bq.DEFAULT_PARSER_CONFIG.maxLookahead,this.lookAheadFuncsCache=(0,ro.isES2015MapSupported)()?new Map:[],(0,ro.isES2015MapSupported)()?(this.getLaFuncFromCache=this.getLaFuncFromMap,this.setLaFuncCache=this.setLaFuncCacheUsingMap):(this.getLaFuncFromCache=this.getLaFuncFromObj,this.setLaFuncCache=this.setLaFuncUsingObj)},r.prototype.preComputeLookaheadFunctions=function(e){var t=this;(0,ro.forEach)(e,function(i){t.TRACE_INIT(i.name+" Rule Lookahead",function(){var n=(0,Ec.collectMethods)(i),s=n.alternation,o=n.repetition,a=n.option,l=n.repetitionMandatory,c=n.repetitionMandatoryWithSeparator,u=n.repetitionWithSeparator;(0,ro.forEach)(s,function(g){var f=g.idx===0?"":g.idx;t.TRACE_INIT(""+(0,Ec.getProductionDslName)(g)+f,function(){var h=(0,ka.buildLookaheadFuncForOr)(g.idx,i,g.maxLookahead||t.maxLookahead,g.hasPredicates,t.dynamicTokensEnabled,t.lookAheadBuilderForAlternatives),p=(0,Ra.getKeyForAutomaticLookahead)(t.fullRuleNameToShort[i.name],Ra.OR_IDX,g.idx);t.setLaFuncCache(p,h)})}),(0,ro.forEach)(o,function(g){t.computeLookaheadFunc(i,g.idx,Ra.MANY_IDX,ka.PROD_TYPE.REPETITION,g.maxLookahead,(0,Ec.getProductionDslName)(g))}),(0,ro.forEach)(a,function(g){t.computeLookaheadFunc(i,g.idx,Ra.OPTION_IDX,ka.PROD_TYPE.OPTION,g.maxLookahead,(0,Ec.getProductionDslName)(g))}),(0,ro.forEach)(l,function(g){t.computeLookaheadFunc(i,g.idx,Ra.AT_LEAST_ONE_IDX,ka.PROD_TYPE.REPETITION_MANDATORY,g.maxLookahead,(0,Ec.getProductionDslName)(g))}),(0,ro.forEach)(c,function(g){t.computeLookaheadFunc(i,g.idx,Ra.AT_LEAST_ONE_SEP_IDX,ka.PROD_TYPE.REPETITION_MANDATORY_WITH_SEPARATOR,g.maxLookahead,(0,Ec.getProductionDslName)(g))}),(0,ro.forEach)(u,function(g){t.computeLookaheadFunc(i,g.idx,Ra.MANY_SEP_IDX,ka.PROD_TYPE.REPETITION_WITH_SEPARATOR,g.maxLookahead,(0,Ec.getProductionDslName)(g))})})})},r.prototype.computeLookaheadFunc=function(e,t,i,n,s,o){var a=this;this.TRACE_INIT(""+o+(t===0?"":t),function(){var l=(0,ka.buildLookaheadFuncForOptionalProd)(t,e,s||a.maxLookahead,a.dynamicTokensEnabled,n,a.lookAheadBuilderForOptional),c=(0,Ra.getKeyForAutomaticLookahead)(a.fullRuleNameToShort[e.name],i,t);a.setLaFuncCache(c,l)})},r.prototype.lookAheadBuilderForOptional=function(e,t,i){return(0,ka.buildSingleAlternativeLookaheadFunction)(e,t,i)},r.prototype.lookAheadBuilderForAlternatives=function(e,t,i,n){return(0,ka.buildAlternativesLookAheadFunc)(e,t,i,n)},r.prototype.getKeyForAutomaticLookahead=function(e,t){var i=this.getLastExplicitRuleShortName();return(0,Ra.getKeyForAutomaticLookahead)(i,e,t)},r.prototype.getLaFuncFromCache=function(e){},r.prototype.getLaFuncFromMap=function(e){return this.lookAheadFuncsCache.get(e)},r.prototype.getLaFuncFromObj=function(e){return this.lookAheadFuncsCache[e]},r.prototype.setLaFuncCache=function(e,t){},r.prototype.setLaFuncCacheUsingMap=function(e,t){this.lookAheadFuncsCache.set(e,t)},r.prototype.setLaFuncUsingObj=function(e,t){this.lookAheadFuncsCache[e]=t},r}();my.LooksAhead=cye});var Sq=w(Lo=>{"use strict";Object.defineProperty(Lo,"__esModule",{value:!0});Lo.addNoneTerminalToCst=Lo.addTerminalToCst=Lo.setNodeLocationFull=Lo.setNodeLocationOnlyOffset=void 0;function uye(r,e){isNaN(r.startOffset)===!0?(r.startOffset=e.startOffset,r.endOffset=e.endOffset):r.endOffset{"use strict";Object.defineProperty(OA,"__esModule",{value:!0});OA.defineNameProp=OA.functionName=OA.classNameFromInstance=void 0;var pye=Gt();function dye(r){return xq(r.constructor)}OA.classNameFromInstance=dye;var vq="name";function xq(r){var e=r.name;return e||"anonymous"}OA.functionName=xq;function Cye(r,e){var t=Object.getOwnPropertyDescriptor(r,vq);return(0,pye.isUndefined)(t)||t.configurable?(Object.defineProperty(r,vq,{enumerable:!1,configurable:!0,writable:!1,value:e}),!0):!1}OA.defineNameProp=Cye});var Fq=w(Si=>{"use strict";Object.defineProperty(Si,"__esModule",{value:!0});Si.validateRedundantMethods=Si.validateMissingCstMethods=Si.validateVisitor=Si.CstVisitorDefinitionError=Si.createBaseVisitorConstructorWithDefaults=Si.createBaseSemanticVisitorConstructor=Si.defaultVisit=void 0;var ps=Gt(),Nd=ox();function Pq(r,e){for(var t=(0,ps.keys)(r),i=t.length,n=0;n: `+(""+s.join(` `).replace(/\n/g,` `)))}}};return t.prototype=i,t.prototype.constructor=t,t._RULE_NAMES=e,t}Si.createBaseSemanticVisitorConstructor=mye;function Eye(r,e,t){var i=function(){};(0,Nd.defineNameProp)(i,r+"BaseSemanticsWithDefaults");var n=Object.create(t.prototype);return(0,ps.forEach)(e,function(s){n[s]=Pq}),i.prototype=n,i.prototype.constructor=i,i}Si.createBaseVisitorConstructorWithDefaults=Eye;var ax;(function(r){r[r.REDUNDANT_METHOD=0]="REDUNDANT_METHOD",r[r.MISSING_METHOD=1]="MISSING_METHOD"})(ax=Si.CstVisitorDefinitionError||(Si.CstVisitorDefinitionError={}));function Dq(r,e){var t=kq(r,e),i=Rq(r,e);return t.concat(i)}Si.validateVisitor=Dq;function kq(r,e){var t=(0,ps.map)(e,function(i){if(!(0,ps.isFunction)(r[i]))return{msg:"Missing visitor method: <"+i+"> on "+(0,Nd.functionName)(r.constructor)+" CST Visitor.",type:ax.MISSING_METHOD,methodName:i}});return(0,ps.compact)(t)}Si.validateMissingCstMethods=kq;var Iye=["constructor","visit","validateVisitor"];function Rq(r,e){var t=[];for(var i in r)(0,ps.isFunction)(r[i])&&!(0,ps.contains)(Iye,i)&&!(0,ps.contains)(e,i)&&t.push({msg:"Redundant visitor method: <"+i+"> on "+(0,Nd.functionName)(r.constructor)+` CST Visitor There is no Grammar Rule corresponding to this method's name. `,type:ax.REDUNDANT_METHOD,methodName:i});return t}Si.validateRedundantMethods=Rq});var Tq=w(Ey=>{"use strict";Object.defineProperty(Ey,"__esModule",{value:!0});Ey.TreeBuilder=void 0;var sf=Sq(),_r=Gt(),Nq=Fq(),yye=jn(),wye=function(){function r(){}return r.prototype.initTreeBuilder=function(e){if(this.CST_STACK=[],this.outputCst=e.outputCst,this.nodeLocationTracking=(0,_r.has)(e,"nodeLocationTracking")?e.nodeLocationTracking:yye.DEFAULT_PARSER_CONFIG.nodeLocationTracking,!this.outputCst)this.cstInvocationStateUpdate=_r.NOOP,this.cstFinallyStateUpdate=_r.NOOP,this.cstPostTerminal=_r.NOOP,this.cstPostNonTerminal=_r.NOOP,this.cstPostRule=_r.NOOP;else if(/full/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=sf.setNodeLocationFull,this.setNodeLocationFromNode=sf.setNodeLocationFull,this.cstPostRule=_r.NOOP,this.setInitialNodeLocation=this.setInitialNodeLocationFullRecovery):(this.setNodeLocationFromToken=_r.NOOP,this.setNodeLocationFromNode=_r.NOOP,this.cstPostRule=this.cstPostRuleFull,this.setInitialNodeLocation=this.setInitialNodeLocationFullRegular);else if(/onlyOffset/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=sf.setNodeLocationOnlyOffset,this.setNodeLocationFromNode=sf.setNodeLocationOnlyOffset,this.cstPostRule=_r.NOOP,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRecovery):(this.setNodeLocationFromToken=_r.NOOP,this.setNodeLocationFromNode=_r.NOOP,this.cstPostRule=this.cstPostRuleOnlyOffset,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRegular);else if(/none/i.test(this.nodeLocationTracking))this.setNodeLocationFromToken=_r.NOOP,this.setNodeLocationFromNode=_r.NOOP,this.cstPostRule=_r.NOOP,this.setInitialNodeLocation=_r.NOOP;else throw Error('Invalid config option: "'+e.nodeLocationTracking+'"')},r.prototype.setInitialNodeLocationOnlyOffsetRecovery=function(e){e.location={startOffset:NaN,endOffset:NaN}},r.prototype.setInitialNodeLocationOnlyOffsetRegular=function(e){e.location={startOffset:this.LA(1).startOffset,endOffset:NaN}},r.prototype.setInitialNodeLocationFullRecovery=function(e){e.location={startOffset:NaN,startLine:NaN,startColumn:NaN,endOffset:NaN,endLine:NaN,endColumn:NaN}},r.prototype.setInitialNodeLocationFullRegular=function(e){var t=this.LA(1);e.location={startOffset:t.startOffset,startLine:t.startLine,startColumn:t.startColumn,endOffset:NaN,endLine:NaN,endColumn:NaN}},r.prototype.cstInvocationStateUpdate=function(e,t){var i={name:e,children:{}};this.setInitialNodeLocation(i),this.CST_STACK.push(i)},r.prototype.cstFinallyStateUpdate=function(){this.CST_STACK.pop()},r.prototype.cstPostRuleFull=function(e){var t=this.LA(0),i=e.location;i.startOffset<=t.startOffset?(i.endOffset=t.endOffset,i.endLine=t.endLine,i.endColumn=t.endColumn):(i.startOffset=NaN,i.startLine=NaN,i.startColumn=NaN)},r.prototype.cstPostRuleOnlyOffset=function(e){var t=this.LA(0),i=e.location;i.startOffset<=t.startOffset?i.endOffset=t.endOffset:i.startOffset=NaN},r.prototype.cstPostTerminal=function(e,t){var i=this.CST_STACK[this.CST_STACK.length-1];(0,sf.addTerminalToCst)(i,t,e),this.setNodeLocationFromToken(i.location,t)},r.prototype.cstPostNonTerminal=function(e,t){var i=this.CST_STACK[this.CST_STACK.length-1];(0,sf.addNoneTerminalToCst)(i,t,e),this.setNodeLocationFromNode(i.location,e.location)},r.prototype.getBaseCstVisitorConstructor=function(){if((0,_r.isUndefined)(this.baseCstVisitorConstructor)){var e=(0,Nq.createBaseSemanticVisitorConstructor)(this.className,(0,_r.keys)(this.gastProductionsCache));return this.baseCstVisitorConstructor=e,e}return this.baseCstVisitorConstructor},r.prototype.getBaseCstVisitorConstructorWithDefaults=function(){if((0,_r.isUndefined)(this.baseCstVisitorWithDefaultsConstructor)){var e=(0,Nq.createBaseVisitorConstructorWithDefaults)(this.className,(0,_r.keys)(this.gastProductionsCache),this.getBaseCstVisitorConstructor());return this.baseCstVisitorWithDefaultsConstructor=e,e}return this.baseCstVisitorWithDefaultsConstructor},r.prototype.getLastExplicitRuleShortName=function(){var e=this.RULE_STACK;return e[e.length-1]},r.prototype.getPreviousExplicitRuleShortName=function(){var e=this.RULE_STACK;return e[e.length-2]},r.prototype.getLastExplicitRuleOccurrenceIndex=function(){var e=this.RULE_OCCURRENCE_STACK;return e[e.length-1]},r}();Ey.TreeBuilder=wye});var Mq=w(Iy=>{"use strict";Object.defineProperty(Iy,"__esModule",{value:!0});Iy.LexerAdapter=void 0;var Lq=jn(),Bye=function(){function r(){}return r.prototype.initLexerAdapter=function(){this.tokVector=[],this.tokVectorLength=0,this.currIdx=-1},Object.defineProperty(r.prototype,"input",{get:function(){return this.tokVector},set:function(e){if(this.selfAnalysisDone!==!0)throw Error("Missing invocation at the end of the Parser's constructor.");this.reset(),this.tokVector=e,this.tokVectorLength=e.length},enumerable:!1,configurable:!0}),r.prototype.SKIP_TOKEN=function(){return this.currIdx<=this.tokVector.length-2?(this.consumeToken(),this.LA(1)):Lq.END_OF_FILE},r.prototype.LA=function(e){var t=this.currIdx+e;return t<0||this.tokVectorLength<=t?Lq.END_OF_FILE:this.tokVector[t]},r.prototype.consumeToken=function(){this.currIdx++},r.prototype.exportLexerState=function(){return this.currIdx},r.prototype.importLexerState=function(e){this.currIdx=e},r.prototype.resetLexerState=function(){this.currIdx=-1},r.prototype.moveToTerminatedState=function(){this.currIdx=this.tokVector.length-1},r.prototype.getLexerPosition=function(){return this.exportLexerState()},r}();Iy.LexerAdapter=Bye});var Kq=w(yy=>{"use strict";Object.defineProperty(yy,"__esModule",{value:!0});yy.RecognizerApi=void 0;var Oq=Gt(),bye=nf(),Ax=jn(),Qye=xd(),Sye=rx(),vye=mn(),xye=function(){function r(){}return r.prototype.ACTION=function(e){return e.call(this)},r.prototype.consume=function(e,t,i){return this.consumeInternal(t,e,i)},r.prototype.subrule=function(e,t,i){return this.subruleInternal(t,e,i)},r.prototype.option=function(e,t){return this.optionInternal(t,e)},r.prototype.or=function(e,t){return this.orInternal(t,e)},r.prototype.many=function(e,t){return this.manyInternal(e,t)},r.prototype.atLeastOne=function(e,t){return this.atLeastOneInternal(e,t)},r.prototype.CONSUME=function(e,t){return this.consumeInternal(e,0,t)},r.prototype.CONSUME1=function(e,t){return this.consumeInternal(e,1,t)},r.prototype.CONSUME2=function(e,t){return this.consumeInternal(e,2,t)},r.prototype.CONSUME3=function(e,t){return this.consumeInternal(e,3,t)},r.prototype.CONSUME4=function(e,t){return this.consumeInternal(e,4,t)},r.prototype.CONSUME5=function(e,t){return this.consumeInternal(e,5,t)},r.prototype.CONSUME6=function(e,t){return this.consumeInternal(e,6,t)},r.prototype.CONSUME7=function(e,t){return this.consumeInternal(e,7,t)},r.prototype.CONSUME8=function(e,t){return this.consumeInternal(e,8,t)},r.prototype.CONSUME9=function(e,t){return this.consumeInternal(e,9,t)},r.prototype.SUBRULE=function(e,t){return this.subruleInternal(e,0,t)},r.prototype.SUBRULE1=function(e,t){return this.subruleInternal(e,1,t)},r.prototype.SUBRULE2=function(e,t){return this.subruleInternal(e,2,t)},r.prototype.SUBRULE3=function(e,t){return this.subruleInternal(e,3,t)},r.prototype.SUBRULE4=function(e,t){return this.subruleInternal(e,4,t)},r.prototype.SUBRULE5=function(e,t){return this.subruleInternal(e,5,t)},r.prototype.SUBRULE6=function(e,t){return this.subruleInternal(e,6,t)},r.prototype.SUBRULE7=function(e,t){return this.subruleInternal(e,7,t)},r.prototype.SUBRULE8=function(e,t){return this.subruleInternal(e,8,t)},r.prototype.SUBRULE9=function(e,t){return this.subruleInternal(e,9,t)},r.prototype.OPTION=function(e){return this.optionInternal(e,0)},r.prototype.OPTION1=function(e){return this.optionInternal(e,1)},r.prototype.OPTION2=function(e){return this.optionInternal(e,2)},r.prototype.OPTION3=function(e){return this.optionInternal(e,3)},r.prototype.OPTION4=function(e){return this.optionInternal(e,4)},r.prototype.OPTION5=function(e){return this.optionInternal(e,5)},r.prototype.OPTION6=function(e){return this.optionInternal(e,6)},r.prototype.OPTION7=function(e){return this.optionInternal(e,7)},r.prototype.OPTION8=function(e){return this.optionInternal(e,8)},r.prototype.OPTION9=function(e){return this.optionInternal(e,9)},r.prototype.OR=function(e){return this.orInternal(e,0)},r.prototype.OR1=function(e){return this.orInternal(e,1)},r.prototype.OR2=function(e){return this.orInternal(e,2)},r.prototype.OR3=function(e){return this.orInternal(e,3)},r.prototype.OR4=function(e){return this.orInternal(e,4)},r.prototype.OR5=function(e){return this.orInternal(e,5)},r.prototype.OR6=function(e){return this.orInternal(e,6)},r.prototype.OR7=function(e){return this.orInternal(e,7)},r.prototype.OR8=function(e){return this.orInternal(e,8)},r.prototype.OR9=function(e){return this.orInternal(e,9)},r.prototype.MANY=function(e){this.manyInternal(0,e)},r.prototype.MANY1=function(e){this.manyInternal(1,e)},r.prototype.MANY2=function(e){this.manyInternal(2,e)},r.prototype.MANY3=function(e){this.manyInternal(3,e)},r.prototype.MANY4=function(e){this.manyInternal(4,e)},r.prototype.MANY5=function(e){this.manyInternal(5,e)},r.prototype.MANY6=function(e){this.manyInternal(6,e)},r.prototype.MANY7=function(e){this.manyInternal(7,e)},r.prototype.MANY8=function(e){this.manyInternal(8,e)},r.prototype.MANY9=function(e){this.manyInternal(9,e)},r.prototype.MANY_SEP=function(e){this.manySepFirstInternal(0,e)},r.prototype.MANY_SEP1=function(e){this.manySepFirstInternal(1,e)},r.prototype.MANY_SEP2=function(e){this.manySepFirstInternal(2,e)},r.prototype.MANY_SEP3=function(e){this.manySepFirstInternal(3,e)},r.prototype.MANY_SEP4=function(e){this.manySepFirstInternal(4,e)},r.prototype.MANY_SEP5=function(e){this.manySepFirstInternal(5,e)},r.prototype.MANY_SEP6=function(e){this.manySepFirstInternal(6,e)},r.prototype.MANY_SEP7=function(e){this.manySepFirstInternal(7,e)},r.prototype.MANY_SEP8=function(e){this.manySepFirstInternal(8,e)},r.prototype.MANY_SEP9=function(e){this.manySepFirstInternal(9,e)},r.prototype.AT_LEAST_ONE=function(e){this.atLeastOneInternal(0,e)},r.prototype.AT_LEAST_ONE1=function(e){return this.atLeastOneInternal(1,e)},r.prototype.AT_LEAST_ONE2=function(e){this.atLeastOneInternal(2,e)},r.prototype.AT_LEAST_ONE3=function(e){this.atLeastOneInternal(3,e)},r.prototype.AT_LEAST_ONE4=function(e){this.atLeastOneInternal(4,e)},r.prototype.AT_LEAST_ONE5=function(e){this.atLeastOneInternal(5,e)},r.prototype.AT_LEAST_ONE6=function(e){this.atLeastOneInternal(6,e)},r.prototype.AT_LEAST_ONE7=function(e){this.atLeastOneInternal(7,e)},r.prototype.AT_LEAST_ONE8=function(e){this.atLeastOneInternal(8,e)},r.prototype.AT_LEAST_ONE9=function(e){this.atLeastOneInternal(9,e)},r.prototype.AT_LEAST_ONE_SEP=function(e){this.atLeastOneSepFirstInternal(0,e)},r.prototype.AT_LEAST_ONE_SEP1=function(e){this.atLeastOneSepFirstInternal(1,e)},r.prototype.AT_LEAST_ONE_SEP2=function(e){this.atLeastOneSepFirstInternal(2,e)},r.prototype.AT_LEAST_ONE_SEP3=function(e){this.atLeastOneSepFirstInternal(3,e)},r.prototype.AT_LEAST_ONE_SEP4=function(e){this.atLeastOneSepFirstInternal(4,e)},r.prototype.AT_LEAST_ONE_SEP5=function(e){this.atLeastOneSepFirstInternal(5,e)},r.prototype.AT_LEAST_ONE_SEP6=function(e){this.atLeastOneSepFirstInternal(6,e)},r.prototype.AT_LEAST_ONE_SEP7=function(e){this.atLeastOneSepFirstInternal(7,e)},r.prototype.AT_LEAST_ONE_SEP8=function(e){this.atLeastOneSepFirstInternal(8,e)},r.prototype.AT_LEAST_ONE_SEP9=function(e){this.atLeastOneSepFirstInternal(9,e)},r.prototype.RULE=function(e,t,i){if(i===void 0&&(i=Ax.DEFAULT_RULE_CONFIG),(0,Oq.contains)(this.definedRulesNames,e)){var n=Qye.defaultGrammarValidatorErrorProvider.buildDuplicateRuleNameError({topLevelRule:e,grammarName:this.className}),s={message:n,type:Ax.ParserDefinitionErrorType.DUPLICATE_RULE_NAME,ruleName:e};this.definitionErrors.push(s)}this.definedRulesNames.push(e);var o=this.defineRule(e,t,i);return this[e]=o,o},r.prototype.OVERRIDE_RULE=function(e,t,i){i===void 0&&(i=Ax.DEFAULT_RULE_CONFIG);var n=[];n=n.concat((0,Sye.validateRuleIsOverridden)(e,this.definedRulesNames,this.className)),this.definitionErrors=this.definitionErrors.concat(n);var s=this.defineRule(e,t,i);return this[e]=s,s},r.prototype.BACKTRACK=function(e,t){return function(){this.isBackTrackingStack.push(1);var i=this.saveRecogState();try{return e.apply(this,t),!0}catch(n){if((0,bye.isRecognitionException)(n))return!1;throw n}finally{this.reloadRecogState(i),this.isBackTrackingStack.pop()}}},r.prototype.getGAstProductions=function(){return this.gastProductionsCache},r.prototype.getSerializedGastProductions=function(){return(0,vye.serializeGrammar)((0,Oq.values)(this.gastProductionsCache))},r}();yy.RecognizerApi=xye});var Yq=w(By=>{"use strict";Object.defineProperty(By,"__esModule",{value:!0});By.RecognizerEngine=void 0;var Pr=Gt(),qn=Cy(),wy=nf(),Uq=kd(),of=Dd(),Hq=jn(),Pye=sx(),Gq=TA(),Td=_g(),Dye=ox(),kye=function(){function r(){}return r.prototype.initRecognizerEngine=function(e,t){if(this.className=(0,Dye.classNameFromInstance)(this),this.shortRuleNameToFull={},this.fullRuleNameToShort={},this.ruleShortNameIdx=256,this.tokenMatcher=Td.tokenStructuredMatcherNoCategories,this.definedRulesNames=[],this.tokensMap={},this.isBackTrackingStack=[],this.RULE_STACK=[],this.RULE_OCCURRENCE_STACK=[],this.gastProductionsCache={},(0,Pr.has)(t,"serializedGrammar"))throw Error(`The Parser's configuration can no longer contain a property. See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_6-0-0 For Further details.`);if((0,Pr.isArray)(e)){if((0,Pr.isEmpty)(e))throw Error(`A Token Vocabulary cannot be empty. Note that the first argument for the parser constructor is no longer a Token vector (since v4.0).`);if(typeof e[0].startOffset=="number")throw Error(`The Parser constructor no longer accepts a token vector as the first argument. See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_4-0-0 For Further details.`)}if((0,Pr.isArray)(e))this.tokensMap=(0,Pr.reduce)(e,function(o,a){return o[a.name]=a,o},{});else if((0,Pr.has)(e,"modes")&&(0,Pr.every)((0,Pr.flatten)((0,Pr.values)(e.modes)),Td.isTokenType)){var i=(0,Pr.flatten)((0,Pr.values)(e.modes)),n=(0,Pr.uniq)(i);this.tokensMap=(0,Pr.reduce)(n,function(o,a){return o[a.name]=a,o},{})}else if((0,Pr.isObject)(e))this.tokensMap=(0,Pr.cloneObj)(e);else throw new Error(" argument must be An Array of Token constructors, A dictionary of Token constructors or an IMultiModeLexerDefinition");this.tokensMap.EOF=Gq.EOF;var s=(0,Pr.every)((0,Pr.values)(e),function(o){return(0,Pr.isEmpty)(o.categoryMatches)});this.tokenMatcher=s?Td.tokenStructuredMatcherNoCategories:Td.tokenStructuredMatcher,(0,Td.augmentTokenTypes)((0,Pr.values)(this.tokensMap))},r.prototype.defineRule=function(e,t,i){if(this.selfAnalysisDone)throw Error("Grammar rule <"+e+`> may not be defined after the 'performSelfAnalysis' method has been called' Make sure that all grammar rule definitions are done before 'performSelfAnalysis' is called.`);var n=(0,Pr.has)(i,"resyncEnabled")?i.resyncEnabled:Hq.DEFAULT_RULE_CONFIG.resyncEnabled,s=(0,Pr.has)(i,"recoveryValueFunc")?i.recoveryValueFunc:Hq.DEFAULT_RULE_CONFIG.recoveryValueFunc,o=this.ruleShortNameIdx<t},r.prototype.orInternal=function(e,t){var i=this.getKeyForAutomaticLookahead(qn.OR_IDX,t),n=(0,Pr.isArray)(e)?e:e.DEF,s=this.getLaFuncFromCache(i),o=s.call(this,n);if(o!==void 0){var a=n[o];return a.ALT.call(this)}this.raiseNoAltException(t,e.ERR_MSG)},r.prototype.ruleFinallyStateUpdate=function(){if(this.RULE_STACK.pop(),this.RULE_OCCURRENCE_STACK.pop(),this.cstFinallyStateUpdate(),this.RULE_STACK.length===0&&this.isAtEndOfInput()===!1){var e=this.LA(1),t=this.errorMessageProvider.buildNotAllInputParsedMessage({firstRedundant:e,ruleName:this.getCurrRuleFullName()});this.SAVE_ERROR(new wy.NotAllInputParsedException(t,e))}},r.prototype.subruleInternal=function(e,t,i){var n;try{var s=i!==void 0?i.ARGS:void 0;return n=e.call(this,t,s),this.cstPostNonTerminal(n,i!==void 0&&i.LABEL!==void 0?i.LABEL:e.ruleName),n}catch(o){this.subruleInternalError(o,i,e.ruleName)}},r.prototype.subruleInternalError=function(e,t,i){throw(0,wy.isRecognitionException)(e)&&e.partialCstResult!==void 0&&(this.cstPostNonTerminal(e.partialCstResult,t!==void 0&&t.LABEL!==void 0?t.LABEL:i),delete e.partialCstResult),e},r.prototype.consumeInternal=function(e,t,i){var n;try{var s=this.LA(1);this.tokenMatcher(s,e)===!0?(this.consumeToken(),n=s):this.consumeInternalError(e,s,i)}catch(o){n=this.consumeInternalRecovery(e,t,o)}return this.cstPostTerminal(i!==void 0&&i.LABEL!==void 0?i.LABEL:e.name,n),n},r.prototype.consumeInternalError=function(e,t,i){var n,s=this.LA(0);throw i!==void 0&&i.ERR_MSG?n=i.ERR_MSG:n=this.errorMessageProvider.buildMismatchTokenMessage({expected:e,actual:t,previous:s,ruleName:this.getCurrRuleFullName()}),this.SAVE_ERROR(new wy.MismatchedTokenException(n,t,s))},r.prototype.consumeInternalRecovery=function(e,t,i){if(this.recoveryEnabled&&i.name==="MismatchedTokenException"&&!this.isBackTracking()){var n=this.getFollowsForInRuleRecovery(e,t);try{return this.tryInRuleRecovery(e,n)}catch(s){throw s.name===Pye.IN_RULE_RECOVERY_EXCEPTION?i:s}}else throw i},r.prototype.saveRecogState=function(){var e=this.errors,t=(0,Pr.cloneArr)(this.RULE_STACK);return{errors:e,lexerState:this.exportLexerState(),RULE_STACK:t,CST_STACK:this.CST_STACK}},r.prototype.reloadRecogState=function(e){this.errors=e.errors,this.importLexerState(e.lexerState),this.RULE_STACK=e.RULE_STACK},r.prototype.ruleInvocationStateUpdate=function(e,t,i){this.RULE_OCCURRENCE_STACK.push(i),this.RULE_STACK.push(e),this.cstInvocationStateUpdate(t,e)},r.prototype.isBackTracking=function(){return this.isBackTrackingStack.length!==0},r.prototype.getCurrRuleFullName=function(){var e=this.getLastExplicitRuleShortName();return this.shortRuleNameToFull[e]},r.prototype.shortRuleNameToFullName=function(e){return this.shortRuleNameToFull[e]},r.prototype.isAtEndOfInput=function(){return this.tokenMatcher(this.LA(1),Gq.EOF)},r.prototype.reset=function(){this.resetLexerState(),this.isBackTrackingStack=[],this.errors=[],this.RULE_STACK=[],this.CST_STACK=[],this.RULE_OCCURRENCE_STACK=[]},r}();By.RecognizerEngine=kye});var qq=w(by=>{"use strict";Object.defineProperty(by,"__esModule",{value:!0});by.ErrorHandler=void 0;var lx=nf(),cx=Gt(),jq=kd(),Rye=jn(),Fye=function(){function r(){}return r.prototype.initErrorHandler=function(e){this._errors=[],this.errorMessageProvider=(0,cx.has)(e,"errorMessageProvider")?e.errorMessageProvider:Rye.DEFAULT_PARSER_CONFIG.errorMessageProvider},r.prototype.SAVE_ERROR=function(e){if((0,lx.isRecognitionException)(e))return e.context={ruleStack:this.getHumanReadableRuleStack(),ruleOccurrenceStack:(0,cx.cloneArr)(this.RULE_OCCURRENCE_STACK)},this._errors.push(e),e;throw Error("Trying to save an Error which is not a RecognitionException")},Object.defineProperty(r.prototype,"errors",{get:function(){return(0,cx.cloneArr)(this._errors)},set:function(e){this._errors=e},enumerable:!1,configurable:!0}),r.prototype.raiseEarlyExitException=function(e,t,i){for(var n=this.getCurrRuleFullName(),s=this.getGAstProductions()[n],o=(0,jq.getLookaheadPathsForOptionalProd)(e,s,t,this.maxLookahead),a=o[0],l=[],c=1;c<=this.maxLookahead;c++)l.push(this.LA(c));var u=this.errorMessageProvider.buildEarlyExitMessage({expectedIterationPaths:a,actual:l,previous:this.LA(0),customUserDescription:i,ruleName:n});throw this.SAVE_ERROR(new lx.EarlyExitException(u,this.LA(1),this.LA(0)))},r.prototype.raiseNoAltException=function(e,t){for(var i=this.getCurrRuleFullName(),n=this.getGAstProductions()[i],s=(0,jq.getLookaheadPathsForOr)(e,n,this.maxLookahead),o=[],a=1;a<=this.maxLookahead;a++)o.push(this.LA(a));var l=this.LA(0),c=this.errorMessageProvider.buildNoViableAltMessage({expectedPathsPerAlt:s,actual:o,previous:l,customUserDescription:t,ruleName:this.getCurrRuleFullName()});throw this.SAVE_ERROR(new lx.NoViableAltException(c,this.LA(1),l))},r}();by.ErrorHandler=Fye});var zq=w(Qy=>{"use strict";Object.defineProperty(Qy,"__esModule",{value:!0});Qy.ContentAssist=void 0;var Jq=Dd(),Wq=Gt(),Nye=function(){function r(){}return r.prototype.initContentAssist=function(){},r.prototype.computeContentAssist=function(e,t){var i=this.gastProductionsCache[e];if((0,Wq.isUndefined)(i))throw Error("Rule ->"+e+"<- does not exist in this grammar.");return(0,Jq.nextPossibleTokensAfter)([i],t,this.tokenMatcher,this.maxLookahead)},r.prototype.getNextPossibleTokenTypes=function(e){var t=(0,Wq.first)(e.ruleStack),i=this.getGAstProductions(),n=i[t],s=new Jq.NextAfterTokenWalker(n,e).startWalking();return s},r}();Qy.ContentAssist=Nye});var rJ=w(xy=>{"use strict";Object.defineProperty(xy,"__esModule",{value:!0});xy.GastRecorder=void 0;var yn=Gt(),Mo=mn(),Tye=Bd(),_q=_g(),$q=TA(),Lye=jn(),Mye=Cy(),vy={description:"This Object indicates the Parser is during Recording Phase"};Object.freeze(vy);var Vq=!0,Xq=Math.pow(2,Mye.BITS_FOR_OCCURRENCE_IDX)-1,eJ=(0,$q.createToken)({name:"RECORDING_PHASE_TOKEN",pattern:Tye.Lexer.NA});(0,_q.augmentTokenTypes)([eJ]);var tJ=(0,$q.createTokenInstance)(eJ,`This IToken indicates the Parser is in Recording Phase See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,-1,-1,-1,-1,-1,-1);Object.freeze(tJ);var Oye={name:`This CSTNode indicates the Parser is in Recording Phase See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,children:{}},Kye=function(){function r(){}return r.prototype.initGastRecorder=function(e){this.recordingProdStack=[],this.RECORDING_PHASE=!1},r.prototype.enableRecording=function(){var e=this;this.RECORDING_PHASE=!0,this.TRACE_INIT("Enable Recording",function(){for(var t=function(n){var s=n>0?n:"";e["CONSUME"+s]=function(o,a){return this.consumeInternalRecord(o,n,a)},e["SUBRULE"+s]=function(o,a){return this.subruleInternalRecord(o,n,a)},e["OPTION"+s]=function(o){return this.optionInternalRecord(o,n)},e["OR"+s]=function(o){return this.orInternalRecord(o,n)},e["MANY"+s]=function(o){this.manyInternalRecord(n,o)},e["MANY_SEP"+s]=function(o){this.manySepFirstInternalRecord(n,o)},e["AT_LEAST_ONE"+s]=function(o){this.atLeastOneInternalRecord(n,o)},e["AT_LEAST_ONE_SEP"+s]=function(o){this.atLeastOneSepFirstInternalRecord(n,o)}},i=0;i<10;i++)t(i);e.consume=function(n,s,o){return this.consumeInternalRecord(s,n,o)},e.subrule=function(n,s,o){return this.subruleInternalRecord(s,n,o)},e.option=function(n,s){return this.optionInternalRecord(s,n)},e.or=function(n,s){return this.orInternalRecord(s,n)},e.many=function(n,s){this.manyInternalRecord(n,s)},e.atLeastOne=function(n,s){this.atLeastOneInternalRecord(n,s)},e.ACTION=e.ACTION_RECORD,e.BACKTRACK=e.BACKTRACK_RECORD,e.LA=e.LA_RECORD})},r.prototype.disableRecording=function(){var e=this;this.RECORDING_PHASE=!1,this.TRACE_INIT("Deleting Recording methods",function(){for(var t=0;t<10;t++){var i=t>0?t:"";delete e["CONSUME"+i],delete e["SUBRULE"+i],delete e["OPTION"+i],delete e["OR"+i],delete e["MANY"+i],delete e["MANY_SEP"+i],delete e["AT_LEAST_ONE"+i],delete e["AT_LEAST_ONE_SEP"+i]}delete e.consume,delete e.subrule,delete e.option,delete e.or,delete e.many,delete e.atLeastOne,delete e.ACTION,delete e.BACKTRACK,delete e.LA})},r.prototype.ACTION_RECORD=function(e){},r.prototype.BACKTRACK_RECORD=function(e,t){return function(){return!0}},r.prototype.LA_RECORD=function(e){return Lye.END_OF_FILE},r.prototype.topLevelRuleRecord=function(e,t){try{var i=new Mo.Rule({definition:[],name:e});return i.name=e,this.recordingProdStack.push(i),t.call(this),this.recordingProdStack.pop(),i}catch(n){if(n.KNOWN_RECORDER_ERROR!==!0)try{n.message=n.message+` This error was thrown during the "grammar recording phase" For more info see: https://chevrotain.io/docs/guide/internals.html#grammar-recording`}catch{throw n}throw n}},r.prototype.optionInternalRecord=function(e,t){return Ld.call(this,Mo.Option,e,t)},r.prototype.atLeastOneInternalRecord=function(e,t){Ld.call(this,Mo.RepetitionMandatory,t,e)},r.prototype.atLeastOneSepFirstInternalRecord=function(e,t){Ld.call(this,Mo.RepetitionMandatoryWithSeparator,t,e,Vq)},r.prototype.manyInternalRecord=function(e,t){Ld.call(this,Mo.Repetition,t,e)},r.prototype.manySepFirstInternalRecord=function(e,t){Ld.call(this,Mo.RepetitionWithSeparator,t,e,Vq)},r.prototype.orInternalRecord=function(e,t){return Uye.call(this,e,t)},r.prototype.subruleInternalRecord=function(e,t,i){if(Sy(t),!e||(0,yn.has)(e,"ruleName")===!1){var n=new Error(" argument is invalid"+(" expecting a Parser method reference but got: <"+JSON.stringify(e)+">")+(` inside top level rule: <`+this.recordingProdStack[0].name+">"));throw n.KNOWN_RECORDER_ERROR=!0,n}var s=(0,yn.peek)(this.recordingProdStack),o=e.ruleName,a=new Mo.NonTerminal({idx:t,nonTerminalName:o,label:i==null?void 0:i.LABEL,referencedRule:void 0});return s.definition.push(a),this.outputCst?Oye:vy},r.prototype.consumeInternalRecord=function(e,t,i){if(Sy(t),!(0,_q.hasShortKeyProperty)(e)){var n=new Error(" argument is invalid"+(" expecting a TokenType reference but got: <"+JSON.stringify(e)+">")+(` inside top level rule: <`+this.recordingProdStack[0].name+">"));throw n.KNOWN_RECORDER_ERROR=!0,n}var s=(0,yn.peek)(this.recordingProdStack),o=new Mo.Terminal({idx:t,terminalType:e,label:i==null?void 0:i.LABEL});return s.definition.push(o),tJ},r}();xy.GastRecorder=Kye;function Ld(r,e,t,i){i===void 0&&(i=!1),Sy(t);var n=(0,yn.peek)(this.recordingProdStack),s=(0,yn.isFunction)(e)?e:e.DEF,o=new r({definition:[],idx:t});return i&&(o.separator=e.SEP),(0,yn.has)(e,"MAX_LOOKAHEAD")&&(o.maxLookahead=e.MAX_LOOKAHEAD),this.recordingProdStack.push(o),s.call(this),n.definition.push(o),this.recordingProdStack.pop(),vy}function Uye(r,e){var t=this;Sy(e);var i=(0,yn.peek)(this.recordingProdStack),n=(0,yn.isArray)(r)===!1,s=n===!1?r:r.DEF,o=new Mo.Alternation({definition:[],idx:e,ignoreAmbiguities:n&&r.IGNORE_AMBIGUITIES===!0});(0,yn.has)(r,"MAX_LOOKAHEAD")&&(o.maxLookahead=r.MAX_LOOKAHEAD);var a=(0,yn.some)(s,function(l){return(0,yn.isFunction)(l.GATE)});return o.hasPredicates=a,i.definition.push(o),(0,yn.forEach)(s,function(l){var c=new Mo.Alternative({definition:[]});o.definition.push(c),(0,yn.has)(l,"IGNORE_AMBIGUITIES")?c.ignoreAmbiguities=l.IGNORE_AMBIGUITIES:(0,yn.has)(l,"GATE")&&(c.ignoreAmbiguities=!0),t.recordingProdStack.push(c),l.ALT.call(t),t.recordingProdStack.pop()}),vy}function Zq(r){return r===0?"":""+r}function Sy(r){if(r<0||r>Xq){var e=new Error("Invalid DSL Method idx value: <"+r+`> `+("Idx value must be a none negative value smaller than "+(Xq+1)));throw e.KNOWN_RECORDER_ERROR=!0,e}}});var nJ=w(Py=>{"use strict";Object.defineProperty(Py,"__esModule",{value:!0});Py.PerformanceTracer=void 0;var iJ=Gt(),Hye=jn(),Gye=function(){function r(){}return r.prototype.initPerformanceTracer=function(e){if((0,iJ.has)(e,"traceInitPerf")){var t=e.traceInitPerf,i=typeof t=="number";this.traceInitMaxIdent=i?t:1/0,this.traceInitPerf=i?t>0:t}else this.traceInitMaxIdent=0,this.traceInitPerf=Hye.DEFAULT_PARSER_CONFIG.traceInitPerf;this.traceInitIndent=-1},r.prototype.TRACE_INIT=function(e,t){if(this.traceInitPerf===!0){this.traceInitIndent++;var i=new Array(this.traceInitIndent+1).join(" ");this.traceInitIndent <"+e+">");var n=(0,iJ.timer)(t),s=n.time,o=n.value,a=s>10?console.warn:console.log;return this.traceInitIndent time: "+s+"ms"),this.traceInitIndent--,o}else return t()},r}();Py.PerformanceTracer=Gye});var sJ=w(Dy=>{"use strict";Object.defineProperty(Dy,"__esModule",{value:!0});Dy.applyMixins=void 0;function Yye(r,e){e.forEach(function(t){var i=t.prototype;Object.getOwnPropertyNames(i).forEach(function(n){if(n!=="constructor"){var s=Object.getOwnPropertyDescriptor(i,n);s&&(s.get||s.set)?Object.defineProperty(r.prototype,n,s):r.prototype[n]=t.prototype[n]}})})}Dy.applyMixins=Yye});var jn=w(dr=>{"use strict";var AJ=dr&&dr.__extends||function(){var r=function(e,t){return r=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(i,n){i.__proto__=n}||function(i,n){for(var s in n)Object.prototype.hasOwnProperty.call(n,s)&&(i[s]=n[s])},r(e,t)};return function(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");r(e,t);function i(){this.constructor=e}e.prototype=t===null?Object.create(t):(i.prototype=t.prototype,new i)}}();Object.defineProperty(dr,"__esModule",{value:!0});dr.EmbeddedActionsParser=dr.CstParser=dr.Parser=dr.EMPTY_ALT=dr.ParserDefinitionErrorType=dr.DEFAULT_RULE_CONFIG=dr.DEFAULT_PARSER_CONFIG=dr.END_OF_FILE=void 0;var en=Gt(),jye=qj(),oJ=TA(),lJ=xd(),aJ=Cq(),qye=sx(),Jye=Qq(),Wye=Tq(),zye=Mq(),Vye=Kq(),Xye=Yq(),Zye=qq(),_ye=zq(),$ye=rJ(),ewe=nJ(),twe=sJ();dr.END_OF_FILE=(0,oJ.createTokenInstance)(oJ.EOF,"",NaN,NaN,NaN,NaN,NaN,NaN);Object.freeze(dr.END_OF_FILE);dr.DEFAULT_PARSER_CONFIG=Object.freeze({recoveryEnabled:!1,maxLookahead:3,dynamicTokensEnabled:!1,outputCst:!0,errorMessageProvider:lJ.defaultParserErrorProvider,nodeLocationTracking:"none",traceInitPerf:!1,skipValidations:!1});dr.DEFAULT_RULE_CONFIG=Object.freeze({recoveryValueFunc:function(){},resyncEnabled:!0});var rwe;(function(r){r[r.INVALID_RULE_NAME=0]="INVALID_RULE_NAME",r[r.DUPLICATE_RULE_NAME=1]="DUPLICATE_RULE_NAME",r[r.INVALID_RULE_OVERRIDE=2]="INVALID_RULE_OVERRIDE",r[r.DUPLICATE_PRODUCTIONS=3]="DUPLICATE_PRODUCTIONS",r[r.UNRESOLVED_SUBRULE_REF=4]="UNRESOLVED_SUBRULE_REF",r[r.LEFT_RECURSION=5]="LEFT_RECURSION",r[r.NONE_LAST_EMPTY_ALT=6]="NONE_LAST_EMPTY_ALT",r[r.AMBIGUOUS_ALTS=7]="AMBIGUOUS_ALTS",r[r.CONFLICT_TOKENS_RULES_NAMESPACE=8]="CONFLICT_TOKENS_RULES_NAMESPACE",r[r.INVALID_TOKEN_NAME=9]="INVALID_TOKEN_NAME",r[r.NO_NON_EMPTY_LOOKAHEAD=10]="NO_NON_EMPTY_LOOKAHEAD",r[r.AMBIGUOUS_PREFIX_ALTS=11]="AMBIGUOUS_PREFIX_ALTS",r[r.TOO_MANY_ALTS=12]="TOO_MANY_ALTS"})(rwe=dr.ParserDefinitionErrorType||(dr.ParserDefinitionErrorType={}));function iwe(r){return r===void 0&&(r=void 0),function(){return r}}dr.EMPTY_ALT=iwe;var ky=function(){function r(e,t){this.definitionErrors=[],this.selfAnalysisDone=!1;var i=this;if(i.initErrorHandler(t),i.initLexerAdapter(),i.initLooksAhead(t),i.initRecognizerEngine(e,t),i.initRecoverable(t),i.initTreeBuilder(t),i.initContentAssist(),i.initGastRecorder(t),i.initPerformanceTracer(t),(0,en.has)(t,"ignoredIssues"))throw new Error(`The IParserConfig property has been deprecated. Please use the flag on the relevant DSL method instead. See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#IGNORING_AMBIGUITIES For further details.`);this.skipValidations=(0,en.has)(t,"skipValidations")?t.skipValidations:dr.DEFAULT_PARSER_CONFIG.skipValidations}return r.performSelfAnalysis=function(e){throw Error("The **static** `performSelfAnalysis` method has been deprecated. \nUse the **instance** method with the same name instead.")},r.prototype.performSelfAnalysis=function(){var e=this;this.TRACE_INIT("performSelfAnalysis",function(){var t;e.selfAnalysisDone=!0;var i=e.className;e.TRACE_INIT("toFastProps",function(){(0,en.toFastProperties)(e)}),e.TRACE_INIT("Grammar Recording",function(){try{e.enableRecording(),(0,en.forEach)(e.definedRulesNames,function(s){var o=e[s],a=o.originalGrammarAction,l=void 0;e.TRACE_INIT(s+" Rule",function(){l=e.topLevelRuleRecord(s,a)}),e.gastProductionsCache[s]=l})}finally{e.disableRecording()}});var n=[];if(e.TRACE_INIT("Grammar Resolving",function(){n=(0,aJ.resolveGrammar)({rules:(0,en.values)(e.gastProductionsCache)}),e.definitionErrors=e.definitionErrors.concat(n)}),e.TRACE_INIT("Grammar Validations",function(){if((0,en.isEmpty)(n)&&e.skipValidations===!1){var s=(0,aJ.validateGrammar)({rules:(0,en.values)(e.gastProductionsCache),maxLookahead:e.maxLookahead,tokenTypes:(0,en.values)(e.tokensMap),errMsgProvider:lJ.defaultGrammarValidatorErrorProvider,grammarName:i});e.definitionErrors=e.definitionErrors.concat(s)}}),(0,en.isEmpty)(e.definitionErrors)&&(e.recoveryEnabled&&e.TRACE_INIT("computeAllProdsFollows",function(){var s=(0,jye.computeAllProdsFollows)((0,en.values)(e.gastProductionsCache));e.resyncFollows=s}),e.TRACE_INIT("ComputeLookaheadFunctions",function(){e.preComputeLookaheadFunctions((0,en.values)(e.gastProductionsCache))})),!r.DEFER_DEFINITION_ERRORS_HANDLING&&!(0,en.isEmpty)(e.definitionErrors))throw t=(0,en.map)(e.definitionErrors,function(s){return s.message}),new Error(`Parser Definition Errors detected: `+t.join(` ------------------------------- `))})},r.DEFER_DEFINITION_ERRORS_HANDLING=!1,r}();dr.Parser=ky;(0,twe.applyMixins)(ky,[qye.Recoverable,Jye.LooksAhead,Wye.TreeBuilder,zye.LexerAdapter,Xye.RecognizerEngine,Vye.RecognizerApi,Zye.ErrorHandler,_ye.ContentAssist,$ye.GastRecorder,ewe.PerformanceTracer]);var nwe=function(r){AJ(e,r);function e(t,i){i===void 0&&(i=dr.DEFAULT_PARSER_CONFIG);var n=this,s=(0,en.cloneObj)(i);return s.outputCst=!0,n=r.call(this,t,s)||this,n}return e}(ky);dr.CstParser=nwe;var swe=function(r){AJ(e,r);function e(t,i){i===void 0&&(i=dr.DEFAULT_PARSER_CONFIG);var n=this,s=(0,en.cloneObj)(i);return s.outputCst=!1,n=r.call(this,t,s)||this,n}return e}(ky);dr.EmbeddedActionsParser=swe});var uJ=w(Ry=>{"use strict";Object.defineProperty(Ry,"__esModule",{value:!0});Ry.createSyntaxDiagramsCode=void 0;var cJ=Dv();function owe(r,e){var t=e===void 0?{}:e,i=t.resourceBase,n=i===void 0?"https://unpkg.com/chevrotain@"+cJ.VERSION+"/diagrams/":i,s=t.css,o=s===void 0?"https://unpkg.com/chevrotain@"+cJ.VERSION+"/diagrams/diagrams.css":s,a=` `,l=` `,c=` ================================================ FILE: compat-tests/fixtures/basic-react17/package/package.json ================================================ { "scripts": { "dev": "parcel serve ./index.html", "build": "NODE_ENV=production parcel build ./index.html --no-minify", "start": "serve dist" }, "dependencies": { "@theatre/core": "0.0.1-COMPAT.1", "@theatre/studio": "0.0.1-COMPAT.1", "parcel-bundler": "^1.12.5", "react": "^17.0.2", "react-dom": "^17.0.2", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", "serve": "14.2.0" } } ================================================ FILE: compat-tests/fixtures/basic-react17/package/src/App/Scene.tsx ================================================ import type {IScrub} from '@theatre/core' import studio from '@theatre/studio' import React, {useLayoutEffect, useMemo, useState} from 'react' import type {ISheet, ISheetObject, IProject} from '@theatre/core' import type {UseDragOpts} from './useDrag' import useDrag from './useDrag' studio.initialize() const boxObjectConfig = { x: 0, y: 0, } const Box: React.FC<{ id: string sheet: ISheet selectedObject: ISheetObject | undefined }> = ({id, sheet, selectedObject}) => { // This is cheap to call and always returns the same value, so no need for useMemo() const obj = sheet.object(id, boxObjectConfig) const isSelected = selectedObject === obj const [pos, setPos] = useState<{x: number; y: number}>({x: 0, y: 0}) useLayoutEffect(() => { const unsubscribeFromChanges = obj.onValuesChange((newValues) => { setPos(newValues) }) return unsubscribeFromChanges }, [id]) const [divRef, setDivRef] = useState(null) const dragOpts = useMemo((): UseDragOpts => { let scrub: IScrub | undefined let initial: typeof obj.value let firstOnDragCalled = false return { onDragStart() { scrub = studio.scrub() initial = obj.value firstOnDragCalled = false }, onDrag(x, y) { if (!firstOnDragCalled) { studio.setSelection([obj]) firstOnDragCalled = true } scrub!.capture(({set}) => { set(obj.props, {x: x + initial.x, y: y + initial.y}) }) }, onDragEnd(dragHappened) { if (dragHappened) { scrub!.commit() } else { scrub!.discard() } }, lockCursorTo: 'move', } }, []) useDrag(divRef, dragOpts) return (

{ studio.setSelection([obj]) }} ref={setDivRef} style={{ width: 100, height: 100, background: 'gray', position: 'absolute', left: pos.x + 'px', top: pos.y + 'px', boxSizing: 'border-box', border: isSelected ? '1px solid #5a92fa' : '1px solid transparent', }} >
) } let lastBoxId = 1 export const Scene: React.FC<{project: IProject}> = ({project}) => { const [boxes, setBoxes] = useState>(['0', '1']) // This is cheap to call and always returns the same value, so no need for useMemo() const sheet = project.sheet('Scene', 'default') const [selection, _setSelection] = useState>([]) useLayoutEffect(() => { return studio.onSelectionChange((newSelection) => { _setSelection( newSelection.filter( (s): s is ISheetObject => s.type === 'Theatre_SheetObject_PublicAPI', ), ) }) }) return (
{boxes.map((id) => ( ))}
) } ================================================ FILE: compat-tests/fixtures/basic-react17/package/src/App/useDrag.ts ================================================ import {useLayoutEffect, useRef} from 'react' const noop = () => {} function createCursorLock(cursor: string) { const el = document.createElement('div') el.style.cssText = ` position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 9999999;` el.style.cursor = cursor document.body.appendChild(el) const relinquish = () => { document.body.removeChild(el) } return relinquish } export type UseDragOpts = { disabled?: boolean dontBlockMouseDown?: boolean lockCursorTo?: string onDragStart?: (event: MouseEvent) => void | false onDragEnd?: (dragHappened: boolean) => void onDrag: (dx: number, dy: number, event: MouseEvent) => void } export default function useDrag( target: HTMLElement | undefined | null, opts: UseDragOpts, ) { const optsRef = useRef(opts) optsRef.current = opts const modeRef = useRef<'dragStartCalled' | 'dragging' | 'notDragging'>( 'notDragging', ) const stateRef = useRef<{ dragHappened: boolean startPos: { x: number y: number } }>({dragHappened: false, startPos: {x: 0, y: 0}}) useLayoutEffect(() => { if (!target) return const getDistances = (event: MouseEvent): [number, number] => { const {startPos} = stateRef.current return [event.screenX - startPos.x, event.screenY - startPos.y] } let relinquishCursorLock = noop const dragHandler = (event: MouseEvent) => { if (!stateRef.current.dragHappened && optsRef.current.lockCursorTo) { relinquishCursorLock = createCursorLock(optsRef.current.lockCursorTo) } if (!stateRef.current.dragHappened) stateRef.current.dragHappened = true modeRef.current = 'dragging' const deltas = getDistances(event) optsRef.current.onDrag(deltas[0], deltas[1], event) } const dragEndHandler = () => { removeDragListeners() modeRef.current = 'notDragging' optsRef.current.onDragEnd && optsRef.current.onDragEnd(stateRef.current.dragHappened) relinquishCursorLock() relinquishCursorLock = noop } const addDragListeners = () => { document.addEventListener('mousemove', dragHandler) document.addEventListener('mouseup', dragEndHandler) } const removeDragListeners = () => { document.removeEventListener('mousemove', dragHandler) document.removeEventListener('mouseup', dragEndHandler) } const preventUnwantedClick = (event: MouseEvent) => { if (optsRef.current.disabled) return if (stateRef.current.dragHappened) { if ( !optsRef.current.dontBlockMouseDown && modeRef.current !== 'notDragging' ) { event.stopPropagation() event.preventDefault() } stateRef.current.dragHappened = false } } const dragStartHandler = (event: MouseEvent) => { const opts = optsRef.current if (opts.disabled === true) return if (event.button !== 0) return const resultOfStart = opts.onDragStart && opts.onDragStart(event) if (resultOfStart === false) return if (!opts.dontBlockMouseDown) { event.stopPropagation() event.preventDefault() } modeRef.current = 'dragStartCalled' const {screenX, screenY} = event stateRef.current.startPos = {x: screenX, y: screenY} stateRef.current.dragHappened = false addDragListeners() } const onMouseDown = (e: MouseEvent) => { dragStartHandler(e) } target.addEventListener('mousedown', onMouseDown) target.addEventListener('click', preventUnwantedClick) return () => { removeDragListeners() target.removeEventListener('mousedown', onMouseDown) target.removeEventListener('click', preventUnwantedClick) relinquishCursorLock() if (modeRef.current !== 'notDragging') { optsRef.current.onDragEnd && optsRef.current.onDragEnd(modeRef.current === 'dragging') } modeRef.current = 'notDragging' } }, [target]) } ================================================ FILE: compat-tests/fixtures/basic-react17/package/src/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom' import studio from '@theatre/studio' import {getProject} from '@theatre/core' import {Scene} from './App/Scene' studio.initialize() ReactDOM.render( , document.getElementById('root')!, ) ================================================ FILE: compat-tests/fixtures/basic-react17/package/tsconfig.json ================================================ { "compilerOptions": { "jsx": "react" } } ================================================ FILE: compat-tests/fixtures/basic-react17/react17.compat-test.ts ================================================ // @cspotcode/zx is zx in CommonJS import {$, cd, path, ProcessPromise} from '@cspotcode/zx' import {defer, testServerAndPage} from '../../utils/testUtils' const PATH_TO_PACKAGE = path.join(__dirname, `./package`) describe(`react17`, () => { test(`build succeeds`, async () => { cd(PATH_TO_PACKAGE) const {exitCode} = await $`npm run build` // at this point, the build should have succeeded expect(exitCode).toEqual(0) }) // this one is failing for some reason, but manually running the server works fine describe(`build`, () => { return function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm start -- -p ${port}` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('Accepting connections'), }) }) }) ================================================ FILE: compat-tests/fixtures/basic-vite4/basic-vite4.compat-test.ts ================================================ // @cspotcode/zx is zx in CommonJS import {$, cd, path, ProcessPromise} from '@cspotcode/zx' import {testServerAndPage} from '../../utils/testUtils' const PATH_TO_PACKAGE = path.join(__dirname, `./package`) describe(`basic-vite4`, () => { test(`\`$ vite build\` should succeed`, async () => { cd(PATH_TO_PACKAGE) const {exitCode} = await $`npm run build` // at this point, the build should have succeeded expect(exitCode).toEqual(0) }) describe(`vite preview`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm run preview -- --port ${port}` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('--host'), }) }) describe(`vite dev`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm run dev -- --port ${port}` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('--host'), }) }) }) ================================================ FILE: compat-tests/fixtures/basic-vite4/package/.gitignore ================================================ /.cache /dist /package-lock.json ================================================ FILE: compat-tests/fixtures/basic-vite4/package/index.html ================================================ ================================================ FILE: compat-tests/fixtures/basic-vite4/package/package.json ================================================ { "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "dependencies": { "@theatre/core": "0.0.1-COMPAT.1", "@theatre/studio": "0.0.1-COMPAT.1" }, "browserslist": { "production": [">0.2%", "not dead", "not op_mini all"], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } ================================================ FILE: compat-tests/fixtures/basic-vite4/package/src/index.ts ================================================ import {getProject} from '@theatre/core' const project = getProject('Sample project') const sheet = project.sheet('Scene') if (import.meta.env.MODE === 'development') { const {default: studio} = await import('@theatre/studio') studio.initialize() } ================================================ FILE: compat-tests/fixtures/basic-vite4/package/tsconfig.json ================================================ { "include": ["./src/**/*.ts"], "compilerOptions": { "types": ["vite/client"], "moduleResolution": "NodeNext", "module": "NodeNext", "target": "ESNext" } } ================================================ FILE: compat-tests/fixtures/r3f-cra/cra.compat-test.ts ================================================ // @cspotcode/zx is zx in CommonJS import {$, cd, path, ProcessPromise} from '@cspotcode/zx' import {defer, testServerAndPage} from '../../utils/testUtils' const PATH_TO_PACKAGE = path.join(__dirname, `./package`) describe(`Create React App`, () => { test(`build succeeds`, async () => { cd(PATH_TO_PACKAGE) const {exitCode} = await $`npm run build` // at this point, the build should have succeeded expect(exitCode).toEqual(0) }) describe(`build`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm run serve -- -p ${port}` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('Accepting connections'), }) }) describe(`dev`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`BROWSER=none PORT=${port} npm run start` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('You can now view'), }) }) }) ================================================ FILE: compat-tests/fixtures/r3f-cra/package/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: compat-tests/fixtures/r3f-cra/package/README.md ================================================ Testing `@theatre/core` and `@theatre/studio` with `npm`, `create-react-app`, and `react@18` ================================================ FILE: compat-tests/fixtures/r3f-cra/package/package.json ================================================ { "name": "@compat/cra-react18", "version": "0.1.0", "private": true, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "serve": "serve -s build" }, "dependencies": { "@react-three/drei": "^9.80.1", "@react-three/fiber": "^8.13.6", "three": "^0.155.0", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@theatre/core": "0.0.1-COMPAT.1", "@theatre/r3f": "0.0.1-COMPAT.1", "@theatre/studio": "0.0.1-COMPAT.1", "@types/jest": "^29.5.3", "@types/node": "^20.4.5", "@types/react": "^18.2.17", "@types/react-dom": "^18.2.7", "react-scripts": "^5.0.1", "typescript": "^3.2.1", "serve": "14.2.0", "web-vitals": "^1.0.1" }, "eslintConfig": { "extends": ["react-app", "react-app/jest"] }, "browserslist": { "production": [">0.2%", "not dead", "not op_mini all"], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } ================================================ FILE: compat-tests/fixtures/r3f-cra/package/public/index.html ================================================ React App
================================================ FILE: compat-tests/fixtures/r3f-cra/package/src/App/App.tsx ================================================ import {getProject} from '@theatre/core' import React, {useEffect, useRef} from 'react' import {Canvas} from '@react-three/fiber' import {editable as e, SheetProvider, PerspectiveCamera} from '@theatre/r3f' import state from './state.json' // credit: https://codesandbox.io/s/camera-pan-nsb7f function Plane({color, theatreKey, ...props}: any) { return ( ) } export default function App() { const light2Ref = useRef() useEffect(() => { const interval = setInterval(() => { if (!light2Ref.current) return clearInterval(interval) const intensityInStateJson = 3 const currentIntensity = light2Ref.current.intensity if (currentIntensity !== intensityInStateJson) { console.error(`Test failed: light2.intensity is ${currentIntensity}`) } else { console.log(`Test passed: light2.intensity is ${intensityInStateJson}`) } }, 50) // see the note on below to understand why we're doing this }, []) return ( {/* @ts-ignore */} ) } ================================================ FILE: compat-tests/fixtures/r3f-cra/package/src/App/state.json ================================================ { "sheetsById": { "R3F-Canvas": { "staticOverrides": { "byObject": { "Light 2": { "intensity": 3 } } } } }, "definitionVersion": "0.4.0", "revisionHistory": [ "jVNB3VWU34BIQK7M", "-NXkC2GceSVBoVqa", "Bw7ng1kdcWmMO5DN" ] } ================================================ FILE: compat-tests/fixtures/r3f-cra/package/src/index.tsx ================================================ import ReactDOM from 'react-dom/client' import studio from '@theatre/studio' import extension from '@theatre/r3f/dist/extension' import App from './App/App.tsx' if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') { studio.extend(extension) studio.initialize({usePersistentStorage: false}) } const container = document.getElementById('root')! const root = ReactDOM.createRoot(container) root.render() ================================================ FILE: compat-tests/fixtures/r3f-next-latest/next-latest.compat-test.ts ================================================ // @cspotcode/zx is zx in CommonJS import {$, cd, path, ProcessPromise} from '@cspotcode/zx' import {testServerAndPage} from '../../utils/testUtils' const PATH_TO_PACKAGE = path.join(__dirname, `./package`) describe(`next`, () => { describe(`build`, () => { test(`command runs without error`, async () => { cd(PATH_TO_PACKAGE) const {exitCode} = await $`npm run build` // at this point, the build should have succeeded expect(exitCode).toEqual(0) }) describe(`next start`, () => { testServerAndPage({ startServerOnPort: (port: number) => { cd(PATH_TO_PACKAGE) return $`npm run start -- --port ${port}` }, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('started server'), }) }) }) // this test is not ready yet, so we'll skip it describe(`$ next dev`, () => { testServerAndPage({ startServerOnPort: (port: number) => { cd(PATH_TO_PACKAGE) return $`npm run dev -- --port ${port}` }, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('compiled client and server successfully'), }) }) }) ================================================ FILE: compat-tests/fixtures/r3f-next-latest/package/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env.local .env.development.local .env.test.local .env.production.local ================================================ FILE: compat-tests/fixtures/r3f-next-latest/package/README.md ================================================ This is a starter template for [Learn Next.js](https://nextjs.org/learn). ================================================ FILE: compat-tests/fixtures/r3f-next-latest/package/next-env.d.ts ================================================ /// /// /// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. ================================================ FILE: compat-tests/fixtures/r3f-next-latest/package/package.json ================================================ { "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start" }, "dependencies": { "@react-three/drei": "^9.80.1", "@react-three/fiber": "^8.13.6", "three": "^0.155.0", "@theatre/core": "0.0.1-COMPAT.1", "@theatre/r3f": "0.0.1-COMPAT.1", "@theatre/studio": "0.0.1-COMPAT.1", "theatric": "0.0.1-COMPAT.1", "next": "latest", "react": "^18.2.0", "react-dom": "^18.2.0" } } ================================================ FILE: compat-tests/fixtures/r3f-next-latest/package/pages/index.js ================================================ import React from 'react' import studio from '@theatre/studio' import extension from '@theatre/r3f/dist/extension' import App from '../src/App/App' if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') { studio.extend(extension) studio.initialize({usePersistentStorage: false}) } export default function Home() { return } ================================================ FILE: compat-tests/fixtures/r3f-next-latest/package/src/App/App.tsx ================================================ import {getProject} from '@theatre/core' import React, {useEffect, useRef} from 'react' import {Canvas} from '@react-three/fiber' import {editable as e, SheetProvider, PerspectiveCamera} from '@theatre/r3f' import state from './state.json' // credit: https://codesandbox.io/s/camera-pan-nsb7f function Plane({color, theatreKey, ...props}: any) { return ( ) } export default function App() { const light2Ref = useRef() useEffect(() => { const interval = setInterval(() => { if (!light2Ref.current) return clearInterval(interval) const intensityInStateJson = 3 const currentIntensity = light2Ref.current.intensity if (currentIntensity !== intensityInStateJson) { console.error(`Test failed: light2.intensity is ${currentIntensity}`) } else { console.log(`Test passed: light2.intensity is ${intensityInStateJson}`) } }, 50) // see the note on below to understand why we're doing this }, []) return ( {/* @ts-ignore */} ) } ================================================ FILE: compat-tests/fixtures/r3f-next-latest/package/src/App/state.json ================================================ { "sheetsById": { "R3F-Canvas": { "staticOverrides": { "byObject": { "Light 2": { "intensity": 3 } } } } }, "definitionVersion": "0.4.0", "revisionHistory": [ "jVNB3VWU34BIQK7M", "-NXkC2GceSVBoVqa", "Bw7ng1kdcWmMO5DN" ] } ================================================ FILE: compat-tests/fixtures/r3f-next-latest/package/tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "incremental": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "plugins": [ { "name": "next" } ], "strictNullChecks": true }, "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts" ], "exclude": [ "node_modules" ] } ================================================ FILE: compat-tests/fixtures/r3f-parcel1/package/.gitignore ================================================ /.cache /dist /package-lock.json ================================================ FILE: compat-tests/fixtures/r3f-parcel1/package/index.html ================================================ Theatre.js Example - DOM
================================================ FILE: compat-tests/fixtures/r3f-parcel1/package/package.json ================================================ { "scripts": { "dev": "parcel serve ./index.html", "build": "NODE_ENV=production parcel build ./index.html --no-minify", "start": "serve dist" }, "dependencies": { "@react-three/drei": "^9.80.1", "@react-three/fiber": "^8.13.6", "three": "^0.155.0", "@theatre/core": "0.0.1-COMPAT.1", "@theatre/r3f": "0.0.1-COMPAT.1", "@theatre/studio": "0.0.1-COMPAT.1", "parcel-bundler": "^1.12.5", "react": "^18.2.0", "react-dom": "^18.2.0", "serve": "14.2.0" } } ================================================ FILE: compat-tests/fixtures/r3f-parcel1/package/src/App/App.tsx ================================================ import {getProject} from '@theatre/core' import React, {useEffect, useRef} from 'react' import {Canvas} from '@react-three/fiber' import {editable as e, SheetProvider, PerspectiveCamera} from '@theatre/r3f' import state from './state.json' // credit: https://codesandbox.io/s/camera-pan-nsb7f function Plane({color, theatreKey, ...props}: any) { return ( ) } export default function App() { const light2Ref = useRef() useEffect(() => { const interval = setInterval(() => { if (!light2Ref.current) return clearInterval(interval) const intensityInStateJson = 3 const currentIntensity = light2Ref.current.intensity if (currentIntensity !== intensityInStateJson) { console.error(`Test failed: light2.intensity is ${currentIntensity}`) } else { console.log(`Test passed: light2.intensity is ${intensityInStateJson}`) } }, 50) // see the note on below to understand why we're doing this }, []) return ( {/* @ts-ignore */} ) } ================================================ FILE: compat-tests/fixtures/r3f-parcel1/package/src/App/state.json ================================================ { "sheetsById": { "R3F-Canvas": { "staticOverrides": { "byObject": { "Light 2": { "intensity": 3 } } } } }, "definitionVersion": "0.4.0", "revisionHistory": [ "jVNB3VWU34BIQK7M", "-NXkC2GceSVBoVqa", "Bw7ng1kdcWmMO5DN" ] } ================================================ FILE: compat-tests/fixtures/r3f-parcel1/package/src/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import studio from '@theatre/studio' import extension from '@theatre/r3f/dist/extension' import App from './App/App' if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') { studio.extend(extension) studio.initialize({usePersistentStorage: false}) } console.log('React', React) const container = document.getElementById('root') const root = ReactDOM.createRoot(container) root.render() ================================================ FILE: compat-tests/fixtures/r3f-parcel1/package/tsconfig.json ================================================ { "compilerOptions": { "jsx": "react" } } ================================================ FILE: compat-tests/fixtures/r3f-parcel1/parcel1.compat-test.ts ================================================ // @cspotcode/zx is zx in CommonJS import {$, cd, path, ProcessPromise} from '@cspotcode/zx' import {defer, testServerAndPage} from '../../utils/testUtils' const PATH_TO_PACKAGE = path.join(__dirname, `./package`) describe(`parcel1`, () => { test(`build succeeds`, async () => { cd(PATH_TO_PACKAGE) const {exitCode} = await $`npm run build` // at this point, the build should have succeeded expect(exitCode).toEqual(0) }) describe(`build`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm start -- -p ${port}` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('Accepting connections'), }) }) describe(`dev`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm run dev -- -p ${port}` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('Server running at'), }) }) }) ================================================ FILE: compat-tests/fixtures/r3f-react18/package/.gitignore ================================================ /.parcel-cache /.cache /dist /package-lock.json ================================================ FILE: compat-tests/fixtures/r3f-react18/package/index.html ================================================ Theatre.js Example - DOM
================================================ FILE: compat-tests/fixtures/r3f-react18/package/package.json ================================================ { "scripts": { "dev": "parcel serve ./index.html", "build": "parcel build ./index.html", "start": "serve dist" }, "dependencies": { "@react-three/drei": "^9.80.1", "@react-three/fiber": "^8.13.6", "three": "^0.155.0", "@theatre/core": "0.0.1-COMPAT.1", "@theatre/r3f": "0.0.1-COMPAT.1", "@theatre/studio": "0.0.1-COMPAT.1", "parcel": "^2.9.3", "react": "^18.1.0", "react-dom": "^18.1.0", "serve": "14.2.0" } } ================================================ FILE: compat-tests/fixtures/r3f-react18/package/src/App/App.tsx ================================================ import {getProject} from '@theatre/core' import React, {useEffect, useRef} from 'react' import {Canvas} from '@react-three/fiber' import {editable as e, SheetProvider, PerspectiveCamera} from '@theatre/r3f' import state from './state.json' // credit: https://codesandbox.io/s/camera-pan-nsb7f function Plane({color, theatreKey, ...props}: any) { return ( ) } export default function App() { const light2Ref = useRef() useEffect(() => { const interval = setInterval(() => { if (!light2Ref.current) return clearInterval(interval) const intensityInStateJson = 3 const currentIntensity = light2Ref.current.intensity if (currentIntensity !== intensityInStateJson) { console.error(`Test failed: light2.intensity is ${currentIntensity}`) } else { console.log(`Test passed: light2.intensity is ${intensityInStateJson}`) } }, 50) // see the note on below to understand why we're doing this }, []) return ( {/* @ts-ignore */} ) } ================================================ FILE: compat-tests/fixtures/r3f-react18/package/src/App/state.json ================================================ { "sheetsById": { "R3F-Canvas": { "staticOverrides": { "byObject": { "Light 2": { "intensity": 3 } } } } }, "definitionVersion": "0.4.0", "revisionHistory": [ "jVNB3VWU34BIQK7M", "-NXkC2GceSVBoVqa", "Bw7ng1kdcWmMO5DN" ] } ================================================ FILE: compat-tests/fixtures/r3f-react18/package/src/index.js ================================================ import ReactDOM from 'react-dom/client' import studio from '@theatre/studio' import extension from '@theatre/r3f/dist/extension' import App from './App/App' if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') { studio.extend(extension) studio.initialize({usePersistentStorage: false}) } const container = document.getElementById('root') const root = ReactDOM.createRoot(container) root.render() ================================================ FILE: compat-tests/fixtures/r3f-react18/package/tsconfig.json ================================================ { "compilerOptions": { "jsx": "react" } } ================================================ FILE: compat-tests/fixtures/r3f-react18/react18.compat-test.ts ================================================ // @cspotcode/zx is zx in CommonJS import {$, cd, path, ProcessPromise} from '@cspotcode/zx' import {defer, testServerAndPage} from '../../utils/testUtils' const PATH_TO_PACKAGE = path.join(__dirname, `./package`) describe(`react18`, () => { test(`build succeeds`, async () => { cd(PATH_TO_PACKAGE) const {exitCode} = await $`npm run build` // at this point, the build should have succeeded expect(exitCode).toEqual(0) }) // this one is failing for some reason, but manually running the server works fine describe(`build`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm start -- -p ${port}` } function waitTilServerIsReady( process: ProcessPromise, port: number, ): Promise<{ url: string }> { const d = defer<{url: string}>() const url = `http://localhost:${port}` process.stdout.on('data', (chunk) => { if (chunk.includes('Accepting connections')) { // server is running now d.resolve({url}) } }) return d.promise } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('Accepting connections'), }) }) describe(`dev`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm run dev -- --port ${port}` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('Server running'), }) }) }) ================================================ FILE: compat-tests/fixtures/r3f-vite2/package/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: compat-tests/fixtures/r3f-vite2/package/index.html ================================================ Vite App
================================================ FILE: compat-tests/fixtures/r3f-vite2/package/package.json ================================================ { "private": true, "version": "0.0.0", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "dependencies": { "@react-three/drei": "^9.80.1", "@react-three/fiber": "^8.13.6", "three": "^0.155.0", "@theatre/core": "0.0.1-COMPAT.1", "@theatre/r3f": "0.0.1-COMPAT.1", "@theatre/studio": "0.0.1-COMPAT.1", "react": "^18.0.0", "react-dom": "^18.0.0" }, "devDependencies": { "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@vitejs/plugin-react": "^1.3.0", "typescript": "^4.6.3", "vite": "^2.9.9" } } ================================================ FILE: compat-tests/fixtures/r3f-vite2/package/src/App/App.tsx ================================================ import {getProject} from '@theatre/core' import React, {useEffect, useRef} from 'react' import {Canvas} from '@react-three/fiber' import {editable as e, SheetProvider, PerspectiveCamera} from '@theatre/r3f' import state from './state.json' // credit: https://codesandbox.io/s/camera-pan-nsb7f function Plane({color, theatreKey, ...props}: any) { return ( ) } export default function App() { const light2Ref = useRef() useEffect(() => { const interval = setInterval(() => { if (!light2Ref.current) return clearInterval(interval) const intensityInStateJson = 3 const currentIntensity = light2Ref.current.intensity if (currentIntensity !== intensityInStateJson) { console.error(`Test failed: light2.intensity is ${currentIntensity}`) } else { console.log(`Test passed: light2.intensity is ${intensityInStateJson}`) } }, 50) // see the note on below to understand why we're doing this }, []) return ( {/* @ts-ignore */} ) } ================================================ FILE: compat-tests/fixtures/r3f-vite2/package/src/App/state.json ================================================ { "sheetsById": { "R3F-Canvas": { "staticOverrides": { "byObject": { "Light 2": { "intensity": 3 } } } } }, "definitionVersion": "0.4.0", "revisionHistory": [ "jVNB3VWU34BIQK7M", "-NXkC2GceSVBoVqa", "Bw7ng1kdcWmMO5DN" ] } ================================================ FILE: compat-tests/fixtures/r3f-vite2/package/src/main.tsx ================================================ import ReactDOM from 'react-dom/client' import studio from '@theatre/studio' import extension from '@theatre/r3f/dist/extension' import App from './App/App' if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') { studio.extend(extension) studio.initialize({usePersistentStorage: false}) } const container = document.getElementById('root')! const root = ReactDOM.createRoot(container) root.render() ================================================ FILE: compat-tests/fixtures/r3f-vite2/package/src/vite-env.d.ts ================================================ /// ================================================ FILE: compat-tests/fixtures/r3f-vite2/package/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", "moduleResolution": "Node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, "include": ["src"], "references": [{"path": "./tsconfig.node.json"}] } ================================================ FILE: compat-tests/fixtures/r3f-vite2/package/tsconfig.node.json ================================================ { "compilerOptions": { "composite": true, "module": "esnext", "moduleResolution": "node" }, "include": ["vite.config.ts"] } ================================================ FILE: compat-tests/fixtures/r3f-vite2/package/vite.config.ts ================================================ import {defineConfig} from 'vite' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], }) ================================================ FILE: compat-tests/fixtures/r3f-vite2/vite2.compat-test.ts ================================================ // @cspotcode/zx is zx in CommonJS import {$, cd, path, ProcessPromise} from '@cspotcode/zx' import {testServerAndPage} from '../../utils/testUtils' const PATH_TO_PACKAGE = path.join(__dirname, `./package`) describe(`vite2`, () => { test(`\`$ vite build\` should succeed`, async () => { cd(PATH_TO_PACKAGE) const {exitCode} = await $`npm run build` // at this point, the build should have succeeded expect(exitCode).toEqual(0) }) describe(`vite preview`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm run preview -- --port ${port}` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('--host'), }) }) describe(`vite dev`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm run dev -- --port ${port}` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('--host'), }) }) }) ================================================ FILE: compat-tests/fixtures/r3f-vite4/package/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: compat-tests/fixtures/r3f-vite4/package/index.html ================================================ Vite App
================================================ FILE: compat-tests/fixtures/r3f-vite4/package/package.json ================================================ { "private": true, "version": "0.0.0", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "dependencies": { "@react-three/drei": "^9.80.1", "@react-three/fiber": "^8.13.6", "three": "^0.155.0", "@theatre/core": "0.0.1-COMPAT.1", "@theatre/r3f": "0.0.1-COMPAT.1", "@theatre/studio": "0.0.1-COMPAT.1", "react": "^18.0.0", "react-dom": "^18.0.0" }, "devDependencies": { "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@vitejs/plugin-react": "^1.3.0", "typescript": "^4.6.3", "vite": "^4.4.7" } } ================================================ FILE: compat-tests/fixtures/r3f-vite4/package/src/App/App.tsx ================================================ import {getProject} from '@theatre/core' import React, {useEffect, useRef} from 'react' import {Canvas} from '@react-three/fiber' import {editable as e, SheetProvider, PerspectiveCamera} from '@theatre/r3f' import state from './state.json' // credit: https://codesandbox.io/s/camera-pan-nsb7f function Plane({color, theatreKey, ...props}: any) { return ( ) } export default function App() { const light2Ref = useRef() useEffect(() => { const interval = setInterval(() => { if (!light2Ref.current) return clearInterval(interval) const intensityInStateJson = 3 const currentIntensity = light2Ref.current.intensity if (currentIntensity !== intensityInStateJson) { console.error(`Test failed: light2.intensity is ${currentIntensity}`) } else { console.log(`Test passed: light2.intensity is ${intensityInStateJson}`) } }, 50) // see the note on below to understand why we're doing this }, []) return ( {/* @ts-ignore */} ) } ================================================ FILE: compat-tests/fixtures/r3f-vite4/package/src/App/state.json ================================================ { "sheetsById": { "R3F-Canvas": { "staticOverrides": { "byObject": { "Light 2": { "intensity": 3 } } } } }, "definitionVersion": "0.4.0", "revisionHistory": [ "jVNB3VWU34BIQK7M", "-NXkC2GceSVBoVqa", "Bw7ng1kdcWmMO5DN" ] } ================================================ FILE: compat-tests/fixtures/r3f-vite4/package/src/main.tsx ================================================ import ReactDOM from 'react-dom/client' import studio from '@theatre/studio' import extension from '@theatre/r3f/dist/extension' import App from './App/App' if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') { studio.extend(extension) studio.initialize({usePersistentStorage: false}) } const container = document.getElementById('root')! const root = ReactDOM.createRoot(container) root.render() ================================================ FILE: compat-tests/fixtures/r3f-vite4/package/src/vite-env.d.ts ================================================ /// ================================================ FILE: compat-tests/fixtures/r3f-vite4/package/tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "lib": ["DOM", "DOM.Iterable", "ESNext"], "allowJs": false, "skipLibCheck": true, "esModuleInterop": false, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "ESNext", "moduleResolution": "Node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] } ================================================ FILE: compat-tests/fixtures/r3f-vite4/package/tsconfig.node.json ================================================ { "compilerOptions": { "composite": true, "module": "esnext", "moduleResolution": "node" }, "include": ["vite.config.ts"] } ================================================ FILE: compat-tests/fixtures/r3f-vite4/package/vite.config.ts ================================================ import {defineConfig} from 'vite' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], }) ================================================ FILE: compat-tests/fixtures/r3f-vite4/vite4.compat-test.ts ================================================ // @cspotcode/zx is zx in CommonJS import {$, cd, path, ProcessPromise} from '@cspotcode/zx' import {testServerAndPage} from '../../utils/testUtils' const PATH_TO_PACKAGE = path.join(__dirname, `./package`) describe(`vite4`, () => { test(`\`$ vite build\` should succeed`, async () => { cd(PATH_TO_PACKAGE) const {exitCode} = await $`npm run build` // at this point, the build should have succeeded expect(exitCode).toEqual(0) }) describe(`vite preview`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm run preview -- --port ${port}` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('--host'), }) }) describe(`vite dev`, () => { function startServerOnPort(port: number): ProcessPromise { cd(PATH_TO_PACKAGE) return $`npm run dev -- --port ${port}` } testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady: (chunk) => chunk.includes('--host'), }) }) }) ================================================ FILE: compat-tests/integrity.compat-test.ts ================================================ import * as path from 'path' import * as fs from 'fs' describe(`Compat tests`, () => { test(`all fixtures prefixed with 'r3f-' should have an App/ directory identical to that of vite4's`, async () => { const vite4AppDir = path.join( __dirname, './fixtures/r3f-vite4/package/src/App', ) const vite4FilesContents = fs .readdirSync(vite4AppDir) .map((file) => [ file, fs.readFileSync(path.join(vite4AppDir, file), 'utf-8'), ]) const allFixtures = fs .readdirSync(path.join(__dirname, './fixtures')) .filter( (fixture) => fixture !== 'r3f-vite4' && fixture.startsWith('r3f-') && // item is a folder fs .lstatSync(path.join(__dirname, './fixtures', fixture)) .isDirectory(), ) for (const fixture of allFixtures) { const appDir = path.join( __dirname, `./fixtures/${fixture}/package/src/App`, ) if (!fs.existsSync(appDir)) { throw new Error(`Fixture ${fixture} does not have an App/ directory`) } for (const [file, contents] of vite4FilesContents) { const fixtureFileContents = fs.readFileSync( path.join(appDir, file), 'utf-8', ) if (fixtureFileContents !== contents) { throw new Error( `The file ${file} in fixture ${fixture} is not identical to that of vite4's`, ) } } } }) }) ================================================ FILE: compat-tests/package.json ================================================ { "name": "@theatre/compat-tests", "private": true, "scripts": { "install-fixtures": "tsx ./scripts/install-fixtures.ts", "clean": "tsx ./scripts/clean.ts" }, "dependencies": { "@cspotcode/zx": "^6.1.2", "@verdaccio/types": "^10.8.0", "esbuild": "^0.18.18", "node-cleanup": "^2.1.2", "playwright": "^1.29.1", "prettier": "^2.6.2", "tsx": "4.7.0", "verdaccio": "^5.26.1", "verdaccio-auth-memory": "^10.2.2", "verdaccio-memory": "^10.3.2" }, "version": "0.0.1-COMPAT.1" } ================================================ FILE: compat-tests/scripts/clean.ts ================================================ import {clean} from './scripts' clean() ================================================ FILE: compat-tests/scripts/install-fixtures.ts ================================================ import {installFixtures} from './scripts' installFixtures() ================================================ FILE: compat-tests/scripts/scripts.ts ================================================ /** * Utility functions for the compatibility tests */ import * as prettier from 'prettier' import * as path from 'path' import {globby, argv, YAML, $, fs, cd, os} from '@cspotcode/zx' import onCleanup from 'node-cleanup' import startVerdaccioServer from 'verdaccio' import {defer} from '../utils/testUtils' /** * @param {string} pkg * @returns boolean */ const isTheatreDependency = (pkg) => pkg.startsWith('@theatre/') || pkg === 'theatric' const verbose = !!argv['verbose'] if (!verbose) { $.verbose = false console.log( 'Running in quiet mode. Add --verbose to see the output of all commands.', ) } const config = { VERDACCIO_PORT: 4823, VERDACCIO_HOST: `localhost`, get VERDACCIO_URL() { return `http://${config.VERDACCIO_HOST}:${config.VERDACCIO_PORT}/` }, PATH_TO_COMPAT_TESTS_ROOT: path.join(__dirname, '..'), MONOREPO_ROOT: path.join(__dirname, '../..'), } /** * Set environment variables so that yarn and npm use verdaccio as the registry. * These are only set for the current process. */ process.env.YARN_NPM_PUBLISH_REGISTRY = config.VERDACCIO_URL process.env.YARN_UNSAFE_HTTP_WHITELIST = config.VERDACCIO_HOST process.env.YARN_NPM_AUTH_IDENT = 'test:test' process.env.NPM_CONFIG_REGISTRY = config.VERDACCIO_URL const tempVersion = '0.0.1-COMPAT.' + (typeof argv['version'] === 'number' ? argv['version'].toString() : // a random integer between 1 and 50000 (Math.floor(Math.random() * 50000) + 1).toString()) const keepAlive = !!argv['keep-alive'] /** * This script starts verdaccio and publishes all the packages in the monorepo to it, then * it runs `npm install` on all the test packages, and finally it closes verdaccio. */ export async function installFixtures(): Promise { onCleanup((exitCode, signal) => { onCleanup.uninstall() restoreTestPackageJsons() process.kill(process.pid, signal) return false }) console.log( `Using temporary version: ${tempVersion} . Use --version=[NUMBER] to change.`, ) console.log('Patching package.json files in ./test-*') const restoreTestPackageJsons = await patchTestPackageJsons() console.log('Starting verdaccio') const verdaccioServer = await startVerdaccio(config.VERDACCIO_PORT) console.log(`Verdaccio is running on ${config.VERDACCIO_URL}`) console.log('Releasing @theatre/* packages to verdaccio') await releaseToVerdaccio() console.log('Running `$ npm install` on test packages') await runNpmInstallOnTestPackages() restoreTestPackageJsons() if (keepAlive) { console.log('Keeping verdaccio alive. Press Ctrl+C to exit.') // wait for ctrl+c await new Promise((resolve) => {}) } else { console.log('Closing verdaccio. Use --keep-alive to keep it running.') await verdaccioServer.close() } console.log('Done') } async function runNpmInstallOnTestPackages() { const packagePaths = await getCompatibilityTestSetups() const promises = packagePaths.map(async (pathToPackageDir) => { await fs.remove(path.join(pathToPackageDir, 'node_modules')) await fs.remove(path.join(pathToPackageDir, 'package-lock.json')) cd(path.join(pathToPackageDir, '../')) const tempPath = fs.mkdtempSync( path.join(os.tmpdir(), 'theatre-compat-test-'), ) await fs.copy(pathToPackageDir, tempPath) cd(path.join(tempPath)) try { console.log('Running npm install on ' + pathToPackageDir + '...') await $`npm install --registry ${config.VERDACCIO_URL} --loglevel ${ verbose ? 'warn' : 'error' } --fund false` console.log('npm install finished successfully in' + tempPath) await fs.move( path.join(tempPath, 'node_modules'), path.join(pathToPackageDir, 'node_modules'), ) await fs.move( path.join(tempPath, 'package-lock.json'), path.join(pathToPackageDir, 'package-lock.json'), ) } catch (error) { console.error(`Failed to install dependencies for ${pathToPackageDir} Try running \`npm install\` in that directory manually via: cd ${pathToPackageDir} npm install --registry ${config.VERDACCIO_URL} Original error: ${error}`) throw new Error(`Failed to install dependencies for ${pathToPackageDir}`) } finally { await fs.remove(tempPath) } }) const result = await Promise.allSettled(promises) const failed = result.filter((x) => x.status === 'rejected') if (failed.length > 0) { console.error( `Failed to install dependencies for the following packages:`, result .map((result, i) => [packagePaths[i], result]) .filter( ([p, result]) => // @ts-ignore result.status === 'rejected', ) .map(([path]) => path), ) } } /** * Takes an absolute path to a package.json file and replaces all of its * dependencies on `@theatre/*` packatges to `version`. * * @param {string} pathToPackageJson absolute path to the package.json file * @param {string} version The version to set all `@theatre/*` dependencies to */ async function patchTheatreDependencies(pathToPackageJson, version) { const originalFileContent = fs.readFileSync(pathToPackageJson, { encoding: 'utf-8', }) // get the package.json file's content const packageJson = JSON.parse(originalFileContent) // find all dependencies on '@theatre/*' packages and replace them with the local version for (const dependencyType of [ 'dependencies', 'devDependencies', 'peerDependencies', ]) { const dependencies = packageJson[dependencyType] if (dependencies) { for (const dependencyName of Object.keys(dependencies)) { if (isTheatreDependency(dependencyName)) { dependencies[dependencyName] = version } } } } // run the json through prettier const jsonStringPrettified = await prettier.format( JSON.stringify(packageJson, null, 2), { parser: 'json', filepath: pathToPackageJson, }, ) // write the modified package.json file fs.writeFileSync(pathToPackageJson, jsonStringPrettified, {encoding: 'utf-8'}) } async function patchTestPackageJsons(): Promise<() => void> { const packagePaths = (await getCompatibilityTestSetups()).map( (pathToPackageDir) => path.join(pathToPackageDir, 'package.json'), ) // replace all dependencies on @theatre/* packages with the local version for (const pathToPackageJson of packagePaths) { patchTheatreDependencies(pathToPackageJson, tempVersion) } return () => { // replace all dependencies on @theatre/* packages with the 0.0.1-COMPAT.1 for (const pathToPackageJson of packagePaths) { patchTheatreDependencies(pathToPackageJson, '0.0.1-COMPAT.1') } } } /** * Starts the verdaccio server and returns a promise that resolves when the serve is up and ready * * Credit: https://github.com/storybookjs/storybook/blob/92b23c080d03433765cbc7a60553d036a612a501/scripts/run-registry.ts */ async function startVerdaccio(port: number): Promise<{close: () => void}> { let resolved = false const deferred = defer<{close: () => void}>() const config = { ...YAML.parse( fs.readFileSync(path.join(__dirname, '../verdaccio.yml'), 'utf8'), ), } if (verbose) { config.logs.level = 'warn' } const cache = path.join(__dirname, '../.verdaccio-cache') config.self_path = cache startVerdaccioServer( config, '6000', cache, '1.0.0', 'verdaccio', (webServer) => { webServer.listen(port, () => { resolved = true deferred.resolve(webServer) }) }, ) await Promise.race([ deferred.promise, new Promise((_, rej) => { setTimeout(() => { rej(new Error(`TIMEOUT - verdaccio didn't start within 10s`)) }, 10000) }), ]) return deferred.promise } const packagesToPublish = [ '@theatre/core', '@theatre/studio', '@theatre/dataverse', '@theatre/react', '@theatre/browser-bundles', '@theatre/r3f', 'theatric', ] /** * Assigns a new version to each of @theatre/* packages. If there a package depends on another package in this monorepo, * this function makes sure the dependency version is fixed at "version" * * @param workspacesListObjects - An Array of objects containing information about the workspaces * @param version - Version of the latest commit (or any other string) * @returns - An async function that restores the package.json files to their original version */ async function writeVersionsToPackageJSONs( workspacesListObjects: Array<{name: string; location: string}>, version: string, ): Promise<() => void> { /** * An array of functions each of which restores a certain package.json to its original state * @type {Array<() => void>} */ const restores = [] for (const workspaceData of workspacesListObjects) { const pathToPackage = path.resolve( config.MONOREPO_ROOT, workspaceData.location, './package.json', ) const originalFileContent = fs.readFileSync(pathToPackage, { encoding: 'utf-8', }) const originalJson = JSON.parse(originalFileContent) restores.push(() => { fs.writeFileSync(pathToPackage, originalFileContent, {encoding: 'utf-8'}) }) let {dependencies, peerDependencies, devDependencies} = originalJson // Normally we don't have to override the package versions in dependencies because yarn would already convert // all the "workspace:*" versions to a fixed version before publishing. However, packages like @theatre/studio // have a peerDependency on @theatre/core set to "*" (meaning they would work with any version of @theatre/core). // This is not the desired behavior in pre-release versions, so here, we'll fix those "*" versions to the set version. for (const deps of [dependencies, peerDependencies, devDependencies]) { if (!deps) continue for (const wpObject of workspacesListObjects) { if (deps[wpObject.name]) { deps[wpObject.name] = version } } } const newJson = { ...originalJson, version, dependencies, peerDependencies, devDependencies, } fs.writeFileSync(pathToPackage, JSON.stringify(newJson, undefined, 2), { encoding: 'utf-8', }) } return () => restores.forEach((fn) => { fn() }) } /** * Builds all the @theatre/* packages with version number 0.0.1-COMPAT.1 and publishes * them all to the verdaccio registry */ async function releaseToVerdaccio() { cd(config.MONOREPO_ROOT) // @ts-ignore ignore process.env.THEATRE_IS_PUBLISHING = true const workspacesListString = await $`yarn workspaces list --json` const workspacesListObjects = workspacesListString.stdout .split(os.EOL) // strip out empty lines .filter(Boolean) .map((x) => JSON.parse(x)) const restorePackages = await writeVersionsToPackageJSONs( workspacesListObjects, tempVersion, ) // Restore the package.json files to their original state when the process is killed process.on('SIGINT', async function cleanup(a) { restorePackages() }) try { await $`yarn cli build clean` await $`yarn cli build` await Promise.all( packagesToPublish.map(async (workspaceName) => { const npmTag = 'compat' await $`yarn workspace ${workspaceName} npm publish --access public --tag ${npmTag}` }), ) } finally { restorePackages() } } /** * Get all the setups from `./compat-tests/` * * @returns An array containing the absolute paths to the compatibility test setups */ export async function getCompatibilityTestSetups(): Promise> { const fixturePackageJsonFiles = await globby( './fixtures/*/package/package.json', { cwd: config.PATH_TO_COMPAT_TESTS_ROOT, gitignore: false, onlyFiles: true, }, ) return fixturePackageJsonFiles.map((entry) => { return path.join(config.PATH_TO_COMPAT_TESTS_ROOT, entry, '../') }) } /** * Deletes ../test-*\/(node_modules|package-lock.json|yarn.lock) */ export async function clean() { const toDelete = await globby( './fixtures/*/package/(node_modules|yarn.lock|package-lock.json|.parcel-cache)', { cwd: config.PATH_TO_COMPAT_TESTS_ROOT, // node_modules et al are gitignored, but we still want to clean them gitignore: false, // include directories too onlyFiles: false, }, ) return await Promise.all( toDelete.map((fileOrDir) => { console.log('deleting', fileOrDir) return fs.remove(path.join(config.PATH_TO_COMPAT_TESTS_ROOT, fileOrDir)) }), ) } ================================================ FILE: compat-tests/tsconfig.json ================================================ { "compilerOptions": { "outDir": "dist", "lib": ["ESNext", "DOM"], "rootDir": ".", "types": ["jest", "node"], "target": "es6", "noEmit": true, "composite": true, "moduleResolution": "node", "skipLibCheck": true }, "include": [ "./scripts/*.ts", "./*.compat-test.ts", "./fixtures/*/*.compat-test.ts", "./utils/**/*.ts" ] } ================================================ FILE: compat-tests/utils/testUtils.ts ================================================ import {Browser, chromium, ConsoleMessage, devices, Page} from 'playwright' import {ProcessPromise} from '@cspotcode/zx' export function testServerAndPage({ startServerOnPort, checkServerStdoutToSeeIfItsReady, }: { startServerOnPort: (port: number) => ProcessPromise checkServerStdoutToSeeIfItsReady: (chunk: string) => boolean }) { if (checkServerStdoutToSeeIfItsReady('') !== false) { throw new Error( `Incorrect test setup. checkServerStdoutToSeeIfItsReady should return false for an empty string.`, ) } const waitTilServerIsReady = async ( process: ProcessPromise, ): Promise => { const d = defer() process.stdout.on('data', (chunk) => { if (checkServerStdoutToSeeIfItsReady(chunk.toString())) { // server is ready d.resolve() } }) await Promise.race([ d.promise, new Promise((_, reject) => setTimeout( () => reject(`Server wasn't ready after two minutes`), 1000 * 60 * 2, ), ), ]) return d.promise } let browser: Browser, page: Page beforeAll(async () => { browser = await chromium.launch() }) afterAll(async () => { await browser.close() }) beforeEach(async () => { page = await browser.newPage() }) afterEach(async () => { await page.close() }) test('The server runs, and the r3f setup works', async () => { // run the production server but don't wait for it to finish // just a random port I'm hoping is free everywhere. const port = await findOpenPort() let process: ProcessPromise | undefined try { process = startServerOnPort(port) } catch (err) { throw new Error(`Failed to start server: ${err}`) } const url = `http://localhost:${port}` try { await waitTilServerIsReady(process) await testTheatreOnPage(page, {url}) } finally { // kill the server await process.kill('SIGTERM') } try { await process } catch (e) { if (e.signal !== 'SIGTERM') { console.log('process exited with error', e) // if it exited for any reason other than us killing it, re-throw the error throw e } // otherwise, process exited because we killed it, which is what we wanted } }) } async function testTheatreOnPage(page: Page, {url}: {url: string}) { const d = defer() const processConsoleEvents = (msg: ConsoleMessage) => { const text = msg.text() if (text.startsWith('Test passed: light2.intensity')) { d.resolve('Passed') } else if (text.startsWith('Test failed: light2.intensity')) { d.reject(text) } } page.on('console', processConsoleEvents) try { await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 1000 * 60 * 2, }) // give the console listener 3 seconds to resolve, otherwise fail the test await Promise.race([ d.promise, new Promise((_, reject) => setTimeout( () => reject('Did not intercept any test-related console logs'), 30000, ), ), ]) } finally { page.off('console', processConsoleEvents) } } export function findOpenPort(): Promise { return new Promise((resolve, reject) => { const server = require('net').createServer() server.unref() server.on('error', reject) server.listen(0, () => { const {port} = server.address() as {port: number} server.close(() => { resolve(port) }) }) }) } interface Deferred { resolve: (d: PromiseType) => void reject: (d: unknown) => void promise: Promise status: 'pending' | 'resolved' | 'rejected' } /** * A simple imperative API for resolving/rejecting a promise. * * Example: * ```ts * function doSomethingAsync() { * const deferred = defer() * * setTimeout(() => { * if (Math.random() > 0.5) { * deferred.resolve('success') * } else { * deferred.reject('Something went wrong') * } * }, 1000) * * // we're just returning the promise, so that the caller cannot resolve/reject it * return deferred.promise * } * * ``` */ export function defer(): Deferred { let resolve: (d: PromiseType) => void let reject: (d: unknown) => void const promise = new Promise((rs, rj) => { resolve = (v) => { rs(v) deferred.status = 'resolved' } reject = (v) => { rj(v) deferred.status = 'rejected' } }) const deferred: Deferred = { resolve: resolve!, reject: reject!, promise, status: 'pending', } return deferred } ================================================ FILE: compat-tests/verdaccio.yml ================================================ store: memory: limit: 10000 auth: auth-memory: users: test: name: test password: test uplinks: npmjs: url: https://registry.npmjs.org/ cache: false packages: '@theatre/*': access: $all publish: $all 'theatric': access: $all publish: $all '@*/*': access: $all publish: $all proxy: npmjs '**': access: $all proxy: npmjs logs: type: stdout format: pretty level: error ================================================ FILE: credits.txt ================================================ The color picker is a fork of https://github.com/omgovich/react-colorful ================================================ FILE: devEnv/api-extractor-base.json ================================================ /** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ // "extends": "./shared/api-extractor-base.json" // "extends": "my-package/include/api-extractor-base.json" /** * Determines the "" token that can be used with other config file settings. The project folder * typically contains the tsconfig.json and package.json config files, but the path is user-defined. * * The path is resolved relative to the folder of the config file that contains the setting. * * The default value for "projectFolder" is the token "", which means the folder is determined by traversing * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error * will be reported. * * SUPPORTED TOKENS: * DEFAULT VALUE: "" */ // "projectFolder": ".", /** * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor * analyzes the symbols exported by this module. * * The file extension must be ".d.ts" and not ".ts". * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , */ "mainEntryPointFilePath": "/dist/index.d.ts", /** * A list of NPM package names whose exports should be treated as part of this package. * * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly * imports library2. To avoid this, we can specify: * * "bundledPackages": [ "library2" ], * * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been * local files for library1. */ "bundledPackages": [], /** * Determines how the TypeScript compiler engine will be invoked by API Extractor. */ "compiler": { /** * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * Note: This setting will be ignored if "overrideTsconfig" is used. * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/tsconfig.json" */ "tsconfigFilePath": "/devEnv/api-extractor.tsconfig.json" /** * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. * The object must conform to the TypeScript tsconfig schema: * * http://json.schemastore.org/tsconfig * * If omitted, then the tsconfig.json file will be read from the "projectFolder". * * DEFAULT VALUE: no overrideTsconfig section */ // "overrideTsconfig": { // "compilerOptions": { // "paths": { // } // } // } /** * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. * * DEFAULT VALUE: false */ // "skipLibCheck": true, }, /** * Configures how the API report file (*.api.md) will be generated. */ "apiReport": { /** * (REQUIRED) Whether to generate an API report. */ "enabled": false /** * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce * a full file path. * * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/". * * SUPPORTED TOKENS: , * DEFAULT VALUE: ".api.md" */ // "reportFileName": ".api.md", /** * Specifies the folder where the API report file is written. The file name portion is determined by * the "reportFileName" setting. * * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, * e.g. for an API review. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/etc/" */ // "reportFolder": "/etc/", /** * Specifies the folder where the temporary report file is written. The file name portion is determined by * the "reportFileName" setting. * * After the temporary file is written to disk, it is compared with the file in the "reportFolder". * If they are different, a production build will fail. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/temp/" */ // "reportTempFolder": "/temp/" }, /** * Configures how the doc model file (*.api.json) will be generated. */ "docModel": { /** * (REQUIRED) Whether to generate a doc model file. */ "enabled": true, /** * The output path for the doc model file. The file extension should be ".api.json". * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/temp/.api.json" */ "apiJsonFilePath": "../.temp/api/.api.json" }, /** * Configures how the .d.ts rollup file will be generated. */ "dtsRollup": { /** * (REQUIRED) Whether to generate the .d.ts rollup file. */ "enabled": false /** * Specifies the output path for a .d.ts rollup file to be generated without any trimming. * This file will include all declarations that are exported by the main entry point. * * If the path is an empty string, then this file will not be written. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/dist/.d.ts" */ // "untrimmedFilePath": "/dist/.d.ts", /** * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. * This file will include only declarations that are marked as "@public" or "@beta". * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "" */ // "betaTrimmedFilePath": "/dist/-beta.d.ts", /** * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. * This file will include only declarations that are marked as "@public". * * If the path is an empty string, then this file will not be written. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "" */ // "publicTrimmedFilePath": "/dist/-public.d.ts", /** * When a declaration is trimmed, by default it will be replaced by a code comment such as * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the * declaration completely. * * DEFAULT VALUE: false */ // "omitTrimmingComments": true }, /** * Configures how the tsdoc-metadata.json file will be generated. */ "tsdocMetadata": { /** * Whether to generate the tsdoc-metadata.json file. * * DEFAULT VALUE: true */ // "enabled": true, /** * Specifies where the TSDoc metadata file should be written. * * The path is resolved relative to the folder of the config file that contains the setting; to change this, * prepend a folder token such as "". * * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup * falls back to "tsdoc-metadata.json" in the package folder. * * SUPPORTED TOKENS: , , * DEFAULT VALUE: "" */ // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" }, /** * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. * To use the OS's default newline kind, specify "os". * * DEFAULT VALUE: "crlf" */ // "newlineKind": "crlf", /** * Configures how API Extractor reports error and warning messages produced during analysis. * * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. */ "messages": { /** * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing * the input .d.ts files. * * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" * * DEFAULT VALUE: A single "default" entry with logLevel=warning. */ "compilerMessageReporting": { /** * Configures the default routing for messages that don't match an explicit rule in this table. */ "default": { /** * Specifies whether the message should be written to the the tool's output log. Note that * the "addToApiReportFile" property may supersede this option. * * Possible values: "error", "warning", "none" * * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes * the "--local" option), the warning is displayed but the build will not fail. * * DEFAULT VALUE: "warning" */ "logLevel": "warning" /** * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), * then the message will be written inside that file; otherwise, the message is instead logged according to * the "logLevel" option. * * DEFAULT VALUE: false */ // "addToApiReportFile": false } // "TS2551": { // "logLevel": "warning", // "addToApiReportFile": true // }, // // . . . }, /** * Configures handling of messages reported by API Extractor during its analysis. * * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" * * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings */ "extractorMessageReporting": { "default": { // let's configure message reporting later "logLevel": "warning" // "addToApiReportFile": false }, "ae-missing-release-tag": { "logLevel": "none" }, "ae-forgotten-export": { "logLevel": "warning" } // "ae-extra-release-tag": { // "logLevel": "warning", // "addToApiReportFile": true // }, // // . . . }, /** * Configures handling of messages reported by the TSDoc parser when analyzing code comments. * * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" * * DEFAULT VALUE: A single "default" entry with logLevel=warning. */ "tsdocMessageReporting": { "default": { "logLevel": "warning" // "addToApiReportFile": false } // "tsdoc-link-tag-unescaped-text": { // "logLevel": "warning", // "addToApiReportFile": true // }, // // . . . } } } ================================================ FILE: devEnv/cli.ts ================================================ import sade from 'sade' import {$, fs, path, question} from '@cspotcode/zx' import * as core from '@actions/core' import * as os from 'os' const root = path.join(__dirname, '..') const prog = sade('cli').describe('CLI for Theatre.js development') // better quote function from https://github.com/google/zx/pull/167 $.quote = function quote(arg) { if (/^[a-z0-9/_.-]+$/i.test(arg)) { return arg } return ( `$'` + arg .replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(/\f/g, '\\f') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t') .replace(/\v/g, '\\v') .replace(/\0/g, '\\0') + `'` ) } const packagesToBuild = [ '@theatre/dataverse', '@theatre/saaz', '@theatre/core', '@theatre/studio', '@theatre/react', '@theatre/r3f', 'theatric', '@theatre/browser-bundles', ] prog .command( 'build clean', 'Cleans the build artifacts and output directories of all the main packages', ) .action(async () => { const packages = [...packagesToBuild] await Promise.all([ ...packages.map((workspace) => $`yarn workspace ${workspace} run clean`), ]) }) prog.command('build', 'Builds all the main packages').action(async () => { async function build() { await $`yarn run build:ts` for (const workspace of packagesToBuild) { await $`yarn workspace ${workspace} run build` } } void build() }) prog .command('release ', 'Releases all the main packages to npm') .option('--skip-ts', 'Skip emitting typescript declarations') .option('--skip-lint', 'Skip typecheck and lint') .action(async (version, opts) => { /** * This script publishes all packages to npm. * * It assigns the same version number to all packages (like lerna's fixed mode). **/ const packagesToPublish = [...packagesToBuild] /** * All these packages will have the same version from monorepo/package.json */ const packagesWhoseVersionsShouldBump = ['.', ...packagesToPublish] // our packages will check for this env variable to make sure their // prepublish script is only called from the `$ cd /path/to/monorepo; yarn run release` // @ts-ignore ignore process.env.THEATRE_IS_PUBLISHING = true async function release() { $.verbose = false const gitTags = (await $`git tag --list`).toString().split('\n') if (typeof version !== 'string') { console.error( `You need to specify a version, like: $ yarn cli release 1.2.0-rc.4`, ) process.exit(1) } else if ( !version.match(/^[0-9]+\.[0-9]+\.[0-9]+(\-(dev|rc)\.[0-9]+)?$/) ) { console.error( `Use a semver version, like 1.2.3-rc.4. Provided: ${version}`, ) process.exit(1) } const previousVersion = require('../package.json').version if (version === previousVersion) { console.error( `Version ${version} is already assigned to root/package.json`, ) process.exit(1) } if (gitTags.some((tag) => tag === version)) { console.error(`There is already a git tag for version ${version}`) process.exit(1) } let npmTag = 'latest' if (version.match(/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/)) { console.log('npm tag: latest') } else { const matches = version.match( /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\-(dev|rc|beta)\.[0-9]{1,3}$/, ) if (!matches) { console.log( 'Invalid version. Currently xx.xx.xx or xx.xx.xx-(dev|rc|beta).xx is allowed', ) process.exit(1) } npmTag = matches[1] console.log('npm tag: ' + npmTag) } if ((await $`git status -s`).toString().length > 0) { console.error(`Git working directory contains uncommitted changes:`) $.verbose = true await $`git status -s` console.log('Commit/stash them and try again.') process.exit(1) } $.verbose = true if (opts['skip-lint'] !== true) { console.log('Running a typecheck and lint pass') await Promise.all([$`yarn run typecheck`, $`yarn run lint:all`]) } else { console.log('Skipping typecheck and lint') } const skipTypescriptEmit = opts['skip-ts'] === true console.log('Assigning versions') await writeVersionsToPackageJSONs(version) console.log('Building all packages') await Promise.all( packagesToBuild.map((workspace) => skipTypescriptEmit ? $`yarn workspace ${workspace} run build:js` : $`yarn workspace ${workspace} run build`, ), ) // temporarily rolling back the version assignments to make sure they don't show // up in `$ git status`. (would've been better to just ignore hese particular changes // but i'm lazy) await restoreVersions() console.log( 'Checking if the build produced artifacts that must first be comitted to git', ) $.verbose = false if ((await $`git status -s`).toString().length > 0) { $.verbose = true await $`git status -s` console.error(`Git directory contains uncommitted changes.`) process.exit(1) } $.verbose = true await writeVersionsToPackageJSONs(version) console.log('Committing/tagging') await $`git add .` await $`git commit -m ${version}` await $`git tag ${version}` // if (!gitTags.some((tag) => tag === version)) { // console.log( // `No git tag found for version "${version}". Run \`$ git tag ${version}\` and try again.`, // ) // process.exit() // } console.log('Publishing to npm') await Promise.all( packagesToPublish.map( (workspace) => $`yarn workspace ${workspace} npm publish --access public --tag ${npmTag}`, ), ) } void release() async function writeVersionsToPackageJSONs(monorepoVersion: string) { for (const packagePathRelativeFromRoot of packagesWhoseVersionsShouldBump) { const pathToPackage = path.resolve( __dirname, '../', packagePathRelativeFromRoot, './package.json', ) const original = JSON.parse( fs.readFileSync(pathToPackage, {encoding: 'utf-8'}), ) const newJson = {...original, version: monorepoVersion} fs.writeFileSync( path.join(pathToPackage), JSON.stringify(newJson, undefined, 2), {encoding: 'utf-8'}, ) await $`prettier --write ${ packagePathRelativeFromRoot + '/package.json' }` } } async function restoreVersions() { const wasVerbose = $.verbose $.verbose = false for (const packagePathRelativeFromRoot of packagesWhoseVersionsShouldBump) { const pathToPackageInGit = packagePathRelativeFromRoot + '/package.json' await $`git checkout ${pathToPackageInGit}` } $.verbose = wasVerbose } }) prog .command( 'prerelease ci', "This script publishes the insider packages from the CI. You can't run it locally unless you have a a valid npm access token and you store its value in the `NPM_TOKEN` environmental variable.", ) .action(async () => { const packagesToPublish = [ '@theatre/core', '@theatre/studio', '@theatre/dataverse', '@theatre/saaz', '@theatre/react', '@theatre/browser-bundles', '@theatre/r3f', 'theatric', ] /** * Receives a version number and returns it without the tags, if there are any * * @param version - Version number * @returns Version number without the tags * * @example * ```javascript * const version_1 = '0.4.8-dev3-ec175817' * const version_2 = '0.4.8' * * stripTag(version_1) === stripTag(version_2) === '0.4.8' // returns `true` * ``` */ function stripTag(version: string) { const regExp = /^[0-9]+\.[0-9]+\.[0-9]+/g const matches = version.match(regExp) if (!matches) { throw new Error(`Version number not found in "${version}"`) } return matches[0] } /** * Creates a version number like `0.4.8-insiders.ec175817` * * @param packageName - Name of the package * @param commitHash - A commit hash */ function getNewVersionName(packageName: string, commitHash: string) { // The `r3f` package has its own release schedule, so its version numbers // are almost always different from the rest of the packages. const pathToPackageJson = packageName === '@theatre/r3f' ? path.resolve(__dirname, '../', 'packages', 'r3f', 'package.json') : path.resolve(__dirname, '../', './package.json') const jsonData = JSON.parse( fs.readFileSync(pathToPackageJson, {encoding: 'utf-8'}), ) const strippedVersion = stripTag(jsonData.version) return `${strippedVersion}-insiders.${commitHash}` } /** * Assigns the latest version names ({@link getNewVersionName}) to the packages' `package.json`s * * @param workspacesListObjects - An Array of objects containing information about the workspaces * @param latestCommitHash - Hash of the latest commit * @returns - A record of `{[packageId]: assignedVersion}` */ async function writeVersionsToPackageJSONs( workspacesListObjects: {name: string; location: string}[], latestCommitHash: string, ): Promise> { const assignedVersionByPackageName: Record = {} for (const workspaceData of workspacesListObjects) { const pathToPackage = path.resolve( __dirname, '../', workspaceData.location, './package.json', ) const original = JSON.parse( fs.readFileSync(pathToPackage, {encoding: 'utf-8'}), ) let {version, dependencies, peerDependencies, devDependencies} = original // The @theatre/r3f package curently doesn't track the same version number of the other packages like @theatre/core, // so we need to generate version numbers independently for each package version = getNewVersionName(workspaceData.name, latestCommitHash) assignedVersionByPackageName[workspaceData.name] = version // Normally we don't have to override the package versions in dependencies because yarn would already convert // all the "workspace:*" versions to a fixed version before publishing. However, packages like @theatre/studio // have a peerDependency on @theatre/core set to "*" (meaning they would work with any version of @theatre/core). // This is not the desired behavior in pre-release versions, so here, we'll fix those "*" versions to the set version. for (const deps of [dependencies, peerDependencies, devDependencies]) { if (!deps) continue for (const wpObject of workspacesListObjects) { if (deps[wpObject.name]) { deps[wpObject.name] = getNewVersionName( wpObject.name, latestCommitHash, ) } } } const newJson = { ...original, version, dependencies, peerDependencies, devDependencies, } fs.writeFileSync( path.join(pathToPackage), JSON.stringify(newJson, undefined, 2), {encoding: 'utf-8'}, ) await $`prettier --write ${workspaceData.location + '/package.json'}` } return assignedVersionByPackageName } async function prerelease() { // @ts-ignore ignore process.env.THEATRE_IS_PUBLISHING = true // In the CI `git log -1` points to a fake merge commit, // so we have to use the value of a special GitHub context variable // through the `GITHUB_SHA` environmental variable. // The length of the abbreviated commit hash can change, that's why we // need the length of the fake merge commit's abbreviated hash. const fakeMergeCommitHashLength = (await $`git log -1 --pretty=format:%h`) .stdout.length if (!process.env.GITHUB_SHA) throw new Error( 'expected `process.env.GITHUB_SHA` to be defined but it was not', ) const latestCommitHash = process.env.GITHUB_SHA.slice( 0, fakeMergeCommitHashLength, ) const workspacesListString = await $`yarn workspaces list --json` const workspacesListObjects = workspacesListString.stdout .split(os.EOL) // strip out empty lines .filter(Boolean) .map((x) => JSON.parse(x)) const assignedVersionByPackageName = await writeVersionsToPackageJSONs( workspacesListObjects, latestCommitHash, ) await Promise.all( packagesToPublish.map(async (workspaceName) => { const npmTag = 'insiders' if (process.env.GITHUB_ACTIONS) { await $`yarn workspace ${workspaceName} npm publish --access public --tag ${npmTag}` } }), ) if (process.env.GITHUB_ACTIONS) { const data = packagesToPublish.map((packageName) => ({ packageName, version: assignedVersionByPackageName[packageName], })) // set the output for github actions. core.setOutput('data', JSON.stringify(data)) } else { for (const packageName of packagesToPublish) { await $`echo ${`Published ${packageName}@${assignedVersionByPackageName[packageName]}`}` } } } void prerelease() }) { const allDevCommands = [ `yarn workspace playground run serve`, `yarn workspace @theatre/app run cli dev all`, `yarn workspace @theatre/sync-server run cli dev all`, ] prog .command('dev all', 'Starts all services to develop all of the packages') .action(async () => { await Promise.all(allDevCommands.map((cmd) => $`${cmd}`)) }) prog .command( 'tmux ', 'A helper command to start all the development services in a tmux session', ) .option('--kill', 'If a session by that name already exists, kill it first') .action(async (session = 'theatre', opts: {kill?: boolean}) => { // check if the session already exists if (opts.kill) { try { await $`tmux kill-session -t ${session}` } catch {} console.log('starting a new tmux session') } else { console.log( 'starting a new tmux session or attaching to an existing one', ) } await $`tmux new-session -d -A -s ${session}` for (const cmd of allDevCommands) { await $`tmux send-keys -t ${session} ${cmd} C-m` await $`tmux split-window -t ${session}` } console.log('to attach to the session, run:') console.log(`tmux attach -t ${session}`) console.log( 'to attach to the session in control mode (so for example you can control tmux via iTerm), run:', ) console.log(`tmux -CC attach -t ${session}`) await question('Press enter to kill the session') await $`tmux kill-session -t ${session}` }) } prog .command(`depcheck`, `Check for unused or unlisted dependencies`) .action(async () => { /** * A handy utility to check for unused or unlisted dependencies. * We could also include this in the CI checks, but the current config reports some false positives. Once that's fixed, we can add it to the CI (low priority). */ await $`yarn knip --config ./knip.config.json --include unlisted,dependencies --no-exit-code` }) prog.parse(process.argv) ================================================ FILE: devEnv/ensurePublishing.js ================================================ if (process.env.THEATRE_IS_PUBLISHING !== 'true') { throw Error( `This script may run only when the "release" command in monorepo's root is running.`, ) } ================================================ FILE: devEnv/eslint/rules/no-relative-imports.js ================================================ const path = require('path') const minimatch = require('minimatch') // @todo find and acknowledge the original source of this (it was a github gist) // @todo this rule is clearly ignoring a bunch of files that do have parent // relative paths but don't get caught. const replace = { meta: { type: 'layout', fixable: 'code', schema: [ { type: 'object', properties: { ignore: { type: 'array', items: { type: 'string', }, }, method: { type: 'string', enum: ['all', 'only-parent'], }, aliases: { type: 'array', minItems: 1, items: { properties: { name: { type: 'string', }, path: { type: 'string', }, }, required: ['name', 'path'], additionalProperties: false, }, }, }, required: ['aliases'], additionalProperties: false, }, ], messages: { 'can-replace': 'Relative import found. Run autofix to replace these relative imports!', 'cannot-replace': 'Relative import found. No alias has been defined which matches this import path', }, }, create: (context) => { return { ImportDeclaration: (node) => { const config = getConfiguration(context) if ( config.ignorePatterns.some((pattern) => minimatch(context.getFilename(), pattern), ) ) { return } evaluateImport(node, config, context) }, } }, } function evaluateImport(node, config, context) { const { source: {value, range, loc}, } = node if (value.startsWith('./') && config.replaceMethod === 'only-parent') { return } else if (!value.startsWith('../')) { return } let canFix = false const currentFileDirectory = path.dirname(context.getFilename()) for (const alias of config.replaceAliases) { const {replaceDir, replaceWith} = alias const fullImportPath = path.resolve(currentFileDirectory, value) if (fullImportPath.startsWith(replaceDir)) { canFix = true context.report({ messageId: 'can-replace', loc, fix: (fixer) => fixer.replaceTextRange( range, `'${fullImportPath.replace(replaceDir, replaceWith)}'`, ), }) break } } if (!canFix) { context.report({ messageId: 'cannot-replace', loc, }) } } function getConfiguration(context) { const options = { ignore: [], method: 'only-parent', ...context.options[0], } return { ignorePatterns: options.ignore, replaceMethod: options.method, replaceAliases: options.aliases.map((alias) => ({ replaceDir: alias.path, replaceWith: alias.name, })), } } module.exports = replace ================================================ FILE: devEnv/getAliasesFromTsConfig.d.ts ================================================ export function getAliasesFromTsConfigForJest(): Record export function getAliasesFromTsConfigForRollup(): Array<{ find: RegExp replacement: string }> ================================================ FILE: devEnv/getAliasesFromTsConfig.js ================================================ const path = require('path') const monorepoRoot = path.resolve(__dirname, '../') function getAliasesFromTsConfigForESLint() { const tsConfigPaths = require('../tsconfig.base.json').compilerOptions.paths const aliases = [] for (const [key, value] of Object.entries(tsConfigPaths)) { aliases.push({ name: key.replace(/\/\*$/, ''), path: value[0].replace(/\/\*$/, ''), }) } return aliases } module.exports.getAliasesFromTsConfigForESLint = getAliasesFromTsConfigForESLint function getAliasesFromTsConfigForJest() { const tsConfigPaths = require('../tsconfig.base.json').compilerOptions.paths const aliases = {} for (let [key, value] of Object.entries(tsConfigPaths)) { if (key.match(/\/\*$/)) { key = key.replace(/\/\*$/, '/(.*)') } else { key = key + '$' } aliases[key] = path.join('', value[0].replace(/\/\*$/, '/$1')) } return aliases } module.exports.getAliasesFromTsConfigForJest = getAliasesFromTsConfigForJest function getAliasesFromTsConfigForRollup() { const tsConfigPaths = require('../tsconfig.base.json').compilerOptions.paths const aliases = [] for (let [key, value] of Object.entries(tsConfigPaths)) { // like '@theatre/core/*' if (key.match(/\/\*$/)) { key = key.replace(/\/\*$/, '/(.*)') } else { // like '@theatre/core' key = key + '$' } aliases.push({ find: new RegExp(key), replacement: path.join(monorepoRoot, value[0].replace(/\/\*$/, '/$1')), }) } return aliases } module.exports.getAliasesFromTsConfigForRollup = getAliasesFromTsConfigForRollup ================================================ FILE: devEnv/tsconfig.json ================================================ { "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "dist", "lib": ["ESNext", "DOM"], "rootDir": ".", "types": ["node", "jest"], "noEmit": true, "target": "es6", "composite": true }, "references": [{"path": "../packages/app"}] } ================================================ FILE: devEnv/typecheck-all-projects/tsconfig.all.json ================================================ { "extends": "../../tsconfig.base.json", "compilerOptions": { "composite": true, "rootDir": "../.." }, "files": [], "references": [ {"path": "../../packages/dataverse"}, {"path": "../../packages/playground"}, {"path": "../../packages/benchmarks"}, {"path": "../../packages/react"}, {"path": "../../packages/r3f"}, {"path": "../../examples/basic-dom"}, {"path": "../../compat-tests"}, {"path": "../../devEnv"}, {"path": "../../packages/app"}, {"path": "../../packages/core"}, {"path": "../../packages/studio"}, {"path": "../../packages/sync-server"}, {"path": "../../packages/saaz"}, {"path": "../../packages/utils"} ] } ================================================ FILE: devEnv/verify-docker-compose.test.ts ================================================ import * as fs from 'fs' import * as path from 'path' import * as yaml from 'yaml' describe(`Docker-compose`, () => { test(`should exclude all node_modules folders`, () => { const dockerComposeFile = fs.readFileSync( path.join(__dirname, '../docker-compose.yml'), {encoding: 'utf8'}, ) const yamlContent = yaml.parse(dockerComposeFile) const dockerVolumes = yamlContent.services['node'].volumes const dockerVolumesThatExludeNodeModules = dockerVolumes.filter( (volume: string) => volume.includes('node_modules'), ) const allFoldersToExclude = findAllNodejsFoldersAt( path.join(__dirname, '..'), ).map((fullPath) => { return path.join( '/app', path.relative(path.join(__dirname, '..'), fullPath), ) }) const missingExclusions = allFoldersToExclude.filter( (folder) => !dockerVolumesThatExludeNodeModules.includes(folder), ) if (missingExclusions.length > 0) { throw new Error( `Some node_modules folders are not excluded from docker-compose.yml. You should add them to the voluems section of the node service:\n${missingExclusions .map((s) => '- ' + s) .join('\n')}`, ) } }) }) function findAllNodejsFoldersAt(dir: string): string[] { const files = fs.readdirSync(dir) const found: string[] = [] for (const file of files) { if (file === 'package.json') { found.push(path.join(dir, 'node_modules')) } else if (file !== 'node_modules') { const filePath = path.join(dir, file) const stats = fs.statSync(filePath) if (stats.isDirectory()) { found.push(...findAllNodejsFoldersAt(filePath)) } } } return found } ================================================ FILE: docker-compose.yml ================================================ # The docker-compose file is only used to *test* the repo on a local linux vm. You don't have # to use docker or know docker to develop the repo. version: '3.8' name: theatre-monorepo services: node: image: mcr.microsoft.com/playwright:v1.40.0-jammy volumes: - .:/app # This ignores all node_modules folders/sub-folders so that we can have a separate installation # of node_modules in host and in the container. # If a folder is missing, the test at devEnv/verify-docker-compose.test.ts will fail, and it'll # tell you which folder(s) are missing. - /app/node_modules - /app/compat-tests/fixtures/basic-react17/package/node_modules - /app/compat-tests/fixtures/r3f-cra/package/node_modules - /app/compat-tests/fixtures/r3f-next-latest/package/.next/node_modules - /app/compat-tests/fixtures/r3f-next-latest/package/.next/types/node_modules - /app/compat-tests/fixtures/r3f-next-latest/package/node_modules - /app/compat-tests/fixtures/r3f-parcel1/package/node_modules - /app/compat-tests/fixtures/r3f-react18/package/node_modules - /app/compat-tests/fixtures/r3f-vite2/package/node_modules - /app/compat-tests/fixtures/r3f-vite4/package/node_modules - /app/compat-tests/fixtures/basic-vite4/package/node_modules - /app/compat-tests/node_modules - /app/examples/basic-dom/node_modules - /app/examples/dom-cra/node_modules - /app/examples/r3f-cra/node_modules - /app/packages/benchmarks/node_modules - /app/packages/browser-bundles/node_modules - /app/packages/dataverse/node_modules - /app/packages/dataverse-experiments/node_modules - /app/packages/playground/node_modules - /app/packages/r3f/node_modules - /app/packages/react/node_modules - /app/packages/theatric/node_modules - /app/packages/core/node_modules - /app/packages/studio/node_modules - /app/packages/sync-server/node_modules - /app/packages/sync-server/prisma/client-generated/node_modules - /app/packages/app/node_modules - /app/packages/app/prisma/client-generated/node_modules - /app/packages/app/.next/node_modules - /app/packages/saaz/node_modules - /app/packages/utils/node_modules - /app/packages/app/.next/types/node_modules command: ['bash', '-c', 'while true; do sleep 1; done'] ================================================ FILE: examples/basic-dom/.gitignore ================================================ /.cache ================================================ FILE: examples/basic-dom/Scene.tsx ================================================ import type {IScrub} from '@theatre/core' import React, {useLayoutEffect, useMemo, useState} from 'react' import type {ISheet, ISheetObject, IProject} from '@theatre/core' import type {UseDragOpts} from './useDrag' import useDrag from './useDrag' import theatre from '@theatre/core' void theatre.init({studio: true}) const boxObjectConfig = { x: 0, y: 0, } const Box: React.FC<{ id: string sheet: ISheet selectedObject: ISheetObject | undefined }> = ({id, sheet, selectedObject}) => { // This is cheap to call and always returns the same value, so no need for useMemo() const obj = sheet.object(id, boxObjectConfig) const isSelected = selectedObject === obj const [pos, setPos] = useState<{x: number; y: number}>({x: 0, y: 0}) useLayoutEffect(() => { const unsubscribeFromChanges = obj.onValuesChange((newValues) => { setPos(newValues) }) return unsubscribeFromChanges }, [id]) const [divRef, setDivRef] = useState(null) const dragOpts = useMemo((): UseDragOpts => { let scrub: IScrub | undefined let initial: typeof obj.value let firstOnDragCalled = false return { onDragStart() { const studio = theatre.getStudioSync()! scrub = studio.scrub() initial = obj.value firstOnDragCalled = false }, onDrag(x, y) { if (!firstOnDragCalled) { const studio = theatre.getStudioSync()! studio.setSelection([obj]) firstOnDragCalled = true } scrub!.capture(({set}) => { set(obj.props, {x: x + initial.x, y: y + initial.y}) }) }, onDragEnd(dragHappened) { if (dragHappened) { scrub!.commit() } else { scrub!.discard() } }, lockCursorTo: 'move', } }, []) useDrag(divRef, dragOpts) return (
{ const studio = theatre.getStudioSync()! studio.setSelection([obj]) }} ref={setDivRef} style={{ width: 100, height: 100, background: 'gray', position: 'absolute', left: pos.x + 'px', top: pos.y + 'px', boxSizing: 'border-box', border: isSelected ? '1px solid #5a92fa' : '1px solid transparent', }} >
) } let lastBoxId = 1 export const Scene: React.FC<{project: IProject}> = ({project}) => { const [boxes, setBoxes] = useState>(['0', '1']) // This is cheap to call and always returns the same value, so no need for useMemo() const sheet = project.sheet('Scene', 'default') const [selection, _setSelection] = useState>([]) useLayoutEffect(() => { const studio = theatre.getStudioSync()! return studio.onSelectionChange((newSelection) => { _setSelection( newSelection.filter( (s): s is ISheetObject => s.type === 'Theatre_SheetObject_PublicAPI', ), ) }) }) return (
{boxes.map((id) => ( ))}
) } ================================================ FILE: examples/basic-dom/index.html ================================================ Theatre.js Example - DOM
================================================ FILE: examples/basic-dom/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import theatre, {getProject} from '@theatre/core' import {Scene} from './Scene' void theatre.init({studio: true}) ReactDOM.createRoot(document.getElementById('root')!).render( , ) ================================================ FILE: examples/basic-dom/package.json ================================================ { "name": "@examples/basic-dom", "scripts": { "start": "parcel serve ./index.html" }, "dependencies": { "@theatre/core": "workspace:*", "@theatre/studio": "workspace:*", "@types/react": "^17.0.19", "@types/react-dom": "^17.0.9", "parcel-bundler": "^1.12.5", "react": "^17.0.2", "react-dom": "^17.0.2", "typescript": "5.1.6" } } ================================================ FILE: examples/basic-dom/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "compilerOptions": { "esModuleInterop": true, "jsx": "react", "skipDefaultLibCheck": true, "skipLibCheck": true, "composite": true, "noEmit": true }, "files": ["index.tsx", "Scene.tsx", "useDrag.ts"], "references": [ {"path": "../../packages/core"}, {"path": "../../packages/studio"} ] } ================================================ FILE: examples/basic-dom/useDrag.ts ================================================ import {useLayoutEffect, useRef} from 'react' const noop = () => {} function createCursorLock(cursor: string) { const el = document.createElement('div') el.style.cssText = ` position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 9999999;` el.style.cursor = cursor document.body.appendChild(el) const relinquish = () => { document.body.removeChild(el) } return relinquish } export type UseDragOpts = { disabled?: boolean dontBlockMouseDown?: boolean lockCursorTo?: string onDragStart?: (event: MouseEvent) => void | false onDragEnd?: (dragHappened: boolean) => void onDrag: (dx: number, dy: number, event: MouseEvent) => void } /** * @deprecated Deprecated in favor of `useChordial` */ export default function useDrag( target: HTMLElement | undefined | null, opts: UseDragOpts, ) { const optsRef = useRef(opts) optsRef.current = opts const modeRef = useRef<'dragStartCalled' | 'dragging' | 'notDragging'>( 'notDragging', ) const stateRef = useRef<{ dragHappened: boolean startPos: { x: number y: number } }>({dragHappened: false, startPos: {x: 0, y: 0}}) useLayoutEffect(() => { if (!target) return const getDistances = (event: MouseEvent): [number, number] => { const {startPos} = stateRef.current return [event.screenX - startPos.x, event.screenY - startPos.y] } let relinquishCursorLock = noop const dragHandler = (event: MouseEvent) => { if (!stateRef.current.dragHappened && optsRef.current.lockCursorTo) { relinquishCursorLock = createCursorLock(optsRef.current.lockCursorTo) } if (!stateRef.current.dragHappened) stateRef.current.dragHappened = true modeRef.current = 'dragging' const deltas = getDistances(event) optsRef.current.onDrag(deltas[0], deltas[1], event) } const dragEndHandler = () => { removeDragListeners() modeRef.current = 'notDragging' optsRef.current.onDragEnd && optsRef.current.onDragEnd(stateRef.current.dragHappened) relinquishCursorLock() relinquishCursorLock = noop } const addDragListeners = () => { document.addEventListener('mousemove', dragHandler) document.addEventListener('mouseup', dragEndHandler) } const removeDragListeners = () => { document.removeEventListener('mousemove', dragHandler) document.removeEventListener('mouseup', dragEndHandler) } const preventUnwantedClick = (event: MouseEvent) => { if (optsRef.current.disabled) return if (stateRef.current.dragHappened) { if ( !optsRef.current.dontBlockMouseDown && modeRef.current !== 'notDragging' ) { event.stopPropagation() event.preventDefault() } stateRef.current.dragHappened = false } } const dragStartHandler = (event: MouseEvent) => { const opts = optsRef.current if (opts.disabled === true) return if (event.button !== 0) return const resultOfStart = opts.onDragStart && opts.onDragStart(event) if (resultOfStart === false) return if (!opts.dontBlockMouseDown) { event.stopPropagation() event.preventDefault() } modeRef.current = 'dragStartCalled' const {screenX, screenY} = event stateRef.current.startPos = {x: screenX, y: screenY} stateRef.current.dragHappened = false addDragListeners() } const onMouseDown = (e: MouseEvent) => { dragStartHandler(e) } target.addEventListener('mousedown', onMouseDown) target.addEventListener('click', preventUnwantedClick) return () => { removeDragListeners() target.removeEventListener('mousedown', onMouseDown) target.removeEventListener('click', preventUnwantedClick) relinquishCursorLock() if (modeRef.current !== 'notDragging') { optsRef.current.onDragEnd && optsRef.current.onDragEnd(modeRef.current === 'dragging') } modeRef.current = 'notDragging' } }, [target]) } ================================================ FILE: examples/dom-cra/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: examples/dom-cra/README.md ================================================ # DOM CRA Example Example of using basic `theatre` with [Create React App](https://github.com/facebook/create-react-app). ================================================ FILE: examples/dom-cra/package.json ================================================ { "name": "@examples/dom-cra", "version": "0.1.0", "private": true, "installConfig": { "hoistingLimits": "dependencies" }, "dependencies": { "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@theatre/core": "workspace:*", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "4.0.3", "web-vitals": "^1.0.1" }, "devDependencies": { "@theatre/studio": "workspace:*" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } ================================================ FILE: examples/dom-cra/public/index.html ================================================ React App
================================================ FILE: examples/dom-cra/public/manifest.json ================================================ { "short_name": "React App", "name": "Create React App Sample", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "logo192.png", "type": "image/png", "sizes": "192x192" }, { "src": "logo512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: examples/dom-cra/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: examples/dom-cra/src/App.css ================================================ .App { text-align: center; } .App-logo { height: 40vmin; pointer-events: none; } @media (prefers-reduced-motion: no-preference) { .App-logo { animation: App-logo-spin infinite 20s linear; } } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ================================================ FILE: examples/dom-cra/src/App.js ================================================ import studio from '@theatre/studio' import {useLayoutEffect, useMemo, useState} from 'react' import useDrag from './useDrag' studio.initialize() const boxObjectConfig = { x: 0, y: 0, } const Box = ({id, sheet, selectedObject}) => { // This is cheap to call and always returns the same value, so no need for useMemo() const obj = sheet.object(id, boxObjectConfig) const isSelected = selectedObject === obj const [pos, setPos] = useState({x: 0, y: 0}) useLayoutEffect(() => { const unsubscribeFromChanges = obj.onValuesChange((newValues) => { setPos(newValues) }) return unsubscribeFromChanges }, [id, obj]) const [divRef, setDivRef] = useState(null) const dragOpts = useMemo(() => { let scrub let initial let firstOnDragCalled = false return { onDragStart() { scrub = studio.scrub() initial = obj.value firstOnDragCalled = false }, onDrag(x, y) { if (!firstOnDragCalled) { studio.setSelection([obj]) firstOnDragCalled = true } scrub.capture(({set}) => { set(obj.props, {x: x + initial.x, y: y + initial.y}) }) }, onDragEnd(dragHappened) { if (dragHappened) { scrub.commit() } else { scrub.discard() } }, lockCursorTo: 'move', } }, []) useDrag(divRef, dragOpts) return (
{ studio.setSelection([obj]) }} ref={setDivRef} style={{ width: 100, height: 100, background: 'gray', position: 'absolute', left: pos.x + 'px', top: pos.y + 'px', boxSizing: 'border-box', border: isSelected ? '1px solid #5a92fa' : '1px solid transparent', }} >
) } let lastBoxId = 1 const App = ({project}) => { const [boxes, setBoxes] = useState(['0', '1']) // This is cheap to call and always returns the same value, so no need for useMemo() const sheet = project.sheet('Scene', 'default') const [selection, _setSelection] = useState([]) useLayoutEffect(() => { return studio.onSelectionChange((newSelection) => { _setSelection(newSelection) }) }) return (
{boxes.map((id) => ( ))}
) } export default App ================================================ FILE: examples/dom-cra/src/App.test.js ================================================ import {render, screen} from '@testing-library/react' test('renders learn react link', () => { render() const linkElement = screen.getByText(/learn react/i) expect(linkElement).toBeInTheDocument() }) ================================================ FILE: examples/dom-cra/src/index.css ================================================ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } ================================================ FILE: examples/dom-cra/src/index.js ================================================ import ReactDOM from 'react-dom' import './index.css' import reportWebVitals from './reportWebVitals' import studio from '@theatre/studio' import {getProject} from '@theatre/core' import React from 'react' import App from './App' studio.initialize() ReactDOM.render( , document.getElementById('root'), ) // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals() ================================================ FILE: examples/dom-cra/src/reportWebVitals.js ================================================ const reportWebVitals = (onPerfEntry) => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => { getCLS(onPerfEntry) getFID(onPerfEntry) getFCP(onPerfEntry) getLCP(onPerfEntry) getTTFB(onPerfEntry) }) } } export default reportWebVitals ================================================ FILE: examples/dom-cra/src/setupTests.js ================================================ // jest-dom adds custom jest matchers for asserting on DOM nodes. // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom' ================================================ FILE: examples/dom-cra/src/useDrag.js ================================================ import {useLayoutEffect, useRef} from 'react' const noop = () => {} function createCursorLock(cursor) { const el = document.createElement('div') el.style.cssText = ` position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 9999999;` el.style.cursor = cursor document.body.appendChild(el) const relinquish = () => { document.body.removeChild(el) } return relinquish } export default function useDrag(target, opts) { const optsRef = useRef(opts) optsRef.current = opts const modeRef = useRef('notDragging') const stateRef = useRef({dragHappened: false, startPos: {x: 0, y: 0}}) useLayoutEffect(() => { if (!target) return const getDistances = (event) => { const {startPos} = stateRef.current return [event.screenX - startPos.x, event.screenY - startPos.y] } let relinquishCursorLock = noop const dragHandler = (event) => { if (!stateRef.current.dragHappened && optsRef.current.lockCursorTo) { relinquishCursorLock = createCursorLock(optsRef.current.lockCursorTo) } if (!stateRef.current.dragHappened) stateRef.current.dragHappened = true modeRef.current = 'dragging' const deltas = getDistances(event) optsRef.current.onDrag(deltas[0], deltas[1], event) } const dragEndHandler = () => { removeDragListeners() modeRef.current = 'notDragging' optsRef.current.onDragEnd && optsRef.current.onDragEnd(stateRef.current.dragHappened) relinquishCursorLock() relinquishCursorLock = noop } const addDragListeners = () => { document.addEventListener('mousemove', dragHandler) document.addEventListener('mouseup', dragEndHandler) } const removeDragListeners = () => { document.removeEventListener('mousemove', dragHandler) document.removeEventListener('mouseup', dragEndHandler) } const preventUnwantedClick = (event) => { if (optsRef.current.disabled) return if (stateRef.current.dragHappened) { if ( !optsRef.current.dontBlockMouseDown && modeRef.current !== 'notDragging' ) { event.stopPropagation() event.preventDefault() } stateRef.current.dragHappened = false } } const dragStartHandler = (event) => { const opts = optsRef.current if (opts.disabled === true) return if (event.button !== 0) return const resultOfStart = opts.onDragStart && opts.onDragStart(event) if (resultOfStart === false) return if (!opts.dontBlockMouseDown) { event.stopPropagation() event.preventDefault() } modeRef.current = 'dragStartCalled' const {screenX, screenY} = event stateRef.current.startPos = {x: screenX, y: screenY} stateRef.current.dragHappened = false addDragListeners() } const onMouseDown = (e) => { dragStartHandler(e) } target.addEventListener('mousedown', onMouseDown) target.addEventListener('click', preventUnwantedClick) return () => { removeDragListeners() target.removeEventListener('mousedown', onMouseDown) target.removeEventListener('click', preventUnwantedClick) relinquishCursorLock() if (modeRef.current !== 'notDragging') { optsRef.current.onDragEnd && optsRef.current.onDragEnd(modeRef.current === 'dragging') } modeRef.current = 'notDragging' } }, [target]) } ================================================ FILE: examples/dom-cra/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "dist", "lib": ["ESNext", "DOM"], "rootDir": "src", "types": ["jest", "node"], "emitDeclarationOnly": true, "composite": true, "checkJs": true }, "references": [ {"path": "../../packages/core"}, {"path": "../../packages/studio"} ], "include": ["./src/**/*"] } ================================================ FILE: examples/r3f-cra/.eslintrc.json ================================================ { "extends": [ "react-app" ] } ================================================ FILE: examples/r3f-cra/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: examples/r3f-cra/README.md ================================================ # R3F CRA Example Example of using `theatre` and `@react-three/fiber` with [Create React App](https://github.com/facebook/create-react-app). ================================================ FILE: examples/r3f-cra/package.json ================================================ { "name": "@examples/r3f-cra", "version": "0.1.0", "private": true, "installConfig": { "hoistingLimits": "workspaces" }, "dependencies": { "@react-three/drei": "^7.2.2", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@theatre/core": "workspace:*", "@theatre/r3f": "workspace:*", "react-scripts": "^5.0.1", "three": "^0.130.1", "web-vitals": "^1.0.1" }, "devDependencies": { "@theatre/studio": "workspace:*" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } ================================================ FILE: examples/r3f-cra/public/index.html ================================================ React App
================================================ FILE: examples/r3f-cra/public/manifest.json ================================================ { "short_name": "React App", "name": "Create React App Sample", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "logo192.png", "type": "image/png", "sizes": "192x192" }, { "src": "logo512.png", "type": "image/png", "sizes": "512x512" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ================================================ FILE: examples/r3f-cra/public/robots.txt ================================================ # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: ================================================ FILE: examples/r3f-cra/src/App.css ================================================ .App { text-align: center; } .App-logo { height: 40vmin; pointer-events: none; } @media (prefers-reduced-motion: no-preference) { .App-logo { animation: App-logo-spin infinite 20s linear; } } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } .App-link { color: #61dafb; } @keyframes App-logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } ================================================ FILE: examples/r3f-cra/src/App.js ================================================ import {getProject} from '@theatre/core' import * as THREE from 'three' import React, {useState, useEffect, useRef} from 'react' import {useFrame, Canvas} from '@react-three/fiber' import {Shadow, softShadows} from '@react-three/drei' import studio from '@theatre/studio' import {editable as e, SheetProvider} from '@theatre/r3f' import extension from '@theatre/r3f/dist/extension' if (process.env.NODE_ENV === 'development') { studio.extend(extension) studio.initialize() } // Soft shadows are expensive, comment and refresh when it's too slow softShadows() // credit: https://codesandbox.io/s/camera-pan-nsb7f function Button() { const vec = new THREE.Vector3() const light = useRef(undefined) const [active, setActive] = useState(false) const [zoom, set] = useState(true) useEffect( () => void (document.body.style.cursor = active ? 'pointer' : 'auto'), [active], ) useFrame((state) => { const step = 0.1 const camera = state.camera camera.fov = THREE.MathUtils.lerp(camera.fov, zoom ? 10 : 42, step) camera.position.lerp( vec.set(zoom ? 25 : 10, zoom ? 1 : 5, zoom ? 0 : 10), step, ) state.camera.lookAt(0, 0, 0) state.camera.updateProjectionMatrix() light.current.position.lerp( vec.set(zoom ? 4 : 0, zoom ? 3 : 8, zoom ? 3 : 5), step, ) }) return ( set(!zoom)} onPointerOver={() => setActive(true)} onPointerOut={() => setActive(false)} theatreKey="The Button" > ) } function Plane({color, theatreKey, ...props}) { return ( ) } function App() { return (
{/* @ts-ignore */}
) } export default App ================================================ FILE: examples/r3f-cra/src/App.test.js ================================================ import {render, screen} from '@testing-library/react' test('renders learn react link', () => { render() const linkElement = screen.getByText(/learn react/i) expect(linkElement).toBeInTheDocument() }) ================================================ FILE: examples/r3f-cra/src/index.css ================================================ body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } ================================================ FILE: examples/r3f-cra/src/index.js ================================================ import ReactDOM from 'react-dom' import './index.css' import reportWebVitals from './reportWebVitals' import '@theatre/studio' import {getProject} from '@theatre/core' import React from 'react' import App from './App' ReactDOM.render( , document.getElementById('root'), ) // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals() ================================================ FILE: examples/r3f-cra/src/react-app-env.d.ts ================================================ /// ================================================ FILE: examples/r3f-cra/src/reportWebVitals.js ================================================ const reportWebVitals = (onPerfEntry) => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => { getCLS(onPerfEntry) getFID(onPerfEntry) getFCP(onPerfEntry) getLCP(onPerfEntry) getTTFB(onPerfEntry) }) } } export default reportWebVitals ================================================ FILE: examples/r3f-cra/src/setupTests.js ================================================ // jest-dom adds custom jest matchers for asserting on DOM nodes. // allows you to do things like: // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom' ================================================ FILE: examples/r3f-cra/src/useDrag.js ================================================ import {useLayoutEffect, useRef} from 'react' const noop = () => {} function createCursorLock(cursor) { const el = document.createElement('div') el.style.cssText = ` position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 9999999;` el.style.cursor = cursor document.body.appendChild(el) const relinquish = () => { document.body.removeChild(el) } return relinquish } export default function useDrag(target, opts) { const optsRef = useRef(opts) optsRef.current = opts const modeRef = useRef('notDragging') const stateRef = useRef({dragHappened: false, startPos: {x: 0, y: 0}}) useLayoutEffect(() => { if (!target) return const getDistances = (event) => { const {startPos} = stateRef.current return [event.screenX - startPos.x, event.screenY - startPos.y] } let relinquishCursorLock = noop const dragHandler = (event) => { if (!stateRef.current.dragHappened && optsRef.current.lockCursorTo) { relinquishCursorLock = createCursorLock(optsRef.current.lockCursorTo) } if (!stateRef.current.dragHappened) stateRef.current.dragHappened = true modeRef.current = 'dragging' const deltas = getDistances(event) optsRef.current.onDrag(deltas[0], deltas[1], event) } const dragEndHandler = () => { removeDragListeners() modeRef.current = 'notDragging' optsRef.current.onDragEnd && optsRef.current.onDragEnd(stateRef.current.dragHappened) relinquishCursorLock() relinquishCursorLock = noop } const addDragListeners = () => { document.addEventListener('mousemove', dragHandler) document.addEventListener('mouseup', dragEndHandler) } const removeDragListeners = () => { document.removeEventListener('mousemove', dragHandler) document.removeEventListener('mouseup', dragEndHandler) } const preventUnwantedClick = (event) => { if (optsRef.current.disabled) return if (stateRef.current.dragHappened) { if ( !optsRef.current.dontBlockMouseDown && modeRef.current !== 'notDragging' ) { event.stopPropagation() event.preventDefault() } stateRef.current.dragHappened = false } } const dragStartHandler = (event) => { const opts = optsRef.current if (opts.disabled === true) return if (event.button !== 0) return const resultOfStart = opts.onDragStart && opts.onDragStart(event) if (resultOfStart === false) return if (!opts.dontBlockMouseDown) { event.stopPropagation() event.preventDefault() } modeRef.current = 'dragStartCalled' const {screenX, screenY} = event stateRef.current.startPos = {x: screenX, y: screenY} stateRef.current.dragHappened = false addDragListeners() } const onMouseDown = (e) => { dragStartHandler(e) } target.addEventListener('mousedown', onMouseDown) target.addEventListener('click', preventUnwantedClick) return () => { removeDragListeners() target.removeEventListener('mousedown', onMouseDown) target.removeEventListener('click', preventUnwantedClick) relinquishCursorLock() if (modeRef.current !== 'notDragging') { optsRef.current.onDragEnd && optsRef.current.onDragEnd(modeRef.current === 'dragging') } modeRef.current = 'notDragging' } }, [target]) } ================================================ FILE: examples/r3f-cra/tsconfig.json ================================================ { "compilerOptions": { "esModuleInterop": true, "jsx": "react-jsx", "skipDefaultLibCheck": true, "skipLibCheck": true, "checkJs": true, "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true }, "include": [ "src/**/*.js" ] } ================================================ FILE: jest.compat-tests.config.js ================================================ /** @type {import('jest').Config} */ module.exports = { testMatch: [ '/compat-tests/fixtures/*/*.compat-test.ts', '/compat-tests/*.compat-test.ts', ], moduleNameMapper: {}, modulePathIgnorePatterns: ['/compat-tests/verdaccio'], automock: false, // transform: { // '^.+\\.tsx?$': [ // 'esbuild-jest', // { // sourcemap: true, // }, // ], // '^.+\\.js$': [ // 'esbuild-jest', // { // sourcemap: true, // }, // ], // }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], // these tests take a long time to run, because each of them either runs a full build of a package, // or tests the build on a browser using playwright testTimeout: 1000 * 60 * 2, transform: { '^.+\\.tsx?$': [ 'jest-esbuild', { sourcemap: true, supported: { 'dynamic-import': false, }, }, ], '^.+\\.js$': [ 'jest-esbuild', { sourcemap: true, supported: { 'dynamic-import': false, }, }, ], }, } ================================================ FILE: jest.config.js ================================================ /** @type {import('jest').Config} */ module.exports = { testMatch: [ '/packages/*/src/**/*.test.ts', '/devEnv/**/*.test.ts', ], moduleNameMapper: { ...require('./devEnv/getAliasesFromTsConfig').getAliasesFromTsConfigForJest(), '\\.(css|svg|png)$': 'identity-obj-proxy', 'lodash-es/(.*)': 'lodash/$1', 'react-use/esm/(.*)': 'react-use/lib/$1', 'lodash-es': 'lodash', // ES modules that jest can't handle at the moment. uuid: '/node_modules/uuid/dist/index.js', nanoid: '/node_modules/nanoid/index.cjs', 'nanoid/non-secure': '/node_modules/nanoid/non-secure/index.cjs', 'react-icons/(.*)': 'identity-obj-proxy', 'react-merge-refs': 'identity-obj-proxy', '@trpc/client': 'identity-obj-proxy', oauth4webapi: 'identity-obj-proxy', }, setupFiles: [ './packages/studio/src/integration-tests/setupIntegrationTestEnv.ts', ], automock: false, transform: { '^.+\\.tsx?$': [ 'jest-esbuild', { sourcemap: true, supported: { 'dynamic-import': false, }, }, ], '^.+\\.js$': [ 'jest-esbuild', { sourcemap: true, supported: { 'dynamic-import': false, }, }, ], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], } ================================================ FILE: knip.config.json ================================================ { "$schema": "https://unpkg.com/knip@3/schema.json", "entry": [ "packages/*/src/index.{ts,tsx}", "packages/*/devEnv/**/*.{ts,js}", "devEnv/**/*.{ts,js}", "packages/playground/src/**/*.{ts,tsx}" ], "project": ["packages/*/{src,devEnv}/**/*.{ts,tsx,js,mjs}"], "typescript": { "config": "packages/*/tsconfig.json" } } ================================================ FILE: lerna.json ================================================ { "packages": ["packages/*", "theatre/*"], "version": "independent", "npmClient": "yarn", "useWorkspaces": true } ================================================ FILE: package.json ================================================ { "name": "theatre-monorepo", "license": "Apache-2.0", "version": "0.7.0", "workspaces": [ "packages/*", "examples/*", "compat-tests" ], "scripts": { "cli": "tsx devEnv/cli.ts", "playground": "yarn workspace playground run serve", "benchmarks": "yarn workspace benchmarks run serve", "test:e2e": "yarn workspace playground run test", "test:e2e:ci": "yarn workspace playground run test:ci", "typecheck": "yarn run build:ts", "build:ts": "tsc --build ./devEnv/typecheck-all-projects/tsconfig.all.json", "test": "jest", "test:compat:install": "yarn workspace @theatre/compat-tests run install-fixtures", "test:compat:run": "jest --config jest.compat-tests.config.js", "postinstall": "husky install && yarn workspace @theatre/app run prisma generate && yarn workspace @theatre/sync-server run prisma generate", "lint:all": "eslint . --ext ts,tsx --ignore-path=.gitignore --rulesdir ./devEnv/eslint/rules" }, "lint-staged": { "(theatre|packages|devEnv|compat-tests)/**/*.(t|j)s?(x)": [ "eslint --rulesdir ./devEnv/eslint/rules --fix" ], "**/*.(t|j)s?(x)": [ "prettier --write" ] }, "devDependencies": { "@cspotcode/zx": "^6.1.2", "@microsoft/api-documenter": "^7.19.0", "@microsoft/api-extractor": "^7.28.6", "@types/eslint": "^8.56.0", "@types/jest": "^26.0.23", "@typescript-eslint/eslint-plugin": "^6.16.0", "@typescript-eslint/parser": "^6.16.0", "esbuild": "^0.18.18", "eslint": "^8.56.0", "eslint-plugin-import": "2.29.1", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.33.2", "eslint-plugin-tsdoc": "^0.2.17", "eslint-plugin-unused-imports": "^3.0.0", "fast-glob": "^3.3.0", "husky": "^6.0.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.3.1", "jest-environment-jsdom": "^29.3.1", "jest-esbuild": "^0.3.0", "jsonc-parser": "^3.1.0", "knip": "^3.9.0", "lint-staged": "^13.0.3", "lodash": "^4.17.21", "node-gyp": "^9.1.0", "prettier": "^3.1.1", "sade": "^1.8.1", "tsx": "4.7.0", "typescript": "5.1.6", "yaml": "^2.3.1" }, "packageManager": "yarn@3.6.3", "engines": { "node": ">=18" }, "dependencies": { "@actions/core": "^1.10.0" } } ================================================ FILE: packages/app/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* .pnpm-debug.log* # local env files .env*.local /.env # vercel .vercel # typescript *.tsbuildinfo next-env.d.ts ================================================ FILE: packages/app/.vscode/settings.json ================================================ { "typescript.tsdk": "node_modules/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true } ================================================ FILE: packages/app/LICENSE ================================================ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . ================================================ FILE: packages/app/README.md ================================================ A stub for the web app (not the final implementation). ================================================ FILE: packages/app/components.json ================================================ { "$schema": "https://ui.shadcn.com/schema.json", "style": "default", "rsc": false, "tsx": true, "tailwind": { "config": "tailwind.config.js", "css": "src/pages/global.css", "baseColor": "zinc", "cssVariables": true }, "aliases": { "components": "src/ui/components", "utils": "src/ui/lib/utils" } } ================================================ FILE: packages/app/devEnv/cli.ts ================================================ import sade from 'sade' import {$, fs, path} from '@cspotcode/zx' import {config} from 'dotenv' import {fromZodError} from 'zod-validation-error' import * as jose from 'jose' import * as envSchema from 'src/envSchema' import type {Env} from 'src/envSchema' const isProduction = process.env.NODE_ENV === 'production' if (!isProduction) { if (!fs.existsSync(path.join(__dirname, '..', '.env'))) { console.log(`Creating .env file`) fs.copyFileSync( path.join(__dirname, '..', '.env.example'), path.join(__dirname, '..', '.env'), ) } config({path: '.env'}) } const usedSchema = isProduction ? envSchema.productionSchema : envSchema.fullSchema function validateEnv() { try { usedSchema.parse(process.env) } catch (error) { console.error("Environment variables aren't valid.") console.log(JSON.stringify(process.env, null, 2)) console.error(fromZodError(error as any)) throw error } const env: Env = process.env as any const url = new URL(env.DATABASE_URL) if (url.protocol !== 'postgresql:' && url.protocol !== 'postgres:') { throw new Error(`DATABASE_URL protocol must be postgresql: Given: ${url}`) } if (!isProduction) { if ( env.DATABASE_URL !== `postgresql://postgres:${env.DEV_DB_PASSWORD}@localhost:${env.DEV_DB_PORT}/postgres` ) { throw new Error( `We're in dev mode, so DATABASE_URL must match the other DB variables set in .env. DATABASE_URL should be: postgresql://postgres:${env.DEV_DB_PASSWORD}@localhost:${env.DEV_DB_PORT}/postgres`, ) } } } const packageRoot = path.join(__dirname, '..') const prog = sade('cli') prog .command('dev all', 'Start all the development services, including next.js') .action(async () => { validateEnv() await devDb() await devNext() }) async function devNext() { validateEnv() await $`next dev` } prog.command('dev next', 'Start next.js dev server').action(devNext) async function devDb() { validateEnv() await devDbDocker() await $`prisma generate` await $`prisma migrate dev` await $`prisma db seed` } prog.command('dev db', 'Start the database service').action(devDb) async function devDbDocker() { validateEnv() await $`docker-compose up -d` // wait for the database to be ready await $`while ! (docker-compose ps postgres | grep -q "Up"); do sleep 2; done` } prog .command( 'dev db docker', `Start the database service. Don't generate bindings`, ) .action(devDbDocker) prog.command('dev db nuke', 'Nuke the database').action(async () => { validateEnv() await $`docker-compose down --volumes --remove-orphans` await $`rm -rf prisma/*.db**` }) prog .command('dev setup', 'Setup the development environment') .action(async () => { if (!fs.existsSync(path.join(packageRoot, '.env'))) { console.log(`Creating .env file`) fs.copyFileSync( path.join(packageRoot, '.env.example'), path.join(packageRoot, '.env'), ) } }) prog .command(`dev generate-keypair`, `Generate public/private keypair`) .action(async () => { const v = await jose.generateKeyPair('RS256', {}) console.log('public key') console.log(await jose.exportSPKI(v.publicKey)) console.log('private key') console.log(await jose.exportPKCS8(v.privateKey)) }) prog.command('prod build', 'Build for production').action(async () => { validateEnv() await $`prisma migrate deploy` await $`next build` }) prog.command('prod start', 'Start in production mode').action(async () => { validateEnv() await $`next start` }) prog.command('prebuild', 'Prebuild pages').action(async () => { validateEnv() await $`prisma generate` await $`prisma migrate deploy` }) prog.parse(process.argv) ================================================ FILE: packages/app/docker-compose.yml ================================================ version: '3.6' services: postgres: image: postgres:13 ports: - '${DEV_DB_PORT}:5432' restart: always volumes: - ./.temp/postgres/data:/var/lib/postgresql/data env_file: - ./.env environment: POSTGRES_PASSWORD: ${DEV_DB_PASSWORD} POSTGRES_HOST_AUTH_METHOD: trust ================================================ FILE: packages/app/next.config.js ================================================ /** @type {import('next').NextConfig} */ const nextConfig = { // ignore typescript errors. The global typecheck script will catch them typescript: { ignoreBuildErrors: true, tsconfigPath: './tsconfig.json', }, // ignore eslint errors. The global lint script will catch them eslint: { ignoreDuringBuilds: true, }, } module.exports = nextConfig ================================================ FILE: packages/app/package.json ================================================ { "name": "@theatre/app", "version": "0.1.0", "private": true, "scripts": { "cli": "tsx devEnv/cli.ts", "start": "yarn cli prod start", "lint": "next lint", "prebuild": "yarn cli prebuild", "postinstall": "prisma generate" }, "prisma": { "seed": "tsx prisma/seed.ts" }, "dependencies": { "@auth/prisma-adapter": "^1.0.3", "@cspotcode/zx": "^6.1.2", "@hookform/resolvers": "^3.3.2", "@prisma/client": "^4.12.0", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-context-menu": "^2.1.5", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@tanstack/react-query": "^4.36.1", "@theatre/utils": "workspace:*", "@trpc/client": "^10.43.0", "@trpc/next": "^10.43.0", "@trpc/react-query": "^10.43.0", "@trpc/server": "^10.43.0", "@types/node": "20.4.9", "@types/react": "18.2.20", "@types/react-dom": "18.2.7", "@types/uuid": "^8.3.0", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cors": "^2.8.5", "cross-env": "^7.0.3", "dotenv": "^16.3.1", "esbuild": "^0.18.20", "eslint-config-next": "13.4.13", "jose": "^4.14.4", "lucide-react": "^0.289.0", "nanoid": "^3.3.1", "next": "latest", "next-auth": "^4.23.2", "npm-run-all": "^4.1.5", "oauth4webapi": "^2.4.0", "pg": "^8.11.2", "prisma": "^4.12.0", "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.47.0", "react-promptify": "^0.3.0", "sade": "^1.8.1", "superjson": "^1.13.1", "tailwind-merge": "^1.14.0", "tailwindcss-animate": "^1.0.7", "tsx": "^3.12.7", "typescript": "5.1.6", "uuid": "^8.3.2", "yaml": "^2.3.1", "zod": "^3.22.4", "zod-validation-error": "^1.3.1" }, "devDependencies": { "@types/jsonwebtoken": "^9.0.2", "autoprefixer": "^10.4.16", "jsonwebtoken": "^9.0.1", "postcss": "^8.4.31", "tailwindcss": "^3.3.3" } } ================================================ FILE: packages/app/postcss.config.js ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } ================================================ FILE: packages/app/prisma/.gitignore ================================================ /client-generated ================================================ FILE: packages/app/prisma/migrations/20230409133858_init/migration.sql ================================================ -- CreateTable CREATE TABLE "User" ( "id" TEXT NOT NULL, CONSTRAINT "User_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Project" ( "id" SERIAL NOT NULL, "name" TEXT NOT NULL, "description" TEXT, "userId" TEXT NOT NULL, CONSTRAINT "Project_pkey" PRIMARY KEY ("id") ); -- AddForeignKey ALTER TABLE "Project" ADD CONSTRAINT "Project_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ================================================ FILE: packages/app/prisma/migrations/20230514190125_lib_auth/migration.sql ================================================ -- CreateTable CREATE TABLE "PreAuthenticationToken" ( "preAuthenticationToken" TEXT NOT NULL, "userCode" TEXT NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL, "validUntil" TIMESTAMP(3) NOT NULL, "lastCheckTime" TIMESTAMP(3) NOT NULL, "state" INTEGER NOT NULL DEFAULT 0, "tokens" TEXT NOT NULL, CONSTRAINT "PreAuthenticationToken_pkey" PRIMARY KEY ("preAuthenticationToken") ); ================================================ FILE: packages/app/prisma/migrations/20230813123201_1/migration.sql ================================================ /* Warnings: - You are about to drop the `PreAuthenticationToken` table. If the table is not empty, all the data it contains will be lost. */ -- DropTable DROP TABLE "PreAuthenticationToken"; -- CreateTable CREATE TABLE "LibAuthenticationFlow" ( "preAuthenticationToken" TEXT NOT NULL, "userCode" TEXT NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL, "clientFlowToken" TEXT NOT NULL, "lastCheckTime" TIMESTAMP(3) NOT NULL, "state" INTEGER NOT NULL DEFAULT 0, "tokens" TEXT NOT NULL, CONSTRAINT "LibAuthenticationFlow_pkey" PRIMARY KEY ("preAuthenticationToken") ); -- CreateIndex CREATE UNIQUE INDEX "LibAuthenticationFlow_userCode_key" ON "LibAuthenticationFlow"("userCode"); ================================================ FILE: packages/app/prisma/migrations/20230813131020_2/migration.sql ================================================ -- AlterTable ALTER TABLE "LibAuthenticationFlow" ALTER COLUMN "createdAt" SET DATA TYPE TIMESTAMPTZ, ALTER COLUMN "lastCheckTime" SET DATA TYPE TIMESTAMPTZ; ================================================ FILE: packages/app/prisma/migrations/20230820072612_3/migration.sql ================================================ /* Warnings: - The `state` column on the `LibAuthenticationFlow` table would be dropped and recreated. This will lead to data loss if there is data in the column. */ -- CreateEnum CREATE TYPE "LibAuthenticationFlowState" AS ENUM ('initialized', 'userAllowedAuth', 'userDeniedAuth', 'tokenAlreadyUsed'); -- AlterTable ALTER TABLE "LibAuthenticationFlow" DROP COLUMN "state", ADD COLUMN "state" "LibAuthenticationFlowState" NOT NULL DEFAULT 'initialized'; ================================================ FILE: packages/app/prisma/migrations/20230820093151_4/migration.sql ================================================ -- CreateTable CREATE TABLE "LibSession" ( "refreshToken" TEXT NOT NULL, "createdAt" TIMESTAMPTZ NOT NULL, "validUntil" TIMESTAMPTZ NOT NULL, "userId" TEXT NOT NULL, CONSTRAINT "LibSession_pkey" PRIMARY KEY ("refreshToken") ); -- AddForeignKey ALTER TABLE "LibSession" ADD CONSTRAINT "LibSession_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ================================================ FILE: packages/app/prisma/migrations/20230820095524_/migration.sql ================================================ /* Warnings: - A unique constraint covering the columns `[auth0Sid]` on the table `User` will be added. If there are existing duplicate values, this will fail. - Added the required column `auth0Data` to the `User` table without a default value. This is not possible if the table is not empty. - Added the required column `auth0Sid` to the `User` table without a default value. This is not possible if the table is not empty. - Added the required column `email` to the `User` table without a default value. This is not possible if the table is not empty. */ -- AlterTable ALTER TABLE "User" ADD COLUMN "auth0Data" JSONB NOT NULL, ADD COLUMN "auth0Sid" TEXT NOT NULL, ADD COLUMN "email" TEXT NOT NULL; -- CreateIndex CREATE UNIQUE INDEX "User_auth0Sid_key" ON "User"("auth0Sid"); -- CreateIndex CREATE INDEX "email" ON "User"("email"); ================================================ FILE: packages/app/prisma/migrations/20230827165303_5/migration.sql ================================================ /* Warnings: - The primary key for the `Project` table will be changed. If it partially fails, the table could be left without primary key constraint. */ -- AlterTable ALTER TABLE "Project" DROP CONSTRAINT "Project_pkey", ALTER COLUMN "id" DROP DEFAULT, ALTER COLUMN "id" SET DATA TYPE TEXT, ADD CONSTRAINT "Project_pkey" PRIMARY KEY ("id"); DROP SEQUENCE "Project_id_seq"; ================================================ FILE: packages/app/prisma/migrations/20230828173601_2/migration.sql ================================================ -- AlterTable ALTER TABLE "Project" ALTER COLUMN "name" DROP NOT NULL; ================================================ FILE: packages/app/prisma/migrations/20231005010012_replace_auth0_with_next_auth/migration.sql ================================================ /* Warnings: - You are about to drop the column `auth0Data` on the `User` table. All the data in the column will be lost. - You are about to drop the column `auth0Sid` on the `User` table. All the data in the column will be lost. - A unique constraint covering the columns `[email]` on the table `User` will be added. If there are existing duplicate values, this will fail. */ -- DropIndex DROP INDEX "User_auth0Sid_key"; -- AlterTable ALTER TABLE "User" DROP COLUMN "auth0Data", DROP COLUMN "auth0Sid", ADD COLUMN "emailVerified" TIMESTAMP(3), ADD COLUMN "image" TEXT, ADD COLUMN "name" TEXT, ALTER COLUMN "email" DROP NOT NULL; -- CreateTable CREATE TABLE "Account" ( "id" TEXT NOT NULL, "userId" TEXT NOT NULL, "type" TEXT NOT NULL, "provider" TEXT NOT NULL, "providerAccountId" TEXT NOT NULL, "refresh_token" TEXT, "access_token" TEXT, "expires_at" INTEGER, "token_type" TEXT, "scope" TEXT, "id_token" TEXT, "session_state" TEXT, CONSTRAINT "Account_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "Session" ( "id" TEXT NOT NULL, "sessionToken" TEXT NOT NULL, "userId" TEXT NOT NULL, "expires" TIMESTAMP(3) NOT NULL, CONSTRAINT "Session_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "VerificationToken" ( "identifier" TEXT NOT NULL, "token" TEXT NOT NULL, "expires" TIMESTAMP(3) NOT NULL ); -- CreateIndex CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId"); -- CreateIndex CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken"); -- CreateIndex CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token"); -- CreateIndex CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token"); -- CreateIndex CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); -- AddForeignKey ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; ================================================ FILE: packages/app/prisma/migrations/20231014131018_referesh_token_grace_period/migration.sql ================================================ -- AlterTable ALTER TABLE "LibSession" ADD COLUMN "succeededByRefreshToken" TEXT, ADD COLUMN "successorLinkExpresAt" TIMESTAMPTZ; ================================================ FILE: packages/app/prisma/migrations/20231017030424_add_teams_and_workspaces/migration.sql ================================================ /* Warnings: - You are about to drop the `Project` table. If the table is not empty, all the data it contains will be lost. */ -- CreateEnum CREATE TYPE "TeamUserRole" AS ENUM ('OWNER', 'MEMBER'); -- CreateEnum CREATE TYPE "AccessLevel" AS ENUM ('READ', 'READ_WRITE'); -- DropForeignKey ALTER TABLE "Project" DROP CONSTRAINT "Project_userId_fkey"; -- DropTable DROP TABLE "Project"; -- CreateTable CREATE TABLE "Team" ( "id" TEXT NOT NULL, "name" TEXT NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "Team_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "TeamMember" ( "teamId" TEXT NOT NULL, "userId" TEXT NOT NULL, "userRole" "TeamUserRole" NOT NULL, "accepted" BOOLEAN NOT NULL DEFAULT false, CONSTRAINT "TeamMember_pkey" PRIMARY KEY ("userId","teamId") ); -- CreateTable CREATE TABLE "Workspace" ( "id" TEXT NOT NULL, "name" TEXT NOT NULL, "description" TEXT NOT NULL, "teamId" TEXT NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "Workspace_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "GuestAccess" ( "workspaceId" TEXT NOT NULL, "userId" TEXT NOT NULL, "accessLevel" "AccessLevel" NOT NULL, "accepted" BOOLEAN NOT NULL DEFAULT false, CONSTRAINT "GuestAccess_pkey" PRIMARY KEY ("userId","workspaceId") ); -- CreateIndex CREATE INDEX "TeamMember_teamId_idx" ON "TeamMember"("teamId"); -- CreateIndex CREATE INDEX "TeamMember_userId_idx" ON "TeamMember"("userId"); -- CreateIndex CREATE INDEX "GuestAccess_workspaceId_idx" ON "GuestAccess"("workspaceId"); -- CreateIndex CREATE INDEX "GuestAccess_userId_idx" ON "GuestAccess"("userId"); -- AddForeignKey ALTER TABLE "TeamMember" ADD CONSTRAINT "TeamMember_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "TeamMember" ADD CONSTRAINT "TeamMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "Workspace" ADD CONSTRAINT "Workspace_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "GuestAccess" ADD CONSTRAINT "GuestAccess_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "GuestAccess" ADD CONSTRAINT "GuestAccess_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE RESTRICT ON UPDATE CASCADE; ================================================ FILE: packages/app/prisma/migrations/20231017070342_add_teams_and_workspaces/migration.sql ================================================ -- DropForeignKey ALTER TABLE "GuestAccess" DROP CONSTRAINT "GuestAccess_userId_fkey"; -- DropForeignKey ALTER TABLE "GuestAccess" DROP CONSTRAINT "GuestAccess_workspaceId_fkey"; -- DropForeignKey ALTER TABLE "TeamMember" DROP CONSTRAINT "TeamMember_teamId_fkey"; -- DropForeignKey ALTER TABLE "TeamMember" DROP CONSTRAINT "TeamMember_userId_fkey"; -- DropForeignKey ALTER TABLE "Workspace" DROP CONSTRAINT "Workspace_teamId_fkey"; -- AddForeignKey ALTER TABLE "TeamMember" ADD CONSTRAINT "TeamMember_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "TeamMember" ADD CONSTRAINT "TeamMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "Workspace" ADD CONSTRAINT "Workspace_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "GuestAccess" ADD CONSTRAINT "GuestAccess_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "GuestAccess" ADD CONSTRAINT "GuestAccess_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE; ================================================ FILE: packages/app/prisma/migrations/20231127144216_/migration.sql ================================================ /* Warnings: - You are about to drop the `LibAuthenticationFlow` table. If the table is not empty, all the data it contains will be lost. */ -- CreateEnum CREATE TYPE "DeviceAuthorizationFlowState" AS ENUM ('initialized', 'userAllowedAuth', 'userDeniedAuth', 'tokenAlreadyUsed'); -- DropTable DROP TABLE "LibAuthenticationFlow"; -- DropEnum DROP TYPE "LibAuthenticationFlowState"; -- CreateTable CREATE TABLE "DeviceAuthorizationFlow" ( "deviceCode" TEXT NOT NULL, "userCode" TEXT NOT NULL, "createdAt" TIMESTAMPTZ NOT NULL, "lastCheckTime" TIMESTAMPTZ NOT NULL, "nounce" TEXT NOT NULL, "state" "DeviceAuthorizationFlowState" NOT NULL DEFAULT 'initialized', "tokens" TEXT NOT NULL, CONSTRAINT "DeviceAuthorizationFlow_pkey" PRIMARY KEY ("deviceCode") ); -- CreateIndex CREATE UNIQUE INDEX "DeviceAuthorizationFlow_userCode_key" ON "DeviceAuthorizationFlow"("userCode"); ================================================ FILE: packages/app/prisma/migrations/20231127153849_pkce/migration.sql ================================================ -- AlterTable ALTER TABLE "DeviceAuthorizationFlow" ADD COLUMN "codeChallenge" TEXT NOT NULL DEFAULT '', ADD COLUMN "codeChallengeMethod" TEXT NOT NULL DEFAULT 'S256'; ================================================ FILE: packages/app/prisma/migrations/20231202190130_scopes/migration.sql ================================================ -- AlterTable ALTER TABLE "DeviceAuthorizationFlow" ADD COLUMN "scopes" JSONB NOT NULL DEFAULT '[]'; ================================================ FILE: packages/app/prisma/migrations/migration_lock.toml ================================================ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) provider = "postgresql" ================================================ FILE: packages/app/prisma/schema.prisma ================================================ // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" output = "./client-generated" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Account { id String @id @default(cuid()) userId String type String provider String providerAccountId String refresh_token String? @db.Text access_token String? @db.Text expires_at Int? token_type String? scope String? id_token String? @db.Text session_state String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) } model User { libSessions LibSession[] id String @id @default(cuid()) name String? email String? @unique emailVerified DateTime? image String? accounts Account[] sessions Session[] guestAccesses GuestAccess[] teams TeamMember[] @@index([email], name: "email") } model Team { id String @id @default(cuid()) name String workspaces Workspace[] members TeamMember[] createdAt DateTime @default(now()) } enum TeamUserRole { OWNER MEMBER } model TeamMember { teamId String userId String team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) userRole TeamUserRole accepted Boolean @default(false) @@id([userId, teamId]) @@index([teamId]) @@index([userId]) } model Workspace { id String @id @default(cuid()) name String description String teamId String team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) guests GuestAccess[] createdAt DateTime @default(now()) } enum AccessLevel { READ READ_WRITE } model GuestAccess { workspaceId String userId String accessLevel AccessLevel user User @relation(fields: [userId], references: [id], onDelete: Cascade) workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) accepted Boolean @default(false) @@id([userId, workspaceId]) @@index([workspaceId]) @@index([userId]) } model Session { id String @id @default(cuid()) sessionToken String @unique userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model VerificationToken { identifier String token String @unique expires DateTime @@unique([identifier, token]) } // We're using OAuth2's device authorization flow to authorize `@theatre/studio` hosted in random origins. model DeviceAuthorizationFlow { deviceCode String @id // a random token generated by the server, and shared with the libray. This token is the key the library will use to obtain the final access/refersh tokens userCode String @unique // a random, short token generated by the server. The user will then be redirected to [app]/auth/?userCode=[userCode]. At that URL, the user can log in via credentials, or if they're already logged in, they'll be asked whether to grant the library permission to edit projects createdAt DateTime @db.Timestamptz() // the time the flow started. If older than a certain interval, the flow is considered expired/ lastCheckTime DateTime @db.Timestamptz() // the time the client last checked the status of this flow. If shorter than a certain interval, the client will be told to slow down. nounce String // a random token generated by the client. It'll be included in the final idToken, so the client can make sure the tokens belong to the authentication flow it initiated codeChallenge String @default("") // code_challenge as per https://tools.ietf.org/html/rfc7636 codeChallengeMethod String @default("S256") // code_challenge_method as per https://tools.ietf.org/html/rfc7636 (currently only "S256" is supported) state DeviceAuthorizationFlowState @default(initialized) tokens String // will be non-empty if state=1. It'll contain a json object containing access/refresh tokens scopes Json @default("[]") } enum DeviceAuthorizationFlowState { initialized userAllowedAuth userDeniedAuth tokenAlreadyUsed } model LibSession { refreshToken String @id createdAt DateTime @db.Timestamptz() validUntil DateTime @db.Timestamptz() userId String user User @relation(fields: [userId], references: [id]) succeededByRefreshToken String? // if this session was refreshed, this will contain the refresh token of the new session successorLinkExpresAt DateTime? @db.Timestamptz() // the time at which the link to the successor will expire. This is supposed to give a minute of leeway for the client to access the new refresh token in case it didn't receive it in the first try } ================================================ FILE: packages/app/prisma/seed.ts ================================================ /** * Adds seed data to your db * * See https://www.prisma.io/docs/guides/database/seed-database */ import {PrismaClient} from './client-generated' const prisma = new PrismaClient() async function main() { // nothing to seed rn } main() .catch((e) => { console.error(e) process.exit(1) }) .finally(async () => { await prisma.$disconnect() }) ================================================ FILE: packages/app/src/.eslintrc.json ================================================ {} ================================================ FILE: packages/app/src/app/(protected)/_components/AccountSettingsPrompt.tsx ================================================ 'use client' import {type FC} from 'react' import type {PromptProps} from '~/app/_components/Prompts' import {confirm, prompt} from '~/app/_components/Prompts' import {DialogHeader, DialogTitle} from '~/ui/components/ui/dialog' import * as schemas from '~/schemas' import {Button} from '~/ui/components/ui/button' import {api} from '~/trpc/react' import {promptValue} from '~/app/_components/Prompts' import {toast} from '~/ui/components/ui/use-toast' import {Separator} from '~/ui/components/ui/separator' import {useRouter} from 'next/navigation' const AccountSettingsPrompt: FC> = ({done}) => { const user = api.me.get.useQuery().data! const updateUser = api.me.update.useMutation().mutateAsync const deleteUser = api.me.delete.useMutation().mutateAsync const queryUtils = api.useUtils() const router = useRouter() return ( <> Account Settings
Name
{user.name}
Email
{user.email}
) } export const promptAccountSettings = () => prompt((done) => ) ================================================ FILE: packages/app/src/app/(protected)/_components/AccountSwitcher.tsx ================================================ 'use client' import * as React from 'react' import {signOut} from 'next-auth/react' import {CaretSortIcon} from '@radix-ui/react-icons' import {cn} from '~/ui/lib/utils' import {Avatar, AvatarFallback, AvatarImage} from '~/ui/components/ui/avatar' import {Button} from '~/ui/components/ui/button' import { Popover, PopoverContent, PopoverTrigger, } from '~/ui/components/ui/popover' import {api} from '~/trpc/react' import {promptAccountSettings} from './AccountSettingsPrompt' export default function AccountSwitcher() { const [open, setOpen] = React.useState(false) const user = api.me.get.useQuery().data return ( ) } ================================================ FILE: packages/app/src/app/(protected)/_components/EditWorkspaceDialog.tsx ================================================ 'use client' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '~/ui/components/ui/dialog' import {Input} from '~/ui/components/ui/input' import {Button} from '~/ui/components/ui/button' import {useForm} from 'react-hook-form' import * as z from 'zod' import {zodResolver} from '@hookform/resolvers/zod' import * as schemas from 'src/schemas' import {api} from '~/trpc/react' import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from 'src/ui/components/ui/form' import {useToast} from '~/ui/components/ui/use-toast' import {Loader} from 'lucide-react' const formSchema = z.object({ name: schemas.workspaceName, description: schemas.workspaceDescription, }) export default function EditWorkspaceDialog({ workspace: { id: workspaceId, name: workspaceName, description: workspaceDescription, }, open, onOpenChange, }: { workspace: { id: string name: string description: string } open: boolean onOpenChange: (isOpen: boolean) => void }) { const {toast} = useToast() const {mutateAsync, isLoading} = api.workspaces.update.useMutation() const queryUtils = api.useUtils() const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { name: workspaceName, description: workspaceDescription, }, }) async function onSubmit(values: z.infer) { try { onOpenChange(false) await mutateAsync({ id: workspaceId, name: values.name, description: values.description, }) void queryUtils.teams.invalidate() void queryUtils.workspaces.invalidate() } catch (error) { toast({ variant: 'destructive', title: 'Uh oh! Something went wrong.', description: "Couldn't update the workspace.", }) } } return ( Edit Workspace
( Name )} /> ( Description )} />
) } ================================================ FILE: packages/app/src/app/(protected)/_components/InviteGuestsDialog.tsx ================================================ 'use client' import {Suspense} from 'react' import {useForm} from 'react-hook-form' import * as z from 'zod' import {zodResolver} from '@hookform/resolvers/zod' import {api} from '~/trpc/react' import {Avatar, AvatarFallback, AvatarImage} from '~/ui/components/ui/avatar' import {Button} from '~/ui/components/ui/button' import { Dialog, DialogContent, DialogDescription, DialogHeader, } from '~/ui/components/ui/dialog' import {Input} from '~/ui/components/ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '~/ui/components/ui/select' import {Separator} from '~/ui/components/ui/separator' import { Form, FormControl, FormField, FormItem, FormMessage, } from '~/ui/components/ui/form' import {useToast} from '~/ui/components/ui/use-toast' const formSchema = z.object({ email: z.string().email(), accessLevel: z.enum(['READ', 'READ_WRITE']), }) export default function InviteGuestsDialog({ workspaceId, open, onOpenChange, }: { workspaceId: string open: boolean onOpenChange: (isOpen: boolean) => void }) { const {mutateAsync: inviteGuests, isLoading} = api.workspaces.inviteGuests.useMutation() const queryUtils = api.useUtils() const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { email: '', accessLevel: 'READ_WRITE', }, }) const {toast} = useToast() async function onSubmit(values: z.infer) { try { await inviteGuests({ id: workspaceId, invites: [{email: values.email, accessLevel: values.accessLevel}], }) void queryUtils.workspaces.invalidate() } catch (error) { toast({ variant: 'destructive', title: 'Uh oh! Something went wrong.', description: 'Make sure the guest has an account.', }) } } return ( Share this workspace
{/* */} ( )} /> ( )} />

Guests

Loading...
}>
) } function Guests({workspaceId}: {workspaceId: string}) { const guests = api.workspaces.getGuests.useQuery({id: workspaceId}).data! const {mutateAsync: removeGuest} = api.workspaces.removeGuest.useMutation() const {mutateAsync: changeAccessLevel} = api.workspaces.changeGuestAccess.useMutation() const queryUtils = api.useUtils() // if no guests, return a call to action if (guests.length === 0) { return ( Invite people to collaborate on this workspace. ) } async function handleRemoveGuest(email: string) { await removeGuest({id: workspaceId, email}) void queryUtils.workspaces.getGuests.invalidate() } async function handleChangeAccessLevel( email: string, accessLevel: 'READ' | 'READ_WRITE', ) { await changeAccessLevel({id: workspaceId, email, accessLevel}) void queryUtils.workspaces.getGuests.invalidate() } return (
{guests.map((guest) => (
{guest.accepted ? ( <> {guest.name!.split(/\s/).map((part) => part.charAt(0))} ) : ( <> )}

{guest.accepted ? guest.name : 'Invitation pending...'}

{guest.email}

))}
) } ================================================ FILE: packages/app/src/app/(protected)/_components/InviteTeamMembersPrompt.tsx ================================================ 'use client' import {Suspense, type FC} from 'react' import {api} from '~/trpc/react' import {Button} from '~/ui/components/ui/button' import {DialogHeader, DialogTitle} from '~/ui/components/ui/dialog' import {Separator} from '~/ui/components/ui/separator' import Members from './TeamMembers' import {prompt} from '~/app/_components/Prompts' import {Input} from '~/ui/components/ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '~/ui/components/ui/select' import { Form, FormControl, FormField, FormItem, FormMessage, } from '~/ui/components/ui/form' import {useToast} from '~/ui/components/ui/use-toast' import {useForm} from 'react-hook-form' import * as z from 'zod' import {zodResolver} from '@hookform/resolvers/zod' const formSchema = z.object({ email: z.string().email(), role: z.enum(['MEMBER', 'OWNER']), }) const InviteTeamMembersPrompt: FC<{id: string}> = ({id}) => { const team = api.teams.get.useQuery({id}).data! const inviteMembers = api.teams.inviteMembers.useMutation().mutateAsync const queryUtils = api.useUtils() const {toast} = useToast() const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { email: '', role: 'MEMBER', }, }) async function onSubmit(values: z.infer) { try { await inviteMembers({ id, invites: [{email: values.email, role: values.role}], }) void queryUtils.teams.invalidate() } catch (error) { toast({ variant: 'destructive', title: 'Uh oh! Something went wrong.', description: 'Make sure the initee has an account.', }) } } return ( <> Invite Members to {team.name}
( )} /> ( )} />

Members

Loading...
}> ) } export const promptInviteMembers = (id: string) => prompt(() => ) ================================================ FILE: packages/app/src/app/(protected)/_components/Navigation.tsx ================================================ 'use client' import {Suspense} from 'react' import Link from 'next/link' import {useSelectedLayoutSegments} from 'next/navigation' import {api} from '~/trpc/react' import {Button} from '~/ui/components/ui/button' import {cn} from '~/ui/lib/utils' import AccountSwitcher from './AccountSwitcher' import NotificationsPopover from './NotificationsPopover' import {Plus} from 'lucide-react' import {promptValue} from '~/app/_components/Prompts' import * as schemas from '~/schemas' import {promptInviteMembers} from './InviteTeamMembersPrompt' export default function Navigation() { const teams = api.teams.getAll.useQuery().data! const createTeam = api.teams.create.useMutation().mutateAsync const segments = useSelectedLayoutSegments() const selected = segments[0] === 'team' ? segments[1] : segments[0] const queryUtils = api.useUtils() return (

Teams

{teams .filter((team) => team.members.some((member) => member.accepted), ) .map((team) => ( // // // // // // // // { // }} // > // Rename // // // ))}
) } ================================================ FILE: packages/app/src/app/(protected)/_components/NewWorkspaceDialog.tsx ================================================ 'use client' import {useState} from 'react' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTrigger, DialogTitle, DialogFooter, } from '~/ui/components/ui/dialog' import {Input} from '~/ui/components/ui/input' import {Button} from '~/ui/components/ui/button' import {Plus} from 'lucide-react' import {useForm} from 'react-hook-form' import * as z from 'zod' import {zodResolver} from '@hookform/resolvers/zod' import * as schemas from 'src/schemas' import {api} from '~/trpc/react' import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from 'src/ui/components/ui/form' import {useToast} from '~/ui/components/ui/use-toast' import {ToastAction} from '~/ui/components/ui/toast' import {Loader} from 'lucide-react' const formSchema = z.object({ name: schemas.workspaceName, description: schemas.workspaceDescription, }) export default function NewWorkspaceDialog({teamId}: {teamId: string}) { const [isOpen, setIsOpen] = useState(false) const {toast} = useToast() const {mutateAsync, isLoading} = api.workspaces.create.useMutation() const queryUtils = api.useUtils() const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { name: 'New Workspace', description: '', }, }) async function onSubmit(values: z.infer) { try { setIsOpen(false) await mutateAsync({ name: values.name, description: values.description, teamId, }) void queryUtils.teams.invalidate() void queryUtils.workspaces.invalidate() } catch (error) { toast({ variant: 'destructive', title: 'Uh oh! Something went wrong.', description: "Couldn't create the workspace.", action: Try again, }) } } return ( setIsOpen(isOpen)}> New Workspace Create a new workspace for your team.
( Name )} /> ( Description )} />
) } ================================================ FILE: packages/app/src/app/(protected)/_components/NotificationsPopover.tsx ================================================ 'use client' import {Suspense} from 'react' import { Popover, PopoverContent, PopoverTrigger, } from '~/ui/components/ui/popover' import {Bell} from 'lucide-react' import {Button} from '~/ui/components/ui/button' import {api} from '~/trpc/react' export default function NotificationsPopover() { return (

Notifications

) } const Invitations = () => { const me = api.me.get.useQuery().data! const guestInvitations = api.me.getGuestInvitations.useQuery().data! const teamInvitations = api.me.getTeamInvitations.useQuery().data! const acceptTeamInvitation = api.teams.acceptInvite.useMutation().mutateAsync const acceptGuestInvitation = api.workspaces.acceptInvite.useMutation().mutateAsync const rejectTeamInvitation = api.teams.removeMember.useMutation().mutateAsync const rejectGuestInvitation = api.workspaces.removeGuest.useMutation().mutateAsync const queryUtils = api.useUtils() return (
{guestInvitations.map((invitation) => (
You have been invited to{' '} {invitation.accessLevel === 'READ' ? 'read' : 'edit'}{' '} {invitation.workspaceName}
))} {teamInvitations.map((invitation) => (
You have been invited to join{' '} {invitation.teamName}
))}
) } ================================================ FILE: packages/app/src/app/(protected)/_components/SessionProvider.tsx ================================================ 'use client' import {type Session} from 'next-auth' import {SessionProvider as NextAuthSessionProvider} from 'next-auth/react' export default async function SessionProvider({ children, session, }: { children: React.ReactNode session: Session }) { return ( {children} ) } ================================================ FILE: packages/app/src/app/(protected)/_components/TeamMembers.tsx ================================================ import {api} from '~/trpc/react' import {Avatar, AvatarFallback, AvatarImage} from '~/ui/components/ui/avatar' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '~/ui/components/ui/select' import {Separator} from '~/ui/components/ui/separator' import {Button} from '~/ui/components/ui/button' import {useToast} from '~/ui/components/ui/use-toast' export default function Members({teamId}: {teamId: string}) { const members = api.teams.getMembers.useQuery({id: teamId}).data! const removeMember = api.teams.removeMember.useMutation().mutateAsync const changeMemberRole = api.teams.changeMemberRole.useMutation().mutateAsync const queryUtils = api.useUtils() const {toast} = useToast() async function handleRemoveMember(email: string) { await removeMember({id: teamId, email}) void queryUtils.teams.getMembers.invalidate() } async function handleChangeRole(email: string, role: 'MEMBER' | 'OWNER') { try { await changeMemberRole({id: teamId, email, role}) void queryUtils.teams.getMembers.invalidate() } catch (error) { toast({ variant: 'destructive', title: 'Uh oh! Something went wrong.', }) } } return (
{members.map((member) => (
{member.accepted ? ( <> {member.name!.split(/\s/).map((part) => part.charAt(0))} ) : ( <> )}

{member.accepted ? member.name : 'Invitation pending...'}

{member.email}

))}
) } ================================================ FILE: packages/app/src/app/(protected)/_components/TeamSettingsPrompt.tsx ================================================ 'use client' import {type FC, Suspense} from 'react' import type {PromptProps} from '~/app/_components/Prompts'; import { confirm, prompt} from '~/app/_components/Prompts' import {DialogHeader, DialogTitle} from '~/ui/components/ui/dialog' import * as schemas from '~/schemas' import {Button} from '~/ui/components/ui/button' import {api} from '~/trpc/react' import {promptValue} from '~/app/_components/Prompts' import {toast} from '~/ui/components/ui/use-toast' import {Separator} from '~/ui/components/ui/separator' import Members from './TeamMembers' import {promptInviteMembers} from './InviteTeamMembersPrompt' import {useRouter} from 'next/navigation' const TeamSettingsPrompt: FC<{id: string} & PromptProps> = ({ id, done, }) => { const team = api.teams.get.useQuery({id}).data! const updateTeam = api.teams.update.useMutation().mutateAsync const deleteTeam = api.teams.delete.useMutation().mutateAsync const queryUtils = api.useUtils() const router = useRouter() return ( <> Team Settings
Team name
{team.name}

Members

Loading...
}> ) } export const promptTeamSettings = (id: string) => prompt((done) => ) ================================================ FILE: packages/app/src/app/(protected)/_components/WorkspaceThumb.tsx ================================================ import {cn} from '~/ui/lib/utils' import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger, } from '~/ui/components/ui/context-menu' interface WorkspaceThumbProps extends React.HTMLAttributes { name: string description: string thumbnail: string onDelete?: () => void onDuplicate?: () => void onInvite?: () => void onEdit?: () => void allowEdit?: boolean } export default function WorkspaceThumb({ name, description, thumbnail, onDelete, onDuplicate, onInvite, onEdit, allowEdit, className, ...props }: WorkspaceThumbProps) { return (
Invite collaborators Edit Duplicate Delete

{name}

{description}

) } ================================================ FILE: packages/app/src/app/(protected)/account-setup/_components/AccountSetupForm.tsx ================================================ 'use client' import React from 'react' import {useForm} from 'react-hook-form' import * as z from 'zod' import {zodResolver} from '@hookform/resolvers/zod' import * as schemas from 'src/schemas' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from 'src/ui/components/ui/form' import {Input} from 'src/ui/components/ui/input' import {Button} from 'src/ui/components/ui/button' import {api} from '~/trpc/react' import {useRouter} from 'next/router' const formSchema = z.object({ name: schemas.personLegalName, email: schemas.email, }) export default function AccountSetupForm({ name, email, }: { name: string email: string }) { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { name, email, }, }) const {mutateAsync} = api.me.update.useMutation() const router = useRouter() async function onSubmit(values: z.infer) { try { await mutateAsync({ name: values.name, email: values.email, }) void router.replace('/') } catch (error) { console.error(error) } } return (
( Name This is your public display name. )} /> ( Email Your email address. People can use this to invite you to teams or workspaces. )} /> ) } ================================================ FILE: packages/app/src/app/(protected)/account-setup/page.tsx ================================================ 'use client' import React from 'react' import {api} from '~/trpc/server' import AccountSetupForm from './_components/AccountSetupForm' export default async function AccountSetupPage() { const user = await api.me.get.query()! return (
) } ================================================ FILE: packages/app/src/app/(protected)/layout.tsx ================================================ import {getAppSession} from '~/utils/authUtils' import Navigation from './_components/Navigation' import SessionProvider from './_components/SessionProvider' import {redirect} from 'next/navigation' import {Suspense} from 'react' import prisma from '~/prisma' import {type Session} from 'next-auth' export default async function ProtectedLayout({ children, }: { children: React.ReactNode }) { const session = await getAppSession() if (!session) { redirect('/api/auth/signin') } await ensureSetupComplete(session) return (
{children}
) } async function ensureSetupComplete(session: Session) { const user = await prisma.user.findUnique({ where: { id: session.user.id, }, include: { teams: true, }, }) // If no team, create one if (user!.teams.length === 0) { await prisma.team.create({ data: { name: user?.name ? `${user?.name}'s Team` : 'My Team', members: { create: { userId: user!.id, userRole: 'OWNER', accepted: true, }, }, }, }) } const setupIncomplete = !user?.email || !user?.name if (setupIncomplete) { redirect('/account-setup') } } ================================================ FILE: packages/app/src/app/(protected)/recents/page.tsx ================================================ export default function Recents() { return (

Recents

This page is under construction
) } ================================================ FILE: packages/app/src/app/(protected)/shared-with-me/_components/Shared.tsx ================================================ 'use client' import {api} from '~/trpc/react' import WorkspaceThumb from '../../_components/WorkspaceThumb' export default function Shared() { const workspaces = api.workspaces.getAll.useQuery().data! const sharedWorkspaces = workspaces.filter( (workspace) => workspace.accessType === 'GUEST', ) return (

Shared with me

{sharedWorkspaces?.map((workspace) => ( <> ))}
) } ================================================ FILE: packages/app/src/app/(protected)/shared-with-me/page.tsx ================================================ import {Suspense} from 'react' import Shared from './_components/Shared' export default function SharedWithME() { return (
) } ================================================ FILE: packages/app/src/app/(protected)/team/[id]/components/Team.tsx ================================================ 'use client' import {useState} from 'react' import WorkspaceThumb from '~/app/(protected)/_components/WorkspaceThumb' import {api} from '~/trpc/react' import NewWorkspaceDialog from '~/app/(protected)/_components/NewWorkspaceDialog' import EditWorkspaceDialog from '~/app/(protected)/_components/EditWorkspaceDialog' import {useToast} from '~/ui/components/ui/use-toast' import InviteGuestsDialog from '~/app/(protected)/_components/InviteGuestsDialog' import {Button} from '~/ui/components/ui/button' import {Settings} from 'lucide-react' import {promptTeamSettings} from '~/app/(protected)/_components/TeamSettingsPrompt' export default function Team({id}: {id: string}) { const {data: team} = api.teams.get.useQuery({id}) const me = api.me.get.useQuery().data! const {mutateAsync: deleteWorkspace} = api.workspaces.delete.useMutation() const {mutateAsync: duplicateWorkspace} = api.workspaces.duplicate.useMutation() const queryUtils = api.useUtils() const workspaces = team?.workspaces const [editingWorkspace, setEditingWorkspace] = useState(null) const [invitingGuests, setInvitingGuests] = useState(null) const isOwner = team?.members.find((member) => member.id === me.id)?.role === 'OWNER' const {toast} = useToast() return (

{team?.name}

{isOwner && ( )}
{workspaces?.map((workspace) => ( <> { await deleteWorkspace({ id: workspace.id, safety: `delete ${workspace.name}`, }) void queryUtils.teams.invalidate() void queryUtils.workspaces.invalidate() }} onEdit={() => setEditingWorkspace(workspace.id)} onDuplicate={async () => { try { await duplicateWorkspace({id: workspace.id}) void queryUtils.teams.invalidate() void queryUtils.workspaces.invalidate() } catch (error) { toast({ variant: 'destructive', title: 'Uh oh! Something went wrong.', description: "Couldn't duplicate the workspace.", }) } }} onInvite={() => setInvitingGuests(workspace.id)} /> { if (!open) { setEditingWorkspace(null) } }} /> { if (!open) { setInvitingGuests(null) } }} /> ))}
) } ================================================ FILE: packages/app/src/app/(protected)/team/[id]/not-found.tsx ================================================ import Link from 'next/link' export default async function NotFound() { return (

Team not found

Could not find requested team

View Home

) } ================================================ FILE: packages/app/src/app/(protected)/team/[id]/page.tsx ================================================ import {Suspense} from 'react' import Team from './components/Team' import {api} from '~/trpc/server' import { redirect} from 'next/navigation' export default async function TeamPage({params: {id}}: {params: {id: string}}) { try { const team = await api.teams.get.query({id}) } catch (error) { // Ideally this would be notFound(), but that breaks for some reason and results in a not a server component error, when it is clearly one redirect('/') } return (
) } ================================================ FILE: packages/app/src/app/(protected)/workspace/[id]/page.tsx ================================================ export default function WorkspacePage() { return
workspace
} ================================================ FILE: packages/app/src/app/(public)/_components/SignIn.tsx ================================================ 'use client' import {signIn} from 'next-auth/react' export default function SignIn() { return } ================================================ FILE: packages/app/src/app/(public)/page.tsx ================================================ import SignIn from './_components/SignIn' import {getAppSession} from '~/utils/authUtils' import {redirect} from 'next/navigation' import {api} from '~/trpc/server' export default async function IndexPage() { const session = await getAppSession() if (session) { const teams = await api.teams.getAll.query() redirect(`/team/${teams[0].id}`) } return (
) } ================================================ FILE: packages/app/src/app/(public)/somethingpublic/page.tsx ================================================ export default function Something() { return
something
} ================================================ FILE: packages/app/src/app/_components/Prompts.tsx ================================================ 'use client' import {Suspense} from 'react' import {type CallbackFn, createPrompter} from 'react-promptify' import {type ZodString, z} from 'zod' import {useForm} from 'react-hook-form' import {zodResolver} from '@hookform/resolvers/zod' import {Button} from '~/ui/components/ui/button' import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from '~/ui/components/ui/dialog' import { FormItem, FormField, FormMessage, FormControl, Form, FormLabel, } from '~/ui/components/ui/form' import {Input} from '~/ui/components/ui/input' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '~/ui/components/ui/alert-dialog' const {Prompter, prompt} = createPrompter() const {Prompter: AlertPrompter, prompt: alert} = createPrompter() export {prompt, alert} export default function Prompts() { return ( <> {({children, open, cancel}) => ( open || cancel()}> {children} )} {({children, open}) => ( {children} )} ) } export type PromptProps = {done: CallbackFn} // A component returning a Dialog with a Form that has one field, a string input const PromptString = ({ done, message, defaultValue, label, schema, }: PromptProps & { message: string defaultValue: string label?: string schema: ZodString }) => { const formSchema = z.object({ value: schema, }) const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { value: defaultValue, }, }) function onSubmit(values: z.infer) { done(values.value) } return ( <> <> {message}
( {label && {label}} )} />
) } export const promptValue = { string: ( message: string, options?: {defaultValue?: string; label?: string; schema?: ZodString}, ) => { const {defaultValue = '', label, schema = z.string()} = options ?? {} return prompt((done) => ( )) }, } export const confirm = async (title: string, description: string) => { return alert((done) => ( <> {title} {description} done(false)}> Cancel done(true)}> Continue )) as Promise } ================================================ FILE: packages/app/src/app/api/auth/[...nextauth]/route.ts ================================================ import NextAuth from 'next-auth' import {nextAuthConfig} from 'src/utils/authUtils' const handler = NextAuth(nextAuthConfig) export {handler as GET, handler as POST} ================================================ FILE: packages/app/src/app/api/jwt-public-key/route.ts ================================================ import {NextResponse} from 'next/server' import {allowCors} from '~/utils' async function handler(req: Request) { if (req.method === 'OPTIONS') { const res = new Response(null, {status: 204}) allowCors(res) return res } const res = NextResponse.json({ publicKey: process.env.STUDIO_AUTH_JWT_PUBLIC_KEY, }) allowCors(res) return res } export {handler as GET, handler as OPTIONS} ================================================ FILE: packages/app/src/app/api/studio-auth/route.ts ================================================ import type {NextRequest} from 'next/server' import {NextResponse} from 'next/server' import prisma from 'src/prisma' import {getAppSession, studioAuth} from 'src/utils/authUtils' import {userCodeLength} from '~/server/studio-api/routes/studioAuthRouter' import {studioAccessScopes} from '~/types' import {type $IntentionalAny} from '@theatre/utils/types' export const dynamic = 'force-dynamic' async function libAuth(req: NextRequest) { const userCode = req.nextUrl.searchParams.get('userCode') if (typeof userCode !== 'string' || userCode.length !== userCodeLength) { return NextResponse.json( { error: `userCode must be a string of length ${userCodeLength}`, }, {status: 400}, ) } const row = await prisma.deviceAuthorizationFlow.findFirst({ where: { userCode, }, }) if (row === null) { return NextResponse.json( { error: 'This authentication flow either does not exist, or has already been used. Try again from the studio.', }, {status: 404}, ) } const session = await getAppSession() // if no session, redirect to login if (!session || !session.user) { console.log('s', req.nextUrl.host, req.nextUrl.hostname, req.nextUrl.origin) const redirectUrl = new URL( `/api/auth/signin?callbackUrl=${encodeURIComponent( req.nextUrl.toString(), )}`, req.nextUrl.origin, ) return NextResponse.redirect(redirectUrl) } if (row.state === 'tokenAlreadyUsed') { return NextResponse.json( { error: 'This authentication flow has already been used. Try again from the studio.', }, {status: 400}, ) } if (row.state === 'userDeniedAuth') { return NextResponse.json( { error: 'This authentication flow has been denied by the user. Try again from the studio.', }, {status: 400}, ) } if (row.state === 'userAllowedAuth') { return NextResponse.json( { error: 'This authentication flow has already been used. Try again from the studio.', }, {status: 400}, ) } if (row.state !== 'initialized') { return NextResponse.json( { error: `This authentication flow is in an invalid state. Try again from the studio.`, }, {status: 500}, ) } const user = session.user const nounce = row.nounce const scopes = row.scopes if (!studioAccessScopes.scopes.parse(scopes)) { console.error(`bad scopes`, scopes) await prisma.deviceAuthorizationFlow.delete({ where: {deviceCode: row.deviceCode}, }) return NextResponse.json( { error: `This authentication flow is in an invalid state. Try again from the studio.`, }, {status: 500}, ) } const {refreshToken, accessToken} = await studioAuth.createSession( nounce, user, scopes as $IntentionalAny, ) await prisma.deviceAuthorizationFlow.update({ where: { deviceCode: row.deviceCode, }, data: { state: 'userAllowedAuth', tokens: JSON.stringify({ accessToken, refreshToken, }), }, }) return NextResponse.json('success', {status: 200}) } export {libAuth as GET} ================================================ FILE: packages/app/src/app/api/studio-trpc/[trpc]/route.ts ================================================ import {fetchRequestHandler} from '@trpc/server/adapters/fetch' import type {NextRequest} from 'next/server' import {createTRPCContext} from '~/server/api/trpc' import {studioTrpcRouter} from '~/server/studio-api/root' import {allowCors} from '~/utils' // we don't need the trpc routes' responses to be cached export const dynamic = 'force-dynamic' const handler = async (req: NextRequest) => { if (req.method === 'OPTIONS') { const res = new Response(null, { status: 204, }) allowCors(res) return res } const res = await fetchRequestHandler({ endpoint: '/api/studio-trpc', req, router: studioTrpcRouter, createContext: () => createTRPCContext(), onError: process.env.NODE_ENV === 'development' ? ({path, error}) => { console.error( `❌ studio-trpc failed on ${path ?? ''}: ${ error.message }`, ) } : undefined, }) allowCors(res) return res } export {handler as GET, handler as POST, handler as OPTIONS} ================================================ FILE: packages/app/src/app/api/trpc/[trpc]/route.ts ================================================ import {fetchRequestHandler} from '@trpc/server/adapters/fetch' import type {NextRequest} from 'next/server' import {appRouter} from '~/server/api/root' import {createTRPCContext} from '~/server/api/trpc' // we don't need the trpc routes' responses to be cached export const dynamic = 'force-dynamic' const handler = async (req: NextRequest) => { const res = await fetchRequestHandler({ endpoint: '/api/trpc', req, router: appRouter, createContext: () => createTRPCContext(), onError: process.env.NODE_ENV === 'development' ? ({path, error}) => { console.error( `❌ tRPC failed on ${path ?? ''}: ${error.message}`, ) } : undefined, }) return res } export {handler as GET, handler as POST, handler as OPTIONS} ================================================ FILE: packages/app/src/app/global.css ================================================ @tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { --background: 0 0% 100%; --foreground: 240 10% 3.9%; --card: 0 0% 100%; --card-foreground: 240 10% 3.9%; --popover: 0 0% 100%; --popover-foreground: 240 10% 3.9%; --primary: 240 5.9% 10%; --primary-foreground: 0 0% 98%; --secondary: 240 4.8% 95.9%; --secondary-foreground: 240 5.9% 10%; --muted: 240 4.8% 95.9%; --muted-foreground: 240 3.8% 46.1%; --accent: 240 4.8% 95.9%; --accent-foreground: 240 5.9% 10%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%; --border: 240 5.9% 90%; --input: 240 5.9% 90%; --ring: 240 10% 3.9%; --radius: 0.5rem; } .dark { --background: 240 10% 3.9%; --foreground: 0 0% 98%; --card: 240 10% 3.9%; --card-foreground: 0 0% 98%; --popover: 240 10% 3.9%; --popover-foreground: 0 0% 98%; --primary: 0 0% 98%; --primary-foreground: 240 5.9% 10%; --secondary: 240 3.7% 15.9%; --secondary-foreground: 0 0% 98%; --muted: 240 3.7% 15.9%; --muted-foreground: 240 5% 64.9%; --accent: 240 3.7% 15.9%; --accent-foreground: 0 0% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 0 0% 98%; --border: 240 3.7% 15.9%; --input: 240 3.7% 15.9%; --ring: 240 4.9% 83.9%; } } @layer base { * { @apply border-border; } body { @apply bg-background text-foreground; } } ================================================ FILE: packages/app/src/app/layout.tsx ================================================ import {headers} from 'next/headers' import {TRPCReactProvider} from '~/trpc/react' import './global.css' import {Toaster} from '~/ui/components/ui/toaster' import Prompts from './_components/Prompts' export default async function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ) } ================================================ FILE: packages/app/src/env.d.ts ================================================ import type {Env} from './envSchema' declare global { namespace NodeJS { interface ProcessEnv extends Env {} } } ================================================ FILE: packages/app/src/env.test.ts ================================================ import * as envSchema from './envSchema' import * as fs from 'fs' import * as path from 'path' import * as dotenv from 'dotenv' import * as yaml from 'yaml' describe(`@theatre/app env`, () => { describe(`.env files`, () => { test(`.env.example should be valid`, () => { const pathToEnvExample = path.join(__dirname, '../.env.example') const envAsString = fs.readFileSync(pathToEnvExample, {encoding: 'utf8'}) // this should use an INI parser const envAsObject = dotenv.parse(envAsString) envSchema.devSchema.parse(envAsObject) }) test(`.env should be valid (if it exists)`, () => { const pathToEnvExample = path.join(__dirname, '../.env') if (!fs.existsSync(pathToEnvExample)) { return } const envAsString = fs.readFileSync(pathToEnvExample, {encoding: 'utf8'}) // this should use an INI parser const envAsObject = dotenv.parse(envAsString) envSchema.devSchema.parse(envAsObject) }) }) describe(`render.yaml`, () => { test(`should include all the env variables required by the production schema`, () => { const pathToRenderYaml = path.join(__dirname, '../../../render.yaml') const yamlContent = yaml.parse( fs.readFileSync(pathToRenderYaml, {encoding: 'utf8'}), ) const appService = yamlContent.services.find( (service: any) => service.name === 'app', ) if (!appService) { throw new Error(`app service not found`) } const envVars: Array<{key: string}> = appService.envVars const envVarKeys = envVars.map((envVar) => envVar.key) // PORT is automatically added by render envVarKeys.push('PORT') const requiredKeys = Object.keys(envSchema.productionSchema.shape) envVarKeys.sort() requiredKeys.sort() // make sure envVarKeys and requiredKeys have all the same values, and no more expect(envVarKeys).toEqual(requiredKeys) }) }) }) ================================================ FILE: packages/app/src/envSchema.ts ================================================ import z from 'zod' // the env variables that are only required in development const devOnly = z.object({ NODE_ENV: z.literal('development'), DEV_DB_PORT: z.string(), DEV_DB_PASSWORD: z.string(), }) // the env variables that both development and production require const commonSchema = z.object({ DATABASE_URL: z.string(), GITHUB_ID: z.string(), GITHUB_SECRET: z.string(), STUDIO_AUTH_JWT_PRIVATE_KEY: z .string() .startsWith('-----BEGIN PRIVATE KEY-----') .endsWith('-----END PRIVATE KEY-----'), STUDIO_AUTH_JWT_PUBLIC_KEY: z .string() .startsWith('-----BEGIN PUBLIC KEY-----') .endsWith('-----END PUBLIC KEY-----'), PORT: z.string().regex(/^\d+$/), HOST: z.string(), NEXT_PUBLIC_WEBAPP_URL: z.string().url(), }) // the env variables that are required in development (devOnly and commonSchema) export const devSchema = commonSchema.merge(devOnly) // the env variables that are only required in production const productionOnly = z.object({ NODE_ENV: z.literal('production'), }) // the env variables that are required in production (productionOnly + commonSchema) export const productionSchema = commonSchema.merge(productionOnly) // the env variables that are required in both development and production export const fullSchema = productionSchema.merge(devOnly) export type Env = z.infer ================================================ FILE: packages/app/src/prisma.ts ================================================ import {PrismaClient} from '../prisma/client-generated' const prisma = new PrismaClient({ datasources: { db: { url: process.env.DATABASE_URL, }, }, }) export default prisma ================================================ FILE: packages/app/src/schemas/index.ts ================================================ import * as z from 'zod' export const personLegalName = z .string() .min(2, { message: 'Name is too short', }) .max(50, { message: 'Name is too long', }) export const email = z.string().email({ message: 'Invalid email', }) export const workspaceName = z .string() .min(2, { message: 'Name is too short', }) .max(50, { message: 'Name is too long', }) export const teamName = z .string() .min(2, { message: 'Name is too short', }) .max(50, { message: 'Name is too long', }) export const workspaceDescription = z.string().max(500, { message: 'Description is over 500 characters', }) ================================================ FILE: packages/app/src/server/api/root.ts ================================================ import * as t from './trpc' import {projectsRouter} from './routes/projectsRouter' import {workspaceRouter} from './routes/workspaceRouter' import {teamsRouter} from './routes/teamsRouter' import {meRouter} from './routes/meRouter' export const appRouter = t.createRouter({ projects: projectsRouter, workspaces: workspaceRouter, teams: teamsRouter, me: meRouter, }) export type AppRouter = typeof appRouter ================================================ FILE: packages/app/src/server/api/routes/meRouter.ts ================================================ import {z} from 'zod' import * as t from '../trpc' import prisma from 'src/prisma' export const meRouter = t.createRouter({ get: t.protectedProcedure .output( z .object({ id: z.string(), email: z.string().nullable(), name: z.string().nullable(), image: z.string().nullable(), }) .strict(), ) .query(async ({ctx, input}) => { const {session} = ctx const user = session.user return { id: user.id, email: user.email, name: user.name, image: user.image, } }), update: t.protectedProcedure .input( z .object({ email: z.string().email(), name: z.string(), image: z.string().url(), }) .partial(), ) .mutation(async ({ctx, input}) => { const {session} = ctx const user = session.user const updatedUser = await prisma.user.update({ where: {id: user.id}, data: { email: input.email, name: input.name, image: input.image, }, }) }), delete: t.protectedProcedure .input(z.object({safety: z.literal('DELETE')})) .mutation(async ({ctx}) => { const {session} = ctx const user = session.user await prisma.$transaction([ // Delete all teams that are left empty after the user is deleted prisma.team.deleteMany({ where: { members: { every: { userId: user.id, }, }, }, }), prisma.user.delete({ where: {id: user.id}, }), ]) }), getGuestInvitations: t.protectedProcedure.query(async ({ctx}) => { const {session} = ctx const user = session.user const guestInvitations = await prisma.guestAccess.findMany({ where: { userId: user.id, accepted: false, }, include: { workspace: { select: { id: true, name: true, }, }, }, }) return guestInvitations.map((guestInvitation) => ({ workspaceId: guestInvitation.workspaceId, workspaceName: guestInvitation.workspace.name, accessLevel: guestInvitation.accessLevel, })) }), getTeamInvitations: t.protectedProcedure.query(async ({ctx}) => { const {session} = ctx const user = session.user const teamInvitations = await prisma.teamMember.findMany({ where: { userId: user.id, accepted: false, }, include: { team: { select: { id: true, name: true, }, }, }, }) return teamInvitations.map((teamInvitation) => ({ teamId: teamInvitation.teamId, teamName: teamInvitation.team.name, role: teamInvitation.userRole, })) }), }) ================================================ FILE: packages/app/src/server/api/routes/projectsRouter.ts ================================================ import {z} from 'zod' import {studioAuth} from 'src/utils/authUtils' import {v4} from 'uuid' import * as t from '../trpc' export const projectsRouter = t.createRouter({ create: t.publicProcedure .input(z.object({studioAuth: studioAuth.input})) .output(z.object({id: z.string()})) .mutation(async (opts) => { const s = await studioAuth.verifyStudioAccessTokenOrThrow(opts) const {userId} = s const id = v4() + '-' + v4() // await prisma.project.create({data: {id, userId, name: ''}}) return {id} }), }) ================================================ FILE: packages/app/src/server/api/routes/teamsRouter.ts ================================================ import {z} from 'zod' import * as t from '../trpc' import prisma from 'src/prisma' import {TRPCError} from '@trpc/server' export const teamsRouter = t.createRouter({ get: t.protectedProcedure .input(z.object({id: z.string()})) .output( z .object({ id: z.string(), name: z.string(), members: z.array( z.object({ id: z.string(), name: z.string().nullable(), email: z.string().nullable(), role: z.string(), }), ), workspaces: z.array( z.object({ id: z.string(), name: z.string(), description: z.string(), }), ), }) .strict(), ) .query(async ({ctx, input}) => { const {id} = input const {session} = ctx const userId = session.user.id const team = await prisma.team.findFirst({ where: { id, members: { some: { userId, }, }, }, include: { members: { include: { user: { select: { id: true, name: true, email: true, }, }, }, }, workspaces: true, }, }) if (!team) { throw new TRPCError({code: 'NOT_FOUND'}) } const clientData = { id: team.id, name: team.name, members: team.members.map((member) => { return { id: member.user.id, name: member.user.name, email: member.user.email, role: member.userRole, } }), workspaces: team.workspaces.map((workspace) => { return { id: workspace.id, name: workspace.name, description: workspace.description, } }), } return clientData }), getAll: t.protectedProcedure .output( z.array( z .object({ id: z.string(), name: z.string(), members: z.array( z.object({ id: z.string(), name: z.string().nullable(), email: z.string().nullable(), role: z.string(), accepted: z.boolean(), }), ), workspaces: z.array( z.object({ id: z.string(), name: z.string(), description: z.string(), }), ), }) .strict(), ), ) .query(async ({ctx, input}) => { const {session} = ctx const userId = session.user.id const teams = await prisma.team.findMany({ where: { members: { some: { userId, }, }, }, include: { members: { include: { user: { select: { id: true, name: true, email: true, }, }, }, }, workspaces: true, }, }) const clientData = teams.map((team) => { return { id: team.id, name: team.name, members: team.members.map((member) => { return { id: member.user.id, name: member.user.name, email: member.user.email, role: member.userRole, accepted: member.accepted, } }), workspaces: team.workspaces.map((workspace) => { return { id: workspace.id, name: workspace.name, description: workspace.description, } }), } }) return clientData }), create: t.protectedProcedure .input( z.object({ name: z.string(), }), ) .output(z.object({id: z.string()}).strict()) .mutation(async ({ctx, input}) => { const {name} = input const {session} = ctx const userId = session.user.id const team = await prisma.team.create({ data: { name, members: { create: { user: { connect: { id: userId, }, }, userRole: 'OWNER', accepted: true, }, }, }, }) return {id: team.id} }), update: t.protectedProcedure .input( z.object({ id: z.string(), name: z.string().optional(), }), ) .output( z.object({ id: z.string(), name: z.string(), }), ) .mutation(async ({ctx, input}) => { const {id, name} = input const {session} = ctx const userId = session.user.id const team = await prisma.team.findUnique({ where: { id, }, select: { members: { where: { userId, }, }, }, }) // Only team owners are allowed to update teams const updateAllowed = team?.members[0]?.userRole === 'OWNER' if (!updateAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } const newTeam = await prisma.team.update({ where: { id, }, data: { name, }, }) return { id: newTeam.id, name: newTeam.name, } }), delete: t.protectedProcedure .input(z.object({id: z.string(), safety: z.string()})) .mutation(async ({ctx, input}) => { const {id, safety} = input const {session} = ctx const userId = session.user.id const team = await prisma.team.findUnique({ where: { id, }, select: { name: true, members: { where: { userId, }, }, }, }) if (safety !== `delete ${team?.name}`) { throw new TRPCError({code: 'BAD_REQUEST'}) } // Only team owners are allowed to delete teams const deleteAllowed = team?.members[0]?.userRole === 'OWNER' if (!deleteAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } await prisma.team.delete({ where: { id, }, }) }), inviteMembers: t.protectedProcedure .input( z.object({ id: z.string(), invites: z.array( z.object({ email: z.string().email(), role: z.enum(['OWNER', 'MEMBER']), }), ), }), ) .mutation(async ({ctx, input}) => { const {id, invites} = input const emails = invites.map((invites) => invites.email) const {session} = ctx const userId = session.user.id const team = await prisma.team.findUnique({ where: { id, }, select: { members: { where: { userId, }, }, }, }) // Make sure users exist const invitedUsers = await prisma.user.findMany({ where: { email: { in: emails, }, }, }) if (invitedUsers.length !== emails.length) { throw new TRPCError({code: 'BAD_REQUEST'}) } // Only team owners are allowed to invite members const inviteAllowed = team?.members[0]?.userRole === 'OWNER' if (!inviteAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } await prisma.team.update({ where: { id, }, data: { members: { upsert: invites.map((invite) => { const invitedUser = invitedUsers.find( (user) => user.email === invite.email, )! return { where: { userId_teamId: { userId: invitedUser.id, teamId: id, }, }, create: { user: { connect: { id: invitedUser.id, }, }, userRole: invite.role, }, update: {}, } }), }, }, }) }), removeMember: t.protectedProcedure .input(z.object({id: z.string(), email: z.string()})) .mutation(async ({ctx, input}) => { const {id, email} = input const {session} = ctx const currentUserId = session.user.id const team = await prisma.team.findUnique({ where: { id, }, select: { members: { where: { userId: currentUserId, }, include: { user: { select: { email: true, }, }, }, }, }, }) // Only team owners are allowed to remove members, or the member themselves const removeAllowed = team?.members[0]?.userRole === 'OWNER' || team?.members[0]?.user.email === email if (!removeAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } const userToRemove = await prisma.user.findUnique({ where: { email, }, }) await prisma.team.update({ where: { id, }, data: { members: { delete: { userId_teamId: { userId: userToRemove?.id!, teamId: id, }, }, }, }, }) }), changeMemberRole: t.protectedProcedure .input( z.object({ id: z.string(), email: z.string(), role: z.enum(['OWNER', 'MEMBER']), }), ) .mutation(async ({ctx, input}) => { const {id, email, role} = input const {session} = ctx const currentUserId = session.user.id const team = await prisma.team.findUnique({ where: { id, }, select: { members: { where: { userId: currentUserId, }, include: { user: { select: { email: true, }, }, }, }, }, }) // Only team owners are allowed to change member roles and a team owner cannot demote themselves const changeAllowed = team?.members[0]?.userRole === 'OWNER' && email !== team?.members[0]?.user.email if (!changeAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } const userToChange = await prisma.user.findUnique({ where: { email, }, }) await prisma.team.update({ where: { id, }, data: { members: { update: { where: { userId_teamId: { userId: userToChange?.id!, teamId: id, }, }, data: { userRole: role, }, }, }, }, }) }), acceptInvite: t.protectedProcedure .input(z.object({id: z.string()})) .mutation(async ({ctx, input}) => { const {id} = input const {session} = ctx const userId = session.user.id const team = await prisma.team.findUnique({ where: { id, }, select: { members: { where: { userId, }, }, }, }) // Only team members are allowed to accept invites const acceptAllowed = team?.members.length !== 0 console.log(acceptAllowed) if (!acceptAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } await prisma.teamMember.update({ where: { userId_teamId: { userId, teamId: id, }, }, data: { accepted: true, }, }) }), getMembers: t.protectedProcedure .input(z.object({id: z.string()})) .output( z.array( z.object({ email: z.string(), name: z.string().nullable(), image: z.string().nullable(), role: z.enum(['OWNER', 'MEMBER']), accepted: z.boolean(), }), ), ) .query(async ({ctx, input}) => { const {id} = input const {session} = ctx const userId = session.user.id const team = await prisma.team.findFirst({ where: { id, members: { some: { userId, }, }, }, include: { members: { include: { user: { select: { email: true, name: true, image: true, }, }, }, }, }, }) if (!team) { throw new TRPCError({code: 'NOT_FOUND'}) } const clientData = team.members.map((member) => { return { email: member.user.email!, name: member.accepted ? member.user.name : null, image: member.accepted ? member.user.image : null, role: member.userRole, accepted: member.accepted, } }) return clientData }), }) ================================================ FILE: packages/app/src/server/api/routes/workspaceRouter.ts ================================================ import {z} from 'zod' import * as t from '../trpc' import prisma from '../../../prisma' import {TRPCError} from '@trpc/server' export const workspaceRouter = t.createRouter({ get: t.protectedProcedure .input(z.object({id: z.string()})) .output( z.object({ id: z.string(), name: z.string(), description: z.string(), accessType: z.enum(['TEAM', 'GUEST', 'GUEST_PENDING']), accessLevel: z.string(), }), ) .query(async (opts) => { const {id} = opts.input const {session} = opts.ctx const userId = session.user.id const workspace = await prisma.workspace.findUnique({ where: { id, }, include: { team: { select: { members: { where: { userId, }, }, }, }, guests: { where: { userId, }, }, }, }) if (!workspace) { throw new TRPCError({code: 'NOT_FOUND'}) } const isGuest = workspace?.guests.length !== 0 // Only team members and guests are allowed to view workspaces const allowed = workspace?.team?.members.length !== 0 || isGuest if (!allowed) { throw new TRPCError({code: 'FORBIDDEN'}) } // workspace including guests const clientData = { id: workspace.id, name: workspace.name, description: workspace?.description, accessType: !isGuest ? ('TEAM' as const) : workspace?.guests[0].accepted ? ('GUEST' as const) : ('GUEST_PENDING' as const), accessLevel: isGuest ? workspace?.guests[0].accessLevel : 'READ_WRITE', } return clientData }), getAll: t.protectedProcedure .output( z.array( z .object({ id: z.string(), name: z.string(), description: z.string(), accessType: z.enum(['TEAM', 'GUEST', 'GUEST_PENDING']), accessLevel: z.string(), }) .strict(), ), ) .query(async ({ctx, input}) => { const {session} = ctx const userId = session.user.id const workspaces = await prisma.workspace.findMany({ where: { OR: [ { guests: { some: { userId, }, }, }, { team: { members: { some: { userId, }, }, }, }, ], }, include: { guests: { where: { userId, }, }, }, }) const clientData = workspaces.map((workspace) => { const guest = workspace.guests.find((guest) => guest.userId === userId) const filtered = { id: workspace.id, name: workspace.name, description: workspace.description, accessType: !guest ? ('TEAM' as const) : guest.accepted ? ('GUEST' as const) : ('GUEST_PENDING' as const), } // If guest, they have the access level defined in the guest settings if (guest) { return { ...filtered, accessLevel: guest.accessLevel, } } // If not guest, they have read/write access because they are a team member return { ...filtered, accessLevel: 'READ_WRITE', } }) return clientData }), create: t.protectedProcedure .input( z.object({name: z.string(), description: z.string(), teamId: z.string()}), ) .output(z.object({id: z.string()})) .mutation(async (opts) => { const {name, description, teamId} = opts.input const {session} = opts.ctx const userId = session.user.id const team = await prisma.team.findUnique({ where: { id: teamId, }, select: { members: { where: { userId, }, }, }, }) // Only team members are allowed to create workspaces const createAllowed = team?.members.length !== 0 if (!createAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } const workspace = await prisma.workspace.create({ data: { name, description, teamId, }, }) return {id: workspace.id} }), duplicate: t.protectedProcedure .input(z.object({id: z.string()})) .mutation(async (opts) => { const {id} = opts.input const {session} = opts.ctx const userId = session.user.id const workspaceToDuplicate = await prisma.workspace.findUnique({ where: { id, }, }) if (!workspaceToDuplicate) { throw new TRPCError({code: 'NOT_FOUND'}) } const team = await prisma.team.findUnique({ where: { id: workspaceToDuplicate?.teamId, }, select: { members: { where: { userId, }, }, }, }) // Only team members are allowed to duplicate workspaces const createAllowed = team?.members.length !== 0 if (!createAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } const workspace = await prisma.workspace.create({ data: { name: `${workspaceToDuplicate?.name} (Copy)`, description: workspaceToDuplicate?.description, teamId: workspaceToDuplicate?.teamId, }, }) return {id: workspace.id} }), update: t.protectedProcedure .input( z.object({ id: z.string(), name: z.string().optional(), description: z.string().optional(), }), ) .mutation(async (opts) => { const {id, name, description} = opts.input const {session} = opts.ctx const userId = session.user.id const workspace = await prisma.workspace.findUnique({ where: { id, }, select: { team: { select: { members: { where: { userId, }, }, }, }, guests: { where: { userId, }, }, }, }) // Team members and guests with write access are allowed to edit workspaces const editAllowed = workspace?.team?.members.length !== 0 || workspace?.guests[0]?.accessLevel === 'READ_WRITE' if (!editAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } await prisma.workspace.update({ where: { id, }, data: { name, description, }, }) }), delete: t.protectedProcedure .input(z.object({id: z.string(), safety: z.string()})) .mutation(async (opts) => { const {id, safety} = opts.input const {session} = opts.ctx const userId = session.user.id const workspace = await prisma.workspace.findUnique({ where: { id, }, select: { name: true, team: { select: { members: { where: { userId, }, }, }, }, guests: { where: { userId, }, }, }, }) if (safety !== `delete ${workspace?.name}`) { throw new TRPCError({code: 'BAD_REQUEST'}) } // Only team members are allowed to remove workspaces const deleteAllowed = workspace?.team?.members.length !== 0 if (!deleteAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } await prisma.workspace.delete({ where: { id, }, }) }), inviteGuests: t.protectedProcedure .input( z.object({ id: z.string(), invites: z.array( z.object({ email: z.string(), accessLevel: z.enum(['READ', 'READ_WRITE']), }), ), }), ) .mutation(async (opts) => { const {id, invites} = opts.input const emails = invites.map((invite) => invite.email) const {session} = opts.ctx const userId = session.user.id const workspace = await prisma.workspace.findUnique({ where: { id, }, select: { team: { select: { members: { where: { userId, }, }, }, }, guests: { where: { userId, }, }, }, }) // Only team members are allowed to invite guests const inviteAllowed = workspace?.team?.members.length !== 0 if (!inviteAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } // Make sure users exist const invitedUsers = await prisma.user.findMany({ where: { email: { in: emails, }, }, }) if (invitedUsers.length !== emails.length) { throw new TRPCError({code: 'BAD_REQUEST'}) } // Create guest access records await prisma.workspace.update({ where: { id, }, data: { guests: { upsert: invites.map((invite) => { const invitedUser = invitedUsers.find( (user) => user.email === invite.email, )! return { where: { userId_workspaceId: { userId: invitedUser.id, workspaceId: id, }, }, create: { user: { connect: { id: invitedUser.id, }, }, accessLevel: invite.accessLevel, }, update: {}, } }), }, }, }) }), removeGuest: t.protectedProcedure .input(z.object({id: z.string(), email: z.string()})) .mutation(async (opts) => { const {id, email} = opts.input const {session} = opts.ctx const userId = session.user.id const workspace = await prisma.workspace.findUnique({ where: { id, }, select: { team: { select: { members: { where: { userId, }, }, }, }, guests: { where: { userId, }, select: { userId: true, user: { select: { email: true, }, }, }, }, }, }) // Team members can remove guests, or the guest can remove themselves const removeAllowed = workspace?.team?.members.length !== 0 || email === workspace?.guests[0]?.user.email if (!removeAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } const userToRemove = await prisma.user.findUnique({ where: { email, }, }) await prisma.guestAccess.delete({ where: { userId_workspaceId: { userId: userToRemove?.id!, workspaceId: id, }, }, }) }), changeGuestAccess: t.protectedProcedure .input( z.object({ id: z.string(), email: z.string(), accessLevel: z.enum(['READ', 'READ_WRITE']), }), ) .mutation(async (opts) => { const {id, email, accessLevel} = opts.input const {session} = opts.ctx const userId = session.user.id const workspace = await prisma.workspace.findUnique({ where: { id, }, select: { team: { select: { members: { where: { userId, }, }, }, }, guests: { where: { userId, }, select: { userId: true, user: { select: { email: true, }, }, }, }, }, }) // Team members can change guest access const changeAllowed = workspace?.team?.members.length !== 0 if (!changeAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } const userToChange = await prisma.user.findUnique({ where: { email, }, }) await prisma.guestAccess.update({ where: { userId_workspaceId: { userId: userToChange?.id!, workspaceId: id, }, }, data: { accessLevel, }, }) }), acceptInvite: t.protectedProcedure .input(z.object({id: z.string()})) .mutation(async (opts) => { const {id} = opts.input const {session} = opts.ctx const userId = session.user.id const workspace = await prisma.workspace.findUnique({ where: { id, }, select: { guests: { where: { userId, }, }, }, }) // Only invitees can accept invites const acceptAllowed = workspace?.guests.length !== 0 if (!acceptAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } await prisma.guestAccess.update({ where: { userId_workspaceId: { userId, workspaceId: id, }, }, data: { accepted: true, }, }) }), getGuests: t.protectedProcedure .input(z.object({id: z.string()})) .output( z.array( z.object({ email: z.string(), name: z.string().nullable(), image: z.string().nullable(), accessLevel: z.enum(['READ', 'READ_WRITE']), accepted: z.boolean(), }), ), ) .query(async (opts) => { const {id} = opts.input const {session} = opts.ctx const userId = session.user.id const workspace = await prisma.workspace.findUnique({ where: { id, }, select: { team: { select: { members: { where: { userId, }, }, }, }, guests: { select: { user: { select: { email: true, image: true, name: true, }, }, accessLevel: true, accepted: true, }, }, }, }) if (!workspace) { throw new TRPCError({code: 'NOT_FOUND'}) } // Only team members can view guests const viewAllowed = workspace.team.members.length !== 0 if (!viewAllowed) { throw new TRPCError({code: 'FORBIDDEN'}) } const guests = workspace.guests.map((guest) => { return { email: guest.user.email!, name: guest.accepted ? guest.user.name : null, image: guest.accepted ? guest.user.image : null, accessLevel: guest.accessLevel, accepted: guest.accepted, } }) return guests }), }) ================================================ FILE: packages/app/src/server/api/trpc.ts ================================================ import {TRPCError, initTRPC} from '@trpc/server' import superjson from 'superjson' import {ZodError} from 'zod' import prisma from '../../prisma' import {getAppSession} from 'src/utils/authUtils' import {type Session} from 'next-auth' /** * 1. CONTEXT * * This section defines the "contexts" that are available in the backend API. * * These allow you to access things when processing a request, like the database, the session, etc. */ interface CreateContextOptions { session: Session | null } /** * This helper generates the "internals" for a tRPC context. If you need to use it, you can export * it from here. * * Examples of things you may need it for: * - testing, so we don't have to mock Next.js' req/res * - tRPC's `createSSGHelpers`, where we don't have req/res * * @see https://create.t3.gg/en/usage/trpc#-serverapitrpcts */ const createInnerTRPCContext = async (opts: CreateContextOptions) => { return { session: opts.session, prisma, } } /** * This is the actual context you will use in your router. It will be used to process every request * that goes through your tRPC endpoint. * * @see https://trpc.io/docs/context */ export const createTRPCContext = async () => { const session = await getAppSession() return createInnerTRPCContext({session}) } /** * 2. INITIALIZATION * * This is where the tRPC API is initialized, connecting the context and transformer. We also parse * ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation * errors on the backend. */ const t = initTRPC.context().create({ transformer: superjson, errorFormatter({shape, error}) { return { ...shape, data: { ...shape.data, zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, }, } }, }) /** * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) * * These are the pieces you use to build your tRPC API. You should import these a lot in the * "/src/server/api/routers" directory. */ /** * This is how you create new routers and sub-routers in your tRPC API. * * @see https://trpc.io/docs/router */ export const createRouter = t.router /** * Public (unauthenticated) procedure * * This is the base piece you use to build new queries and mutations on your tRPC API. It does not * guarantee that a user querying is authorized, but you can still access user session data if they * are logged in. */ export const publicProcedure = t.procedure const isAuthed = t.middleware(({ctx, next}) => { if (!ctx.session || !ctx.session.user) { throw new TRPCError({code: 'UNAUTHORIZED'}) } return next({ ctx: { // infers the `session` as non-nullable session: ctx.session, }, }) }) /** * Protected (authenticated) procedure * * This is the base piece you use to build new queries and mutations on your tRPC API. It guarantees * that a user querying is authorized, and you can access user session data. */ export const protectedProcedure = t.procedure.use(isAuthed) ================================================ FILE: packages/app/src/server/studio-api/root.ts ================================================ import {studioAuthRouter} from './routes/studioAuthRouter' import * as t from '../api/trpc' export const studioTrpcRouter = t.createRouter({ syncServerUrl: t.publicProcedure.query(() => `ws://localhost:3001/api/trpc`), studioAuth: studioAuthRouter, }) export type StudioTRPCRouter = typeof studioTrpcRouter ================================================ FILE: packages/app/src/server/studio-api/routes/studioAuthRouter.ts ================================================ import {z} from 'zod' import {nanoid} from 'nanoid' import {studioAuth} from 'src/utils/authUtils' import {v4} from 'uuid' import * as t from '../../api/trpc' import prisma from 'src/prisma' import {calculatePKCECodeChallenge} from 'oauth4webapi' import type {studioAuthTokens} from '~/types' import {studioAccessScopes} from '~/types' export const userCodeLength = 8 export const FLOW_CHECK_INTERVAL = 1000 export const studioAuthRouter = t.createRouter({ deviceCode: t.publicProcedure .input( z.object({ nounce: z .string() .min(32) .describe( `This is a random string that should be unique for each client flow. It is generated by the client, will be included in the refresh token.`, ), codeChallenge: z .string() .min(43) .max(1024) .describe(`The code_challenge as defined in OAuth RFC 7636`), codeChallengeMethod: z .enum(['S256']) .describe(`The code_challenge_method as defined in OAuth RFC 7636`), scopes: studioAccessScopes.scopes.describe( `The scopes the client is requesting access to. In case \`originalIdToken\` is provided, only the additional scopes should be defined here`, ), originalIdToken: z .string() .optional() .describe( `In case the client (the studio) already has an idToken but is requesting more access, it should provide the original idToken. (This happens e.g. when the studio has access to workspaceA, but now also needs access to workspaceB)`, ), }), ) .output( z.object({ interval: z .number() .int() .min(1000) .describe( 'If 1000, it means the library should check the `$.tokens()` every 1000ms or longer.', ), verificationUriComplete: z .string() .url() .describe( `The URL that the user should be redirected to (or the url to be open via popup) ` + `for the user to log in. Note that if the user is already logged ` + `into the app, they won't be prompted to log in again.`, ), deviceCode: z .string() .min(72) .describe(`A unique token that should be passed to $.tokens()`), }), ) .mutation(async (opts) => { const userCode = nanoid(userCodeLength) const deviceCode = v4() + v4() await prisma.deviceAuthorizationFlow.create({ data: { nounce: opts.input.nounce, createdAt: new Date().toISOString(), lastCheckTime: new Date().toISOString(), codeChallenge: opts.input.codeChallenge, codeChallengeMethod: opts.input.codeChallengeMethod, deviceCode, tokens: '', userCode: userCode, state: 'initialized', }, }) return { interval: FLOW_CHECK_INTERVAL, verificationUriComplete: process.env.NEXT_PUBLIC_WEBAPP_URL + `/api/studio-auth?userCode=${userCode}`, deviceCode, } }), tokens: t.publicProcedure .input( z.object({ deviceCode: z .string() .describe(`The \`deviceCode\` generated by deviceCode()`), codeVerifier: z .string() .describe(`The \`codeVerifier\` as defined in 7636`), }), ) .output( z.union([ z.object({ isError: z.literal(true), error: z.enum([ 'invalidDeviceCode', 'invalidCodeVerifier', 'userDeniedAuth', 'slowDown', 'notYetReady', ]), errorMessage: z.string(), }), z.object({ isError: z.literal(false), accessToken: z.string(), idToken: z.string(), }), ]), ) .mutation(async ({input}) => { const flow = await prisma.deviceAuthorizationFlow.findFirst({ where: {deviceCode: input.deviceCode}, }) if (!flow) { return { isError: true, error: 'invalidDeviceCode', errorMessage: 'The deviceCode is invalid. It may also have been expired, or already used.', } } await prisma.deviceAuthorizationFlow.update({ where: {deviceCode: input.deviceCode}, data: {lastCheckTime: new Date().toISOString()}, }) // if flow.lastCheckTime is more recent than 5 seconds ago, return the same thing as last time if ( new Date(flow.lastCheckTime).getTime() > Date.now() - FLOW_CHECK_INTERVAL ) { return { isError: true, error: 'slowDown', errorMessage: 'You are checking too often. Slow down.', } } switch (flow.state) { case 'initialized': return { isError: true, error: 'notYetReady', errorMessage: `The user hasn't decided to grant/deny access yet.`, } case 'userDeniedAuth': return { isError: true, error: 'userDeniedAuth', errorMessage: `The user denied access.`, } case 'userAllowedAuth': const tokens = JSON.parse(flow.tokens) const codeChallenge = await calculatePKCECodeChallenge( input.codeVerifier, ) if (codeChallenge !== flow.codeChallenge) { return { isError: true, error: 'invalidCodeVerifier', errorMessage: `The codeVerifier is invalid.`, } } await prisma.deviceAuthorizationFlow.update({ where: {deviceCode: input.deviceCode}, data: {state: 'tokenAlreadyUsed'}, }) return { isError: false, accessToken: tokens.accessToken, idToken: tokens.refreshToken, } // otherwise default: console.error('Invalid state', flow.state) return { isError: true, error: 'invalidDeviceCode', errorMessage: 'The preAutenticationToken is invalid. It may also have been expired, or already used.', } } }), invalidateRefreshToken: t.publicProcedure .input( z.object({ refreshToken: z.string(), }), ) .output( z.union([ z.object({ isError: z.literal(true), error: z.enum(['unknown']), errorMessage: z.string(), }), z.object({ isError: z.literal(false), }), ]), ) .mutation(async ({input}) => { try { await studioAuth.destroySession(input.refreshToken) return {isError: false} } catch (err) { console.error(err) return { isError: true, error: 'unknown', errorMessage: `An unknown error occured.`, } } }), refreshAccessToken: t.publicProcedure .input( z.object({ refreshToken: z.string(), }), ) .output( z.union([ z.object({ isError: z.literal(true), error: z.enum(['invalidRefreshToken', 'unknown']), errorMessage: z.string(), }), z.object({ isError: z.literal(false), accessToken: z.string(), refreshToken: z .string() .describe( `The new refresh token. The old refresh token is now invalid.`, ), }), ]), ) .mutation(async ({input}) => { try { const {accessToken, refreshToken} = await studioAuth.refreshSession( input.refreshToken, ) return {isError: false, accessToken, refreshToken} } catch (err: any) { console.error(err) if (err.message === 'Invalid refresh token') { return { isError: true, error: 'invalidRefreshToken', errorMessage: `The refresh token is invalid.`, } } else { return { isError: true, error: 'unknown', errorMessage: `An unknown error occured.`, } } } }), destroyIdToken: t.publicProcedure .input(z.object({idToken: z.string()})) .output( z.union([ z.object({ isError: z.literal(true), error: z.enum(['unknown']), errorMessage: z.string(), }), z.object({ isError: z.literal(false), }), ]), ) .mutation(async ({input}) => { try { await studioAuth.destroySession(input.idToken) return {isError: false} } catch (err) { console.error(err) return { isError: true, error: 'unknown', errorMessage: `An unknown error occured.`, } } }), canIEditProject: t.publicProcedure .input( z.object({ studioAuth: studioAuth.input, projectId: z.string(), }), ) .output( z.union([ z.object({canEdit: z.literal(true)}), z.object({ canEdit: z.literal(false), reason: z.enum(['AccessTokenInvalid', 'UserHasNoAccess', 'Unknown']), }), ]), ) .query(async (opts) => { let payload!: studioAuthTokens.AccessTokenPayload try { payload = await studioAuth.verifyStudioAccessTokenOrThrow(opts) } catch (err) { return {canEdit: false, reason: 'AccessTokenInvalid'} } const {userId} = payload const proj = await prisma.workspace.findFirst({ where: { // TODO check if user has access to project // userId, id: opts.input.projectId, }, }) if (proj) { return {canEdit: true} } else { return {canEdit: false, reason: 'UserHasNoAccess'} } }), }) ================================================ FILE: packages/app/src/trpc/react.tsx ================================================ 'use client' import {QueryClient, QueryClientProvider} from '@tanstack/react-query' import {loggerLink, unstable_httpBatchStreamLink} from '@trpc/client' import {createTRPCReact} from '@trpc/react-query' import {useState} from 'react' import {type AppRouter} from '~/server/api/root' import {getUrl, transformer} from './shared' export const api = createTRPCReact() export function TRPCReactProvider(props: { children: React.ReactNode headers: Headers }) { const [queryClient] = useState( () => new QueryClient({ defaultOptions: { queries: { suspense: true, }, }, }), ) const [trpcClient] = useState(() => api.createClient({ transformer, links: [ loggerLink({ enabled: (op) => process.env.NODE_ENV === 'development' || (op.direction === 'down' && op.result instanceof Error), }), unstable_httpBatchStreamLink({ url: getUrl(), headers() { const heads = new Map(props.headers) heads.set('x-trpc-source', 'react') return Object.fromEntries(heads) }, }), ], }), ) return ( {props.children} ) } ================================================ FILE: packages/app/src/trpc/server.ts ================================================ import { createTRPCProxyClient, loggerLink, unstable_httpBatchStreamLink, } from '@trpc/client' import {headers} from 'next/headers' import {type AppRouter} from '~/server/api/root' import {getUrl, transformer} from './shared' export const api = createTRPCProxyClient({ transformer, links: [ loggerLink({ enabled: (op) => process.env.NODE_ENV === 'development' || (op.direction === 'down' && op.result instanceof Error), }), unstable_httpBatchStreamLink({ url: getUrl(), headers() { const heads = new Map(headers()) heads.set('x-trpc-source', 'rsc') return Object.fromEntries(heads) }, }), ], }) ================================================ FILE: packages/app/src/trpc/shared.ts ================================================ import {type inferRouterInputs, type inferRouterOutputs} from '@trpc/server' import superjson from 'superjson' import {type AppRouter} from '~/server/api/root' export const transformer = superjson function getBaseUrl() { if (typeof window !== 'undefined') // browser should use relative path return '' if (process.env.VERCEL_URL) // reference for vercel.com return `https://${process.env.VERCEL_URL}` if (process.env.RENDER_INTERNAL_HOSTNAME) // reference for render.com return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}` // assume localhost return `http://localhost:${process.env.PORT ?? 3000}` } export function getUrl() { return getBaseUrl() + '/api/trpc' } /** * Inference helper for inputs. * * @example type HelloInput = RouterInputs['example']['hello'] */ export type RouterInputs = inferRouterInputs /** * Inference helper for outputs. * * @example type HelloOutput = RouterOutputs['example']['hello'] */ export type RouterOutputs = inferRouterOutputs ================================================ FILE: packages/app/src/types.ts ================================================ import {z} from 'zod' export namespace studioAccessScopes { export const listWorkspaces = z .literal(`workspaces-list`) .describe( `This scope allows the client (studio) to get the list of workspaces the user has access to, including their ids, names, thumbnails, and last edit time`, ) export type ListWorkspaces = z.infer export const editWorkspace = z .custom<`edit-workspace:${string}`>((v) => typeof v === 'string' && /^edit-workspace\:([a-zA-Z0-9\n\-]+)$/.test(v) ? true : false, ) .describe( `This scope allows the client (studio) to edit a specific workspace (assuming the user has access to it).`, ) export type EditWorkspace = z.infer export const scope = z.union([listWorkspaces, editWorkspace]) export type Scope = z.infer export const scopes = z.array(scope) export type Scopes = z.infer } export namespace studioAuthTokens { export const accessTokenPayload = z.object({ userId: z.string(), email: z.string(), scopes: studioAccessScopes.scopes, }) export type AccessTokenPayload = z.infer export const idTokenPayload = accessTokenPayload.extend({nounce: z.string()}) export type IdTokenPayload = z.infer } ================================================ FILE: packages/app/src/ui/components/ui/alert-dialog.tsx ================================================ import * as React from 'react' import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' import {cn} from 'src/ui/lib/utils' import {buttonVariants} from 'src/ui/components/ui/button' const AlertDialog = AlertDialogPrimitive.Root const AlertDialogTrigger = AlertDialogPrimitive.Trigger const AlertDialogPortal = AlertDialogPrimitive.Portal const AlertDialogOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, children, ...props}, ref) => ( )) AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName const AlertDialogContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
) AlertDialogHeader.displayName = 'AlertDialogHeader' const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
) AlertDialogFooter.displayName = 'AlertDialogFooter' const AlertDialogTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName const AlertDialogDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName const AlertDialogAction = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName const AlertDialogCancel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName export { AlertDialog, AlertDialogPortal, AlertDialogOverlay, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel, } ================================================ FILE: packages/app/src/ui/components/ui/avatar.tsx ================================================ import * as React from 'react' import * as AvatarPrimitive from '@radix-ui/react-avatar' import {cn} from 'src/ui/lib/utils' const Avatar = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) Avatar.displayName = AvatarPrimitive.Root.displayName const AvatarImage = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) AvatarImage.displayName = AvatarPrimitive.Image.displayName const AvatarFallback = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName export {Avatar, AvatarImage, AvatarFallback} ================================================ FILE: packages/app/src/ui/components/ui/button.tsx ================================================ import * as React from 'react' import {Slot} from '@radix-ui/react-slot' import {cva, type VariantProps} from 'class-variance-authority' import {cn} from 'src/ui/lib/utils' const buttonVariants = cva( 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline', }, size: { default: 'h-10 px-4 py-2', sm: 'h-9 rounded-md px-3', lg: 'h-11 rounded-md px-8', icon: 'h-10 w-10', }, }, defaultVariants: { variant: 'default', size: 'default', }, }, ) export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean } const Button = React.forwardRef( ({className, variant, size, asChild = false, ...props}, ref) => { const Comp = asChild ? Slot : 'button' return ( ) }, ) Button.displayName = 'Button' export {Button, buttonVariants} ================================================ FILE: packages/app/src/ui/components/ui/context-menu.tsx ================================================ import * as React from 'react' import * as ContextMenuPrimitive from '@radix-ui/react-context-menu' import {Check, ChevronRight, Circle} from 'lucide-react' import {cn} from 'src/ui/lib/utils' const ContextMenu = ContextMenuPrimitive.Root const ContextMenuTrigger = ContextMenuPrimitive.Trigger const ContextMenuGroup = ContextMenuPrimitive.Group const ContextMenuPortal = ContextMenuPrimitive.Portal const ContextMenuSub = ContextMenuPrimitive.Sub const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup const ContextMenuSubTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean } >(({className, inset, children, ...props}, ref) => ( {children} )) ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName const ContextMenuSubContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName const ContextMenuContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName const ContextMenuItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean } >(({className, inset, ...props}, ref) => ( )) ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName const ContextMenuCheckboxItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, children, checked, ...props}, ref) => ( {children} )) ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName const ContextMenuRadioItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, children, ...props}, ref) => ( {children} )) ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName const ContextMenuLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { inset?: boolean } >(({className, inset, ...props}, ref) => ( )) ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName const ContextMenuSeparator = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { return ( ) } ContextMenuShortcut.displayName = 'ContextMenuShortcut' export { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut, ContextMenuGroup, ContextMenuPortal, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuRadioGroup, } ================================================ FILE: packages/app/src/ui/components/ui/dialog.tsx ================================================ import * as React from 'react' import * as DialogPrimitive from '@radix-ui/react-dialog' import {X} from 'lucide-react' import {cn} from 'src/ui/lib/utils' const Dialog = DialogPrimitive.Root const DialogTrigger = DialogPrimitive.Trigger const DialogPortal = DialogPrimitive.Portal const DialogClose = DialogPrimitive.Close const DialogOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) DialogOverlay.displayName = DialogPrimitive.Overlay.displayName const DialogContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, children, ...props}, ref) => ( {children} Close )) DialogContent.displayName = DialogPrimitive.Content.displayName const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
) DialogHeader.displayName = 'DialogHeader' const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
) DialogFooter.displayName = 'DialogFooter' const DialogTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) DialogTitle.displayName = DialogPrimitive.Title.displayName const DialogDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( )) DialogDescription.displayName = DialogPrimitive.Description.displayName export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, } ================================================ FILE: packages/app/src/ui/components/ui/form.tsx ================================================ import * as React from 'react' import type * as LabelPrimitive from '@radix-ui/react-label' import {Slot} from '@radix-ui/react-slot' import type { ControllerProps, FieldPath, FieldValues} from 'react-hook-form'; import { Controller, FormProvider, useFormContext, } from 'react-hook-form' import {cn} from 'src/ui/lib/utils' import {Label} from 'src/ui/components/ui/label' const Form = FormProvider type FormFieldContextValue< TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath, > = { name: TName } const FormFieldContext = React.createContext( {} as FormFieldContextValue, ) const FormField = < TFieldValues extends FieldValues = FieldValues, TName extends FieldPath = FieldPath, >({ ...props }: ControllerProps) => { return ( ) } const useFormField = () => { const fieldContext = React.useContext(FormFieldContext) const itemContext = React.useContext(FormItemContext) const {getFieldState, formState} = useFormContext() const fieldState = getFieldState(fieldContext.name, formState) if (!fieldContext) { throw new Error('useFormField should be used within ') } const {id} = itemContext return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState, } } type FormItemContextValue = { id: string } const FormItemContext = React.createContext( {} as FormItemContextValue, ) const FormItem = React.forwardRef< HTMLDivElement, React.HTMLAttributes >(({className, ...props}, ref) => { const id = React.useId() return (
) }) FormItem.displayName = 'FormItem' const FormLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({className, ...props}, ref) => { const {error, formItemId} = useFormField() return (
} ``` ### Prisms Prisms are functions that derive a value from an atom or from another prism. ```ts import {Atom, prism, val} from '@theatre/dataverse' const atom = new Atom({a: 1, b: 2, foo: 10}) // the value of this prism will always be equal to the sum of `a` and `b` const sum = prism(() => { const a = val(atom.pointer.a) const b = val(atom.pointer.b) return a + b }) ``` Prisms can also refer to other prisms. ```ts const double = prism(() => { return 2 * val(sum) }) console.log(val(double)) // 6 ``` #### Reading the value of a prism, _None-reactively_ ```ts console.log(val(prism)) // 3 atom.setByPointer(atom.pointer.a, 2) console.log(val(prism)) // 4 ``` #### Reading the value of a prism, _reactively, in React_ Just like atoms, prisms can be subscribed to via `useVal()` ```tsx function Component() { return (
{useVal(atom.pointer.a)} + {useVal(atom.pointer.b)} = {useVal(prism)}
) } ``` #### Reading the value of a prism, _reactively, outside of React_ Prisms can also be subscribed to, outside of React's renderloop. This requires the use of a Ticker, which we'll cover in the next section. ### Tickers Tickers are a way to schedule and synchronise computations. They're useful when reacting to changes in atoms or prisms _outside of React's renderloop_. ```ts import {Ticker, onChange} from '@theatre/dataverse' const ticker = new Ticker() // advance the ticker roughly 60 times per second (note that it's better to use requestAnimationFrame) setInterval(ticker.tick, 1000 / 60) onChange(atom.pointer.intensity, (newIntensity) => { console.log('intensity changed to', newIntensity) }) atom.setByPointer(atom.pointer.intensity, 3) // After a few milliseconds, logs 'intensity changed to 3' setTimeout(() => { atom.setByPointer(atom.pointer.intensity, 4) atom.setByPointer(atom.pointer.intensity, 5) // updates are batched because our ticker advances every 16ms, so we // will only get one log for 'intensity changed to 5', even though we changed the intensity twice }, 1000) ``` Tickers should normally be advanced using `requestAnimationFrame` to make sure all the computations are done in sync with the browser's refresh rate. ```ts const frame = () => { ticker.tick() requestAnimationFrame(frame) } requestAnimationFrame(frame) ``` #### Benefits of using Tickers Tickers make sure that our computations are batched and only advance atomically. They also make sure that we don't recompute the same value twice in the same frame. Most importantly, Tickers allow us to align our computations to the browser's (or the XR-device's) refresh rate. ### Prism hooks Prism hooks are inspired by [React hooks](https://reactjs.org/docs/hooks-intro.html). They are a convenient way to cache, memoize, batch, and run effects inside prisms, while ensuring that the prism can be used in a declarative, encapsulated way. #### `prism.source()` The `prism.source()` hook allows a prism to read to and react to changes in values that reside outside of an atom or another prism, for example, the value of an `` element. ```ts function prismFromInputElement(input: HTMLInputElement): Prism { function subscribe(cb: (value: string) => void) { const listener = () => { cb(input.value) } input.addEventListener('input', listener) return () => { input.removeEventListener('input', listener) } } function get() { return input.value } return prism(() => prism.source(subscribe, get)) } const p = prismFromInputElement(document.querySelector('input')) p.onChange(ticker, (value) => { console.log('input value changed to', value) }) ``` #### `prism.ref()` Just like React's `useRef()`, `prism.ref()` allows us to create a prism that holds a reference to some value. The only difference is that `prism.ref()` requires a key to be passed into it, whlie `useRef()` doesn't. This means that we can call `prism.ref()` in any order, and we can call it multiple times with the same key. ```ts const p = prism(() => { const inputRef = prism.ref('some-unique-key') if (!inputRef.current) { inputRef.current = document.$('input.username') } // this prism will always reflect the value of return val(prismFromInputElement(inputRef.current)) }) p.onChange(ticker, (value) => { console.log('username changed to', value) }) ``` #### `prism.memo()` `prism.memo()` works just like React's `useMemo()` hook. It's a way to cache the result of a function call. The only difference is that `prism.memo()` requires a key to be passed into it, whlie `useMemo()` doesn't. This means that we can call `prism.memo()` in any order, and we can call it multiple times with the same key. ```ts import {Atom, prism, val} from '@theatre/dataverse' const atom = new Atom(0) function factorial(n: number): number { if (n === 0) return 1 return n * factorial(n - 1) } const p = prism(() => { // num will be between 0 and 9. This is so we can test what happens when the atom's value changes, but // the memoized value doesn't change. const num = val(atom.pointer) const numMod10 = num % 10 const value = prism.memo( // we need a string key to identify the hook. This allows us to call `prism.memo()` in any order, or even conditionally. 'factorial', // the function to memoize () => { console.log('Calculating factorial') factorial(numMod10) }, // the dependencies of the function. If any of the dependencies change, the function will be called again. [numMod10], ) return `number is ${num}, num % 10 is ${numMod10} and its factorial is ${value}` }) p.onChange(ticker, (value) => { console.log('=>', value) }) atom.set(1) // Calculating factorial // => number is 1, num % 10 is 1 and its factorial is 1 atom.set(2) // Calculating factorial // => number is 2, num % 10 is 2 and its factorial is 2 atom.set(12) // won't recalculate the factorial // => number is 12, num % 10 is 2 and its factorial is 2 ``` #### `prism.effect()` and `prism.state()` These are two more hooks that are similar to React's `useEffect()` and `useState()` hooks. `prism.effect()` is similar to React's `useEffect()` hook. It allows us to run side-effects when the prism is calculated. Note that prisms are supposed to be "virtually" pure functions. That means they either should not have side-effects (and thus, no calls for `prism.effect()`), or their side-effects should clean themselves up when the prism goes cold. `prism.state()` is similar to React's `useState()` hook. It allows us to create a stateful value that is scoped to the prism. We'll defer to React's documentation for [a more detailed explanation of how `useEffect()`](https://reactjs.org/docs/hooks-effect.html) and how [`useState()`](https://reactjs.org/docs/hooks-state.html) work. But here's a quick example: ```tsx import {prism} from '@theatre/dataverse' import {useVal} from '@theatre/react' // This prism holds the current mouse position and updates when the mouse moves const mousePositionPr = prism(() => { const [pos, setPos] = prism.state<[x: number, y: number]>('pos', [0, 0]) prism.effect( 'setupListeners', () => { const handleMouseMove = (e: MouseEvent) => { setPos([e.screenX, e.screenY]) } document.addEventListener('mousemove', handleMouseMove) return () => { document.removeEventListener('mousemove', handleMouseMove) } }, [], ) return pos }) function Component() { const [x, y] = useVal(mousePositionPr) return (
Mouse position: {x}, {y}
) } ``` #### `prism.sub()` `prism.sub()` is a shortcut for creating a prism inside another prism. It's equivalent to calling `prism.memo(key, () => prism(fn), deps).getValue()`. `prism.sub()` is useful when you want to divide your prism into smaller prisms, each of which would _only_ recalculate when _certain_ dependencies change. In other words, it's an optimization tool. ```ts function factorial(num: number): number { if (num === 0) return 1 return num * factorial(num - 1) } const events: Array<'foo-calculated' | 'bar-calculated'> = [] // example: const state = new Atom({foo: 0, bar: 0}) const pr = prism(() => { const resultOfFoo = prism.sub( 'foo', () => { events.push('foo-calculated') const foo = val(state.pointer.foo) % 10 // Note how `prism.sub()` is more powerful than `prism.memo()` because it allows us to use `prism.memo()` and other hooks inside of it: return prism.memo('factorial', () => factorial(foo), [foo]) }, [], ) const resultOfBar = prism.sub( 'bar', () => { events.push('bar-calculated') const bar = val(state.pointer.bar) % 10 return prism.memo('factorial', () => factorial(bar), [bar]) }, [], ) return `result of foo is ${resultOfFoo}, result of bar is ${resultOfBar}` }) const unsub = pr.onChange(ticker, () => {}) // on the first run, both subs should be calculated: console.log(events) // ['foo-calculated', 'bar-calculated'] events.length = 0 // clear the events array // now if we change the value of `bar`, only `bar` should be recalculated: state.setByPointer(state.pointer.bar, 2) pr.getValue() console.log(events) // ['bar-calculated'] unsub() ``` since prism hooks are keyed (as opposed to React hooks where they're identified by their order), it's possible to have multiple hooks with the same key in the same prism. To avoid this, we can use `prism.scope()` to create a "scope" for our hooks. Example: ```ts const pr = prism(() => { prism.scope('a', () => { prism.memo('foo', () => 1, []) }) prism.scope('b', () => { prism.memo('foo', () => 1, []) }) }) ``` ### `usePrism()` `usePrism()` is a _React_ hook that allows us to create a prism inside a React component. This way, we can optimize our React components in a fine-grained way by moving their computations outside of React's render loop. ```tsx import {usePrism} from '@theatre/react' function Component() { const value = usePrism(() => { // [insert heavy calculation here] }, []) } ``` ### Hot and cold prisms Prisms can have three states: - 🧊 Cold: The prism was just created. It does not have dependents, or its dependents are also 🧊 cold. - 🔥 Hot: The prism is either being subscribed to (via `useVal()`, `prism.onChange()`, `prism.onStale()`, etc). Or, one of its dependents is 🔥 hot. - A 🔥 Hot prism itself has two states: - 🪵 Stale: The prism is hot, but its value is stale. This happens when one or more of its dependencies have changed, but the value of the prism hasn't been read since that change. Reading the value of a 🪵 Stale prism will cause it to recalculate, and make it 🌲 Fresh. - 🌲 Fresh: The prism is hot, and its value is fresh. This happens when the prism's value has been read since the last change in its dependencies. Re-reading the value of a 🌲 Fresh prism will _not_ cause it to recalculate. Or, as a typescript annotation: ```ts type PrismState = | {isHot: false} // 🧊 | {isHot: true; isFresh: false} // 🔥🪵 | {isHot: true; isFresh: true} // 🔥🌲 ``` Let's demonstrate this with an example of a prism, and its `onStale()` method. ```ts const atom = new Atom(0) const a = prism(() => val(atom.pointer)) // 🧊 // onStale(cb) calls `cb` when the prism goes from 🌲 to 🪵 a.onStale(() => { console.log('a is stale') }) // a from 🧊 to 🔥 // console: a is stale // reading the value of `a` will cause it to recalculate, and make it 🌲 fresh. console.log(val(a)) // 1 // a from 🔥🪵 to 🔥🌲 atom.set(1) // a from 🔥🌲 to 🔥🪵 // console: a is stale // reading the value of `a` will cause it to recalculate, and make it 🌲 fresh. console.log(val(a)) // 2 ``` Prism states propogate through the prism dependency graph. Let's look at an example: ```ts const atom = new Atom({a: 0, b: 0}) const a = prism(() => val(atom.pointer.a)) const b = prism(() => val(atom.pointer.b)) const sum = prism(() => val(a) + val(b)) // a | b | sum | // 🧊 | 🧊 | 🧊 | let unsub = a.onStale(() => {}) // there is now a subscription to `a`, so it's 🔥 hot // a | b | sum | // 🔥🪵 | 🧊 | 🧊 | unsub() // there are no subscriptions to `a`, so it's 🧊 cold again // a | b | sum | // 🧊 | 🧊 | 🧊 | unsub = sum.onStale(() => {}) // there is now a subscription to `sum`, so it goes 🔥 hot, and so do its dependencies // a | b | sum | // 🔥🪵 | 🔥🪵 | 🔥🪵 | val(sum) // reading the value of `sum` will cause it to recalculate, and make it 🌲 fresh. // a | b | sum | // 🔥🌲 | 🔥🌲 | 🔥🌲 | atom.setByPointer(atom.pointer.a, 1) // `a` is now stale, which will cause `sum` to become stale as well // a | b | sum | // 🔥🪵 | 🔥🌲 | 🔥🪵 | val(a) // reading the value of `a` will cause it to recalculate, and make it 🌲 fresh. But notice that `sum` is still 🪵 stale. // a | b | sum | // 🔥🌲 | 🔥🌲 | 🔥🪵 | atom.setByPointer(atom.pointer.b, 1) // `b` now goes stale. Since sum was already stale, it will remain so // a | b | sum | // 🔥🌲 | 🔥🪵 | 🔥🪵 | val(sum) // reading the value of `sum` will cause it to recalculate and go 🌲 fresh. // a | b | sum | // 🔥🌲 | 🔥🌲 | 🔥🌲 | unsub() // there are no subscriptions to `sum`, so it goes 🧊 cold again, and so do its dependencies, since they don't have any other hot dependents // a | b | sum | // 🧊 | 🧊 | 🧊 | ``` The state transitions propogate in topological order. Let's demonstrate this by adding one more prism to our dependency graph: ```ts // continued from the previous example const double = prism(() => val(sum) * 2) // Initially, all prisms are 🧊 cold // a | b | sum | double | // 🧊 | 🧊 | 🧊 | 🧊 | let unsub = double.onStale(() => {}) // here is how the state transitions will happen, step by step: // (step) | a | b | sum | double | // 1 | 🧊 | 🧊 | 🧊 | 🔥🪵 | // 2 | 🧊 | 🧊 | 🔥🪵 | 🔥🪵 | // 3 | 🔥🪵 | 🔥🪵 | 🔥🪵 | 🔥🪵 | val(double) // freshening happens in the reverse order // (step) | a | b | sum | double | // 0 | 🔥🪵 | 🔥🪵 | 🔥🪵 | 🔥🪵 | // --------------------------------------------------| // 1 ▲ ▼ | double reads the value of sum // └────◄────┘ | // --------------------------------------------------| // 2 ▲ ▲ ▼ | sum reads the value of a and b // │ │ │ | // └────◄───┴────◄─────┘ | // --------------------------------------------------| // 3 | 🔥🌲 | 🔥🌲 | 🔥🪵 | 🔥🪵 | a and b go fresh // --------------------------------------------------| // 4 | 🔥🌲 | 🔥🌲 | 🔥🌲 | 🔥🪵 | sum goes fresh // --------------------------------------------------| // 5 | 🔥🌲 | 🔥🌲 | 🔥🌲 | 🔥🌲 | double goes fresh // --------------------------------------------------| ``` ## Links - [API Reference](./api/README.md) - [The exhaustive guide to dataverse](./src/dataverse.test.ts) - It's also fun to [open the monorepo](https://github1s.com/theatre-js/theatre/blob/main/packages/dataverse/src/index.ts) in VSCode and look up references to `Atom`, `prism()` and other dataverse methods. Since dataverse is used internally in Theatre.js, there are a lot of examples of how to use it. - Also see [`@theatre/react`](../react/README.md) to learn more about the React bindings. ================================================ FILE: packages/dataverse/api/.nojekyll ================================================ TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. ================================================ FILE: packages/dataverse/api/README.md ================================================ @theatre/dataverse # @theatre/dataverse The animation-optimized FRP library powering the internals of Theatre.js. ## Table of contents ### Namespaces - [prism](modules/prism.md) ### Classes - [Atom](classes/Atom.md) - [PointerProxy](classes/PointerProxy.md) - [Ticker](classes/Ticker.md) ### Interfaces - [PointerToPrismProvider](interfaces/PointerToPrismProvider.md) - [Prism](interfaces/Prism-1.md) ### Type Aliases - [Pointer](README.md#pointer) - [PointerMeta](README.md#pointermeta) - [PointerType](README.md#pointertype) ### Functions - [getPointerParts](README.md#getpointerparts) - [isPointer](README.md#ispointer) - [isPrism](README.md#isprism) - [iterateAndCountTicks](README.md#iterateandcountticks) - [iterateOver](README.md#iterateover) - [pointer](README.md#pointer-1) - [pointerToPrism](README.md#pointertoprism) - [prism](README.md#prism) - [val](README.md#val) ## Type Aliases ### Pointer Ƭ **Pointer**<`O`\>: [`PointerType`](README.md#pointertype)<`O`\> & `PointerInner`<`Exclude`<`O`, `undefined`\>, `undefined` extends `O` ? `undefined` : `never`\> The type of [Atom](classes/Atom.md) pointers. See [pointer()](README.md#pointer-1) for an explanation of pointers. **`See`** Atom #### Type parameters | Name | | :------ | | `O` | #### Defined in [pointer.ts:64](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/pointer.ts#L64) ___ ### PointerMeta Ƭ **PointerMeta**: `Object` #### Type declaration | Name | Type | | :------ | :------ | | `path` | (`string` \| `number`)[] | | `root` | {} | #### Defined in [pointer.ts:5](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/pointer.ts#L5) ___ ### PointerType Ƭ **PointerType**<`O`\>: `Object` A wrapper type for the type a `Pointer` points to. #### Type parameters | Name | | :------ | | `O` | #### Type declaration | Name | Type | Description | | :------ | :------ | :------ | | `$$__pointer_type` | `O` | Only accessible via the type system. This is a helper for getting the underlying pointer type via the type space. | #### Defined in [pointer.ts:35](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/pointer.ts#L35) ## Functions ### getPointerParts ▸ **getPointerParts**<`_`\>(`p`): `Object` Returns the root object and the path of the pointer. #### Type parameters | Name | | :------ | | `_` | #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `p` | [`Pointer`](README.md#pointer)<`_`\> | The pointer. | #### Returns `Object` An object with two properties: `root`-the root object or the pointer, and `path`-the path of the pointer. `path` is an array of the property-chain. | Name | Type | | :------ | :------ | | `path` | `PathToProp` | | `root` | {} | **`Example`** ```ts const {root, path} = getPointerParts(pointer) ``` #### Defined in [pointer.ts:136](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/pointer.ts#L136) ___ ### isPointer ▸ **isPointer**(`p`): p is Pointer Returns whether `p` is a pointer. #### Parameters | Name | Type | | :------ | :------ | | `p` | `any` | #### Returns p is Pointer #### Defined in [pointer.ts:187](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/pointer.ts#L187) ___ ### isPrism ▸ **isPrism**(`d`): d is Prism Returns whether `d` is a prism. #### Parameters | Name | Type | | :------ | :------ | | `d` | `any` | #### Returns d is Prism #### Defined in [prism/Interface.ts:66](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/Interface.ts#L66) ___ ### iterateAndCountTicks ▸ **iterateAndCountTicks**<`V`\>(`pointerOrPrism`): `Generator`<{ `ticks`: `number` ; `value`: `V` }, `void`, `void`\> #### Type parameters | Name | | :------ | | `V` | #### Parameters | Name | Type | | :------ | :------ | | `pointerOrPrism` | [`Prism`](interfaces/Prism-1.md)<`V`\> \| [`Pointer`](README.md#pointer)<`V`\> | #### Returns `Generator`<{ `ticks`: `number` ; `value`: `V` }, `void`, `void`\> #### Defined in [prism/iterateAndCountTicks.ts:7](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/iterateAndCountTicks.ts#L7) ___ ### iterateOver ▸ **iterateOver**<`V`\>(`pointerOrPrism`): `Generator`<`V`, `void`, `void`\> #### Type parameters | Name | | :------ | | `V` | #### Parameters | Name | Type | | :------ | :------ | | `pointerOrPrism` | [`Prism`](interfaces/Prism-1.md)<`V`\> \| [`Pointer`](README.md#pointer)<`V`\> | #### Returns `Generator`<`V`, `void`, `void`\> #### Defined in [prism/iterateOver.ts:8](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/iterateOver.ts#L8) ___ ### pointer ▸ **pointer**<`O`\>(`args`): [`Pointer`](README.md#pointer)<`O`\> Creates a pointer to a (nested) property of an [Atom](classes/Atom.md). #### Type parameters | Name | Description | | :------ | :------ | | `O` | The type of the value being pointed to. | #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `args` | `Object` | The pointer parameters. | | `args.path?` | (`string` \| `number`)[] | - | | `args.root` | `Object` | - | #### Returns [`Pointer`](README.md#pointer)<`O`\> **`Example`** ```ts // Here, sum is a prism that updates whenever the a or b prop of someAtom does. const sum = prism(() => { return val(pointer({root: someAtom, path: ['a']})) + val(pointer({root: someAtom, path: ['b']})); }); // Note, atoms have a convenience Atom.pointer property that points to the root, // which you would normally use in this situation. const sum = prism(() => { return val(someAtom.pointer.a) + val(someAtom.pointer.b); }); ``` #### Defined in [pointer.ts:172](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/pointer.ts#L172) ___ ### pointerToPrism ▸ **pointerToPrism**<`P`\>(`pointer`): [`Prism`](interfaces/Prism-1.md)<`P` extends [`PointerType`](README.md#pointertype)<`T`\> ? `T` : `void`\> Returns a prism of the value at the provided pointer. Prisms are cached per pointer. #### Type parameters | Name | Type | | :------ | :------ | | `P` | extends [`PointerType`](README.md#pointertype)<`any`\> | #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `pointer` | `P` | The pointer to return the prism at. | #### Returns [`Prism`](interfaces/Prism-1.md)<`P` extends [`PointerType`](README.md#pointertype)<`T`\> ? `T` : `void`\> #### Defined in [pointerToPrism.ts:41](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/pointerToPrism.ts#L41) ___ ### prism ▸ **prism**<`T`\>(`fn`): [`Prism`](interfaces/Prism-1.md)<`T`\> Creates a prism from the passed function that adds all prisms referenced in it as dependencies, and reruns the function when these change. #### Type parameters | Name | | :------ | | `T` | #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `fn` | () => `T` | The function to rerun when the prisms referenced in it change. | #### Returns [`Prism`](interfaces/Prism-1.md)<`T`\> #### Defined in [prism/prism.ts:817](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/prism.ts#L817) ___ ### val ▸ **val**<`P`\>(`input`): `P` extends [`PointerType`](README.md#pointertype)<`T`\> ? `T` : `P` extends [`Prism`](interfaces/Prism-1.md)<`T`\> ? `T` : `P` extends `undefined` \| ``null`` ? `P` : `unknown` Convenience function that returns a plain value from its argument, whether it is a pointer, a prism or a plain value itself. #### Type parameters | Name | Type | | :------ | :------ | | `P` | extends `undefined` \| ``null`` \| [`Prism`](interfaces/Prism-1.md)<`any`\> \| [`PointerType`](README.md#pointertype)<`any`\> | #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `input` | `P` | The argument to return a value from. | #### Returns `P` extends [`PointerType`](README.md#pointertype)<`T`\> ? `T` : `P` extends [`Prism`](interfaces/Prism-1.md)<`T`\> ? `T` : `P` extends `undefined` \| ``null`` ? `P` : `unknown` #### Defined in [val.ts:19](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/val.ts#L19) ================================================ FILE: packages/dataverse/api/classes/Atom.md ================================================ [@theatre/dataverse](../README.md) / Atom # Class: Atom Wraps an object whose (sub)properties can be individually tracked. ## Type parameters | Name | | :------ | | `State` | ## Implements - [`PointerToPrismProvider`](../interfaces/PointerToPrismProvider.md) ## Table of contents ### Constructors - [constructor](Atom.md#constructor) ### Properties - [pointer](Atom.md#pointer) - [prism](Atom.md#prism) ### Methods - [get](Atom.md#get) - [getByPointer](Atom.md#getbypointer) - [onChange](Atom.md#onchange) - [onChangeByPointer](Atom.md#onchangebypointer) - [pointerToPrism](Atom.md#pointertoprism) - [reduce](Atom.md#reduce) - [reduceByPointer](Atom.md#reducebypointer) - [set](Atom.md#set) - [setByPointer](Atom.md#setbypointer) ## Constructors ### constructor • **new Atom**<`State`\>(`initialState`) #### Type parameters | Name | | :------ | | `State` | #### Parameters | Name | Type | | :------ | :------ | | `initialState` | `State` | #### Defined in [Atom.ts:119](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L119) ## Properties ### pointer • `Readonly` **pointer**: [`Pointer`](../README.md#pointer)<`State`\> Convenience property that gives you a pointer to the root of the atom. #### Defined in [Atom.ts:113](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L113) ___ ### prism • `Readonly` **prism**: [`Prism`](../interfaces/Prism-1.md)<`State`\> #### Defined in [Atom.ts:115](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L115) ## Methods ### get ▸ **get**(): `State` Returns the current state of the atom. #### Returns `State` #### Defined in [Atom.ts:139](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L139) ___ ### getByPointer ▸ **getByPointer**<`S`\>(`pointerOrFn`): `S` Returns the value at the given pointer #### Type parameters | Name | | :------ | | `S` | #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `pointerOrFn` | [`Pointer`](../README.md#pointer)<`S`\> \| (`p`: [`Pointer`](../README.md#pointer)<`State`\>) => [`Pointer`](../README.md#pointer)<`S`\> | A pointer to the desired path. Could also be a function returning a pointer Example ```ts const atom = atom({ a: { b: 1 } }) atom.getByPointer(atom.pointer.a.b) // 1 atom.getByPointer((p) => p.a.b) // 1 ``` | #### Returns `S` #### Defined in [Atom.ts:155](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L155) ___ ### onChange ▸ **onChange**(`cb`): () => `void` Adds a listener that will be called whenever the state of the atom changes. #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `cb` | (`v`: `State`) => `void` | The callback to call when the value changes | #### Returns `fn` A function that can be called to unsubscribe from the listener **NOTE** Unlike [prism](../README.md#prism)s, `onChangeByPointer` and `onChange()` are traditional event listeners. They don't provide any of the benefits of prisms. They don't compose, they can't be coordinated via a Ticker, their derivations aren't cached, etc. You're almost always better off using a prism (which will internally use `onChangeByPointer`). ```ts const a = atom({foo: 1}) const unsubscribe = a.onChange((v) => { console.log('a changed to', v) }) a.set({foo: 3}) // logs 'a changed to {foo: 3}' unsubscribe() ``` ▸ (): `void` Adds a listener that will be called whenever the state of the atom changes. ##### Returns `void` A function that can be called to unsubscribe from the listener **NOTE** Unlike [prism](../README.md#prism)s, `onChangeByPointer` and `onChange()` are traditional event listeners. They don't provide any of the benefits of prisms. They don't compose, they can't be coordinated via a Ticker, their derivations aren't cached, etc. You're almost always better off using a prism (which will internally use `onChangeByPointer`). ```ts const a = atom({foo: 1}) const unsubscribe = a.onChange((v) => { console.log('a changed to', v) }) a.set({foo: 3}) // logs 'a changed to {foo: 3}' unsubscribe() ``` #### Defined in [Atom.ts:305](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L305) ___ ### onChangeByPointer ▸ **onChangeByPointer**<`S`\>(`pointerOrFn`, `cb`): () => `void` Adds a listener that will be called whenever the value at the given pointer changes. #### Type parameters | Name | | :------ | | `S` | #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `pointerOrFn` | [`Pointer`](../README.md#pointer)<`S`\> \| (`p`: [`Pointer`](../README.md#pointer)<`State`\>) => [`Pointer`](../README.md#pointer)<`S`\> | - | | `cb` | (`v`: `S`) => `void` | The callback to call when the value changes | #### Returns `fn` A function that can be called to unsubscribe from the listener **NOTE** Unlike [prism](../README.md#prism)s, `onChangeByPointer` and `onChange()` are traditional event listeners. They don't provide any of the benefits of prisms. They don't compose, they can't be coordinated via a Ticker, their derivations aren't cached, etc. You're almost always better off using a prism (which will internally use `onChangeByPointer`). ```ts const a = atom({foo: 1}) const unsubscribe = a.onChangeByPointer(a.pointer.foo, (v) => { console.log('foo changed to', v) }) a.setByPointer(a.pointer.foo, 2) // logs 'foo changed to 2' a.set({foo: 3}) // logs 'foo changed to 3' unsubscribe() ``` ▸ (): `void` ##### Returns `void` #### Defined in [Atom.ts:271](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L271) ___ ### pointerToPrism ▸ **pointerToPrism**<`P`\>(`pointer`): [`Prism`](../interfaces/Prism-1.md)<`P`\> Returns a new prism of the value at the provided path. #### Type parameters | Name | | :------ | | `P` | #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `pointer` | [`Pointer`](../README.md#pointer)<`P`\> | The path to create the prism at. ```ts const pr = atom({ a: { b: 1 } }).pointerToPrism(atom.pointer.a.b) pr.getValue() // 1 ``` | #### Returns [`Prism`](../interfaces/Prism-1.md)<`P`\> #### Implementation of [PointerToPrismProvider](../interfaces/PointerToPrismProvider.md).[pointerToPrism](../interfaces/PointerToPrismProvider.md#pointertoprism) #### Defined in [Atom.ts:319](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L319) ___ ### reduce ▸ **reduce**(`fn`): `void` #### Parameters | Name | Type | | :------ | :------ | | `fn` | (`state`: `State`) => `State` | #### Returns `void` #### Defined in [Atom.ts:173](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L173) ___ ### reduceByPointer ▸ **reduceByPointer**<`S`\>(`pointerOrFn`, `reducer`): `void` Reduces the value at the given pointer #### Type parameters | Name | | :------ | | `S` | #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `pointerOrFn` | [`Pointer`](../README.md#pointer)<`S`\> \| (`p`: [`Pointer`](../README.md#pointer)<`State`\>) => [`Pointer`](../README.md#pointer)<`S`\> | A pointer to the desired path. Could also be a function returning a pointer Example ```ts const atom = atom({ a: { b: 1 } }) atom.reduceByPointer(atom.pointer.a.b, (b) => b + 1) // atom.get().a.b === 2 atom.reduceByPointer((p) => p.a.b, (b) => b + 1) // atom.get().a.b === 2 ``` | | `reducer` | (`s`: `S`) => `S` | - | #### Returns `void` #### Defined in [Atom.ts:189](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L189) ___ ### set ▸ **set**(`newState`): `void` Sets the state of the atom. #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `newState` | `State` | The new state of the atom. | #### Returns `void` #### Defined in [Atom.ts:129](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L129) ___ ### setByPointer ▸ **setByPointer**<`S`\>(`pointerOrFn`, `val`): `void` Sets the value at the given pointer #### Type parameters | Name | | :------ | | `S` | #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `pointerOrFn` | [`Pointer`](../README.md#pointer)<`S`\> \| (`p`: [`Pointer`](../README.md#pointer)<`State`\>) => [`Pointer`](../README.md#pointer)<`S`\> | A pointer to the desired path. Could also be a function returning a pointer Example ```ts const atom = atom({ a: { b: 1 } }) atom.setByPointer(atom.pointer.a.b, 2) // atom.get().a.b === 2 atom.setByPointer((p) => p.a.b, 2) // atom.get().a.b === 2 ``` | | `val` | `S` | - | #### Returns `void` #### Defined in [Atom.ts:214](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Atom.ts#L214) ================================================ FILE: packages/dataverse/api/classes/PointerProxy.md ================================================ [@theatre/dataverse](../README.md) / PointerProxy # Class: PointerProxy Allows creating pointer-prisms where the pointer can be switched out. ## Type parameters | Name | Type | | :------ | :------ | | `O` | extends `Object` | ## Implements - [`PointerToPrismProvider`](../interfaces/PointerToPrismProvider.md) ## Table of contents ### Constructors - [constructor](PointerProxy.md#constructor) ### Properties - [pointer](PointerProxy.md#pointer) ### Methods - [pointerToPrism](PointerProxy.md#pointertoprism) - [setPointer](PointerProxy.md#setpointer) ## Constructors ### constructor • **new PointerProxy**<`O`\>(`currentPointer`) #### Type parameters | Name | Type | | :------ | :------ | | `O` | extends `Object` | #### Parameters | Name | Type | | :------ | :------ | | `currentPointer` | [`Pointer`](../README.md#pointer)<`O`\> | #### Defined in [PointerProxy.ts:34](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/PointerProxy.ts#L34) ## Properties ### pointer • `Readonly` **pointer**: [`Pointer`](../README.md#pointer)<`O`\> Convenience pointer pointing to the root of this PointerProxy. #### Defined in [PointerProxy.ts:32](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/PointerProxy.ts#L32) ## Methods ### pointerToPrism ▸ **pointerToPrism**<`P`\>(`pointer`): [`Prism`](../interfaces/Prism-1.md)<`P`\> Returns a prism of the value at the provided sub-path of the proxied pointer. #### Type parameters | Name | | :------ | | `P` | #### Parameters | Name | Type | | :------ | :------ | | `pointer` | [`Pointer`](../README.md#pointer)<`P`\> | #### Returns [`Prism`](../interfaces/Prism-1.md)<`P`\> #### Implementation of [PointerToPrismProvider](../interfaces/PointerToPrismProvider.md).[pointerToPrism](../interfaces/PointerToPrismProvider.md#pointertoprism) #### Defined in [PointerProxy.ts:52](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/PointerProxy.ts#L52) ___ ### setPointer ▸ **setPointer**(`p`): `void` Sets the underlying pointer. #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `p` | [`Pointer`](../README.md#pointer)<`O`\> | The pointer to be proxied. | #### Returns `void` #### Defined in [PointerProxy.ts:43](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/PointerProxy.ts#L43) ================================================ FILE: packages/dataverse/api/classes/Ticker.md ================================================ [@theatre/dataverse](../README.md) / Ticker # Class: Ticker The Ticker class helps schedule callbacks. Scheduled callbacks are executed per tick. Ticks can be triggered by an external scheduling strategy, e.g. a raf. ## Table of contents ### Constructors - [constructor](Ticker.md#constructor) ### Properties - [\_\_ticks](Ticker.md#__ticks) ### Accessors - [dormant](Ticker.md#dormant) - [time](Ticker.md#time) ### Methods - [offNextTick](Ticker.md#offnexttick) - [offThisOrNextTick](Ticker.md#offthisornexttick) - [onNextTick](Ticker.md#onnexttick) - [onThisOrNextTick](Ticker.md#onthisornexttick) - [tick](Ticker.md#tick) ## Constructors ### constructor • **new Ticker**(`_conf?`) #### Parameters | Name | Type | | :------ | :------ | | `_conf?` | `Object` | | `_conf.onActive?` | () => `void` | | `_conf.onDormant?` | () => `void` | #### Defined in [Ticker.ts:43](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Ticker.ts#L43) ## Properties ### \_\_ticks • **\_\_ticks**: `number` = `0` Counts up for every tick executed. Internally, this is used to measure ticks per second. This is "public" to TypeScript, because it's a tool for performance measurements. Consider this as experimental, and do not rely on it always being here in future releases. #### Defined in [Ticker.ts:41](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Ticker.ts#L41) ## Accessors ### dormant • `get` **dormant**(): `boolean` Whether the Ticker is dormant #### Returns `boolean` #### Defined in [Ticker.ts:31](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Ticker.ts#L31) ___ ### time • `get` **time**(): `number` The time at the start of the current tick if there is a tick in progress, otherwise defaults to `performance.now()`. #### Returns `number` #### Defined in [Ticker.ts:122](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Ticker.ts#L122) ## Methods ### offNextTick ▸ **offNextTick**(`fn`): `void` De-registers a fn to be called on the next tick. #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `fn` | `ICallback` | The function to be de-registered. | #### Returns `void` **`See`** onNextTick #### Defined in [Ticker.ts:114](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Ticker.ts#L114) ___ ### offThisOrNextTick ▸ **offThisOrNextTick**(`fn`): `void` De-registers a fn to be called either on this tick or the next tick. #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `fn` | `ICallback` | The function to be de-registered. | #### Returns `void` **`See`** onThisOrNextTick #### Defined in [Ticker.ts:103](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Ticker.ts#L103) ___ ### onNextTick ▸ **onNextTick**(`fn`): `void` Registers a side effect to be called on the next tick. #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `fn` | `ICallback` | The function to be registered. | #### Returns `void` **`See`** - onThisOrNextTick - offNextTick #### Defined in [Ticker.ts:89](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Ticker.ts#L89) ___ ### onThisOrNextTick ▸ **onThisOrNextTick**(`fn`): `void` Registers for fn to be called either on this tick or the next tick. If `onThisOrNextTick()` is called while `Ticker.tick()` is running, the side effect _will_ be called within the running tick. If you don't want this behavior, you can use `onNextTick()`. Note that `fn` will be added to a `Set()`. Which means, if you call `onThisOrNextTick(fn)` with the same fn twice in a single tick, it'll only run once. #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `fn` | `ICallback` | The function to be registered. | #### Returns `void` **`See`** offThisOrNextTick #### Defined in [Ticker.ts:74](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Ticker.ts#L74) ___ ### tick ▸ **tick**(`t?`): `void` Triggers a tick which starts executing the callbacks scheduled for this tick. #### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `t` | `number` | The time at the tick. | #### Returns `void` **`See`** - onThisOrNextTick - onNextTick #### Defined in [Ticker.ts:149](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/Ticker.ts#L149) ================================================ FILE: packages/dataverse/api/interfaces/PointerToPrismProvider.md ================================================ [@theatre/dataverse](../README.md) / PointerToPrismProvider # Interface: PointerToPrismProvider Interface for objects that can provide a prism at a certain path. ## Implemented by - [`Atom`](../classes/Atom.md) - [`PointerProxy`](../classes/PointerProxy.md) ## Table of contents ### Methods - [pointerToPrism](PointerToPrismProvider.md#pointertoprism) ## Methods ### pointerToPrism ▸ **pointerToPrism**<`P`\>(`pointer`): [`Prism`](Prism-1.md)<`P`\> Returns a prism of the value at the provided pointer. #### Type parameters | Name | | :------ | | `P` | #### Parameters | Name | Type | | :------ | :------ | | `pointer` | [`Pointer`](../README.md#pointer)<`P`\> | #### Returns [`Prism`](Prism-1.md)<`P`\> #### Defined in [pointerToPrism.ts:21](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/pointerToPrism.ts#L21) ================================================ FILE: packages/dataverse/api/interfaces/Prism-1.md ================================================ [@theatre/dataverse](../README.md) / Prism # Interface: Prism Common interface for prisms. ## Type parameters | Name | | :------ | | `V` | ## Table of contents ### Properties - [isHot](Prism-1.md#ishot) - [isPrism](Prism-1.md#isprism) ### Methods - [getValue](Prism-1.md#getvalue) - [keepHot](Prism-1.md#keephot) - [onChange](Prism-1.md#onchange) - [onStale](Prism-1.md#onstale) ## Properties ### isHot • **isHot**: `boolean` Whether the prism is hot. #### Defined in [prism/Interface.ts:18](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/Interface.ts#L18) ___ ### isPrism • **isPrism**: ``true`` Whether the object is a prism. #### Defined in [prism/Interface.ts:13](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/Interface.ts#L13) ## Methods ### getValue ▸ **getValue**(): `V` Gets the current value of the prism. If the value is stale, it causes the prism to freshen. #### Returns `V` #### Defined in [prism/Interface.ts:60](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/Interface.ts#L60) ___ ### keepHot ▸ **keepHot**(): `VoidFn` Keep the prism hot, even if there are no tappers (subscribers). #### Returns `VoidFn` #### Defined in [prism/Interface.ts:34](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/Interface.ts#L34) ___ ### onChange ▸ **onChange**(`ticker`, `listener`, `immediate?`): `VoidFn` Calls `listener` with a fresh value every time the prism _has_ a new value, throttled by Ticker. #### Parameters | Name | Type | | :------ | :------ | | `ticker` | [`Ticker`](../classes/Ticker.md) | | `listener` | (`v`: `V`) => `void` | | `immediate?` | `boolean` | #### Returns `VoidFn` #### Defined in [prism/Interface.ts:23](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/Interface.ts#L23) ___ ### onStale ▸ **onStale**(`cb`): `VoidFn` #### Parameters | Name | Type | | :------ | :------ | | `cb` | () => `void` | #### Returns `VoidFn` #### Defined in [prism/Interface.ts:29](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/Interface.ts#L29) ================================================ FILE: packages/dataverse/api/modules/prism.md ================================================ [@theatre/dataverse](../README.md) / prism # Namespace: prism Creates a prism from the passed function that adds all prisms referenced in it as dependencies, and reruns the function when these change. **`Param`** The function to rerun when the prisms referenced in it change. ## Table of contents ### Variables - [effect](prism.md#effect) - [ensurePrism](prism.md#ensureprism) - [inPrism](prism.md#inprism) - [memo](prism.md#memo) - [ref](prism.md#ref) - [scope](prism.md#scope) - [source](prism.md#source) - [state](prism.md#state) - [sub](prism.md#sub) ## Variables ### effect • **effect**: (`key`: `string`, `cb`: () => () => `void`, `deps?`: `unknown`[]) => `void` #### Type declaration ▸ (`key`, `cb`, `deps?`): `void` An effect hook, similar to React's `useEffect()`, but is not sensitive to call order by using `key`. ##### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `key` | `string` | the key for the effect. Should be uniqe inside of the prism. | | `cb` | () => () => `void` | the callback function. Requires returning a cleanup function. | | `deps?` | `unknown`[] | the dependency array | ##### Returns `void` #### Defined in [prism/prism.ts:885](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/prism.ts#L885) ___ ### ensurePrism • **ensurePrism**: () => `void` #### Type declaration ▸ (): `void` This is useful to make sure your code is running inside a `prism()` call. ##### Returns `void` **`Example`** ```ts import {prism} from '@theatre/dataverse' function onlyUsefulInAPrism() { prism.ensurePrism() } prism(() => { onlyUsefulInAPrism() // will run fine }) setTimeout(() => { onlyUsefulInAPrism() // throws an error console.log('This will never get logged') }, 0) ``` #### Defined in [prism/prism.ts:887](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/prism.ts#L887) ___ ### inPrism • **inPrism**: () => `boolean` #### Type declaration ▸ (): `boolean` ##### Returns `boolean` true if the current function is running inside a `prism()` call. #### Defined in [prism/prism.ts:891](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/prism.ts#L891) ___ ### memo • **memo**: (`key`: `string`, `fn`: () => `T`, `deps`: `undefined` \| `any`[] \| readonly `any`[]) => `T` #### Type declaration ▸ <`T`\>(`key`, `fn`, `deps`): `T` `prism.memo()` works just like React's `useMemo()` hook. It's a way to cache the result of a function call. The only difference is that `prism.memo()` requires a key to be passed into it, whlie `useMemo()` doesn't. This means that we can call `prism.memo()` in any order, and we can call it multiple times with the same key. ##### Type parameters | Name | | :------ | | `T` | ##### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `key` | `string` | The key for the memo. Should be unique inside of the prism | | `fn` | () => `T` | The function to memoize | | `deps` | `undefined` \| `any`[] \| readonly `any`[] | The dependency array. Provide `[]` if you want to the value to be memoized only once and never re-calculated. | ##### Returns `T` The result of the function call **`Example`** ```ts const pr = prism(() => { const memoizedReturnValueOfExpensiveFn = prism.memo("memo1", expensiveFn, []) }) ``` #### Defined in [prism/prism.ts:886](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/prism.ts#L886) ___ ### ref • **ref**: (`key`: `string`, `initialValue`: `T`) => `IRef`<`T`\> #### Type declaration ▸ <`T`\>(`key`, `initialValue`): `IRef`<`T`\> Just like React's `useRef()`, `prism.ref()` allows us to create a prism that holds a reference to some value. The only difference is that `prism.ref()` requires a key to be passed into it, whlie `useRef()` doesn't. This means that we can call `prism.ref()` in any order, and we can call it multiple times with the same key. ##### Type parameters | Name | | :------ | | `T` | ##### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `key` | `string` | The key for the ref. Should be unique inside of the prism. | | `initialValue` | `T` | The initial value for the ref. | ##### Returns `IRef`<`T`\> `{current: V}` - The ref object. Note that the ref object will always return its initial value if the prism is cold. It'll only record its current value if the prism is hot (and will forget again if the prism goes cold again). **`Example`** ```ts const pr = prism(() => { const ref1 = prism.ref("ref1", 0) console.log(ref1.current) // will print 0, and if the prism is hot, it'll print the current value ref1.current++ // changing the current value of the ref }) ``` #### Defined in [prism/prism.ts:884](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/prism.ts#L884) ___ ### scope • **scope**: (`key`: `string`, `fn`: () => `T`) => `T` #### Type declaration ▸ <`T`\>(`key`, `fn`): `T` ##### Type parameters | Name | | :------ | | `T` | ##### Parameters | Name | Type | | :------ | :------ | | `key` | `string` | | `fn` | () => `T` | ##### Returns `T` #### Defined in [prism/prism.ts:889](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/prism.ts#L889) ___ ### source • **source**: (`subscribe`: (`fn`: (`val`: `V`) => `void`) => `VoidFn`, `getValue`: () => `V`) => `V` #### Type declaration ▸ <`V`\>(`subscribe`, `getValue`): `V` `prism.source()` allow a prism to react to changes in some external source (other than other prisms). For example, `Atom.pointerToPrism()` uses `prism.source()` to create a prism that reacts to changes in the atom's value. ##### Type parameters | Name | | :------ | | `V` | ##### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `subscribe` | (`fn`: (`val`: `V`) => `void`) => `VoidFn` | The prism will call this function as soon as the prism goes hot. This function should return an unsubscribe function function which the prism will call when it goes cold. | | `getValue` | () => `V` | A function that returns the current value of the external source. | ##### Returns `V` The current value of the source Example: ```ts function prismFromInputElement(input: HTMLInputElement): Prism { function listen(cb: (value: string) => void) { const listener = () => { cb(input.value) } input.addEventListener('input', listener) return () => { input.removeEventListener('input', listener) } } function get() { return input.value } return prism(() => prism.source(listen, get)) } ``` #### Defined in [prism/prism.ts:892](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/prism.ts#L892) ___ ### state • **state**: (`key`: `string`, `initialValue`: `T`) => [`T`, (`val`: `T`) => `void`] #### Type declaration ▸ <`T`\>(`key`, `initialValue`): [`T`, (`val`: `T`) => `void`] A state hook, similar to react's `useState()`. ##### Type parameters | Name | | :------ | | `T` | ##### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `key` | `string` | the key for the state | | `initialValue` | `T` | the initial value | ##### Returns [`T`, (`val`: `T`) => `void`] [currentState, setState] **`Example`** ```ts import {prism} from 'dataverse' // This prism holds the current mouse position and updates when the mouse moves const mousePositionD = prism(() => { const [pos, setPos] = prism.state<[x: number, y: number]>('pos', [0, 0]) prism.effect( 'setupListeners', () => { const handleMouseMove = (e: MouseEvent) => { setPos([e.screenX, e.screenY]) } document.addEventListener('mousemove', handleMouseMove) return () => { document.removeEventListener('mousemove', handleMouseMove) } }, [], ) return pos }) ``` #### Defined in [prism/prism.ts:888](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/prism.ts#L888) ___ ### sub • **sub**: (`key`: `string`, `fn`: () => `T`, `deps`: `undefined` \| `any`[]) => `T` #### Type declaration ▸ <`T`\>(`key`, `fn`, `deps`): `T` Just an alias for `prism.memo(key, () => prism(fn), deps).getValue()`. It creates a new prism, memoizes it, and returns the value. `prism.sub()` is useful when you want to divide your prism into smaller prisms, each of which would _only_ recalculate when _certain_ dependencies change. In other words, it's an optimization tool. ##### Type parameters | Name | | :------ | | `T` | ##### Parameters | Name | Type | Description | | :------ | :------ | :------ | | `key` | `string` | The key for the memo. Should be unique inside of the prism | | `fn` | () => `T` | The function to run inside the prism | | `deps` | `undefined` \| `any`[] | The dependency array. Provide `[]` if you want to the value to be memoized only once and never re-calculated. | ##### Returns `T` The value of the inner prism #### Defined in [prism/prism.ts:890](https://github.com/theatre-js/theatre/blob/main/packages/dataverse/src/prism/prism.ts#L890) ================================================ FILE: packages/dataverse/devEnv/api-extractor.json ================================================ /** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../../devEnv/api-extractor-base.json", // "extends": "my-package/include/api-extractor-base.json" /** * Determines the "" token that can be used with other config file settings. The project folder * typically contains the tsconfig.json and package.json config files, but the path is user-defined. * * The path is resolved relative to the folder of the config file that contains the setting. * * The default value for "projectFolder" is the token "", which means the folder is determined by traversing * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error * will be reported. * * SUPPORTED TOKENS: * DEFAULT VALUE: "" */ "projectFolder": ".." } ================================================ FILE: packages/dataverse/devEnv/api-extractor.tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "../tsconfig.json" } ================================================ FILE: packages/dataverse/devEnv/build.ts ================================================ import * as path from 'path' import {build} from 'esbuild' const definedGlobals = {} function createBundles(watch: boolean) { const pathToPackage = path.join(__dirname, '../') const esbuildConfig: Parameters[0] = { entryPoints: [path.join(pathToPackage, 'src/index.ts')], bundle: true, sourcemap: true, define: definedGlobals, watch, platform: 'neutral', mainFields: ['browser', 'module', 'main'], target: ['es2020'], conditions: ['browser', 'node'], } void build({ ...esbuildConfig, outfile: path.join(pathToPackage, 'dist/index.js'), format: 'cjs', }) // build({ // ...esbuildConfig, // outfile: path.join(pathToPackage, 'dist/index.mjs'), // format: 'esm', // }) } createBundles(false) ================================================ FILE: packages/dataverse/devEnv/tsconfig.json ================================================ { } ================================================ FILE: packages/dataverse/package.json ================================================ { "name": "@theatre/dataverse", "version": "0.7.0", "license": "Apache-2.0", "author": { "name": "Aria Minaei", "email": "aria@theatrejs.com", "url": "https://github.com/AriaMinaei" }, "repository": { "type": "git", "url": "https://github.com/AriaMinaei/theatre", "directory": "packages/dataverse" }, "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist/**/*" ], "scripts": { "prepack": "node ../../devEnv/ensurePublishing.js", "typecheck": "yarn run build:ts", "build": "run-s build:ts build:js build:api-json", "build:ts": "tsc --build ./tsconfig.json", "build:js": "tsx ./devEnv/build.ts", "build:api-json": "api-extractor run --local --config devEnv/api-extractor.json", "prepublish": "node ../../devEnv/ensurePublishing.js", "clean": "rm -rf ./dist && rm -f tsconfig.tsbuildinfo", "docs": "typedoc src/index.ts --out api --plugin typedoc-plugin-markdown --readme none", "precommit": "yarn run docs" }, "devDependencies": { "@microsoft/api-extractor": "^7.36.4", "@types/jest": "^26.0.23", "@types/lodash-es": "^4.17.4", "@types/node": "^15.6.2", "esbuild": "^0.12.15", "npm-run-all": "^4.1.5", "tsx": "4.7.0", "typedoc": "^0.24.8", "typedoc-plugin-markdown": "^3.15.4", "typescript": "5.1.6" }, "dependencies": { "lodash-es": "^4.17.21" } } ================================================ FILE: packages/dataverse/src/Atom.test.ts ================================================ // eslint-disable-next-line import/no-extraneous-dependencies import {Atom} from '@theatre/dataverse' describe(`Atom`, () => { test(`Usage of Atom and pointer, without prism`, async () => { const data = {foo: 'foo', bar: 0} const atom = new Atom(data) // atom.get() returns an exact reference to the data expect(atom.get()).toBe(data) atom.set({foo: 'foo', bar: 1}) expect(atom.get()).not.toBe(data) expect(atom.get()).toEqual({foo: 'foo', bar: 1}) atom.reduce(({foo, bar}) => ({foo, bar: bar + 1})) expect(atom.get()).toEqual({foo: 'foo', bar: 2}) atom.setByPointer((p) => p.bar, 3) expect(atom.get()).toEqual({foo: 'foo', bar: 3}) atom.setByPointer((p) => p.foo, 'foo2') expect(atom.get()).toEqual({foo: 'foo2', bar: 3}) // this would work in runtime, but typescript will complain because `baz` is not a property of the state // let's silence the error for the sake of the test // @ts-ignore atom.setByPointer((p) => p.baz, 'baz') expect(atom.get()).toEqual({foo: 'foo2', bar: 3, baz: 'baz'}) atom.setByPointer((p) => p, {foo: 'newfoo', bar: -1}) expect(atom.get()).toEqual({foo: 'newfoo', bar: -1}) // `getByPointer()` is to `get()` what `setByPointer()` is to `set()` expect(atom.getByPointer((p) => p.bar)).toBe(-1) // `reduceByPointer()` is to `setByPointer()` what `reduce()` is to `set()` atom.reduceByPointer( (p) => p.bar, (bar) => bar + 1, ) expect(atom.get()).toEqual({foo: 'newfoo', bar: 0}) // we can use external pointers too const externalPointer = atom.pointer.bar atom.setByPointer(() => externalPointer, 1) expect(atom.get()).toEqual({foo: 'newfoo', bar: 1}) let internalPointer // the pointer passed to `setByPointer()` is the same as the one returned by `atom.pointer` atom.setByPointer((p) => { internalPointer = p return p.bar }, 2) expect(internalPointer).toBe(atom.pointer) expect(atom.pointer).toBe(atom.pointer) expect(atom.pointer.bar).toBe(atom.pointer.bar) // pointers don't change when the atom's state changes const oldPointer = atom.pointer.bar atom.set({foo: 'foo', bar: 10}) expect(atom.pointer.bar).toBe(oldPointer) }) }) ================================================ FILE: packages/dataverse/src/Atom.ts ================================================ import get from 'lodash-es/get' import isPlainObject from 'lodash-es/isPlainObject' import last from 'lodash-es/last' import type {Prism} from './prism/Interface' import type {Pointer} from './pointer' import {getPointerParts} from './pointer' import {isPointer} from './pointer' import pointer from './pointer' import type {$FixMe, $IntentionalAny} from './types' import updateDeep from './utils/updateDeep' import prism from './prism/prism' import type {PointerToPrismProvider} from './pointerToPrism' type Listener = (newVal: unknown) => void enum ValueTypes { Dict, Array, Other, } const getTypeOfValue = (v: unknown): ValueTypes => { if (Array.isArray(v)) return ValueTypes.Array if (isPlainObject(v)) return ValueTypes.Dict return ValueTypes.Other } const getKeyOfValue = ( v: unknown, key: string | number, vType: ValueTypes = getTypeOfValue(v), ): unknown => { if (vType === ValueTypes.Dict && typeof key === 'string') { return (v as $IntentionalAny)[key] } else if (vType === ValueTypes.Array && isValidArrayIndex(key)) { return (v as $IntentionalAny)[key] } else { return undefined } } const isValidArrayIndex = (key: string | number): boolean => { const inNumber = typeof key === 'number' ? key : parseInt(key, 10) return ( !isNaN(inNumber) && inNumber >= 0 && inNumber < Infinity && (inNumber | 0) === inNumber ) } class Scope { children: Map = new Map() identityChangeListeners: Set = new Set() constructor( readonly _parent: undefined | Scope, readonly _path: (string | number)[], ) {} addIdentityChangeListener(cb: Listener) { this.identityChangeListeners.add(cb) } removeIdentityChangeListener(cb: Listener) { this.identityChangeListeners.delete(cb) this._checkForGC() } removeChild(key: string | number) { this.children.delete(key) this._checkForGC() } getChild(key: string | number) { return this.children.get(key) } getOrCreateChild(key: string | number) { let child = this.children.get(key) if (!child) { child = child = new Scope(this, this._path.concat([key])) this.children.set(key, child) } return child } _checkForGC() { if (this.identityChangeListeners.size > 0) return if (this.children.size > 0) return if (this._parent) { this._parent.removeChild(last(this._path) as string | number) } } } /** * Wraps an object whose (sub)properties can be individually tracked. */ export default class Atom implements PointerToPrismProvider { private _currentState: State /** * @internal */ readonly $$isPointerToPrismProvider = true private readonly _rootScope: Scope /** * Convenience property that gives you a pointer to the root of the atom. * * @remarks * Equivalent to `pointer({ root: thisAtom, path: [] })`. */ readonly pointer: Pointer = pointer({root: this as $FixMe, path: []}) readonly prism: Prism = this.pointerToPrism( this.pointer, ) as $IntentionalAny constructor(initialState: State) { this._currentState = initialState this._rootScope = new Scope(undefined, []) } /** * Sets the state of the atom. * * @param newState - The new state of the atom. */ set(newState: State) { const oldState = this._currentState this._currentState = newState this._checkUpdates(this._rootScope, oldState, newState) } /** * Returns the current state of the atom. */ get(): State { return this._currentState } /** * Returns the value at the given pointer * * @param pointerOrFn - A pointer to the desired path. Could also be a function returning a pointer * * Example * ```ts * const atom = atom({ a: { b: 1 } }) * atom.getByPointer(atom.pointer.a.b) // 1 * atom.getByPointer((p) => p.a.b) // 1 * ``` */ getByPointer( pointerOrFn: Pointer | ((p: Pointer) => Pointer), ): S { const pointer = isPointer(pointerOrFn) ? pointerOrFn : (pointerOrFn as $IntentionalAny)(this.pointer) const path = getPointerParts(pointer).path return this._getIn(path) as S } /** * Gets the state of the atom at `path`. */ private _getIn(path: (string | number)[]): unknown { return path.length === 0 ? this.get() : get(this.get(), path) } reduce(fn: (state: State) => State) { this.set(fn(this.get())) } /** * Reduces the value at the given pointer * * @param pointerOrFn - A pointer to the desired path. Could also be a function returning a pointer * * Example * ```ts * const atom = atom({ a: { b: 1 } }) * atom.reduceByPointer(atom.pointer.a.b, (b) => b + 1) // atom.get().a.b === 2 * atom.reduceByPointer((p) => p.a.b, (b) => b + 1) // atom.get().a.b === 2 * ``` */ reduceByPointer( pointerOrFn: Pointer | ((p: Pointer) => Pointer), reducer: (s: S) => S, ) { const pointer = isPointer(pointerOrFn) ? pointerOrFn : (pointerOrFn as $IntentionalAny)(this.pointer) const path = getPointerParts(pointer).path const newState = updateDeep(this.get(), path, reducer) this.set(newState) } /** * Sets the value at the given pointer * * @param pointerOrFn - A pointer to the desired path. Could also be a function returning a pointer * * Example * ```ts * const atom = atom({ a: { b: 1 } }) * atom.setByPointer(atom.pointer.a.b, 2) // atom.get().a.b === 2 * atom.setByPointer((p) => p.a.b, 2) // atom.get().a.b === 2 * ``` */ setByPointer( pointerOrFn: Pointer | ((p: Pointer) => Pointer), val: S, ) { this.reduceByPointer(pointerOrFn, () => val) } private _checkUpdates(scope: Scope, oldState: unknown, newState: unknown) { if (oldState === newState) return for (const cb of scope.identityChangeListeners) { cb(newState) } if (scope.children.size === 0) return // @todo we can probably skip checking value types const oldValueType = getTypeOfValue(oldState) const newValueType = getTypeOfValue(newState) if (oldValueType === ValueTypes.Other && oldValueType === newValueType) return for (const [childKey, childScope] of scope.children) { const oldChildVal = getKeyOfValue(oldState, childKey, oldValueType) const newChildVal = getKeyOfValue(newState, childKey, newValueType) this._checkUpdates(childScope, oldChildVal, newChildVal) } } private _getOrCreateScopeForPath(path: (string | number)[]): Scope { let curScope = this._rootScope for (const pathEl of path) { curScope = curScope.getOrCreateChild(pathEl) } return curScope } /** * Adds a listener that will be called whenever the value at the given pointer changes. * @param pointer - The pointer to listen to * @param cb - The callback to call when the value changes * @returns A function that can be called to unsubscribe from the listener * * **NOTE** Unlike {@link prism}s, `onChangeByPointer` and `onChange()` are traditional event listeners. They don't * provide any of the benefits of prisms. They don't compose, they can't be coordinated via a Ticker, their derivations * aren't cached, etc. You're almost always better off using a prism (which will internally use `onChangeByPointer`). * * ```ts * const a = atom({foo: 1}) * const unsubscribe = a.onChangeByPointer(a.pointer.foo, (v) => { * console.log('foo changed to', v) * }) * a.setByPointer(a.pointer.foo, 2) // logs 'foo changed to 2' * a.set({foo: 3}) // logs 'foo changed to 3' * unsubscribe() * ``` */ onChangeByPointer = ( pointerOrFn: Pointer | ((p: Pointer) => Pointer), cb: (v: S) => void, ): (() => void) => { const pointer = isPointer(pointerOrFn) ? pointerOrFn : (pointerOrFn as $IntentionalAny)(this.pointer) const {path} = getPointerParts(pointer) const scope = this._getOrCreateScopeForPath(path) scope.identityChangeListeners.add(cb as $IntentionalAny) const unsubscribe = () => { scope.identityChangeListeners.delete(cb as $IntentionalAny) } return unsubscribe } /** * Adds a listener that will be called whenever the state of the atom changes. * @param cb - The callback to call when the value changes * @returns A function that can be called to unsubscribe from the listener * * **NOTE** Unlike {@link prism}s, `onChangeByPointer` and `onChange()` are traditional event listeners. They don't * provide any of the benefits of prisms. They don't compose, they can't be coordinated via a Ticker, their derivations * aren't cached, etc. You're almost always better off using a prism (which will internally use `onChangeByPointer`). * * ```ts * const a = atom({foo: 1}) * const unsubscribe = a.onChange((v) => { * console.log('a changed to', v) * }) * a.set({foo: 3}) // logs 'a changed to {foo: 3}' * unsubscribe() * ``` */ onChange(cb: (v: State) => void): () => void { return this.onChangeByPointer(this.pointer, cb) } /** * Returns a new prism of the value at the provided path. * * @param pointer - The path to create the prism at. * * ```ts * const pr = atom({ a: { b: 1 } }).pointerToPrism(atom.pointer.a.b) * pr.getValue() // 1 * ``` */ pointerToPrism

(pointer: Pointer

): Prism

{ const {path} = getPointerParts(pointer) const subscribe = (listener: (val: unknown) => void) => this.onChangeByPointer(pointer, listener) const getValue = () => this._getIn(path) return prism(() => { return prism.source(subscribe, getValue) }) as Prism

} } ================================================ FILE: packages/dataverse/src/PointerProxy.ts ================================================ import Atom from './Atom' import {val} from './val' import type {Pointer} from './pointer' import {getPointerMeta} from './pointer' import pointer from './pointer' import type {$FixMe, $IntentionalAny} from './types' import prism from './prism/prism' import type {Prism} from './prism/Interface' import type {PointerToPrismProvider} from './pointerToPrism' /** * Allows creating pointer-prisms where the pointer can be switched out. * * @remarks * This allows reacting not just to value changes at a certain pointer, but changes * to the proxied pointer too. */ export default class PointerProxy implements PointerToPrismProvider { /** * @internal */ readonly $$isPointerToPrismProvider = true private readonly _currentPointerBox: Atom> /** * Convenience pointer pointing to the root of this PointerProxy. * * @remarks * Allows convenient use of {@link pointerToPrism} and {@link val}. */ readonly pointer: Pointer constructor(currentPointer: Pointer) { this._currentPointerBox = new Atom(currentPointer) this.pointer = pointer({root: this as $FixMe, path: []}) } /** * Sets the underlying pointer. * @param p - The pointer to be proxied. */ setPointer(p: Pointer) { this._currentPointerBox.set(p) } /** * Returns a prism of the value at the provided sub-path of the proxied pointer. * * @param path - The path to create the prism at. */ pointerToPrism

(pointer: Pointer

): Prism

{ const {path} = getPointerMeta(pointer) return prism(() => { const currentPointer = this._currentPointerBox.prism.getValue() const subPointer = path.reduce( (pointerSoFar, pathItem) => (pointerSoFar as $IntentionalAny)[pathItem], currentPointer, ) return val(subPointer) as P }) } } ================================================ FILE: packages/dataverse/src/Ticker.test.ts ================================================ // eslint-disable-next-line import/no-extraneous-dependencies import {Ticker} from '@theatre/dataverse' import {EMPTY_TICKS_BEFORE_GOING_DORMANT} from './Ticker' describe(`Ticker`, () => { test(`Usage of Ticker`, async () => { const ticker = new Ticker() const listener = jest.fn() ticker.onNextTick(listener) ticker.tick() expect(listener).toHaveBeenCalledTimes(1) ticker.tick() expect(listener).toHaveBeenCalledTimes(1) }) test(`Tickers go dormant`, () => { const onDormant = jest.fn() const onActive = jest.fn() const ticker = new Ticker({onDormant, onActive}) expect(ticker.dormant).toBe(true) const listener = jest.fn() ticker.onNextTick(listener) expect(ticker.dormant).toBe(false) expect(onActive).toHaveBeenCalledTimes(1) ticker.tick() expect(listener).toHaveBeenCalledTimes(1) // at this point, the ticker is active, but after a few ticks, it should go dormant for (let i = 0; i < EMPTY_TICKS_BEFORE_GOING_DORMANT; i++) { ticker.tick() if (i < EMPTY_TICKS_BEFORE_GOING_DORMANT - 1) { expect(ticker.dormant).toBe(false) expect(onDormant).toHaveBeenCalledTimes(0) } } expect(ticker.dormant).toBe(true) expect(onDormant).toHaveBeenCalledTimes(1) // after going dormant, it should stay dormant ticker.tick() expect(ticker.dormant).toBe(true) expect(onDormant).toHaveBeenCalledTimes(1) // but if we schedule a callback, it should go active again ticker.onNextTick(listener) expect(ticker.dormant).toBe(false) expect(onActive).toHaveBeenCalledTimes(2) }) test(`Ticker.onThisOrNextTick()`, () => { const ticker = new Ticker() const mainListener = jest.fn(() => { ticker.onNextTick(thisWillBeCalledOnTheNextTick) ticker.onThisOrNextTick(thisWillBeCalledOnTheSameTick) }) const thisWillBeCalledOnTheSameTick = jest.fn() const thisWillBeCalledOnTheNextTick = jest.fn() ticker.onThisOrNextTick(mainListener) expect(mainListener).toHaveBeenCalledTimes(0) expect(thisWillBeCalledOnTheSameTick).toHaveBeenCalledTimes(0) expect(thisWillBeCalledOnTheNextTick).toHaveBeenCalledTimes(0) ticker.tick() expect(mainListener).toHaveBeenCalledTimes(1) expect(thisWillBeCalledOnTheSameTick).toHaveBeenCalledTimes(1) expect(thisWillBeCalledOnTheNextTick).toHaveBeenCalledTimes(0) ticker.tick() expect(mainListener).toHaveBeenCalledTimes(1) expect(thisWillBeCalledOnTheSameTick).toHaveBeenCalledTimes(1) expect(thisWillBeCalledOnTheNextTick).toHaveBeenCalledTimes(1) }) }) ================================================ FILE: packages/dataverse/src/Ticker.ts ================================================ type ICallback = (t: number) => void /** * The number of ticks that can pass without any scheduled callbacks before the Ticker goes dormant. This is to prevent * the Ticker from staying active forever, even if there are no scheduled callbacks. * * Perhaps counting ticks vs. time is not the best way to do this. But it's a start. */ export const EMPTY_TICKS_BEFORE_GOING_DORMANT = 60 /*fps*/ * 3 /*seconds*/ // on a 60fps screen, 3 seconds should pass before the ticker goes dormant /** * The Ticker class helps schedule callbacks. Scheduled callbacks are executed per tick. Ticks can be triggered by an * external scheduling strategy, e.g. a raf. */ export default class Ticker { private _scheduledForThisOrNextTick: Set private _scheduledForNextTick: Set private _timeAtCurrentTick: number private _ticking: boolean = false /** * Whether the Ticker is dormant */ private _dormant: boolean = true private _numberOfDormantTicks = 0 /** * Whether the Ticker is dormant */ get dormant(): boolean { return this._dormant } /** * Counts up for every tick executed. * Internally, this is used to measure ticks per second. * * This is "public" to TypeScript, because it's a tool for performance measurements. * Consider this as experimental, and do not rely on it always being here in future releases. */ public __ticks = 0 constructor( private _conf?: { /** * This is called when the Ticker goes dormant. */ onDormant?: () => void /** * This is called when the Ticker goes active. */ onActive?: () => void }, ) { this._scheduledForThisOrNextTick = new Set() this._scheduledForNextTick = new Set() this._timeAtCurrentTick = 0 } /** * Registers for fn to be called either on this tick or the next tick. * * If `onThisOrNextTick()` is called while `Ticker.tick()` is running, the * side effect _will_ be called within the running tick. If you don't want this * behavior, you can use `onNextTick()`. * * Note that `fn` will be added to a `Set()`. Which means, if you call `onThisOrNextTick(fn)` * with the same fn twice in a single tick, it'll only run once. * * @param fn - The function to be registered. * * @see offThisOrNextTick */ onThisOrNextTick(fn: ICallback) { this._scheduledForThisOrNextTick.add(fn) if (this._dormant) { this._goActive() } } /** * Registers a side effect to be called on the next tick. * * @param fn - The function to be registered. * * @see onThisOrNextTick * @see offNextTick */ onNextTick(fn: ICallback) { this._scheduledForNextTick.add(fn) if (this._dormant) { this._goActive() } } /** * De-registers a fn to be called either on this tick or the next tick. * * @param fn - The function to be de-registered. * * @see onThisOrNextTick */ offThisOrNextTick(fn: ICallback) { this._scheduledForThisOrNextTick.delete(fn) } /** * De-registers a fn to be called on the next tick. * * @param fn - The function to be de-registered. * * @see onNextTick */ offNextTick(fn: ICallback) { this._scheduledForNextTick.delete(fn) } /** * The time at the start of the current tick if there is a tick in progress, otherwise defaults to * `performance.now()`. */ get time() { if (this._ticking) { return this._timeAtCurrentTick } else return performance.now() } private _goActive() { if (!this._dormant) return this._dormant = false this._conf?.onActive?.() } private _goDormant() { if (this._dormant) return this._dormant = true this._numberOfDormantTicks = 0 this._conf?.onDormant?.() } /** * Triggers a tick which starts executing the callbacks scheduled for this tick. * * @param t - The time at the tick. * * @see onThisOrNextTick * @see onNextTick */ tick(t: number = performance.now()) { if (process.env.NODE_ENV === 'development') { if (!(this instanceof Ticker)) { throw new Error( 'ticker.tick must be called while bound to the ticker. As in, "ticker.tick(time)" or "requestAnimationFrame((t) => ticker.tick(t))" for performance.', ) } } this.__ticks++ if (!this._dormant) { if ( this._scheduledForNextTick.size === 0 && this._scheduledForThisOrNextTick.size === 0 ) { this._numberOfDormantTicks++ if (this._numberOfDormantTicks >= EMPTY_TICKS_BEFORE_GOING_DORMANT) { this._goDormant() return } } } this._ticking = true this._timeAtCurrentTick = t for (const v of this._scheduledForNextTick) { this._scheduledForThisOrNextTick.add(v) } this._scheduledForNextTick.clear() this._tick(0) this._ticking = false } private _tick(iterationNumber: number): void { const time = this.time if (iterationNumber > 10) { console.warn('_tick() recursing for 10 times') } if (iterationNumber > 100) { throw new Error(`Maximum recursion limit for _tick()`) } const oldSet = this._scheduledForThisOrNextTick this._scheduledForThisOrNextTick = new Set() for (const fn of oldSet) { fn(time) } if (this._scheduledForThisOrNextTick.size > 0) { return this._tick(iterationNumber + 1) } } } ================================================ FILE: packages/dataverse/src/atom.typeTest.ts ================================================ import Atom from './Atom' import {val} from './val' import {expectType, _any} from './utils/typeTestUtils' ;() => { const p = new Atom<{foo: string; bar: number; optional?: boolean}>(_any) .pointer expectType(val(p.foo)) // @ts-expect-error TypeTest expectType(val(p.foo)) expectType(val(p.bar)) // @ts-expect-error TypeTest expectType(val(p.bar)) // @ts-expect-error TypeTest expectType<{}>(val(p.nonExistent)) expectType(val(p.optional)) // @ts-expect-error TypeTest expectType(val(p.optional)) // @ts-expect-error TypeTest expectType(val(p.optional)) // @ts-expect-error TypeTest expectType(val(p.optional)) } ================================================ FILE: packages/dataverse/src/dataverse.test.ts ================================================ // eslint-disable-next-line import/no-extraneous-dependencies import type {Pointer, Prism} from '@theatre/dataverse' // eslint-disable-next-line import/no-extraneous-dependencies import { isPointer, isPrism, pointerToPrism, Atom, getPointerParts, pointer, prism, Ticker, val, } from '@theatre/dataverse' import {set as lodashSet} from 'lodash-es' import {isPointerToPrismProvider} from './pointerToPrism' describe(`The exhaustive guide to dataverse`, () => { // Hi there! I'm writing this test suite as an ever-green and thorough guide to dataverse. You should be able // to read it from top to bottom and learn pretty much all there is to know about dataverse. // // This is not a quick-start guide, so feel free to skip around. // // Since this is a test suite, you should be able to run it in [debug mode](https://jestjs.io/docs/en/troubleshooting) // and inspect the value of variables at any point in the test. // We recommend you follow this guide using VSCode's [Jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest), // which allows you to run/debug tests from within the editor and see the values of variables in the editor itself. describe(`0 - Concepts`, () => { // There 4 main concepts in dataverse: // - Atoms, hold the state of your application. // - Pointers are a type-safe way to refer to specific properties of atoms. // - Prisms are functions that derive values from atoms or from other prisms. // - Tickers are a way to schedule and synchronise computations. // before we dive into the concepts, let me show you how a simple dataverse setup looks like. test('0.1 - A simple dataverse setup', () => { // In this setup, we're gonna write a program that renders an image of a sunset, // like this: // | // \ / // .-"-. // -- / \ -- // `~~^~^~^~^~^~^~^~^~^-=======-~^~^~^~~^~^~^~^~^~^~^~` // `~^_~^~^~-~^_~^~^_~-=========- -~^~^~^-~^~^_~^~^~^~` // `~^~-~~^~^~-^~^_~^~~ -=====- ~^~^~-~^~_~^~^~~^~-~^~` // `~^~^~-~^~~^~-~^~~-~^~^~-~^~~^-~^~^~^-~^~^~^~^~~^~-` // (Art by Joan G. Stark) https://www.asciiart.eu/nature/sunset // our program is going to have one parameter, which is `timeOfDay`, which is a number between 0 and 24. // the idea is that as `timeOfDay` changes, our program would render the sun in a different position. // Let's express the state of our program as a dataverse `Atom`. An `Atom` basically holds // a piece of state, and it can be read from and written to. It also provides a way to listen // to changes in the state. const state = new Atom({timeOfDay: 0, imageSize: 100}) // we should be able to advance the time of day by calling `timeOfDay.set()` state.set({...state.get(), timeOfDay: 12}) // now, we're going to write a function that renders the image of the sunset. // this function is going to be a "reactive function", which means that it's going to // re-run whenever any of its dependencies change. // in this case, the only dependency is `timeOfDay`, so we're going to use `prism()` to create // a reactive function out of it. const renderSunset = prism(() => { const timeOfDay = val(state.pointer.timeOfDay) // we're gonna cover what `val()` and `pointer` mean, later. For now, just know that // `val()` is a function that returns the value of a pointer, // and `state.pointer.timeOfDay` helps `val()` find only get the value of `timeOfDay` and // not the value of the whole state. // Okay, we're not _actually_ going to render a piece of ascii art here, although that would have been cool. // For now, just a simple string will do. return `The time of day is ${timeOfDay}` }) // now, if we call `renderSunset.getValue()`, we'll get the string that we returned from the function. expect(renderSunset.getValue()).toBe(`The time of day is 12`) // now, to make our program reactive, we can simply listen to changes to the value of our prism: // in order to listen to changes, we need to create a `Ticker`. We're gonna cover what a `Ticker` is later. // But for now, just know that it's a way to schedule and batch computations, for performance reasons. const ticker = new Ticker() // Now let's define our listener. This one will be a jest mock function. const listener = jest.fn() const unsubscribe = renderSunset.onChange(ticker, listener) // now, if we change the time of day, our listener should be called. state.set({...state.get(), timeOfDay: 13}) ticker.tick() expect(listener).toBeCalledTimes(1) expect(listener).toBeCalledWith(`The time of day is 13`) // and if we change the time of day again, state.set({...state.get(), timeOfDay: 14}) // our listener would _not_ be called, because we haven't ticked the ticker yet. expect(listener).toBeCalledTimes(1) // but if we tick the ticker, ticker.tick() // our listener would be called again. expect(listener).toBeCalledTimes(2) // And that would be it for our simple program. But let's take stock of the concepts we've encountered so far. // 1. We've created an `Atom` to hold the state of our program. // 2. We've created a `prism` to create a reactive function out of `timeOfDay`. // 3. We've used a pointer to get the value of `timeOfDay` from the state. // 4. We've used a `Ticker` to schedule and batch computations. // In the rest of this guide, we're gonna cover each of these concepts in detail. // But let's wrap this test case up by cleaning up our resources. unsubscribe() }) }) describe(`1 - What is a prism?`, () => { // A Prism is a way to create a value that depends on other values. // let's start with a simple example: test(`1.1 - A pretty useless prism`, async () => { // Each prism has a calculate function that it runs to calculate its value. let's make a simple function that just returns 1 const calculate = jest.fn(() => 1) // Now we can make a prism out of it const pr = prism(calculate) // Now, this prism is pretty useless. It doesn't depend on anything, and it doesn't have any dependents. // But we can already illustrate some of the concepts that are important to understand prisms. // Our `calculate` function will never be called until it's actually needed - prisms are lazy. expect(calculate).not.toHaveBeenCalled() // We can get the value of the prism, which will be what the `calculate` function returned, expect(pr.getValue()).toBe(1) // and of course our calculate function will have been called. expect(calculate).toHaveBeenCalledTimes(1) // Now, you might expect that if we call `getValue()` again, the calculate function won't be called again. // But that's _not_ the case. the calculate function will be called again, because our prism is cold. // We'll talk about cold/hot in a bit, but let's just say that cold prisms are calculated every time they're read. pr.getValue() expect(calculate).toHaveBeenCalledTimes(2) // We can even check whether a prism is hot or cold. Ours is cold. expect(pr.isHot).toBe(false) // We'll get to hot prisms soon, but let's talk about dependencies and dependents first. }) // prisms can depend on other prisms. let's make a prism that depends on another prism. test(`1.2 - prisms can depend on other prisms`, async () => { const calculateA = jest.fn(() => 1) const a = prism(calculateA) const calculateATimesTwo = jest.fn(() => a.getValue() * 2) const aTimesTwo = prism(calculateATimesTwo) // let's define a function that clears the count of mocks, as we're gonna do that quite a few times. function clearMocks() { calculateA.mockClear() calculateATimesTwo.mockClear() } // So, `aTimesTwo` depends on `a`. // In our parlance, `aTimesTwo` is a dependent of `a`, and `a` is a dependency of `aTimesTwo`. // Now if we read the value of `aTimesTwo`, it will call `calculateATimesTwo`, which will call `calculateA`: expect(aTimesTwo.getValue()).toBe(2) expect(calculateA).toHaveBeenCalledTimes(1) expect(calculateATimesTwo).toHaveBeenCalledTimes(1) clearMocks() // And like we saw in the previous test, if we read the value of `aTimesTwo` again, it will call both of our calculate functions again: aTimesTwo.getValue() expect(calculateATimesTwo).toHaveBeenCalledTimes(1) expect(calculateA).toHaveBeenCalledTimes(1) clearMocks() // But if we read the value of `a`, it won't call `calculateATimesTwo`: a.getValue() expect(calculateATimesTwo).toHaveBeenCalledTimes(0) expect(calculateA).toHaveBeenCalledTimes(1) clearMocks() // Now let's see what happens if we make our prism hot. // One way to make a prism hot, is to add an `onStale` listener to it. const onStale = jest.fn() const unsubscribe = aTimesTwo.onStale(onStale) // As soon as a prism has an `onStale` listener, it becomes hot... expect(aTimesTwo.isHot).toBe(true) // ... and so will its dependencies, and _their_ dependencies, and so on. expect(a.isHot).toBe(true) // So, let's see what happens when we read the value of `aTimesTwo` again: aTimesTwo.getValue() // Since this is the first time we're calculating `aTimesTwo` after it went hot, `calculateATimesTwo` will be called again, expect(calculateATimesTwo).toHaveBeenCalledTimes(1) // and so will `calculateA`, expect(calculateA).toHaveBeenCalledTimes(1) clearMocks() // But if we read `aTimesTwo` again, none of the calculate functions will be called again. aTimesTwo.getValue() expect(calculateATimesTwo).toHaveBeenCalledTimes(0) expect(calculateA).toHaveBeenCalledTimes(0) clearMocks() // This behavior propogates up the dependency chain, so if we read `a` again, `calculateA` won't be called again, // because its value is already fresh. a.getValue() expect(calculateA).toHaveBeenCalledTimes(0) clearMocks() // At this point, since none of our prisms depend on a prism whose value will change, they'll remain // fresh forever. a.getValue() aTimesTwo.getValue() a.getValue() aTimesTwo.getValue() expect(calculateATimesTwo).toHaveBeenCalledTimes(0) expect(calculateA).toHaveBeenCalledTimes(0) clearMocks() // But as soon as we unsubscribe from our `onStale()` listener, the prisms will become cold again, unsubscribe() expect(aTimesTwo.isHot).toBe(false) expect(a.isHot).toBe(false) // and they'll return back to their cold behavior. aTimesTwo.getValue() expect(calculateATimesTwo).toHaveBeenCalledTimes(1) expect(calculateA).toHaveBeenCalledTimes(1) clearMocks() aTimesTwo.getValue() expect(calculateATimesTwo).toHaveBeenCalledTimes(1) expect(calculateA).toHaveBeenCalledTimes(1) clearMocks() // Now, one more thing before we move on. What will happen if we make `a` hot, but not `aTimesTwo`? // Let's try it out. const unsubcribeFromAOnStale = a.onStale(() => {}) // `a` will go hot: expect(a.isHot).toBe(true) // but `aTimesTwo` stays cold: expect(aTimesTwo.isHot).toBe(false) // Now let's read the value of `a` a.getValue() // `calculateA` will be called expect(calculateA).toHaveBeenCalledTimes(1) // And `calculateATimesTwo` won't. expect(calculateATimesTwo).toHaveBeenCalledTimes(0) clearMocks() // And if we re-read the value of `a`, `calculateA` won't be called again, becuase `a` is hot and its value is fresh. a.getValue() expect(calculateA).toHaveBeenCalledTimes(0) clearMocks() // But if we read the value of `aTimesTwo`, `calculateATimesTwo` will be called, because `aTimesTwo` is cold. aTimesTwo.getValue() expect(calculateATimesTwo).toHaveBeenCalledTimes(1) // yet `calculateA` won't be called, because `a` is hot and its value is fresh. expect(calculateA).toHaveBeenCalledTimes(0) clearMocks() // in conclusion, if we make a prism hot, it'll make its dependencies hot too. // if we read the value of a cold prism, it'll call its calculate function, which will // call the calculate functions of its dependencies, and so on. // but if we read the value of a hot prism, it'll only call its calculate function if its value is stale. // le'ts wrap up this part by unsubscribing from `a`'s `onStale` listener to not have any lingering listeners. unsubcribeFromAOnStale() }) describe('1.3 - What about state?', () => { // so far, our prisms have not depended on any changing values, so in turn, _their_ values have never changed either. // but what if we want to create a prism that depends on a changing value? // we call those values "sources", and we can create them using the `prism.source()` hook: test('1.3.1 - The wrong way to depend on state', () => { // let's say we want to create a prism that depends on this value: let value = 0 // the _wrong_ way to do this, is to create a prism that directly reads this value const p = prism(() => value) // this will actually work if the prism is cold: expect(p.getValue()).toBe(0) value = 1 expect(p.getValue()).toBe(1) // but if we make the prism hot, it'll never update its value, because it's not subscribed to any sources. const unsubscribe = p.onStale(() => {}) expect(p.isHot).toBe(true) // on first read, it'll give us the current value of `value`, which is 1. expect(p.getValue()).toBe(1) // but if we change `value` again, the prism won't know value = 2 expect(p.getValue()).toBe(1) // and so it'll keep returning the old value. expect(p.getValue()).toBe(1) unsubscribe() }) test('1.3.2 - The _less_ wrong way to depend on state', () => { let value = 0 // the source hook requires a `listen` function, and a `get` function. // let's skip the `listen` function for now, and just focus on the `getValue` function. const listen = jest.fn(() => () => {}) // the `getValue` function should return the current value of the source. const get = jest.fn(() => value) const p = prism(() => { return prism.source(listen, get) * 2 }) value = 1 // our prism is cold right now. let's see what happens when we read its value. expect(p.getValue()).toBe(2) // `get` will be called once, because we're reading the value of the source for the first time. expect(get).toHaveBeenCalledTimes(1) // and `listen` won't be called at all expect(listen).toHaveBeenCalledTimes(0) get.mockClear() // now let's make the prism hot const unsubscribe = p.onStale(() => {}) expect(p.isHot).toBe(true) expect(p.getValue()).toBe(2) // `get` will be called again, because we're reading the value of the source for the second time. expect(get).toHaveBeenCalledTimes(1) // and `listen` will be called once, because our prism wants to be notified when the source changes. expect(listen).toHaveBeenCalledTimes(1) get.mockClear() listen.mockClear() // now, since our `listen` function is a mock, it won't actually do anything, // so the prism still won't know when the source changes. value = 2 expect(p.getValue()).toBe(2) // `get` won't be called again, because the source hasn't changed. expect(get).toHaveBeenCalledTimes(0) unsubscribe() }) test('1.3.3 - The right way to depend on state', () => { let value = 0 // now let's implement an actual `listen` function. // first, we need to keep track of all the listeners that our source wil have const listeners = new Set<(val: number) => void>() // the `listen` function should return an stop function. // the stop function should stop listening to the source. const listen = jest.fn((fn) => { listeners.add(fn) return function stop() { listeners.delete(fn) } }) const get = jest.fn(() => value) // and now we need to define a function that will notify all the listeners that the source has changed. const set = (newValue: number) => { if (newValue !== value) { value = newValue listeners.forEach((fn) => fn(value)) } } // don't worry, there are helpers for this in dataverse. but for now, we'll implement // it manually to understand how it works. // now let's create a prism that depends on our source. const p = prism(() => { return prism.source(listen, get) * 2 }) // let's make the prism hot const staleListener = jest.fn() const unsubscribe = p.onStale(staleListener) expect(p.isHot).toBe(true) // and let's read its value expect(p.getValue()).toBe(0) // `get` will be called once, because we're reading the value of the source for the first time. expect(get).toHaveBeenCalledTimes(1) // and `listen` will be called once, because our prism wants to be notified when the source changes. expect(listen).toHaveBeenCalledTimes(1) get.mockClear() listen.mockClear() // now let's change the value of the source set(1) // this time, our prism will know that the source has changed, and it'll update its value. expect(p.getValue()).toBe(2) // and that's how we create a prism that depends on a changing value. unsubscribe() }) }) // in practice, we'll almost never need to use the `source` hook directly, // and we'll never need to provide our own `listen` and `get` functions. // instead, we'll use `Atom`s, which are sources that are already implemented for us. }) describe(`2 - Atoms`, () => { // In the final test of the previous chapter, we learned how to create our own sources of state, // and make a prism depend on them, using the `prism.source()` hook. In this chapter, we'll learn // how to use the `Atom` class, which is a source of state that's already implemented for us and comes // with a lot of useful features. test(`2.1 - Using Atoms without prisms`, () => { const initialState = {foo: 'foo', bar: 0} // Let's create an atom with an initial state. const atom = new Atom(initialState) // We can read our atom's state via `atom.get()` which returns an exact reference to its state expect(atom.get()).toBe(initialState) // `atom.set()` will replace the state with a new object. atom.set({foo: 'foo', bar: 1}) expect(atom.get()).not.toBe(initialState) expect(atom.get()).toEqual({foo: 'foo', bar: 1}) // Another way to change the state, with the reducer pattern. atom.reduce(({foo, bar}) => ({foo, bar: bar + 1})) expect(atom.get()).toEqual({foo: 'foo', bar: 2}) // Having to write `({foo, bar}) => ({foo, bar: bar + 1})` every time we want to change the state // is a bit annoying. This is one place where pointers come in handy. We'll have a whole chapter // about pointers later, but for now, let's just say that they're a type-safe way to refer to a sub-prop of our atom's state. // // In this example, we're using the `setByPointer()` method to change the `bar` property of the state. atom.setByPointer((p) => p.bar, 3) expect(atom.get()).toEqual({foo: 'foo', bar: 3}) // Also, note that there is nothing magical about pointers. They're just a type-safe encoding of `['path', 'to', 'property']`. // Pointers can even point to non-existent properties, and they'll be created when we use them. Typescript will complain if we // try to use a pointer to a non-existent property, but in the runtime, there will be no errors. // Let's silence the typescript error for the sake of the test // @ts-ignore and refer to `baz`, which doesn't actually exist in our state. atom.setByPointer((p) => p.baz, 'baz') // Atom will create the `baz` property for us: expect(atom.get()).toEqual({foo: 'foo', bar: 3, baz: 'baz'}) // The pointer can also refer to the whole state, and we can use it to replace the whole state. atom.setByPointer((p) => p, {foo: 'newfoo', bar: -1}) expect(atom.get()).toEqual({foo: 'newfoo', bar: -1}) // `getByPointer()` is to `get()` what `setByPointer()` is to `set()` expect(atom.getByPointer((p) => p.bar)).toBe(-1) // `reduceByPointer()` is to `setByPointer()` what `reduce()` is to `set()` atom.reduceByPointer( (p) => p.bar, (bar) => bar + 1, ) expect(atom.get()).toEqual({foo: 'newfoo', bar: 0}) // we can use external pointers too (which we'll learn how to create in the next Pointers chapter) const externalPointer = atom.pointer.bar atom.setByPointer(() => externalPointer, 1) expect(atom.get()).toEqual({foo: 'newfoo', bar: 1}) let internalPointer // the pointer passed to `setByPointer()` is the same as the one returned by `atom.pointer` atom.setByPointer((p) => { internalPointer = p return p.bar }, 2) expect(internalPointer).toBe(atom.pointer) expect(atom.pointer).toBe(atom.pointer) expect(atom.pointer.bar).toBe(atom.pointer.bar) // pointers don't change when the atom's state changes const oldPointer = atom.pointer.bar atom.set({foo: 'foo', bar: 10}) expect(atom.pointer.bar).toBe(oldPointer) }) // Now that we know how to set/get the state of Atoms, let's learn how to use them with prisms. test(`2.2 - The hard way to use Atoms with prisms`, () => { // In Chapter 1.3.3, we learned how to create a prism that depends on a changing value, // but we had to provide our own `listen` and `get` functions. Now let's see how to do the same // thing with an Atom. // Just to learn how things work under the hood, we're still going to use the `prism.source()` hook. // In the next chapter, we'll learn how to skip that step too. // Let's create an atom with an initial state. const atom = new Atom({foo: 'foo', bar: 0}) // The same prism from chapter 1.3.3: const pr = prism(() => { return prism.source(listen, get) * 2 }) // now let's define the `listen` and `get` functions that we'll pass to `prism.source()` function listen(cb: (value: number) => void) { // `atom.onChangeByPointer()` is a method that we can use to listen to changes in a specific path of the atom's state. // onChangeByPointer() returns an unsubscribe function, so we'll just return that as is. return atom.onChangeByPointer( // the path to listen to is just the pointer to the `bar` property of the atom's state. atom.pointer.bar, cb, ) } // The `get` function will just return the value of the `bar` property of the atom's state. function get() { return atom.get().bar } // And that's it! We can now use the prism with the atom's state. // let's make the prism hot const staleListener = jest.fn() const unsubscribe = pr.onStale(staleListener) expect(pr.isHot).toBe(true) // and let's read its value expect(pr.getValue()).toBe(0) // now let's change the value of the source atom.setByPointer((p) => p.bar, 1) // our prism will know that the source has changed, and it'll update its value. expect(pr.getValue()).toBe(2) unsubscribe() // and that's how we create a prism that depends on an atom, but that's still // pretty verbose. Let's see how to do the same thing in a more convenient way. }) test(`2.3 - The easy way to use Atoms with prisms`, () => { // In the previous chapter, we learned how to create a prism that depends on an atom, // but we had to provide our own `listen` and `get` functions. Now let's see how to do the same // thing with an Atom, but in the idiomatic way. We'll use pointers and `val()`. // Let's create an atom with an initial state. const atom = new Atom({foo: 'foo', bar: 0}) // Now instead of using `prism.source()`, we'll use val(atom.pointer): const pr = prism(() => { // We'll cover pointers and `val()` soon, but for now, just know that `val(atom.pointer.bar)` // will return the value of the `bar` property of the atom's state. return val(atom.pointer.bar) * 2 }) // and that's it! // let's test that it works as expected const staleListener = jest.fn() const unsubscribe = pr.onStale(staleListener) expect(pr.isHot).toBe(true) // and let's read its value expect(pr.getValue()).toBe(0) // now let's change the value of the source atom.setByPointer((p) => p.bar, 1) // this time, our prism will know that the source has changed, and it'll update its value. expect(pr.getValue()).toBe(2) unsubscribe() }) }) describe(`3 - Pointers`, () => { test('3.0 - Why pointers?', () => { // We've come across pointers a few times already. { // For example, we saw that Atoms provide `set|get|reduceByPointer()` methods: const atom = new Atom({foo: 'foo', bar: 0}) atom.setByPointer((p) => p.bar, 1) // or equivalently: atom.setByPointer(atom.pointer.bar, 1) } // You might be wondering why not just use dot-delimited paths like in lodash's `set(val, 'path.to.prop', 1)`? // The answer is that pointers are much easier to type, and they work well with typescript's autocomplete. // Another benefit is that pointers are always cached, in so that `pointer.bar === pointer.bar` will always be true, // which means we can use them to attach metadata to a pointer. We'll see how to do that in a bit. // Another alternative to pointers is array paths, like in lodash's `set(val, ['path', 'to', 'prop'], 1)`. // Similar to dot-delimited paths, array paths are also not easy to type, and they don't work well with typescript's autocomplete. // Another problem is that creating an array path every time we want to access a property is not very efficient. // The JS engine will have to allocate a new array every time, and then it'll have to iterate over it to find the property. // Pointers on the other hand, are always cached, so they're allocated only once. // We'll learn how to take advantage of these benefits in the next sub-chapters. }) test(`3.1 - Pointers in the runtime`, () => { // Let's have a look at how pointers work in the runtime. // Pointers refer to a specific nested property of an object. The object is called the "root" of the pointer, // and the property is called the "path" of the pointer. // So for example, if this is our root object: const root = {foo: 'foo', bar: 0} // This pointer will refer to the whole object: const p = pointer({root: root, path: []}) // We can inspect the pointer's root and path using `getPointerParts()`: const parts = getPointerParts(p) expect(parts.root).toBe(root) expect(parts.path).toEqual([]) // This pointer will refer to the `foo` property of the root object: const pointerToFoo = p.foo // p.foo is a pointer to the `foo` property of the root object. its only difference to p is that its path is `['foo']` expect(getPointerParts(pointerToFoo).path).toEqual(['foo']) expect(getPointerParts(pointerToFoo).root).toBe(root) // subPointers are cached. Calling `p.foo` twice will return the same pointer: expect(pointerToFoo).toBe(p.foo) // we can also manually construct the pointer to foo: const pointerToFoo2 = pointer({root: root, path: ['foo']}) expect(getPointerParts(pointerToFoo2).path).toEqual(['foo']) }) test(`3.2 - Pointers in typescript`, () => { // Pointers become more useful when we properly type them. Let's do that now: type Data = {str: string; foo?: {bar?: {baz: number}}} const root: Data = {str: 'some string'} const p = pointer({ root, path: [], }) // now typescript will error if we try to access a property that doesn't exist // @ts-expect-error p.baz // but accessing known properties and nested properties is fine p.foo p.foo.bar.baz // we don't need to manually type the pointer since pointers are usually provided by Atoms, and those are already typed const atom = new Atom(root) // so this will be fine by typescript: atom.pointer.foo.bar.baz // while this will error // @ts-ignore atom.pointer.foo.bar.baz.nonExistentProperty }) test(`3.3 - Creating type-safe utility functions with pointers`, () => { // Now that we know how to create pointers, let's see how to use them to create utility functions. // Let's create a function that will set a property of an object by a pointer, similar to `lodash.set()`. // The function will take the root object, the pointer, and the new value. function setByPointer( root: Root, getPointer: (ptr: Pointer) => Pointer, newValue: Value, ): Root { // we'll create a pointer to the root object, which would not be efficient // if `setByPointer` was called many times. We'll see how to improve this in the next sub-chapters. const rootPointer = pointer({ root: root, path: [], }) as Pointer // We'll use `getPointerParts()` to get the root and path of the pointer. const {path} = getPointerParts(getPointer(rootPointer)) // @ts-ignore we'll ignore the typescript error because `lodash.set()` is not typed return lodashSet(root, path, newValue) } // now let's test our utility function const data = {foo: {bar: 0}} const newData = setByPointer(data, (p) => p.foo.bar, 1) expect(newData).toEqual({foo: {bar: 1}}) // Compared to `lodash.set()`, our function is type-safe and plays nicely with intellisense and autocomplete. }) test('3.4 - Converting pointers to prisms', () => { // So, how does the `val()` function work? // Let's look at its implementation: const val = (input: any) => { // if the input is a pointer, we'll convert it to a prism and `getValue()` on it if (isPointer(input)) { return pointerToPrism(input).getValue() // otherwise if it's already a prism, we `getValue()` on it } else if (isPrism(input)) { return input.getValue() } else { // or otherwise we return the input as is. return input } } // So, the interesting part is the `pointerToPrism()` function. How does it // convert a pointer to a prism? // Let's implement it: function pointerToPrismV1(ptr: Pointer): Prism { // we'll use `getPointerParts()` to get the root and path of the pointer const {root} = getPointerParts(ptr) // Then we check whether the root is an atom if (!(root instanceof Atom)) { throw new Error( `pointerToPrismV1() only supports pointers whose root is an Atom`, ) } // We'll need to define the listen/get functions as well // the listen function will listen to changes on the pointer const listen = (cb: (newValue: V) => void): (() => void) => { return atom.onChangeByPointer(ptr, cb) } const get = (): V => { return root.getByPointer(ptr) } // then we'll create a prism that sources from the atom return prism(() => { return prism.source(listen, get) }) } // Now let's test it: const atom = new Atom({foo: {bar: 0}}) const ptr = atom.pointer.foo.bar const p = pointerToPrismV1(ptr) expect(p.getValue()).toBe(0) // It works! // Now let's see how we can improve it. // First, we can cache the prism so that we don't create a new prism every time we call `pointerToPrism()`. // This will improve performance and reduce memory usage. const cache = new WeakMap, Prism>() function pointerToPrismV2(ptr: Pointer): Prism { // we'll check whether the pointer is already in the cache const cached = cache.get(ptr as any) if (cached) { return cached as any } // if not, we'll create a new prism and cache it const p = pointerToPrismV1(ptr) cache.set(ptr as any, p) return p } // Now let's test it: expect(pointerToPrismV2(ptr)).toBe(pointerToPrismV2(ptr)) // the cache works expect(pointerToPrismV2(ptr).getValue()).toBe(0) // the prism works // The second improvement would be to decouple `pointerToPrism()` from the implementation of `Atom`. // Namely, `pointerToPrism()` only calls `Atom.onChangeByPointer()` and `Atom.getByPointer()`, which // are methods that can be implemented on other objects as well. Instead, we can just define an interface // that requires these methods to be implemented. // We call this interface `PointerToPrismProvider`: // For example, Atom implements this interface: expect(isPointerToPrismProvider(atom)).toBe(true) // So our implementation can be updated to: function pointerToPrismV3(ptr: Pointer): Prism { const cached = cache.get(ptr as any) if (cached) { return cached as any } const {root} = getPointerParts(ptr) if (!isPointerToPrismProvider(root)) { throw new Error( `pointerToPrismV3() only supports pointers whose root implements PointerToPrismProvider`, ) } // one final improvement is to allow the implementation of `PointerToPrismProvider` to create // the prism, rather than us calling `prism()`, and `prism.source` directly. This will allow // the implementation to custmoize and possibly optimise how the prism sources its value. const pr = root.pointerToPrism(ptr) cache.set(ptr as any, pr) return pr } // Now let's test it: expect(pointerToPrismV3(ptr)).toBe(pointerToPrismV3(ptr)) // the cache works expect(pointerToPrismV3(ptr).getValue()).toBe(0) // the prism works // To summarize: // * we've learned how to implement a `val()` function that works with pointers and prisms. // * we've learned how to implement a `pointerToPrism()` function that converts a pointer to a prism. // * we've learned how to improve the performance of `pointerToPrism()` by caching the prisms. // * we've learned how to decouple `pointerToPrism()` from the implementation of `Atom` by using an interface. }) }) describe('5 - Tickers', () => { // A ticker is how dataverse allows you to coordinate the timing of your computations. // For example, let's say we have a prism whose value changes every 5 milliseconds. And we want to // render the value of that prism every ~16 milliseconds (60fps). A ticker allows us to do that. test('5.1 - Our prism has gone stale...', () => { // In order to see how tickers fit into the picture, we should first understand how prisms // go stale. const atom = new Atom('1') const aParsed = prism(() => parseInt(val(atom.pointer))) // To illustrate how prisms go stale, we'll create a prism that computes the factorial of the atom's value. // Since factorial is a computationally expensive operation, we'll only want to compute it when we actually // need it. function factorial(n: number): number { if (n === 0) return 1 return n * factorial(n - 1) } // we'll want to track how many times our prism actually recalculates its value, so we'll use a jest spy const recalculateSpy = jest.fn() const aFactoriel = prism(() => { recalculateSpy() return factorial(val(aParsed)) }) // To make it easy to inspect the state of a prism, we'll create a helper function: const prismState = ( p: Prism, ): 'cold' | 'hot:stale' | 'hot:fresh' => { // @ts-ignore this is a hack to access the internal state of the prism const internalState = p._state as any return internalState.hot === false ? 'cold' : internalState.handle._isFresh ? 'hot:fresh' : 'hot:stale' } // Every prism starts out as 'cold' expect(prismState(aFactoriel)).toBe('cold') expect(prismState(aParsed)).toBe('cold') { // as soon as we subscribe to its `onStale` event, it becomes 'hot:fresh' const unsubscribe = aFactoriel.onStale(jest.fn()) expect(prismState(aFactoriel)).toBe('hot:fresh') // since its value is fresh, it should have already called our spy expect(recalculateSpy).toHaveBeenCalledTimes(1) recalculateSpy.mockClear() // and if we try to get its value, it won't recalculate it expect(aFactoriel.getValue()).toBe(1) expect(recalculateSpy).toHaveBeenCalledTimes(0) // and if we change the state of our atom, atom.set('2') // our prism will go stale: expect(prismState(aFactoriel)).toBe('hot:stale') // And so will its dependency: expect(prismState(aParsed)).toBe('hot:stale') // Has the recalculate spy been called? expect(recalculateSpy).toHaveBeenCalledTimes(0) // it hasn't. It'll only recalculate when we actually need its value: expect(aFactoriel.getValue()).toBe(2) expect(recalculateSpy).toHaveBeenCalledTimes(1) unsubscribe() } // So far we have established that instead of recalculating their values, prisms simply go stale when their dependencies change. // and they'll go fresh again when we call `getValue()` on them. // tickers are a way to make sure `getValue()` is called at the rate/frequency we want. const ticker = new Ticker() const onChange = jest.fn() // notice how we're using `onChange` only on the prism that we care about, and not on its dependencies. const unsubscribe = aFactoriel.onChange(ticker, onChange) // now our prism will go stale every time our atom changes, but it won't recalculate its value until we call `tick()` atom.set('3') expect(onChange).toHaveBeenCalledTimes(0) ticker.tick() expect(onChange).toHaveBeenCalledTimes(1) expect(onChange).toHaveBeenCalledWith(6) // We'd usually create a single ticker for an entire page, and call `tick()` on it every frame. // For example, on a regular web page, we'd use `requestAnimationFrame()` to `tick()` our ticker. // On an XR session, we'd use `XRSession.requestAnimationFrame()`. function tickEveryFrame() { ticker.tick() requestAnimationFrame(tickEveryFrame) } // now we're not gonna call `tickEveryFrame()` because our tests are running on node, but you get the idea. unsubscribe() // Also note that we can have multiple tickers for the same prism: // `pr.onChange(ticker1, ...); pr.onChange(ticker2, ...);` is perfectly valid. // And it would be useful if we're using the value of the same prism in multiple places. }) // That's pretty much it for tickers. If you're curious how they work, have a look at `./Ticker.test.ts` }) describe('6 - Prism hooks', () => { // Prism hooks are inspired by [React hooks](https://reactjs.org/docs/hooks-intro.html) ) and work in a similar way. describe(`6.1 - prism.source()`, () => { // We've already come across `prism.source()` in chapter 3. `prism.source()` allow a prism to react to changes in // some external source (other than other prisms). For example, `Atom.pointerToPrism()` uses `prism.source()` to // create a prism that reacts to changes in the atom's value. // Here is another example. Let's say we want to create a prism that reacts to changes in the value of an HTML input element: test(`6.1.1 - Example: listening to changes in an input element`, () => { function prismFromInputElement(input: HTMLInputElement): Prism { function subscribe(cb: (value: string) => void) { const listener = () => { cb(input.value) } input.addEventListener('input', listener) return () => { input.removeEventListener('input', listener) } } function get() { return input.value } return prism(() => prism.source(subscribe, get)) } // And this is how we'd use it: // const el = document.querySelector('input.our-input') // const prism = prismFromInputElement(el) // our prism will start listening to changes in the input element as soon as it goes hot, // and it will stop listening when it goes cold. }) test('6.2.2 - Behavior of `prism.source()`', () => { // Let's use a few spies to see what's going on under the hood: const events: Array<'get' | 'subscribe' | 'unsubscribe'> = [] const subscribe = () => { events.push('subscribe') return () => { events.push('unsubscribe') } } const get = () => { events.push('get') } const pr = prism(() => prism.source(subscribe, get)) expect(events).toEqual([]) pr.getValue() // since our prism is cold, it won't subscribe to the source and will only call `get()` expect(events).toEqual(['get']) events.length = 0 // reset the events array // as we know, cold prisms do not cache their values, so calling `getValue()` again will call `get()` again: pr.getValue() expect(events).toEqual(['get']) events.length = 0 // reset the events array // now let's make our prism hot: const unsub = pr.onStale(() => {}) // as soon as the prism goes hot, it will subscribe to the source, and it'll also call `get()` for the first time: expect(events).toEqual(['subscribe', 'get']) events.length = 0 // reset the events array pr.getValue() expect(events).toEqual([]) // now let's make our prism cold again: unsub() // as soon as the prism goes cold, it will unsubscribe from the source: expect(events).toEqual(['unsubscribe']) }) }) test(`6.2 - prism.ref()`, () => { // Just like React's `useRef()`, `prism.ref()` allows us to create a prism that holds a reference to some value. // The only difference is that `prism.ref()` requires a key to be passed into it, whlie `useRef()` doesn't. // This means that we can call `prism.ref()` in any order, and we can call it multiple times with the same key. const spy = jest.fn() const atom = new Atom(0) const pr = prism(() => { val(atom.pointer) // just to make our prism depend on the atom. we don't care about the value of the atom. const elRef = prism.ref('my-key', undefined) spy(elRef.current) if (elRef.current === undefined) { // @ts-ignore - we're just testing the behavior here, we won't create a real dom node elRef.current = {} } }) // now, what happens if we get the value of our prism? pr.getValue() expect(spy).toHaveBeenCalledWith(undefined) spy.mockClear() // and if we get its value again? pr.getValue() expect(spy).toHaveBeenCalledWith(undefined) // the ref is still undefined spy.mockClear() // this is because `prism.ref()` only works when the prism is hot, otherwise it'll always return the initial value of the ref. // So let's make our prism hot: const unsub = pr.onStale(() => {}) expect(spy).toHaveBeenCalledWith(undefined) spy.mockClear() // now let's make the prism go stale atom.set(1) // of course the atom won't recalculate as long as we don't call `getValue()` on it: expect(spy).not.toHaveBeenCalled() // so let's call `getValue()` on it: pr.getValue() expect(spy).toHaveBeenCalledWith({}) // and that's how `prism.ref()` works! unsub() }) describe(`6.3 - prism.memo()`, () => { // `prism.memo()` works just like React's `useMemo()` hook. It's a way to cache the result of a function call. // The only difference is that `prism.memo()` requires a key to be passed into it, whlie `useMemo()` doesn't. // This means that we can call `prism.memo()` in any order, and we can call it multiple times with the same key. test(`6.3.1 - Example: using prism.memo()`, () => { const atom = new Atom(1) function factorial(n: number): number { if (n === 0) return 1 return n * factorial(n - 1) } const spy = jest.fn() const pr = prism(() => { // num will be between 0 and 9. This is so we can test what happens when the atom's value changes, but // the memoized value doesn't change. const num = val(atom.pointer) const numMod10 = num % 10 const value = prism.memo( // we need a string key to identify the hook. This allows us to call `prism.memo()` in any order, or even conditionally. 'factorial', // the function to memoize () => { spy() return factorial(numMod10) }, // the dependencies of the function. If any of the dependencies change, the function will be called again. [numMod10], ) return `number is ${num}, num % 10 is ${numMod10} and its factorial is ${value}` }) // firts let's test our prism when it's cold: expect(pr.getValue()).toBe( 'number is 1, num % 10 is 1 and its factorial is 1', ) expect(spy).toHaveBeenCalledTimes(1) // since cold prisms don't cache their values, calling `getValue()` again will call the factorial function again: expect(pr.getValue()).toBe( 'number is 1, num % 10 is 1 and its factorial is 1', ) expect(spy).toHaveBeenCalledTimes(2) spy.mockClear() // now let's make our prism hot: const unsub = pr.onStale(() => {}) pr.getValue() expect(spy).toHaveBeenCalledTimes(1) spy.mockClear() // if the memo's dependencies don't change, the memoized function won't be called again: pr.getValue() expect(spy).toHaveBeenCalledTimes(0) // now let's change the atom's value, but not the factorial value: atom.set(11) // our prism _will_ recalculate, but the memoized function won't be called again: expect(pr.getValue()).toBe( 'number is 11, num % 10 is 1 and its factorial is 1', ) expect(spy).toHaveBeenCalledTimes(0) unsub() // and that's how `prism.memo()` works! }) }) describe(`6.4 - prism.effect() and prism.state()`, () => { // These are two more hooks that are similar to React's `useEffect()` and `useState()` hooks. // `prism.effect()` is similar to React's `useEffect()` hook. It allows us to run side-effects when the prism is calculated. // Note that prisms are supposed to be "virtually" pure functions. That means they either should not have side-effects (and thus, no calls for `prism.effect()`), // or their side-effects should clean themselves up when the prism goes cold. // `prism.state()` is similar to React's `useState()` hook. It allows us to create a stateful value that is scoped to the prism. // We'll defer to React's documentation for [a more detailed explanation of how `useEffect()`](https://reactjs.org/docs/hooks-effect.html) // and how [`useState()`](https://reactjs.org/docs/hooks-state.html) work. // But here's a quick example: test(`6.4.1 - Example: using prism.effect() and prism.state()`, () => { jest.useFakeTimers() const events: Array< 'effectInstalled' | 'intervalCalled' | 'effectCleanedUp' > = [] const pr = prism(() => { const [randomValue, setRandomValue] = prism.state('randomValue', 0) // This is only allowed for prisms that are supposed to be hot before their first calculation. // Otherwise it will log a warning and no effect will run. prism.effect( 'update-random-value', () => { events.push('effectInstalled') const interval = setInterval(() => { events.push('intervalCalled') setRandomValue(Math.random()) }, 1000) return () => { events.push('effectCleanedUp') clearInterval(interval) } }, [], ) return randomValue }) // let's make our prism hot: const unsub = pr.onStale(() => {}) // which should already have called the effect: expect(events).toEqual(['effectInstalled']) pr.getValue() events.length = 0 // clear the events array // now let's fast-forward the time by 2500ms: jest.advanceTimersByTime(2500) // and we should have seen the interval called twice: expect(events).toEqual(['intervalCalled', 'intervalCalled']) expect(pr.getValue()).toEqual(expect.any(Number)) events.length = 0 // clear the events array // now let's unsubscribe from the prism: unsub() expect(events).toEqual(['effectCleanedUp']) }) test('6.4.2 - A more useful example', () => { // This prism holds the current mouse position and updates when the mouse moves const mousePositionPr = prism(() => { const [pos, setPos] = prism.state<[x: number, y: number]>( 'pos', [0, 0], ) prism.effect( 'setupListeners', () => { const handleMouseMove = (e: MouseEvent) => { setPos([e.screenX, e.screenY]) } document.addEventListener('mousemove', handleMouseMove) return () => { document.removeEventListener('mousemove', handleMouseMove) } }, [], ) return pos }) // We can't test this since our test environment doesn't have a mouse, but you get the idea :) }) }) test(`6.5 - prism.sub()`, () => { // `prism.sub()` is a shortcut for creating a prism inside another prism. // It's equivalent to calling `prism.memo(key, () => prism(fn), deps).getValue()`. // `prism.sub()` is useful when you want to divide your prism into smaller prisms, each of which // would _only_ recalculate when _certain_ dependencies change. In other words, it's an optimization tool. function factorial(num: number): number { if (num === 0) return 1 return num * factorial(num - 1) } const events: Array<'foo-calculated' | 'bar-calculated'> = [] // example: const state = new Atom({foo: 0, bar: 0}) const pr = prism(() => { const resultOfFoo = prism.sub( 'foo', () => { events.push('foo-calculated') const foo = val(state.pointer.foo) % 10 // Note how `prism.sub()` is more powerful than `prism.memo()` because it allows us to use `prism.memo()` and other hooks inside of it: return prism.memo('factorial', () => factorial(foo), [foo]) }, [], ) const resultOfBar = prism.sub( 'bar', () => { events.push('bar-calculated') const bar = val(state.pointer.bar) % 10 return prism.memo('factorial', () => factorial(bar), [bar]) }, [], ) return `result of foo is ${resultOfFoo}, result of bar is ${resultOfBar}` }) const unsub = pr.onStale(() => {}) // on the first run, both subs should be calculated: expect(events).toEqual(['foo-calculated', 'bar-calculated']) events.length = 0 // clear the events array // now if we change the value of `bar`, only `bar` should be recalculated: state.setByPointer(state.pointer.bar, 2) pr.getValue() expect(events).toEqual(['bar-calculated']) unsub() }) test(`6.6 - prism.scope()`, () => { // since prism hooks are keyed (as opposed to React hooks where they're identified by their order), // it's possible to have multiple hooks with the same key in the same prism. // To avoid this, we can use `prism.scope()` to create a "scope" for our hooks. // Example: const pr = prism(() => { prism.scope('a', () => { prism.memo('foo', () => 1, []) }) prism.scope('b', () => { prism.memo('foo', () => 1, []) }) }) }) }) // What's next? // At this point we have covered all of `@theatre/dataverse`. // If you're planning to use Dataverse with React, have a look at [`@theatre/react`](https://github.com/theatre-js/theatre/tree/main/packages/react) // which provides a React integration for Dataverse as well. }) ================================================ FILE: packages/dataverse/src/index.ts ================================================ /** * The animation-optimized FRP library powering the internals of Theatre.js. * * @packageDocumentation */ export type {PointerToPrismProvider} from './pointerToPrism' export {default as Atom} from './Atom' export {val} from './val' export {pointerToPrism} from './pointerToPrism' export {isPrism} from './prism/Interface' export type {Prism} from './prism/Interface' export {default as iterateAndCountTicks} from './prism/iterateAndCountTicks' export {default as iterateOver} from './prism/iterateOver' export {default as prism} from './prism/prism' export {default as pointer, getPointerParts, isPointer} from './pointer' export type {Pointer, PointerType, PointerMeta} from './pointer' export {default as Ticker} from './Ticker' export {default as PointerProxy} from './PointerProxy' // export {default as asyncIterateOver} from './prism/asyncIterateOver' ================================================ FILE: packages/dataverse/src/integration.test.ts ================================================ /* * @jest-environment jsdom */ import Atom from './Atom' import {val} from './val' import prism from './prism/prism' import Ticker from './Ticker' describe(`integration`, () => { describe(`identity pointers`, () => { it(`should work`, () => { const data = {foo: 'hi', bar: 0} const a = new Atom(data) const dataP = a.pointer const bar = dataP.bar expect(val(bar)).toEqual(0) const d = prism(() => { return val(bar) }) expect(d.getValue()).toEqual(0) const ticker = new Ticker() const changes: number[] = [] d.onChange(ticker, (c) => { changes.push(c) }) a.set({...data, bar: 1}) ticker.tick() expect(changes).toHaveLength(1) expect(changes[0]).toEqual(1) a.set({...data, bar: 1}) ticker.tick() expect(changes).toHaveLength(1) }) }) }) ================================================ FILE: packages/dataverse/src/pointer.test.ts ================================================ // eslint-disable-next-line import/no-extraneous-dependencies import {pointer, getPointerParts, Atom} from '@theatre/dataverse' describe(`pointer`, () => { test(`Basic useage of pointer`, async () => { const root = {foo: 'foo', bar: 0} const p = pointer({root: root, path: []}) const parts = getPointerParts(p) expect(parts.root).toBe(root) expect(parts.path).toEqual([]) const pointerToFoo = p.foo // p.foo is a pointer to the `foo` property of the root object. its only difference to p is that its path is `['foo']` expect(getPointerParts(pointerToFoo).path).toEqual(['foo']) expect(getPointerParts(pointerToFoo).root).toBe(root) // subPointers are cached expect(pointerToFoo).toBe(p.foo) // we can also manually construct the pointer to foo: const pointerToFoo2 = pointer({root: root, path: ['foo']}) expect(getPointerParts(pointerToFoo2).path).toEqual(['foo']) }) test(`Well-typed pointers`, () => { type Data = {str: string; foo?: {bar?: {baz: number}}} const root: Data = {str: 'some string'} // pointers bocome useful when we properly type them. Let's do that now: const p = pointer({ root, path: [], }) // or alternatively: `pointer(...) as Pointer` // now typescript will error if we try to access a property that doesn't exist // @ts-expect-error p.baz // but it will not error if we access a property that does exist p.foo // won't get an error when accessing foo.bar.baz p.foo.bar.baz // but will get an error when accessing foo.bar.baz.nonExistentProperty // @ts-ignore p.foo.bar.baz.nonExistentProperty // we don't need to manually type the pointer since pointers are usually provided by Atoms, and those are already typed const atom = new Atom(root) // so this will be fine by typescript: atom.pointer.foo.bar.baz // while this will error // @ts-ignore atom.pointer.foo.bar.baz.nonExistentProperty }) }) ================================================ FILE: packages/dataverse/src/pointer.ts ================================================ import type {$IntentionalAny} from './types' type PathToProp = Array export type PointerMeta = { root: {} path: (string | number)[] } /** We are using an empty object as a WeakMap key for storing pointer meta data */ type WeakPointerKey = {} export type UnindexableTypesForPointer = | number | string | boolean | null | void | undefined | Function // eslint-disable-line @typescript-eslint/ban-types export type UnindexablePointer = { [K in $IntentionalAny]: Pointer } const pointerMetaWeakMap = new WeakMap() const cachedSubPathPointersWeakMap = new WeakMap< WeakPointerKey, Map> >() /** * A wrapper type for the type a `Pointer` points to. */ export type PointerType = { /** * Only accessible via the type system. * This is a helper for getting the underlying pointer type * via the type space. */ $$__pointer_type: O } /** * The type of {@link Atom} pointers. See {@link pointer|pointer()} for an * explanation of pointers. * * @see Atom * * @remarks * The Pointer type is quite tricky because it doesn't play well with `any` and other inexact types. * Here is an example that one would expect to work, but currently doesn't: * ```ts * declare function expectAnyPointer(pointer: Pointer): void * * expectAnyPointer(null as Pointer<{}>) // this shows as a type error because Pointer<{}> is not assignable to Pointer, even though it should * ``` * * The current solution is to just avoid using `any` with pointer-related code (or type-test it well). * But if you enjoy solving typescript puzzles, consider fixing this :) * Potentially, [TypeScript variance annotations in 4.7+](https://devblogs.microsoft.com/typescript/announcing-typescript-4-7-beta/#optional-variance-annotations-for-type-parameters) * might be able to help us. */ export type Pointer = PointerType & // `Exclude` will remove `undefined` from the first type // `undefined extends O ? undefined : never` will give us `undefined` if `O` is `... | undefined` PointerInner, undefined extends O ? undefined : never> // By separating the `O` (non-undefined) from the `undefined` or `never`, we // can properly use `O extends ...` to determine the kind of potential value // without actually discarding optionality information. type PointerInner = O extends UnindexableTypesForPointer ? UnindexablePointer : unknown extends O ? UnindexablePointer : O extends (infer T)[] ? Pointer[] : O extends {} ? { [K in keyof O]-?: Pointer } : UnindexablePointer const pointerMetaSymbol = Symbol('pointerMeta') const proxyHandler = { get( pointerKey: WeakPointerKey, prop: string | typeof pointerMetaSymbol, ): $IntentionalAny { if (prop === pointerMetaSymbol) return pointerMetaWeakMap.get(pointerKey)! let subPathPointers = cachedSubPathPointersWeakMap.get(pointerKey) if (!subPathPointers) { subPathPointers = new Map() cachedSubPathPointersWeakMap.set(pointerKey, subPathPointers) } const existing = subPathPointers.get(prop) if (existing !== undefined) return existing const meta = pointerMetaWeakMap.get(pointerKey)! const subPointer = pointer({root: meta.root, path: [...meta.path, prop]}) subPathPointers.set(prop, subPointer) return subPointer }, } /** * Returns the metadata associated with the pointer. Usually the root object and * the path. * * @param p - The pointer. */ export const getPointerMeta = <_>(p: PointerType<_>): PointerMeta => { // @ts-ignore @todo const meta: PointerMeta = p[ pointerMetaSymbol as unknown as $IntentionalAny ] as $IntentionalAny return meta } /** * Returns the root object and the path of the pointer. * * @example * ```ts * const {root, path} = getPointerParts(pointer) * ``` * * @param p - The pointer. * * @returns An object with two properties: `root`-the root object or the pointer, and `path`-the path of the pointer. `path` is an array of the property-chain. */ export const getPointerParts = <_>( p: Pointer<_>, ): {root: {}; path: PathToProp} => { const {root, path} = getPointerMeta(p) return {root, path} } /** * Creates a pointer to a (nested) property of an {@link Atom}. * * @remarks * Pointers are used to make prisms of properties or nested properties of * {@link Atom|Atoms}. * * Pointers also allow easy construction of new pointers pointing to nested members * of the root object, by simply using property chaining. E.g. `somePointer.a.b` will * create a new pointer that has `'a'` and `'b'` added to the path of `somePointer`. * * @example * ```ts * // Here, sum is a prism that updates whenever the a or b prop of someAtom does. * const sum = prism(() => { * return val(pointer({root: someAtom, path: ['a']})) + val(pointer({root: someAtom, path: ['b']})); * }); * * // Note, atoms have a convenience Atom.pointer property that points to the root, * // which you would normally use in this situation. * const sum = prism(() => { * return val(someAtom.pointer.a) + val(someAtom.pointer.b); * }); * ``` * * @param args - The pointer parameters. * * @typeParam O - The type of the value being pointed to. */ function pointer(args: {root: {}; path?: Array}) { const meta: PointerMeta = { root: args.root as $IntentionalAny, path: args.path ?? [], } const pointerKey: WeakPointerKey = {} pointerMetaWeakMap.set(pointerKey, meta) return new Proxy(pointerKey, proxyHandler) as Pointer } export default pointer /** * Returns whether `p` is a pointer. */ export const isPointer = (p: $IntentionalAny): p is Pointer => { return p && !!getPointerMeta(p) } ================================================ FILE: packages/dataverse/src/pointer.typeTest.ts ================================================ import type {Pointer, UnindexablePointer} from './pointer' import type {$IntentionalAny} from './types' const nominal = Symbol() type Nominal = string & {[nominal]: Name} type Key = Nominal<'key'> type Id = Nominal<'id'> type IdObject = { inner: true } type KeyObject = { inner: { byIds: Partial> } } type NestedNominalThing = { optional?: true byKeys: Partial> } interface TypeError {} type Debug = T type IsTrue = T type IsFalse = F type IsExtends = F type IsExactly = F extends R ? true : TypeError<[F, 'does not extend', R]> function test() { const p = todo>() const key1 = todo() const id1 = todo() type A = UnindexablePointer[typeof key1] type BaseChecks = [ IsExtends, IsExtends, IsExtends, IsTrue>>, IsTrue< IsExactly['...']['...'], Pointer> >, IsTrue< IsExactly< Pointer>[Key], Pointer > >, IsTrue[Key], Pointer>>, // Debug>[Key]>, IsTrue>[string], Pointer>>, IsTrue< IsExactly< Pointer>[string], Pointer > >, IsTrue< IsExactly< Pointer>[Key], Pointer > >, // Debug['...']['...']>, // IsFalse ? true : false>, // what extends what IsTrue<1 & undefined extends undefined ? true : false>, IsFalse<1 | undefined extends undefined ? true : false>, ] t>() // .isExactly(p.optional).ok t>() // .isExactly(p.byKeys[key1]).ok t>() // .isExactly(p.byKeys[key1].inner).ok t>() // .isExactly(p.byKeys[key1].inner.byIds[id1]).ok p.byKeys[key1] } function todo(hmm?: TemplateStringsArray): T { return null as $IntentionalAny } function t(): { isExactly( hmm: R, ): T extends R ? // any extends R // ? TypeError<[R, 'is any']> // : {ok: true} : TypeError<[T, 'does not extend', R]> } { return {isExactly: (hmm) => hmm as $IntentionalAny} } ================================================ FILE: packages/dataverse/src/pointerToPrism.ts ================================================ import type {Prism} from './prism/Interface' import type {Pointer, PointerType} from './pointer' import {getPointerMeta} from './pointer' import type {$IntentionalAny} from './types' const identifyPrismWeakMap = new WeakMap<{}, Prism>() /** * Interface for objects that can provide a prism at a certain path. */ export interface PointerToPrismProvider { /** * @internal * Future: We could consider using a `Symbol.for("dataverse/PointerToPrismProvider")` as a key here, similar to * how {@link Iterable} works for `of`. */ readonly $$isPointerToPrismProvider: true /** * Returns a prism of the value at the provided pointer. */ pointerToPrism

(pointer: Pointer

): Prism

} export function isPointerToPrismProvider( val: unknown, ): val is PointerToPrismProvider { return ( typeof val === 'object' && val !== null && (val as $IntentionalAny)['$$isPointerToPrismProvider'] === true ) } /** * Returns a prism of the value at the provided pointer. Prisms are * cached per pointer. * * @param pointer - The pointer to return the prism at. */ export const pointerToPrism =

>( pointer: P, ): Prism

? T : void> => { const meta = getPointerMeta(pointer) let prismInstance = identifyPrismWeakMap.get(meta) if (!prismInstance) { const root = meta.root if (!isPointerToPrismProvider(root)) { throw new Error( `Cannot run pointerToPrism() on a pointer whose root is not an PointerToPrismProvider`, ) } prismInstance = root.pointerToPrism(pointer as $IntentionalAny) identifyPrismWeakMap.set(meta, prismInstance) } return prismInstance as $IntentionalAny } ================================================ FILE: packages/dataverse/src/prism/Interface.ts ================================================ import type Ticker from '../Ticker' import type {$IntentionalAny, VoidFn} from '../types' type IDependent = (msgComingFrom: Prism<$IntentionalAny>) => void /** * Common interface for prisms. */ export interface Prism { /** * Whether the object is a prism. */ isPrism: true /** * Whether the prism is hot. */ isHot: boolean /** * Calls `listener` with a fresh value every time the prism _has_ a new value, throttled by Ticker. */ onChange( ticker: Ticker, listener: (v: V) => void, immediate?: boolean, ): VoidFn onStale(cb: () => void): VoidFn /** * Keep the prism hot, even if there are no tappers (subscribers). */ keepHot(): VoidFn /** * Add a prism as a dependent of this prism. * * @param d - The prism to be made a dependent of this prism. * * @see _removeDependent * * @internal */ _addDependent(d: IDependent): void /** * Remove a prism as a dependent of this prism. * * @param d - The prism to be removed from as a dependent of this prism. * * @see _addDependent * @internal */ _removeDependent(d: IDependent): void /** * Gets the current value of the prism. If the value is stale, it causes the prism to freshen. */ getValue(): V } /** * Returns whether `d` is a prism. */ export function isPrism(d: any): d is Prism { return !!(d && d.isPrism && d.isPrism === true) } ================================================ FILE: packages/dataverse/src/prism/asyncIterateOver.ts ================================================ // import {pointerToPrism} from '../pointerToPrism' // import type {Pointer} from '../pointer' // import {isPointer} from '../pointer' // import type {Prism} from './Interface' // import {isPrism} from './Interface' // export default async function* asyncIterateOver( // pointerOrPrism: Prism | Pointer, // ): AsyncGenerator { // let d: Prism // if (isPointer(pointerOrPrism)) { // d = pointerToPrism(pointerOrPrism) as Prism // } else if (isPrism(pointerOrPrism)) { // d = pointerOrPrism // } else { // throw new Error(`Only pointers and prisms are supported`) // } // const unsub = d.keepHot() // let lastValue = d.getValue() // yield lastValue // try { // while (true) { // const newValue = d.getValue() // if (newValue !== lastValue) { // lastValue = newValue // yield lastValue // } else { // const deferred = defer() // const stop = d.onStale(() => { // const newValue = d.getValue() // if (newValue !== lastValue) { // lastValue = newValue // stop() // deferred.resolve(lastValue) // } // }) // yield deferred.promise // } // } // } finally { // unsub() // } // } // interface Deferred { // resolve: (d: PromiseType) => void // reject: (d: unknown) => void // promise: Promise // status: 'pending' | 'resolved' | 'rejected' // } // /** // * A simple imperative API for resolving/rejecting a promise. // * // * Example: // * ```ts // * function doSomethingAsync() { // * const deferred = defer() // * // * setTimeout(() => { // * if (Math.random() > 0.5) { // * deferred.resolve('success') // * } else { // * deferred.reject('Something went wrong') // * } // * }, 1000) // * // * // we're just returning the promise, so that the caller cannot resolve/reject it // * return deferred.promise // * } // * // * ``` // */ // function defer(): Deferred { // let resolve: (d: PromiseType) => void // let reject: (d: unknown) => void // const promise = new Promise((rs, rj) => { // resolve = (v) => { // rs(v) // deferred.status = 'resolved' // } // reject = (v) => { // rj(v) // deferred.status = 'rejected' // } // }) // const deferred: Deferred = { // resolve: resolve!, // reject: reject!, // promise, // status: 'pending', // } // return deferred // } ================================================ FILE: packages/dataverse/src/prism/discoveryMechanism.ts ================================================ import type {$IntentionalAny} from '../types' import Stack from '../utils/Stack' import type {Prism} from './Interface' function createMechanism() { const noop = () => {} const stack = new Stack() const noopCollector: Collector = noop type Collector = (d: Prism<$IntentionalAny>) => void const pushCollector = (collector: Collector): void => { stack.push(collector) } const popCollector = (collector: Collector): void => { const existing = stack.peek() if (existing !== collector) { throw new Error(`Popped collector is not on top of the stack`) } stack.pop() } const startIgnoringDependencies = () => { stack.push(noopCollector) } const stopIgnoringDependencies = () => { if (stack.peek() !== noopCollector) { if (process.env.NODE_ENV === 'development') { console.warn('This should never happen') } } else { stack.pop() } } const reportResolutionStart = (d: Prism<$IntentionalAny>) => { const possibleCollector = stack.peek() if (possibleCollector) { possibleCollector(d) } stack.push(noopCollector) } const reportResolutionEnd = (_d: Prism<$IntentionalAny>) => { stack.pop() } return { type: 'Dataverse_discoveryMechanism' as 'Dataverse_discoveryMechanism', startIgnoringDependencies, stopIgnoringDependencies, reportResolutionStart, reportResolutionEnd, pushCollector, popCollector, } } function getSharedMechanism(): ReturnType { const varName = '__dataverse_discoveryMechanism_sharedStack' const root = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {} if (root) { const existingMechanism: ReturnType | undefined = // @ts-ignore ignore root[varName] if ( existingMechanism && typeof existingMechanism === 'object' && existingMechanism.type === 'Dataverse_discoveryMechanism' ) { return existingMechanism } else { const mechanism = createMechanism() // @ts-ignore ignore root[varName] = mechanism return mechanism } } else { return createMechanism() } } export const { startIgnoringDependencies, stopIgnoringDependencies, reportResolutionEnd, reportResolutionStart, pushCollector, popCollector, } = getSharedMechanism() ================================================ FILE: packages/dataverse/src/prism/iterateAndCountTicks.ts ================================================ import {pointerToPrism} from '../pointerToPrism' import type {Pointer} from '../pointer' import {isPointer} from '../pointer' import type {Prism} from './Interface' import {isPrism} from './Interface' export default function* iterateAndCountTicks( pointerOrPrism: Prism | Pointer, ): Generator<{value: V; ticks: number}, void, void> { let d if (isPointer(pointerOrPrism)) { d = pointerToPrism(pointerOrPrism) as Prism } else if (isPrism(pointerOrPrism)) { d = pointerOrPrism } else { throw new Error(`Only pointers and prisms are supported`) } let ticksCountedSinceLastYield = 0 const untap = d.onStale(() => { ticksCountedSinceLastYield++ }) try { while (true) { const ticks = ticksCountedSinceLastYield ticksCountedSinceLastYield = 0 yield {value: d.getValue(), ticks} } } finally { untap() } } ================================================ FILE: packages/dataverse/src/prism/iterateOver.test.ts ================================================ /* * @jest-environment jsdom */ import Atom from '../Atom' import iterateOver from './iterateOver' describe(`iterateOver()`, () => { test('it should work', () => { const a = new Atom(0) let iter = iterateOver(a.pointer) expect(iter.next().value).toEqual(0) a.set(1) a.set(2) expect(iter.next()).toMatchObject({value: 2, done: false}) iter.return() iter = iterateOver(a.pointer) expect(iter.next().value).toEqual(2) a.set(3) expect(iter.next()).toMatchObject({done: false, value: 3}) iter.return() }) }) ================================================ FILE: packages/dataverse/src/prism/iterateOver.ts ================================================ import {pointerToPrism} from '../pointerToPrism' import type {Pointer} from '../pointer' import {isPointer} from '../pointer' import Ticker from '../Ticker' import type {Prism} from './Interface' import {isPrism} from './Interface' export default function* iterateOver( pointerOrPrism: Prism | Pointer, ): Generator { let d if (isPointer(pointerOrPrism)) { d = pointerToPrism(pointerOrPrism) as Prism } else if (isPrism(pointerOrPrism)) { d = pointerOrPrism } else { throw new Error(`Only pointers and prisms are supported`) } const ticker = new Ticker() const untap = d.onChange(ticker, (v) => {}) try { while (true) { ticker.tick() yield d.getValue() } } finally { untap() } } ================================================ FILE: packages/dataverse/src/prism/prism.test.ts ================================================ /* * @jest-environment jsdom */ import Atom from '../Atom' import {val} from '../val' import Ticker from '../Ticker' import type {$FixMe, $IntentionalAny} from '../types' import iterateAndCountTicks from './iterateAndCountTicks' import prism from './prism' describe('prism', () => { let ticker: Ticker beforeEach(() => { ticker = new Ticker() }) it('should work', () => { const o = new Atom({foo: 'foo'}) const d = prism(() => { return val(o.pointer.foo) + 'boo' }) expect(d.getValue()).toEqual('fooboo') const changes: Array<$FixMe> = [] d.onChange(ticker, (c) => { changes.push(c) }) o.reduce(({foo}) => ({foo: 'foo2'})) ticker.tick() expect(changes).toMatchObject(['foo2boo']) }) it('should only collect immediate dependencies', () => { const aD = prism(() => 1) const bD = prism(() => aD.getValue() * 2) const cD = prism(() => { return bD.getValue() }) expect(cD.getValue()).toEqual(2) const unsubscribe = cD.keepHot() expect((cD as $IntentionalAny)._state.handle._dependencies.size).toEqual(1) unsubscribe() }) describe('prism.ref()', () => { it('should work', () => { const theAtom: Atom = new Atom(2) const isEvenD = prism((): {isEven: boolean} => { const ref = prism.ref<{isEven: boolean} | undefined>('cache', undefined) const currentN = val(theAtom.pointer) const isEven = currentN % 2 === 0 if (ref.current && ref.current.isEven === isEven) { return ref.current } else { ref.current = {isEven} return ref.current } }) const iterator = iterateAndCountTicks(isEvenD) theAtom.reduce(() => 3) expect(iterator.next().value).toMatchObject({ value: {isEven: false}, ticks: 0, }) theAtom.reduce(() => 5) theAtom.reduce(() => 7) expect(iterator.next().value).toMatchObject({ value: {isEven: false}, ticks: 1, }) theAtom.reduce(() => 2) theAtom.reduce(() => 4) expect(iterator.next().value).toMatchObject({ value: {isEven: true}, ticks: 1, }) expect(iterator.next().value).toMatchObject({ value: {isEven: true}, ticks: 0, }) }) }) describe('prism.effect()', () => { it('should work', async () => { let iteration = 0 const sequence: unknown[] = [] let deps: unknown[] = [] const a = new Atom('a') const prsm = prism(() => { const n = val(a.pointer) const iterationAtTimeOfCall = iteration sequence.push({prismCall: iterationAtTimeOfCall}) prism.effect( 'f', () => { sequence.push({effectCall: iterationAtTimeOfCall}) return () => { sequence.push({cleanupCall: iterationAtTimeOfCall}) } }, [...deps], ) return n }) const untap = prsm.onChange(ticker, (change) => { sequence.push({change}) }) expect(sequence).toMatchObject([{prismCall: 0}, {effectCall: 0}]) sequence.length = 0 iteration++ a.set('b') ticker.tick() expect(sequence).toMatchObject([{prismCall: 1}, {change: 'b'}]) sequence.length = 0 deps = [1] iteration++ a.set('c') ticker.tick() expect(sequence).toMatchObject([ {prismCall: 2}, {cleanupCall: 0}, {effectCall: 2}, {change: 'c'}, ]) sequence.length = 0 untap() // takes a tick before untap takes effect await new Promise((resolve) => setTimeout(resolve, 1)) expect(sequence).toMatchObject([{cleanupCall: 2}]) }) }) describe('prism.memo()', () => { it('should work', async () => { let iteration = 0 const sequence: unknown[] = [] let deps: unknown[] = [] const a = new Atom('a') const prsm = prism(() => { const n = val(a.pointer) const iterationAtTimeOfCall = iteration sequence.push({prismCall: iterationAtTimeOfCall}) const resultOfMemo = prism.memo( 'memo', () => { sequence.push({memoCall: iterationAtTimeOfCall}) return iterationAtTimeOfCall }, [...deps], ) sequence.push({resultOfMemo}) return n }) const untap = prsm.onChange(ticker, (change) => { sequence.push({change}) }) expect(sequence).toMatchObject([ {prismCall: 0}, {memoCall: 0}, {resultOfMemo: 0}, ]) sequence.length = 0 iteration++ a.set('b') ticker.tick() expect(sequence).toMatchObject([ {prismCall: 1}, {resultOfMemo: 0}, {change: 'b'}, ]) sequence.length = 0 deps = [1] iteration++ a.set('c') ticker.tick() expect(sequence).toMatchObject([ {prismCall: 2}, {memoCall: 2}, {resultOfMemo: 2}, {change: 'c'}, ]) sequence.length = 0 untap() }) }) describe(`prism.scope()`, () => { it('should prevent name conflicts', () => { const d = prism(() => { const thisNameWillBeUsedForBothMemos = 'blah' const a = prism.scope('a', () => { return prism.memo(thisNameWillBeUsedForBothMemos, () => 'a', []) }) const b = prism.scope('b', () => { return prism.memo(thisNameWillBeUsedForBothMemos, () => 'b', []) }) return {a, b} }) expect(d.getValue()).toMatchObject({a: 'a', b: 'b'}) }) }) }) ================================================ FILE: packages/dataverse/src/prism/prism.ts ================================================ import type Ticker from '../Ticker' import type {$IntentionalAny, VoidFn} from '../types' import Stack from '../utils/Stack' import type {Prism} from './Interface' import {isPrism} from './Interface' import { startIgnoringDependencies, stopIgnoringDependencies, pushCollector, popCollector, reportResolutionStart, reportResolutionEnd, } from './discoveryMechanism' type IDependent = (msgComingFrom: Prism<$IntentionalAny>) => void const voidFn = () => {} class HotHandle { private _didMarkDependentsAsStale: boolean = false private _isFresh: boolean = false protected _cacheOfDendencyValues: Map, unknown> = new Map() /** * @internal */ protected _dependents: Set = new Set() /** * @internal */ protected _dependencies: Set> = new Set() protected _possiblyStaleDeps = new Set>() private _scope: HotScope = new HotScope( this as $IntentionalAny as HotHandle, ) /** * @internal */ protected _lastValue: undefined | V = undefined /** * If true, the prism is stale even though its dependencies aren't * marked as such. This is used by `prism.source()` and `prism.state()` * to mark the prism as stale. */ private _forciblySetToStale: boolean = false constructor( private readonly _fn: () => V, private readonly _prismInstance: PrismInstance, ) { for (const d of this._dependencies) { d._addDependent(this._reactToDependencyGoingStale) } startIgnoringDependencies() this.getValue() stopIgnoringDependencies() } get hasDependents(): boolean { return this._dependents.size > 0 } removeDependent(d: IDependent) { this._dependents.delete(d) } addDependent(d: IDependent) { this._dependents.add(d) } destroy() { for (const d of this._dependencies) { d._removeDependent(this._reactToDependencyGoingStale) } cleanupScopeStack(this._scope) } getValue(): V { if (!this._isFresh) { const newValue = this._recalculate() this._lastValue = newValue this._isFresh = true this._didMarkDependentsAsStale = false this._forciblySetToStale = false } return this._lastValue! } _recalculate() { let value: V if (!this._forciblySetToStale) { if (this._possiblyStaleDeps.size > 0) { let anActuallyStaleDepWasFound = false startIgnoringDependencies() for (const dep of this._possiblyStaleDeps) { if (this._cacheOfDendencyValues.get(dep) !== dep.getValue()) { anActuallyStaleDepWasFound = true break } } stopIgnoringDependencies() this._possiblyStaleDeps.clear() if (!anActuallyStaleDepWasFound) { return this._lastValue! } } } const newDeps: Set> = new Set() this._cacheOfDendencyValues.clear() const collector = (observedDep: Prism): void => { newDeps.add(observedDep) this._addDependency(observedDep) } pushCollector(collector) hookScopeStack.push(this._scope) try { value = this._fn() } catch (error) { console.error(error) } finally { const topOfTheStack = hookScopeStack.pop() if (topOfTheStack !== this._scope) { console.warn( // @todo guide the user to report the bug in an issue `The Prism hook stack has slipped. This is a bug.`, ) } } popCollector(collector) for (const dep of this._dependencies) { if (!newDeps.has(dep)) { this._removeDependency(dep) } } this._dependencies = newDeps startIgnoringDependencies() for (const dep of newDeps) { this._cacheOfDendencyValues.set(dep, dep.getValue()) } stopIgnoringDependencies() return value! } forceStale() { this._forciblySetToStale = true this._markAsStale() } protected _reactToDependencyGoingStale = (which: Prism<$IntentionalAny>) => { this._possiblyStaleDeps.add(which) this._markAsStale() } private _markAsStale() { if (this._didMarkDependentsAsStale) return this._didMarkDependentsAsStale = true this._isFresh = false for (const dependent of this._dependents) { dependent(this._prismInstance) } } /** * @internal */ protected _addDependency(d: Prism<$IntentionalAny>) { if (this._dependencies.has(d)) return this._dependencies.add(d) d._addDependent(this._reactToDependencyGoingStale) } /** * @internal */ protected _removeDependency(d: Prism<$IntentionalAny>) { if (!this._dependencies.has(d)) return this._dependencies.delete(d) d._removeDependent(this._reactToDependencyGoingStale) } } const emptyObject = {} class PrismInstance implements Prism { /** * Whether the object is a prism. */ readonly isPrism: true = true private _state: | {hot: false; handle: undefined} | {hot: true; handle: HotHandle} = { hot: false, handle: undefined, } constructor(private readonly _fn: () => V) {} /** * Whether the prism is hot. */ get isHot(): boolean { return this._state.hot } onChange( ticker: Ticker, listener: (v: V) => void, immediate: boolean = false, ): VoidFn { // the prism will call this function every time it goes from fresh to stale const dependent = () => { // schedule the listener to be called on the next tick, unless // we're already on a tick, in which case it'll be called on the current tick. ticker.onThisOrNextTick(refresh) } // let's cache the last value so we don't call the listener if the value hasn't changed let lastValue: V | typeof emptyObject = // use an empty object as the initial value so that the listener is called on the first tick. // if we were to use, say, undefined, and this.getValue() also returned undefined, the listener // would never be called. emptyObject // this function will be _scheduled_ to be called on the currently running, or next tick, // after the prism has gone from fresh to stale. const refresh = () => { const newValue = this.getValue() // if the value hasn't changed, don't call the listener if (newValue === lastValue) return // the value has changed - cache it lastValue = newValue // and let the listener know listener(newValue) } // add the dependent to the prism's list of dependents (which will make it go hot) this._addDependent(dependent) // if the caller wants the listener to be called immediately, call it now if (immediate) { lastValue = this.getValue() listener(lastValue as $IntentionalAny as V) } // the unsubscribe function const unsubscribe = () => { // remove the dependent from the prism's list of dependents (and if it was the last dependent, the prism will go cold) this._removeDependent(dependent) // in case we're scheduled for a tick, cancel that ticker.offThisOrNextTick(refresh) ticker.offNextTick(refresh) } return unsubscribe } /** * Calls `callback` every time the prism's state goes from `fresh-\>stale.` Returns an `unsubscribe()` function. */ onStale(callback: () => void): VoidFn { const untap = () => { this._removeDependent(onStaleCallback) } const onStaleCallback = () => callback() this._addDependent(onStaleCallback) return untap } /** * Keep the prism hot, even if there are no tappers (subscribers). */ keepHot() { return this.onStale(() => {}) } /** * Add a prism as a dependent of this prism. * * @param d - The prism to be made a dependent of this prism. * * @see _removeDependent */ _addDependent(d: IDependent) { if (!this._state.hot) { this._goHot() } this._state.handle!.addDependent(d) } private _goHot() { const hotHandle = new HotHandle(this._fn, this) this._state = { hot: true, handle: hotHandle, } } /** * Remove a prism as a dependent of this prism. * * @param d - The prism to be removed from as a dependent of this prism. * * @see _addDependent */ _removeDependent(d: IDependent) { const state = this._state if (!state.hot) { return } const handle = state.handle handle.removeDependent(d) if (!handle.hasDependents) { this._state = {hot: false, handle: undefined} handle.destroy() } } /** * Gets the current value of the prism. If the value is stale, it causes the prism to freshen. */ getValue(): V { /** * TODO We should prevent (or warn about) a common mistake users make, which is reading the value of * a prism in the body of a react component (e.g. `der.getValue()` (often via `val()`) instead of `useVal()` * or `uesPrism()`). * * Although that's the most common example of this mistake, you can also find it outside of react components. * Basically the user runs `der.getValue()` assuming the read is detected by a wrapping prism when it's not. * * Sometiems the prism isn't even hot when the user assumes it is. * * We can fix this type of mistake by: * 1. Warning the user when they call `getValue()` on a cold prism. * 2. Warning the user about calling `getValue()` on a hot-but-stale prism * if `getValue()` isn't called by a known mechanism like a `PrismEmitter`. * * Design constraints: * - This fix should not have a perf-penalty in production. Perhaps use a global flag + `process.env.NODE_ENV !== 'production'` * to enable it. * - In the case of `onStale()`, we don't control when the user calls * `getValue()` (as opposed to `onChange()` which calls `getValue()` directly). * Perhaps we can disable the check in that case. * - Probably the best place to add this check is right here in this method plus some changes to `reportResulutionStart()`, * which would have to be changed to let the caller know if there is an actual collector (a prism) * present in its stack. */ reportResolutionStart(this) const state = this._state let val: V if (state.hot) { val = state.handle.getValue() } else { val = calculateColdPrism(this._fn) } reportResolutionEnd(this) return val } } interface PrismScope { effect(key: string, cb: () => () => void, deps?: unknown[]): void memo( key: string, fn: () => T, deps: undefined | $IntentionalAny[] | ReadonlyArray<$IntentionalAny>, ): T state(key: string, initialValue: T): [T, (val: T) => void] ref(key: string, initialValue: T): IRef sub(key: string): PrismScope source(subscribe: (fn: (val: V) => void) => VoidFn, getValue: () => V): V } class HotScope implements PrismScope { constructor(private readonly _hotHandle: HotHandle) {} protected readonly _refs: Map> = new Map() ref(key: string, initialValue: T): IRef { let ref = this._refs.get(key) if (ref !== undefined) { return ref as $IntentionalAny as IRef } else { const ref = { current: initialValue, } this._refs.set(key, ref) return ref } } isPrismScope = true // NOTE probably not a great idea to eager-allocate all of these objects/maps for every scope, // especially because most wouldn't get used in the majority of cases. However, back when these // were stored on weakmaps, they were uncomfortable to inspect in the debugger. readonly subs: Record = {} readonly effects: Map = new Map() effect(key: string, cb: () => () => void, deps?: unknown[]): void { let effect = this.effects.get(key) if (effect === undefined) { effect = { cleanup: voidFn, deps: undefined, } this.effects.set(key, effect) } if (depsHaveChanged(effect.deps, deps)) { effect.cleanup() startIgnoringDependencies() effect.cleanup = safelyRun(cb, voidFn).value stopIgnoringDependencies() effect.deps = deps } /** * TODO: we should cleanup dangling effects too. * Example: * ```ts * let i = 0 * prism(() => { * if (i === 0) prism.effect("this effect will only run once", () => {}, []) * i++ * }) * ``` */ } readonly memos: Map = new Map() memo( key: string, fn: () => T, deps: undefined | $IntentionalAny[] | ReadonlyArray<$IntentionalAny>, ): T { let memo = this.memos.get(key) if (memo === undefined) { memo = { cachedValue: null, // undefined will always indicate "deps have changed", so we set its initial value as such deps: undefined, } this.memos.set(key, memo) } if (depsHaveChanged(memo.deps, deps)) { startIgnoringDependencies() memo.cachedValue = safelyRun(fn, undefined).value stopIgnoringDependencies() memo.deps = deps } return memo.cachedValue as $IntentionalAny as T } state(key: string, initialValue: T): [T, (val: T) => void] { const {value, setValue} = this.memo( 'state/' + key, () => { const value = {current: initialValue} const setValue = (newValue: T) => { value.current = newValue this._hotHandle.forceStale() } return {value, setValue} }, [], ) return [value.current, setValue] } sub(key: string): HotScope { if (!this.subs[key]) { this.subs[key] = new HotScope(this._hotHandle) } return this.subs[key] } cleanupEffects() { for (const effect of this.effects.values()) { safelyRun(effect.cleanup, undefined) } this.effects.clear() } source(subscribe: (fn: (val: V) => void) => VoidFn, getValue: () => V): V { const sourceKey = '$$source/blah' this.effect( sourceKey, () => { const unsub = subscribe(() => { this._hotHandle.forceStale() }) return unsub }, [subscribe], ) return getValue() } } function cleanupScopeStack(scope: HotScope) { for (const sub of Object.values(scope.subs)) { cleanupScopeStack(sub) } scope.cleanupEffects() } function safelyRun( fn: () => T, returnValueInCaseOfError: U, ): {ok: true; value: T} | {ok: false; value: U} { try { return {value: fn(), ok: true} } catch (error) { // Naming this function can allow the error reporter additional context to the user on where this error came from setTimeout(function PrismReportThrow() { // ensure that the error gets reported, but does not crash the current execution scope throw error }) return {value: returnValueInCaseOfError, ok: false} } } const hookScopeStack = new Stack() type IRef = { current: T } type IEffect = { deps: undefined | unknown[] cleanup: VoidFn } type IMemo = { deps: undefined | unknown[] | ReadonlyArray cachedValue: unknown } /** * Just like React's `useRef()`, `prism.ref()` allows us to create a prism that holds a reference to some value. * The only difference is that `prism.ref()` requires a key to be passed into it, whlie `useRef()` doesn't. * This means that we can call `prism.ref()` in any order, and we can call it multiple times with the same key. * @param key - The key for the ref. Should be unique inside of the prism. * @param initialValue - The initial value for the ref. * @returns `{current: V}` - The ref object. * * Note that the ref object will always return its initial value if the prism is cold. It'll only record * its current value if the prism is hot (and will forget again if the prism goes cold again). * * @example * ```ts * const pr = prism(() => { * const ref1 = prism.ref("ref1", 0) * console.log(ref1.current) // will print 0, and if the prism is hot, it'll print the current value * ref1.current++ // changing the current value of the ref * }) * ``` */ function ref(key: string, initialValue: T): IRef { const scope = hookScopeStack.peek() if (!scope) { throw new Error(`prism.ref() is called outside of a prism() call.`) } return scope.ref(key, initialValue) } /** * An effect hook, similar to React's `useEffect()`, but is not sensitive to call order by using `key`. * * @param key - the key for the effect. Should be uniqe inside of the prism. * @param cb - the callback function. Requires returning a cleanup function. * @param deps - the dependency array */ function effect(key: string, cb: () => () => void, deps?: unknown[]): void { const scope = hookScopeStack.peek() if (!scope) { throw new Error(`prism.effect() is called outside of a prism() call.`) } return scope.effect(key, cb, deps) } function depsHaveChanged( oldDeps: undefined | unknown[] | ReadonlyArray, newDeps: undefined | unknown[] | ReadonlyArray, ): boolean { if (oldDeps === undefined || newDeps === undefined) { return true } const len = oldDeps.length if (len !== newDeps.length) return true for (let i = 0; i < len; i++) { if (oldDeps[i] !== newDeps[i]) return true } return false } /** * `prism.memo()` works just like React's `useMemo()` hook. It's a way to cache the result of a function call. * The only difference is that `prism.memo()` requires a key to be passed into it, whlie `useMemo()` doesn't. * This means that we can call `prism.memo()` in any order, and we can call it multiple times with the same key. * * @param key - The key for the memo. Should be unique inside of the prism * @param fn - The function to memoize * @param deps - The dependency array. Provide `[]` if you want to the value to be memoized only once and never re-calculated. * @returns The result of the function call * * @example * ```ts * const pr = prism(() => { * const memoizedReturnValueOfExpensiveFn = prism.memo("memo1", expensiveFn, []) * }) * ``` */ function memo( key: string, fn: () => T, deps: undefined | $IntentionalAny[] | ReadonlyArray<$IntentionalAny>, ): T { const scope = hookScopeStack.peek() if (!scope) { throw new Error(`prism.memo() is called outside of a prism() call.`) } return scope.memo(key, fn, deps) } /** * A state hook, similar to react's `useState()`. * * @param key - the key for the state * @param initialValue - the initial value * @returns [currentState, setState] * * @example * ```ts * import {prism} from 'dataverse' * * // This prism holds the current mouse position and updates when the mouse moves * const mousePositionD = prism(() => { * const [pos, setPos] = prism.state<[x: number, y: number]>('pos', [0, 0]) * * prism.effect( * 'setupListeners', * () => { * const handleMouseMove = (e: MouseEvent) => { * setPos([e.screenX, e.screenY]) * } * document.addEventListener('mousemove', handleMouseMove) * * return () => { * document.removeEventListener('mousemove', handleMouseMove) * } * }, * [], * ) * * return pos * }) * ``` */ function state(key: string, initialValue: T): [T, (val: T) => void] { const scope = hookScopeStack.peek() if (!scope) { throw new Error(`prism.state() is called outside of a prism() call.`) } return scope.state(key, initialValue) } /** * This is useful to make sure your code is running inside a `prism()` call. * * @example * ```ts * import {prism} from '@theatre/dataverse' * * function onlyUsefulInAPrism() { * prism.ensurePrism() * } * * prism(() => { * onlyUsefulInAPrism() // will run fine * }) * * setTimeout(() => { * onlyUsefulInAPrism() // throws an error * console.log('This will never get logged') * }, 0) * ``` */ function ensurePrism(): void { const scope = hookScopeStack.peek() if (!scope) { throw new Error(`The parent function is called outside of a prism() call.`) } } function scope(key: string, fn: () => T): T { const parentScope = hookScopeStack.peek() if (!parentScope) { throw new Error(`prism.scope() is called outside of a prism() call.`) } const subScope = parentScope.sub(key) hookScopeStack.push(subScope) const ret = safelyRun(fn, undefined).value hookScopeStack.pop() return ret as $IntentionalAny as T } /** * Just an alias for `prism.memo(key, () => prism(fn), deps).getValue()`. It creates a new prism, memoizes it, and returns the value. * `prism.sub()` is useful when you want to divide your prism into smaller prisms, each of which * would _only_ recalculate when _certain_ dependencies change. In other words, it's an optimization tool. * * @param key - The key for the memo. Should be unique inside of the prism * @param fn - The function to run inside the prism * @param deps - The dependency array. Provide `[]` if you want to the value to be memoized only once and never re-calculated. * @returns The value of the inner prism */ function sub( key: string, fn: () => T, deps: undefined | $IntentionalAny[], ): T { return memo(key, () => prism(fn), deps).getValue() } /** * @returns true if the current function is running inside a `prism()` call. */ function inPrism(): boolean { return !!hookScopeStack.peek() } const possiblePrismToValue =

| unknown>( input: P, ): P extends Prism ? T : P => { if (isPrism(input)) { return input.getValue() as $IntentionalAny } else { return input as $IntentionalAny } } /** * `prism.source()` allow a prism to react to changes in some external source (other than other prisms). * For example, `Atom.pointerToPrism()` uses `prism.source()` to create a prism that reacts to changes in the atom's value. * @param subscribe - The prism will call this function as soon as the prism goes hot. This function should return an unsubscribe function function which the prism will call when it goes cold. * @param getValue - A function that returns the current value of the external source. * @returns The current value of the source * * Example: * ```ts * function prismFromInputElement(input: HTMLInputElement): Prism { * function listen(cb: (value: string) => void) { * const listener = () => { * cb(input.value) * } * input.addEventListener('input', listener) * return () => { * input.removeEventListener('input', listener) * } * } * * function get() { * return input.value * } * return prism(() => prism.source(listen, get)) * } * ``` */ function source( subscribe: (fn: (val: V) => void) => VoidFn, getValue: () => V, ): V { const scope = hookScopeStack.peek() if (!scope) { throw new Error(`prism.source() is called outside of a prism() call.`) } return scope.source(subscribe, getValue) } type IPrismFn = { (fn: () => T): Prism ref: typeof ref effect: typeof effect memo: typeof memo ensurePrism: typeof ensurePrism state: typeof state scope: typeof scope sub: typeof sub inPrism: typeof inPrism source: typeof source } /** * Creates a prism from the passed function that adds all prisms referenced * in it as dependencies, and reruns the function when these change. * * @param fn - The function to rerun when the prisms referenced in it change. */ const prism: IPrismFn = (fn) => { return new PrismInstance(fn) } class ColdScope implements PrismScope { effect(key: string, cb: () => () => void, deps?: unknown[]): void { console.warn(`prism.effect() does not run in cold prisms`) } memo( key: string, fn: () => T, deps: any[] | readonly any[] | undefined, ): T { return fn() } state(key: string, initialValue: T): [T, (val: T) => void] { return [initialValue, () => {}] } ref(key: string, initialValue: T): IRef { return {current: initialValue} } sub(key: string): ColdScope { return new ColdScope() } source(subscribe: (fn: (val: V) => void) => VoidFn, getValue: () => V): V { return getValue() } } function calculateColdPrism(fn: () => V): V { const scope = new ColdScope() hookScopeStack.push(scope) let value: V try { value = fn() } catch (error) { console.error(error) } finally { const topOfTheStack = hookScopeStack.pop() if (topOfTheStack !== scope) { console.warn( // @todo guide the user to report the bug in an issue `The Prism hook stack has slipped. This is a bug.`, ) } } return value! } prism.ref = ref prism.effect = effect prism.memo = memo prism.ensurePrism = ensurePrism prism.state = state prism.scope = scope prism.sub = sub prism.inPrism = inPrism prism.source = source export default prism ================================================ FILE: packages/dataverse/src/setupTestEnv.ts ================================================ export {} ================================================ FILE: packages/dataverse/src/types.ts ================================================ /** For `any`s that aren't meant to stay `any`*/ export type $FixMe = any /** For `any`s that we don't care about */ export type $IntentionalAny = any export type VoidFn = () => void ================================================ FILE: packages/dataverse/src/utils/Stack.ts ================================================ interface Node { next: undefined | Node data: Data } /** * Just a simple LinkedList */ export default class Stack { _head: undefined | Node constructor() { this._head = undefined } peek() { return this._head && this._head.data } pop() { const head = this._head if (!head) { return undefined } this._head = head.next return head.data } push(data: Data) { const node = {next: this._head, data} this._head = node } } ================================================ FILE: packages/dataverse/src/utils/typeTestUtils.ts ================================================ import type {$IntentionalAny} from '../types' /** * Useful in type tests, such as: const a: SomeType = _any */ export const _any: $IntentionalAny = null /** * Useful in typeTests. If you want to ensure that value v follows type V, * just write `expectType(v)` */ export const expectType = (v: T): T => v ================================================ FILE: packages/dataverse/src/utils/updateDeep.ts ================================================ import type {$FixMe, $IntentionalAny} from '../types' export default function updateDeep( state: S, path: (string | number | undefined)[], reducer: (...args: $IntentionalAny[]) => $IntentionalAny, ): S { if (path.length === 0) return reducer(state) return hoop(state, path as $IntentionalAny, reducer) } const hoop = ( s: $FixMe, path: (string | number)[], reducer: $FixMe, ): $FixMe => { if (path.length === 0) { return reducer(s) } if (Array.isArray(s)) { let [index, ...restOfPath] = path index = parseInt(String(index), 10) if (isNaN(index)) index = 0 const oldVal = s[index] const newVal = hoop(oldVal, restOfPath, reducer) if (oldVal === newVal) return s const newS = [...s] newS.splice(index, 1, newVal) return newS } else if (typeof s === 'object' && s !== null) { const [key, ...restOfPath] = path const oldVal = s[key] const newVal = hoop(oldVal, restOfPath, reducer) if (oldVal === newVal) return s const newS = {...s, [key]: newVal} return newS } else { const [key, ...restOfPath] = path return {[key]: hoop(undefined, restOfPath, reducer)} } } ================================================ FILE: packages/dataverse/src/val.ts ================================================ import type {Prism} from './prism/Interface' import {isPrism} from './prism/Interface' import type {PointerType} from './pointer' import {isPointer} from './pointer' import type {$IntentionalAny} from './types' import {pointerToPrism} from './pointerToPrism' /** * Convenience function that returns a plain value from its argument, whether it * is a pointer, a prism or a plain value itself. * * @remarks * For pointers, the value is returned by first creating a prism, so it is * reactive e.g. when used in a `prism`. * * @param input - The argument to return a value from. */ export const val = < P extends | PointerType<$IntentionalAny> | Prism<$IntentionalAny> | undefined | null, >( input: P, ): P extends PointerType ? T : P extends Prism ? T : P extends undefined | null ? P : unknown => { if (isPointer(input)) { return pointerToPrism(input).getValue() as $IntentionalAny } else if (isPrism(input)) { return input.getValue() as $IntentionalAny } else { return input as $IntentionalAny } } ================================================ FILE: packages/dataverse/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "dist", "lib": ["ESNext", "DOM"], "rootDir": "src", "types": ["jest", "node"], "emitDeclarationOnly": true, "target": "es6", "composite": true }, "include": ["./src/**/*"] } ================================================ FILE: packages/dataverse/typedoc.json ================================================ { "visibilityFilters": { "protected": false, "private": false, "inherited": true, "external": false, "@alpha": false, "@beta": false }, "excludeTags": [ "@override", "@virtual", "@privateRemarks", "@satisfies", "@overload", "@remarks" ], "categorizeByGroup": false, "excludeInternal": true, "excludeProtected": true, "excludePrivate": true, "sourceLinkTemplate": "https://github.com/theatre-js/theatre/blob/main/{path}#L{line}" } ================================================ FILE: packages/dataverse-experiments/.babelrc.js ================================================ module.exports = {} ================================================ FILE: packages/dataverse-experiments/.gitignore ================================================ /dist ================================================ FILE: packages/dataverse-experiments/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: packages/dataverse-experiments/README.md ================================================ # Dataverse experiments This package contains some experiments in [@theatre/dataverse](../dataverse). We are keeping these experiments in the main branch so they don't bitrot. The experimental `AbstractDerivation` in this package uses some ideas from [incremental](https://github.com/janestreet/incremental/blob/master/src/incremental_intf.ml). ================================================ FILE: packages/dataverse-experiments/package.json ================================================ { "name": "@theatre/dataverse-experiments", "version": "1.0.0-dev", "license": "Apache-2.0", "author": { "name": "Aria Minaei", "email": "aria@theatrejs.com", "url": "https://github.com/AriaMinaei" }, "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { "typecheck": "yarn run _declarations:emit", "_declarations:emit": "tsc --build ./tsconfig.json" }, "devDependencies": { "@types/jest": "^26.0.23", "@types/lodash-es": "^4.17.4", "@types/node": "^15.6.2", "@types/react": "^17.0.9", "typescript": "5.1.6" }, "dependencies": { "lodash-es": "^4.17.21" } } ================================================ FILE: packages/dataverse-experiments/src/Atom.ts ================================================ import get from 'lodash-es/get' import isPlainObject from 'lodash-es/isPlainObject' import last from 'lodash-es/last' import DerivationFromSource from './derivations/DerivationFromSource' import type {IDerivation} from './derivations/IDerivation' import {isDerivation} from './derivations/IDerivation' import type {Pointer, PointerType} from './pointer' import pointer, {getPointerMeta} from './pointer' import type {$FixMe, $IntentionalAny} from './types' import type {PathBasedReducer} from './utils/PathBasedReducer' import updateDeep from './utils/updateDeep' type Listener = (newVal: unknown) => void enum ValueTypes { Dict, Array, Other, } const getTypeOfValue = (v: unknown): ValueTypes => { if (Array.isArray(v)) return ValueTypes.Array if (isPlainObject(v)) return ValueTypes.Dict return ValueTypes.Other } const getKeyOfValue = ( v: unknown, key: string | number, vType: ValueTypes = getTypeOfValue(v), ): unknown => { if (vType === ValueTypes.Dict && typeof key === 'string') { return (v as $IntentionalAny)[key] } else if (vType === ValueTypes.Array && isValidArrayIndex(key)) { return (v as $IntentionalAny)[key] } else { return undefined } } const isValidArrayIndex = (key: string | number): boolean => { const inNumber = typeof key === 'number' ? key : parseInt(key, 10) return ( !isNaN(inNumber) && inNumber >= 0 && inNumber < Infinity && (inNumber | 0) === inNumber ) } class Scope { children: Map = new Map() identityChangeListeners: Set = new Set() constructor( readonly _parent: undefined | Scope, readonly _path: (string | number)[], ) {} addIdentityChangeListener(cb: Listener) { this.identityChangeListeners.add(cb) } removeIdentityChangeListener(cb: Listener) { this.identityChangeListeners.delete(cb) this._checkForGC() } removeChild(key: string | number) { this.children.delete(key) this._checkForGC() } getChild(key: string | number) { return this.children.get(key) } getOrCreateChild(key: string | number) { let child = this.children.get(key) if (!child) { child = child = new Scope(this, this._path.concat([key])) this.children.set(key, child) } return child } _checkForGC() { if (this.identityChangeListeners.size > 0) return if (this.children.size > 0) return if (this._parent) { this._parent.removeChild(last(this._path) as string | number) } } } export default class Atom { private _currentState: State private readonly _rootScope: Scope readonly pointer: Pointer constructor(initialState: State) { this._currentState = initialState this._rootScope = new Scope(undefined, []) this.pointer = pointer({root: this as $FixMe, path: []}) } setState(newState: State) { const oldState = this._currentState this._currentState = newState this._checkUpdates(this._rootScope, oldState, newState) } getState() { return this._currentState } getIn(path: (string | number)[]): unknown { return path.length === 0 ? this.getState() : get(this.getState(), path) } reduceState: PathBasedReducer = ( path: $IntentionalAny[], reducer: $IntentionalAny, ) => { const newState = updateDeep(this.getState(), path, reducer) this.setState(newState) return newState } setIn(path: $FixMe[], val: $FixMe) { return this.reduceState(path, () => val) } private _checkUpdates(scope: Scope, oldState: unknown, newState: unknown) { if (oldState === newState) return scope.identityChangeListeners.forEach((cb) => cb(newState)) if (scope.children.size === 0) return const oldValueType = getTypeOfValue(oldState) const newValueType = getTypeOfValue(newState) if (oldValueType === ValueTypes.Other && oldValueType === newValueType) return scope.children.forEach((childScope, childKey) => { const oldChildVal = getKeyOfValue(oldState, childKey, oldValueType) const newChildVal = getKeyOfValue(newState, childKey, newValueType) this._checkUpdates(childScope, oldChildVal, newChildVal) }) } private _getOrCreateScopeForPath(path: (string | number)[]): Scope { let curScope = this._rootScope for (const pathEl of path) { curScope = curScope.getOrCreateChild(pathEl) } return curScope } onPathValueChange(path: (string | number)[], cb: (v: unknown) => void) { const scope = this._getOrCreateScopeForPath(path) scope.identityChangeListeners.add(cb) const untap = () => { scope.identityChangeListeners.delete(cb) } return untap } } const identityDerivationWeakMap = new WeakMap<{}, IDerivation>() export const valueDerivation =

>( pointer: P, ): IDerivation

? T : void> => { const meta = getPointerMeta(pointer) let pr = identityDerivationWeakMap.get(meta) if (!pr) { const root = meta.root if (!(root instanceof Atom)) { throw new Error( `Cannot run valueDerivation on a pointer whose root is not an Atom`, ) } const {path} = meta pr = new DerivationFromSource<$IntentionalAny>( (listener) => root.onPathValueChange(path, listener), () => root.getIn(path), ) identityDerivationWeakMap.set(meta, pr) } return pr as $IntentionalAny } export const val =

( pointerOrDerivationOrPlainValue: P, ): P extends PointerType ? T : P extends IDerivation ? T : unknown => { if (isPointer(pointerOrDerivationOrPlainValue)) { return valueDerivation( pointerOrDerivationOrPlainValue, ).getValue() as $IntentionalAny } else if (isDerivation(pointerOrDerivationOrPlainValue)) { return pointerOrDerivationOrPlainValue.getValue() as $IntentionalAny } else { return pointerOrDerivationOrPlainValue as $IntentionalAny } } export const isPointer = (p: $IntentionalAny): p is Pointer => { return p && p.$pointerMeta ? true : false } ================================================ FILE: packages/dataverse-experiments/src/Box.ts ================================================ import DerivationFromSource from './derivations/DerivationFromSource' import type {IDerivation} from './derivations/IDerivation' import Emitter from './utils/Emitter' export interface IBox { set(v: V): void get(): V derivation: IDerivation } export default class Box implements IBox { private _publicDerivation: IDerivation private _emitter = new Emitter() constructor(protected _value: V) { this._publicDerivation = new DerivationFromSource( (listener) => this._emitter.tappable.tap(listener), this.get.bind(this), ) } set(v: V) { this._value = v this._emitter.emit(v) } get() { return this._value } get derivation() { return this._publicDerivation } } ================================================ FILE: packages/dataverse-experiments/src/Ticker.ts ================================================ type ICallback = (t: number) => void export default class Ticker { private _scheduledForThisOrNextTick: Set private _scheduledForNextTick: Set private _timeAtCurrentTick: number private _ticking: boolean = false constructor() { this._scheduledForThisOrNextTick = new Set() this._scheduledForNextTick = new Set() this._timeAtCurrentTick = 0 } /** * Registers for fn to be called either on this tick or the next tick. * * If registerSideEffect() is called while Ticker.tick() is running, the * side effect _will_ be called within the running tick. If you don't want this * behavior, you can use registerSideEffectForNextTick(). * * Note that fn will be added to a Set(). Which means, if you call registerSideEffect(fn) * with the same fn twice in a single tick, it'll only run once. */ onThisOrNextTick(fn: ICallback) { this._scheduledForThisOrNextTick.add(fn) } /** * Registers a side effect to be called on the next tick. * * @see Ticker:onThisOrNextTick() */ onNextTick(fn: ICallback) { this._scheduledForNextTick.add(fn) } offThisOrNextTick(fn: ICallback) { this._scheduledForThisOrNextTick.delete(fn) } offNextTick(fn: ICallback) { this._scheduledForNextTick.delete(fn) } get time() { if (this._ticking) { return this._timeAtCurrentTick } else return performance.now() } tick(t: number = performance.now()) { this._ticking = true this._timeAtCurrentTick = t this._scheduledForNextTick.forEach((v) => this._scheduledForThisOrNextTick.add(v), ) this._scheduledForNextTick.clear() this._tick(0) this._ticking = false } private _tick(iterationNumber: number): void { const time = this.time if (iterationNumber > 10) { console.warn('_tick() recursing for 10 times') } if (iterationNumber > 100) { throw new Error(`Maximum recursion limit for _tick()`) } const oldSet = this._scheduledForThisOrNextTick this._scheduledForThisOrNextTick = new Set() oldSet.forEach((fn) => { fn(time) }) if (this._scheduledForThisOrNextTick.size > 0) { return this._tick(iterationNumber + 1) } } } ================================================ FILE: packages/dataverse-experiments/src/atom.typeTest.ts ================================================ import Atom, {val} from './Atom' import {expectType, _any} from './utils/typeTestUtils' ;() => { const p = new Atom<{foo: string; bar: number; optional?: boolean}>(_any) .pointer expectType(val(p.foo)) // @ts-expect-error TypeTest expectType(val(p.foo)) expectType(val(p.bar)) // @ts-expect-error TypeTest expectType(val(p.bar)) // @ts-expect-error TypeTest expectType<{}>(val(p.nonExistent)) expectType(val(p.optional)) // @ts-expect-error TypeTest expectType(val(p.optional)) // @ts-expect-error TypeTest expectType(val(p.optional)) // @ts-expect-error TypeTest expectType(val(p.optional)) } ================================================ FILE: packages/dataverse-experiments/src/derivations/AbstractDerivation.ts ================================================ import type {$IntentionalAny} from '../types' import type Tappable from '../utils/Tappable' import DerivationEmitter from './DerivationEmitter' import flatMap from './flatMap' import type {GraphNode, IDerivation} from './IDerivation' import map from './map' import { reportResolutionEnd, reportResolutionStart, } from './prism/discoveryMechanism' export default abstract class AbstractDerivation implements IDerivation { readonly isDerivation: true = true private _didMarkDependentsAsStale: boolean = false private _isHot: boolean = false private _isFresh: boolean = false protected _lastValue: undefined | V = undefined protected _dependents: Set = new Set() protected _dependencies: Set> = new Set() /** * _height is the maximum height of all dependents, plus one. * * -1 means it's not yet calculated * 0 is reserved only for listeners */ private _height: number = -1 private _graphNode: GraphNode protected abstract _recalculate(): V protected abstract _reactToDependencyBecomingStale( which: IDerivation, ): void constructor() { const self = this this._graphNode = { get height() { return self._height }, recalculate() { // @todo }, } } get isHot(): boolean { return this._isHot } get height() { return this._height } protected _addDependency(d: IDerivation<$IntentionalAny>) { if (this._dependencies.has(d)) return this._dependencies.add(d) if (this._isHot) d.addDependent(this._graphNode) } protected _removeDependency(d: IDerivation<$IntentionalAny>) { if (!this._dependencies.has(d)) return this._dependencies.delete(d) if (this._isHot) d.removeDependent(this._graphNode) } changes(): Tappable { return new DerivationEmitter(this).tappable() } addDependent(d: GraphNode) { const hadDepsBefore = this._dependents.size > 0 this._dependents.add(d) if (d.height > this._height - 1) { this._setHeight(d.height + 1) } if (!hadDepsBefore) { this._reactToNumberOfDependentsChange() } } /** * @sealed */ removeDependent(d: GraphNode) { const hadDepsBefore = this._dependents.size > 0 this._dependents.delete(d) const hasDepsNow = this._dependents.size > 0 if (hadDepsBefore !== hasDepsNow) { this._reactToNumberOfDependentsChange() } } reportDependentHeightChange(d: GraphNode) { if (process.env.NODE_ENV === 'development') { if (!this._dependents.has(d)) { throw new Error( `Got a reportDependentHeightChange from a non-dependent.`, ) } } this._recalculateHeight() } private _recalculateHeight() { let maxHeightOfDependents = -1 this._dependents.forEach((d) => { maxHeightOfDependents = Math.max(maxHeightOfDependents, d.height) }) const newHeight = maxHeightOfDependents + 1 if (this._height !== newHeight) { this._setHeight(newHeight) } } private _setHeight(h: number) { this._height = h this._dependencies.forEach((d) => { d.reportDependentHeightChange(this._graphNode) }) } /** * This is meant to be called by subclasses * * @sealed */ protected _markAsStale(which: IDerivation<$IntentionalAny>) { this._internal_markAsStale(which) } private _internal_markAsStale = (which: IDerivation<$IntentionalAny>) => { this._reactToDependencyBecomingStale(which) if (this._didMarkDependentsAsStale) return this._didMarkDependentsAsStale = true this._isFresh = false this._dependents.forEach((dependent) => { dependent.recalculate() }) } getValue(): V { reportResolutionStart(this) if (!this._isFresh) { const newValue = this._recalculate() this._lastValue = newValue if (this.isHot) { this._isFresh = true this._didMarkDependentsAsStale = false } } reportResolutionEnd(this) return this._lastValue! } private _reactToNumberOfDependentsChange() { const shouldBecomeHot = this._dependents.size > 0 if (shouldBecomeHot === this._isHot) return this._isHot = shouldBecomeHot this._didMarkDependentsAsStale = false this._isFresh = false if (shouldBecomeHot) { this._dependencies.forEach((d) => { d.addDependent(this._graphNode) }) this._keepHot() } else { this._dependencies.forEach((d) => { d.removeDependent(this._graphNode) }) this._becomeCold() } } protected _keepHot() {} protected _becomeCold() {} map(fn: (v: V) => T): IDerivation { return map(this, fn) } flatMap( fn: (v: V) => R, ): IDerivation ? T : R> { return flatMap(this, fn) } } ================================================ FILE: packages/dataverse-experiments/src/derivations/AbstractDerivation.typeTest.ts ================================================ import type {$IntentionalAny} from '../types' import type {IDerivation} from './IDerivation' const _any: $IntentionalAny = null // map ;() => { const a: IDerivation = _any // $ExpectType IDerivation // eslint-disable-next-line unused-imports/no-unused-vars-ts a.map((s: string) => 10) // @ts-expect-error // eslint-disable-next-line unused-imports/no-unused-vars-ts a.map((s: number) => 10) } // flatMap() /* eslint-disable unused-imports/no-unused-vars-ts */ ;() => { const a: IDerivation = _any // okay a.flatMap((s: string) => {}) // @ts-expect-error TypeTest a.flatMap((s: number) => {}) // $ExpectType IDerivation a.flatMap((s): IDerivation => _any) // $ExpectType IDerivation a.flatMap((s): number => _any) } /* eslint-enable unused-imports/no-unused-vars-ts */ ================================================ FILE: packages/dataverse-experiments/src/derivations/ConstantDerivation.ts ================================================ import AbstractDerivation from './AbstractDerivation' export default class ConstantDerivation extends AbstractDerivation { _v: V constructor(v: V) { super() this._v = v return this } _recalculate() { return this._v } _reactToDependencyBecomingStale() {} } ================================================ FILE: packages/dataverse-experiments/src/derivations/DerivationEmitter.ts ================================================ import Emitter from '../utils/Emitter' import type {default as Tappable} from '../utils/Tappable' import type {GraphNode, IDerivation} from './IDerivation' export default class DerivationEmitter { private _emitter: Emitter private _lastValue: undefined | V private _lastValueRecorded: boolean private _hadTappers: boolean private _graphNode: GraphNode constructor(private readonly _derivation: IDerivation) { this._emitter = new Emitter() this._graphNode = { height: 0, recalculate: () => { this._emit() }, } this._emitter.onNumberOfTappersChange(() => { this._reactToNumberOfTappersChange() }) this._hadTappers = false this._lastValueRecorded = false this._lastValue = undefined return this } private _reactToNumberOfTappersChange() { const hasTappers = this._emitter.hasTappers() if (hasTappers !== this._hadTappers) { this._hadTappers = hasTappers if (hasTappers) { this._derivation.addDependent(this._graphNode) } else { this._derivation.removeDependent(this._graphNode) } } } tappable(): Tappable { return this._emitter.tappable } private _emit = () => { const newValue = this._derivation.getValue() if (newValue === this._lastValue && this._lastValueRecorded === true) return this._lastValue = newValue this._lastValueRecorded = true this._emitter.emit(newValue) } } ================================================ FILE: packages/dataverse-experiments/src/derivations/DerivationFromSource.ts ================================================ import type {VoidFn} from '../types' import AbstractDerivation from './AbstractDerivation' const noop = () => {} export default class DerivationFromSource extends AbstractDerivation { private _untapFromChanges: () => void private _cachedValue: undefined | V private _hasCachedValue: boolean constructor( private readonly _tapToSource: (listener: (newValue: V) => void) => VoidFn, private readonly _getValueFromSource: () => V, ) { super() this._untapFromChanges = noop this._cachedValue = undefined this._hasCachedValue = false } _recalculate() { if (this.isHot) { if (!this._hasCachedValue) { this._cachedValue = this._getValueFromSource() this._hasCachedValue = true } return this._cachedValue as V } else { return this._getValueFromSource() } } _keepHot() { this._hasCachedValue = false this._cachedValue = undefined this._untapFromChanges = this._tapToSource((newValue) => { this._hasCachedValue = true this._cachedValue = newValue this._markAsStale(this) }) } _becomeCold() { this._untapFromChanges() this._untapFromChanges = noop this._hasCachedValue = false this._cachedValue = undefined } _reactToDependencyBecomingStale() {} } ================================================ FILE: packages/dataverse-experiments/src/derivations/Freshener.ts ================================================ import type {GraphNode} from './IDerivation' export default class Freshener { schedulePeak(d: GraphNode) {} } ================================================ FILE: packages/dataverse-experiments/src/derivations/IDerivation.ts ================================================ import type Tappable from '../utils/Tappable' export type GraphNode = { height: number recalculate(): void } export interface IDerivation { isDerivation: true isHot: boolean changes(): Tappable addDependent(d: GraphNode): void removeDependent(d: GraphNode): void reportDependentHeightChange(d: GraphNode): void getValue(): V map(fn: (v: V) => T): IDerivation flatMap( fn: (v: V) => R, ): IDerivation ? T : R> } export function isDerivation(d: any): d is IDerivation { return d && d.isDerivation && d.isDerivation === true } ================================================ FILE: packages/dataverse-experiments/src/derivations/flatMap.ts ================================================ import type {$FixMe} from '../types' import AbstractDerivation from './AbstractDerivation' import type {IDerivation} from './IDerivation' enum UPDATE_NEEDED_FROM { none = 0, dep = 1, inner = 2, } const makeFlatMapDerivationClass = () => { class FlatMapDerivation extends AbstractDerivation { private _innerDerivation: undefined | null | IDerivation private _staleDependency: UPDATE_NEEDED_FROM static displayName = 'flatMap' constructor( readonly _depDerivation: IDerivation, readonly _fn: (v: DepType) => IDerivation | V, ) { super() this._innerDerivation = undefined this._staleDependency = UPDATE_NEEDED_FROM.dep this._addDependency(_depDerivation) return this } _recalculateHot() { const updateNeededFrom = this._staleDependency this._staleDependency = UPDATE_NEEDED_FROM.none if (updateNeededFrom === UPDATE_NEEDED_FROM.inner) { // @ts-ignore return this._innerDerivation.getValue() } const possibleInnerDerivation = this._fn(this._depDerivation.getValue()) if (possibleInnerDerivation instanceof AbstractDerivation) { this._innerDerivation = possibleInnerDerivation this._addDependency(possibleInnerDerivation) return possibleInnerDerivation.getValue() } else { return possibleInnerDerivation } } protected _recalculateCold() { const possibleInnerDerivation = this._fn(this._depDerivation.getValue()) if (possibleInnerDerivation instanceof AbstractDerivation) { return possibleInnerDerivation.getValue() } else { return possibleInnerDerivation } } protected _recalculate() { return this.isHot ? this._recalculateHot() : this._recalculateCold() } protected _reactToDependencyBecomingStale( msgComingFrom: IDerivation, ) { const updateNeededFrom = msgComingFrom === this._depDerivation ? UPDATE_NEEDED_FROM.dep : UPDATE_NEEDED_FROM.inner if ( updateNeededFrom === UPDATE_NEEDED_FROM.inner && msgComingFrom !== this._innerDerivation ) { throw Error( `got a _pipostale() from neither the dep nor the inner derivation`, ) } if (this._staleDependency === UPDATE_NEEDED_FROM.none) { this._staleDependency = updateNeededFrom if (updateNeededFrom === UPDATE_NEEDED_FROM.dep) { this._removeInnerDerivation() } } else if (this._staleDependency === UPDATE_NEEDED_FROM.dep) { } else { if (updateNeededFrom === UPDATE_NEEDED_FROM.dep) { this._staleDependency = UPDATE_NEEDED_FROM.dep this._removeInnerDerivation() } } } private _removeInnerDerivation() { if (this._innerDerivation) { this._removeDependency(this._innerDerivation) this._innerDerivation = undefined } } protected _keepHot() { this._staleDependency = UPDATE_NEEDED_FROM.dep this.getValue() } protected _becomeCold() { this._staleDependency = UPDATE_NEEDED_FROM.dep this._removeInnerDerivation() } } return FlatMapDerivation } let cls: ReturnType | undefined = undefined export default function flatMap( dep: IDerivation, fn: (v: V) => R, ): IDerivation ? T : R> { if (!cls) { cls = makeFlatMapDerivationClass() } return new cls(dep, fn) as $FixMe } ================================================ FILE: packages/dataverse-experiments/src/derivations/iterateAndCountTicks.ts ================================================ import {isPointer, valueDerivation} from '../Atom' import type {Pointer} from '../pointer' import type {IDerivation} from './IDerivation' import {isDerivation} from './IDerivation' export default function* iterateAndCountTicks( pointerOrDerivation: IDerivation | Pointer, ): Generator<{value: V; ticks: number}, void, void> { let d if (isPointer(pointerOrDerivation)) { d = valueDerivation(pointerOrDerivation) as IDerivation } else if (isDerivation(pointerOrDerivation)) { d = pointerOrDerivation } else { throw new Error(`Only pointers and derivations are supported`) } let ticksCountedSinceLastYield = 0 const untap = d.changes().tap(() => { ticksCountedSinceLastYield++ }) try { while (true) { const ticks = ticksCountedSinceLastYield ticksCountedSinceLastYield = 0 yield {value: d.getValue(), ticks} } } finally { untap() } } ================================================ FILE: packages/dataverse-experiments/src/derivations/iterateOver.test.ts ================================================ import Atom from '../Atom' import iterateOver from './iterateOver' describe.skip(`iterateOver()`, () => { test('it should work', () => { const a = new Atom({a: 0}) let iter = iterateOver(a.pointer.a) expect(iter.next().value).toEqual(0) a.setIn(['a'], 1) a.setIn(['a'], 2) expect(iter.next()).toMatchObject({value: 2, done: false}) iter.return() iter = iterateOver(a.pointer.a) expect(iter.next().value).toEqual(2) a.setIn(['a'], 3) expect(iter.next()).toMatchObject({done: false, value: 3}) iter.return() }) }) ================================================ FILE: packages/dataverse-experiments/src/derivations/iterateOver.ts ================================================ import {isPointer, valueDerivation} from '../Atom' import type {Pointer} from '../pointer' import Ticker from '../Ticker' import type {IDerivation} from './IDerivation' import {isDerivation} from './IDerivation' export default function* iterateOver( pointerOrDerivation: IDerivation | Pointer, ): Generator { let d if (isPointer(pointerOrDerivation)) { d = valueDerivation(pointerOrDerivation) as IDerivation } else if (isDerivation(pointerOrDerivation)) { d = pointerOrDerivation } else { throw new Error(`Only pointers and derivations are supported`) } const ticker = new Ticker() const untap = d.changes().tap((v) => {}) try { while (true) { ticker.tick() yield d.getValue() } } finally { untap() } } ================================================ FILE: packages/dataverse-experiments/src/derivations/map.ts ================================================ import AbstractDerivation from './AbstractDerivation' import type {IDerivation} from './IDerivation' // Exporting from a function because of the circular dependency with AbstractDerivation const makeMapDerivationClass = () => class MapDerivation extends AbstractDerivation { constructor( private readonly _dep: IDerivation, private readonly _fn: (t: T) => V, ) { super() this._addDependency(_dep) } _recalculate() { return this._fn(this._dep.getValue()) } _reactToDependencyBecomingStale() {} } let cls: ReturnType | undefined = undefined export default function flatMap( dep: IDerivation, fn: (v: V) => R, ): IDerivation { if (!cls) { cls = makeMapDerivationClass() } return new cls(dep, fn) } ================================================ FILE: packages/dataverse-experiments/src/derivations/prism/discoveryMechanism.ts ================================================ import type {$IntentionalAny} from '../../types' import Stack from '../../utils/Stack' import type {IDerivation} from '../IDerivation' const noop = () => {} const stack = new Stack() const noopCollector: Collector = noop type Collector = (d: IDerivation<$IntentionalAny>) => void export const collectObservedDependencies = ( cb: () => void, collector: Collector, ) => { stack.push(collector) cb() stack.pop() } export const startIgnoringDependencies = () => { stack.push(noopCollector) } export const stopIgnoringDependencies = () => { if (stack.peek() !== noopCollector) { if (process.env.NODE_ENV === 'development') { console.warn('This should never happen') } } else { stack.pop() } } export const reportResolutionStart = (d: IDerivation<$IntentionalAny>) => { const possibleCollector = stack.peek() if (possibleCollector) { possibleCollector(d) } stack.push(noopCollector) } export const reportResolutionEnd = (_d: IDerivation<$IntentionalAny>) => { stack.pop() } export const isCollectingDependencies = () => { return stack.peek() !== noopCollector } ================================================ FILE: packages/dataverse-experiments/src/derivations/prism/prism.test.ts ================================================ import Atom, {val} from '../../Atom' import Ticker from '../../Ticker' import type {$FixMe, $IntentionalAny} from '../../types' import ConstantDerivation from '../ConstantDerivation' import iterateAndCountTicks from '../iterateAndCountTicks' import prism, {PrismDerivation} from './prism' describe.skip('prism', () => { let ticker: Ticker beforeEach(() => { ticker = new Ticker() }) it('should work', () => { const o = new Atom({foo: 'foo'}) const d = new PrismDerivation(() => { return val(o.pointer.foo) + 'boo' }) expect(d.getValue()).toEqual('fooboo') const changes: Array<$FixMe> = [] d.changes().tap((c) => { changes.push(c) }) o.reduceState(['foo'], () => 'foo2') ticker.tick() expect(changes).toMatchObject(['foo2boo']) }) it('should only collect immediate dependencies', () => { const aD = new ConstantDerivation(1) const bD = aD.map((v) => v * 2) const cD = prism(() => { return bD.getValue() }) expect(cD.getValue()).toEqual(2) expect((cD as $IntentionalAny)._dependencies.size).toEqual(1) }) describe('prism.ref()', () => { it('should work', () => { const theAtom: Atom<{n: number}> = new Atom({n: 2}) const isEvenD = prism((): {isEven: boolean} => { const ref = prism.ref<{isEven: boolean} | undefined>('cache', undefined) const currentN = val(theAtom.pointer.n) const isEven = currentN % 2 === 0 if (ref.current && ref.current.isEven === isEven) { return ref.current } else { ref.current = {isEven} return ref.current } }) const iterator = iterateAndCountTicks(isEvenD) theAtom.reduceState(['n'], () => 3) expect(iterator.next().value).toMatchObject({ value: {isEven: false}, ticks: 0, }) theAtom.reduceState(['n'], () => 5) theAtom.reduceState(['n'], () => 7) expect(iterator.next().value).toMatchObject({ value: {isEven: false}, ticks: 1, }) theAtom.reduceState(['n'], () => 2) theAtom.reduceState(['n'], () => 4) expect(iterator.next().value).toMatchObject({ value: {isEven: true}, ticks: 1, }) expect(iterator.next().value).toMatchObject({ value: {isEven: true}, ticks: 0, }) }) }) describe('prism.effect()', () => { it('should work', async () => { let iteration = 0 const sequence: unknown[] = [] let deps: unknown[] = [] const a = new Atom({letter: 'a'}) const derivation = prism(() => { const n = val(a.pointer.letter) const iterationAtTimeOfCall = iteration sequence.push({derivationCall: iterationAtTimeOfCall}) prism.effect( 'f', () => { sequence.push({effectCall: iterationAtTimeOfCall}) return () => { sequence.push({cleanupCall: iterationAtTimeOfCall}) } }, [...deps], ) return n }) const untap = derivation.changes().tap((change) => { sequence.push({change}) }) expect(sequence).toMatchObject([{derivationCall: 0}, {effectCall: 0}]) sequence.length = 0 iteration++ a.setIn(['letter'], 'b') ticker.tick() expect(sequence).toMatchObject([{derivationCall: 1}, {change: 'b'}]) sequence.length = 0 deps = [1] iteration++ a.setIn(['letter'], 'c') ticker.tick() expect(sequence).toMatchObject([ {derivationCall: 2}, {cleanupCall: 0}, {effectCall: 2}, {change: 'c'}, ]) sequence.length = 0 untap() // takes a tick before untap takes effect await new Promise((resolve) => setTimeout(resolve, 1)) expect(sequence).toMatchObject([{cleanupCall: 2}]) }) }) describe('prism.memo()', () => { it('should work', async () => { let iteration = 0 const sequence: unknown[] = [] let deps: unknown[] = [] const a = new Atom({letter: 'a'}) const derivation = prism(() => { const n = val(a.pointer.letter) const iterationAtTimeOfCall = iteration sequence.push({derivationCall: iterationAtTimeOfCall}) const resultOfMemo = prism.memo( 'memo', () => { sequence.push({memoCall: iterationAtTimeOfCall}) return iterationAtTimeOfCall }, [...deps], ) sequence.push({resultOfMemo}) return n }) const untap = derivation.changes().tap((change) => { sequence.push({change}) }) expect(sequence).toMatchObject([ {derivationCall: 0}, {memoCall: 0}, {resultOfMemo: 0}, ]) sequence.length = 0 iteration++ a.setIn(['letter'], 'b') ticker.tick() expect(sequence).toMatchObject([ {derivationCall: 1}, {resultOfMemo: 0}, {change: 'b'}, ]) sequence.length = 0 deps = [1] iteration++ a.setIn(['letter'], 'c') ticker.tick() expect(sequence).toMatchObject([ {derivationCall: 2}, {memoCall: 2}, {resultOfMemo: 2}, {change: 'c'}, ]) sequence.length = 0 untap() }) }) describe(`prism.scope()`, () => { it('should prevent name conflicts', () => { const d = prism(() => { const thisNameWillBeUsedForBothMemos = 'blah' const a = prism.scope('a', () => { return prism.memo(thisNameWillBeUsedForBothMemos, () => 'a', []) }) const b = prism.scope('b', () => { return prism.memo(thisNameWillBeUsedForBothMemos, () => 'b', []) }) return {a, b} }) expect(d.getValue()).toMatchObject({a: 'a', b: 'b'}) }) }) }) ================================================ FILE: packages/dataverse-experiments/src/derivations/prism/prism.ts ================================================ import Box from '../../Box' import type {$IntentionalAny, VoidFn} from '../../types' import Stack from '../../utils/Stack' import AbstractDerivation from '../AbstractDerivation' import type {IDerivation} from '../IDerivation' import { collectObservedDependencies, startIgnoringDependencies, stopIgnoringDependencies, } from './discoveryMechanism' const voidFn = () => {} export class PrismDerivation extends AbstractDerivation { protected _cacheOfDendencyValues: Map, unknown> = new Map() protected _possiblyStaleDeps = new Set>() private _prismScope = new PrismScope() constructor(readonly _fn: () => V) { super() } _recalculate() { let value: V if (this._possiblyStaleDeps.size > 0) { let anActuallyStaleDepWasFound = false startIgnoringDependencies() for (const dep of this._possiblyStaleDeps) { if (this._cacheOfDendencyValues.get(dep) !== dep.getValue()) { anActuallyStaleDepWasFound = true break } } stopIgnoringDependencies() this._possiblyStaleDeps.clear() if (!anActuallyStaleDepWasFound) { // console.log('ok') return this._lastValue! } } const newDeps: Set> = new Set() this._cacheOfDendencyValues.clear() collectObservedDependencies( () => { hookScopeStack.push(this._prismScope) try { value = this._fn() } catch (error) { console.error(error) } finally { const topOfTheStack = hookScopeStack.pop() if (topOfTheStack !== this._prismScope) { console.warn( // @todo guide the user to report the bug in an issue `The Prism hook stack has slipped. This is a bug.`, ) } } }, (observedDep) => { newDeps.add(observedDep) this._addDependency(observedDep) }, ) this._dependencies.forEach((dep) => { if (!newDeps.has(dep)) { this._removeDependency(dep) } }) this._dependencies = newDeps startIgnoringDependencies() newDeps.forEach((dep) => { this._cacheOfDendencyValues.set(dep, dep.getValue()) }) stopIgnoringDependencies() return value! } _reactToDependencyBecomingStale(msgComingFrom: IDerivation) { this._possiblyStaleDeps.add(msgComingFrom) } _keepHot() { this._prismScope = new PrismScope() startIgnoringDependencies() this.getValue() stopIgnoringDependencies() } _becomeCold() { cleanupScopeStack(this._prismScope) this._prismScope = new PrismScope() } } class PrismScope { isPrismScope = true private _subs: Record = {} sub(key: string) { if (!this._subs[key]) { this._subs[key] = new PrismScope() } return this._subs[key] } get subs() { return this._subs } } function cleanupScopeStack(scope: PrismScope) { for (const [_, sub] of Object.entries(scope.subs)) { cleanupScopeStack(sub) } cleanupEffects(scope) } function cleanupEffects(scope: PrismScope) { const effects = effectsWeakMap.get(scope) if (effects) { for (const k of Object.keys(effects)) { const effect = effects[k] safelyRun(effect.cleanup, undefined) } } effectsWeakMap.delete(scope) } function safelyRun( fn: () => T, returnValueInCaseOfError: U, ): {success: boolean; returnValue: T | U} { let returnValue: T | U = returnValueInCaseOfError let success = false try { returnValue = fn() success = true } catch (error) { setTimeout(() => { throw error }) } return {success, returnValue} } const hookScopeStack = new Stack() const refsWeakMap = new WeakMap>>() type IRef = { current: T } const effectsWeakMap = new WeakMap>() type IEffect = { deps: undefined | unknown[] cleanup: VoidFn } const memosWeakMap = new WeakMap>() type IMemo = { deps: undefined | unknown[] cachedValue: unknown } function ref(key: string, initialValue: T): IRef { const scope = hookScopeStack.peek() if (!scope) { throw new Error(`prism.ref() is called outside of a prism() call.`) } let refs = refsWeakMap.get(scope) if (!refs) { refs = {} refsWeakMap.set(scope, refs) } if (refs[key]) { return refs[key] as $IntentionalAny as IRef } else { const ref: IRef = { current: initialValue, } refs[key] = ref return ref } } function effect(key: string, cb: () => () => void, deps?: unknown[]): void { const scope = hookScopeStack.peek() if (!scope) { throw new Error(`prism.effect() is called outside of a prism() call.`) } let effects = effectsWeakMap.get(scope) if (!effects) { effects = {} effectsWeakMap.set(scope, effects) } if (!effects[key]) { effects[key] = { cleanup: voidFn, deps: [{}], } } const effect = effects[key] if (depsHaveChanged(effect.deps, deps)) { effect.cleanup() startIgnoringDependencies() effect.cleanup = safelyRun(cb, voidFn).returnValue stopIgnoringDependencies() effect.deps = deps } } function depsHaveChanged( oldDeps: undefined | unknown[], newDeps: undefined | unknown[], ): boolean { if (oldDeps === undefined || newDeps === undefined) { return true } else if (oldDeps.length !== newDeps.length) { return true } else { return oldDeps.some((el, i) => el !== newDeps[i]) } } function memo( key: string, fn: () => T, deps: undefined | $IntentionalAny[], ): T { const scope = hookScopeStack.peek() if (!scope) { throw new Error(`prism.memo() is called outside of a prism() call.`) } let memos = memosWeakMap.get(scope) if (!memos) { memos = {} memosWeakMap.set(scope, memos) } if (!memos[key]) { memos[key] = { cachedValue: null, deps: [{}], } } const memo = memos[key] if (depsHaveChanged(memo.deps, deps)) { startIgnoringDependencies() memo.cachedValue = safelyRun(fn, undefined).returnValue stopIgnoringDependencies() memo.deps = deps } return memo.cachedValue as $IntentionalAny as T } function state(key: string, initialValue: T): [T, (val: T) => void] { const {b, setValue} = prism.memo( 'state/' + key, () => { const b = new Box(initialValue) const setValue = (val: T) => b.set(val) return {b, setValue} }, [], ) return [b.derivation.getValue(), setValue] } function ensurePrism(): void { const scope = hookScopeStack.peek() if (!scope) { throw new Error(`The parent function is called outside of a prism() call.`) } } function scope(key: string, fn: () => T): T { const parentScope = hookScopeStack.peek() if (!parentScope) { throw new Error(`prism.memo() is called outside of a prism() call.`) } const subScope = parentScope.sub(key) hookScopeStack.push(subScope) const ret = safelyRun(fn, undefined).returnValue hookScopeStack.pop() return ret as $IntentionalAny as T } type IPrismFn = { (fn: () => T): IDerivation ref: typeof ref effect: typeof effect memo: typeof memo ensurePrism: typeof ensurePrism state: typeof state scope: typeof scope } const prism: IPrismFn = (fn) => { return new PrismDerivation(fn) } prism.ref = ref prism.effect = effect prism.memo = memo prism.ensurePrism = ensurePrism prism.state = state prism.scope = scope export default prism ================================================ FILE: packages/dataverse-experiments/src/index.ts ================================================ export {default as Atom, isPointer, val, valueDerivation} from './Atom' export {default as Box} from './Box' export type {IBox} from './Box' export {default as AbstractDerivation} from './derivations/AbstractDerivation' export {default as ConstantDerivation} from './derivations/ConstantDerivation' export {default as DerivationFromSource} from './derivations/DerivationFromSource' export {isDerivation} from './derivations/IDerivation' export type {IDerivation} from './derivations/IDerivation' export {default as iterateAndCountTicks} from './derivations/iterateAndCountTicks' export {default as iterateOver} from './derivations/iterateOver' export {default as prism} from './derivations/prism/prism' export {default as pointer, getPointerParts} from './pointer' export type {Pointer} from './pointer' export {default as Ticker} from './Ticker' ================================================ FILE: packages/dataverse-experiments/src/integration.test.ts ================================================ import Atom, {val} from './Atom' import prism from './derivations/prism/prism' import Ticker from './Ticker' describe.skip(`dataverse-experiments integration tests`, () => { describe(`identity pointers`, () => { it(`should work`, () => { const data = {foo: 'hi', bar: 0} const a = new Atom(data) const dataP = a.pointer const bar = dataP.bar expect(val(bar)).toEqual(0) const d = prism(() => { return val(bar) }) expect(d.getValue()).toEqual(0) const ticker = new Ticker() const changes: number[] = [] d.changes().tap((c) => { changes.push(c) }) a.setState({...data, bar: 1}) ticker.tick() expect(changes).toHaveLength(1) expect(changes[0]).toEqual(1) a.setState({...data, bar: 1}) ticker.tick() expect(changes).toHaveLength(1) }) }) }) ================================================ FILE: packages/dataverse-experiments/src/pointer.ts ================================================ import type {$IntentionalAny} from './types' type PathToProp = Array type PointerMeta = { root: {} path: (string | number)[] } export type UnindexableTypesForPointer = | number | string | boolean | null | void | undefined | Function // eslint-disable-line @typescript-eslint/ban-types export type UnindexablePointer = { [K in $IntentionalAny]: Pointer } const pointerMetaWeakMap = new WeakMap<{}, PointerMeta>() export type PointerType = { $$__pointer_type: O } export type Pointer = PointerType & (O extends UnindexableTypesForPointer ? UnindexablePointer : unknown extends O ? UnindexablePointer : O extends (infer T)[] ? Pointer[] : O extends {} ? {[K in keyof O]-?: Pointer} : UnindexablePointer) const pointerMetaSymbol = Symbol('pointerMeta') const cachedSubPointersWeakMap = new WeakMap< {}, Record> >() const handler = { get(obj: {}, prop: string | typeof pointerMetaSymbol): $IntentionalAny { if (prop === pointerMetaSymbol) return pointerMetaWeakMap.get(obj)! let subs = cachedSubPointersWeakMap.get(obj) if (!subs) { subs = {} cachedSubPointersWeakMap.set(obj, subs) } if (subs[prop]) return subs[prop] const meta = pointerMetaWeakMap.get(obj)! const subPointer = pointer({root: meta.root, path: [...meta.path, prop]}) subs[prop] = subPointer return subPointer }, } export const getPointerMeta = (p: Pointer<$IntentionalAny>): PointerMeta => { const meta: PointerMeta = p[ pointerMetaSymbol as unknown as $IntentionalAny ] as $IntentionalAny return meta } export const getPointerParts = ( p: Pointer<$IntentionalAny>, ): {root: {}; path: PathToProp} => { const {root, path} = getPointerMeta(p) return {root, path} } function pointer({ root, path, }: { root: {} path: Array }): Pointer function pointer(args: {root: {}; path?: Array}) { const meta: PointerMeta = { root: args.root as $IntentionalAny, path: args.path ?? [], } const hiddenObj = {} pointerMetaWeakMap.set(hiddenObj, meta) return new Proxy(hiddenObj, handler) as Pointer<$IntentionalAny> } export default pointer ================================================ FILE: packages/dataverse-experiments/src/setupTestEnv.ts ================================================ export {} ================================================ FILE: packages/dataverse-experiments/src/types.ts ================================================ /** For `any`s that aren't meant to stay `any`*/ export type $FixMe = any /** For `any`s that we don't care about */ export type $IntentionalAny = any export type VoidFn = () => void ================================================ FILE: packages/dataverse-experiments/src/utils/Emitter.test.ts ================================================ import Emitter from './Emitter' describe.skip('dataverse-experiments.Emitter', () => { it('should work', () => { const e: Emitter = new Emitter() e.emit('no one will see this') e.emit('nor this') const tappedEvents: string[] = [] const untap = e.tappable.tap((payload) => { tappedEvents.push(payload) }) e.emit('foo') e.emit('bar') untap() e.emit('baz') expect(tappedEvents).toMatchObject(['foo', 'bar']) }) }) ================================================ FILE: packages/dataverse-experiments/src/utils/Emitter.ts ================================================ import Tappable from './Tappable' type Tapper = (v: V) => void type Untap = () => void export default class Emitter { private _tappers: Map void> private _lastTapperId: number readonly tappable: Tappable private _onNumberOfTappersChangeListener: undefined | ((n: number) => void) constructor() { this._lastTapperId = 0 this._tappers = new Map() this.tappable = new Tappable({ tapToSource: (cb: Tapper) => { return this._tap(cb) }, }) } _tap(cb: Tapper): Untap { const tapperId = this._lastTapperId++ this._tappers.set(tapperId, cb) this._onNumberOfTappersChangeListener && this._onNumberOfTappersChangeListener(this._tappers.size) return () => { this._removeTapperById(tapperId) } } _removeTapperById(id: number) { const oldSize = this._tappers.size this._tappers.delete(id) const newSize = this._tappers.size if (oldSize !== newSize) { this._onNumberOfTappersChangeListener && this._onNumberOfTappersChangeListener(this._tappers.size) } } emit(payload: V) { this._tappers.forEach((cb) => { cb(payload) }) } hasTappers() { return this._tappers.size !== 0 } onNumberOfTappersChange(cb: (n: number) => void) { this._onNumberOfTappersChangeListener = cb } } ================================================ FILE: packages/dataverse-experiments/src/utils/EventEmitter.ts ================================================ import forEach from 'lodash-es/forEach' import without from 'lodash-es/without' import type {$FixMe} from '../types' type Listener = (v: $FixMe) => void /** * A simple barebones event emitter */ export default class EventEmitter { _listenersByType: {[eventName: string]: Array} constructor() { this._listenersByType = {} } addEventListener(eventName: string, listener: Listener) { const listeners = this._listenersByType[eventName] || (this._listenersByType[eventName] = []) listeners.push(listener) return this } removeEventListener(eventName: string, listener: Listener) { const listeners = this._listenersByType[eventName] if (listeners) { const newListeners = without(listeners, listener) if (newListeners.length === 0) { delete this._listenersByType[eventName] } else { this._listenersByType[eventName] = newListeners } } return this } emit(eventName: string, payload: unknown) { const listeners = this.getListenersFor(eventName) if (listeners) { forEach(listeners, (listener) => { listener(payload) }) } } getListenersFor(eventName: string) { return this._listenersByType[eventName] } hasListenersFor(eventName: string) { return this.getListenersFor(eventName) ? true : false } } ================================================ FILE: packages/dataverse-experiments/src/utils/PathBasedReducer.ts ================================================ export type PathBasedReducer = { < A0 extends keyof S, A1 extends keyof S[A0], A2 extends keyof S[A0][A1], A3 extends keyof S[A0][A1][A2], A4 extends keyof S[A0][A1][A2][A3], A5 extends keyof S[A0][A1][A2][A3][A4], A6 extends keyof S[A0][A1][A2][A3][A4][A5], A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6], A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7], A9 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8], A10 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9], >( addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10], reducer: ( d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9][A10], ) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9][A10], ): ReturnType < A0 extends keyof S, A1 extends keyof S[A0], A2 extends keyof S[A0][A1], A3 extends keyof S[A0][A1][A2], A4 extends keyof S[A0][A1][A2][A3], A5 extends keyof S[A0][A1][A2][A3][A4], A6 extends keyof S[A0][A1][A2][A3][A4][A5], A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6], A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7], A9 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8], >( addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9], reducer: ( d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9], ) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9], ): ReturnType < A0 extends keyof S, A1 extends keyof S[A0], A2 extends keyof S[A0][A1], A3 extends keyof S[A0][A1][A2], A4 extends keyof S[A0][A1][A2][A3], A5 extends keyof S[A0][A1][A2][A3][A4], A6 extends keyof S[A0][A1][A2][A3][A4][A5], A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6], A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7], >( addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8], reducer: ( d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8], ) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8], ): ReturnType < A0 extends keyof S, A1 extends keyof S[A0], A2 extends keyof S[A0][A1], A3 extends keyof S[A0][A1][A2], A4 extends keyof S[A0][A1][A2][A3], A5 extends keyof S[A0][A1][A2][A3][A4], A6 extends keyof S[A0][A1][A2][A3][A4][A5], A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6], >( addr: [A0, A1, A2, A3, A4, A5, A6, A7], reducer: ( d: S[A0][A1][A2][A3][A4][A5][A6][A7], ) => S[A0][A1][A2][A3][A4][A5][A6][A7], ): ReturnType < A0 extends keyof S, A1 extends keyof S[A0], A2 extends keyof S[A0][A1], A3 extends keyof S[A0][A1][A2], A4 extends keyof S[A0][A1][A2][A3], A5 extends keyof S[A0][A1][A2][A3][A4], A6 extends keyof S[A0][A1][A2][A3][A4][A5], >( addr: [A0, A1, A2, A3, A4, A5, A6], reducer: ( d: S[A0][A1][A2][A3][A4][A5][A6], ) => S[A0][A1][A2][A3][A4][A5][A6], ): ReturnType < A0 extends keyof S, A1 extends keyof S[A0], A2 extends keyof S[A0][A1], A3 extends keyof S[A0][A1][A2], A4 extends keyof S[A0][A1][A2][A3], A5 extends keyof S[A0][A1][A2][A3][A4], >( addr: [A0, A1, A2, A3, A4, A5], reducer: (d: S[A0][A1][A2][A3][A4][A5]) => S[A0][A1][A2][A3][A4][A5], ): ReturnType < A0 extends keyof S, A1 extends keyof S[A0], A2 extends keyof S[A0][A1], A3 extends keyof S[A0][A1][A2], A4 extends keyof S[A0][A1][A2][A3], >( addr: [A0, A1, A2, A3, A4], reducer: (d: S[A0][A1][A2][A3][A4]) => S[A0][A1][A2][A3][A4], ): ReturnType < A0 extends keyof S, A1 extends keyof S[A0], A2 extends keyof S[A0][A1], A3 extends keyof S[A0][A1][A2], >( addr: [A0, A1, A2, A3], reducer: (d: S[A0][A1][A2][A3]) => S[A0][A1][A2][A3], ): ReturnType ( addr: [A0, A1, A2], reducer: (d: S[A0][A1][A2]) => S[A0][A1][A2], ): ReturnType ( addr: [A0, A1], reducer: (d: S[A0][A1]) => S[A0][A1], ): ReturnType (addr: [A0], reducer: (d: S[A0]) => S[A0]): ReturnType (addr: undefined[], reducer: (d: S) => S): ReturnType } ================================================ FILE: packages/dataverse-experiments/src/utils/Stack.ts ================================================ interface Node { next: undefined | Node data: Data } /** * Just a simple LinkedList */ export default class Stack { _head: undefined | Node constructor() { this._head = undefined } peek() { return this._head && this._head.data } pop() { const head = this._head if (!head) { return undefined } this._head = head.next return head.data } push(data: Data) { const node = {next: this._head, data} this._head = node } } ================================================ FILE: packages/dataverse-experiments/src/utils/Tappable.ts ================================================ type Untap = () => void type UntapFromSource = () => void interface IProps { tapToSource: (cb: (payload: V) => void) => UntapFromSource } type Listener = ((v: V) => void) | (() => void) export default class Tappable { private _props: IProps private _tappers: Map private _untapFromSource: null | UntapFromSource private _lastTapperId: number private _untapFromSourceTimeout: null | NodeJS.Timer = null constructor(props: IProps) { this._lastTapperId = 0 this._untapFromSource = null this._props = props this._tappers = new Map() } private _check() { if (this._untapFromSource) { if (this._tappers.size === 0) { this._scheduleToUntapFromSource() /* * this._untapFromSource() * this._untapFromSource = null */ } } else { if (this._tappers.size !== 0) { this._untapFromSource = this._props.tapToSource(this._cb) } } } private _scheduleToUntapFromSource() { if (this._untapFromSourceTimeout !== null) return this._untapFromSourceTimeout = setTimeout(() => { this._untapFromSourceTimeout = null if (this._tappers.size === 0) { this._untapFromSource!() this._untapFromSource = null } }, 0) } private _cb: any = (arg: any): void => { this._tappers.forEach((cb) => { cb(arg) }) } tap(cb: Listener): Untap { const tapperId = this._lastTapperId++ this._tappers.set(tapperId, cb) this._check() return () => { this._removeTapperById(tapperId) } } /* * tapImmediate(cb: Listener): Untap { * const ret = this.tap(cb) * return ret * } */ private _removeTapperById(id: number) { this._tappers.delete(id) this._check() } // /** // * @deprecated // */ // map(transform: {bivarianceHack(v: V): T}['bivarianceHack']): Tappable { // return new Tappable({ // tapToSource: (cb: (v: T) => void) => { // return this.tap((v: $IntentionalAny) => { // return cb(transform(v)) // }) // }, // }) // } } ================================================ FILE: packages/dataverse-experiments/src/utils/typeTestUtils.ts ================================================ import type {$IntentionalAny} from '../types' /** * Useful in type tests, such as: const a: SomeType = _any */ export const _any: $IntentionalAny = null /** * Useful in typeTests. If you want to ensure that value v follows type V, * just write `expectType(v)` */ export const expectType = (v: T): T => v ================================================ FILE: packages/dataverse-experiments/src/utils/updateDeep.ts ================================================ import type {$FixMe, $IntentionalAny} from '../types' export default function updateDeep( state: S, path: (string | number | undefined)[], reducer: (...args: $IntentionalAny[]) => $IntentionalAny, ): S { if (path.length === 0) return reducer(state) return hoop(state, path as $IntentionalAny, reducer) } const hoop = ( s: $FixMe, path: (string | number)[], reducer: $FixMe, ): $FixMe => { if (path.length === 0) { return reducer(s) } if (Array.isArray(s)) { let [index, ...restOfPath] = path index = parseInt(String(index), 10) if (isNaN(index)) index = 0 const oldVal = s[index] const newVal = hoop(oldVal, restOfPath, reducer) if (oldVal === newVal) return s const newS = [...s] newS.splice(index, 1, newVal) return newS } else if (typeof s === 'object' && s !== null) { const [key, ...restOfPath] = path const oldVal = s[key] const newVal = hoop(oldVal, restOfPath, reducer) if (oldVal === newVal) return s const newS = {...s, [key]: newVal} return newS } else { const [key, ...restOfPath] = path return {[key]: hoop(undefined, restOfPath, reducer)} } } ================================================ FILE: packages/dataverse-experiments/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": ".temp/declarations", "lib": ["ESNext", "DOM"], "rootDir": ".", "types": ["jest", "node"], "composite": true }, "include": ["./src/**/*"] } ================================================ FILE: packages/playground/.gitignore ================================================ /dist /test-results/ /playwright-report/ /build /dist ================================================ FILE: packages/playground/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: packages/playground/README.md ================================================ # The playground The playground is the quickest way to hack on the internals of Theatre. It also hosts our end-to-end tests. It uses a build setup (see the live-reload esbuild server in [./devEnv/build.ts](./devEnv/build.ts)) that builds all the related packages in one go, so you _don't_ have to run a bunch of build commands separately to start developing. ## Directory structure ``` src/ shared/ <---- playgrounds shared with teammates. [playground-name]/ <---- each playground has a name... index.tsx <---- and an entry file. personal/ <---- personal playgrounds (gitignored). [playground-name]/ <---- personal playgrounds also have names, index.tsx <---- and an entry file. tests/ <---- playgrounds for e2e testing. [playground-name]/ <---- the name of the test playground, index.tsx <---- and its entry file. [test-file-name].e2e.ts <---- The playwright test script that tests this particular playground. [test2].e2e.ts <---- We can have more than one test file per playground. ``` ## How to use the playground Simply run `yarn run serve` in this folder to start the dev server. There are some shared playgrounds in `src/shared` which are committed to the repo. You can make your own playgrounds in `src/personal` which will be `.gitignore`d. Note that every playground must include an entry file called `index.tsx` (as you see in the [Directory structure section](#directory-structure)). ## How to write and run end-to-end tests The end-to-end tests are in the `src/tests` folder. Look at [directory structure](#directory-structure) to see how test files are organized. The end-to-end tests are made using [playwright](https://playwright.dev). You should refer to playwright's documentation ```bash $ cd playground $ yarn test # runs the end-to-end tests $ yarn test --project=firefox # only run the tests in firefox $ yarn test --project=firefox --headed # run the test in headed mode in firefox $ yarn test --debug # run in debug mode using the inspector: https://playwright.dev/docs/inspector ``` ### Using playwright codegen To use [playwright's codegen tool](https://playwright.dev/docs/codegen), first serve the playground and then run the codegen on the a url that points to the playground you wish to test: ```bash $ cd playground $ yarn serve # first serve the playground $ yarn playwright codegen http://localhost:8080/tests/[playground-name] # run the codegen for [playground-name] ``` ## Visual regression testing Some `.e2e.ts` files also contain visual regression tetst. These tests run only the the [CI](../../.github/workflows/main.yml) using [Github actions](https://github.com/theatre-js/theatre/actions). Look at the example at [`src/tests/setting-static-props/test.e2e.ts`](src/tests/setting-static-props/test.e2e.ts) for an example of recording and diffing a screenshot. Note that CI runs the visual regression tests in a linux VM, which is bound to produce a slightly different screenshot than a browser on Mac/Windows. Because of that, we have a `docker-compose.yml` file at the root of the repo which you can use to produce a screenshot in a linux vm. Here is how you can use it: ```bash $ cd repo $ docker-compose up -d # start the linux vm $ docker-compose exec -it node bash # ssh into the vm $ cd app $ yarn $ yarn test:e2e:ci ``` If you're submitting a PR that breaks the visual regression tests and you're not familiar with Docker, simply ask the mainainers to update the screenshots for you. ================================================ FILE: packages/playground/devEnv/.gitignore ================================================ build.compiled.js ================================================ FILE: packages/playground/devEnv/playwright-report/index.html ================================================ Playwright Test Report

================================================ FILE: packages/playground/devEnv/playwright.config.ts ================================================ import type {PlaywrightTestConfig} from '@playwright/test' import {devices} from '@playwright/test' const port = 8082 const url = `http://localhost:${port}` /** * Read environment variables from file. * https://github.com/motdotla/dotenv */ // require('dotenv').config(); /** * See https://playwright.dev/docs/test-configuration. */ const config: PlaywrightTestConfig = { testDir: '../src', testMatch: /.*\.e2e\.ts/, /* Maximum time one test can run for. */ timeout: 4000, expect: { // maximum timeout for expect assertions. If longer than the test timeout above, it'll still fail. timeout: 10000, }, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ retries: process.env.CI ? 0 : 0, /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: process.env.CI ? 'github' : 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ // actionTimeout: 200, /* Base URL to use in actions like `await page.goto('/')`. */ // baseURL: 'http://localhost:3000', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', baseURL: url, }, /* Configure projects for major browsers */ projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'], launchOptions: { // args: ["--headless","--no-sandbox","--use-angle=gl"] args: ['--no-sandbox'], }, }, }, // { // name: 'firefox', // use: { // ...devices['Desktop Firefox'], // }, // }, ], /* Folder for test artifacts such as screenshots, videos, traces, etc. */ outputDir: '../test-results/', /* This will serve the playground before running the tests, unless the playground is already running. Note that if the playground is not running but some other server is serving at port 8080, this will fail. TODO 👆 */ webServer: { command: `yarn run serve:ci --port ${port}`, reuseExistingServer: !process.env.CI, url: url, }, } export default config ================================================ FILE: packages/playground/package.json ================================================ { "name": "playground", "version": "1.0.0-dev", "license": "Apache-2.0", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist/**/*" ], "scripts": { "serve": "vite", "serve:ci": "vite build && vite preview", "build": "vite build --force", "build:static": "echo 'building for vercel' && yarn run build", "typecheck": "tsc --noEmit", "test": "playwright test --config=devEnv/playwright.config.ts", "test:ci": "playwright test --reporter=dot --config=devEnv/playwright.config.ts --project=chromium" }, "devDependencies": { "@playwright/test": "^1.36.2", "@react-three/drei": "^9.80.1", "@react-three/fiber": "^8.13.6", "@theatre/core": "workspace:*", "@theatre/dataverse": "workspace:*", "@theatre/r3f": "workspace:*", "@theatre/react": "workspace:*", "@theatre/studio": "workspace:*", "@types/jest": "^26.0.23", "@types/lodash-es": "^4.17.4", "@types/node": "^15.6.2", "@types/react": "^18.2.18", "@types/react-dom": "^18.2.7", "@types/styled-components": "^5.1.26", "@vitejs/plugin-react": "^4.0.0", "@vitejs/plugin-react-swc": "^3.3.2", "dotenv": "^16.3.1", "fast-glob": "^3.3.0", "lodash-es": "^4.17.21", "maath": "^0.10.7", "react": "^18.2.0", "react-dom": "^18.2.0", "styled-components": "^5.3.11", "theatric": "workspace:*", "three": "^0.155.0", "typescript": "5.1.6", "vite": "^4.3.9" } } ================================================ FILE: packages/playground/src/.gitignore ================================================ personal ================================================ FILE: packages/playground/src/home/ItemSectionWithPreviews.tsx ================================================ import React from 'react' import styled from 'styled-components' export const ItemSectionWithPreviews = (props: { groupName: string modules: string[] collapsedByDefault: boolean collapsible: boolean }) => { const {groupName, modules, collapsedByDefault, collapsible} = props console.log(groupName) const [collapsed, setCollapsed] = React.useState( collapsible && collapsedByDefault, ) const toggleCollapse = () => { if (!collapsible) return setCollapsed(!collapsed) } return (
{groupName} {!collapsed && ( {modules.map((moduleName) => { const href = `/${groupName}/${moduleName}/` return (

{moduleName}

{href}

) })}
)}
) } const SectionHeader = styled.h3<{collapsible: boolean}>` font-family: 'Inter', sans-serif; font-style: normal; font-weight: 400; font-size: 16px; line-height: 19px; text-transform: capitalize; /* White/White50 */ color: rgba(255, 255, 255, 0.5); text-decoration: ${({collapsible}) => (collapsible ? 'underline' : 'none')}; cursor: ${({collapsible}) => (collapsible ? 'pointer' : 'default')}; user-select: none; ` const ItemDesc = styled.div` display: flex; flex-direction: column; align-items: flex-start; padding: 8px 12px; gap: 4px; & > h3 { margin: 0; font-family: 'Inter', sans-serif; font-style: normal; font-weight: 600; font-size: 15px; line-height: 18px; /* White/White80 */ color: rgba(255, 255, 255, 0.8); } & > p { margin: 0; font-weight: 400; font-size: 13px; line-height: 16px; /* identical to box height, or 123% */ /* White/White60 */ color: rgba(255, 255, 255, 0.6); } ` const ItemContainer = styled.div` /* display: inline-flex; */ ` const ItemListContainer = styled.div` display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; ` const PreviewContainer = styled.div` --previewHeight: 450px; --previewWidth: 800px; --previewScale: 0.3; position: relative; overflow: hidden; height: calc(var(--previewHeight) * var(--previewScale)); width: calc(var(--previewWidth) * var(--previewScale)); /* Neutral/Neutral800 */ background: rgba(33, 35, 39, 0.9); &::after { content: ''; position: absolute; display: block; z-index: 1; top: 0; left: 0; height: calc(var(--previewHeight) * var(--previewScale)); width: calc(var(--previewWidth) * var(--previewScale)); } iframe { /* don't want original size of iframe affecting layout */ position: absolute; transform-origin: top left; transform: scale(var(--previewScale)); height: var(--previewHeight); width: var(--previewWidth); } ` const ItemLink = styled.a` border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 4px; text-decoration: none; overflow: hidden; display: flex; flex-direction: column; ` ================================================ FILE: packages/playground/src/home/PlaygroundHeader.tsx ================================================ import React from 'react' import styled from 'styled-components' const PlaygroundHeaderContainer = styled.div` /* Auto layout */ display: flex; flex-direction: row; justify-content: space-between; flex-wrap: wrap; padding: 1rem; /* White/White8 */ border-bottom: 1px solid rgba(255, 255, 255, 0.08); ` const PlaygroundHeaderVersion = styled.div` padding: 6px 16px; background: rgba(34, 103, 99, 0.38); border-radius: 30px; font-family: 'Source Code Pro', 'Monaco', monospace; font-style: normal; font-weight: 400; font-size: 13px; line-height: 17px; /* Teal/Teal300 */ color: #64c4bf; ` const HeaderGroup = styled.div` /* Auto layout */ display: flex; flex-direction: row; align-items: flex-start; gap: 12px; ` const HeaderLink = styled.a` font-family: 'Inter', sans-serif; font-style: normal; font-weight: 400; font-size: 16px; line-height: 1; text-decoration: none; display: inline-flex; padding: 0.5rem; /* identical to box height, or 169% */ /* White/White100 */ color: #ffffff; transition: color 0.2s ease-in; &:hover, &:focus { /* Teal/Teal300 */ color: #64c4bf; } ` export function PlaygroundHeader(props: { version?: { displayText: string linkHref?: string } links: { label: string href: string }[] }) { return ( {props.version && ( {props.version.displayText} )} {props.links.map((link) => ( {link.label} ))} ) } const TheatreLogo = () => ( ) ================================================ FILE: packages/playground/src/home/PlaygroundPage.tsx ================================================ import React from 'react' import styled, {StyleSheetManager} from 'styled-components' import {ItemSectionWithPreviews} from './ItemSectionWithPreviews' import {PlaygroundHeader} from './PlaygroundHeader' // @ts-ignore import {version} from '../../../studio/package.json' const HomeContainer = styled.div` position: fixed; inset: 0; background: #1b1c1e; overflow: auto; ` const ContentContainer = styled.div` padding: 0 5rem; @media screen and (max-width: 920px) { padding: 0 2rem; } ` // const {version} = require('') const PageTitleH1 = styled.h1` padding: 1rem 0; ` export const PlaygroundPage = ({ groups, }: { groups: {[groupName: string]: string[]} }) => { return ( Playground {Object.entries(groups).map(([groupName, modules]) => ( ))} ) } ================================================ FILE: packages/playground/src/index.html ================================================ Playground – Theatre.js
================================================ FILE: packages/playground/src/index.tsx ================================================ import {PlaygroundPage} from './home/PlaygroundPage' import ReactDom from 'react-dom/client' import React from 'react' // like [{'./shared/hello/index.html': () => import('./shared/hello/index.html')}] const modules: Record Promise> = ( import.meta as any ).glob('./(shared|personal|tests)/*/index.html') const groups = (Object.keys(modules) as string[]).reduce((acc, path) => { const [_, groupName, moduleName] = path.split('/') if (!acc[groupName]) { acc[groupName] = [] } acc[groupName].push(moduleName) return acc }, {} as {[groupName: string]: string[]}) ReactDom.createRoot(document.getElementById('root')!).render( , ) ================================================ FILE: packages/playground/src/playground-globals.d.ts ================================================ declare module '*.png' { export default string } declare module '*.glb' { export default string } declare module '*.gltf' { export default string } declare module '*.mp3' { export default string } declare module '*.ogg' { export default string } ================================================ FILE: packages/playground/src/public/_redirects ================================================ /* /index.html 200 ================================================ FILE: packages/playground/src/shared/camera/App.tsx ================================================ import { editable as e, RefreshSnapshot, SheetProvider, PerspectiveCamera, } from '@theatre/r3f' import {Stars} from '@react-three/drei' import {getProject} from '@theatre/core' import React, {Suspense, useRef, useState} from 'react' import {Canvas} from '@react-three/fiber' import {useGLTF} from '@react-three/drei' import sceneGLB from './scene.glb' import type {Mesh} from 'three' document.body.style.backgroundColor = '#171717' function Model({url}: {url: string}) { const {nodes} = useGLTF(url) as any return ( ) } function App() { const bgs = ['#272730', '#b7c5d1'] const [bgIndex, setBgIndex] = useState(0) const bg = bgs[bgIndex] const cameraTargetRef = useRef(null!) return (
{ // return setBgIndex((bgIndex) => (bgIndex + 1) % bgs.length) }} style={{ height: '100vh', }} >
) } export default App ================================================ FILE: packages/playground/src/shared/camera/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/camera/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import theatre from '@theatre/core' import extension from '@theatre/r3f/dist/extension' void theatre.getStudio().then((studio) => { studio.extend(extension) }) void theatre.init({serverUrl: 'http://localhost:3000'}) ReactDOM.createRoot(document.getElementById('root')!).render() ================================================ FILE: packages/playground/src/shared/custom-editable-components/App.tsx ================================================ import {editable as e, SheetProvider} from '@theatre/r3f' import {getProject} from '@theatre/core' import React from 'react' import {Canvas} from '@react-three/fiber' const EditablePoints = e('points', 'mesh') function App() { return (
) } export default App ================================================ FILE: packages/playground/src/shared/custom-editable-components/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/custom-editable-components/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import theatre from '@theatre/core' import extension from '@theatre/r3f/dist/extension' void theatre.getStudio().then((studio) => studio.extend(extension)) void theatre.init({studio: true, serverUrl: 'http://localhost:3000'}) ReactDOM.createRoot(document.getElementById('root')!).render() ================================================ FILE: packages/playground/src/shared/custom-raf-driver/App.tsx ================================================ import {editable as e, RafDriverProvider, SheetProvider} from '@theatre/r3f' import type {IRafDriver} from '@theatre/core' import {getProject} from '@theatre/core' import React from 'react' import {Canvas} from '@react-three/fiber' const EditablePoints = e('points', 'mesh') function App(props: {rafDriver: IRafDriver}) { return (
) } export default App ================================================ FILE: packages/playground/src/shared/custom-raf-driver/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/custom-raf-driver/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import theatre from '@theatre/core' import extension from '@theatre/r3f/dist/extension' import {createRafDriver} from '@theatre/core' const rafDriver = createRafDriver({name: 'a custom 5fps raf driver'}) setInterval(() => { rafDriver.tick(performance.now()) }, 200) void theatre.getStudio().then((studio) => studio.extend(extension)) void theatre.init({studio: true, __experimental_rafDriver: rafDriver}) ReactDOM.createRoot(document.getElementById('root')!).render( , ) ================================================ FILE: packages/playground/src/shared/dom/Scene.tsx ================================================ import { getStudioSync} from '@theatre/core' import type {UseDragOpts} from './useDrag' import useDrag from './useDrag' import React, {useLayoutEffect, useMemo, useRef, useState} from 'react' import type { IProject, ISheet, ShorthandCompoundPropsToInitialValue, } from '@theatre/core' import {onChange, types} from '@theatre/core' import type {IScrub, IStudio} from '@theatre/core' const textInterpolate = (left: string, right: string, progression: number) => { if (!left || right.startsWith(left)) { const length = Math.floor( Math.max(0, (right.length - left.length) * progression), ) return left + right.slice(left.length, left.length + length) } return left } const globalConfig = { background: { type: types.stringLiteral('black', { black: 'black', white: 'white', dynamic: 'dynamic', }), dynamic: types.rgba(), }, } const boxObjectConfig = { test: types.string('Hello?', {interpolate: textInterpolate}), testLiteral: types.stringLiteral('a', {a: 'Option A', b: 'Option B'}), bool: types.boolean(false), favoriteFood: types.compound({ name: types.string('Pie'), // if needing more compounds, consider adding weight with different units price: types.compound({ currency: types.stringLiteral('USD', {USD: 'USD', EUR: 'EUR'}), amount: types.number(10, {range: [0, 1000], label: '$'}), }), }), pos: { x: types.number(200), y: types.number(200), }, color: types.rgba({r: 1, g: 0, b: 0, a: 1}), } // this can also be inferred with type _State = ShorthandCompoundPropsToInitialValue type State = { pos: { x: number y: number } test: string testLiteral: string bool: boolean // a compound compound prop favoriteFood: { name: string price: { amount: number currency: string } } color: { r: number g: number b: number a: number } } const Box: React.FC<{ id: string sheet: ISheet selection: IStudio['selection'] }> = ({id, sheet, selection}) => { const defaultConfig = useMemo( () => Object.assign({}, boxObjectConfig, { // give the box initial values offset from each other pos: { x: ((id.codePointAt(0) ?? 0) % 15) * 100, y: ((id.codePointAt(0) ?? 0) % 15) * 100, }, }), [id], ) // This is cheap to call and always returns the same value, so no need for useMemo() const obj = sheet.object(id, defaultConfig) const isSelected = selection.includes(obj) const boxRef = useRef(null!) const preRef = useRef(null!) const colorRef = useRef(null!) useLayoutEffect(() => { const unsubscribeFromChanges = onChange(obj.props, (newValues) => { boxRef.current.style.transform = `translate(${newValues.pos.x}px, ${newValues.pos.y}px)` preRef.current.innerText = JSON.stringify(newValues, null, 2) colorRef.current.style.background = newValues.color.toString() }) return unsubscribeFromChanges }, []) const dragOpts = useMemo((): UseDragOpts => { let scrub: IScrub | undefined let initial: typeof obj.value.pos let firstOnDragCalled = false return { onDragStart() { scrub = getStudioSync()!.scrub() initial = obj.value.pos firstOnDragCalled = false }, onDrag(x, y) { if (!firstOnDragCalled) { getStudioSync()!.setSelection([obj]) firstOnDragCalled = true } scrub!.capture(({set}) => { set(obj.props.pos, { ...initial, x: x + initial.x, y: y + initial.y, }) }) }, onDragEnd(dragHappened) { if (dragHappened) { scrub!.commit() } else { scrub!.discard() } }, lockCursorTo: 'move', } }, []) useDrag(boxRef.current, dragOpts) return (
{ getStudioSync()!.setSelection([obj]) }} ref={boxRef} style={{ width: 300, height: 300, color: 'white', position: 'absolute', boxSizing: 'border-box', border: isSelected ? '1px solid #5a92fa' : '1px solid white', }} >

      
) } let lastBoxId = 1 export const Scene: React.FC<{project: IProject}> = ({project}) => { const [boxes, setBoxes] = useState>(['0', '1']) // This is cheap to call and always returns the same value, so no need for useMemo() const sheet = project.sheet('Scene', 'default') const [selection, setSelection] = useState() useLayoutEffect(() => { return getStudioSync()!.onSelectionChange((newState) => { setSelection(newState) }) }, []) const containerRef = useRef(null!) const globalObj = sheet.object('global', globalConfig) useLayoutEffect(() => { const unsubscribeFromChanges = onChange(globalObj.props, (newValues) => { containerRef.current.style.background = newValues.background.type !== 'dynamic' ? newValues.background.type : newValues.background.dynamic.toString() }) return unsubscribeFromChanges }, [globalObj]) return (
{boxes.map((id) => ( ))}
) } ================================================ FILE: packages/playground/src/shared/dom/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/dom/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import theatre from '@theatre/core' import {getProject} from '@theatre/core' import {Scene} from './Scene' /** * This is a basic example of using Theatre.js for manipulating the DOM. * * It also uses {@link IStudio.selection | studio.selection} to customize * the selection behavior. */ void theatre.init({studio: true}) ReactDOM.createRoot(document.getElementById('root')!).render( , ) ================================================ FILE: packages/playground/src/shared/dom/useDrag.ts ================================================ import {useLayoutEffect, useRef} from 'react' const noop = () => {} function createCursorLock(cursor: string) { const el = document.createElement('div') el.style.cssText = ` position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 9999999;` el.style.cursor = cursor document.body.appendChild(el) const relinquish = () => { document.body.removeChild(el) } return relinquish } export type UseDragOpts = { disabled?: boolean dontBlockMouseDown?: boolean lockCursorTo?: string onDragStart?: (event: MouseEvent) => void | false onDragEnd?: (dragHappened: boolean) => void onDrag: (dx: number, dy: number, event: MouseEvent) => void } export default function useDrag( target: HTMLElement | undefined | null, opts: UseDragOpts, ) { const optsRef = useRef(opts) optsRef.current = opts const modeRef = useRef<'dragStartCalled' | 'dragging' | 'notDragging'>( 'notDragging', ) const stateRef = useRef<{ dragHappened: boolean startPos: { x: number y: number } }>({dragHappened: false, startPos: {x: 0, y: 0}}) useLayoutEffect(() => { if (!target) return const getDistances = (event: MouseEvent): [number, number] => { const {startPos} = stateRef.current return [event.screenX - startPos.x, event.screenY - startPos.y] } let relinquishCursorLock = noop const dragHandler = (event: MouseEvent) => { if (!stateRef.current.dragHappened && optsRef.current.lockCursorTo) { relinquishCursorLock = createCursorLock(optsRef.current.lockCursorTo) } if (!stateRef.current.dragHappened) stateRef.current.dragHappened = true modeRef.current = 'dragging' const deltas = getDistances(event) optsRef.current.onDrag(deltas[0], deltas[1], event) } const dragEndHandler = () => { removeDragListeners() modeRef.current = 'notDragging' optsRef.current.onDragEnd && optsRef.current.onDragEnd(stateRef.current.dragHappened) relinquishCursorLock() relinquishCursorLock = noop } const addDragListeners = () => { document.addEventListener('mousemove', dragHandler) document.addEventListener('mouseup', dragEndHandler) } const removeDragListeners = () => { document.removeEventListener('mousemove', dragHandler) document.removeEventListener('mouseup', dragEndHandler) } const preventUnwantedClick = (event: MouseEvent) => { if (optsRef.current.disabled) return if (stateRef.current.dragHappened) { if ( !optsRef.current.dontBlockMouseDown && modeRef.current !== 'notDragging' ) { event.stopPropagation() event.preventDefault() } stateRef.current.dragHappened = false } } const dragStartHandler = (event: MouseEvent) => { const opts = optsRef.current if (opts.disabled === true) return if (event.button !== 0) return const resultOfStart = opts.onDragStart && opts.onDragStart(event) if (resultOfStart === false) return if (!opts.dontBlockMouseDown) { event.stopPropagation() event.preventDefault() } modeRef.current = 'dragStartCalled' const {screenX, screenY} = event stateRef.current.startPos = {x: screenX, y: screenY} stateRef.current.dragHappened = false addDragListeners() } const onMouseDown = (e: MouseEvent) => { dragStartHandler(e) } target.addEventListener('mousedown', onMouseDown) target.addEventListener('click', preventUnwantedClick) return () => { removeDragListeners() target.removeEventListener('mousedown', onMouseDown) target.removeEventListener('click', preventUnwantedClick) relinquishCursorLock() if (modeRef.current !== 'notDragging') { optsRef.current.onDragEnd && optsRef.current.onDragEnd(modeRef.current === 'dragging') } modeRef.current = 'notDragging' } }, [target]) } ================================================ FILE: packages/playground/src/shared/dom-basic/Box3D.tsx ================================================ import React, {useEffect, useRef} from 'react' import type {CSSProperties} from 'react' import {types} from '@theatre/core' import type {ISheet} from '@theatre/core' // Box element export const BoxSize = 100 const Box3DCSS: CSSProperties = { border: '1px solid #999', position: 'absolute', width: `${BoxSize}px`, height: `${BoxSize}px`, } const Box3DTextCSS: CSSProperties = { margin: '0', padding: '0', position: 'absolute', left: '50%', top: '50%', transform: 'translate(-50%, -50%)', textAlign: 'center', width: '100%', } export const Box3D: React.FC<{ sheet: ISheet name: string x: number y: number }> = ({sheet, name, x, y}) => { const elementRef = useRef(null) // Animation useEffect(() => { const element = elementRef.current! const sheetObj = sheet.object(`Box - ${name}`, { background: types.rgba({r: 16 / 255, g: 16 / 255, b: 16 / 255, a: 1}), opacity: types.number(1, {range: [0, 1]}), position: { x: x, y: y, z: 0, }, rotation: { x: types.number(0, {range: [-360, 360]}), y: types.number(0, {range: [-360, 360]}), z: types.number(0, {range: [-360, 360]}), }, scale: { x: 1, y: 1, z: 1, }, }) const unsubscribe = sheetObj.onValuesChange((values: any) => { const {background, opacity, position, rotation, scale} = values element.style.backgroundColor = `rgba(${background.r * 255}, ${ background.g * 255 }, ${background.b * 255}, 1)` element.style.opacity = opacity const translate3D = `translate3d(${position.x}px, ${position.y}px, ${position.z}px)` const rotate3D = `rotateX(${rotation.x}deg) rotateY(${rotation.y}deg) rotateZ(${rotation.z}deg)` const scale3D = `scaleX(${scale.x}) scaleY(${scale.y}) scaleZ(${scale.z})` const transform = `${scale3D} ${translate3D} ${rotate3D}` element.style.transform = transform }) return () => { unsubscribe() } }, []) return (
{name}
) } ================================================ FILE: packages/playground/src/shared/dom-basic/Scene.tsx ================================================ import React, {useEffect, useRef} from 'react' import type {CSSProperties} from 'react' import {types} from '@theatre/core' import type {IProject} from '@theatre/core' import {Box3D, BoxSize} from './Box3D' // Scene const SceneCSS: CSSProperties = { overflow: 'hidden', position: 'absolute', left: '0', right: '0', top: '0', bottom: '0', } export const Scene: React.FC<{project: IProject}> = ({project}) => { const containerRef = useRef(null!) const sheet = project.sheet('DOM') useEffect(() => { const container = containerRef.current! const sheetObj = sheet.object('Container', { perspective: types.number( Math.max(window.innerWidth, window.innerHeight), {range: [0, 2000]}, ), originX: types.number(50, {range: [0, 100]}), originY: types.number(50, {range: [0, 100]}), }) const unsubscribe = sheetObj.onValuesChange((values: any) => { container.style.perspective = `${values.perspective}px` container.style.perspectiveOrigin = `${values.originX}% ${values.originY}%` }) return () => { unsubscribe() } }, []) const padding = 100 const right = window.innerWidth - padding - BoxSize const bottom = window.innerHeight - padding - BoxSize return (
) } ================================================ FILE: packages/playground/src/shared/dom-basic/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/dom-basic/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import theatre from '@theatre/core' import {getProject} from '@theatre/core' import {Scene} from './Scene' /** * This is a basic example of using Theatre.js for manipulating the DOM. */ void theatre.init({studio: true}) ReactDOM.createRoot(document.getElementById('root')!).render( , ) ================================================ FILE: packages/playground/src/shared/file/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/file/index.tsx ================================================ import {getProject, types} from '@theatre/core' import theatre from '@theatre/core' import React, {useEffect, useState} from 'react' import ReactDom from 'react-dom/client' import styled from 'styled-components' const project = getProject('Image type playground', { assets: { baseUrl: '/', }, }) void theatre.init({studio: true}) const sheet = project.sheet('Image type') const Wrapper = styled.div` position: absolute; inset: 0; display: flex; justify-content: center; align-items: center; ` const FileTypeExample: React.FC<{}> = (props) => { const [fileUrl, setFileUrl] = useState() useEffect(() => { const object = sheet.object('File holder', { f: types.file('', { label: 'The OBJ', }), }) object.onValuesChange(({f}) => { setFileUrl(project.getAssetUrl(f)) }) return () => { sheet.detachObject('canvas') } }, []) return File url is: {fileUrl} } project.ready .then(() => { ReactDom.createRoot(document.getElementById('root')!).render( , ) }) .catch((err) => { console.error(err) }) ================================================ FILE: packages/playground/src/shared/hello-world-extension/App.tsx ================================================ import {editable as e, SheetProvider} from '@theatre/r3f' import {Stars, TorusKnot} from '@react-three/drei' import {getProject, onChange} from '@theatre/core' import React from 'react' import {Canvas} from '@react-three/fiber' function App() { const sheet = getProject('Space').sheet('Scene') onChange(sheet.sequence.pointer, (a) => { console.log('gasp!!', a) }) return (
{ // return setBgIndex((bgIndex) => (bgIndex + 1) % bgs.length) }} style={{ height: '100vh', }} >
) } export default App ================================================ FILE: packages/playground/src/shared/hello-world-extension/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/hello-world-extension/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import theatre from '@theatre/core' import extension from '@theatre/r3f/dist/extension' void theatre.getStudio().then((studio) => { studio.extend(extension) studio.extend({ id: '@theatre/hello-world-extension', toolbars: { global(set, studio) { let switchValue = 'mobile' const updateToolset = () => set([ { type: 'Switch', value: switchValue, onChange: (value) => { switchValue = value updateToolset() }, options: [ { value: 'mobile', label: 'view mobile version', svgSource: '😀', }, { value: 'desktop', label: 'view desktop version', svgSource: '🪢', }, ], }, { type: 'Icon', title: 'Example Icon', svgSource: '👁‍🗨', onClick: () => { studio.createPane('example') }, }, { type: 'Flyout', label: '🫠', items: [ { label: 'Item 1', onClick: () => { console.log('Item 1 clicked') }, }, { label: 'Item 2', onClick: () => { console.log('Item 2 clicked') }, }, { label: 'Item 3', onClick: () => { console.log('Item 3 clicked') }, }, { label: 'Item 4', onClick: () => { console.log('Item 4 clicked') }, }, ], }, ]) updateToolset() return () => { // remove any listeners if necessary when the extension is unloaded } }, }, panes: [ { class: 'example', mount: ({paneId, node}) => { studio.ui.renderToolset('global', node) return () => {} }, }, ], }) }) void theatre.init({studio: true}) ReactDOM.createRoot(document.getElementById('root')!).render() ================================================ FILE: packages/playground/src/shared/hello-world-extension-dataverse/App.tsx ================================================ import {editable as e, SheetProvider} from '@theatre/r3f' import {Stars, TorusKnot} from '@react-three/drei' import {getProject} from '@theatre/core' import React from 'react' import {Canvas} from '@react-three/fiber' function App() { return (
{ // return setBgIndex((bgIndex) => (bgIndex + 1) % bgs.length) }} style={{ height: '100vh', }} >
) } export default App ================================================ FILE: packages/playground/src/shared/hello-world-extension-dataverse/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/hello-world-extension-dataverse/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import type {ToolsetConfig} from '@theatre/core' import theatre from '@theatre/core' import extension from '@theatre/r3f/dist/extension' import {Atom, prism, val} from '@theatre/dataverse' import {onChange} from '@theatre/core' /** * Let's take a look at how we can use `prism`, `Ticker`, and `val` from Theatre.js's Dataverse library * to create a switch with state that is updated automatically, * and is even stored in a Theatre.js object. * * Without going into the details of `prism`, `Ticker`, and `val`, note that by wrapping our `ToolsetConfig` in a prism, our * ```ts * ... .onChange(Ticker.raf, (toolset) => { * set(toolset) * }, true) * ``` * code will be called whenever `val(obj.props.exampleProp)` changes (whenever the user clicks the switch and the `onChange` callback is called). * This will ensure that our switch's value matches its state and is reflected in the UI via `set(toolset)`. */ void theatre.getStudio().then((studio) => { studio.extend(extension) studio.extend({ id: '@theatre/hello-world-extension', toolbars: { global(set, studio) { const exampleBox = new Atom('mobile') const untapFn = onChange( prism(() => [ { type: 'Switch', value: val(exampleBox.prism), onChange: (value) => exampleBox.set(value), options: [ { value: 'mobile', label: 'view mobile version', svgSource: '😀', }, { value: 'desktop', label: 'view desktop version', svgSource: '🪢', }, ], }, { type: 'Icon', title: 'Example Icon', svgSource: '👁‍🗨', onClick: () => { console.log('hello') }, }, ]), (value) => { set(value) }, ) // listen to changes to this prism using the requestAnimationFrame shared ticker return untapFn }, }, panes: [], }) }) void theatre.init({studio: true}) ReactDOM.createRoot(document.getElementById('root')!).render() ================================================ FILE: packages/playground/src/shared/hello-world-extension-using-sheet-object/App.tsx ================================================ import {editable as e, SheetProvider} from '@theatre/r3f' import {Stars, TorusKnot} from '@react-three/drei' import {getProject} from '@theatre/core' import React from 'react' import {Canvas} from '@react-three/fiber' function App() { return (
{ // return setBgIndex((bgIndex) => (bgIndex + 1) % bgs.length) }} style={{ height: '100vh', }} >
) } export default App ================================================ FILE: packages/playground/src/shared/hello-world-extension-using-sheet-object/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/hello-world-extension-using-sheet-object/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import type {ISheetObject} from '@theatre/core' import {onChange, types, val} from '@theatre/core' import theatre from '@theatre/core' import extension from '@theatre/r3f/dist/extension' const dataConfig = { exampleProp: types.stringLiteral('yes', { no: 'no', yes: 'yes', }), } void theatre.getStudio().then((studio) => { studio.extend(extension) studio.extend({ id: '@theatre/hello-world-extension', toolbars: { global(set, studio) { // A sheet object used by this extension const obj: ISheetObject = studio .getStudioProject() .sheet('example extension UI') .object('editor', dataConfig) const updateToolset = () => set([ { type: 'Switch', value: val(obj.props.exampleProp), onChange: (value) => studio.transaction(({set}) => set(obj.props.exampleProp, value), ), options: [ { value: 'no', label: 'say no', svgSource: '👎', }, { value: 'yes', label: 'say yes', svgSource: '👍', }, ], }, ]) const untapFn = onChange(obj.props.exampleProp, () => { updateToolset() }) // initial update updateToolset() return untapFn }, }, panes: [], }) }) void theatre.init({studio: true}) ReactDOM.createRoot(document.getElementById('root')!).render() ================================================ FILE: packages/playground/src/shared/image/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/image/index.tsx ================================================ import {getProject, types} from '@theatre/core' import theatre from '@theatre/core' import React, {useEffect, useState} from 'react' import ReactDom from 'react-dom/client' import styled from 'styled-components' const project = getProject('Image type playground', { assets: { baseUrl: '/', }, }) void theatre.init({studio: true}) const sheet = project.sheet('Image type') const Wrapper = styled.div` position: absolute; inset: 0; display: flex; justify-content: center; align-items: center; ` const ImageTypeExample: React.FC<{}> = (props) => { const [imageUrl, setImageUrl] = useState() useEffect(() => { const object = sheet.object('image', { image: types.image('', { label: 'texture', }), image2: types.image('', { label: 'another texture', }), // audio: types.__genericAsset(''), something: 'asdf', color: types.rgba(), }) object.onValuesChange(({image}) => { setImageUrl(project.getAssetUrl(image)) }) return () => { sheet.detachObject('canvas') } }, []) return ( { if (sheet.sequence.position === 0) { sheet.sequence.position = 0 void sheet.sequence.play() } else { sheet.sequence.position = 0 } }} > ) } project.ready .then(() => { ReactDom.createRoot(document.getElementById('root')!).render( , ) }) .catch((err) => { console.error(err) }) ================================================ FILE: packages/playground/src/shared/instances/App.tsx ================================================ import {editable as e, RefreshSnapshot, SheetProvider} from '@theatre/r3f' import {Stars} from '@react-three/drei' import {getProject} from '@theatre/core' import React, {Suspense, useState} from 'react' import {Canvas} from '@react-three/fiber' import {useGLTF, PerspectiveCamera} from '@react-three/drei' import sceneGLB from './scene.glb' document.body.style.backgroundColor = '#171717' const EditableCamera = e(PerspectiveCamera, 'perspectiveCamera') function Model({ url, instance, ...props }: {url: string; instance?: string} & JSX.IntrinsicElements['group']) { const {nodes} = useGLTF(url) as any return ( ) } function App() { const bgs = ['#272730', '#b7c5d1'] const [bgIndex, setBgIndex] = useState(0) const bg = bgs[bgIndex] return (
{ // return setBgIndex((bgIndex) => (bgIndex + 1) % bgs.length) }} style={{ height: '100vh', }} >
) } export default App ================================================ FILE: packages/playground/src/shared/instances/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/instances/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import theatre from '@theatre/core' import extension from '@theatre/r3f/dist/extension' void theatre.getStudio().then((studio) => studio.extend(extension)) void theatre.init({studio: true}) ReactDOM.createRoot(document.getElementById('root')!).render( , ) ================================================ FILE: packages/playground/src/shared/notifications/Scene.tsx ================================================ import React, {useLayoutEffect, useRef} from 'react' import type {IProject} from '@theatre/core' import {onChange, types} from '@theatre/core' const globalConfig = { background: { type: types.stringLiteral('black', { black: 'black', white: 'white', dynamic: 'dynamic', }), dynamic: types.rgba(), }, } export const Scene: React.FC<{project: IProject}> = ({project}) => { // This is cheap to call and always returns the same value, so no need for useMemo() const sheet = project.sheet('Scene', 'default') const containerRef = useRef(null!) const globalObj = sheet.object('global', globalConfig) useLayoutEffect(() => { const unsubscribeFromChanges = onChange(globalObj.props, (newValues) => { containerRef.current.style.background = newValues.background.type !== 'dynamic' ? newValues.background.type : newValues.background.dynamic.toString() }) return unsubscribeFromChanges }, [globalObj]) return (
) } ================================================ FILE: packages/playground/src/shared/notifications/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/notifications/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import theatre from '@theatre/core' import {getProject, notify} from '@theatre/core' import {Scene} from './Scene' void theatre.init({studio: true}) // trigger warning notification void getProject('Sample project').sheet('Scene').sequence.play() // fire an info notification notify.info( 'Welcome to the notifications playground!', 'This is a basic example of a notification! You can see the code for this notification ' + '(and all others) at the start of index.tsx. You can also see examples of success and warnign notifications.', ) void getProject('Sample project').ready.then(() => { // fire a success notification on project load notify.success( 'Project loaded!', 'Now you can start calling `sequence.play()` to trigger animations. ;)', ) }) ReactDOM.createRoot(document.getElementById('root')!).render( , ) ================================================ FILE: packages/playground/src/shared/notifications/useDrag.ts ================================================ import {useLayoutEffect, useRef} from 'react' const noop = () => {} function createCursorLock(cursor: string) { const el = document.createElement('div') el.style.cssText = ` position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 9999999;` el.style.cursor = cursor document.body.appendChild(el) const relinquish = () => { document.body.removeChild(el) } return relinquish } export type UseDragOpts = { disabled?: boolean dontBlockMouseDown?: boolean lockCursorTo?: string onDragStart?: (event: MouseEvent) => void | false onDragEnd?: (dragHappened: boolean) => void onDrag: (dx: number, dy: number, event: MouseEvent) => void } export default function useDrag( target: HTMLElement | undefined | null, opts: UseDragOpts, ) { const optsRef = useRef(opts) optsRef.current = opts const modeRef = useRef<'dragStartCalled' | 'dragging' | 'notDragging'>( 'notDragging', ) const stateRef = useRef<{ dragHappened: boolean startPos: { x: number y: number } }>({dragHappened: false, startPos: {x: 0, y: 0}}) useLayoutEffect(() => { if (!target) return const getDistances = (event: MouseEvent): [number, number] => { const {startPos} = stateRef.current return [event.screenX - startPos.x, event.screenY - startPos.y] } let relinquishCursorLock = noop const dragHandler = (event: MouseEvent) => { if (!stateRef.current.dragHappened && optsRef.current.lockCursorTo) { relinquishCursorLock = createCursorLock(optsRef.current.lockCursorTo) } if (!stateRef.current.dragHappened) stateRef.current.dragHappened = true modeRef.current = 'dragging' const deltas = getDistances(event) optsRef.current.onDrag(deltas[0], deltas[1], event) } const dragEndHandler = () => { removeDragListeners() modeRef.current = 'notDragging' optsRef.current.onDragEnd && optsRef.current.onDragEnd(stateRef.current.dragHappened) relinquishCursorLock() relinquishCursorLock = noop } const addDragListeners = () => { document.addEventListener('mousemove', dragHandler) document.addEventListener('mouseup', dragEndHandler) } const removeDragListeners = () => { document.removeEventListener('mousemove', dragHandler) document.removeEventListener('mouseup', dragEndHandler) } const preventUnwantedClick = (event: MouseEvent) => { if (optsRef.current.disabled) return if (stateRef.current.dragHappened) { if ( !optsRef.current.dontBlockMouseDown && modeRef.current !== 'notDragging' ) { event.stopPropagation() event.preventDefault() } stateRef.current.dragHappened = false } } const dragStartHandler = (event: MouseEvent) => { const opts = optsRef.current if (opts.disabled === true) return if (event.button !== 0) return const resultOfStart = opts.onDragStart && opts.onDragStart(event) if (resultOfStart === false) return if (!opts.dontBlockMouseDown) { event.stopPropagation() event.preventDefault() } modeRef.current = 'dragStartCalled' const {screenX, screenY} = event stateRef.current.startPos = {x: screenX, y: screenY} stateRef.current.dragHappened = false addDragListeners() } const onMouseDown = (e: MouseEvent) => { dragStartHandler(e) } target.addEventListener('mousedown', onMouseDown) target.addEventListener('click', preventUnwantedClick) return () => { removeDragListeners() target.removeEventListener('mousedown', onMouseDown) target.removeEventListener('click', preventUnwantedClick) relinquishCursorLock() if (modeRef.current !== 'notDragging') { optsRef.current.onDragEnd && optsRef.current.onDragEnd(modeRef.current === 'dragging') } modeRef.current = 'notDragging' } }, [target]) } ================================================ FILE: packages/playground/src/shared/r3f-rocket/App.tsx ================================================ import {editable as e, RefreshSnapshot, SheetProvider} from '@theatre/r3f' import {Stars} from '@react-three/drei' import {getProject} from '@theatre/core' import React, {Suspense, useState} from 'react' import {Canvas} from '@react-three/fiber' import {useGLTF, PerspectiveCamera} from '@react-three/drei' import sceneGLB from './scene.glb' document.body.style.backgroundColor = '#171717' const EditableCamera = e(PerspectiveCamera, 'perspectiveCamera') function Model({url}: {url: string}) { const {nodes} = useGLTF(url) as any return ( ) } function App() { const bgs = ['#272730', '#b7c5d1'] const [bgIndex, setBgIndex] = useState(0) const bg = bgs[bgIndex] return (
{ // return setBgIndex((bgIndex) => (bgIndex + 1) % bgs.length) }} style={{ height: '100vh', }} >
) } export default App ================================================ FILE: packages/playground/src/shared/r3f-rocket/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/r3f-rocket/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import theatre from '@theatre/core' import extension from '@theatre/r3f/dist/extension' void theatre.getStudio().then((studio) => studio.extend(extension)) void theatre.init({studio: true}) ReactDOM.createRoot(document.getElementById('root')!).render() ================================================ FILE: packages/playground/src/shared/remote/Box3D.tsx ================================================ import React, {useEffect, useRef} from 'react' import type {CSSProperties} from 'react' import {types} from '@theatre/core' import type {ISheet} from '@theatre/core' import {remote} from './Remote' // Box element export const BoxSize = 100 const Box3DCSS: CSSProperties = { border: '1px solid #999', position: 'absolute', width: `${BoxSize}px`, height: `${BoxSize}px`, } const Box3DTextCSS: CSSProperties = { margin: '0', padding: '0', position: 'absolute', left: '50%', top: '50%', transform: 'translate(-50%, -50%)', textAlign: 'center', width: '100%', } export const Box3D: React.FC<{ sheet: ISheet name: string x: number y: number }> = ({sheet, name, x, y}) => { const elementRef = useRef(null) // Animation useEffect(() => { const element = elementRef.current! const id = `Box - ${name}` const sheetObj = remote.sheetObject( sheet.address.sheetId, id, { background: types.rgba({r: 16 / 255, g: 16 / 255, b: 16 / 255, a: 1}), opacity: types.number(1, {range: [0, 1]}), position: { x: x, y: y, z: 0, }, rotation: { x: types.number(0, {range: [-360, 360]}), y: types.number(0, {range: [-360, 360]}), z: types.number(0, {range: [-360, 360]}), }, scale: { x: 1, y: 1, z: 1, }, }, (values: any) => { const {background, opacity, position, rotation, scale} = values element.style.backgroundColor = `rgba(${background.r * 255}, ${ background.g * 255 }, ${background.b * 255}, 1)` element.style.opacity = opacity const translate3D = `translate3d(${position.x}px, ${position.y}px, ${position.z}px)` const rotate3D = `rotateX(${rotation.x}deg) rotateY(${rotation.y}deg) rotateZ(${rotation.z}deg)` const scale3D = `scaleX(${scale.x}) scaleY(${scale.y}) scaleZ(${scale.z})` const transform = `${scale3D} ${translate3D} ${rotate3D}` element.style.transform = transform }, ) return () => { if (sheetObj !== undefined) remote.unsubscribe(sheetObj) } }, []) return (
{name}
) } ================================================ FILE: packages/playground/src/shared/remote/Remote.ts ================================================ import type {IProject, ISheet, ISheetObject} from '@theatre/core' import type {VoidFn} from '@theatre/dataverse/src/types' export type TheatreUpdateCallback = (data: any) => void export type BroadcastDataEvent = | 'setSheet' | 'setSheetObject' | 'updateSheetObject' | 'updateTimeline' export interface BroadcastData { event: BroadcastDataEvent data: any } export type BroadcastCallback = (data: BroadcastData) => void // Default SheetObject.onValuesChange callback const noop: TheatreUpdateCallback = (values: any) => {} function isColor(obj: any) { return ( obj.r !== undefined && obj.g !== undefined && obj.b !== undefined && obj.a !== undefined ) } // Which hashtag to add to the URL const hashtag = 'editor' class RemoteSingleton { // Remote mode = 'listener' channel: BroadcastChannel // Theatre project!: IProject sheets: Map = new Map() sheetObjects: Map = new Map() sheetObjectCBs: Map = new Map() sheetObjectUnsubscribe: Map = new Map() constructor() { this.channel = new BroadcastChannel('theatre') this.showTheatre = document.location.hash.search(hashtag) > -1 } // Remote send(data: BroadcastData) { if (this.mode === 'theatre') { this.channel.postMessage(data) } } listen(callback: BroadcastCallback) { if (this.mode === 'listener') { this.channel.onmessage = (event: MessageEvent) => { callback(event.data) } } } // Theatre sheet(name: string): ISheet { let sheet: any = this.sheets.get(name) if (sheet !== undefined) return sheet sheet = this.project.sheet(name) this.sheets.set(name, sheet) return sheet } sheetObject( sheetName: string, key: string, props: any, onUpdate?: TheatreUpdateCallback, ): ISheetObject | undefined { const sheet = this.sheets.get(sheetName) if (sheet === undefined) return undefined const objName = `${sheetName}_${key}` let obj = this.sheetObjects.get(objName) if (obj !== undefined) { obj = sheet.object(key, {...props, ...obj.value}, {reconfigure: true}) return obj } obj = sheet.object(key, props) this.sheetObjects.set(objName, obj) this.sheetObjectCBs.set(objName, onUpdate !== undefined ? onUpdate : noop) const unsubscribe = obj.onValuesChange((values: any) => { if (this.showTheatre) { for (let i in values) { const value = values[i] if (typeof value === 'object') { if (isColor(value)) { values[i] = { r: value.r, g: value.g, b: value.b, a: value.a, } } } } this.send({ event: 'updateSheetObject', data: { sheetObject: objName, values: values, }, }) } else { const callback = this.sheetObjectCBs.get(objName) if (callback !== undefined) callback(values) } }) this.sheetObjectUnsubscribe.set(objName, unsubscribe) return obj } unsubscribe(sheet: ISheetObject) { const id = `${sheet.address.sheetId}_${sheet.address.objectKey}` const unsubscribe = this.sheetObjectUnsubscribe.get(id) if (unsubscribe !== undefined) { unsubscribe() } } // Getters / Setters get showTheatre(): boolean { return this.mode === 'theatre' } set showTheatre(value: boolean) { if (value) { this.mode = 'theatre' document.title += ' - Editor' } } } export const remote = new RemoteSingleton() ================================================ FILE: packages/playground/src/shared/remote/RemoteController.ts ================================================ import type {IProject, ISheet} from '@theatre/core' import {getStudioSync} from '@theatre/core' import {remote} from './Remote' import type {BroadcastData, BroadcastDataEvent} from './Remote' /** * Handles the communication between windows */ export default function RemoteController(project: IProject) { let activeSheet: ISheet | undefined = undefined remote.project = project /** * Editor is hidden, this window receives updates */ const receiveRemote = () => { const studio = getStudioSync()! studio.ui.hide() remote.listen((msg: BroadcastData) => { switch (msg.event) { case 'setSheet': const sheet = remote.sheets.get(msg.data.sheet) if (sheet !== undefined) { activeSheet = sheet studio.setSelection([sheet]) } break case 'setSheetObject': const sheetObj = remote.sheetObjects.get( `${msg.data.sheet}_${msg.data.key}`, ) if (sheetObj !== undefined) { studio.setSelection([sheetObj]) } break case 'updateSheetObject': const sheetObjCB = remote.sheetObjectCBs.get(msg.data.sheetObject) if (sheetObjCB !== undefined) sheetObjCB(msg.data.values) break case 'updateTimeline': activeSheet = remote.sheets.get(msg.data.sheet) if (activeSheet !== undefined) { activeSheet.sequence.position = msg.data.position } break } }) } /** * Editor is visible, this window sends updates */ const sendRemote = () => { const studio = getStudioSync()! studio.ui.restore() studio.onSelectionChange((value: any[]) => { if (value.length < 1) return value.forEach((obj: any) => { let id = obj.address.sheetId let type: BroadcastDataEvent = 'setSheet' let data = {} switch (obj.type) { case 'Theatre_Sheet_PublicAPI': type = 'setSheet' data = { sheet: obj.address.sheetId, } activeSheet = remote.sheets.get(obj.address.sheetId) break case 'Theatre_SheetObject_PublicAPI': type = 'setSheetObject' id += `_${obj.address.objectKey}` data = { sheet: obj.address.sheetId, key: obj.address.objectKey, } break } remote.send({event: type, data: data}) }) }) // Timeline let position = 0 const onRafUpdate = () => { if ( activeSheet !== undefined && position !== activeSheet.sequence.position ) { position = activeSheet.sequence.position const t = activeSheet as ISheet remote.send({ event: 'updateTimeline', data: { position: position, sheet: t.address.sheetId, }, }) } } const onRaf = () => { onRafUpdate() requestAnimationFrame(onRaf) } onRafUpdate() // Initial position onRaf() } if (remote.showTheatre) { sendRemote() } else { receiveRemote() } } ================================================ FILE: packages/playground/src/shared/remote/Scene.tsx ================================================ import React, {useEffect, useRef} from 'react' import type {CSSProperties} from 'react' import {types} from '@theatre/core' import {Box3D} from './Box3D' import {remote} from './Remote' // Scene const SceneCSS: CSSProperties = { overflow: 'hidden', position: 'absolute', left: '0', right: '0', top: '0', bottom: '0', } export const Scene: React.FC<{}> = ({}) => { const containerRef = useRef(null!) const sheet = remote.sheet('DOM') useEffect(() => { const container = containerRef.current! const sheetObj = remote.sheetObject( 'DOM', 'Container', { perspective: types.number( Math.max(window.innerWidth, window.innerHeight), {range: [0, 2000]}, ), originX: types.number(50, {range: [0, 100]}), originY: types.number(50, {range: [0, 100]}), }, (values: any) => { container.style.perspective = `${values.perspective}px` container.style.perspectiveOrigin = `${values.originX}% ${values.originY}%` }, ) return () => { if (sheetObj !== undefined) remote.unsubscribe(sheetObj) } }, []) if (remote.showTheatre) { SceneCSS.display = 'none' SceneCSS.visibility = 'hidden' } return (
) } ================================================ FILE: packages/playground/src/shared/remote/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/remote/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import theatre from '@theatre/core' import {getProject} from '@theatre/core' import {Scene} from './Scene' import RemoteController from './RemoteController' const project = getProject('Sample project') void theatre.init({studio: true}) RemoteController(project) ReactDOM.createRoot(document.getElementById('root')!).render() ================================================ FILE: packages/playground/src/shared/sync/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/sync/index.tsx ================================================ import theatre from '@theatre/core' import {getProject} from '@theatre/core' void theatre.init({ studio: true, // __experimental_syncServer: 'wss://syncserver-kspg.onrender.com', serverUrl: 'http://localhost:3000', // usePersistentStorage: false, }) const project = getProject('Syncing project') const sheet = project.sheet('sheet') const obj = sheet.object('obj', {x: 0, a: ''}) // onChange(obj.props, (props) => {}) ================================================ FILE: packages/playground/src/shared/theatric/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/theatric/index.tsx ================================================ import {button, initialize, types, useControls} from 'theatric' import ReactDom from 'react-dom/client' import React, {useState} from 'react' import state from './state.json' void initialize({state}) function SomeComponent({id}: {id: string}) { const {foo, $get, $set} = useControls( { foo: 0, bar: 0, bez: button(() => { $set((p) => p.foo, 2) $set((p) => p.bar, 3) console.log($get((p) => p.foo)) }), }, {folder: id}, ) return (
{id}: {foo}
) } function App() { const {bar, $set, $get} = useControls({ bar: {foo: 'bar'}, baz: button(() => console.log($get((p) => p.bar))), }) const {another, panel, col, yo} = useControls( { another: '', panel: '', yo: types.number(0), col: types.rgba(), }, {panel: 'My panel'}, ) const {} = useControls({}) const [showComponent, setShowComponent] = useState(false) return (
{/*
{JSON.stringify(bar)}
*/} {showComponent && } {yo} {JSON.stringify(col)}
) } ReactDom.createRoot(document.getElementById('root')!).render() ================================================ FILE: packages/playground/src/shared/theatric/state.json ================================================ { "sheetsById": { "Panels": { "staticOverrides": { "byObject": { "Default panel": { "first": { "foo": 73 } } } } } }, "definitionVersion": "0.4.0", "revisionHistory": [ "cfRereBjMbBNLzC3" ] } ================================================ FILE: packages/playground/src/shared/three-basic/ThreeScene.tsx ================================================ import React, {useEffect, useRef} from 'react' import {types} from '@theatre/core' import type {ISheetObject, IProject} from '@theatre/core' import { Color, DirectionalLight, Mesh, MeshPhongMaterial, PerspectiveCamera, RawShaderMaterial, Scene, ShaderMaterial, SphereGeometry, Vector2, Vector3, WebGLRenderer, } from 'three' type ThreeSceneProps = { project: IProject } export default function ThreeScene(props: ThreeSceneProps) { const canvasRef = useRef(null) const sheet = props.project.sheet('Sphere') // Animation let sheetObj: ISheetObject | undefined = undefined let mesh: Mesh | undefined = undefined function animate(key: string, props: any) { if (sheetObj === undefined) { sheetObj = sheet.object(key, props) } else { sheetObj = sheet.object( key, {...props, ...sheetObj.value}, {reconfigure: true}, ) } return sheetObj } function animateMaterial() { if (mesh === undefined) return const keys = {} // Cycle through props for (const i in mesh.material) { // @ts-ignore const value = mesh.material[i] if (typeof value === 'number') { // @ts-ignore keys[i] = value } else if (value instanceof Vector2) { // @ts-ignore keys[i] = {x: value.x, y: value.y} } else if (value instanceof Vector3) { // @ts-ignore keys[i] = {x: value.x, y: value.y, z: value.z} } else if (value instanceof Color) { // @ts-ignore keys[i] = types.rgba({ r: value.r * 255, g: value.g * 255, b: value.b * 255, a: 1, }) } } // Uniforms if ( mesh.material instanceof ShaderMaterial || mesh.material instanceof RawShaderMaterial ) { const uniforms = mesh.material.uniforms // @ts-ignore keys.uniforms = {} for (const i in uniforms) { const uniform = uniforms[i].value if (typeof uniform === 'number') { // @ts-ignore keys.uniforms[i] = uniform } else if (uniform instanceof Vector2) { const value = uniform as Vector2 // @ts-ignore keys.uniforms[i] = {x: value.x, y: value.y} } else if (uniform instanceof Vector3) { const value = uniform as Vector3 // @ts-ignore keys.uniforms[i] = {x: value.x, y: value.y, z: value.z} } else if (uniform instanceof Color) { const value = uniform as Color // @ts-ignore keys.uniforms[i] = {r: value.r, g: value.g, b: value.b} } } } // Animate animate('Material', {material: keys}).onValuesChange((values: any) => { const {material} = values for (const key in material) { if (key === 'uniforms') { const uniforms = material[key] for (const uniKey in uniforms) { const uniform = uniforms[uniKey] if (typeof uniform === 'number') { // @ts-ignore mesh.material.uniforms[uniKey].value = uniform } else { // @ts-ignore mesh.material.uniforms[uniKey].value.copy(uniform) } } } else { const value = material[key] if (typeof value === 'number') { // @ts-ignore mesh.material[key] = value } else if (value.r !== undefined) { // color // @ts-ignore mesh.material[key].copy(value) } else if (value.x !== undefined) { // vector // @ts-ignore mesh.material[key].copy(value) } } } }) } function animateTransform() { if (mesh === undefined) return animate('Transform', { transform: { position: { x: mesh.position.x, y: mesh.position.y, z: mesh.position.z, }, rotation: { x: mesh.rotation.x, y: mesh.rotation.y, z: mesh.rotation.z, }, scale: { x: mesh.scale.x, y: mesh.scale.y, z: mesh.scale.z, }, visible: mesh.visible, }, }).onValuesChange((values: any) => { if (mesh === undefined) return const {transform} = values mesh.position.set( transform.position.x, transform.position.y, transform.position.z, ) mesh.rotation.set( transform.rotation.x, transform.rotation.y, transform.rotation.z, ) mesh.scale.set(transform.scale.x, transform.scale.y, transform.scale.z) mesh.visible = transform.visible }) } useEffect(() => { // Basic Three let raf = -1 const width = window.innerWidth const height = window.innerHeight const renderer = new WebGLRenderer({ antialias: true, canvas: canvasRef.current!, }) renderer.setPixelRatio(devicePixelRatio) renderer.setSize(width, height) const scene = new Scene() const camera = new PerspectiveCamera(60, width / height) camera.position.z = 10 const light = new DirectionalLight() light.position.set(1, 5, 4) scene.add(light) mesh = new Mesh(new SphereGeometry(3), new MeshPhongMaterial()) scene.add(mesh) // RAF function render() { raf = requestAnimationFrame(render) renderer.render(scene, camera) } render() return () => { cancelAnimationFrame(raf) renderer.dispose() } }, []) return (
) } ================================================ FILE: packages/playground/src/shared/three-basic/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/three-basic/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import theatre from '@theatre/core' import {getProject} from '@theatre/core' import ThreeScene from './ThreeScene' void theatre.init({studio: true}) ReactDOM.createRoot(document.getElementById('root')!).render( , ) ================================================ FILE: packages/playground/src/shared/turtle/TurtleRenderer.tsx ================================================ import type {MutableRefObject} from 'react' import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react' import theatre, {getStudioSync} from '@theatre/core' import type {ISheet} from '@theatre/core' import {types} from '@theatre/core' import type {ITurtle} from './turtle' import {drawTurtlePlan, makeTurtlePlan} from './turtle' void theatre.init({studio: true}) const objConfig = { startingPoint: { x: types.number(0.5, {range: [0, 1]}), y: types.number(0.5, {range: [0, 1]}), }, scale: types.number(1, {range: [0.1, 1000]}), } const TurtleRenderer: React.FC<{ sheet: ISheet objKey: string width: number height: number programFn: (t: ITurtle) => void }> = (props) => { const [canvas, setCanvas] = useState(null) const context = useMemo(() => { if (canvas) { return canvas.getContext('2d')! } }, [canvas]) const dimsRef = useRef({width: props.width, height: props.height}) dimsRef.current = {width: props.width, height: props.height} const obj = useMemo(() => { return props.sheet.object(props.objKey, objConfig) }, [props.sheet, props.objKey]) useEffect(() => { obj.onValuesChange((v) => { setTransforms(v) }) }, [obj]) const [transforms, transformsRef, setTransforms] = useStateAndRef< typeof obj.value >({scale: 1, startingPoint: {x: 0.5, y: 0.5}}) const bounds = useMemo(() => canvas?.getBoundingClientRect(), [canvas]) useLayoutEffect(() => { if (!canvas) return const receiveWheelEvent = (event: WheelEvent) => { event.preventDefault() event.stopPropagation() const oldTransform = transformsRef.current const newTransform: typeof oldTransform = { ...oldTransform, startingPoint: {...oldTransform.startingPoint}, } if (event.ctrlKey) { const scaleFactor = 1 - (event.deltaY / dimsRef.current.height) * 1.2 newTransform.scale *= scaleFactor // const bounds = canvas.getBoundingClientRect() const anchorPoint = { x: (event.clientX - bounds!.left) / dimsRef.current.width, y: (event.clientY - bounds!.top) / dimsRef.current.height, } newTransform.startingPoint.x = anchorPoint.x - (anchorPoint.x - newTransform.startingPoint.x) * scaleFactor newTransform.startingPoint.y = anchorPoint.y - (anchorPoint.y - newTransform.startingPoint.y) * scaleFactor } else { newTransform.startingPoint.x = oldTransform.startingPoint.x - event.deltaX / dimsRef.current.width newTransform.startingPoint.y = oldTransform.startingPoint.y - event.deltaY / dimsRef.current.height } const studio = getStudioSync()! studio.transaction((api) => { api.set(obj.props, newTransform) }) // setTransforms(newTransform) } const listenerOptions = { capture: true, passive: false, } canvas.addEventListener('wheel', receiveWheelEvent, listenerOptions) return () => { canvas.removeEventListener('wheel', receiveWheelEvent, listenerOptions) } }, [canvas]) const plan = useMemo(() => makeTurtlePlan(props.programFn), [props.programFn]) useEffect(() => { if (!context) return drawTurtlePlan( plan, context, { width: props.width, height: props.height, scale: transforms.scale, startFrom: { x: transforms.startingPoint.x * props.width, y: transforms.startingPoint.y * props.height, }, }, 1, ) }, [props.width, props.height, plan, context, transforms]) return ( ) } function useStateAndRef( initial: S, ): [S, MutableRefObject, (s: S) => void] { const [state, setState] = useState(initial) const stateRef = useRef(state) const set = useCallback((s: S) => { stateRef.current = s setState(s) }, []) return [state, stateRef, set] } export default TurtleRenderer ================================================ FILE: packages/playground/src/shared/turtle/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/shared/turtle/index.tsx ================================================ /** * A super basic Turtle geometry renderer hooked up to Theatre, so the parameters * can be tweaked and animated. */ import React, {useMemo, useState} from 'react' import ReactDom from 'react-dom/client' import {getProject} from '@theatre/core' import type {ITurtle} from './turtle' import TurtleRenderer from './TurtleRenderer' import {useBoundingClientRect} from './utils' const project = getProject('Turtle Playground') const sheet = project.sheet('Turtle', 'The only one') const TurtleExample: React.FC<{}> = (props) => { const [container, setContainer] = useState(null) const programFn = useMemo(() => { return ({forward, backward, left, right, repeat}: ITurtle) => { const steps = 10 repeat(steps, () => { forward(steps * 2) right(360 / steps) }) } }, []) const bounds = useBoundingClientRect(container) return (
{bounds && ( )}
) } ReactDom.createRoot(document.getElementById('root')!).render() ================================================ FILE: packages/playground/src/shared/turtle/turtle.ts ================================================ import clamp from 'lodash-es/clamp' type Op_Move = { type: 'Move' amount: number angle: number penDown: boolean } type Op_ModifyContext = { type: 'ContextModifier' fn: (ctx: CanvasRenderingContext2D) => void } type Op = Op_ModifyContext | Op_Move type IPlan = { totalTravel: number ops: Op[] } export function makeTurtlePlan(fn: (turtle: Turtle) => void): IPlan { const plan: IPlan = { totalTravel: 0, ops: [], } const turtle = new Turtle(plan) fn(turtle) return plan } export function drawTurtlePlan( plan: IPlan, ctx: CanvasRenderingContext2D, { width, height, scale, startFrom, }: { width: number height: number scale: number startFrom: {x: number; y: number} }, tilProgression: number, ): void { const {ops} = plan if (ops.length === 0) return const targetDistance = clamp(tilProgression, 0, 1) * plan.totalTravel ctx.clearRect(0, 0, width, height) let traveledSoFar = 0 let pos = {...startFrom} ctx.beginPath() ctx.lineWidth = 2 ctx.strokeStyle = 'white' ctx.moveTo(pos.x, pos.y) for (const op of ops) { if (traveledSoFar >= targetDistance) return if (op.type === 'ContextModifier') { op.fn(ctx) } else { let amount = Math.abs(op.amount) const sign = op.amount < 0 ? -1 : 1 const {angle} = op const roomTilTarget = targetDistance - traveledSoFar const distanceInThisStep = roomTilTarget < amount ? roomTilTarget : amount traveledSoFar += distanceInThisStep pos = move(pos, angle, distanceInThisStep * sign, scale, op.penDown, ctx) ctx.stroke() } } } function move( pointA: {x: number; y: number}, _angle: number, amount: number, scale: number, penIsDown: boolean, ctx: CanvasRenderingContext2D, ): {x: number; y: number} { const angle = (_angle * Math.PI) / 180 const unrotatedTarget = { x: pointA.x + amount * scale, y: pointA.y, } const pointB = { x: pointA.x + Math.cos(angle) * (unrotatedTarget.x - pointA.x) - Math.sin(angle) * (unrotatedTarget.y - pointA.y), y: pointA.y + Math.sin(angle) * (unrotatedTarget.x - pointA.x) - Math.sin(angle) * (unrotatedTarget.y - pointA.y), } if (penIsDown) { ctx.lineTo(pointB.x, pointB.y) } else { ctx.moveTo(pointB.x, pointB.y) } return pointB } class Turtle { private _state = { penIsDown: true, angle: -90, } constructor(private _plan: IPlan) {} fn = (innerFn: () => void) => { return innerFn } private _pushContextModifier(fn: (ctx: CanvasRenderingContext2D) => void) { this._plan.ops.push({type: 'ContextModifier', fn}) } press = (n: number) => { this._pushContextModifier((ctx) => { ctx.lineWidth = n }) } forward = (amount: number) => { this._plan.ops.push({ type: 'Move', amount, penDown: this._state.penIsDown, angle: this._state.angle, }) this._plan.totalTravel += Math.abs(amount) return this } backward = (amount: number) => { return this.forward(amount) } right = (deg: number) => { this._rotate(deg) return this } left = (deg: number) => { this._rotate(-deg) return this } private _rotate(deg: number) { this._state.angle += deg } penup = () => { this._state.penIsDown = false return this } pendown = () => { this._state.penIsDown = true return this } repeat = (n: number, fn: (i: number) => void) => { for (let i = 0; i < n; i++) { fn(i) } return this } } export type ITurtle = Turtle ================================================ FILE: packages/playground/src/shared/turtle/utils.ts ================================================ import {useLayoutEffect, useState} from 'react' export function useBoundingClientRect( node: HTMLElement | null, ): null | DOMRect { const [bounds, set] = useState(null) useLayoutEffect(() => { if (node) { set(node.getBoundingClientRect()) } return () => { set(null) } }, [node]) return bounds } ================================================ FILE: packages/playground/src/shared/utils/useExtensionButton.ts ================================================ import theatre from '@theatre/core' import {useEffect, useMemo, useRef} from 'react' let idCounter = 0 export function useExtensionButton( title: string, callback: () => void, svgSource: string = stepForward, ) { const refs = useRef({callback, svgSource}) const id = useMemo(() => 'useExtensionButton#' + idCounter++, []) useEffect(() => { const studio = theatre.getStudioSync()! studio.extend({ id: id, toolbars: { global(set) { set([ { type: 'Icon', title, onClick() { refs.current.callback() }, svgSource: refs.current.svgSource, }, ]) return () => {} }, }, }) }, [id]) } export function extensionButton( title: string, callback: () => void, svgSource?: string, ) { const id = 'useExtensionButton#' + idCounter++ const studio = theatre.getStudioSync()! studio.extend({ id: id, toolbars: { global(set) { set([ { type: 'Icon', title, onClick() { callback() }, svgSource: svgSource ?? stepForward, }, ]) return () => {} }, }, }) } // FontAwesome FaStepForward const stepForward = `` ================================================ FILE: packages/playground/src/tests/hot-reload-extension-pane/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/tests/hot-reload-extension-pane/index.tsx ================================================ import type {IExtension} from '@theatre/core' import theatre from '@theatre/core' import '@theatre/core' import {extensionButton} from '../../shared/utils/useExtensionButton' const ext1: IExtension = { id: '@theatre/hello-world-extension', toolbars: {}, panes: [], } void theatre.init({studio: true, usePersistentStorage: false}) let currentStep = -1 extensionButton( 'Forward', () => { if (currentStep < steps.length - 1) { currentStep++ steps[currentStep]() } }, '>', ) const steps = [ function step0() { const studio = theatre.getStudioSync()! studio.extend( { ...ext1, panes: [ { class: 'pane1', mount: ({paneId, node}) => { const el = document.createElement('div') el.innerHTML = 'pane1-config1' node.appendChild(el) return function unmount() { node.removeChild(el) console.log('unmount pane1-config1') } }, }, ], }, {__experimental_reconfigure: true}, ) studio.createPane('pane1') }, function step1() { const studio = theatre.getStudioSync()! studio.extend( { ...ext1, panes: [ { class: 'pane1', mount: ({paneId, node}) => { const el = document.createElement('div') el.innerHTML = 'pane1-config2' node.appendChild(el) return function unmount() { node.removeChild(el) console.log('unmount pane1-config2') } }, }, ], }, {__experimental_reconfigure: true}, ) }, function step2() { const studio = theatre.getStudioSync()! studio.extend( { ...ext1, panes: [], }, {__experimental_reconfigure: true}, ) }, function step3() { steps[1]() }, ] ================================================ FILE: packages/playground/src/tests/hot-reload-extension-pane/test.e2e.ts ================================================ import {test, expect} from '@playwright/test' test.describe('hot-reload-extension-pane', () => { test('works', async ({page}) => { await page.goto('./tests/hot-reload-extension-pane/') const toolbar = page.locator( '[data-testid="theatre-extensionToolbar-global"]', ) const forwardButton = toolbar.getByRole('button', {name: '>'}) await forwardButton.click() const pane = page.locator('[data-testid="theatre-pane-content-pane1 \\#1"]') expect(await pane.textContent()).toEqual('pane1-config1') await forwardButton.click() expect(await pane.textContent()).toEqual('pane1-config2') await forwardButton.click() await expect(pane).not.toBeAttached() await forwardButton.click() expect(await pane.textContent()).toEqual('pane1-config2') }) }) ================================================ FILE: packages/playground/src/tests/hot-reload-extension-toolbar/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/tests/hot-reload-extension-toolbar/index.tsx ================================================ import type {IExtension} from '@theatre/core' import theatre from '@theatre/core' import '@theatre/core' import {extensionButton} from '../../shared/utils/useExtensionButton' const ext1: IExtension = { id: '@theatre/hello-world-extension', toolbars: { global(set, studio) { console.log('mount 1') set([ { type: 'Icon', title: 'Icon 1', svgSource: '1', onClick: () => { console.log('Icon 1') }, }, ]) return () => { console.log('unmount 1') } }, }, panes: [], } void theatre.init({studio: true, usePersistentStorage: false}) let currentStep = -1 extensionButton( 'Forward', () => { if (currentStep < steps.length - 1) { currentStep++ steps[currentStep]() } }, '>', ) const steps = [ function step1() { const studio = theatre.getStudioSync()! studio.extend(ext1) }, function step2() { const studio = theatre.getStudioSync()! studio.extend( { ...ext1, toolbars: { global(set, studio) { console.log('mount 2') set([ { type: 'Icon', title: 'Icon 2', svgSource: '2', onClick: () => { console.log('Icon 2') }, }, ]) return () => { console.log('unmount 2') } }, }, }, {__experimental_reconfigure: true}, ) }, function step3() { const studio = theatre.getStudioSync()! studio.extend( { ...ext1, toolbars: {}, }, {__experimental_reconfigure: true}, ) }, ] ================================================ FILE: packages/playground/src/tests/hot-reload-extension-toolbar/test.e2e.ts ================================================ import {test, expect} from '@playwright/test' test.describe('hot-reload-extension-toolbar', () => { test('works', async ({page}) => { await page.goto('./tests/hot-reload-extension-toolbar/') const toolbar = page.locator( '[data-testid="theatre-extensionToolbar-global"]', ) const forwardButton = toolbar.getByRole('button', {name: '>'}) await forwardButton.click() const otherButton = toolbar.getByRole('button').nth(1) expect(await otherButton.textContent()).toEqual('1') await forwardButton.click() expect(await otherButton.textContent()).toEqual('2') await forwardButton.click() // expect otherButton not to exist await expect(otherButton).not.toBeAttached() }) }) ================================================ FILE: packages/playground/src/tests/r3f-dynamic-tree/App.tsx ================================================ import {editable as e, SheetProvider} from '@theatre/r3f' import {getProject} from '@theatre/core' import React, {useEffect, useState} from 'react' import {Canvas} from '@react-three/fiber' import {PerspectiveCamera} from '@react-three/drei' import {useExtensionButton} from '../../shared/utils/useExtensionButton' document.body.style.backgroundColor = '#171717' const EditableCamera = e(PerspectiveCamera, 'perspectiveCamera') function App() { const project = getProject('R3F Hot Reload Test') const sheet = project.sheet('Scene') return (
) } // initial config of "Cube 1" const cube1Config1 = {a: 1} // we change the default value of a, and add a new prop const cube1Config2 = {a: 2, b: 2} // we re-use the previous config const cube1Config3 = cube1Config2 function Scene() { const [state, setState] = useState(1) useExtensionButton( 'Step forward', () => { setState((s) => s + 1) }, '>', ) useEffect(() => {}, []) if (state === 1) { return ( ) } else if (state === 2) { return ( <> ) } else if (state === 3) { return ( ) } else { return null } } export default App ================================================ FILE: packages/playground/src/tests/r3f-dynamic-tree/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/tests/r3f-dynamic-tree/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import theatre from '@theatre/core' import extension from '@theatre/r3f/dist/extension' void theatre.getStudio().then((studio) => studio.extend(extension)) void theatre.init({studio: true, usePersistentStorage: false}) ReactDOM.createRoot(document.getElementById('root')!).render() ================================================ FILE: packages/playground/src/tests/r3f-dynamic-tree/test.e2e.ts ================================================ import {test, expect} from '@playwright/test' test.describe('r3f-dynamic-tree', () => { test('works', async ({page}) => { test.setTimeout(30000) await page.goto('./tests/r3f-dynamic-tree/') const toolbar = page.locator( '[data-testid="theatre-extensionToolbar-global"]', ) const snapshotButton = toolbar.getByRole('button').nth(0) await snapshotButton.click() const pane = page.getByTestId('theatre-pane-content-snapshot #1') await expect(pane).toHaveScreenshot({}) const forwardButton = toolbar.getByRole('button', {name: '>'}) await forwardButton.click() await forwardButton.click() await forwardButton.click() await expect(pane).toHaveScreenshot({}) }) }) ================================================ FILE: packages/playground/src/tests/r3f-stress-test/App.tsx ================================================ import {editable as e, RefreshSnapshot, SheetProvider} from '@theatre/r3f' import {Stars} from '@react-three/drei' import {getProject, types} from '@theatre/core' import React, {Suspense, useState} from 'react' import {Canvas} from '@react-three/fiber' import {useGLTF, PerspectiveCamera} from '@react-three/drei' import sceneGLB from './scene.glb' import state from './SpaceStress.theatre-project-state.json' document.body.style.backgroundColor = '#171717' const EditableCamera = e(PerspectiveCamera, 'perspectiveCamera') function Model({url}: {url: string}) { const {nodes} = useGLTF(url) as any return ( ) } // Initially, just copied from the shared/dom example const textInterpolate = (left: string, right: string, progression: number) => { if (!left || right.startsWith(left)) { const length = Math.floor( Math.max(0, (right.length - left.length) * progression), ) return left + right.slice(left.length, left.length + length) } return left } const allPropsObjectConfig = { test: types.string('Typing', {interpolate: textInterpolate}), testLiteral: types.stringLiteral('a', {a: 'Option A', b: 'Option B'}), bool: types.boolean(false), favoriteFood: types.compound({ name: types.string('Pie'), // if needing more compounds, consider adding weight with different units price: types.compound({ currency: types.stringLiteral('USD', {USD: 'USD', EUR: 'EUR'}), amount: types.number(10, {range: [0, 1000], label: '$'}), }), }), x: types.number(200), y: types.number(200), color: types.rgba({r: 1, g: 0, b: 0, a: 1}), } function App() { const bgs = ['#272730', '#b7c5d1'] const [bgIndex, setBgIndex] = useState(0) const bg = bgs[bgIndex] const project = getProject('SpaceStress', {state}) const sheet = project.sheet('Scene') void project.ready.then(() => sheet.sequence.play({iterationCount: Infinity})) const allPropsObj = sheet.object('All Props Tester', allPropsObjectConfig) console.log('allPropsObj', allPropsObj) return (
{ // return setBgIndex((bgIndex) => (bgIndex + 1) % bgs.length) }} style={{ height: '100vh', }} >
) } export default App ================================================ FILE: packages/playground/src/tests/r3f-stress-test/SpaceStress.theatre-project-state.json ================================================ { "sheetsById": { "Scene": { "staticOverrides": { "byObject": { "Example Namespace / Debris 2": { "position": { "x": -0.41416894961196427, "y": 0.05418979316853111, "z": -0.6307631253507592 }, "rotation": { "x": -0.28479242226491835, "y": 0.10635706958376893, "z": -0.4508041396948808 }, "scale": { "x": 1, "y": 1, "z": 1 } }, "Camera": { "position": { "z": 16.000930185092166 }, "rotation": { "x": 0, "y": 0, "z": 0 }, "scale": { "x": 1, "y": 1, "z": 1 }, "near": 0.1, "far": 2000, "fov": 75, "zoom": 1 }, "Example Namespace / Thingy": { "position": { "x": -0.4019278546805294, "y": 0.05258817044159253, "z": -0.6121204161281066 }, "rotation": { "x": 0, "y": 0, "z": 0 }, "scale": { "x": 1, "y": 1, "z": 1 } }, "Light 3": { "scale": { "x": 1, "y": 1, "z": 1 }, "intensity": 1 }, "Light 1": { "rotation": { "x": 0, "y": 0, "z": 0 }, "scale": { "x": 1, "y": 1, "z": 1 }, "distance": 0, "decay": 0 } } }, "sequence": { "subUnitsPerUnit": 30, "length": 10, "type": "PositionalSequence", "tracksByObject": { "Camera": { "trackData": { "8bGG2wTnH3": { "type": "BasicKeyframedTrack", "__debugName": "Camera:[\"position\",\"x\"]", "keyframes": [ { "id": "lYjaxYA49k", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 }, { "id": "pD7z50G6KN", "position": 4.1, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 5.612978002902869 }, { "id": "JVYh0YShNX", "position": 4.5, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 5.679999999999999 }, { "id": "0wFEq_6Nfn", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 8.944223985920669 }, { "id": "1BuwkmWcWV", "position": 7.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -5.162845587908198 }, { "id": "BZL8yHn_Xs", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.2971937537323752 }, { "id": "oVcDPNRlQ7", "position": 10.5, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 } ] }, "8Fgrl6pszk": { "type": "BasicKeyframedTrack", "__debugName": "Camera:[\"position\",\"y\"]", "keyframes": [ { "id": "cuTuWmf384", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 5.762709938174263 }, { "id": "x0Oy5PcUAf", "position": 4.1, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 3.8429232922248016 }, { "id": "b06BUhE2r4", "position": 4.5, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 3.819999999999999 }, { "id": "kCcZk3u5AS", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 3.7514667068203535 }, { "id": "_epso83yxl", "position": 7.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.09121846016466982 }, { "id": "EEfNDpDccl", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 }, { "id": "kTfZnzd2Wq", "position": 10.5, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 } ] }, "66OlLrjm5i": { "type": "BasicKeyframedTrack", "__debugName": "Camera:[\"rotation\",\"x\"]", "keyframes": [ { "id": "kbqPSD6gyD", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 }, { "id": "m3QhEagzQL", "position": 4.1, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.634700286412289 }, { "id": "b1-ACYPwOt", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.6185566084664134 }, { "id": "RIsIF2mwMW", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 } ] }, "ab1K51U9Ru": { "type": "BasicKeyframedTrack", "__debugName": "Camera:[\"rotation\",\"y\"]", "keyframes": [ { "id": "_eNjR3KQZY", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0.3122263362371538 }, { "id": "LfmuQ751y_", "position": 4.1, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0.12170199514666553 }, { "id": "oQDr2JbDeN", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0.1116576445180289 }, { "id": "5ot5uuiFY4", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.27319885967680496 } ] }, "N2KCxPTdhe": { "type": "BasicKeyframedTrack", "__debugName": "Camera:[\"rotation\",\"z\"]", "keyframes": [ { "id": "qUyj8pwdYB", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 }, { "id": "jYt_GSQTi7", "position": 4.1, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 6.990600239440247e-18 }, { "id": "PKznC75gKj", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 6.812793483511706e-18 }, { "id": "uRerrV8Wvc", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 } ] } }, "trackIdByPropPath": { "[\"position\",\"x\"]": "8bGG2wTnH3", "[\"position\",\"y\"]": "8Fgrl6pszk", "[\"rotation\",\"x\"]": "66OlLrjm5i", "[\"rotation\",\"y\"]": "ab1K51U9Ru", "[\"rotation\",\"z\"]": "N2KCxPTdhe" } }, "Example Namespace / Debris 2": { "trackData": { "lIbSozvAt9": { "type": "BasicKeyframedTrack", "__debugName": "Example Namespace / Debris 2:[\"position\",\"y\"]", "keyframes": [ { "id": "Oxuiu3NGpi", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.13382142277468495 }, { "id": "5EOkGetIzj", "position": 5.167, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.0718956778318585 }, { "id": "8HTEX9vP2s", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.10315674062798819 } ] }, "Dzh9nGps1E": { "type": "BasicKeyframedTrack", "__debugName": "Example Namespace / Debris 2:[\"position\",\"x\"]", "keyframes": [ { "id": "alO_55vxU7", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0.06843989016124558 }, { "id": "4K5GtQjYrM", "position": 5.167, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.4048544192882112 }, { "id": "8F8LNtzSaK", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.16592821593917476 } ] }, "q-0uBvC1xm": { "type": "BasicKeyframedTrack", "__debugName": "Example Namespace / Debris 2:[\"position\",\"z\"]", "keyframes": [ { "id": "W0sEFD1zmk", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0.21464398477965263 }, { "id": "9VmCX-KaDO", "position": 5.167, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.50616475310079 }, { "id": "1j11jf9H5W", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.142289482166487 } ] }, "Vz3jGxL2Zs": { "type": "BasicKeyframedTrack", "__debugName": "Example Namespace / Debris 2:[\"rotation\",\"x\"]", "keyframes": [ { "id": "RFeOWD-WKR", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.7627888195445028 }, { "id": "siVdf2zPKN", "position": 5.167, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.09553015695204622 }, { "id": "uXHx7HMjwG", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0.7084430674128276 } ] }, "MhTQTImxQ8": { "type": "BasicKeyframedTrack", "__debugName": "Example Namespace / Debris 2:[\"rotation\",\"y\"]", "keyframes": [ { "id": "_NoYnBU5De", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0.1851909123865111 }, { "id": "mXUULi87zO", "position": 5.167, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0.019640469054676018 }, { "id": "uWMpLQSrJN", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 1.072695780791852 } ] }, "3uezjGiONv": { "type": "BasicKeyframedTrack", "__debugName": "Example Namespace / Debris 2:[\"rotation\",\"z\"]", "keyframes": [ { "id": "PGnAc7EhbA", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.06326767198149798 }, { "id": "cFu4iE-Wfp", "position": 5.167, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -0.5974796207743504 }, { "id": "Z3DgKA58ki", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 2.5772222890936374 } ] } }, "trackIdByPropPath": { "[\"position\",\"y\"]": "lIbSozvAt9", "[\"position\",\"x\"]": "Dzh9nGps1E", "[\"position\",\"z\"]": "q-0uBvC1xm", "[\"rotation\",\"x\"]": "Vz3jGxL2Zs", "[\"rotation\",\"y\"]": "MhTQTImxQ8", "[\"rotation\",\"z\"]": "3uezjGiONv" } }, "Light 3": { "trackData": { "F_zGYMlJnJ": { "type": "BasicKeyframedTrack", "__debugName": "Light 3:[\"rotation\",\"x\"]", "keyframes": [ { "id": "qNipvSYpNq", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 }, { "id": "q0qtixduGx", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 }, { "id": "JsR6BTDzzI", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 } ] }, "9XT3gt5Z5K": { "type": "BasicKeyframedTrack", "__debugName": "Light 3:[\"rotation\",\"y\"]", "keyframes": [ { "id": "32VxUAOdAt", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 }, { "id": "VgzYEEDWlO", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 }, { "id": "VVPFYd6aXA", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 } ] }, "pDLaY7cXQP": { "type": "BasicKeyframedTrack", "__debugName": "Light 3:[\"rotation\",\"z\"]", "keyframes": [ { "id": "MLKw1ZZHIR", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 }, { "id": "VBAFfx0C81", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 }, { "id": "mVM-MhZKIQ", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 0 } ] }, "4Sumy-DYPM": { "type": "BasicKeyframedTrack", "__debugName": "Light 3:[\"position\",\"x\"]", "keyframes": [ { "id": "257acl7t3r", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 2.1160344001512037 }, { "id": "T0yROMmEBf", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 5.130306777135816 }, { "id": "iovRaYG3OI", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 10.280226555296956 } ] }, "U3G7sYTGDm": { "type": "BasicKeyframedTrack", "__debugName": "Light 3:[\"position\",\"y\"]", "keyframes": [ { "id": "of52BiNAK6", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 2.737755043250702 }, { "id": "V7KF6oZr8R", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 11.735225307221182 }, { "id": "9im5esO-bk", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 6.229532917807006 } ] }, "dJ_oFOfKY_": { "type": "BasicKeyframedTrack", "__debugName": "Light 3:[\"position\",\"z\"]", "keyframes": [ { "id": "V1BVrhTyV7", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -10.215152660100808 }, { "id": "htIzuh6GLJ", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -7.959406158801434 }, { "id": "qmyDaISgo7", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -21.47891083288782 } ] } }, "trackIdByPropPath": { "[\"rotation\",\"x\"]": "F_zGYMlJnJ", "[\"rotation\",\"y\"]": "9XT3gt5Z5K", "[\"rotation\",\"z\"]": "pDLaY7cXQP", "[\"position\",\"x\"]": "4Sumy-DYPM", "[\"position\",\"y\"]": "U3G7sYTGDm", "[\"position\",\"z\"]": "dJ_oFOfKY_" } }, "Light 1": { "trackData": { "Eb6N5K-NHK": { "type": "BasicKeyframedTrack", "__debugName": "Light 1:[\"position\",\"x\"]", "keyframes": [ { "id": "Ittwa--FbM", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -10.00980876514102 }, { "id": "edt3gnbQg-", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -17.867044246638432 }, { "id": "MgpxfOdTcu", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 1.0979985208214078 } ] }, "xl7ucQ72mO": { "type": "BasicKeyframedTrack", "__debugName": "Light 1:[\"position\",\"y\"]", "keyframes": [ { "id": "YFblyWrExK", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -18.963006327076492 }, { "id": "h9G6P5GFR0", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 1.1014291847928668 }, { "id": "yxgqyijJp4", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -8.16442444739367 } ] }, "krnG46oHdk": { "type": "BasicKeyframedTrack", "__debugName": "Light 1:[\"position\",\"z\"]", "keyframes": [ { "id": "HyjmyM_ehV", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -9.969611988526236 }, { "id": "1crYou7EUl", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -36.92029081302925 }, { "id": "7Rpa4BCmiD", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": -19.1221872093312 } ] }, "oKQV1ruUQi": { "type": "BasicKeyframedTrack", "__debugName": "Light 1:[\"intensity\"]", "keyframes": [ { "id": "nzR42CL7U1", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 1 }, { "id": "H-1ffXJK-9", "position": 4.867, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 1.4260784907802817 }, { "id": "6QhF5itoDw", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 1.9 } ] } }, "trackIdByPropPath": { "[\"position\",\"x\"]": "Eb6N5K-NHK", "[\"position\",\"y\"]": "xl7ucQ72mO", "[\"position\",\"z\"]": "krnG46oHdk", "[\"intensity\"]": "oKQV1ruUQi" } } } } } }, "definitionVersion": "0.4.0", "revisionHistory": [ "C3YuIxAoFMCH3bLq" ] } ================================================ FILE: packages/playground/src/tests/r3f-stress-test/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/tests/r3f-stress-test/index.tsx ================================================ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import theatre from '@theatre/core' import extension from '@theatre/r3f/dist/extension' import getStudio from '@theatre/studio/getStudio' const studioPrivate = getStudio() void theatre.getStudio().then((studio) => studio.extend(extension)) void theatre.init({studio: true}) ReactDOM.createRoot(document.getElementById('root')!).render() const raf = studioPrivate.ticker // Show "ticks per second" information in performance measurements using the User Timing API // See https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API // See https://web.dev/user-timings/ setInterval(() => { const start = { ticks: raf.__ticks, now: Date.now(), } const id = start.now.toString(36) + 'fps' performance.mark(id) setTimeout(() => { const ticksPerSec = ((raf.__ticks - start.ticks) * 1000) / (Date.now() - start.now) // This shows up in the performance tab of devtools if you record! performance.measure( `${ticksPerSec.toFixed(0)}t/1s - ${(ticksPerSec * 0.5).toFixed( 0, )}t/500ms`, id, ) }, 2000) }, 500) ================================================ FILE: packages/playground/src/tests/reading-obj-value/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/tests/reading-obj-value/index.tsx ================================================ import theatre from '@theatre/core' import {getProject, types} from '@theatre/core' import state from './reading obj value.theatre-project-state.json' void theatre.init({studio: true, usePersistentStorage: false}) const project = getProject('reading obj value', {state}) const TOTAL_ELEMENTS = 300 const TOTAL_ELEMENTS_R = 1 / TOTAL_ELEMENTS const playbackControlObj = project .sheet('controls sheet') .object('playback control', { interval: types.number(50, { range: [1, 20000], }), }) const elements = new Array(TOTAL_ELEMENTS).fill(0).map((_, idx) => { const sheet = project.sheet('sample sheet', `#${idx}`) const obj = sheet.object('sample object', { position: { x: 0, y: 0, }, }) const el = document.createElement('div') Object.assign(el.style, { position: 'absolute', height: '2rem', width: '2rem', borderRadius: '50%', background: `hsl(${idx * 360 * TOTAL_ELEMENTS_R}, 100%, 80%)`, left: 'var(--x)', top: 'var(--y)', }) document.body.appendChild(el) return {el, sheet, obj} }) void project.ready.then(() => { const studio = theatre.getStudioSync()! // select the playback controls obj so it shows as a tweakable control studio.setSelection([playbackControlObj]) for (let i = 0; i < elements.length; i++) { const sheet = elements[i].sheet sheet.sequence.position = i * TOTAL_ELEMENTS_R * 5 void sheet.sequence.play({ iterationCount: Infinity, }) } }) function render() { for (let i = 0; i < elements.length; i++) { const element = elements[i] // read directly from value to test keepHot from https://linear.app/theatre/issue/P-217/if-objvalue-is-read-make-sure-its-derivation-remains-hot-for-a-while const value = element.obj.value element.el.style.setProperty('--x', value.position.x + 'px') element.el.style.setProperty('--y', value.position.y + 'px') } setTimeout(render, playbackControlObj.value.interval) } render() ================================================ FILE: packages/playground/src/tests/reading-obj-value/reading obj value.theatre-project-state.json ================================================ { "sheetsById": { "sample sheet": { "staticOverrides": { "byObject": { "sample object": { "position": { "x": 303, "y": 268 }, "interval": 50 } } }, "sequence": { "subUnitsPerUnit": 30, "length": 10, "type": "PositionalSequence", "tracksByObject": { "sample object": { "trackData": { "2LcFioJysu": { "type": "BasicKeyframedTrack", "__debugName": "sample object:[\"position\",\"x\"]", "keyframes": [ { "id": "Kxy5pUojNt", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 303 }, { "id": "Gw4Mws7OD9", "position": 1.833, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 30.799745441261848 }, { "id": "Hf8yST9Wh5", "position": 8, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 414 }, { "id": "yVYv4gA4oO", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 303 } ] }, "oWt_kxiglj": { "type": "BasicKeyframedTrack", "__debugName": "sample object:[\"position\",\"y\"]", "keyframes": [ { "id": "idPYVcMMFV", "position": 0, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 268 }, { "id": "DkaWGJJs5y", "position": 3.1, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 136.76766033714398 }, { "id": "wSMOxRFOHI", "position": 5, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 518 }, { "id": "iurvWeyPBD", "position": 7.3, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 274.6142665912034 }, { "id": "9IRlJBphrf", "position": 10, "connectedRight": true, "handles": [ 0.5, 1, 0.5, 0 ], "value": 268 } ] } }, "trackIdByPropPath": { "[\"position\",\"x\"]": "2LcFioJysu", "[\"position\",\"y\"]": "oWt_kxiglj" } } } } } }, "definitionVersion": "0.4.0", "revisionHistory": [ "B_AcNZ7EnqbSzdJj" ] } ================================================ FILE: packages/playground/src/tests/setting-static-props/index.html ================================================ Theatre.js Playground
================================================ FILE: packages/playground/src/tests/setting-static-props/index.tsx ================================================ import theatre from '@theatre/core' import {getProject} from '@theatre/core' void theatre.init({studio: true, usePersistentStorage: false}) const project = getProject('sample project') const sheet = project.sheet('sample sheet') const obj = sheet.object('sample object', { position: { x: 0, y: 0, }, }) ================================================ FILE: packages/playground/src/tests/setting-static-props/test.e2e.ts ================================================ import {test, expect} from '@playwright/test' const isMac = process.platform === 'darwin' const delay = (dur: number) => new Promise((resolve) => setTimeout(resolve, dur)) test.describe('setting-static-props', () => { // temporarily skipping this test until undo/redo is implemented in Saaz test.skip('Undo/redo', async ({page}) => { await page.goto('./tests/setting-static-props/') // https://github.com/microsoft/playwright/issues/12298 // The div does in fact intercept pointer events, but it is meant to 🤦‍ await page .locator('span:has-text("sample object")') .first() .click({force: true}) const detailPanel = page.locator('[data-testid="DetailPanel-Object"]') const firstInput = detailPanel.locator('input[type="text"]').first() // Click input[type="text"] >> nth=0 await firstInput.click() // Fill input[type="text"] >> nth=0 await firstInput.fill('1') // Press Enter await firstInput.press('Enter') const secondInput = detailPanel.locator('input[type="text"]').nth(1) // Click input[type="text"] >> nth=1 await secondInput.click() // Fill input[type="text"] >> nth=1 await secondInput.fill('2') // Press Enter await secondInput.press('Enter') const metaKey = isMac ? 'Meta' : 'Control' // Press z with modifiers await page.locator('body').press(`${metaKey}+z`) await expect(firstInput).toHaveAttribute('value', '1') await expect(secondInput).toHaveAttribute('value', '0') await page.locator('body').press(`${metaKey}+Shift+z`) await expect(firstInput).toHaveAttribute('value', '1') await expect(secondInput).toHaveAttribute('value', '2') await expect(page).toHaveScreenshot() }) }) ================================================ FILE: packages/playground/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "dist", "lib": ["ESNext", "DOM"], "rootDir": ".", "types": ["jest", "node"], "emitDeclarationOnly": false, "composite": true, "resolveJsonModule": true }, "references": [ {"path": "../core"}, {"path": "../studio"}, {"path": "../dataverse"}, {"path": "../r3f"}, {"path": "../theatric"} ], "include": [ "./src/**/*", "./src/**/*.json", "./devEnv/**/*", "./vite.config.ts" ] } ================================================ FILE: packages/playground/vite.config.ts ================================================ import {defineConfig} from 'vite' import react from '@vitejs/plugin-react-swc' import path from 'path' import fg from 'fast-glob' import {getAliasesFromTsConfigForRollup} from '../../devEnv/getAliasesFromTsConfig' import {definedGlobals} from '../core/devEnv/definedGlobals' import * as dotenv from 'dotenv' import * as fs from 'fs' const fromPlaygroundDir = (folder: string) => path.resolve(__dirname, folder) const srcDir = fromPlaygroundDir('src') const sharedDir = fromPlaygroundDir('src/shared') const personalDir = fromPlaygroundDir('src/personal') const testDir = fromPlaygroundDir('src/tests') const repoRoot = path.resolve(__dirname, '../..') function findAppUrl() { const defaultUrl = 'https://app.theatrejs.com' function validateURL(url: string) { const pattern = new RegExp('^https?:\\/\\/[^\\s/$.?#].[^\\s]*$', 'i') return pattern.test(url) } const pathToAppEnv = path.resolve(repoRoot, 'packages/app/.env') const relativePath = path.relative(repoRoot, pathToAppEnv) if (!fs.existsSync(pathToAppEnv)) { console.warn( `WARNING: the .env file at ${relativePath} does not exist, so we'll assume the web app's url is at https://app.theatrejs.com`, ) } else { const envFileContent = fs.readFileSync(pathToAppEnv, {encoding: 'utf-8'}) try { const webAppEnv = dotenv.parse(envFileContent) const url = 'http://' + webAppEnv.HOST + ':' + webAppEnv.PORT if (validateURL(url)) { console.info(`Using ${url} as the app url, read from ${relativePath}`) return url } else { console.warn( `WARNING: HOST/PORT values in ${relativePath} don't form a correct URL. Defaulting to ${defaultUrl}`, ) return defaultUrl } } catch (err) { console.warn(`WARNING: Could not read ${relativePath}`) } } return defaultUrl } // https://vitejs.dev/config/ const config = defineConfig(async ({command}) => { const dev = command === 'serve' console.log('dev', dev) const groups = { shared: await fg(path.join(sharedDir, '*/index.html')), personal: await fg(path.join(personalDir, '*/index.html')), test: await fg(path.join(testDir, '*/index.html')), } const rollupInputs = (() => { // eg ['path/to/src/group/playground/index.html'] const paths = ([] as string[]).concat(...Object.values(groups)) // eg ['group/playground'] const names = paths.map((entry) => { // convert "/path/to/src/group/playground/index.html" to "group/playground" const relativePath = path.relative(srcDir, entry) const entryName = relativePath.replace(/\/index\.html$/, '') return entryName }) // eg { 'group/playground': 'path/to/src/group/playground/index.html' } return Object.fromEntries(names.map((name, index) => [name, paths[index]])) })() return { root: srcDir, plugins: [react()], appType: 'mpa', server: { port: 8082, // base: '/playground/', }, assetsInclude: ['**/*.gltf', '**/*.glb'], resolve: { /* This will alias paths like `@theatre/core` to `path/to/theatre/core/src/index.ts` and so on, so vite won't treat the monorepo's packages as externals and won't pre-bundle them. */ alias: [...getAliasesFromTsConfigForRollup()], }, define: { ...definedGlobals, 'window.__IS_VISUAL_REGRESSION_TESTING': 'false', 'process.env.BACKEND_URL': JSON.stringify(findAppUrl()), }, optimizeDeps: { exclude: dev ? ['@theatre/core', '@theatre/studio'] : [], // include: !dev ? ['@theatre/core', '@theatre/studio'] : [], // needsInterop: ['@theatre/core', '@theatre/studio'], }, build: { outDir: '../build', minify: false, sourcemap: true, rollupOptions: { input: { ...rollupInputs, main: fromPlaygroundDir('src/index.html'), }, }, }, } }) export default config ================================================ FILE: packages/r3f/.gitignore ================================================ /dist ================================================ FILE: packages/r3f/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: packages/r3f/README.md ================================================ # @theatre/r3f A [Theatre.js](https://github.com/AriaMinaei/theatre) extension for [THREE.js](https://threejs.org/) with [React Three Fiber](https://github.com/pmndrs/react-three-fiber). **Here be dragons! 🐉** `@theatre/r3f` is pre-release software, the API, the internal logic, and even the package name can and will drastically change at any time, without warning. ## Quickstart ```bash # r3f and its deps yarn add react yarn add three yarn add @react-three/fiber # Theatre.js yarn add @theatre/core yarn add @theatre/studio yarn add @theatre/r3f ``` ```tsx import React from 'react'; import { Canvas } from 'react-three-fiber'; import {editable as e, SheetProvider, extension} from '@theatre/r3f'; import studio from '@theatre/studio'; studio.extend(extension) studio.initialize() export default function App() { return ( {/* Mark objects as editable. */} {/* Properties in the code are used as initial values and reset points in the editor. */} ); } ``` ## Why When creating a 3D scene for react-three-fiber, you can choose two routes: you can either code it in r3f, which gives you reactivity, and the flexibility that comes with it, or you can use a 3D software like Blender and export it, but then if you want to dynamically modify that scene at runtime, you'll have to fall back to imperative code. The best middle ground so far has been *gltfjsx*, which generates JSX from your exported scene, however it still involves a lot of manual work if you want to split your scene into components, and any modifications you make will have to be reapplied if you make changes to the scene. `@theatre/r3f` aims to fill this gap by allowing you to set up your scene in JSX, giving you reactivity, while allowing you to tweak the properties of these objects in a visual editor, including their transforms, which you can then bake into a json file to be used by the runtime in production. An explicit goal of the project is to mirror regular react-three-fiber code as much as possible. This lets you add it to an existing project with ease, take it out when you don't need it, and generally use it as little or as much as you want, without feeling locked in. ## API ### `editable` Use it to make objects editable. The properties on `editable` mirror the intrinsic elements of react-three-fiber, however there's no full parity yet. E.g. if you want to create an editable ``, you do it by using `` instead. These elements have the same interface as the normal ones, with the addition of the below props. Any editable property you set in the code (like `position`) will be used as an initial value/reset point in the editor. `editable` is also a function, which allows you to make your custom components editable. Your component does have to be compatible with the interface of the editable object type it is meant to represent. You need to pass it the component you want to wrap, and the object type it represents (see object types). ```ts import { editable } from '@theatre/r3f'; import { PerspectiveCamera } from '@react-three/drei'; const EditableCamera = editable(PerspectiveCamera, 'perspectiveCamera'); ``` #### Props `theatreKey: string`: a unique name used to identify the object in the editor. ### `` Provider component you need to wrap any scene with that you use editable components in. #### Props `sheet: TheatreSheetObject`: A function that returns the Theatre.js sheet associated with the scene. ## Object types React Three Editable currently supports the following object types: - group - mesh - spotLight - directionalLight - pointLight - perspectiveCamera - orthographicCamera These are available as properties of `editable`, and you need to pass them as the second parameter when wrapping custom components. ================================================ FILE: packages/r3f/devEnv/bundle.ts ================================================ import path = require('path') import {build} from 'esbuild' const definedGlobals = { 'process.env.NODE_ENV': JSON.stringify('production'), } void createBundles() async function createBundles() { await Promise.all([createMainBundle(), createExtensionBundle()]) async function createMainBundle() { const pathToEntry = path.join(__dirname, '../src/index.ts') const esbuildConfig: Parameters[0] = { entryPoints: [pathToEntry], target: ['es6'], loader: {'.svg': 'text', '.png': 'dataurl'}, bundle: true, sourcemap: true, define: {...definedGlobals}, external: [ '@theatre/core', '@theatre/dataverse', '@theatre/react', '@theatre/studio', 'react', 'react-dom', 'three', '@react-three/fiber', ], platform: 'browser', mainFields: ['browser', 'module', 'main'], conditions: ['browser', 'node'], outfile: path.join(__dirname, '../dist/index.js'), format: 'cjs', metafile: true, } const result = await Promise.all([ build(esbuildConfig), build({ ...esbuildConfig, outfile: path.join(__dirname, '../dist/index.esm.js'), format: 'esm', }), ]) } /** * We were initially externalizing react+fiber+drei+stdlib and having them as peer deps. Once we started to test this setup with different * versions of each, we realized that: * * 1. It can be confusing for npm users to satisfy the peer dep ranges. I (Aria) struggled to get the peer deps right in a sample project. * 2. More importantly, some permutations of these deps ended up not necessarily working together even though they satisfied the peer dep * ranges. * 3. Also, since react 17 and 18 have subtly different behaviors (useEffect, suspend, etc), we thought that us having to support both of * those behaviors in the snapshot editor is probably not that useful to the user. So we thought removing that surface area by bundling * react into the snapshot editor would reduce the chance of running into bugs caused by the differences between react 17 and 18. * * So we made the call to bundle all of these libraries in the `/extension` bundle. * * The downsides we thought about: * * 1. The bundle size of the snapshot editor increases, but since users don't ship the snapshot editor to their end users, we thought this * should be tolerable (let us know if it's not). * 2. Another downside we thought of is that having two versions of react and fiber on the same page may cause issues, but we haven't ran * into any yet, so don't know if those issues couldn't be worked around. */ async function createExtensionBundle() { const pathToEntry = path.join(__dirname, '../src/extension/index.ts') const esbuildConfig: Parameters[0] = { entryPoints: [pathToEntry], target: 'es6', loader: {'.svg': 'text', '.png': 'dataurl'}, bundle: true, sourcemap: true, define: {...definedGlobals}, external: [ '@theatre/core', '@theatre/studio', '@theatre/dataverse', '@theatre/r3f', 'three', // '@react-three/fiber', // '@react-three/drei', // 'three-stdlib', ], platform: 'browser', mainFields: ['browser', 'module', 'main'], conditions: ['browser'], outfile: path.join(__dirname, '../dist/extension/index.js'), format: 'cjs', metafile: true, /** * Don't minify the extension bundle because it'll eventually get minified by the bundler of the user's project. * However, we do want to tree shake the bundle and minify the syntax, so at least all the `if (false) {...}` blocks * are removed. This also removes React's error that says "react is running in production mode but dead code elimination has not been applied...". */ minifyIdentifiers: false, minifySyntax: true, minifyWhitespace: false, treeShaking: true, } const result = await Promise.all([ build(esbuildConfig), build({ ...esbuildConfig, outfile: path.join(__dirname, '../dist/extension/index.esm.js'), format: 'esm', }), ]) } } ================================================ FILE: packages/r3f/devEnv/tsconfig.json ================================================ { } ================================================ FILE: packages/r3f/package.json ================================================ { "name": "@theatre/r3f", "version": "0.7.0", "license": "Apache-2.0", "authors": [ { "name": "Andrew Prifer", "email": "andrew.prifer@gmail.com", "url": "https://github.com/AndrewPrifer" }, { "name": "Aria Minaei", "email": "aria@theatrejs.com", "url": "https://github.com/AriaMinaei" } ], "repository": { "type": "git", "url": "https://github.com/AriaMinaei/theatre", "directory": "packages/r3f" }, "main": "dist/index.js", "module": "dist/index.esm.js", "sideEffects": false, "files": [ "dist/**/*" ], "exports": { ".": { "import": "./dist/index.esm.js", "require": "./dist/index.js", "types": "./dist/index.d.ts" }, "./dist/extension": { "import": "./dist/extension/index.esm.js", "require": "./dist/extension/index.js", "types": "./dist/extension/index.d.ts" } }, "scripts": { "prepack": "yarn run build", "typecheck": "yarn run build", "build": "run-s build:ts build:js", "build:js": "tsx devEnv/bundle.ts", "build:ts": "tsc --build ./tsconfig.json", "prepublish": "yarn run build", "clean": "rm -rf ./dist && rm -f tsconfig.tsbuildinfo" }, "devDependencies": { "@react-three/drei": "^9.80.1", "@react-three/fiber": "^8.13.6", "@theatre/dataverse": "workspace:*", "@theatre/react": "workspace:*", "@types/jest": "^26.0.23", "@types/lodash-es": "^4.17.4", "@types/node": "^15.6.2", "@types/react": "^18.2.18", "@types/react-dom": "^18.2.7", "@types/styled-components": "^5.1.26", "@types/three": "0.155.0", "esbuild": "^0.18.17", "lodash-es": "^4.17.21", "npm-run-all": "^4.1.5", "polished": "^4.1.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.2.0", "react-merge-refs": "^2.0.2", "react-shadow": "^20.4.0", "react-use-measure": "^2.1.1", "reakit": "^1.3.8", "styled-components": "^5.3.11", "three": "0.155.0", "three-stdlib": "^2.24.1", "tsx": "4.7.0", "typescript": "5.1.6", "zustand": "^3.5.1" }, "peerDependencies": { "@react-three/fiber": "^8.13.6", "@theatre/core": "*", "@theatre/studio": "*", "react": ">=17.0.2", "react-dom": ">=17.0.2", "three": ">=0.155.0" } } ================================================ FILE: packages/r3f/src/.eslintrc.js ================================================ module.exports = { rules: { 'no-restricted-syntax': [ 'error', { selector: `ImportDeclaration[source.value=/@theatre\\u002F(studio|core)\\u002F/]`, message: `Importing Theatre's submodules would not work in the production build.`, }, ], }, } ================================================ FILE: packages/r3f/src/drei/OrthographicCamera.tsx ================================================ import * as React from 'react' import type { OrthographicCamera as OrthographicCameraImpl, Object3D, } from 'three' import {useFrame, useThree} from '@react-three/fiber' import {mergeRefs} from 'react-merge-refs' import {editable} from '../index' import {Vector3} from 'three' import type {MutableRefObject} from 'react' import {editorStore} from '../main/store' export type OrthographicCameraProps = Omit< JSX.IntrinsicElements['orthographicCamera'], 'lookAt' > & { lookAt?: | [number, number, number] | Vector3 | MutableRefObject makeDefault?: boolean manual?: boolean children?: React.ReactNode } export const OrthographicCamera = editable( React.forwardRef( ({makeDefault, lookAt, ...props}: OrthographicCameraProps, ref) => { const set = useThree(({set}) => set) const camera = useThree(({camera}) => camera) const size = useThree(({size}) => size) const cameraRef = React.useRef(null!) React.useLayoutEffect(() => { if (!props.manual) { cameraRef.current.updateProjectionMatrix() } }, [size, props]) React.useLayoutEffect(() => { cameraRef.current.updateProjectionMatrix() }) React.useLayoutEffect(() => { if (makeDefault) { const oldCam = camera set(() => ({camera: cameraRef.current!})) return () => set(() => ({camera: oldCam})) } // The camera should not be part of the dependency list because this components camera is a stable reference // that must exchange the default, and clean up after itself on unmount. }, [cameraRef, makeDefault, set]) useFrame(() => { if (lookAt && cameraRef.current) { cameraRef.current.lookAt( Array.isArray(lookAt) ? new Vector3(...lookAt) : (lookAt as MutableRefObject).current ? (lookAt as MutableRefObject).current.position : (lookAt as Vector3), ) // how could we make it possible for users to do something like this too? const snapshot = editorStore.getState().editablesSnapshot if (snapshot) { snapshot[ cameraRef.current.userData.__storeKey ].proxyObject?.rotation.copy(cameraRef.current.rotation) } } }) return ( ) }, ), 'orthographicCamera', ) ================================================ FILE: packages/r3f/src/drei/PerspectiveCamera.tsx ================================================ import * as React from 'react' import type {PerspectiveCamera as PerspectiveCameraImpl, Object3D} from 'three' import {useFrame, useThree} from '@react-three/fiber' import {mergeRefs} from 'react-merge-refs' import {editable} from '../index' import {Vector3} from 'three' import {editorStore} from '../main/store' import type {MutableRefObject} from 'react' export type PerspectiveCameraProps = Omit< JSX.IntrinsicElements['perspectiveCamera'], 'lookAt' > & { lookAt?: | [number, number, number] | Vector3 | MutableRefObject makeDefault?: boolean manual?: boolean children?: React.ReactNode } export const PerspectiveCamera = editable( React.forwardRef( ({makeDefault, lookAt, ...props}: PerspectiveCameraProps, ref) => { const set = useThree(({set}) => set) const camera = useThree(({camera}) => camera) const size = useThree(({size}) => size) const cameraRef = React.useRef(null!) React.useLayoutEffect(() => { if (!props.manual) { cameraRef.current.aspect = size.width / size.height } }, [size, props]) React.useLayoutEffect(() => { cameraRef.current.updateProjectionMatrix() }) React.useLayoutEffect(() => { if (makeDefault) { const oldCam = camera set(() => ({camera: cameraRef.current!})) return () => set(() => ({camera: oldCam})) } // The camera should not be part of the dependency list because this components camera is a stable reference // that must exchange the default, and clean up after itself on unmount. }, [cameraRef, makeDefault, set]) useFrame(() => { if (lookAt && cameraRef.current) { cameraRef.current.lookAt( Array.isArray(lookAt) ? new Vector3(...lookAt) : (lookAt as MutableRefObject).current ? (lookAt as MutableRefObject).current.position : (lookAt as Vector3), ) // how could we make it possible for users to do something like this too? const snapshot = editorStore.getState().editablesSnapshot if (snapshot) { snapshot[ cameraRef.current.userData.__storeKey ].proxyObject?.rotation.copy(cameraRef.current.rotation) } } }) return }, ), 'perspectiveCamera', ) ================================================ FILE: packages/r3f/src/drei/index.ts ================================================ export * from './PerspectiveCamera' export * from './OrthographicCamera' ================================================ FILE: packages/r3f/src/extension/.eslintrc.js ================================================ module.exports = { rules: { 'no-restricted-syntax': [ 'error', { selector: `ImportDeclaration[importKind!='type'][source.value=/\\u002Fmain\\u002F/]`, message: `The extension should not be able to import the internals of main. If you need to use this API, expose it as __private_api from @theatre/r3f/src/index.ts`, }, ], }, } ================================================ FILE: packages/r3f/src/extension/InfiniteGridHelper/index.ts ================================================ // Inspired by https://github.com/Fyrestar/THREE.InfiniteGridHelper import { Color, DoubleSide, GLSL3, Mesh, PlaneGeometry, ShaderMaterial, } from 'three' export class InfiniteGridHelper extends Mesh { constructor({ divisions = 10, scale = 0.1, color = new Color('white'), distance = 8000, subgridOpacity = 0.05, gridOpacity = 0.15, } = {}) { const geometry = new PlaneGeometry(2, 2, 1, 1) const material = new ShaderMaterial({ // Needs to be set so threejs doesn't mess things up in glsl3 code by trying to make glsl1 forward compatible glslVersion: GLSL3, side: DoubleSide, uniforms: { uScale: { value: scale, }, uDivisions: { value: divisions, }, uColor: { value: color, }, uDistance: { value: distance, }, uSubgridOpacity: { value: subgridOpacity, }, uGridOpacity: { value: gridOpacity, }, }, transparent: true, // language=GLSL vertexShader: ` out vec3 worldPosition; uniform float uDistance; void main() { // Scale the plane by the drawing distance worldPosition = position.xzy * uDistance; worldPosition.xz += cameraPosition.xz; gl_Position = projectionMatrix * modelViewMatrix * vec4(worldPosition, 1.0); } `, // language=GLSL fragmentShader: ` out vec4 fragColor; in vec3 worldPosition; uniform float uDivisions; uniform float uScale; uniform vec3 uColor; uniform float uDistance; uniform float uSubgridOpacity; uniform float uGridOpacity; float getGrid(float gapSize) { vec2 worldPositionByDivision = worldPosition.xz / gapSize; // Inverted, 0 where line, >1 where there's no line // We use the worldPosition (which in this case we use similarly to UVs) differential to control the anti-aliasing // We need to do the -0.5)-0.5 trick because the result fades out from 0 to 1, and we want both // worldPositionByDivision == 0.3 and worldPositionByDivision == 0.7 to result in the same fade, i.e. 0.3, // otherwise only one side of the line will be anti-aliased vec2 grid = abs(fract(worldPositionByDivision-0.5)-0.5) / fwidth(worldPositionByDivision) / 2.0; float gridLine = min(grid.x, grid.y); // Uninvert and clamp return 1.0 - min(gridLine, 1.0); } void main() { float cameraDistanceToGridPlane = distance(cameraPosition.y, worldPosition.y); float cameraDistanceToFragmentOnGridPlane = distance(cameraPosition.xz, worldPosition.xz); // The size of the grid and subgrid are powers of each other and they are determined based on camera distance. // The current grid will become the next subgrid when it becomes too small, and its next power becomes the new grid. float subGridPower = pow(uDivisions, floor(log(cameraDistanceToGridPlane) / log(uDivisions))); float gridPower = subGridPower * uDivisions; // If we want to fade both the grid and its subgrid, we need to displays 3 different opacities, with the next grid being the third float nextGridPower = gridPower * uDivisions; // 1 where grid, 0 where no grid float subgrid = getGrid(subGridPower * uScale); float grid = getGrid(gridPower * uScale); float nextGrid = getGrid(nextGridPower * uScale); // Where we are between the introduction of the current grid power and when we switch to the next grid power float stepPercentage = (cameraDistanceToGridPlane - subGridPower)/(gridPower - subGridPower); // The last x percentage of the current step over which we want to fade float fadeRange = 0.3; // We calculate the fade percentage from the step percentage and the fade range float fadePercentage = max(stepPercentage - 1.0 + fadeRange, 0.0) / fadeRange; // Set base opacity based on how close we are to the drawing distance, with a cubic falloff float baseOpacity = subgrid * pow(1.0 - min(cameraDistanceToFragmentOnGridPlane / uDistance, 1.0), 3.0); // Shade the subgrid fragColor = vec4(uColor.rgb, (baseOpacity - fadePercentage) * uSubgridOpacity); // Somewhat arbitrary additional fade coefficient to counter anti-aliasing popping when switching between grid powers float fadeCoefficient = 0.5; // Shade the grid fragColor.a = mix(fragColor.a, baseOpacity * uGridOpacity - fadePercentage * (uGridOpacity - uSubgridOpacity) * fadeCoefficient, grid); // Shade the next grid fragColor.a = mix(fragColor.a, baseOpacity * uGridOpacity, nextGrid); if (fragColor.a <= 0.0) discard; } `, extensions: { derivatives: true, }, }) super(geometry, material) this.frustumCulled = false } } ================================================ FILE: packages/r3f/src/extension/components/DragDetector.tsx ================================================ import type {FC, ReactNode} from 'react' import React, { createContext, useContext, useEffect, useRef, useState, } from 'react' const dragDetectorContext = createContext(false) interface DragDetectorProviderProps { children: ReactNode } export const DragDetectorProvider: FC = ({ children, }) => { const mouseDownRef = useRef(false) const [dragging, setDragging] = useState(false) useEffect(() => { document.addEventListener('mousedown', () => (mouseDownRef.current = true)) document.addEventListener('mousemove', () => { if (mouseDownRef.current) { setDragging(true) } }) document.addEventListener('mouseup', () => { mouseDownRef.current = false setDragging(false) }) }, []) return ( {children} ) } export const useDragDetector = () => useContext(dragDetectorContext) ================================================ FILE: packages/r3f/src/extension/components/EditableProxy.tsx ================================================ import type {Object3D} from 'three' import React, {useEffect, useLayoutEffect, useMemo, useState} from 'react' import {Sphere, Html} from '@react-three/drei' import shallow from 'zustand/shallow' import {useSelected} from './useSelected' import {useVal} from '@theatre/react' import {getEditorSheetObject} from '../editorStuff' import type {IconID} from '../icons' import icons from '../icons' import type {Helper} from '../../main/editableFactoryConfigUtils' import {invalidate, useFrame, useThree} from '@react-three/fiber' import {useDragDetector} from './DragDetector' import useExtensionStore from '../useExtensionStore' import {getStudioSync} from '@theatre/core' export interface EditableProxyProps { storeKey: string object: Object3D } const EditableProxy: React.FC = ({storeKey, object}) => { const editorObject = getEditorSheetObject() const [setSnapshotProxyObject, editables] = useExtensionStore( (state) => [state.setSnapshotProxyObject, state.editables], shallow, ) const dragging = useDragDetector() const editable = editables[storeKey] const selected = useSelected() const showOverlayIcons = useVal(editorObject?.props.viewport.showOverlayIcons) ?? false useEffect(() => { setSnapshotProxyObject(object, storeKey) return () => setSnapshotProxyObject(null, storeKey) }, [storeKey, object, setSnapshotProxyObject]) useLayoutEffect(() => { const originalVisibility = object.visible if (editable?.visibleOnlyInEditor) { object.visible = true } return () => { object.visible = originalVisibility } }, [editable?.visibleOnlyInEditor, object.visible]) const [hovered, setHovered] = useState(false) // Helpers const scene = useThree((state) => state.scene) const helper = useMemo( () => editable?.objectConfig.createHelper?.(object), [object], ) useEffect(() => { if (helper == undefined) { return } if (selected === storeKey || hovered) { scene.add(helper) invalidate() } return () => { scene.remove(helper) invalidate() } }, [selected, hovered, helper, scene]) useFrame(() => { if (helper == undefined) { return } if (helper.update) { helper.update() } }) useEffect(() => { if (dragging) { setHovered(false) } }, [dragging]) // subscribe to external changes useEffect(() => { if (!editable) return const sheetObject = editable.sheetObject const objectConfig = editable.objectConfig const setFromTheatre = (newValues: any) => { // @ts-ignore Object.entries(objectConfig.props).forEach(([key, value]) => { // @ts-ignore return value.apply(newValues[key], object) }) objectConfig.updateObject?.(object) invalidate() } setFromTheatre(sheetObject.value) const untap = sheetObject.onValuesChange(setFromTheatre) return () => { untap() } }, [editable]) if (!editable) return null return ( <> { if (e.delta < 2) { e.stopPropagation() const theatreObject = useExtensionStore.getState().editables[storeKey].sheetObject if (!theatreObject) { console.log('no Theatre.js object for', storeKey) } else { const studio = getStudioSync(true)! studio.setSelection([theatreObject]) } } }} onPointerOver={ !dragging ? (e) => { e.stopPropagation() setHovered(true) } : undefined } onPointerOut={ !dragging ? (e) => { e.stopPropagation() setHovered(false) } : undefined } > {(showOverlayIcons || (editable.objectConfig.dimensionless && selected !== storeKey)) && (
{icons[editable.objectConfig.icon as IconID]}
)} {editable.objectConfig.dimensionless && ( { if (e.delta < 2) { e.stopPropagation() const theatreObject = useExtensionStore.getState().editables[storeKey].sheetObject if (!theatreObject) { console.log('no Theatre.js object for', storeKey) } else { const studio = getStudioSync(true)! studio.setSelection([theatreObject]) } } }} userData={{helper: true}} > )}
) } export default EditableProxy ================================================ FILE: packages/r3f/src/extension/components/OrbitControls/OrbitControlsImpl.ts ================================================ import type {Matrix4} from 'three' import { EventDispatcher, MOUSE, OrthographicCamera, PerspectiveCamera, Quaternion, Spherical, TOUCH, Vector2, Vector3, } from 'three' // Almost an exact copy of https://github.com/pmndrs/three-stdlib/blob/4c04593ee49bb0b022025718844f3ce2b21f67bf/src/controls/OrbitControls.ts // The only change is that we added `(if (altKey)` at line 866 to only rotate if alt key is pressed // This set of controls performs orbiting, dollying (zooming), and panning. // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). // // Orbit - left mouse / touch: one-finger move // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish // Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move const moduloWrapAround = (offset: number, capacity: number) => ((offset % capacity) + capacity) % capacity class OrbitControls extends EventDispatcher { object: PerspectiveCamera | OrthographicCamera domElement: HTMLElement | undefined // Set to false to disable this control enabled = true // "target" sets the location of focus, where the object orbits around target = new Vector3() // How far you can dolly in and out ( PerspectiveCamera only ) minDistance = 0 maxDistance = Infinity // How far you can zoom in and out ( OrthographicCamera only ) minZoom = 0 maxZoom = Infinity // How far you can orbit vertically, upper and lower limits. // Range is 0 to Math.PI radians. minPolarAngle = 0 // radians maxPolarAngle = Math.PI // radians // How far you can orbit horizontally, upper and lower limits. // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) minAzimuthAngle = -Infinity // radians maxAzimuthAngle = Infinity // radians // Set to true to enable damping (inertia) // If damping is enabled, you must call controls.update() in your animation loop enableDamping = false dampingFactor = 0.05 // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. // Set to false to disable zooming enableZoom = true zoomSpeed = 1.0 // Set to false to disable rotating enableRotate = true rotateSpeed = 1.0 // Set to false to disable panning enablePan = true panSpeed = 1.0 screenSpacePanning = true // if false, pan orthogonal to world-space direction camera.up keyPanSpeed = 7.0 // pixels moved per arrow key push // Set to true to automatically rotate around the target // If auto-rotate is enabled, you must call controls.update() in your animation loop autoRotate = false autoRotateSpeed = 2.0 // 30 seconds per orbit when fps is 60 reverseOrbit = false // true if you want to reverse the orbit to mouse drag from left to right = orbits left // The four arrow keys keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown', } // Mouse buttons mouseButtons: Partial<{ LEFT: MOUSE MIDDLE: MOUSE RIGHT: MOUSE }> = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN, } // Touch fingers touches: Partial<{ ONE: TOUCH TWO: TOUCH }> = {ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN} target0: Vector3 position0: Vector3 zoom0: number // the target DOM element for key events _domElementKeyEvents: any = null getPolarAngle: () => number getAzimuthalAngle: () => number setPolarAngle: (x: number) => void setAzimuthalAngle: (x: number) => void getDistance: () => number listenToKeyEvents: (domElement: HTMLElement) => void stopListenToKeyEvents: () => void saveState: () => void reset: () => void update: () => void connect: (domElement: HTMLElement) => void dispose: () => void constructor( object: PerspectiveCamera | OrthographicCamera, domElement?: HTMLElement, ) { super() this.object = object this.domElement = domElement // for reset this.target0 = this.target.clone() this.position0 = this.object.position.clone() this.zoom0 = this.object.zoom // // public methods // this.getPolarAngle = (): number => spherical.phi this.getAzimuthalAngle = (): number => spherical.theta this.setPolarAngle = (value: number): void => { // use modulo wrapping to safeguard value let phi = moduloWrapAround(value, 2 * Math.PI) let currentPhi = spherical.phi // convert to the equivalent shortest angle if (currentPhi < 0) currentPhi += 2 * Math.PI if (phi < 0) phi += 2 * Math.PI let phiDist = Math.abs(phi - currentPhi) if (2 * Math.PI - phiDist < phiDist) { if (phi < currentPhi) { phi += 2 * Math.PI } else { currentPhi += 2 * Math.PI } } sphericalDelta.phi = phi - currentPhi scope.update() } this.setAzimuthalAngle = (value: number): void => { // use modulo wrapping to safeguard value let theta = moduloWrapAround(value, 2 * Math.PI) let currentTheta = spherical.theta // convert to the equivalent shortest angle if (currentTheta < 0) currentTheta += 2 * Math.PI if (theta < 0) theta += 2 * Math.PI let thetaDist = Math.abs(theta - currentTheta) if (2 * Math.PI - thetaDist < thetaDist) { if (theta < currentTheta) { theta += 2 * Math.PI } else { currentTheta += 2 * Math.PI } } sphericalDelta.theta = theta - currentTheta scope.update() } this.getDistance = (): number => scope.object.position.distanceTo(scope.target) this.listenToKeyEvents = (domElement: HTMLElement): void => { domElement.addEventListener('keydown', onKeyDown) this._domElementKeyEvents = domElement } this.stopListenToKeyEvents = (): void => { this._domElementKeyEvents.removeEventListener('keydown', onKeyDown) this._domElementKeyEvents = null } this.saveState = (): void => { scope.target0.copy(scope.target) scope.position0.copy(scope.object.position) scope.zoom0 = scope.object.zoom } this.reset = (): void => { scope.target.copy(scope.target0) scope.object.position.copy(scope.position0) scope.object.zoom = scope.zoom0 scope.object.updateProjectionMatrix() scope.dispatchEvent(changeEvent) scope.update() state = STATE.NONE } // this method is exposed, but perhaps it would be better if we can make it private... this.update = ((): (() => void) => { const offset = new Vector3() const up = new Vector3(0, 1, 0) // so camera.up is the orbit axis const quat = new Quaternion().setFromUnitVectors(object.up, up) const quatInverse = quat.clone().invert() const lastPosition = new Vector3() const lastQuaternion = new Quaternion() const twoPI = 2 * Math.PI return function update(): boolean { const position = scope.object.position // update new up direction quat.setFromUnitVectors(object.up, up) quatInverse.copy(quat).invert() offset.copy(position).sub(scope.target) // rotate offset to "y-axis-is-up" space offset.applyQuaternion(quat) // angle from z-axis around y-axis spherical.setFromVector3(offset) if (scope.autoRotate && state === STATE.NONE) { rotateLeft(getAutoRotationAngle()) } if (scope.enableDamping) { spherical.theta += sphericalDelta.theta * scope.dampingFactor spherical.phi += sphericalDelta.phi * scope.dampingFactor } else { spherical.theta += sphericalDelta.theta spherical.phi += sphericalDelta.phi } // restrict theta to be between desired limits let min = scope.minAzimuthAngle let max = scope.maxAzimuthAngle if (isFinite(min) && isFinite(max)) { if (min < -Math.PI) min += twoPI else if (min > Math.PI) min -= twoPI if (max < -Math.PI) max += twoPI else if (max > Math.PI) max -= twoPI if (min <= max) { spherical.theta = Math.max(min, Math.min(max, spherical.theta)) } else { spherical.theta = spherical.theta > (min + max) / 2 ? Math.max(min, spherical.theta) : Math.min(max, spherical.theta) } } // restrict phi to be between desired limits spherical.phi = Math.max( scope.minPolarAngle, Math.min(scope.maxPolarAngle, spherical.phi), ) spherical.makeSafe() spherical.radius *= scale // restrict radius to be between desired limits spherical.radius = Math.max( scope.minDistance, Math.min(scope.maxDistance, spherical.radius), ) // move target to panned location if (scope.enableDamping === true) { scope.target.addScaledVector(panOffset, scope.dampingFactor) } else { scope.target.add(panOffset) } offset.setFromSpherical(spherical) // rotate offset back to "camera-up-vector-is-up" space offset.applyQuaternion(quatInverse) position.copy(scope.target).add(offset) scope.object.lookAt(scope.target) if (scope.enableDamping === true) { sphericalDelta.theta *= 1 - scope.dampingFactor sphericalDelta.phi *= 1 - scope.dampingFactor panOffset.multiplyScalar(1 - scope.dampingFactor) } else { sphericalDelta.set(0, 0, 0) panOffset.set(0, 0, 0) } scale = 1 // update condition is: // min(camera displacement, camera rotation in radians)^2 > EPS // using small-angle approximation cos(x/2) = 1 - x^2 / 8 if ( zoomChanged || lastPosition.distanceToSquared(scope.object.position) > EPS || 8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS ) { scope.dispatchEvent(changeEvent) lastPosition.copy(scope.object.position) lastQuaternion.copy(scope.object.quaternion) zoomChanged = false return true } return false } })() // https://github.com/mrdoob/three.js/issues/20575 this.connect = (domElement: HTMLElement): void => { if ((domElement as any) === document) { console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.', ) } scope.domElement = domElement // disables touch scroll // touch-action needs to be defined for pointer events to work on mobile // https://stackoverflow.com/a/48254578 scope.domElement.style.touchAction = 'none' scope.domElement.addEventListener('contextmenu', onContextMenu) scope.domElement.addEventListener('pointerdown', onPointerDown) scope.domElement.addEventListener('pointercancel', onPointerCancel) scope.domElement.addEventListener('wheel', onMouseWheel) } this.dispose = (): void => { scope.domElement?.removeEventListener('contextmenu', onContextMenu) scope.domElement?.removeEventListener('pointerdown', onPointerDown) scope.domElement?.removeEventListener('pointercancel', onPointerCancel) scope.domElement?.removeEventListener('wheel', onMouseWheel) scope.domElement?.ownerDocument.removeEventListener( 'pointermove', onPointerMove, ) scope.domElement?.ownerDocument.removeEventListener( 'pointerup', onPointerUp, ) if (scope._domElementKeyEvents !== null) { scope._domElementKeyEvents.removeEventListener('keydown', onKeyDown) } //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? } // // internals // const scope = this const changeEvent = {type: 'change'} const startEvent = {type: 'start'} const endEvent = {type: 'end'} const STATE = { NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_PAN: 4, TOUCH_DOLLY_PAN: 5, TOUCH_DOLLY_ROTATE: 6, } let state = STATE.NONE const EPS = 0.000001 // current position in spherical coordinates const spherical = new Spherical() const sphericalDelta = new Spherical() let scale = 1 const panOffset = new Vector3() let zoomChanged = false const rotateStart = new Vector2() const rotateEnd = new Vector2() const rotateDelta = new Vector2() const panStart = new Vector2() const panEnd = new Vector2() const panDelta = new Vector2() const dollyStart = new Vector2() const dollyEnd = new Vector2() const dollyDelta = new Vector2() const pointers: PointerEvent[] = [] const pointerPositions: {[key: string]: Vector2} = {} function getAutoRotationAngle(): number { return ((2 * Math.PI) / 60 / 60) * scope.autoRotateSpeed } function getZoomScale(): number { return Math.pow(0.95, scope.zoomSpeed) } function rotateLeft(angle: number): void { if (scope.reverseOrbit) { sphericalDelta.theta += angle } else { sphericalDelta.theta -= angle } } function rotateUp(angle: number): void { if (scope.reverseOrbit) { sphericalDelta.phi += angle } else { sphericalDelta.phi -= angle } } const panLeft = (() => { const v = new Vector3() return function panLeft(distance: number, objectMatrix: Matrix4) { v.setFromMatrixColumn(objectMatrix, 0) // get X column of objectMatrix v.multiplyScalar(-distance) panOffset.add(v) } })() const panUp = (() => { const v = new Vector3() return function panUp(distance: number, objectMatrix: Matrix4) { if (scope.screenSpacePanning === true) { v.setFromMatrixColumn(objectMatrix, 1) } else { v.setFromMatrixColumn(objectMatrix, 0) v.crossVectors(scope.object.up, v) } v.multiplyScalar(distance) panOffset.add(v) } })() // deltaX and deltaY are in pixels; right and down are positive const pan = (() => { const offset = new Vector3() return function pan(deltaX: number, deltaY: number) { const element = scope.domElement if ( element && scope.object instanceof PerspectiveCamera && scope.object.isPerspectiveCamera ) { // perspective const position = scope.object.position offset.copy(position).sub(scope.target) let targetDistance = offset.length() // half of the fov is center to top of screen targetDistance *= Math.tan(((scope.object.fov / 2) * Math.PI) / 180.0) // we use only clientHeight here so aspect ratio does not distort speed panLeft( (2 * deltaX * targetDistance) / element.clientHeight, scope.object.matrix, ) panUp( (2 * deltaY * targetDistance) / element.clientHeight, scope.object.matrix, ) } else if ( element && scope.object instanceof OrthographicCamera && scope.object.isOrthographicCamera ) { // orthographic panLeft( (deltaX * (scope.object.right - scope.object.left)) / scope.object.zoom / element.clientWidth, scope.object.matrix, ) panUp( (deltaY * (scope.object.top - scope.object.bottom)) / scope.object.zoom / element.clientHeight, scope.object.matrix, ) } else { // camera neither orthographic nor perspective console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.', ) scope.enablePan = false } } })() function dollyOut(dollyScale: number) { if ( scope.object instanceof PerspectiveCamera && scope.object.isPerspectiveCamera ) { scale /= dollyScale } else if ( scope.object instanceof OrthographicCamera && scope.object.isOrthographicCamera ) { scope.object.zoom = Math.max( scope.minZoom, Math.min(scope.maxZoom, scope.object.zoom * dollyScale), ) scope.object.updateProjectionMatrix() zoomChanged = true } else { console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.', ) scope.enableZoom = false } } function dollyIn(dollyScale: number) { if ( scope.object instanceof PerspectiveCamera && scope.object.isPerspectiveCamera ) { scale *= dollyScale } else if ( scope.object instanceof OrthographicCamera && scope.object.isOrthographicCamera ) { scope.object.zoom = Math.max( scope.minZoom, Math.min(scope.maxZoom, scope.object.zoom / dollyScale), ) scope.object.updateProjectionMatrix() zoomChanged = true } else { console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.', ) scope.enableZoom = false } } // // event callbacks - update the object state // function handleMouseDownRotate(event: MouseEvent) { rotateStart.set(event.clientX, event.clientY) } function handleMouseDownDolly(event: MouseEvent) { dollyStart.set(event.clientX, event.clientY) } function handleMouseDownPan(event: MouseEvent) { panStart.set(event.clientX, event.clientY) } function handleMouseMoveRotate(event: MouseEvent) { rotateEnd.set(event.clientX, event.clientY) rotateDelta .subVectors(rotateEnd, rotateStart) .multiplyScalar(scope.rotateSpeed) const element = scope.domElement if (element) { rotateLeft((2 * Math.PI * rotateDelta.x) / element.clientHeight) // yes, height rotateUp((2 * Math.PI * rotateDelta.y) / element.clientHeight) } rotateStart.copy(rotateEnd) scope.update() } function handleMouseMoveDolly(event: MouseEvent) { dollyEnd.set(event.clientX, event.clientY) dollyDelta.subVectors(dollyEnd, dollyStart) if (dollyDelta.y > 0) { dollyOut(getZoomScale()) } else if (dollyDelta.y < 0) { dollyIn(getZoomScale()) } dollyStart.copy(dollyEnd) scope.update() } function handleMouseMovePan(event: MouseEvent) { panEnd.set(event.clientX, event.clientY) panDelta.subVectors(panEnd, panStart).multiplyScalar(scope.panSpeed) pan(panDelta.x, panDelta.y) panStart.copy(panEnd) scope.update() } function handleMouseWheel(event: WheelEvent) { if (event.deltaY < 0) { dollyIn(getZoomScale()) } else if (event.deltaY > 0) { dollyOut(getZoomScale()) } scope.update() } function handleKeyDown(event: KeyboardEvent) { let needsUpdate = false switch (event.code) { case scope.keys.UP: pan(0, scope.keyPanSpeed) needsUpdate = true break case scope.keys.BOTTOM: pan(0, -scope.keyPanSpeed) needsUpdate = true break case scope.keys.LEFT: pan(scope.keyPanSpeed, 0) needsUpdate = true break case scope.keys.RIGHT: pan(-scope.keyPanSpeed, 0) needsUpdate = true break } if (needsUpdate) { // prevent the browser from scrolling on cursor keys event.preventDefault() scope.update() } } function handleTouchStartRotate() { if (pointers.length == 1) { rotateStart.set(pointers[0].pageX, pointers[0].pageY) } else { const x = 0.5 * (pointers[0].pageX + pointers[1].pageX) const y = 0.5 * (pointers[0].pageY + pointers[1].pageY) rotateStart.set(x, y) } } function handleTouchStartPan() { if (pointers.length == 1) { panStart.set(pointers[0].pageX, pointers[0].pageY) } else { const x = 0.5 * (pointers[0].pageX + pointers[1].pageX) const y = 0.5 * (pointers[0].pageY + pointers[1].pageY) panStart.set(x, y) } } function handleTouchStartDolly() { const dx = pointers[0].pageX - pointers[1].pageX const dy = pointers[0].pageY - pointers[1].pageY const distance = Math.sqrt(dx * dx + dy * dy) dollyStart.set(0, distance) } function handleTouchStartDollyPan() { if (scope.enableZoom) handleTouchStartDolly() if (scope.enablePan) handleTouchStartPan() } function handleTouchStartDollyRotate() { if (scope.enableZoom) handleTouchStartDolly() if (scope.enableRotate) handleTouchStartRotate() } function handleTouchMoveRotate(event: PointerEvent) { if (pointers.length == 1) { rotateEnd.set(event.pageX, event.pageY) } else { const position = getSecondPointerPosition(event) const x = 0.5 * (event.pageX + position.x) const y = 0.5 * (event.pageY + position.y) rotateEnd.set(x, y) } rotateDelta .subVectors(rotateEnd, rotateStart) .multiplyScalar(scope.rotateSpeed) const element = scope.domElement if (element) { rotateLeft((2 * Math.PI * rotateDelta.x) / element.clientHeight) // yes, height rotateUp((2 * Math.PI * rotateDelta.y) / element.clientHeight) } rotateStart.copy(rotateEnd) } function handleTouchMovePan(event: PointerEvent) { if (pointers.length == 1) { panEnd.set(event.pageX, event.pageY) } else { const position = getSecondPointerPosition(event) const x = 0.5 * (event.pageX + position.x) const y = 0.5 * (event.pageY + position.y) panEnd.set(x, y) } panDelta.subVectors(panEnd, panStart).multiplyScalar(scope.panSpeed) pan(panDelta.x, panDelta.y) panStart.copy(panEnd) } function handleTouchMoveDolly(event: PointerEvent) { const position = getSecondPointerPosition(event) const dx = event.pageX - position.x const dy = event.pageY - position.y const distance = Math.sqrt(dx * dx + dy * dy) dollyEnd.set(0, distance) dollyDelta.set(0, Math.pow(dollyEnd.y / dollyStart.y, scope.zoomSpeed)) dollyOut(dollyDelta.y) dollyStart.copy(dollyEnd) } function handleTouchMoveDollyPan(event: PointerEvent) { if (scope.enableZoom) handleTouchMoveDolly(event) if (scope.enablePan) handleTouchMovePan(event) } function handleTouchMoveDollyRotate(event: PointerEvent) { if (scope.enableZoom) handleTouchMoveDolly(event) if (scope.enableRotate) handleTouchMoveRotate(event) } // // event handlers - FSM: listen for events and reset state // function onPointerDown(event: PointerEvent) { if (scope.enabled === false) return if (pointers.length === 0) { scope.domElement?.ownerDocument.addEventListener( 'pointermove', onPointerMove, ) scope.domElement?.ownerDocument.addEventListener( 'pointerup', onPointerUp, ) } addPointer(event) if (event.pointerType === 'touch') { onTouchStart(event) } else { onMouseDown(event) } } function onPointerMove(event: PointerEvent) { if (scope.enabled === false) return if (event.pointerType === 'touch') { onTouchMove(event) } else { onMouseMove(event) } } function onPointerUp(event: PointerEvent) { removePointer(event) if (pointers.length === 0) { scope.domElement?.releasePointerCapture(event.pointerId) scope.domElement?.ownerDocument.removeEventListener( 'pointermove', onPointerMove, ) scope.domElement?.ownerDocument.removeEventListener( 'pointerup', onPointerUp, ) } scope.dispatchEvent(endEvent) state = STATE.NONE } function onPointerCancel(event: PointerEvent) { removePointer(event) } function onMouseDown(event: MouseEvent) { let mouseAction switch (event.button) { case 0: mouseAction = scope.mouseButtons.LEFT break case 1: mouseAction = scope.mouseButtons.MIDDLE break case 2: mouseAction = scope.mouseButtons.RIGHT break default: mouseAction = -1 } switch (mouseAction) { case MOUSE.DOLLY: if (scope.enableZoom === false) return handleMouseDownDolly(event) state = STATE.DOLLY break case MOUSE.ROTATE: if (event.ctrlKey || event.metaKey || event.shiftKey) { if (scope.enablePan === false) return handleMouseDownPan(event) state = STATE.PAN } else if (event.altKey) { if (scope.enableRotate === false) return handleMouseDownRotate(event) state = STATE.ROTATE } break case MOUSE.PAN: if (event.ctrlKey || event.metaKey || event.shiftKey) { if (scope.enableRotate === false) return handleMouseDownRotate(event) state = STATE.ROTATE } else { if (scope.enablePan === false) return handleMouseDownPan(event) state = STATE.PAN } break default: state = STATE.NONE } if (state !== STATE.NONE) { scope.dispatchEvent(startEvent) } } function onMouseMove(event: MouseEvent) { if (scope.enabled === false) return switch (state) { case STATE.ROTATE: if (scope.enableRotate === false) return handleMouseMoveRotate(event) break case STATE.DOLLY: if (scope.enableZoom === false) return handleMouseMoveDolly(event) break case STATE.PAN: if (scope.enablePan === false) return handleMouseMovePan(event) break } } function onMouseWheel(event: WheelEvent) { if ( scope.enabled === false || scope.enableZoom === false || (state !== STATE.NONE && state !== STATE.ROTATE) ) { return } event.preventDefault() scope.dispatchEvent(startEvent) handleMouseWheel(event) scope.dispatchEvent(endEvent) } function onKeyDown(event: KeyboardEvent) { if (scope.enabled === false || scope.enablePan === false) return handleKeyDown(event) } function onTouchStart(event: PointerEvent) { trackPointer(event) switch (pointers.length) { case 1: switch (scope.touches.ONE) { case TOUCH.ROTATE: if (scope.enableRotate === false) return handleTouchStartRotate() state = STATE.TOUCH_ROTATE break case TOUCH.PAN: if (scope.enablePan === false) return handleTouchStartPan() state = STATE.TOUCH_PAN break default: state = STATE.NONE } break case 2: switch (scope.touches.TWO) { case TOUCH.DOLLY_PAN: if (scope.enableZoom === false && scope.enablePan === false) return handleTouchStartDollyPan() state = STATE.TOUCH_DOLLY_PAN break case TOUCH.DOLLY_ROTATE: if (scope.enableZoom === false && scope.enableRotate === false) return handleTouchStartDollyRotate() state = STATE.TOUCH_DOLLY_ROTATE break default: state = STATE.NONE } break default: state = STATE.NONE } if (state !== STATE.NONE) { scope.dispatchEvent(startEvent) } } function onTouchMove(event: PointerEvent) { trackPointer(event) switch (state) { case STATE.TOUCH_ROTATE: if (scope.enableRotate === false) return handleTouchMoveRotate(event) scope.update() break case STATE.TOUCH_PAN: if (scope.enablePan === false) return handleTouchMovePan(event) scope.update() break case STATE.TOUCH_DOLLY_PAN: if (scope.enableZoom === false && scope.enablePan === false) return handleTouchMoveDollyPan(event) scope.update() break case STATE.TOUCH_DOLLY_ROTATE: if (scope.enableZoom === false && scope.enableRotate === false) return handleTouchMoveDollyRotate(event) scope.update() break default: state = STATE.NONE } } function onContextMenu(event: Event) { if (scope.enabled === false) return event.preventDefault() } function addPointer(event: PointerEvent) { pointers.push(event) } function removePointer(event: PointerEvent) { delete pointerPositions[event.pointerId] for (let i = 0; i < pointers.length; i++) { if (pointers[i].pointerId == event.pointerId) { pointers.splice(i, 1) return } } } function trackPointer(event: PointerEvent) { let position = pointerPositions[event.pointerId] if (position === undefined) { position = new Vector2() pointerPositions[event.pointerId] = position } position.set(event.pageX, event.pageY) } function getSecondPointerPosition(event: PointerEvent) { const pointer = event.pointerId === pointers[0].pointerId ? pointers[1] : pointers[0] return pointerPositions[pointer.pointerId] } // connect events if (domElement !== undefined) this.connect(domElement) // force an update at start this.update() } } // This set of controls performs orbiting, dollying (zooming), and panning. // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). // This is very similar to OrbitControls, another set of touch behavior // // Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish // Pan - left mouse, or arrow keys / touch: one-finger move class MapControls extends OrbitControls { constructor( object: PerspectiveCamera | OrthographicCamera, domElement?: HTMLElement, ) { super(object, domElement) this.screenSpacePanning = false // pan orthogonal to world-space direction camera.up this.mouseButtons.LEFT = MOUSE.PAN this.mouseButtons.RIGHT = MOUSE.ROTATE this.touches.ONE = TOUCH.PAN this.touches.TWO = TOUCH.DOLLY_ROTATE } } export {OrbitControls as OrbitControlsImpl, MapControls} ================================================ FILE: packages/r3f/src/extension/components/OrbitControls/index.tsx ================================================ import type {EventManager, ReactThreeFiber} from '@react-three/fiber' import {useFrame, useThree} from '@react-three/fiber' import * as React from 'react' import type {Camera, Event} from 'three' import {OrbitControlsImpl} from './OrbitControlsImpl' export type OrbitControlsChangeEvent = Event & { target: EventTarget & {object: Camera} } export type OrbitControlsProps = Omit< ReactThreeFiber.Overwrite< ReactThreeFiber.Object3DNode, { camera?: Camera domElement?: HTMLElement enableDamping?: boolean makeDefault?: boolean onChange?: (e?: OrbitControlsChangeEvent) => void onEnd?: (e?: Event) => void onStart?: (e?: Event) => void regress?: boolean target?: ReactThreeFiber.Vector3 keyEvents?: boolean | HTMLElement } >, 'ref' > export const OrbitControls = React.forwardRef< OrbitControlsImpl, OrbitControlsProps >( ( { makeDefault, camera, regress, domElement, enableDamping = true, keyEvents = false, onChange, onStart, onEnd, ...restProps }, ref, ) => { const invalidate = useThree((state) => state.invalidate) const defaultCamera = useThree((state) => state.camera) const gl = useThree((state) => state.gl) const events = useThree( (state) => state.events, ) as EventManager const setEvents = useThree((state) => state.setEvents) const set = useThree((state) => state.set) const get = useThree((state) => state.get) const performance = useThree((state) => state.performance) const explCamera = (camera || defaultCamera) as | THREE.OrthographicCamera | THREE.PerspectiveCamera const explDomElement = (domElement || events.connected || gl.domElement) as HTMLElement const controls = React.useMemo( () => new OrbitControlsImpl(explCamera), [explCamera], ) useFrame(() => { if (controls.enabled) controls.update() }, -1) React.useEffect(() => { if (keyEvents) { controls.connect(keyEvents === true ? explDomElement : keyEvents) } controls.connect(explDomElement) return () => void controls.dispose() }, [keyEvents, explDomElement, regress, controls, invalidate]) React.useEffect(() => { const callback = (e: OrbitControlsChangeEvent) => { invalidate() if (regress) performance.regress() if (onChange) onChange(e) } const onStartCb = (e: Event) => { if (onStart) onStart(e) } const onEndCb = (e: Event) => { if (onEnd) onEnd(e) } controls.addEventListener('change', callback) controls.addEventListener('start', onStartCb) controls.addEventListener('end', onEndCb) return () => { controls.removeEventListener('start', onStartCb) controls.removeEventListener('end', onEndCb) controls.removeEventListener('change', callback) } }, [onChange, onStart, onEnd, controls, invalidate, setEvents]) React.useEffect(() => { if (makeDefault) { const old = get().controls set({controls}) return () => set({controls: old}) } }, [makeDefault, controls]) return ( ) }, ) export {OrbitControlsImpl} ================================================ FILE: packages/r3f/src/extension/components/ProxyManager.tsx ================================================ import type {FC} from 'react' import React, {useLayoutEffect, useMemo, useRef, useState} from 'react' import type {Editable} from '../../main/store' import {createPortal, invalidate} from '@react-three/fiber' import EditableProxy from './EditableProxy' import type {OrbitControls} from 'three-stdlib' import TransformControls from './TransformControls' import shallow from 'zustand/shallow' import type {Material, Mesh, Object3D} from 'three' import {MeshBasicMaterial, MeshPhongMaterial} from 'three' import {getStudioSync, type IScrub} from '@theatre/core' import {useSelected} from './useSelected' import {useVal} from '@theatre/react' import {getEditorSheetObject} from '../editorStuff' import useExtensionStore from '../useExtensionStore' export interface ProxyManagerProps { orbitControlsRef: React.MutableRefObject } type IEditableProxy = { portal: ReturnType object: Object3D editable: Editable } const ProxyManager: FC = ({orbitControlsRef}) => { const isBeingEdited = useRef(false) const editorObject = getEditorSheetObject() const [sceneSnapshot, editables] = useExtensionStore( (state) => [state.sceneSnapshot, state.editables], shallow, ) const transformControlsMode = useVal(editorObject?.props.transformControls.mode) ?? 'translate' const transformControlsSpace = useVal(editorObject?.props.transformControls.space) ?? 'world' const viewportShading = useVal(editorObject?.props.viewport.shading) ?? 'rendered' const sceneProxy = useMemo(() => sceneSnapshot?.clone(), [sceneSnapshot]) const [editableProxies, setEditableProxies] = useState<{ [name in string]?: IEditableProxy }>({}) // set up scene proxies useLayoutEffect(() => { if (!sceneProxy) { return } const editableProxies: {[name: string]: IEditableProxy} = {} sceneProxy.traverse((object) => { if (object.userData.__editable) { const theatreKey = object.userData.__storeKey if ( // there are duplicate theatreKeys in the scene, only display one instance in the editor editableProxies[theatreKey] || // this object has been unmounted !editables[theatreKey] ) { object.parent!.remove(object) } else { editableProxies[theatreKey] = { portal: ( // we gotta wrap the portal because as of [this commit](https://github.com/pmndrs/react-three-fiber/commit/5d1652ce5b63397ad79c39d3dd100b26a465c41f) // in react-three-fiber, portals use the uuid of their parent object as their own key. Since many of these objects are nested // inside the same parent, they end up having the same react key. We avoid this issue by wrapping the portal in a component // so that its react key is unique within its parent component. , object.parent!, )} key={`portal-wrapper-${theatreKey}`} /> ), object: object, editable: editables[theatreKey]!, } } } }) setEditableProxies(editableProxies) }, [orbitControlsRef, sceneProxy]) const selected = useSelected() const editableProxyOfSelected = selected && editableProxies[selected] const editable = selected ? editables[selected] : undefined // set up viewport shading modes const [renderMaterials, setRenderMaterials] = useState<{ [id: string]: Material | Material[] }>({}) useLayoutEffect(() => { if (!sceneProxy) { return } const renderMaterials: { [id: string]: Material | Material[] } = {} sceneProxy.traverse((object) => { const mesh = object as Mesh if (mesh.isMesh && !mesh.userData.helper) { renderMaterials[mesh.id] = mesh.material } }) setRenderMaterials(renderMaterials) return () => { // @todo do we need this cleanup? // Object.entries(renderMaterials).forEach(([id, material]) => { // ;(sceneProxy.getObjectById(Number.parseInt(id)) as Mesh).material = // material // }) } }, [sceneProxy]) useLayoutEffect(() => { if (!sceneProxy) { return } sceneProxy.traverse((object) => { const mesh = object as Mesh if (mesh.isMesh && !mesh.userData.helper) { let material switch (viewportShading) { case 'wireframe': mesh.material = new MeshBasicMaterial({ wireframe: true, color: 'black', }) break case 'flat': // it is possible that renderMaterials hasn't updated yet if (!renderMaterials[mesh.id]) { return } material = new MeshBasicMaterial() if (renderMaterials[mesh.id].hasOwnProperty('color')) { material.color = (renderMaterials[mesh.id] as any).color } if (renderMaterials[mesh.id].hasOwnProperty('map')) { material.map = (renderMaterials[mesh.id] as any).map } if (renderMaterials[mesh.id].hasOwnProperty('vertexColors')) { material.vertexColors = ( renderMaterials[mesh.id] as any ).vertexColors } mesh.material = material break case 'solid': // it is possible that renderMaterials hasn't updated yet if (!renderMaterials[mesh.id]) { return } material = new MeshPhongMaterial() if (renderMaterials[mesh.id].hasOwnProperty('color')) { material.color = (renderMaterials[mesh.id] as any).color } if (renderMaterials[mesh.id].hasOwnProperty('map')) { material.map = (renderMaterials[mesh.id] as any).map } if (renderMaterials[mesh.id].hasOwnProperty('vertexColors')) { material.vertexColors = ( renderMaterials[mesh.id] as any ).vertexColors } mesh.material = material break case 'rendered': mesh.material = renderMaterials[mesh.id] } } invalidate() }) }, [viewportShading, renderMaterials, sceneProxy]) const scrub = useRef(undefined!) if (!sceneProxy) { return null } return ( <> {selected && editableProxyOfSelected && editable && editable.objectConfig.useTransformControls && ( { const sheetObject = editableProxyOfSelected.editable.sheetObject const obj = editableProxyOfSelected.object // interestingly, for some reason, only updating a transform when it actually changes breaks it scrub.current.capture(({set}) => { if (transformControlsMode === 'translate') { set(sheetObject.props.position, { ...sheetObject.value.position, x: obj.position.x, y: obj.position.y, z: obj.position.z, }) } if (transformControlsMode === 'rotate') { set(sheetObject.props.rotation, { ...sheetObject.value.rotation, x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, }) } if (transformControlsMode === 'scale') { set(sheetObject.props.scale, { x: obj.scale.x, y: obj.scale.y, z: obj.scale.z, }) } }) }} onDraggingChange={(event) => { if (event.value) { const studio = getStudioSync(true)! scrub.current = studio.scrub() } else { scrub.current.commit() } return (isBeingEdited.current = event.value) }} /> )} {Object.values(editableProxies).map( (editableProxy) => editableProxy!.portal, )} ) } const PortalWrapper: React.FC<{portal: React.ReactNode}> = ({portal}) => { return <>{portal} } export default ProxyManager ================================================ FILE: packages/r3f/src/extension/components/ReferenceWindow/ReferenceWindow.tsx ================================================ import type {VFC} from 'react' import {useMemo} from 'react' import React, {useEffect, useLayoutEffect, useRef} from 'react' import shallow from 'zustand/shallow' import type {WebGLRenderer} from 'three' import useMeasure from 'react-use-measure' import styled, {keyframes} from 'styled-components' import {TiWarningOutline} from 'react-icons/ti' import noiseImageUrl from './noise-transparent.png' import useExtensionStore from '../../useExtensionStore' const Container = styled.div<{minimized: boolean}>` position: relative; display: flex; justify-content: center; align-items: center; pointer-events: auto; cursor: pointer; overflow: hidden; border-radius: ${({minimized}) => (minimized ? '2px' : '4px')}; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.25), 0 2px 6px rgba(0, 0, 0, 0.15); ` const Canvas = styled.canvas<{width: number; height: number}>` display: block; width: ${({width, height}) => (width > height ? 'auto' : '100%')}; height: ${({width, height}) => (height > width ? 'auto' : '100%')}; ` const staticAnimation = keyframes` 0% { transform: translate(0,0) } 10% { transform: translate(-5%,-5%) } 20% { transform: translate(-10%,5%) } 30% { transform: translate(5%,-10%) } 40% { transform: translate(-5%,15%) } 50% { transform: translate(-10%,5%) } 60% { transform: translate(15%,0) } 70% { transform: translate(0,10%) } 80% { transform: translate(-15%,0) } 90% { transform: translate(10%,5%) } 100% { transform: translate(5%,0) } ` const Static = styled.div` position: relative; display: flex; width: 200px; height: 120px; padding: 18px; ::before { content: ''; position: absolute; z-index: -1; top: -50%; left: -50%; right: -50%; bottom: -50%; background: #2f2f2f url(${noiseImageUrl}) repeat 0 0; animation: ${staticAnimation} 0.2s infinite; } ` const Warning = styled.div` display: flex; flex-direction: column; gap: 12px; align-items: center; text-align: center; opacity: 0.8; ` interface ReferenceWindowProps { maxHeight: number maxWidth: number minimized: boolean onToggleMinified: () => void } const ReferenceWindow: VFC = ({ maxHeight, maxWidth, minimized, onToggleMinified, }) => { const canvasRef = useRef(null) const [gl] = useExtensionStore((state) => [state.gl], shallow) const [ref, {width: origWidth, height: origHeight}] = useMeasure() const preserveDrawingBuffer = gl?.getContextAttributes()?.preserveDrawingBuffer ?? false useLayoutEffect(() => { if (gl) { ref(gl?.domElement) } }, [gl, ref]) const [width, height] = useMemo(() => { if (!gl) return [0, 0] const aspectRatio = origWidth / (origHeight || Number.EPSILON) || Number.EPSILON const width = Math.min(aspectRatio * maxHeight, maxWidth) const height = width / aspectRatio return [width, height] }, [gl, maxWidth, maxHeight, origWidth, origHeight]) useEffect(() => { let animationHandle: number const draw = (gl: WebGLRenderer) => () => { animationHandle = requestAnimationFrame(draw(gl)) if (!gl.domElement || !preserveDrawingBuffer) { cancelAnimationFrame(animationHandle) return } const ctx = canvasRef.current!.getContext('2d')! // https://stackoverflow.com/questions/17861447/html5-canvas-drawimage-how-to-apply-antialiasing ctx.imageSmoothingQuality = 'high' ctx.fillStyle = 'white' ctx.fillRect(0, 0, width, height) ctx.drawImage(gl.domElement, 0, 0, width, height) } if (gl) { draw(gl)() } return () => { cancelAnimationFrame(animationHandle) } }, [gl, width, height, preserveDrawingBuffer]) return ( {preserveDrawingBuffer ? ( ) : ( Please set
{`gl={{ preserveDrawingBuffer: true }}`}
on the r3f Canvas for the reference window to work.
)}
) } export default ReferenceWindow ================================================ FILE: packages/r3f/src/extension/components/ReferenceWindow/noiseImage.ts ================================================ const imageDataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQBAMAAABykSv/AAAAFVBMVEUDAwPv7+/Dw8M3Nzd5eXmdnZ1YWFh6HLOtAAAAB3RSTlMSEhISEhISn+TpXAAAKwlJREFUeNq0mclaQjkQhf9UJVkXg64jKOsAwjogsA4KroMK7/8I/fWgrbZTT49wq27O+c8pUIMOa4dR6TrxOoDdgX6JJFaHp8fHUeEyu3ryrE4kHgoFNd9NZSZxSbw19HAFtzgBIFTl3iVAWbCroF5vPAl0SkGRfMOl6bXojcLlrXPKLefSfJhdGjDLMXYvmjwA89hzy+hDvOAenJZOdl2zLEgr80rfHcZAUBHdPMFScass17BDzCApzpeE0Lr3pYjjBCVUqCDXuVkChafmzi0/Mg9YJYqGkswec3GUSa9iA2CYfGw78+IqhC7R74xt2yN4S+wa6+1F6HNVCIrlSjICqoBI8lZXpk+3Ny20CR36rAtOoaG3l1mJDRUwomlCTemzBRktwMrUIR0ZEMItjZ7P/two+yvaBqOgyIZaUE+KVINTEpEgaFYXqk7pNZMQDFiyqbjY1zjDnh4w9j7Y8yRPsPhkkiq5dHdfT5Jo3NNsFyAApQvXIrNMMRH6ddzjDgjg7kvBZSb5akltfk9TBFCh+HTMeql1IXpAWgC/G44h+Rs6MZ24TCktwxmiBMjLjQgS3FK7wCKPKSUZ2ajQiOSDjp4y44FeEfAlnSAnHByYwJjsSjAYPBnOxwwcDgsRyKV7GeJiyExYhUzk+ujv8prp3opDIIC3jcvmY7KEO5tRR4Ltig+xEHNw5cKjsoERYRqoi/BIjSVc5Anm1IhQaWmiR3r3q7Chzv2KXOSau9Nlni+jBSbV/C3rtiCKz4Gu1BQeoWZjv5Y2R3feCVyFgmZoQDFJfUfNu1ZPsTJobPPcO0o3hQbRh0mOlaPcaugRPQBRgiNmD15JCJB85RqpJpQE9Twq3sON4eBoNIc3dn9d//HD9dc367/6av2K372byPRHE9EPJpLdoVIoLvrgb50eI8eaH8nMFMdiCyHDBuFhpq5VUeOcdOOcK0vVtMw5YJh10IIT7x0RUOou3Y12zIAbx1WMaEp1OPach610oKUx4BAHzoiVeyge8EGbR6Y9DgXqXeiAkhLUg2uutK6hj7VVl1CQGT0oLCSbNPYJI4drnHHynVKgGwrEBUAcuCouJsw2CphjjhIr2SUcJi74SpVMYgzOfDVHwCVLZZIIK3MrmuA355vT5Am54SzyFy3vS/Ey273X8tNvWh5CvODuWcvPnrV8WunLw7gM6PjskimbPomSfIRwkLukdNtWZZSyMnZ97kgd4WAhswDnyZkog248HwFuRfFuWx0xgADtOKe2PMzOHIUcXQeZEyYj2Ycic8jEDZ2DxqytugAt5Z04F6ce83oxw0kCmVA2YUFJZ8A20dZbwnmoJBZC2OD3XdsW2UHNTgPioJIzkAYsgl8wX1/OudfkzBW/A8tEmA1uyM4yhsEgA4dqenGHv+5CrP5GWvPkc4D7fIfUixBZLLPH95DSqOA7JVUnQUktQKrUcFuj00NhUdj3yETPEgKxdUM5TjTqA+KBOuySY0yEmnWSGZXA+SaEAGgjJdfcILi+2HJRNYONQ7hfm0ie1uCu0zD1fSYiloLzKQTLlidzQWl+NKk1BbkgdcHO7jgNe5BIBh04EhqeApQ65xt5Ni5skKj7FHH5hsWB8OuH+ubwkBm14CEXoQUH8UBNqSamZNszzW7uSHUL+BSPYIeSH5OLm+TwtSyAmlO6PhW8Zfo3DAuhcOFAyNhKyCm6qydPstjw7CEAw5vohOd/CuDRx1RbqpAWfQlYqlkiFQkE9nuazfv4PmGbFUrxCSonl3bxLuV2nUgFgoyT0h5gr0yHyBPVPbVaKzBHhHO6qXbKhb/sMgWnVvdPW9JQZ9TeUiau3EDjIlBtSdqqaBw2I0eW7SoIdWAiQ5xb5/gwzsiLUvGDPzPD5pM/M573LSXUu0GpT92mNflyB03v63ruq862HiNBhYVvPUd5cAKdWoO05GRFqU/wSO2LFfJlQxp3rYZru8+BFbc9TQ4bQyMQRdO8uslT2bWgbVXVMyxhjkPxqTtGD+ApS07k0cD3CaRCVwgOm+pYYxr3URwgAbzB9OLWdWqjk3IC0OgcpdqZbUp1PkF68DwozZLzwsA1Cxo2IbYjMiL0tBXzbsA90Mr9kCOxXqW8zZQCoC1V45ECJQdIIKzRwsCRvEBwrWV4EZXtz0Xl4dXq/GtR+WN1qfM3Vrf52erC0xerG+XA/F+Buf+/wHyUlY7icCBDIF45JmeZm0WCNas8ihR1DfCPIUOOLB+f35FLOLf/9R195/jjvzi+/IeOzx+On3/u+JjDrK4JDJFP4uLiJS4eT89x8fGjuOj/iIsPb+Pi9HVcLM9x0UCn+SUufogYYXb1CWK8j4s8L56/lci+XPzxz8UrhMeQGYjV5vtDCovMZST8NT5MnuPD7Ojv2qv44N/Fh77/PT7cF+9f4gNjFgOvNa0SxwuYdyshYQmAPTm0LkD2FtZALte5CDDNHjLDkGXBtKTrnAnrZJZDcm1EGe63gA5ph8E5owTaDrmfWq0PFhrYtevR2TolMfI6w5Xz8zNJzm7oOrI/FMELpcG9XQnZjYLlwVHJap0SsjhLscMd3uBjBlMUXxwQjVtQCEUBAGq4fpIwh0HbztjczrtyU7tyLDvwlXahleZzKMblfa7Ekj3Dpn5WoPaM/NB1Ss8yXJSyBKCPLORAl0QiRPUQ5DJnANGARy0R6uR6XxxdjSNW0NIRSHEFPXTodilEljWSSUBOnmW8Y1Jm3tLcGFFhvZwawio7e4hNliI+bSsJKuovW6qk2k/jBL6n5r041BEgQtNok6cbtFsnlcpK/k5bYN+3BX8+q8u/Re7zqpfucKhfk/viLbl3/yD35Rty7/9B7vUjcm9vyf30yV+zhZLlc79sBT8GI26JGAtCiy6UBBW8S2hJozu8IyW31k4OGahwdvQwRIfy27bX/2DbF5WUf7rtb60vfWx9+w+s7/wL61v/poDlRQHrW+vjtQKevbW+9wp49aEC8jG/61t+5xW/lz/4/fIdvz98we/+CDb+k99DrT/n99VX/P6SCflqxP1vTaZ8bzLf08XtByMef9VRtQ86Kn5ej6iXd/VIfFuP5Mm39Uh9VY9EfVuP7D+uRx76goW4R63IpllbccTRBboUf4+zGnP3jRaXaqracT1fOaOh6GlaS+5Lvr9gCK62tPwX5J9g2rl1nfQd+S9fkX95T/7lNfnf/UH+NGalWELqDqYQQTk/w+OxVuo62by2uNeIorVh/szgDnyjxmHN54E0xjXx2azvU574oVIdzjnjGiyVYjSghbAMk2PRCj5bqK0xkSBTK67t7cKIohYvSnLUY2XsuTy56EusmEvgkmfd6AQeXXJc6g61c+Gqw4sDtJ91Nx86QPmBA8w/dYAXbtB/wQ08T62+TG3269Rm/3Rq+YupXcpwKlgBwuOaFsMQR6Qh1qXiIwaQb0h3w4Z1D1B9QKB0r5xcifdYpzWf7aTZsTH2qkhTfhY7WwHgOXZ+Y6Ol4Mf5jY2Wj2w0fGyjvX9io5gjTy0/aKeQMSBVWICOz1KnG3FdFQByM/PBua1/CtgSJKYSU1YnWEl/qwv3HoD5p114+ksX3vtrFy6vunB+tan6bFM/q5l4sSn9wKa+ry7lubo8va0uF8j31eU0v20Zrv9oGbDx6QbtpvdIqp8iqXt3wKq/Iunq0wNW+PqApX2Nk5cDlv/JAYuXA9brxuFTra9/S+vTj1ueVv/y3MK/pNbhHZ6XU4h+fBz68hTy3uuP773+4e95Pb96/fAX4s1mOW0YjKJXv2uXAmtRqNeCBNYKSbtWgWRtHML7P0LHBGNAlmRBOl12ppNFCJ/vPfe4mUISnvUIXxM3lHsr2GP7NVk2oTylgu2dChb5e0en/bt/2L9fkzcTUm5tfDOx4eb1TQ6b5rXyPHe7XlmuNKMUWevi+HhxZcuUxVEFF8fj4EyvF0fbsjjiiEuYi0v4JS7hbbjEVLhk9QSArRtcIttxCT5xyQemJs/7n7hkUOMSecIlsxqXlB5cYhgDih8jtS0LAH3DYQb4xLvvoc2It2xGqiHzQx/eVWd49+Ea78oD3p1d4F0/mecHvLuryLx1yPx8ZyYFqn/Rqt0+x9rt5s5264LdeLutq1eU7+Xn+szHe4CHPlJZfoE+szmcj1cMK3Djm1xrfcY9HxN6Oh/2MraDSgtQfLwIAq2n2Tf9C8RKQGPxPueMmLEoq0OCgaT8gKGxJHmGhwwwXChZMFqqJwNLOYhSsBlhnvg2S41vQ098o258A8Bb6Cxr+/RmoeKsDnSWfcodAjYXYbmDvsPyr5M7sIB+3gCCTMH4SGWaTBlA+5wrACAGkyzPiKKggC20Aezo+ViBtIXBlAKvcv1rc6gjZcENZKfPWXk/Zx78nO3Z57w8PkAGt2dp6WTpo1ZkE7ySFK0ItVYk2rQizCsmrCsm7OUQPYdDrF0OMU3gEKnZVFfZlEQWyHOUwIIowS3F4u5SLNtLce+qFJuqFNOWUlycSrEzr/jtLFHPK61f4IXA+kkEvsB5/AvMqGwJ4A/dAnhcl6z3r5l7772gvk+30+reS01cTGNPoN7GQP2wO6jXsg8Iof9kgKYUEDLL0Zekmq/mUqAHaGBsiYYEz8YEbxArEDCsDb5BFk99DhC1AbDLAGCpBlBZCUJ4oQGCjB3aChh6QEaguq+Wu33wKc1ryRX05o1n9E83HnK18diLjWd/VagzihTLa1FbXtrw3wWFnIDjuEIatW9dIVnSCklPsXoCs179CcRqfblCwmESiToVb5FWlCOtJERjc2U+rGvzIRKNa4ygjqfaeE513oHa2RBGMF0xgtuzdzW1C3wNED+lc+8pTRddZUTrDO4WIsQyHBi9MudXbt+IhFEYnTRiqwZGF3eP2H1dAIExfNn157hj+CH4wBt8xqcBBlXwKarg8+My+EjV24aDD7sMPo0bdT+ktl+x9T7Glv32rRcP0IPVSBKM+EAAEpoKgBiGBTEgWMj+bAzU2FlRCSCzgNSmFKz6z7SL4MkbwZPfJHiKsOAJ72/gDrCmbncbrM9tMJE3IXxD7OBe2yfH/DyOaqdPpsVRGruhsADFrm5yvU5Nbpxh4W9yzeDS1uSSBhd61thNhQL9Lx8ESLvykXbjJ+0BnzJ+M7YQxvOIjKNo3GACLo7uv3VMwP/o/ie7gwrid5Mj30I50qqdP0em49kiD+VIZOvvHB/YKa0XEFAbs4YA0w8oPGf5kdjIWV4KSSlAE717fp93r9UzLFE/Kd40BCMQG5uzxiN4iXgEmkGmewTzC49gfuER0FsW8UR1Lz2tKCetxHWRH44uEk8rOO+Psn5Jsgxarw3lxYHyNtbr2jsSTSLWq9v6iUN5wyORXiTNNNYL8GTLTJPwYhgNvxdoogCvQ8V927unSQLbY8V9ubHi8taK25ymcbzi1ssRNUNk7G4dKeXB+c/CNv4Sd/bqacNQGP4s25rdNmRWcWBWSprZBZLZCZRZ/DT3fwl9AiixfWwdH5y0U6YMgC0dfT+vBp1EbrpPIglzEmGiYGIJFjRbQWthvz62FoaeFp9Mx2iWP9KW3k5c/1fryx9FvDlLIN87yh9FwZc/NsQdpL2d4tzb2S3iO7NFGU8iPJhkk2Rwp30QtHqmkswau7uPmtUze6ye7fMMyDqqZyXyrUS7TE7a5XS4dolBRUevT7hP0SdEO3VtvaWjoKuut34UfDyvt+O+6+2sst7et623v5tOfdxYb2mxYZofnfq9LzagGjL2JUGa3nKBkHFOQ8Z8eitpWWH49JbrXmGEz+pusM4+Z9pBZE7aMnOSd8OrZqjuaYaOFxoAYGpm6JKaoYeaGWo0YKEDpjdjhgaO0F+xukRe+NJiV9uQXe1ZBIzbpcUS7cpLtMAlM1EZudA7WnTPRNNT+WhP0jS+fJQ0ykfmnKYxtTTN/D1Nc4UMtgRm+IIsQQz3JUPusBq7SblG7iwslsgAbJFmgAXWsMe/QIrVY2FWDmqafiuTP/nW3PyMzHSdFsADtMp+QmOBdQT1sE+BQ4xCdT3GWuTpP8f52dOfVx9jPpezCeVy6M57IDtv2wnUBpPsZf0EGpe2dgI1LSdQP+0KarfGm94d0+6Um3apTDT5ACclVN9huiXuAqRM34ATzmvOc9RT0oRys95BUtdivkBWGomrQdLhpZFMFWmB28gZ1IUgNUgIQrFLtUCf/0gAAwAV0BT7t4szrymmnKaYEE2RKoRypxmvU4KrTQkuoKZwmTkbzswp2iG+b4/cwiOnbjymhFFT8PlJgf7R+33DMXEVx4RjR+F1KL71bd89bfvS+Cq/4aYoRpPzhmsArTcu1bH63L33jXX1UKkKHIKsK+erAv+TGvbQWOJDwRYufqobwZZdn4E76xAm1xVh0r9KfPwU5/hpfIX38wUuOl9smzkex6VtWR3Wf1w0Pq5+/7jX5OOi0MbqqCP2zuup9wI9lQ9EDgBtQegR8xCg8qjEpJk19odXYpRXYsZAdvUKAcq6IUAF8q2Nj0oMqkrMdVCJES8Om8GD+ZiGbWMStpWGquseoqp4iIrnh5UCfph0v5d7iOyOdtsr+3ZNdjQ2+8ZnAF46y2SUK9nTgkorFtSu1YJah5LCY+SywIy8sSwDqYyq3E1SHCBBUiJhcr2jgrDa+kmYcwDM8yl3rB9fWh3rbX+Zcklkyg4J5FQoOkkgAnt1SOF41MxWOyZb7QvHCgpYvBaORypJuwrHMEBVMS5Ioo31f0YB/4f3dtcCFIAOowBARVt55faf0QJ33bTAIY2OYPGazwElldnoWgUrgT96zEZdRpDl5Cg5WMHEskCElbQh8KGIzw9aAg0b2qEIKlQAiyYMaNm+N9+Tnt4J8mHN96i/O4vAu7YXWLBSH9XFGxolnyfVlmWRKld2qrm26aM2+ca3nQCer3UAT+IBPB6gqesATSMA8PQEaOZJ4Pe9nIzoAoEELsnHht3vpFwvDEy2Li9hfL9QxrcQkUB1D/kGRvsZ/Esl72fIyZQ1oUeMh2e0/AFCjxhGeNLh1l6HI9Q9RocbIYMbqMOBPWrPe3hgAKCHy8r8GN/Yw8Y9MIqRoMCEN5Jqn03ABJ7XeUfVvnjbBBbvm4DvEy15S6/nDAWGySbc8tOyeBIw2QRb/k19yyeTd9/WrxpBxW2tX9d6mcDS4nYSk81WRrtzkm8WYcqf67u0lHKbMAaS3pzTiLsm4WylrQAk51xGWs1Uq3crrWhYab9DmerdhVaa6ckyN55l7mXM4/IVdyqFZcDCN1ULnyRR+DKHqh3idlF+eRJlpIG+X1FCvqJA5k6Aey/xrZK545RedOaYEKrdUMgun4uRYxxW/B0dfHXx/BNdwcZuBGSx2y4mFkDhAEDBOCBKAbhEG9OEy8dvcPlJN1x+eYTL38vh8iMKl88rcHl1hMuXv45weQsRovpS0W59z+z2QmjyLCWinVD50B6G5JFzrPLx5JFzNGUvR85RVyjyrlBzb30UHKcFYefNBarWXlDa3lgEYAVMjfdfMiV50fPfYaBXQeM/U0qlUZC5VzHSU2qkQ4B/nciSPDFJ8pQ/T0me6OlrMZ5ZEyUAsLkrNdyzhYHTwPfIpEDhIgDQiVXp7QuiaJohRXFUmKNxmY1LU5ZRdK2xiLSbIUNLkYbBZzyxnGsa3I4/PbgN4pe353Ed5V5wtH60JW0OtaQNB7dLWk2mdrjdwPs/5GlGhstrKmlGiXx8sr5HvPXtByIISggbKC7YPTR4sfMPEoL3ZoC5PoFJHwYAXRzfp3Z9AgV00UI1lj37qoU5ENmA6avO+FJYECdIA9BRMZ2OPE6wnsfCaeZ99hdLyKrmM4Lr4GfeWQf5yJCZV4QZRzz/pCupkuOVVF29oBemFyRIo+DINAKpNYZiOMUALhlRKK4PLQpFB4+fv0+LECtpxp6XGn3kLxZIjQkb+ds1Uz13IakRiabpXS85yK9P+0zJgZyna8kp9MwwJcIMk6EZJl6Z0F3KxJxXJlDabzzAvgPFrj6IOhfh65c26lzUlYyY+mREqjyKvWa8/GgxXg7iy1XlPoSmnYK/zJ1Lc9NAEITbep2VgDkLnOS8eThn8TwLHHJWTJL//xMoOTFImtXMth4UV4qiQGx2dma6+ytZXgX4ISwLS+QbxYrhmu1eGsUwEi0fGfmgkmjtq/kLQaI9bGpxnCq09RQE1HWqd/9uri+J7u9WCcA1EdV5HfbdlZJIDu31eOulgbpK8CnLUx4d7KKmJzzTbt7BYJdKuHn9D2UUd6Mmmt+Ts7WOy4zauEyX9zB/m6Mw1w0Kc1NEt+FgX4jVOnyX4/TVOvyrdSdX6y5ktX7V9/2HLZbRXiwXhMHDeu3ZHmF7M3OHN6vCEYoTDYoyVnHCsMUKdYRIIQZEZqyNGPCnFZz5pb5bQur7sT/0UpQr/0LIUfOp82dephrxApm7OfwwpRJK2DKxeSSWKNlxiSLko0RUEZU1Di3Pz6Z3psRicTslH96OlyYafKH32x8kCDmv96vnJxmFyi5PrDJWx4WQXRLsYiG7ZBU3mKtTYDWdvIAnU/NO5piDpLauoGrNQa4jxHHbQXbazEFUXUF2mNnV2swOGgRpG9vBwctvvKR3ffV93d14JaiVXbCduFay/a9afSLzVOviqRB/W8mI5yf72VeyMbJj5RA9LcOniKgr5IIcNsgygTqP8o9VBdTxH1lK+fZVlnK4msuyJ0v5lSRFNSxL+dmTpdR9Wcqzp4lzG6wUWYrRxKWowA1KeFMuPSjhT0HSnAJoQSq7bGNMVP+R1+zDiek1Az7VfYfrleJwTV9ugP+Nl18AHORDJtPHDWUPdoEpsUoJoi4ZjrItx6WcoXFyPNITY5tROd5vgM4lOFe/w7+jDG9MYXqfIH51vDFxRX3ne/0737LcDd7Hxads1tH9wnbKb65FT1PlQJUwQQuBmgJfHMGZYAVqIumYsZnz2VxHIJUJS+WTaIlTQMgP91rHueGZMbdax1k2HScUQ4hsT3VX4EVbFSXZi8uqokDAfEcuTOtJ8748cN4nZlNiFUcwpZcT+Nox2vB93k9L7aPzEb44BGbctRgz45Kd/anaeShcbx/DHXEUda7dZTsVR4EQze/lI4FHEZxtE4+S25xt20SKJE08S4TqtorPIoJTLCOPZalcVMv92tsKy4Xobf9aLhKmt528oPwasKAs4LLX4K5qiCggM/DkTshPFHi4NP87r5SdEMsRBFzWbxK/Nk2iU1Eq73A5HqWyDWgS6YwFWP6AmxJuIp6r2FN4LnfEc1HPwBBn0WZBZ1Hx4iyq+3v4w8kQe3gVsrNt80zddjLPNPEqy9YngTxT5xpl2Ztu5s4uIHMnMDLnyYtMuPMgE/TT8UA2CXkorgI8WdXWFxs4wCgskTalpvogsn0mRSPc+6zm4xdfMm/kmOLX0aJeSm9nCE8hmZenwFkZxurkCmP8Kxg31vg3msa4QWsML54qcgyfVeKpkoW7Q2+YO7zQtFTSHYpJxrmao8Xsb+7UwtiJ4av8PcXJQE+h3CLE6JbfDdb90W2zG4zee91wuXDDxQc33NVfN1wFmz8ijZXfibkVuLnVh/Hno509knIxnwsy9XmMBCa2s81ormBGc3WuQqLeKe2sConS1a3REgszVaMdmWvTIfsMctts3LZ2bp+X+pkopt2Zgv9AOSZ/ydkg6imBe7swAmEl1pZQQAgXTMIJzXa9sdiuWypkAkdTZHIwRXb/JjGhSqPjLq5TVB+juUjdW5hXYtG/EkWeiYDT8qpCa3Jbm6pCiPSiJQRuyciEhTtiPUeELUt8wIpLEbBi6ZSwZXvY7zP8bZeTgGd9vk7ROcyn/sMs6rs8zBB1m1+pteGOat3ebZi6reJuHkSW4jFqQh07P089JHLsPLcCXNFcUoKpZAatgMhJtUPTHv+oAkC4BScRiKjCe84XXnELdQ5Y2T9gtRKOqa8cQ6EgGLlyhLxcjs2DhHc9mpcLn/wl45uvKP19/opx77Gc9tzkbCaWk93zJmrPe3mLFeL6SxUnVKA03KREEPt9e9MdR0tZ3qUcRyOZ0YQeDZnQryMZ6nYQXxMgVWcEJhwTBHW4Yd4JtCDghle+V21kBloIr4Wp8oMShElwQwtF6UVqptw4zdToLepJ4ODmhsVAvyUw0G2+RoiiphQRq6Sixu6a3cScIWBBPUyhEatk2qgp2LhU/G8+RTs4Rfs590CTYs6yZV7rGp+q4Qea+GEG0cQypflULc12EgBvjTPYQulhqf4QDi6oCe+FtlRfsaHPxmO21B+z5cTHbCpmN0qgVWt2I6dIIL4q42jhrf7PXqv/Zc/qfzpk9QchEUZGR7/LKG2535T3fCTueTtP7l/4ionTycNej60WhG/Fs7h88i4uSzbFirvrc+6uhyuV1krBeyuhymJuQ5Nx2Pv77QpIbelDMoP0gTY0lm0Yb8DSEhyGx/W6pqxnZtooGJ6FzUxzKZjeMAqmowFqpVkguTKMoDSV2IgdvCwvLt4qGFgFP5F3MuH2fzPhztr4iZ2dEmJEtmz8isj4z4deHz+0EBEqkS2KiFCtzNvhyoznFZ9Vw5+wWJywqAjIqiGg0eBw5HYgKLGF808J+NjL22b3hdGsrG9KDpJMHrdzkJwWGHqmBIaWLz+5SigbYaISuUN6KBtvomp0a6mmW7OgrXLb82UxNYeKYsijITVHeng1iAEBu1aPibV6t4C6poBSa3X/2PHhUECRQdbngv3j094fX1L1GSK+EXfIGpHt+eldINjS9aakkRZRYUd1tuSR1wPoi1+2PDIccpy15JEJOEElnQ1d81Bum0Euodwd4Lhk98tE8XMqUfz6wO4/Zll52f1C+Zb7fAsnlmMTqQiSdUPsmqTNrmlm+N7Q4ygMwZ2o7BoVzPLLF6CynL9cBpjcKSLFWIgUCyRNZP/7qijLdmR/ctl8L4eLlwCRqjq9qs5cCY/WiL8UidBZyhpwLiIS2qGz67Y1QIkZIyRPT3JYdU4NqyzJk83VkXt71II5uODkejYEH1C3A62zWdh5nL4lFhHPtr5FRjxDzZ4NTEJIjqlhJiW04iihCN+EAcjgohRYlTFuVtUramwDeHK/Xblvdv6t6zoTud/qdU3mXREZ9Vg77Dy0hEAIW4+W8En8vIrm6ExhAkSCgyWFgV/FCvWFg4Xmf6ENfEsk8O0pjTvAt0wHvu27/wt7pWgqpACx+H7jo5ldHRffESJUhyOfr5zLLxDV+e5Ngic8Fs7dIkVxV+6QInbXqNc/3mcrvE/WKX0glSB67kAmzaeoPQdyVhM+ytP7WVzivA4JkqW/+6nRNJyXpvHuYj041opGoQ7OSaoqfJRtKkF5FYIa2bzM/DZhHyd9nfmt9Zlfkl9/2B8/DkPqVdY1En9drFIC/0OTT0XXDSoO4FOMOiwO4F7VYU5PGJZMawXcMvt24alnrHQzWqLw3EcJRP+IOSenVszxvheqE12An01i77BSpUwsiOJOIFl6cLU5/39hSPZ9CgC4JcJOcrGWFO6gcdn35WCCpdVFvB8sRaDynxfwonRouxAGC81ztqA2JRHsS06b0tQ5fn8r9TcXujQW85hIFP0NpLZSMoWvCWCu4MPRwFzeVF3DAe1A3kj5e7+1uHakGTwb8fd2yuwSoWFwCIkB4x88UodY+vUp6aPx4CkBAIyLwwkXh3g9pJJP8GjyCbYBfIJhxCkG9jyMa5efetjCe+naNcBWICc98t+czvZv/uBxMgXDvLCI204WA16oSOakYViNv4tJNX5K6Eq+BmPHIqz7e6vSMwEGYU25XawRlxpK/vWjWI01KwYpKmKsGLyoqEkGsgZB0WEQ5AYHQWV7EJR4iITKIEhZJPEzMZg5AoVI71Fi8fKAPrjs+hFLupL5++DT+xl2CPL8v5/REIcADTGIV/bxBWoR5vdGAGUR3pMRVIERW+Nn/9aYQ1PLRbIMwPvQLJJDA/BsHUM6W2j6+sRXDyK5EUTdDQtLEQGf9bCwSc+47+IZR6RW0qT6SvWYo9ATjdKp4YuE1FCEL8q8wmFrBVCL629J0NhiJwCs+kp5oX4aVl8Vhmab9dxIzTZCVYmxb/G2VVSJElOe2jDujEjk+NgdCFbGG+OJe2NMp/1U46RUsDJufjd3LmtNQ1EUXrmOo0DHkUvHKVLGoSLjFMRxKNj3fwQ/aFOT7JN9OWnUR1Da03PWXvv/N2R9UM0FW/LAzBOCRCRcsFSPRAQ39e3OGxe9UOVVF6oYC2GhvhBWtAl76JJf7FDGlMwOIgJlNKmW4KdasjbYatJg23TvIpHzLpIPlNouz3cwXqHUlnZLbdjDeHEaHEptYFfuvRVUzMr9lQUzt7jVNLhekFRECcsMNGkVWl5imH0MNL8dBpousf2GLDFwYvvetLfpAUGorNeKyvolt9g+7bQoPnxpMfmSKmXk07FRPp6RDxalxf1tMlPDjU7y7Cgt9kAliztMw4EO8elnPu40HAK+4RA3DQeyuGPrvhUA4BHrv9oqyXe6SvIyjDfy47KiB/rucQlBi73w1WIXjBa7vnw/A22LXEuEbKkDhD4kmoqKxv1kIlXIiylrf/dTFpSmndqFYeOblmjVtL6A3/h2c02wv6n+IgRbJbybEmzt8G4DjiwdMvxgkgkSjR6uCd5OEmaYyGV5COugyv4ktg9tUhZD8ep8EqPdO2vM/e6NNQz3zgr2mkY21iw/Wd+Y3ln7mgZ/JfFFyojr/IVqa7+tOpDZzrT9eDsfo1aZza/g4RH7ZPeIsbDiowwZjnrlEev+q+kG3zRlNEEfa1oXGEwZLybNmUGv4poM7Xoq17qcbbQsn21sHh6yoHtvfMDntHbcG73XiJPOoUyb0vRQLnv3RjRl4KCcX5zuDuWz3juCz8eTHfF/t4pfOJkHkrUluDUxD0pv9JCrHj5pYPDktzhUSHUSgSs/nl8SN2hIAlHmax3Wp70aMBP76bFnhPOuNcMWjn2o+SDtFVqJwUbNwqv7fp9WmP1d+UT4ZPb7wOEEWtidQLlJttfYCdTiUM1YbUjQStd9qaZs6yVojWRBa456vf+M1MrPCOPDsWUlm0NW8ubOSiISfgtZCZpBPJqsxL3RHS4vMwCEUqZjv9HkhT5rHrS/oHE3fU7cIA4+fcYIM+u/qURV7koUDNtCwSFcp6+2E1fQGApBo5UQxFUvcczawkzIDu5dsk0t9XwprOBxi0t2XEjU53m89yJP5F7keN4W1KiomFyaxA+C/dLkH+ew9XP/nLoeOHvjlTanZs7ey8PZW/4ZPMLxWLuxPNZyKZnbOpO5yFIKoskcRb4fKY87dxbH75s8rjIT2+2rNzLHW65VVGoGiEyo8RX44T9y/b9a8KNk4U9/LSlleKrMB6Npa8JfS54e71RpKyYmyMp3M/KfwE3F7genYhh39daiypnn2Zmya/SLR5X7WzusTnW7teOJlJQZFgWGm82Jn2OLAVicErof49gyViVoaGBXxEQCRGW8ImYtQ1T8GiI+0WcouHY342pdwEkUhDXmQbIoyrSKVlEU1ajC5P3TkuNrVKTB83lWZ2F2V1VElaq2uieN1Z1VpfoXi+H+90a6utaZ0gp6fahrrZ2GlJzUtbADbnXrWklT16pIXauAUtIgza4/91uWZHZNtOT6kClXIByg/vq9dMaMa5Wh6ULFMJLhnVv5VoRWiSBhSVxU+SeTfeThSMSeEJKNu4WkRlOw9c0XvxjyxTQk+SJnUTPV9uHH1Khd/z3jZOXP4/qf8FkFubauggSuV9/9oEku4TOVyCX6wqiBi5lN628a7poIaRUE7n35n3+zWPfWbG3ljsBiGaYrTVjM1JamtR+vWluLZT5+axFL0l+RoVJyEnoqTA3nR/8QwxcdO/rjxOVfzccp3ah7mhi889Vxmlf9Ox/V45NlMj98u0GPv94JAvLusBzxOCWYjQDORys3g9EKaLSSEAJ4F8BbNQDe9kTOrdmetTTbZWsit7zVT+RWRLMtxALroVgATtlyotQ++7f3Ux9cV8hcLUDPgpXvD1qlg3CQHzRNlppI636wt2TmzupiTTQZmupiWM5nDDJPf/RvwM+mP/Oz6br7TdjKARmUAVlpFs57Rcf2zTTvectWiyqXu9KG92ZNUAle6ubEB5Xg7vycaYkDZebo/Ny41nnWQ8SByrrOQ+VUVRZCp5uK9GpBoQ3kH+zxdGSQX9sJOFWJhlOVE3Ihx6miPYcm59ZsKoCkPd6VIjOwM9s/+csBUIn5I5uJG2hOSEYl+tSqMT61PIPBL3IvCUgjpC6Q0snfEHThmOX358nK77LcCNreK1FQcb3XTN17TY822mMCMWPD/A5J7QWk++7ZMC/a8RXoUSooKXsQ2MLEOC57jOPynXFccYzj+KMy9tarjFGKltI3qKIFrRkMm/EgTMhBKBFc9SXv/2COza5OSWejvUxl14yc9zUj5YAcqfiACl0coEIhQuDpHSp0hTESoK1z49bdZIo8mkyPmIVlHKoQCsBSVsdFRB1nhrRsXC/52grejoeHBGAYWXzaKnIpdLKl1CVbuq2i03DzUp7jU1wEeSNbqjnZErmoWoS/P5qLqiD8fbMS5O3eNiQm/sr0If0LDemrrULI5bt67f9GKfZ0dQNLV3b6VyXcm4P0zW3B9IeGzblj8blQLn2/2/TATJvavd93u9J/t2l6hR1kglZ+7RoN+8TDM+WMnWO7FhbIvRlRGH7TqOvuEbOP4/eHCQv0tYoa193hT0Sj9rt21P4buFRXQB0a1NQAAAAASUVORK5CYII=' export default imageDataUrl ================================================ FILE: packages/r3f/src/extension/components/SnapshotEditor.tsx ================================================ import {useCallback, useEffect, useLayoutEffect, useMemo, useState} from 'react' import React from 'react' import {Canvas, useThree} from '@react-three/fiber' import type {BaseSheetObjectType} from '../../main/store' // eslint-disable-next-line import/no-extraneous-dependencies import {__private_allRegisteredObjects as allRegisteredObjects} from '@theatre/r3f' import shallow from 'zustand/shallow' import root from 'react-shadow/styled-components' import ProxyManager from './ProxyManager' import {useVal} from '@theatre/react' import styled, {createGlobalStyle, StyleSheetManager} from 'styled-components' import {getStudioSync, type ISheet} from '@theatre/core' import useSnapshotEditorCamera from './useSnapshotEditorCamera' import {getEditorSheet, getEditorSheetObject} from '../editorStuff' import type {$IntentionalAny} from '../../types' import {InfiniteGridHelper} from '../InfiniteGridHelper' import {DragDetectorProvider} from './DragDetector' import ReferenceWindow from './ReferenceWindow/ReferenceWindow' import useExtensionStore from '../useExtensionStore' import useMeasure from 'react-use-measure' const GlobalStyle = createGlobalStyle` :host { contain: strict; all: initial; color: white; font: 11px -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe Editor, HelveticaNeue-Light, Ubuntu, Droid Sans, sans-serif; } * { padding: 0; margin: 0; font: inherit; vertical-align: baseline; list-style: none; } ` const EditorScene: React.FC<{snapshotEditorSheet: ISheet; paneId: string}> = ({ snapshotEditorSheet, paneId, }) => { const [gl, scene, camera] = useThree( (store) => [store.gl, store.scene, store.camera] as const, shallow, ) const [editorCamera, orbitControlsRef] = useSnapshotEditorCamera( snapshotEditorSheet, paneId, ) const editorObject = getEditorSheetObject() const helpersRoot = useExtensionStore((state) => state.helpersRoot, shallow) const showGrid = useVal(editorObject?.props.viewport.showGrid) ?? true const showAxes = useVal(editorObject?.props.viewport.showAxes) ?? true const grid = useMemo(() => new InfiniteGridHelper(), []) return ( {showGrid && } {showAxes && } {editorCamera} ) } const Wrapper = styled.div` tab-size: 4; line-height: 1.15; /* 1 */ -webkit-text-size-adjust: 100%; /* 2 */ margin: 0; position: absolute; top: 0; right: 0; bottom: 0; left: 0; overflow: hidden; ` const CanvasWrapper = styled.div` position: relative; z-index: 0; height: 100%; overflow: hidden; ` const Overlay = styled.div` position: absolute; inset: 0; z-index: 2; pointer-events: none; ` const Tools = styled.div` position: absolute; left: 12px; top: 12px; pointer-events: auto; ` const ReferenceWindowContainer = styled.div` position: absolute; right: 12px; top: 12px; justify-content: center; ` const WaitForSceneInitMessage = styled.div<{active?: boolean}>` position: absolute; margin: auto; left: 0; right: 0; width: 300px; top: 12px; padding: 16px; border-radius: 4px; border: 1px solid rgba(255, 255, 255, 0.08); backdrop-filter: blur(14px); background: rgba(40, 43, 47, 0.8); @supports not (backdrop-filter: blur()) { background-color: rgba(40, 43, 47, 0.95); } ` const SnapshotEditor: React.FC<{paneId: string}> = (props) => { const snapshotEditorSheet = getEditorSheet() const paneId = props.paneId const editorObject = getEditorSheetObject() const [ref, bounds] = useMeasure() const [sceneSnapshot, createSnapshot] = useExtensionStore( (state) => [state.sceneSnapshot, state.createSnapshot], shallow, ) useLayoutEffect(() => { // Create a fresh snapshot when the editor is opened createSnapshot() }, []) const onPointerMissed = useCallback(() => { const studio = getStudioSync(true)! // This callback runs when the user clicks in an empty space inside a SnapshotEditor. // We'll try to set the current selection to the nearest sheet _if_ at least one object // belonging to R3F was selected previously. const obj: undefined | BaseSheetObjectType = studio.selection.find( (sheetOrObject) => allRegisteredObjects.has(sheetOrObject as $IntentionalAny), ) as $IntentionalAny if (obj) { studio.setSelection([obj.sheet]) } }, []) const [toolsContainer, setToolsContainer] = useState() useEffect(() => { if (!toolsContainer) return const studio = getStudioSync(true)! return studio.ui.renderToolset('snapshot-editor', toolsContainer) }, [toolsContainer]) if (!editorObject) return <> const referenceWindowVisibility = useVal(getEditorSheetObject()?.props.viewport.referenceWindow) ?? 'minimized' return ( <> {referenceWindowVisibility !== 'hidden' && ( { const studio = getStudioSync(true)! studio.transaction(({set}) => { set( getEditorSheetObject()!.props.viewport .referenceWindow, referenceWindowVisibility === 'minimized' ? 'maximized' : 'minimized', ) }) }} /> )} {!sceneSnapshot && ( The scene hasn't been initialized yet. It will appear in the editor as soon as it is. )} { gl.setClearColor('white') }} shadows dpr={[1, 2]} frameloop="demand" onPointerMissed={onPointerMissed} > ) } export default SnapshotEditor ================================================ FILE: packages/r3f/src/extension/components/TransformControls.tsx ================================================ import type {Object3D, Event} from 'three' import React, {forwardRef, useLayoutEffect, useEffect, useMemo} from 'react' import type {ReactThreeFiber, Overwrite} from '@react-three/fiber' import {useThree} from '@react-three/fiber' import {TransformControls as TransformControlsImpl} from 'three-stdlib' import type {OrbitControls} from 'three-stdlib' type R3fTransformControls = Overwrite< ReactThreeFiber.Object3DNode< TransformControlsImpl, typeof TransformControlsImpl >, {target?: ReactThreeFiber.Vector3} > export interface TransformControlsProps extends R3fTransformControls { object: Object3D orbitControlsRef?: React.MutableRefObject onObjectChange?: (event: Event) => void onDraggingChange?: (event: Event) => void // not a complete list of props that transform controls can take mode: TransformControlsImpl['mode'] space: TransformControlsImpl['space'] } const TransformControls = forwardRef( ( { children, object, orbitControlsRef, onObjectChange, onDraggingChange, ...props }: TransformControlsProps, ref, ) => { const {camera, gl, invalidate} = useThree() const controls = useMemo( () => new TransformControlsImpl(camera, gl.domElement), [camera, gl.domElement], ) useLayoutEffect(() => { controls.attach(object) return () => void controls.detach() }, [object, controls]) useEffect(() => { controls?.addEventListener?.('change', () => invalidate()) return () => controls?.removeEventListener?.('change', () => invalidate()) }, [controls, invalidate]) useEffect(() => { const callback = (event: Event) => { if (orbitControlsRef && orbitControlsRef.current) { // @ts-ignore TODO orbitControlsRef.current.enabled = !event.value } } if (controls) { controls.addEventListener!('dragging-changed', callback) } return () => { controls.removeEventListener!('dragging-changed', callback) } }, [controls, orbitControlsRef]) useEffect(() => { if (onObjectChange) { controls.addEventListener('objectChange', onObjectChange) } return () => { if (onObjectChange) { controls.removeEventListener('objectChange', onObjectChange) } } }, [onObjectChange, controls]) useEffect(() => { if (onDraggingChange) { controls.addEventListener('dragging-changed', onDraggingChange) } return () => { if (onDraggingChange) { controls.removeEventListener('dragging-changed', onDraggingChange) } } }, [controls, onDraggingChange]) return }, ) export default TransformControls ================================================ FILE: packages/r3f/src/extension/components/useRefAndState.ts ================================================ import type {MutableRefObject} from 'react' import {useMemo, useState} from 'react' /** * Combines useRef() and useState(). * * @example * Usage: * ```ts * const [ref, val] = useRefAndState(null) * * useEffect(() => { * val.addEventListener(...) * }, [val]) * * return
* ``` */ export default function useRefAndState( initialValue: T, ): [ref: MutableRefObject, state: T] { const ref = useMemo(() => { let current = initialValue return { get current() { return current }, set current(v: T) { current = v setState(v) }, } }, []) const [state, setState] = useState(() => initialValue) return [ref, state] } ================================================ FILE: packages/r3f/src/extension/components/useSelected.tsx ================================================ import {useLayoutEffect, useRef, useState} from 'react' // eslint-disable-next-line import/no-extraneous-dependencies import { __private_allRegisteredObjects as allRegisteredObjects, __private_makeStoreKey as makeStoreKey, } from '@theatre/r3f' import {getStudioSync, type ISheetObject, type IStudio} from '@theatre/core' import type {$IntentionalAny} from '../../types' export function useSelected(): undefined | string { const [state, set] = useState(undefined) const stateRef = useRef(state) stateRef.current = state useLayoutEffect(() => { const setFromStudio = (selection: IStudio['selection']) => { const item = selection.find( (s): s is ISheetObject => s.type === 'Theatre_SheetObject_PublicAPI' && allRegisteredObjects.has(s as $IntentionalAny), ) if (!item) { set(undefined) } else { set(makeStoreKey(item.address)) } } const studio = getStudioSync(true)! setFromStudio(studio.selection) return studio.onSelectionChange(setFromStudio) }, []) return state } export function getSelected(): undefined | string { const studio = getStudioSync(true)! const item = studio.selection.find( (s): s is ISheetObject => s.type === 'Theatre_SheetObject_PublicAPI' && allRegisteredObjects.has(s as $IntentionalAny), ) if (!item) { return undefined } else { return makeStoreKey(item.address) } } ================================================ FILE: packages/r3f/src/extension/components/useSnapshotEditorCamera.tsx ================================================ import {PerspectiveCamera} from '@react-three/drei' import {OrbitControls} from './OrbitControls' import type {OrbitControlsImpl} from './OrbitControls' import type {MutableRefObject} from 'react' import {useLayoutEffect, useRef} from 'react' import React from 'react' import useRefAndState from './useRefAndState' import type {IScrub} from '@theatre/core' import type {PerspectiveCamera as PerspectiveCameraImpl} from 'three' import type {ISheet} from '@theatre/core' import {getStudioSync, types} from '@theatre/core' import type {ISheetObject} from '@theatre/core' import {useThree} from '@react-three/fiber' import type {$IntentionalAny} from '../../types' const camConf = { transform: { position: { x: types.number(10), y: types.number(10), z: types.number(0), }, target: { x: types.number(0), y: types.number(0), z: types.number(0), }, }, lens: { zoom: types.number(1, {range: [0.0001, 10]}), fov: types.number(50, {range: [1, 1000]}), near: types.number(0.1, {range: [0, Infinity]}), far: types.number(2000, {range: [0, Infinity]}), focus: types.number(10, {range: [0, Infinity]}), filmGauge: types.number(35, {range: [0, Infinity]}), filmOffset: types.number(0, {range: [0, Infinity]}), }, } export default function useSnapshotEditorCamera( snapshotEditorSheet: ISheet, paneId: string, ): [ node: React.ReactNode, orbitControlsRef: MutableRefObject, ] { // OrbitControls and Cam might change later on, so we use useRefAndState() // instead of useRef() to catch those changes. const [orbitControlsRef, orbitControls] = useRefAndState(null) const [camRef, cam] = useRefAndState( undefined, ) const objRef = useRef | null>(null) useLayoutEffect(() => { if (!objRef.current) { objRef.current = snapshotEditorSheet.object( `Editor Camera ${paneId}`, camConf, ) } }, [paneId]) usePassValuesFromTheatreToCamera(cam, orbitControls, objRef) usePassValuesFromOrbitControlsToTheatre(cam, orbitControls, objRef) const node = ( <> ) return [node, orbitControlsRef] } /** * This debounce does delay the placement of a keyframe, so you * need to be wary of it being too long and causing an issue like * the following: * * 1. user moves playhead t 0 * 2. user changes orbit * 3. user moves playead to 10 * 4. user changes orbit again * * User expects a keyframe on t0 and t10 */ const COMMIT_DEBOUNCE_MS = 200 function usePassValuesFromOrbitControlsToTheatre( cam: PerspectiveCameraImpl | undefined, orbitControls: OrbitControlsImpl | null, objRef: MutableRefObject | null>, ) { useLayoutEffect(() => { if (!cam || orbitControls == null) return let currentScrub: | undefined | { /** "debounce" like timer for making commits to the orbit's changes */ scheduledCommit?: { timer: $IntentionalAny // Future, might be possible to remember the position we need to apply this scrub to (when the last 'end' event was received) // position: number } scrub: IScrub } const scheduleCommit = () => { if (currentScrub) { clearTimeout(currentScrub.scheduledCommit?.timer) const reference = currentScrub // capture current scrub to make sure currentScrub isn't out of date currentScrub.scheduledCommit = { timer: setTimeout(() => { if (reference === currentScrub) { currentScrub.scrub.commit() currentScrub = undefined } }, COMMIT_DEBOUNCE_MS), } } } const onStart = () => { if (currentScrub) { // prevent existing scheduled commit from executing during the start of a rotation or pan. // This currentScrub will continue being used and will be scheduled again onEnd clearTimeout(currentScrub.scheduledCommit?.timer) } else { const studio = getStudioSync(true)! // start new scrub currentScrub = { scrub: studio.scrub(), } } } const onEnd = () => { scheduleCommit() } const onChange = () => { if (!currentScrub) return const p = cam!.position const position = {x: p.x, y: p.y, z: p.z} const t = orbitControls!.target const target = {x: t.x, y: t.y, z: t.z} const transform = { position, target, } currentScrub.scrub.capture(({set}) => { set(objRef.current!.props.transform, transform) }) } orbitControls.addEventListener('start', onStart) orbitControls.addEventListener('end', onEnd) orbitControls.addEventListener('change', onChange) return () => { orbitControls.removeEventListener('start', onStart) orbitControls.removeEventListener('end', onEnd) orbitControls.removeEventListener('change', onChange) if (currentScrub) { // defensively discard in progress changes currentScrub.scrub.discard() currentScrub = undefined } } }, [cam, orbitControls]) } function usePassValuesFromTheatreToCamera( cam: PerspectiveCameraImpl | undefined, orbitControls: OrbitControlsImpl | null, objRef: MutableRefObject | null>, ) { const invalidate = useThree(({invalidate}) => invalidate) useLayoutEffect(() => { if (!cam || orbitControls === null) return const obj = objRef.current! const setFromTheatre = ( props: ISheetObject['value'], ): void => { const {position, target} = props.transform cam.zoom = props.lens.zoom cam.fov = props.lens.fov cam.near = props.lens.near cam.far = props.lens.far cam.focus = props.lens.focus cam.filmGauge = props.lens.filmGauge cam.filmOffset = props.lens.filmOffset cam.position.set(position.x, position.y, position.z) cam.updateProjectionMatrix() orbitControls.target.set(target.x, target.y, target.z) orbitControls.update() invalidate() } const unsub = obj.onValuesChange(setFromTheatre) setFromTheatre(obj.value) return unsub }, [cam, orbitControls, objRef, invalidate]) } ================================================ FILE: packages/r3f/src/extension/editorStuff.ts ================================================ import type {ISheet, ISheetObject} from '@theatre/core' import {types, getStudioSync} from '@theatre/core' let sheet: ISheet | undefined = undefined let sheetObject: ISheetObject | undefined = undefined const editorSheetObjectConfig = { viewport: types.compound( { showAxes: types.boolean(true, {label: 'Axes'}), showGrid: types.boolean(true, {label: 'Grid'}), showOverlayIcons: types.boolean(false, {label: 'Overlay Icons'}), shading: types.stringLiteral( 'rendered', { flat: 'Flat', rendered: 'Rendered', solid: 'Solid', wireframe: 'Wireframe', }, {as: 'menu', label: 'Shading'}, ), referenceWindow: types.stringLiteral( 'minimized', { maximized: 'Maximized', minimized: 'Minimized', hidden: 'Hidden', }, {as: 'menu', label: 'Reference Window'}, ), }, {label: 'Viewport Config'}, ), transformControls: types.compound( { mode: types.stringLiteral( 'translate', { translate: 'Translate', rotate: 'Rotate', scale: 'Scale', }, {as: 'switch', label: 'Mode'}, ), space: types.stringLiteral( 'world', { local: 'Local', world: 'World', }, {as: 'switch', label: 'Space'}, ), }, {label: 'Transform Controls'}, ), } export function getEditorSheet(): ISheet { if (!sheet) { sheet = getStudioSync(true)!.getStudioProject().sheet('R3F UI') } return sheet } export function getEditorSheetObject(): ISheetObject< typeof editorSheetObjectConfig > | null { if (!sheetObject) { sheetObject = getEditorSheet().object('Editor', editorSheetObjectConfig) || null } return sheetObject } ================================================ FILE: packages/r3f/src/extension/icons.tsx ================================================ import { BsCameraVideoFill, BsFillCollectionFill, BsCloudFill, } from 'react-icons/bs' import {GiCube, GiLightBulb, GiLightProjector} from 'react-icons/gi' import {BiSun} from 'react-icons/bi' import React from 'react' const icons = { collection: , cube: , lightBulb: , spotLight: , sun: , camera: , cloud: , } export type IconID = keyof typeof icons export default icons ================================================ FILE: packages/r3f/src/extension/index.ts ================================================ import SnapshotEditor from './components/SnapshotEditor' import type {IExtension} from '@theatre/core' import {prism, val} from '@theatre/dataverse' import {getEditorSheetObject} from './editorStuff' import ReactDOM from 'react-dom/client' import React from 'react' import type {ToolsetConfig} from '@theatre/core' import useExtensionStore from './useExtensionStore' import {onChange} from '@theatre/core' const io5CameraOutline = `Camera` const gameIconMove = `` const gameIconClockwiseRotation = `` const gameIconResize = `` const boxIconsGlobe = `` const boxIconsCube = `` const gameIconsCube = `` const fontAwesomeCube = `` const gameIconsIceCube = `` const r3fExtension: IExtension = { id: '@theatre/r3f', toolbars: { global(set, studio) { const calc = prism(() => { const editorObject = getEditorSheetObject() return [ { type: 'Icon', title: 'Create Snapshot', svgSource: io5CameraOutline, onClick: () => { studio.createPane('snapshot') }, }, ] }) return onChange(calc, () => { set(calc.getValue()) }) }, 'snapshot-editor': (set, studio) => { const {createSnapshot} = useExtensionStore.getState() const calc = prism(() => { const editorObject = getEditorSheetObject() const transformControlsMode = val(editorObject?.props.transformControls.mode) ?? 'translate' const transformControlsSpace = val(editorObject?.props.transformControls.space) ?? 'world' const viewportShading = val(editorObject?.props.viewport.shading) ?? 'rendered' return [ { type: 'Icon', onClick: createSnapshot, title: 'Refresh Snapshot', svgSource: ``, }, { type: 'Switch', value: transformControlsMode, onChange: (value) => studio.transaction(({set}) => set(editorObject!.props.transformControls.mode, value), ), options: [ { value: 'translate', label: 'Tool: Translate', svgSource: gameIconMove, }, { value: 'rotate', label: 'Tool: Rotate', svgSource: gameIconClockwiseRotation, }, { value: 'scale', label: 'Tool: Scale', svgSource: gameIconResize, }, ], }, { type: 'Switch', value: transformControlsSpace, onChange: (space) => studio.transaction(({set}) => { set(editorObject!.props.transformControls.space, space) }), options: [ { value: 'world', label: 'Space: World', svgSource: boxIconsGlobe, }, { value: 'local', label: 'Space: Local', svgSource: boxIconsCube, }, ], }, { type: 'Switch', value: viewportShading, onChange: (shading) => studio.transaction(({set}) => { set(editorObject!.props.viewport.shading, shading) }), options: [ { value: 'wireframe', label: 'Display: Wireframe', svgSource: boxIconsCube, }, { value: 'flat', label: 'Display: Flat', svgSource: gameIconsCube, }, { value: 'solid', label: 'Display: Solid', svgSource: fontAwesomeCube, }, { value: 'rendered', label: 'Display: Rendered', svgSource: gameIconsIceCube, }, ], }, ] }) return onChange(calc, () => { set(calc.getValue()) }) }, }, panes: [ { class: 'snapshot', mount: ({paneId, node}) => { const root = ReactDOM.createRoot(node) root.render(React.createElement(SnapshotEditor, {paneId})) function unmount() { // gotta unmount in the next tick, otherwise react will complain https://github.com/facebook/react/issues/25675 setTimeout(() => { root.unmount() }, 0) } return unmount }, }, ], } export default r3fExtension ================================================ FILE: packages/r3f/src/extension/useExtensionStore.ts ================================================ // eslint-disable-next-line import/no-extraneous-dependencies import {____private_editorStore} from '@theatre/r3f' import create from 'zustand' const useExtensionStore = create(____private_editorStore) export default useExtensionStore ================================================ FILE: packages/r3f/src/globals.d.ts ================================================ declare module '*.txt' { export default string } declare module '*.png' { export default string } ================================================ FILE: packages/r3f/src/index.ts ================================================ export {default as editable} from './main/editable' export type {EditableState, BindFunction} from './main/store' /** * This is a private API that's exported so that `@theatre/r3f/dist/extension` * and `@theatre/r3f` can talk to one another. This API _could_ change * between patch releases, so please don't build on it :) * * @internal */ export { editorStore as ____private_editorStore, allRegisteredObjects as __private_allRegisteredObjects, } from './main/store' /** * This is a private API that's exported so that `@theatre/r3f/dist/extension` * and `@theatre/r3f` can talk to one another. This API _could_ change * between patch releases, so please don't build on it :) * * @internal */ export {makeStoreKey as __private_makeStoreKey} from './main/utils' export {default as SheetProvider, useCurrentSheet} from './main/SheetProvider' export { default as RafDriverProvider, useCurrentRafDriver, } from './main/RafDriverProvider' export {refreshSnapshot} from './main/utils' export {default as RefreshSnapshot} from './main/RefreshSnapshot' export * from './drei' ================================================ FILE: packages/r3f/src/main/.eslintrc.js ================================================ module.exports = { rules: { 'no-restricted-syntax': [ 'error', { selector: `ImportDeclaration[importKind!='type'][source.value=/\\u002Fextension\\u002F/]`, message: `The main bundle should not be able to import the internals of extension.`, }, ], }, } ================================================ FILE: packages/r3f/src/main/RafDriverProvider.tsx ================================================ import type {ReactNode} from 'react' import React, {createContext, useContext, useEffect} from 'react' import type {IRafDriver} from '@theatre/core' const ctx = createContext<{rafDriver: IRafDriver}>(undefined!) export const useCurrentRafDriver = (): IRafDriver | undefined => { return useContext(ctx)?.rafDriver } const RafDriverProvider: React.FC<{ driver: IRafDriver children: ReactNode }> = ({driver, children}) => { useEffect(() => { if (!driver || driver.type !== 'Theatre_RafDriver_PublicAPI') { throw new Error( `driver in has an invalid value`, ) } }, [driver]) return {children} } export default RafDriverProvider ================================================ FILE: packages/r3f/src/main/RefreshSnapshot.tsx ================================================ import React, {useEffect} from 'react' import {refreshSnapshot} from './utils' /** * Putting this element in a suspense tree makes sure the snapshot editor * gets refreshed once the tree renders. * * Alternatively you can use {@link refreshSnapshot} * * @example * Usage * ```jsx * * * * * ``` */ const RefreshSnapshot: React.FC<{}> = () => { useEffect(() => { setTimeout(() => { refreshSnapshot() }) }, []) return <> } export default RefreshSnapshot ================================================ FILE: packages/r3f/src/main/SheetProvider.tsx ================================================ import type {ReactNode} from 'react' import React, { createContext, useContext, useEffect, useLayoutEffect, } from 'react' import {useThree} from '@react-three/fiber' import type {ISheet} from '@theatre/core' import {bindToCanvas} from './store' const ctx = createContext<{sheet: ISheet}>(undefined!) const useWrapperContext = (): {sheet: ISheet} => { const val = useContext(ctx) if (!val) { throw new Error( `No sheet found. You need to add a higher up in the tree. https://docs.theatrejs.com/r3f.html#sheetprovider`, ) } return val } export const useCurrentSheet = (): ISheet | undefined => { return useWrapperContext().sheet } const SheetProvider: React.FC<{ sheet: ISheet children: ReactNode }> = ({sheet, children}) => { const {scene, gl} = useThree((s) => ({scene: s.scene, gl: s.gl})) useEffect(() => { if (!sheet || sheet.type !== 'Theatre_Sheet_PublicAPI') { throw new Error(`sheet in has an invalid value`) } }, [sheet]) useLayoutEffect(() => { bindToCanvas({gl, scene}) }, [scene, gl]) return {children} } export default SheetProvider ================================================ FILE: packages/r3f/src/main/defaultEditableFactoryConfig.ts ================================================ import type {EditableFactoryConfig} from './editableFactoryConfigUtils' import { createColorPropConfig, createNumberPropConfig, createVector, createVectorPropConfig, extendObjectProps, } from './editableFactoryConfigUtils' import type { DirectionalLight, Object3D, OrthographicCamera, PerspectiveCamera, PointLight, SpotLight, } from 'three' import { BoxHelper, CameraHelper, Color, DirectionalLightHelper, PointLightHelper, SpotLightHelper, } from 'three' const baseObjectConfig = { props: { position: createVectorPropConfig('position'), rotation: createVectorPropConfig('rotation'), scale: createVectorPropConfig('scale', createVector([1, 1, 1])), }, useTransformControls: true, icon: 'cube' as const, createHelper: (object: Object3D) => new BoxHelper(object, selectionColor), } const baseLightConfig = { ...extendObjectProps(baseObjectConfig, { intensity: createNumberPropConfig('intensity', 1), distance: createNumberPropConfig('distance'), decay: createNumberPropConfig('decay'), color: createColorPropConfig('color', new Color('white')), }), dimensionless: true, } const baseCameraConfig = { ...extendObjectProps(baseObjectConfig, { near: createNumberPropConfig('near', 0.1, {nudgeMultiplier: 0.1}), far: createNumberPropConfig('far', 2000, {nudgeMultiplier: 0.1}), }), updateObject: (camera: PerspectiveCamera | OrthographicCamera) => { camera.updateProjectionMatrix() }, icon: 'camera' as const, dimensionless: true, createHelper: (camera: PerspectiveCamera) => new CameraHelper(camera), } const selectionColor = '#40AAA4' const defaultEditableFactoryConfig = { group: { ...baseObjectConfig, icon: 'collection' as const, createHelper: (object: Object3D) => new BoxHelper(object, selectionColor), }, mesh: { ...baseObjectConfig, icon: 'cube' as const, createHelper: (object: Object3D) => new BoxHelper(object, selectionColor), }, spotLight: { ...extendObjectProps(baseLightConfig, { angle: createNumberPropConfig('angle', 0, {nudgeMultiplier: 0.001}), penumbra: createNumberPropConfig('penumbra', 0, {nudgeMultiplier: 0.001}), }), icon: 'spotLight' as const, createHelper: (light: SpotLight) => new SpotLightHelper(light, selectionColor), }, directionalLight: { ...extendObjectProps(baseObjectConfig, { intensity: createNumberPropConfig('intensity', 1), color: createColorPropConfig('color', new Color('white')), }), icon: 'sun' as const, dimensionless: true, createHelper: (light: DirectionalLight) => new DirectionalLightHelper(light, 1, selectionColor), }, pointLight: { ...baseLightConfig, icon: 'lightBulb' as const, createHelper: (light: PointLight) => new PointLightHelper(light, 1, selectionColor), }, ambientLight: { props: { intensity: createNumberPropConfig('intensity', 1), color: createColorPropConfig('color', new Color('white')), }, useTransformControls: false, icon: 'lightBulb' as const, }, hemisphereLight: { props: { intensity: createNumberPropConfig('intensity', 1), color: createColorPropConfig('color', new Color('white')), groundColor: createColorPropConfig('groundColor', new Color('white')), }, useTransformControls: false, icon: 'lightBulb' as const, }, perspectiveCamera: extendObjectProps(baseCameraConfig, { fov: createNumberPropConfig('fov', 50, {nudgeMultiplier: 0.1}), zoom: createNumberPropConfig('zoom', 1), }), orthographicCamera: baseCameraConfig, points: baseObjectConfig, line: baseObjectConfig, lineLoop: baseObjectConfig, lineSegments: baseObjectConfig, fog: { props: { color: createColorPropConfig('color'), near: createNumberPropConfig('near', 1, {nudgeMultiplier: 0.1}), far: createNumberPropConfig('far', 1000, {nudgeMultiplier: 0.1}), }, useTransformControls: false, icon: 'cloud' as const, }, } // Assert that the config is indeed of EditableFactoryConfig without actually // forcing it to that type so that we can pass the real type to the editable factory defaultEditableFactoryConfig as EditableFactoryConfig export default defaultEditableFactoryConfig ================================================ FILE: packages/r3f/src/main/editable.tsx ================================================ import type {ComponentProps, ComponentType, Ref, RefAttributes} from 'react' import {useMemo, useState} from 'react' import React, {forwardRef, useEffect, useLayoutEffect, useRef} from 'react' import {allRegisteredObjects, editorStore} from './store' import {mergeRefs} from 'react-merge-refs' import useInvalidate from './useInvalidate' import {useCurrentSheet} from './SheetProvider' import defaultEditableFactoryConfig from './defaultEditableFactoryConfig' import type {EditableFactoryConfig} from './editableFactoryConfigUtils' import {makeStoreKey} from './utils' import type {$FixMe, $IntentionalAny} from '../types' import type {ISheetObject} from '@theatre/core' import {notify} from '@theatre/core' import {useCurrentRafDriver} from './RafDriverProvider' const createEditable = ( config: EditableFactoryConfig, ) => { const editable = < T extends ComponentType | keyof JSX.IntrinsicElements | 'primitive', U extends Keys, >( Component: T, type: T extends 'primitive' ? null : U, ) => { type Props = Omit, 'visible'> & { theatreKey: string visible?: boolean | 'editor' additionalProps?: $FixMe objRef?: $FixMe } & (T extends 'primitive' ? { editableType: U } : {}) & RefAttributes if (Component !== 'primitive' && !type) { throw new Error( `You must provide the type of the component you're creating an editable for. For example: editable(MyComponent, 'mesh').`, ) } // TODO: detect if `editable()` is being called in the body of a react component, which is a common // mistake. If it is, throw an error. return forwardRef( ( { theatreKey, visible, editableType, additionalProps, objRef, ...props }: Props, ref, ) => { //region Runtime type checks if (typeof theatreKey !== 'string') { throw new Error( `No valid theatreKey was provided to the editable component. theatreKey must be a string. Received: ${theatreKey}`, ) } if (Component === 'primitive' && !editableType) { throw new Error( `When using the primitive component, you must provide the editableType prop. Received: ${editableType}`, ) } //endregion const actualType = type ?? editableType const objectRef = useRef() const sheet = useCurrentSheet()! const rafDriver = useCurrentRafDriver() const [sheetObject, setSheetObject] = useState< undefined | ISheetObject<$FixMe> >(undefined) const storeKey = useMemo( () => makeStoreKey({ ...sheet.address, objectKey: theatreKey as $IntentionalAny, }), [sheet, theatreKey], ) const invalidate = useInvalidate() // warn about cameras in r3f useEffect(() => { if ( Component === 'perspectiveCamera' || Component === 'orthographicCamera' ) { const dreiComponent = Component.charAt(0).toUpperCase() + Component.slice(1) notify.warning( `Possibly incorrect use of `, `You seem to have declared the camera "${theatreKey}" simply as \`\`. This alone won't make r3f use it for rendering. The easiest way to create a custom animatable \`${dreiComponent}\` is to import it from \`@react-three/drei\`, and make it editable. \`\`\` import {${dreiComponent}} from '@react-three/drei' const EditableCamera = editable(${dreiComponent}, '${Component}') \`\`\` Then you can use it in your JSX like any other editable component. Note the makeDefault prop exposed by drei, which makes r3f use it for rendering. \`\`\` \`\`\` `, ) } }, [Component, theatreKey]) // create sheet object and add editable to store useLayoutEffect(() => { if (!sheet) return if (sheetObject) { sheet.object( theatreKey, Object.assign( { ...additionalProps, }, // @ts-ignore ...Object.values(config[actualType].props).map( // @ts-ignore (value) => value.type, ), ), {reconfigure: true}, ) return } else { const sheetObject = sheet.object( theatreKey, Object.assign( { ...additionalProps, }, // @ts-ignore ...Object.values(config[actualType].props).map( // @ts-ignore (value) => value.type, ), ), ) allRegisteredObjects.add(sheetObject) setSheetObject(sheetObject) if (objRef) typeof objRef === 'function' ? objRef(sheetObject) : (objRef.current = sheetObject) editorStore.getState().addEditable(storeKey, { type: actualType, sheetObject, visibleOnlyInEditor: visible === 'editor', // @ts-ignore objectConfig: config[actualType], }) } }, [sheet, storeKey, additionalProps]) // store initial values of props useLayoutEffect(() => { if (!sheetObject) return sheetObject!.initialValue = Object.fromEntries( // @ts-ignore Object.entries(config[actualType].props).map( // @ts-ignore ([key, value]) => [key, value.parse(props)], ), ) }, [ sheetObject, // @ts-ignore ...Object.keys(config[actualType].props).map( // @ts-ignore (key) => props[key], ), ]) // subscribe to prop changes from theatre useLayoutEffect(() => { if (!sheetObject) return const object = objectRef.current! const setFromTheatre = (newValues: any) => { // @ts-ignore Object.entries(config[actualType].props).forEach( // @ts-ignore ([key, value]) => value.apply(newValues[key], object), ) // @ts-ignore config[actualType].updateObject?.(object) invalidate() } setFromTheatre(sheetObject.value) const unsubscribe = sheetObject.onValuesChange( setFromTheatre, rafDriver, ) return () => { unsubscribe() sheetObject.sheet.detachObject(theatreKey) allRegisteredObjects.delete(sheetObject) editorStore.getState().removeEditable(storeKey) } }, [sheetObject, rafDriver]) return ( // @ts-ignore ) }, ) } const extensions = { ...Object.fromEntries( Object.keys(config).map((key) => [ key, // @ts-ignore editable(key, key), ]), ), primitive: editable('primitive', null), } as unknown as { [Property in Keys]: React.ForwardRefExoticComponent< Omit & { theatreKey: string visible?: boolean | 'editor' additionalProps?: $FixMe objRef?: $FixMe // not exactly sure how to get the type of the threejs object itself ref?: Ref } > } & { primitive: React.ForwardRefExoticComponent< { object: any theatreKey: string visible?: boolean | 'editor' additionalProps?: $FixMe objRef?: $FixMe editableType: keyof JSX.IntrinsicElements // not exactly sure how to get the type of the threejs object itself ref?: Ref } & { // Have to reproduce the primitive component's props here because we need to // lift this index type here to the outside to make auto-complete work [props: string]: any } > } return Object.assign(editable, extensions) } const editable = createEditable( defaultEditableFactoryConfig, ) export default editable ================================================ FILE: packages/r3f/src/main/editableFactoryConfigUtils.ts ================================================ import type {UnknownShorthandCompoundProps} from '@theatre/core' import {notify} from '@theatre/core' import {types} from '@theatre/core' import type {Object3D} from 'three' import type {IconID} from '../extension/icons' import {Color} from 'three' export type Helper = Object3D & { update?: () => void } type PropConfig = { parse: (props: Record) => T apply: (value: T, object: any) => void type: UnknownShorthandCompoundProps } type Props = Record> type Meta = { useTransformControls: boolean updateObject?: (object: T) => void icon: IconID dimensionless?: boolean createHelper?: (object: T) => Helper } export type ObjectConfig = {props: Props} & Meta export type EditableFactoryConfig = Partial< Record> > type Vector3 = { x: number y: number z: number } function isNumber(value: any) { return typeof value === 'number' && isFinite(value) } function isVectorObject(value: any) { return (['x', 'y', 'z'] as const).every((axis) => isNumber(value[axis])) } export const createVector = (components?: [number, number, number]) => { return components ? {x: components[0], y: components[1], z: components[2]} : { x: 0, y: 0, z: 0, } } export const createVectorPropConfig = ( key: string, defaultValue = createVector(), {nudgeMultiplier = 0.01} = {}, ): PropConfig => ({ parse: (props) => { const propValue = props[key] // if prop exists const vector = !propValue ? defaultValue : // if prop is an array Array.isArray(propValue) ? createVector(propValue as any) : // if prop is a scalar isNumber(propValue) ? { x: propValue, y: propValue, z: propValue, } : // if prop is a threejs Vector3 isVectorObject(propValue) ? { x: propValue.x, y: propValue.y, z: propValue.z, } : // show a warning and return defaultValue (notify.warning( `Invalid value for vector prop "${key}"`, `Couldn't make sense of \`${key}={${JSON.stringify( propValue, )}}\`, falling back to \`${key}={${JSON.stringify([ defaultValue.x, defaultValue.y, defaultValue.z, ])}}\`. To fix this, make sure the prop is set to either a number, an array of numbers, or a three.js Vector3 object.`, ), defaultValue) ;(['x', 'y', 'z'] as const).forEach((axis) => { // e.g. r3f also accepts prop keys like "scale-x" if (props[`${key}-${axis}` as any]) vector[axis] = props[`${key}-${axis}` as any] }) return vector }, apply: (value, object) => { object[key].set(value.x, value.y, value.z) }, type: { [key]: { x: types.number(defaultValue.x, {nudgeMultiplier}), y: types.number(defaultValue.y, {nudgeMultiplier}), z: types.number(defaultValue.z, {nudgeMultiplier}), }, }, }) export const createNumberPropConfig = ( key: string, defaultValue: number = 0, {nudgeMultiplier = 0.01} = {}, ): PropConfig => ({ parse: (props) => { return props[key] ?? defaultValue }, apply: (value, object) => { object[key] = value }, type: { [key]: types.number(defaultValue, {nudgeMultiplier}), }, }) export type Rgba = { r: number g: number b: number a: number } export const createColorPropConfig = ( key: string, defaultValue = new Color(0, 0, 0), ): PropConfig => ({ parse: (props) => { return {...(props[key] ?? defaultValue), a: 1} }, apply: (value, object) => { object[key].setRGB(value.r, value.g, value.b) }, type: { [key]: types.rgba({...defaultValue, a: 1}), }, }) export const extendObjectProps = ( objectConfig: T, extension: Props, ) => ({ ...objectConfig, props: {...objectConfig.props, ...extension}, }) ================================================ FILE: packages/r3f/src/main/store.ts ================================================ import type {StateCreator} from 'zustand' import create from 'zustand/vanilla' import type {Object3D, Scene, WebGLRenderer} from 'three' import {Group} from 'three' import type {ISheetObject} from '@theatre/core' import type {ObjectConfig} from './editableFactoryConfigUtils' export type TransformControlsMode = 'translate' | 'rotate' | 'scale' export type TransformControlsSpace = 'world' | 'local' export type ViewportShading = 'wireframe' | 'flat' | 'solid' | 'rendered' export type BaseSheetObjectType = ISheetObject export const allRegisteredObjects = new WeakSet() export interface Editable { type: string sheetObject: ISheetObject objectConfig: ObjectConfig visibleOnlyInEditor: boolean } export type EditableSnapshot = Editable> = { proxyObject?: Object3D | null } & T export interface SerializedEditable { type: string } export interface EditableState { editables: Record } export type EditorStore = { scene: Scene | null gl: WebGLRenderer | null helpersRoot: Group editables: Record> // this will come in handy when we start supporting multiple canvases canvasName: string sceneSnapshot: Scene | null editablesSnapshot: Record | null init: (scene: Scene, gl: WebGLRenderer) => void addEditable: (theatreKey: string, editable: Editable) => void removeEditable: (theatreKey: string) => void createSnapshot: () => void setSnapshotProxyObject: ( proxyObject: Object3D | null, theatreKey: string, ) => void } const config: StateCreator = (set, get) => { return { sheet: null, editorObject: null, scene: null, gl: null, helpersRoot: new Group(), editables: {}, canvasName: 'default', sceneSnapshot: null, editablesSnapshot: null, initialEditorCamera: {}, init: (scene, gl) => { set({ scene, gl, }) // Create a snapshot, so that if the editor is already open, it gets refreshed // when the scene is initialized get().createSnapshot() }, addEditable: (theatreKey, editable) => { set((state) => ({ editables: { ...state.editables, [theatreKey]: editable, }, })) }, removeEditable: (theatreKey) => { set((state) => { const editables = {...state.editables} delete editables[theatreKey] return { editables, } }) }, createSnapshot: () => { set((state) => ({ sceneSnapshot: state.scene?.clone() ?? null, editablesSnapshot: state.editables, })) }, setSnapshotProxyObject: (proxyObject, theatreKey) => { set((state) => ({ editablesSnapshot: { ...state.editablesSnapshot, [theatreKey]: { ...state.editablesSnapshot![theatreKey], proxyObject, }, }, })) }, } } export const editorStore = create(config) export type BindFunction = (options: { allowImplicitInstancing?: boolean gl: WebGLRenderer scene: Scene }) => void export const bindToCanvas: BindFunction = ({gl, scene}) => { const init = editorStore.getState().init init(scene, gl) } ================================================ FILE: packages/r3f/src/main/useInvalidate.ts ================================================ import {useThree} from '@react-three/fiber' export default function useInvalidate() { return useThree(({invalidate}) => invalidate) } ================================================ FILE: packages/r3f/src/main/utils.ts ================================================ import {editorStore} from './store' import type {ISheetObject} from '@theatre/core' export const refreshSnapshot = editorStore.getState().createSnapshot export const makeStoreKey = (sheetObjectAddress: ISheetObject['address']) => `${sheetObjectAddress.sheetId}:${sheetObjectAddress.sheetInstanceId}:${sheetObjectAddress.objectKey}` ================================================ FILE: packages/r3f/src/types.ts ================================================ export type $FixMe = any export type $IntentionalAny = any ================================================ FILE: packages/r3f/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "dist", "lib": ["ESNext", "DOM"], "rootDir": "src", "types": ["jest", "node"], "jsx": "react", "emitDeclarationOnly": true, "composite": true }, "references": [{"path": "../studio"}, {"path": "../core"}], "include": ["./src/**/*"] } ================================================ FILE: packages/react/.gitignore ================================================ /dist ================================================ FILE: packages/react/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: packages/react/README.md ================================================ # @theatre/react Utilities for using [Theatre.js](https://www.theatrejs.com) or [Dataverse](https://github.com/theatre-js/theatre/tree/main/packages/dataverse) with React. ## Documentation ### `useVal(pointerOrPrism)` A React hook that returns the value of the given prism or pointer. Usage with Dataverse pointers: ```tsx import {Atom} from '@theatre/dataverse' import {useVal} from '@theatre/react' const atom = new Atom({foo: 'foo'}) function Component() { const foo = useVal(atom.pointer.foo) return
{foo}
} ``` Usage with Dataverse prisms: ```tsx import {prism} from '@theatre/dataverse' import {useVal} from '@theatre/react' const pr = prism(() => 'some value') function Component() { const value = useVal(pr) return
{value}
} ``` Usage with Theatre.js pointers: ```tsx import {useVal} from '@theatre/react' import {getProject} from '@theatre/core' const obj = getProject('my project') .sheet('my sheet') .object('my object', {foo: 'default value of props.foo'}) function Component() { const value = useVal(obj.props.foo) return
obj.foo is {value}
} ``` _Note that `useVal()` is a React hook, so it can only be used inside a React component's render function. `val()` on the other hand, can be used either inside a `prism` (which would be reactive) or anywhere where reactive values are not needed._ ### `usePrism(fn, deps)` Creates a prism out of `fn` and subscribes the element to the value of the created prism. ```tsx import {Atom, val, prism} from '@theatre/dataverse' import {usePrism} from '@theatre/react' const state = new Atom({a: 1, b: 1}) function Component(props: {which: 'a' | 'b'}) { const value = usePrism( () => { prism.isPrism() // true // note that this function is running inside a prism, so all of prism's // hooks (prism.memo(), prism.effect(), etc) are available const num = val(props.which === 'a' ? state.pointer.a : state.pointer.b) return doExpensiveComputation(num) }, // since our prism reads `props.which`, we should include it in the deps array [props.which], ) return
{value}
} ``` > Note that just like `useMemo(..., deps)`, it's necessary to provide a `deps` > array to `usePrism()`. ### `usePrismInstance(prismInstance)` Subscribes the element to the value of the given prism instance. ```tsx import {Atom, val, prism} from '@theatre/dataverse' import {usePrismInstance} from '@theatre/react' const state = new Atom({a: 1, b: 1}) const p = prism(() => { return val(state.pointer.a) + val(state.pointer.b) }) function Component() { const value = usePrismInstance(p) return
{value}
} ``` ### `useAtom(initialValue)` /\*\* Creates a new Atom, similar to useState(), but the component won't re-render if the value of the atom changes. ```tsx import {useAtom, useVal} from '@theatre/react' import {useEffect} from 'react' function MyComponent() { const atom = useAtom({count: 0, ready: false}) const onClick = () => atom.setByPointer( (p) => p.count, (count) => count + 1, ) useEffect(() => { setTimeout(() => { atom.setByPointer((p) => p.ready, true) }, 1000) }, []) const ready = useVal(atom.pointer.ready) if (!ready) return
Loading...
return } ``` ## Links - Learn more about [Dataverse](../dataverse/README.md) ================================================ FILE: packages/react/devEnv/api-extractor.json ================================================ /** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../../devEnv/api-extractor-base.json", // "extends": "my-package/include/api-extractor-base.json" /** * Determines the "" token that can be used with other config file settings. The project folder * typically contains the tsconfig.json and package.json config files, but the path is user-defined. * * The path is resolved relative to the folder of the config file that contains the setting. * * The default value for "projectFolder" is the token "", which means the folder is determined by traversing * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error * will be reported. * * SUPPORTED TOKENS: * DEFAULT VALUE: "" */ "projectFolder": ".." } ================================================ FILE: packages/react/devEnv/api-extractor.tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "../tsconfig.json", "compilerOptions": { "paths": {} } } ================================================ FILE: packages/react/devEnv/build.ts ================================================ import * as path from 'path' import {build} from 'esbuild' import type {Plugin} from 'esbuild' const externalPlugin = (patterns: RegExp[]): Plugin => { return { name: `external`, setup(build) { build.onResolve({filter: /.*/}, (args) => { const external = patterns.some((p) => { return p.test(args.path) }) if (external) { return {path: args.path, external} } }) }, } } const definedGlobals = { global: 'window', } async function createBundles(watch: boolean) { const pathToPackage = path.join(__dirname, '../') const esbuildConfig: Parameters[0] = { entryPoints: [path.join(pathToPackage, 'src/index.ts')], bundle: true, sourcemap: true, define: definedGlobals, watch, platform: 'neutral', mainFields: ['browser', 'module', 'main'], target: ['firefox57', 'chrome58'], conditions: ['browser', 'node'], plugins: [externalPlugin([/^[\@a-zA-Z]+/])], } await build({ ...esbuildConfig, outfile: path.join(pathToPackage, 'dist/index.js'), format: 'cjs', }) // build({ // ...esbuildConfig, // outfile: path.join(pathToPackage, 'dist/index.mjs'), // format: 'esm', // }) } void createBundles(false) ================================================ FILE: packages/react/devEnv/tsconfig.json ================================================ { } ================================================ FILE: packages/react/package.json ================================================ { "name": "@theatre/react", "version": "0.7.0", "license": "Apache-2.0", "author": { "name": "Aria Minaei", "email": "aria@theatrejs.com", "url": "https://github.com/AriaMinaei" }, "repository": { "type": "git", "url": "https://github.com/AriaMinaei/theatre", "directory": "packages/react" }, "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist/**/*" ], "scripts": { "prepack": "node ../../devEnv/ensurePublishing.js", "typecheck": "yarn run build", "build": "run-s build:ts build:js build:api-json", "build:ts": "tsc --build ./tsconfig.json", "build:js": "tsx ./devEnv/build.ts", "build:api-json": "api-extractor run --local --config devEnv/api-extractor.json", "prepublish": "node ../../devEnv/ensurePublishing.js", "clean": "rm -rf ./dist && rm -f tsconfig.tsbuildinfo" }, "devDependencies": { "@microsoft/api-extractor": "^7.18.11", "@types/jest": "^26.0.23", "@types/lodash-es": "^4.17.4", "@types/node": "^15.6.2", "@types/react": "^17.0.9", "@types/react-dom": "^17.0.6", "esbuild": "^0.12.15", "npm-run-all": "^4.1.5", "tsx": "4.7.0", "typescript": "5.1.6" }, "dependencies": { "@theatre/dataverse": "workspace:*", "lodash-es": "^4.17.21", "queue-microtask": "^1.2.3" }, "peerDependencies": { "react": "*", "react-dom": "*" } } ================================================ FILE: packages/react/src/index.ts ================================================ /** * React bindings for dataverse. * * @packageDocumentation */ import type {Prism} from '@theatre/dataverse' import {prism, val, Atom} from '@theatre/dataverse' import {findIndex} from 'lodash-es' import queueMicrotask from 'queue-microtask' import {useCallback, useLayoutEffect, useRef, useState} from 'react' import {unstable_batchedUpdates} from 'react-dom' type $IntentionalAny = any type VoidFn = () => void /** * Enables a few traces and debug points to help identify performance glitches in `@theatre/react`. * Look up references to this value to see how to make use of those traces. */ const TRACE: boolean = false && process.env.NODE_ENV !== 'production' function useForceUpdate(debugLabel?: string) { const [, setTick] = useState(0) const update = useCallback(() => { setTick((tick) => tick + 1) }, []) return update } /** * A React hook that executes the callback function and returns its return value * whenever there's a change in the values of the dependency array, or in the * prisms that are used within the callback function. * * @param fn - The callback function * @param deps - The dependency array * @param debugLabel - The label used by the debugger * * @remarks * * A common mistake with `usePrism()` is not including its deps in its dependency array. Let's * have an eslint rule to catch that. */ export function usePrism( fn: () => T, deps: unknown[], debugLabel?: string, ): T { const refs = useRef<{ atom: Atom prism: Prism deps: unknown[] }>() if (!refs.current) { refs.current = { atom: new Atom(fn), prism: prism(() => { const fn = refs.current!.atom.prism.getValue() return fn() }), deps, } } else { if (deps.length !== refs.current.deps.length) { refs.current.deps = deps refs.current.atom.set(fn) } else { for (let i = 0; i < deps.length; i++) { if (deps[i] !== refs.current.deps[i]) { refs.current.deps = deps refs.current.atom.set(fn) break } } } } return _usePrismStaticInstance(refs.current.prism, debugLabel) } export const useVal: typeof val = (p: $IntentionalAny, debugLabel?: string) => { return usePrism(() => val(p), [p], debugLabel) } /** * Each usePrism() call is assigned an `order`. Parents have a smaller * order than their children, and so on. */ let lastOrder = 0 /** * A sorted array of prisms that need to be refreshed. The prisms are sorted * by their order, which means a parent prism always gets priority to children * and descendents. Ie. we refresh the prisms top to bottom. */ const queue: QueueItem[] = [] const setOfQueuedItems = new Set() type QueueItem = { order: number /** * runUpdate() is the equivalent of a forceUpdate() call. It would only be called * if the value of the inner prism has _actually_ changed. */ runUpdate: VoidFn /** * Some debugging info that are only present if {@link TRACE} is true */ debug?: { /** * The `debugLabel` given to `usePrism()/usePrismInstance()` */ label?: string /** * A trace of the first time the component got rendered */ traceOfFirstTimeRender: Error /** * An array of the operations done on/about this usePrismInstance. This is helpful to trace * why a usePrismInstance's update was added to the queue and why it re-rendered */ history: Array< /** * Item reached its turn in the queue */ | `queue reached` /** * Item reached its turn in the queue, and errored (likely something in `prism()` threw an error) */ | `queue: der.getValue() errored` /** * The item was added to the queue (may be called multiple times, but will only queue once) */ | `queueUpdate()` /** * `cb` in `item.der.onStale(cb)` was called */ | `onStale(cb)` /** * Item was rendered */ | `rendered` > } /** * A reference to the prism */ der: Prism /** * The last value of this prism. */ lastValue: T /** * Would be set to true if the element hosting the `usePrismInstance()` was unmounted */ unmounted: boolean /** * Adds the `usePrismInstance` to the update queue */ queueUpdate: () => void /** * Untaps from `this.der.unStale()` */ untap: () => void } let microtaskIsQueued = false const pushToQueue = (item: QueueItem) => { _pushToQueue(item) queueIfNeeded() } const _pushToQueue = (item: QueueItem) => { if (setOfQueuedItems.has(item)) return setOfQueuedItems.add(item) if (queue.length === 0) { queue.push(item) } else { const index = findIndex( queue, (existingItem) => existingItem.order >= item.order, ) if (index === -1) { queue.push(item) } else { const right = queue[index] if (right.order > item.order) { queue.splice(index, 0, item) } } } } /** * Plucks items from the queue */ const removeFromQueue = (item: QueueItem) => { if (!setOfQueuedItems.has(item)) return setOfQueuedItems.delete(item) const index = findIndex(queue, (o) => o === item) queue.splice(index, 1) } function queueIfNeeded() { if (microtaskIsQueued) return microtaskIsQueued = true queueMicrotask(() => { unstable_batchedUpdates(function runQueue() { while (queue.length > 0) { const item = queue.shift()! setOfQueuedItems.delete(item) let newValue if (TRACE) { item.debug?.history.push(`queue reached`) } try { newValue = item.der.getValue() } catch (error) { if (TRACE) { item.debug?.history.push(`queue: der.getValue() errored`) } console.error( 'A `der.getValue()` in `usePrismInstance(der)` threw an error. ' + "This may be a zombie child issue, so we're gonna try to get its value again in a normal react render phase." + 'If you see the same error again, then you either have an error in your prism code, or the deps array in `usePrism(fn, deps)` is missing ' + 'a dependency and causing the prism to read stale values.', ) console.error(error) item.runUpdate() continue } if (newValue !== item.lastValue) { item.lastValue = newValue item.runUpdate() } } microtaskIsQueued = false }, 1) }) } /** * Like `usePrismInstance()`, but it expects the prism's instance to be * static and never change between renders. * * @param der - The prism * @param debugLabel - The label used by the debugger * @returns The value of the prism */ export function _usePrismStaticInstance( der: Prism, debugLabel?: string, ): T { const _forceUpdate = useForceUpdate(debugLabel) const ref = useRef>(undefined as $IntentionalAny) if (!ref.current) { lastOrder++ ref.current = { order: lastOrder, runUpdate: () => { if (!ref.current.unmounted) { _forceUpdate() } }, der, lastValue: undefined as $IntentionalAny, unmounted: false, queueUpdate: () => { if (TRACE) { ref.current.debug?.history.push(`queueUpdate()`) } pushToQueue(ref.current) }, untap: der.onStale(() => { if (TRACE) { ref.current.debug!.history.push(`onStale(cb)`) } ref.current!.queueUpdate() }), } if (TRACE) { ref.current.debug = { label: debugLabel, traceOfFirstTimeRender: new Error(), history: [], } } } if (process.env.NODE_ENV !== 'production') { if (der !== ref.current.der) { console.error( 'Argument `der` in `usePrismInstance(der)` should not change between renders.', ) } } useLayoutEffect(() => { return function onUnmount() { ref.current.unmounted = true ref.current.untap() removeFromQueue(ref.current) } }, []) // if we're queued but are rendering before our turn, remove us from the queue removeFromQueue(ref.current) const newValue = ref.current.der.getValue() ref.current.lastValue = newValue if (TRACE) { ref.current.debug?.history.push(`rendered`) } return newValue } /** * A React hook that returns the value of the prism that it received as the first argument. * It works like an implementation of Dataverse's Ticker, except that it runs the side effects in * an order where a component's prism is guaranteed to run before any of its descendents' prisms. * * @param der - The prism * @param debugLabel - The label used by the debugger * * @remarks * It looks like this new implementation of usePrism() manages to: * 1. Not over-calculate the prisms * 2. Render prism in ancestor -\> descendent order * 3. Not set off React's concurrent mode alarms * * * I'm happy with how little bookkeeping we ended up doing here. * * --- * * Notes on the latest implementation: * * # Remove cold prism reads * * Prior to the latest change, the first render of every `usePrismInstance()` resulted in a cold read of its inner prism. * Cold reads are predictably slow. The reason we'd run cold reads was to comply with react's rule of not running side-effects * during render. (Turning a prism hot is _technically_ a side-effect). * * However, now that users are animating scenes with hundreds of objects in the same sequence, the lag started to be noticable. * * This commit changes `usePrismInstance()` so that it turns its prism hot before rendering them. * * # Freshen prisms before render * * Previously in order to avoid the zombie child problem (https://kaihao.dev/posts/stale-props-and-zombie-children-in-redux) * we deferred freshening the prisms to the render phase of components. This meant that if a prism's dependencies * changed, `usePrismInstance()` would schedule a re-render, regardless of whether that change actually affected the prism's * value. Here is a contrived example: * * ```ts * const num = new Box(1) * const isPositiveD = prism(() => num.prism.getValue() >= 0) * * const Comp = () => { * return
{usePrismInstance(isPositiveD)}
* } * * num.set(2) // would cause Comp to re-render- even though 1 is still a positive number * ``` * * We now avoid this problem by freshening the prism (i.e. calling `der.getValue()`) inside `runQueue()`, * and then only causing a re-render if the prism's value is actually changed. * * This still avoids the zombie-child problem because `runQueue` reads the prisms in-order of their position in * the mounting tree. * * On the off-chance that one of them still turns out to be a zombile child, `runQueue` will defer that particular * `usePrismInstance()` to be read inside a normal react render phase. */ export function usePrismInstance(der: Prism, debugLabel?: string): T { return usePrism(() => der.getValue(), [der], debugLabel) } /** * Creates a new Atom, similar to useState(), but the component * won't re-render if the value of the atom changes. * * @param initialState - Initial state * @returns The Atom * * @example * * Usage * ```tsx * import {useAtom, useVal} from '@theatre/react' * import {useEffect} from 'react' * * function MyComponent() { * const atom = useAtom({count: 0, ready: false}) * * const onClick = () => * atom.setByPointer( * (p) => p.count, * (count) => count + 1, * ) * * useEffect(() => { * setTimeout(() => { * atom.setByPointer((p) => p.ready, true) * }, 1000) * }, []) * * const ready = useVal(atom.pointer.ready) * if (!ready) return
Loading...
* return * } * ``` */ export function useAtom(initialState: T): Atom { const ref = useRef>(undefined as $IntentionalAny) if (!ref.current) { ref.current = new Atom(initialState) } return ref.current } ================================================ FILE: packages/react/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "dist", "lib": ["ESNext", "DOM"], "rootDir": "src", "types": ["jest", "node"], "emitDeclarationOnly": true, "target": "es6", "composite": true }, "include": ["./src/**/*"], "references": [{"path": "../dataverse"}] } ================================================ FILE: packages/saaz/.gitignore ================================================ /dist ================================================ FILE: packages/saaz/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: packages/saaz/README.md ================================================ # Saaz ================================================ FILE: packages/saaz/devEnv/api-extractor.json ================================================ /** * Config file for API Extractor. For more info, please visit: https://api-extractor.com */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", /** * Optionally specifies another JSON config file that this file extends from. This provides a way for * standard settings to be shared across multiple projects. * * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be * resolved using NodeJS require(). * * SUPPORTED TOKENS: none * DEFAULT VALUE: "" */ "extends": "../../../devEnv/api-extractor-base.json", // "extends": "my-package/include/api-extractor-base.json" /** * Determines the "" token that can be used with other config file settings. The project folder * typically contains the tsconfig.json and package.json config files, but the path is user-defined. * * The path is resolved relative to the folder of the config file that contains the setting. * * The default value for "projectFolder" is the token "", which means the folder is determined by traversing * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error * will be reported. * * SUPPORTED TOKENS: * DEFAULT VALUE: "" */ "projectFolder": ".." } ================================================ FILE: packages/saaz/devEnv/api-extractor.tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "../tsconfig.json" } ================================================ FILE: packages/saaz/devEnv/build.ts ================================================ import * as path from 'path' import {build} from 'esbuild' const definedGlobals = {} function createBundles(watch: boolean) { const pathToPackage = path.join(__dirname, '../') const esbuildConfig: Parameters[0] = { entryPoints: [path.join(pathToPackage, 'src/index.ts')], bundle: true, sourcemap: true, define: definedGlobals, watch, platform: 'neutral', mainFields: ['browser', 'module', 'main'], target: ['firefox57', 'chrome58'], conditions: ['browser', 'node'], } void build({ ...esbuildConfig, outfile: path.join(pathToPackage, 'dist/index.js'), format: 'cjs', }) // build({ // ...esbuildConfig, // outfile: path.join(pathToPackage, 'dist/index.mjs'), // format: 'esm', // }) } createBundles(false) ================================================ FILE: packages/saaz/devEnv/tsconfig.json ================================================ { } ================================================ FILE: packages/saaz/package.json ================================================ { "name": "@theatre/saaz", "version": "0.7.0", "license": "Apache-2.0", "author": { "name": "Aria Minaei", "email": "aria@theatrejs.com", "url": "https://github.com/AriaMinaei" }, "repository": { "type": "git", "url": "https://github.com/theatre-js/theatre", "directory": "packages/saaz" }, "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist/**/*" ], "scripts": { "prepack": "node ../../devEnv/ensurePublishing.js", "typecheck": "yarn run build:ts", "build": "run-s build:ts build:js build:api-json", "build:ts": "tsc --build ./tsconfig.json", "build:js": "tsx ./devEnv/build.ts", "build:api-json": "api-extractor run --local --config devEnv/api-extractor.json", "prepublish": "node ../../devEnv/ensurePublishing.js", "clean": "rm -rf ./dist && rm -f tsconfig.tsbuildinfo", "docs": "typedoc src/index.ts --out api --plugin typedoc-plugin-markdown --readme none", "precommit": "yarn run docs" }, "devDependencies": { "@microsoft/api-extractor": "^7.36.4", "@theatre/dataverse": "workspace:*", "@theatre/utils": "workspace:*", "@types/jest": "^26.0.23", "@types/lodash-es": "^4.17.4", "@types/node": "^15.6.2", "esbuild": "^0.12.15", "fast-deep-equal": "^3.1.3", "immer": "^9.0.6", "jest-diff": "^29.6.4", "lodash-es": "^4.17.21", "nanoid": "^4.0.2", "npm-run-all": "^4.1.5", "tsx": "4.7.0", "typedoc": "^0.24.8", "typedoc-plugin-markdown": "^3.15.4", "typescript": "5.1.6" }, "dependencies": { "idb": "^7.1.1" } } ================================================ FILE: packages/saaz/src/__snapshots__/rogue.test.ts.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Rogue merging defaults 1`] = ` { "a": { "aStep": 1, "bStep": 1, "foo": "setBy1", "obj": { "objA": "true", "objB": "true", }, }, } `; exports[`Rogue overriding an existing prop 1`] = ` [ { "branchName": "base", "path": [ [ "base", "a", ], ], "type": "SetBoxedValue", "value": 2, }, ] `; exports[`Rogue setting a non-existing prop 1`] = ` [ { "$branches": { "base": { "$mapProps": { "a": { "$branches": { "base": { "$boxedValue": 1, }, }, "$type": [ "boxed", "base", ], }, }, }, }, "$type": [ "map", "base", ], }, [ { "path": [ [ "base", "a", ], ], "type": "ChangeType", "value": [ "boxed", "base", ], }, { "branchName": "base", "path": [ [ "base", "a", ], ], "type": "SetBoxedValue", "value": 1, }, ], ] `; exports[`Rogue setting a non-existing prop to an object 1`] = ` [ { "$branches": { "base": { "$mapProps": { "a": { "$branches": { "base": { "$mapProps": { "b": { "$branches": { "base": { "$boxedValue": 1, }, }, "$type": [ "boxed", "base", ], }, }, }, }, "$type": [ "map", "base", ], }, }, }, }, "$type": [ "map", "base", ], }, [ { "path": [ [ "base", "a", ], ], "type": "ChangeType", "value": [ "map", "base", ], }, { "path": [ [ "base", "a", ], [ "base", "b", ], ], "type": "ChangeType", "value": [ "boxed", "base", ], }, { "branchName": "base", "path": [ [ "base", "a", ], [ "base", "b", ], ], "type": "SetBoxedValue", "value": 1, }, ], ] `; ================================================ FILE: packages/saaz/src/back/BackMemoryAdapter.ts ================================================ import type {$IntentionalAny, BackStorageAdapter} from '../types' export class BackMemoryAdapter implements BackStorageAdapter { private _state: { lists: { [key in string]?: Array<{id: string}> } singularValues: { [key in string]?: unknown } } = {lists: {}, singularValues: {}} export(): unknown { return this._state } constructor(state?: unknown) { if (state) { this._state = state as $IntentionalAny } } async get(key: string): Promise { return this._state.singularValues[key] as T | void } async set(key: string, value: T): Promise { this._state.singularValues[key] = value } async pushToList( key: string, rows: T[], ): Promise { if (!this._state.lists[key]) { this._state.lists[key] = [] } const list = this._state.lists[key]! if (list.some((row) => rows.some((r) => r.id === row.id))) { throw new Error( `Cannot push to list "${key}" because one or more rows already exist`, ) } list.push(...rows) } async getList(key: string): Promise { if (!this._state.lists[key]) { return [] } return this._state.lists[key]! as $IntentionalAny } async pluckFromList( key: string, ids: string[], ): Promise> { if (!this._state.lists[key]) { return [] } const list = this._state.lists[key]! return ids.map((id) => { const index = list.findIndex((row) => row.id === id) if (index === -1) { return undefined } else { const row = list[index] list.splice(index, 1) return row as T } }) } } ================================================ FILE: packages/saaz/src/back/BackStorage.ts ================================================ import {defer} from '@theatre/utils/defer' import type {BackStorageAdapter, Transaction} from '../types' export class BackStorage { private _readyD = defer() constructor(opts: {dbName: string; storageAdapter: BackStorageAdapter}) { this._readyD.resolve(void 0) } get ready() { return this._readyD.promise } async pushToJournal(opts: { dbName: string clock: number jsonDiff: unknown peerId: string peerClockFrom: number peerClockTo: number timestamp: number transactions: Transaction[] }) {} } ================================================ FILE: packages/saaz/src/back/SaazBack.ts ================================================ import {defer} from '@theatre/utils/defer' import {applyOptimisticUpdateToState} from '../shared/transactions' import type { BackApplyUpdateOps, SaazBackInterface, BackStorageAdapter, BackGetUpdateSinceClockResult, Schema, $IntentionalAny, PeerSubscribeCallback, AllPeersPresenceState, PeerPresenceState, FullSnapshot, } from '../types' import {BackStorage} from './BackStorage' import type {DebouncedFunc} from 'lodash-es' import {cloneDeep, throttle} from 'lodash-es' import {ensureStateIsUptodate as ensureOpStateIsUptodate} from '../shared/utils' import {Atom} from '@theatre/dataverse' import deepEqual from '@theatre/utils/deepEqual' export default class SaazBack implements SaazBackInterface { private _dbName: string private _storage: BackStorage private _readyDeferred = defer() private _dbState: FullSnapshot<$IntentionalAny> = {cell: {}, op: {}} private _clock: number | null = null private _peerStates: { [peerId in string]?: { lastIncorporatedPeerClock: number } } = {} private _subsribers: Array = [] private _schema: Schema<{$schemaVersion: number}> private _presenceState: Atom = new Atom({}) private _schedulePresenseUpdate: DebouncedFunc<() => void> constructor(opts: { dbName: string storageAdapter: BackStorageAdapter schema: Schema<$IntentionalAny> }) { this._schema = opts.schema this._dbName = opts.dbName this._storage = new BackStorage({ dbName: opts.dbName, storageAdapter: opts.storageAdapter, }) void this._storage.ready.then(() => { this._readyDeferred.resolve() }) this._schedulePresenseUpdate = throttle( () => { this._callSubscribersForPresenceUpdate() }, 1000 / 30, {leading: true}, ) this._schedulePresenseUpdate.cancel() } get ready() { return this._readyDeferred.promise } get isReady(): boolean { return this._readyDeferred.status === 'resolved' } async getUpdatesSinceClock(opts: { clock: number | null peerId: string }): Promise { await this._readyDeferred.promise return this._getUpdatesSinceClockSync(opts) } async applyUpdates( opts: BackApplyUpdateOps, ): Promise< ({ok: true} & BackGetUpdateSinceClockResult) | {ok: false; error: unknown} > { await this._readyDeferred.promise return this._applyUpdatesSync(cloneDeep(opts)) } async subscribe( opts: { peerId: string }, onUpdate: PeerSubscribeCallback, ): Promise<() => void> { await this._readyDeferred.promise return this._subscribeSync(cloneDeep(opts), onUpdate) } private _getUpdatesSinceClockSync(opts: { clock: number | null peerId: string }): BackGetUpdateSinceClockResult { if (!this.ready) { throw new Error('Backend is not ready') } const lastIncorporatedPeerClock = this._peerStates[opts.peerId]?.lastIncorporatedPeerClock ?? null const clock = this._clock ?? -1 const {peerId} = opts if ((opts.clock ?? -1) === clock) { return {hasUpdates: false, peerId, clock, lastIncorporatedPeerClock} } return { hasUpdates: true, clock, lastIncorporatedPeerClock, peerId, snapshot: { type: 'Snapshot', value: this._dbState, }, } } async getLastIncorporatedPeerClock(opts: { peerId: string }): Promise<{lastIncorporatedPeerClock: number | null; peerId: string}> { await this._readyDeferred.promise return { peerId: opts.peerId, lastIncorporatedPeerClock: this._peerStates[opts.peerId]?.lastIncorporatedPeerClock ?? null, } } async closePeer(opts: {peerId: string}): Promise<{ok: boolean}> { delete this._peerStates[opts.peerId] return {ok: true} } async updatePresence(opts: { peerId: string presence: PeerPresenceState }): Promise<{ok: true} | {ok: false; error: unknown}> { await this._readyDeferred.promise return this._updatePresenceSync(cloneDeep(opts)) } private _updatePresenceSync(opts: { peerId: string presence: PeerPresenceState }): {ok: true} | {ok: false; error: unknown} { if (!this.ready) { throw new Error('Backend is not ready') } if (!deepEqual(this._presenceState.get()[opts.peerId], opts.presence)) { this._presenceState.setByPointer((p) => p[opts.peerId], opts.presence) this._schedulePresenseUpdate() } return {ok: true} } private _applyUpdatesSync( opts: BackApplyUpdateOps, ): | ({ok: true} & BackGetUpdateSinceClockResult) | {ok: false; error: unknown} { if (!this.ready) { throw new Error('Backend is not ready') } if (opts.updates.length === 0) { return { ok: true, ...this._getUpdatesSinceClockSync({ clock: opts.backendClock, peerId: opts.peerId, }), } } if (!this._peerStates[opts.peerId]) { this._peerStates[opts.peerId] = { lastIncorporatedPeerClock: -1, } } const peerState = this._peerStates[opts.peerId]! // const rebasing = opts.backendClock !== this._clock let snapshotSoFar = ensureOpStateIsUptodate(this._dbState, this._schema) let lastAcknowledgedClock = peerState.lastIncorporatedPeerClock let backendClock = this._clock ?? -1 // const updatesToIncorporate = [] for (const update of opts.updates) { if (peerState.lastIncorporatedPeerClock >= update.peerClock) { continue } const snapshotBefore = snapshotSoFar const [opSnapshotAfter] = applyOptimisticUpdateToState( update, snapshotBefore, this._schema, true, ) snapshotSoFar = opSnapshotAfter lastAcknowledgedClock = update.peerClock backendClock++ } if (lastAcknowledgedClock !== peerState.lastIncorporatedPeerClock) { this._dbState = snapshotSoFar peerState.lastIncorporatedPeerClock = lastAcknowledgedClock this._clock = backendClock this._callSubscribersForBackendStateUpdate() // TODO: save individual updates to storage } const s = this._getUpdatesSinceClockSync({ clock: opts.backendClock, peerId: opts.peerId, }) return { ok: true, ...s, } } private _subscribeSync( opts: {peerId: string}, onUpdate: PeerSubscribeCallback, ): () => void { this._subsribers.push(onUpdate) let unsubscribed = false const unsub = () => { if (unsubscribed) return unsubscribed = true const i = this._subsribers.indexOf(onUpdate) if (i !== -1) { this._subsribers.splice(i, 1) } } return unsub } private _callSubscribersForBackendStateUpdate() { this._schedulePresenseUpdate.cancel() for (const sub of this._subsribers) { sub({presence: this._presenceState.get(), shouldCheckForUpdates: true}) } } private _callSubscribersForPresenceUpdate() { this._schedulePresenseUpdate.cancel() for (const sub of this._subsribers) { sub({presence: this._presenceState.get(), shouldCheckForUpdates: false}) } } } ================================================ FILE: packages/saaz/src/front/FrontIdbAdapter.ts ================================================ import type {IDBPTransaction} from 'idb' import {openDB} from 'idb' import type { FrontStorageAdapterTransaction, FrontStorageAdapter, } from '../types' type SingularValue = { sessionAndKey: string session: string value: T key: string } type ListItem = { sessionAndKeyAndId: `${string}/${string}/${string}` session: string value: V key: string sessionAndKey: `${string}/${string}` } export class FrontIDBAdapter implements FrontStorageAdapter { constructor( private _idbName: string, private _currentSessionName: string, ) {} private _dbPromise = this._initializeDB() async _initializeDB() { return openDB(this._idbName, 1, { upgrade(db) { const singularValueStore = db.createObjectStore('singularValues', { keyPath: 'sessionAndKey', }) singularValueStore.createIndex('session', 'session') const listsStore = db.createObjectStore('lists', { keyPath: 'sessionAndKeyAndId', }) listsStore.createIndex('session', 'session') listsStore.createIndex('sessionAndKey', 'sessionAndKey') }, }) } get ready() { return this._dbPromise } async transaction( fn: (opts: FrontStorageAdapterTransaction) => Promise, ): Promise { const db = await this.ready const tx = db.transaction(['singularValues', 'lists'], 'readwrite') const t = new IDBTransaction(tx) return await fn(t) } async get(key: string, session: string): Promise { return await this.transaction(({get}) => get(key, session)) } async getList( key: string, session: string, ): Promise { return await this.transaction(({getList}) => getList(key, session)) } } class IDBTransaction implements FrontStorageAdapterTransaction { constructor(private _tx: IDBPTransaction) {} async get(key: string, session: string): Promise { const sessionAndKey = `${session}/${key}` const store = this._tx.objectStore('singularValues') const v = (await store.get(sessionAndKey)) as SingularValue return v?.value } async getAll(key: string): Promise> { const index = this._tx.objectStore('singularValues').index('session') const all: Array> = await index.getAll() const record: Record = {} for (const row of all) { record[row.session] = row.value } return record } async set(key: string, value: T, session: string): Promise { const sessionAndKey = `${session}/${key}` const store = this._tx.objectStore('singularValues') const s: SingularValue = { sessionAndKey: sessionAndKey, value, session, key, } await store.put(s) } async deleteSession(session: string): Promise { { const store = this._tx.objectStore('singularValues') const index = store.index('session') const sessionIndex = await index.getAllKeys(session) for (const key of sessionIndex) { await store.delete(key) } } const listsStore = this._tx.objectStore('lists') const listsIndex = listsStore.index('session') const listsSessionIndex = await listsIndex.getAllKeys(session) for (const key of listsSessionIndex) { await listsStore.delete(key) } } async pushToList( key: string, rows: T[], session: string, ): Promise { const store = this._tx.objectStore('lists') for (const row of rows) { const listItem: ListItem = { key, session, value: row, sessionAndKeyAndId: `${session}/${key}/${row.id}`, sessionAndKey: `${session}/${key}`, } const existingItem = await store.get(listItem.sessionAndKeyAndId) if (existingItem) { throw new Error( `Cannot push to list "${key}" because an entry with id "${row.id}" already exists`, ) } // Save the row with key and id properties await store.put(listItem) } } async getList( key: string, session: string, ): Promise { const store = this._tx.objectStore('lists') const keyIndex = store.index('sessionAndKey') const sessionAndKey = `${session}/${key}` const all = await keyIndex.getAll(sessionAndKey) return all.map((s) => s.value) } async pluckFromList( key: string, ids: string[], session: string, ): Promise> { const store = this._tx.objectStore('lists') const results: Array = [] for (const id of ids) { const sessionAndKeyAndId = `${session}/${key}/${id}` const item = await store.get(sessionAndKeyAndId) if (item) { results.push(item.value as T) await store.delete(item) } else { results.push(undefined) } } return results } } ================================================ FILE: packages/saaz/src/front/FrontMemoryAdapter.ts ================================================ import type {Draft} from 'immer' import {createDraft, current, finishDraft} from 'immer' import type {$IntentionalAny, FrontStorageAdapterTransaction} from '../types' import type {FrontStorageAdapter} from '../types' import {defer} from '@theatre/utils/defer' export class FrontMemoryAdapter implements FrontStorageAdapter { private _state: { sessions: { [key in string]?: { lists: { [key in string]?: Array<{id: string}> } keyval: { [key in string]?: unknown } } } } = {sessions: {}} private _readyDeferred = defer() private _lastTransactionPromise: Promise = Promise.resolve() export(): unknown { return this._state } constructor(state?: unknown) { if (state) { this._state = state as $IntentionalAny } // nothing to initialize, so just resolve the promise this._readyDeferred.resolve() } get ready() { return this._readyDeferred.promise } async transaction( fn: (opts: FrontStorageAdapterTransaction) => Promise, ): Promise { const deferred = defer() const oldTransactionPromise = this._lastTransactionPromise this._lastTransactionPromise = deferred.promise try { await oldTransactionPromise } catch (err) { // ignore the error. it'll be handled elsewhere } await this.ready const originalState = this._state const draft = createDraft(originalState) const t = new Transaction(draft) try { const result = await fn(t) this._state = finishDraft(draft) return result } catch (error) { throw error } finally { deferred.resolve() } } async get(key: string, session: string): Promise { return await this.transaction(({get}) => get(key, session)) } async getList( key: string, session: string, ): Promise { return await this.transaction(({getList}) => getList(key, session)) } } class Transaction implements FrontStorageAdapterTransaction { constructor(private _draft: Draft) {} async get(key: string, session: string): Promise { return current(this._draft.sessions[session]?.keyval)?.[key] as T | void } async set(key: string, value: T, session: string): Promise { this._draft.sessions[session] ??= {keyval: {}, lists: {}} const s = this._draft.sessions[session]! s.keyval[key] = value } async getAll(key: string): Promise> { const vals: Record = {} for (const [sessionId, v] of Object.entries(this._draft.sessions)) { if (!v) continue if (Object.hasOwn(v.keyval, key)) { const value: T | undefined = v.keyval[key] as $IntentionalAny if (value === undefined) continue vals[sessionId] = value } } return vals } async deleteSession(session: string): Promise { delete this._draft.sessions[session] } async pushToList( key: string, rows: T[], session: string, ): Promise { this._draft.sessions[session] ??= {keyval: {}, lists: {}} const s = this._draft.sessions[session]! if (!s.lists[key]) { s.lists[key] = [] } const list = s.lists[key]! if (list.some((row) => rows.some((r) => r.id === row.id))) { throw new Error( `Cannot push to list "${key}" because one or more rows already exist`, ) } list.push(...rows) } async getList( key: string, session: string, ): Promise { const s = this._draft.sessions[session] if (!s?.lists[key]) { return [] } return current(s?.lists)[key]! as $IntentionalAny } async pluckFromList( key: string, ids: string[], session: string, ): Promise> { this._draft.sessions[session] ??= {keyval: {}, lists: {}} const s = this._draft.sessions[session]! if (!s.lists[key]) { return [] } const list = s.lists[key]! return ids.map((id) => { const index = list.findIndex((row) => row.id === id) if (index === -1) { return undefined } else { const row = list[index] list.splice(index, 1) return row as T } }) } } ================================================ FILE: packages/saaz/src/front/FrontStorage.ts ================================================ import type { $IntentionalAny, SessionState, FrontStorageAdapterTransaction, Transaction, } from '../types' import type {FrontStorageAdapter} from '../types' export class FrontStorage { constructor( readonly dbName: string, private readonly _adapter: FrontStorageAdapter, ) {} async transaction(fn: (opts: FrontStorageTransaction) => Promise) { return await this._adapter.transaction(async (adapterOpts) => { const t = new FrontStorageTransactionImpl(adapterOpts) return await fn(t) }) } } class FrontStorageTransactionImpl { constructor(private _adapterTransaction: FrontStorageAdapterTransaction) {} async setSessionState(s: SessionState, session: string) { // TODO: serializing the state every time this changes probably wastes IO. // better to create a writeahead log and compact it every once in a while await this._adapterTransaction.set('sessionState', s, session) } async getSessionState( session: string, ): Promise | undefined> { const v = this._adapterTransaction.get('sessionState', session) return v as $IntentionalAny } async deleteSession(session: string): Promise { await this._adapterTransaction.deleteSession(session) } async getMostRecentlySyncedSessionState(): Promise< SessionState | undefined > { const allSessionStates = this._adapterTransaction.getAll>('sessionState') let latest: SessionState | undefined for (const [peerId, sessionState] of Object.entries(allSessionStates)) { if ( typeof sessionState.backendClock === 'number' && sessionState.backendClock > (latest?.backendClock ?? -1) ) { latest = sessionState } } return latest } async getOptimisticUpdates(session: string): Promise { const s = await this._adapterTransaction.getList<{ id: string transaction: Transaction }>('optimisticUpdates', session) return s.map((t) => t.transaction) } async pluckOptimisticUpdates( transactions: Pick[], session: string, ): Promise { await this._adapterTransaction.pluckFromList( 'optimisticUpdates', transactions.map(({peerClock}) => '#' + peerClock), session, ) } async getAllExistingSessionIds(): Promise { const all = await this._adapterTransaction.getAll('session') return Object.keys(all) } async pushOptimisticUpdates( transactions: Transaction[], session: string, ): Promise { await this._adapterTransaction.pushToList<{ id: string transaction: Transaction }>( 'optimisticUpdates', transactions.map((t) => ({ id: '#' + t.peerClock, transaction: t, })), session, ) } } export type FrontStorageTransaction = FrontStorageTransactionImpl ================================================ FILE: packages/saaz/src/front/SaazFront.ts ================================================ import type { $IntentionalAny, AllPeersPresenceState, BackGetUpdateSinceClockResult, SessionState, FullSnapshot, SaazBackInterface, Schema, TempTransaction, TempTransactionApi, Transaction, ValidOpSnapshot, } from '../types' import type {ValidGenerators, EditorDefinitionToEditorInvocable} from '../types' import type {OnDiskSnapshot} from '../types' import type {FrontStorageAdapter} from '../types' import { applyOptimisticUpdateToState, recordInvokations, } from '../shared/transactions' import {FrontStorage} from './FrontStorage' import {ensureStateIsUptodate} from '../shared/utils' import {debounce} from 'lodash-es' import type {Prism} from '@theatre/dataverse' import {Atom, prism, val} from '@theatre/dataverse' import waitForPrism from '@theatre/utils/waitForPrism' import {subscribeDebounced} from '@theatre/utils/subscribeDebounced' import fastDeepEqual from 'fast-deep-equal' import {diff} from 'jest-diff' import type {Ops} from '../rogue' import {jsonFromCell, makeDraft} from '../rogue' import memoizeFn from '@theatre/utils/memoizeFn' import {nanoid} from 'nanoid' import {defer} from '@theatre/utils/defer' import type {VoidFn} from '@theatre/utils/types' const emptyObject = {} const MAX_UNDO_STACK_SIZE = 1000 const PEERID_LENGTH = 32 const LOCK_PREFIX = 'theatrejs-saaz-closed-session-lock/' type UndoStackItem = { forwardOps: Ops backwardOps: Ops } type SessionLock = { peerId: string unlock: VoidFn } type AtomState = { /** * The queue of optimistic updates that have not been acknowledged by the backend yet. As soon as * the backend acknowledges an update, it will be removed from this queue. */ optimisticUpdatesQueue: Transaction[] sessionState: SessionState | null emptySnapshot: FullSnapshot tempTransactions: TempTransaction[] allPeersPresenceState: AllPeersPresenceState initialized: boolean /** * This is a mirror of the frontend state _as it is stored_ on the front storage. * We can use this to determine if the front storage is up to date. */ frontStorageStateMirror: { backendState: SessionState | null optimisticUpdatesQueue: Transaction[] } peerClock: number closedSessions: ClosedSession[] undoRedo: { // the stack is a list of transactions that can be undone. The first item is the most recent transaction. stack: UndoStackItem[] // 0 means no undo has been called since the last transaction. cursor: number } } export class SaazFront< OpSnapshot extends ValidOpSnapshot, Editors extends {}, Generators extends ValidGenerators, CellShape extends {} = {}, > { /** * Using an atom here so we can react to changes to its state */ private readonly _atom: Atom> /** * The initial snapshot that was saved to disk, and provided as opts.initialSnapshot to the constructor */ private readonly _diskSnapshot: OnDiskSnapshot | null /** * The peer id of this frontend. It is supposed to be unique per-tab, and globally. If there are more than * one Saaz instance per tab, then each should have its own peer id. Better to generate this via UUID. * * Note that the backend will associate the peerId with the user's account to make sure the peerId cannot * be stolen. * * Each user account may have many peerIds associated with it (the user may have multiple tabs, or multiple devices open at the same time). * * The backend will periodically garbage-collect the peerIds that have not been used for a while. In the off-chance that a peerId is garbage-collected * on the backend but the frontend still has it, the frontend will generate a new peerId and use that instead. */ private readonly _peerId: string /** * The name of the database. This is used to namespace the keys in the storage adapter. */ private _dbName: string /** * The storage adapter that the frontend uses to store its state. The storage is meant to allow the user to work offline * and be able to close the tab and reopen it later and continue working. */ private _storage: FrontStorage /** * A promise that resolves when the frontend is ready to be used. This is the same as the promise returned by the ready getter. */ private _initializedPromise: Promise /** * The backend that the frontend communicates with. In environments where the backend and the frontend are in the same process, * then a reference to SaazBack can be passed here. This is useful for testing. * * In environments where the backend and the frontend are in different processes (a client/server setup), an object that * implements `SaazBackInterface` should be passed here. See `@theatre/sync-server` for a TRPC-based implementation. */ private _backend: SaazBackInterface /** * The schema includes the shape of the snapshot, a nested object of editors, and a shallow object of generator functions. */ private readonly _schema: Schema /** * A counter that is used to generate unique ids for temp transactions. */ private _tempTransactionCounter: number = 0 /** * A list of functions that will be called when the frontend is destroyed. */ private _teardownCallbacks: (() => void)[] = [] /** * We use dataverse prisms to derive several values from the atom. This makes the reactive parts of the code easier to maintain. */ private _prisms: { base: Prism> /** * The state as it is on the backend, plus all the optimistic updates that have not been acknowledged by the backend yet. */ optimisticState: Prism> /** * This is optimisticState (see above), plus the temp transactions. */ withTemps: Prism> /** * This is withTemps (see above), plus the temp transactions of all the peers. */ withPeers: Prism> /** * The number of optimistic updates that have not been acknowledged by the backend yet. * If this is 0, then the backend has received all the updates that the frontend has sent. * This doesn't mean that the backend has processed all the updates yet. * It also doesn't mean the frontend has received all the updates from other peers through the backend. */ countOfUnpushedUpdatesToBackend: Prism /** * A prism that resolves to true if the frontend has synced to _its own storage_. This means * that if the user closes the tab or the session crashes, and navigates back to the page, * the frontend will be able to restore its state from the storage. */ allSyncedToFrontStorage: Prism } = { base: prism>(() => { return ( val(this._atom.pointer.sessionState.snapshot) ?? val(this._atom.pointer.emptySnapshot) ) }), optimisticState: prism>(() => { const base = this._prisms.base.getValue() let stateSoFar = base // this may become a bottleneck const closedSessions = val(this._atom.pointer.closedSessions) for (const session of closedSessions) { for (const update of session.optimisticUpdates) { stateSoFar = this._cachedApplyTransactionToState( update, base, stateSoFar, ) } } const optimisticUpdates = val(this._atom.pointer.optimisticUpdatesQueue) const lastIncorporatedPeerClock = val(this._atom.pointer.sessionState.lastIncorporatedPeerClock) ?? -1 for (const update of optimisticUpdates) { // if the update has already been incorporated into the backend state, skip it (it'll be garbage collected soon) if (update.peerClock <= lastIncorporatedPeerClock) continue stateSoFar = this._cachedApplyTransactionToState( update, base, stateSoFar, ) } return stateSoFar }), withTemps: prism>(() => { let currentState = this._prisms.optimisticState.getValue() const temps = val(this._atom.pointer.tempTransactions) for (const temp of temps) { ;[currentState] = applyOptimisticUpdateToState( temp, currentState, this._schema, true, ) } return currentState }), withPeers: prism>(() => { let currentState = this._prisms.withTemps.getValue() for (const [peerId, presence] of Object.entries( val(this._atom.pointer.allPeersPresenceState), )) { if (peerId === this._peerId) continue if (!presence) continue for (const temp of presence.tempTransactions) { ;[currentState] = applyOptimisticUpdateToState( temp, currentState, this._schema, true, ) } } return currentState }), countOfUnpushedUpdatesToBackend: prism(() => { return val(this._atom.pointer.optimisticUpdatesQueue).length }), allSyncedToFrontStorage: prism(() => { // if not initialized, then we're not synced if (!val(this._atom.pointer.initialized)) return false // backendClock as of the last time the front communicated with the backend const backendClock = val(this._atom.pointer.sessionState.backendClock) ?? -1 // backendClock as of the last time we stored the backend's state in the front storage const lastStoredBackendClock = val( this._atom.pointer.frontStorageStateMirror.backendState.backendClock, ) ?? -1 // if we haven't yet stored the backend's state in the front storage, return false if (backendClock !== lastStoredBackendClock) return false // TODO: how about closed sessions? const outstandingUpdates = val(this._atom.pointer.optimisticUpdatesQueue) const storedUpdates = val( this._atom.pointer.frontStorageStateMirror.optimisticUpdatesQueue, ) if (outstandingUpdates.length === 0) { // there are no outstanding optimistic updates (they're all incorporated into the backend state), // so we can assume that the front storage is up to date. Note that front storage may not have // gartbage collected the old updates yet, but that's ok. they'll be garbage collected eventually. return true } const lastOutstandingUpdate = outstandingUpdates[outstandingUpdates.length - 1] const lastStoredUpdate = storedUpdates[storedUpdates.length - 1] if ( lastStoredUpdate && lastStoredUpdate.peerClock === lastOutstandingUpdate.peerClock ) { // the last outstanding update is the same as the last stored update, so we can assume that the front storage is up to date. return true } else { // the front storage has yet to catch up return false } }), } private _caches = { transactionToState: new WeakMap< Transaction, { before: FullSnapshot after: FullSnapshot base: FullSnapshot } >(), } constructor(opts: { schema: Schema backend: SaazBackInterface diskSnapshot?: OnDiskSnapshot peerId?: string dbName: string storageAdapter: FrontStorageAdapter /** * If true (default), the frontend will keep the prisms hot. This is a perf optimization */ keepPrismsHot?: boolean }) { if (opts.diskSnapshot) { this._diskSnapshot = { ...opts.diskSnapshot, snapshot: ensureStateIsUptodate( opts.diskSnapshot.snapshot, opts.schema, ), } } else { this._diskSnapshot = null } this._atom = new Atom>({ optimisticUpdatesQueue: [], emptySnapshot: ensureStateIsUptodate(null, opts.schema), sessionState: null, tempTransactions: [], allPeersPresenceState: emptyObject, initialized: false, frontStorageStateMirror: { backendState: null, optimisticUpdatesQueue: [], }, peerClock: -1, closedSessions: [], undoRedo: { stack: [], cursor: 0, }, }) this._initializedPromise = waitForPrism( prism(() => val(this._atom.pointer.initialized)), (v) => v === true, ) if (opts.keepPrismsHot !== false) { this._teardownCallbacks.push(this._prisms.withPeers.keepHot()) } this._schema = opts.schema this._peerId = opts.peerId ?? nanoid(PEERID_LENGTH) this._backend = opts.backend this._dbName = opts.dbName this._storage = new FrontStorage(this._dbName, opts.storageAdapter) void this._init() } private async _init() { await this._loadClosedSessions() // The most recent backend state that one of the closed sessions has cached. // We can use this until we get the first update from the backend const cachedBackendState: SessionState | undefined = await this._storage.transaction(async (t) => t.getMostRecentlySyncedSessionState(), ) // this will create the database and start a transaction await this._storage.transaction(async (t) => { const initialSnapshot = this._diskSnapshot if (!cachedBackendState) { // there are no closed/crashed sessions that have a backend state. this._setSessionState({ backendClock: initialSnapshot?.clock ?? null, lastIncorporatedPeerClock: null, lastSyncTime: null, snapshot: initialSnapshot?.snapshot ?? null, peerId: this._peerId, }) } else { if (initialSnapshot) { // TODO throw new Error(`Not implemented`) } else { this._atom.setByPointer((p) => p.sessionState, { lastSyncTime: null, backendClock: cachedBackendState.backendClock ?? null, lastIncorporatedPeerClock: null, peerId: this._peerId, snapshot: ensureStateIsUptodate( cachedBackendState.snapshot as $IntentionalAny, this._schema, ), }) } } this._atom.setByPointer((p) => p.initialized, true) }) this._teardownCallbacks.push( this._subscribeToBackend(), this._reflectBackendStateToStorage(), this._reflectOptimisticUpdatesToStorage(), this._reflectPresenceToBackend(), this._removeStalePeerPresenceStates(), this._reflectUpdatesToBackend(), this._gcClosedSessions(), ) } private async _loadClosedSessions() { // in case there are crashed/closed sesions that haven't synced // with backend yet, let's take them over const closedSessionsLocks = await this._acquireLocksOnClosedSessions() const closedSessions: ClosedSession[] = await this._storage.transaction( async (t) => await Promise.all( closedSessionsLocks.map( async (closedSessionLock): Promise => { return { optimisticUpdates: await t.getOptimisticUpdates( closedSessionLock.peerId, ), peerId: closedSessionLock.peerId, lastIncorporatedPeerClock: null, } }, ), ), ) this._atom.setByPointer((p) => p.closedSessions, closedSessions) } private async _acquireLocksOnClosedSessions(): Promise> { const allPeerIds: string[] = await this._storage.transaction(async (t) => t.getAllExistingSessionIds(), ) const all: Array = await Promise.all( allPeerIds.map(async (peerId) => { try { const d = defer() void navigator.locks.request( LOCK_PREFIX + peerId, { mode: 'exclusive', ifAvailable: true, steal: false, }, async (lock) => { if (!lock) { d.resolve(undefined) return } const unlockDeferred = defer() let locked = true d.resolve({ peerId, unlock: () => { if (!locked) return locked = false unlockDeferred.resolve() }, }) await unlockDeferred.promise }, ) return d.promise } catch (error) { return undefined } }), ) return all.filter((v): v is SessionLock => !!v) } teardown(): void { for (const cb of this._teardownCallbacks) { cb() } this._teardownCallbacks.length = 0 } private _removeStalePeerPresenceStates() { const schedule = debounce(() => { this._atom.setByPointer((p) => p.allPeersPresenceState, emptyObject) }, 1000 * 15) return this._atom.onChangeByPointer( (p) => p.allPeersPresenceState, schedule, ) } private _reflectPresenceToBackend() { let lastTempTransactions: TempTransaction[] = [] let lastUpdateSent: number = 0 return subscribeDebounced( this._atom.pointer.tempTransactions, async (tempTransactions) => { const now = Date.now() if ( !fastDeepEqual(tempTransactions, lastTempTransactions) || now - lastUpdateSent > 1000 * 5 ) { lastTempTransactions = tempTransactions lastUpdateSent = now void this._backend.updatePresence({ peerId: this._peerId, presence: {tempTransactions}, }) } await new Promise((resolve) => setTimeout(resolve, 30)) }, ) } private _reflectUpdatesToBackend() { const p = prism<{ peerId: string // lastIncorporatedPeerClock: number | null updates: Transaction[] }>(() => { const closedSessions = val(this._atom.pointer.closedSessions) for (const closedSession of closedSessions) { const updatesLeftToPush = closedSession.optimisticUpdates.filter( (update) => update.peerClock > (closedSession.lastIncorporatedPeerClock ?? -1), ) if (updatesLeftToPush.length > 0) { return { peerId: closedSession.peerId, lastIncorporatedPeerClock: closedSession.lastIncorporatedPeerClock ?? -1, updates: updatesLeftToPush, } } } // no closed sessions left to sync. let's sync the current session const lastIncorporatedPeerClock = val(this._atom.pointer.sessionState.lastIncorporatedPeerClock) ?? -1 const updatesLeftToPush = val( this._atom.pointer.optimisticUpdatesQueue, ).filter((update) => update.peerClock > lastIncorporatedPeerClock) return { updates: updatesLeftToPush, lastIncorporatedPeerClock, peerId: this._peerId, } }) return subscribeDebounced(p, async ({updates, peerId}) => { if (updates.length === 0) return const backendClock = val(this._atom.pointer.sessionState.backendClock) as | number | null try { const res = await this._backend.applyUpdates({ backendClock, peerId, updates, }) if (res.ok) { this._processBackendUpdate(res) } else { console.error(res.error) throw new Error('Backend rejected optimistic update') } } catch (errs) { console.error(errs) } }) } private _processBackendUpdate(s: BackGetUpdateSinceClockResult) { const originalBackendState = this._atom.get().sessionState if (!originalBackendState) { throw new Error('backend state not initialized') } if (!s.hasUpdates) { this._setSessionState({ ...originalBackendState, lastSyncTime: Date.now(), peerId: s.peerId, lastIncorporatedPeerClock: s.lastIncorporatedPeerClock, }) return } else { const {snapshot} = s if (snapshot.type !== 'Snapshot') { throw new Error('Non-snapshot updates not implemented') } this._setSessionState({ backendClock: s.clock, lastIncorporatedPeerClock: s.lastIncorporatedPeerClock, lastSyncTime: Date.now(), snapshot: snapshot.value, peerId: s.peerId, }) } } private _reflectBackendStateToStorage() { return subscribeDebounced( this._atom.pointer.sessionState, async (backendState) => { if (!backendState) return try { await this._storage.transaction(async (t) => { await t.setSessionState(backendState, this._peerId) }) this._atom.setByPointer( (p) => p.frontStorageStateMirror.backendState, backendState, ) } catch (error) { console.error(error) } }, ) } private _gcClosedSessions() { return subscribeDebounced( this._atom.pointer.closedSessions, async (closedSessions) => { const sessionsWithNoUpdates = closedSessions.filter( (s) => s.optimisticUpdates.length === 0, ) for (const emptySession of sessionsWithNoUpdates) { this._atom.reduceByPointer( (p) => p.closedSessions, (a) => a.filter((c) => c.peerId !== emptySession.peerId), ) await this._storage.transaction(async (t) => { await t.deleteSession(emptySession.peerId) }) void this._backend.closePeer({peerId: emptySession.peerId}) } }, ) } private _reflectOptimisticUpdatesToStorage() { return subscribeDebounced( this._atom.pointer.optimisticUpdatesQueue, async (memory) => { const last = this._atom.get().frontStorageStateMirror.optimisticUpdatesQueue if (memory.length === 0 && last.length === 0) return const toPush: Transaction[] = [] const toPluck: Transaction[] = [] for (const transacion of memory) { const existing = last.find( (t) => t.peerClock === transacion.peerClock, ) if (!existing) { toPush.push(transacion) } else { toPluck.push(existing) } } try { await this._storage.transaction(async (t) => { await t.pushOptimisticUpdates(toPush, this._peerId) await t.pluckOptimisticUpdates(toPluck, this._peerId) }) this._atom.setByPointer( (p) => p.frontStorageStateMirror.optimisticUpdatesQueue, memory, ) } catch (error) { console.error(error) return } }, ) } private _cachedApplyTransactionToState( transaction: Transaction, base: FullSnapshot, before: FullSnapshot, ): FullSnapshot { let cache = this._caches.transactionToState.get(transaction) if (cache) { if (cache.before === before) { return cache.after } } const [after] = applyOptimisticUpdateToState( transaction, before, this._schema, true, ) if (!cache) { cache = {before: before, after: after, base} this._caches.transactionToState.set(transaction, cache) } else { cache.before = before cache.after = after cache.base = base } return after } _setSessionState(opts: SessionState) { const s = { ...opts, value: ensureStateIsUptodate(opts.snapshot, this._schema), } if (s.peerId === this._peerId) { this._atom.setByPointer((p) => p.sessionState, s) // let's GC the updates the backend has incorporated const lastAcknowledgedPeerClock = s.lastIncorporatedPeerClock ?? -1 const existingQueue = this._atom.get().optimisticUpdatesQueue if ( existingQueue.length > 0 && existingQueue[0].peerClock <= lastAcknowledgedPeerClock ) { const newQueue = existingQueue.filter( (update) => update.peerClock > lastAcknowledgedPeerClock, ) this._atom.setByPointer((p) => p.optimisticUpdatesQueue, newQueue) } } else { // the session state comes from an update belonging to a different session. let's update the // snapshot/backendClock and other relevant bits, but keep peerId and lastIncorporatedPeerClock unchanged this._atom.reduceByPointer( (p) => p.sessionState, (oldSessionstate): SessionState => { return { backendClock: s.backendClock, lastSyncTime: s.lastSyncTime, peerId: this._peerId, lastIncorporatedPeerClock: oldSessionstate?.lastIncorporatedPeerClock ?? null, snapshot: s.snapshot, } }, ) const lastAcknowledgedPeerClock = s.lastIncorporatedPeerClock ?? -1 const index = this._atom .get() .closedSessions.findIndex((s) => s.peerId === this._peerId) if (index === -1) return const closedSession = this._atom.get().closedSessions[index] if (!closedSession) { return } const existingQueue = closedSession.optimisticUpdates if ( existingQueue.length > 0 && existingQueue[0].peerClock <= lastAcknowledgedPeerClock ) { const newQueue = existingQueue.filter( (update) => update.peerClock > lastAcknowledgedPeerClock, ) this._atom.setByPointer((p) => p.closedSessions[index], { optimisticUpdates: newQueue, lastIncorporatedPeerClock: s.lastIncorporatedPeerClock, peerId: s.peerId, }) } } } private async _pullUpdatesFromBackend(): Promise { const originalBackendState = this._atom.get().sessionState if (!originalBackendState) { throw new Error('backend state not initialized') } const s = await this._backend.getUpdatesSinceClock({ clock: originalBackendState.backendClock ?? null, peerId: this._peerId, }) this._processBackendUpdate(s) } private _subscribeToBackend(): () => void { void this._pullUpdatesFromBackend() const stop = this._backend.subscribe( { peerId: this._peerId, }, async (s) => { if (s.shouldCheckForUpdates) { void this._pullUpdatesFromBackend().catch((err) => { console.error(err) }) } this._atom.setByPointer((p) => p.allPeersPresenceState, s.presence) }, ) const unsub = () => { void stop.then((s) => stop) } return unsub } get state(): {op: OpSnapshot; cell: CellShape} { return finalState(this._prisms.withPeers.getValue()) as $IntentionalAny } get isReady(): boolean { return this._atom.get().initialized === true } get ready(): Promise { return this._initializedPromise } tx( editorFn?: (editors: EditorDefinitionToEditorInvocable) => void, draftFn?: (cellDraft: CellShape) => void, undoable: boolean = true, ): void { const [update, isEmpty, backwardOps] = this._createTransaction( this._prisms.optimisticState.getValue(), editorFn, draftFn, ) if (isEmpty) return this._pushOptimisticUpdate(update, undoable ? backwardOps : []) } tempTx( editorFn?: (editors: EditorDefinitionToEditorInvocable) => void, draftFn?: (cellDraft: CellShape) => void, existingTempTransaction?: TempTransactionApi, ): TempTransactionApi { if (existingTempTransaction) { existingTempTransaction.recapture(editorFn, draftFn) return existingTempTransaction } const [o, originalIsEmpty, originalBackwardOps] = this._createTransaction( this._prisms.optimisticState.getValue(), editorFn, draftFn, ) const originalTransaction: TempTransaction = { ...o, tempId: this._tempTransactionCounter++, backwardOps: originalBackwardOps, } this._setTempTransaction(originalTransaction.tempId, originalTransaction) let currentTransaction: TempTransaction = originalTransaction let currentIsEmpty: boolean = originalIsEmpty let transactionState: 'alive' | 'committed' | 'discarded' = 'alive' const commit = (undoable: boolean = true): void => { if (transactionState !== 'alive') { throw new Error('Transaction is already ' + transactionState) } transactionState = 'committed' this._setTempTransaction(originalTransaction.tempId, undefined) if (currentIsEmpty) return const finalUpdate = {...currentTransaction} this._pushOptimisticUpdate( finalUpdate, undoable ? currentTransaction.backwardOps : [], ) } const discard = (): void => { if (transactionState !== 'alive') { throw new Error('Transaction is already ' + transactionState) } transactionState = 'discarded' this._setTempTransaction(originalTransaction.tempId, undefined) } const recapture = ( editorFn?: (editors: EditorDefinitionToEditorInvocable) => void, draftFn?: (cellDraft: CellShape) => void, ): void => { if (transactionState !== 'alive') { throw new Error('Transaction is already ' + transactionState) } const [update, newIsEmpty, backwardOps] = this._createTransaction( this._prisms.optimisticState.getValue(), editorFn, draftFn, ) const newTransaction: TempTransaction = { ...update, tempId: originalTransaction.tempId, backwardOps, } currentTransaction = newTransaction currentIsEmpty = newIsEmpty this._setTempTransaction(originalTransaction.tempId, newTransaction) } const reset = (): void => { if (transactionState !== 'alive') { throw new Error('Transaction is already ' + transactionState) } this._setTempTransaction(originalTransaction.tempId, undefined) } return {commit, discard: discard, recapture, reset} } private _setTempTransaction( id: number, transaction: TempTransaction | undefined, ): void { const prev = this._atom.get().tempTransactions const existingIndex = prev.findIndex((t) => t.tempId === id) const next = [...prev] let changed = false if (existingIndex > -1) { next.splice(existingIndex, 1) changed = true } if (transaction) { if (existingIndex > -1) { next.splice(existingIndex, 0, transaction) changed = true } else { next.push(transaction) changed = true } } if (changed) { this._atom.setByPointer((p) => p.tempTransactions, next) } } private _createTransaction( fullSnapshot: FullSnapshot, editorFn?: (editors: EditorDefinitionToEditorInvocable) => void, draftFn?: (draft: CellShape) => void, warnIfNoInvokations: boolean = false, ): [ udpate: Omit, isEmpty: boolean, backwardOps: Ops, ] { const invokations = editorFn ? recordInvokations(this._schema.editors, editorFn) : [] if (invokations.length === 0) { if (warnIfNoInvokations && editorFn) console.info(`Transaction didn't invoke any editors. It's a no-op.`) } let backwardOps: Ops = [] let draftOps: any[] = [] if (typeof draftFn === 'function') { const [draft, fin] = makeDraft(fullSnapshot.cell) draftFn(draft) const [_, forwardOps, _backwardOps] = fin() if (forwardOps.length > 0) { draftOps = forwardOps backwardOps = _backwardOps } } const [producedSnapshot, generatorRecordings] = applyOptimisticUpdateToState( {invokations, generatorRecordings: {}, draftOps}, fullSnapshot, this._schema, false, ) const transaction: Omit = { invokations, generatorRecordings: generatorRecordings, draftOps: draftOps, peerId: this._peerId, } if (process.env.NODE_ENV !== 'production' && editorFn) { if ( !fastDeepEqual( invokations, recordInvokations(this._schema.editors, editorFn), ) ) { throw new Error( `Transaction function seems to invoke different editors each time it is called. This means it is not deterministic, and running it several times will create different states. To fix this, make sure the transaction calls exactly the same editors, in the same order, with the same arguments`, ) } const [secondSnapshot] = applyOptimisticUpdateToState( transaction, fullSnapshot, this._schema, true, ) if (!fastDeepEqual(secondSnapshot, producedSnapshot)) { // at least one editor is not deterministic // let's see if we can find which one it is, to help the user debug let invokationsSoFar: typeof invokations = [] for (const invokation of invokations) { // run each invokation one-by-one, and see which one produces a different snapshot invokationsSoFar = [...invokationsSoFar, invokation] // first call const [newSnapshot1] = applyOptimisticUpdateToState( { invokations: invokationsSoFar, generatorRecordings: transaction.generatorRecordings, draftOps: transaction.draftOps, }, fullSnapshot, this._schema, true, ) // second call const [newSnapshot2] = applyOptimisticUpdateToState( { invokations: invokationsSoFar, generatorRecordings: transaction.generatorRecordings, draftOps: transaction.draftOps, }, fullSnapshot, this._schema, true, ) if (!fastDeepEqual(newSnapshot1, newSnapshot2)) { // found the culprit throw new Error( `Transaction is not deterministic, because editor ${ invokation[0] }(${JSON.stringify( invokation[1], )}) is not deterministic. It produces different results when called twice. \n${diff( newSnapshot1, newSnapshot2, )}`, ) } } // couldn't find which editor is not deterministic. let's just throw a generic error const diffString = diff(producedSnapshot, secondSnapshot) throw new Error( `The second invocation of the transaction produced a different state than the first invocation. \n${diffString}`, ) } } return [ transaction, invokations.length === 0 && transaction.draftOps.length === 0, backwardOps, ] } async waitForStorageSync() { await waitForPrism(this._prisms.allSyncedToFrontStorage, (v) => v === true) } private _pushOptimisticUpdate( updateWithoutPeerClock: Omit, // if defined, then it'll constitute an undo-able operation backwardOps: Ops | undefined, ): void { const clockBefore = this._atom.get().peerClock const newClock = clockBefore + 1 const transaction: Transaction = { generatorRecordings: updateWithoutPeerClock.generatorRecordings, invokations: updateWithoutPeerClock.invokations, peerId: updateWithoutPeerClock.peerId, peerClock: newClock, draftOps: updateWithoutPeerClock.draftOps, } this._atom.reduce((state) => ({ ...state, peerClock: newClock, optimisticUpdatesQueue: [...state.optimisticUpdatesQueue, transaction], })) if (backwardOps?.length === 0) { // console.log('no backward ops', transaction.draftOps) } if (backwardOps && backwardOps.length > 0) this._addToUndoStack({backwardOps, forwardOps: transaction.draftOps}) } async waitForBackendSync(): Promise { await this.ready await waitForPrism( this._prisms.countOfUnpushedUpdatesToBackend, (v) => v === 0, ) } private _addToUndoStack(op: UndoStackItem) { this._atom.reduceByPointer( (p) => p.undoRedo, (o) => { let stack = // copy the stack [...o.stack] // and only keep the items that are before the cursor (so if the user has undone, and then does a new operation, we'll discard the redo stack) .slice(o.cursor) stack.unshift(op) if (stack.length > MAX_UNDO_STACK_SIZE) stack.length = MAX_UNDO_STACK_SIZE return { cursor: 0, stack, } }, ) } undo() { const undoRedo = this._atom.get().undoRedo if (undoRedo.cursor >= undoRedo.stack.length) return const item = undoRedo.stack[undoRedo.cursor] this._atom.reduceByPointer( (p) => p.undoRedo, (o) => { return { ...o, cursor: o.cursor + 1, } }, ) this._pushOptimisticUpdate( { draftOps: item.backwardOps, generatorRecordings: {}, invokations: [], peerId: this._peerId, }, undefined, ) } redo() { const undoRedo = this._atom.get().undoRedo if (undoRedo.cursor === 0) return const item = undoRedo.stack[undoRedo.cursor - 1] this._atom.reduceByPointer( (p) => p.undoRedo, (o) => { return { ...o, cursor: o.cursor - 1, } }, ) this._pushOptimisticUpdate( { draftOps: item.forwardOps, generatorRecordings: {}, invokations: [], peerId: this._peerId, }, undefined, ) } subscribe( fn: (newState: {op: OpSnapshot; cell: CellShape}) => void, ): () => void { const withPeers = this._prisms.withPeers let oldState = withPeers.getValue() return withPeers.onStale(() => { const newState = withPeers.getValue() if (newState !== oldState) { oldState = newState fn(finalState(newState) as $IntentionalAny) } }) } } type ClosedSession = { peerId: string optimisticUpdates: Transaction[] lastIncorporatedPeerClock: number | null } const finalState = memoizeFn((s: FullSnapshot): {op: S; cell: {}} => { return { op: s.op, cell: jsonFromCell(s.cell) as $IntentionalAny, } }) ================================================ FILE: packages/saaz/src/index.test.ts ================================================ import {SaazFront} from './front/SaazFront' import {FrontMemoryAdapter} from './front/FrontMemoryAdapter' import SaazBack from './back/SaazBack' import type {$IntentionalAny, Schema} from './types' import {BackMemoryAdapter} from './back/BackMemoryAdapter' jest.setTimeout(1000) describe(`saaz`, () => { test('everything', async () => { type OpShape = { $schemaVersion: number opCount: number } type Generators = { rand: () => number } let randNum = 10 const generators: Generators = { rand: () => { return randNum++ }, } const opEditors = { increaseBy(state: OpShape, generators: Generators, opts: {by: number}) { let count = state.opCount ?? 0 state.opCount = count + opts.by }, // this is a bad editor because it uses a random number generator, so it's not deterministic. // we expect saaz.tx() to throw an error if we try to use it. randomizeCountBadly(state: OpShape, generators: Generators, opts: {}) { state.opCount = Math.random() }, // this is a good editor because it uses a random number generator, but it's deterministic. randomizeCountWell(state: OpShape, generators: Generators, opts: {}) { state.opCount = generators.rand() }, } type CellShape = {cellCount?: number} const schema: Schema< OpShape, typeof opEditors, typeof generators, CellShape > = { opShape: null as $IntentionalAny as OpShape, // migrateOp(state: $IntentionalAny) {}, // migrateCell(s) {}, version: 1, editors: opEditors, generators: generators, cellShape: null as any as CellShape, } as const const mem = new FrontMemoryAdapter() const backend = new SaazBack({ storageAdapter: new BackMemoryAdapter(), dbName: 'test', schema, }) const saaz = new SaazFront({ schema, backend, peerId: 'peer1', storageAdapter: mem, dbName: 'test', }) await saaz.ready expect(saaz.state.op.opCount).toEqual(undefined) expect(saaz.state.cell.cellCount).toEqual(undefined) expect(() => saaz.tx((editors) => { editors.randomizeCountBadly({}) }), ).toThrow() expect(() => saaz.tx(undefined, (draft) => { draft.cellCount = 1 throw new Error('oops') }), ).toThrow() expect(saaz.state.op.opCount).toEqual(undefined) expect(saaz.state.cell.cellCount).toEqual(undefined) expect(() => saaz.tx((editors) => { editors.increaseBy({by: 1}) throw new Error('oops') }), ).toThrow() expect(saaz.state.op.opCount).toEqual(undefined) expect(() => saaz.tx((editors) => { editors.randomizeCountWell({}) }), ).not.toThrow() await new Promise((resolve) => setTimeout(resolve, 100)) expect(saaz.state.op.opCount).toEqual(10) saaz.tx(undefined, (draft) => { draft.cellCount = 1 }) expect(saaz.state.cell.cellCount).toEqual(1) saaz.tx((editors) => { editors.increaseBy({by: 3}) }) expect(saaz.state.op.opCount).toEqual(13) await saaz.waitForBackendSync() await saaz.waitForStorageSync() expect( (mem.export() as $IntentionalAny).sessions['peer1'].keyval.sessionState .value.op, ).toEqual({opCount: 13}) // const fauxBackennd: SaazBackInterface = { // async getUpdatesSinceClock() { // return {clock: null, hasUpdates: false} // }, // applyUpdates(opts) { // return Promise.resolve({ok: true, hasUpdates: false}) // }, // updatePresence(opts) { // return Promise.resolve({ok: true}) // }, // async subscribe() { // return () => {} // }, // } const saaz2 = new SaazFront({ schema, peerId: '2', storageAdapter: new FrontMemoryAdapter(), dbName: 'test', backend: backend, }) await saaz2.ready await saaz2.waitForBackendSync() expect(saaz2.state).toEqual(saaz.state) const saaz3 = new SaazFront({ schema, peerId: '3', storageAdapter: new FrontMemoryAdapter(), dbName: 'test', backend: backend, }) await saaz3.ready await saaz3.waitForBackendSync() expect(saaz3.state).toEqual(saaz.state) saaz.tx(undefined, (draft) => { draft.cellCount = 2 }) expect(saaz.state.cell.cellCount).toEqual(2) saaz.tx(undefined, (draft) => { draft.cellCount = 3 }) expect(saaz.state.cell.cellCount).toEqual(3) await saaz.waitForBackendSync() await saaz3.waitForBackendSync() expect(saaz3.state).toEqual(saaz.state) saaz.undo() expect(saaz.state.cell.cellCount).toEqual(2) await saaz.waitForBackendSync() await saaz3.waitForBackendSync() expect(saaz3.state).toEqual(saaz.state) saaz.undo() expect(saaz.state.cell.cellCount).toEqual(1) saaz.redo() expect(saaz.state.cell.cellCount).toEqual(2) saaz.redo() expect(saaz.state.cell.cellCount).toEqual(3) saaz.undo() saaz.undo() expect(saaz.state.cell.cellCount).toEqual(1) saaz.tx(undefined, (draft) => { draft.cellCount = 4 }) expect(saaz.state.cell.cellCount).toEqual(4) saaz.redo() expect(saaz.state.cell.cellCount).toEqual(4) saaz.undo() expect(saaz.state.cell.cellCount).toEqual(1) saaz.teardown() saaz2.teardown() saaz3.teardown() }) }) ================================================ FILE: packages/saaz/src/index.ts ================================================ export {SaazFront} from './front/SaazFront' export {default as SaazBack} from './back/SaazBack' export {FrontIDBAdapter} from './front/FrontIdbAdapter' export {FrontMemoryAdapter} from './front/FrontMemoryAdapter' export type { FrontStorageAdapter, BackStorageAdapter, SaazBackInterface, Schema, } from './types' export {BackMemoryAdapter} from './back/BackMemoryAdapter' export {current} from './rogue' ================================================ FILE: packages/saaz/src/rogue.test.ts ================================================ import {BackMemoryAdapter} from './back/BackMemoryAdapter' import SaazBack from './back/SaazBack' import {FrontMemoryAdapter} from './front/FrontMemoryAdapter' import {SaazFront} from './front/SaazFront' import type {Root} from './rogue' import {change, fromOps, jsonFromCell} from './rogue' import type { Schema} from './types' const ahistoricSnapshot: Root = { $type: ['map', 'base'], $branches: { base: { $mapProps: { foo: { $type: ['map', 'base'], $branches: { base: { $boxedValue: 'some value here, but this will be ignored, because this is an obj register.', $mapProps: { bar: { $type: ['boxed', 'base'], $branches: { base: { $boxedValue: 'some value here. this is an lww register, and it can contain any json value.', }, }, }, }, }, }, }, }, }, }, } describe(`Rogue`, () => { test('setting a non-existing prop', () => { const [rep, ops] = change({}, (draft) => { expect(draft.a).toBe(undefined) draft.a = 1 expect(draft.a).toBe(1) }) expect(jsonFromCell(rep)).toEqual({a: 1}) expect([rep, ops]).toMatchSnapshot() const [rep2] = fromOps({}, ops) expect(rep2).toEqual(rep) }) test('overriding an existing prop with the same value', () => { const [rep1] = change({}, (draft) => { draft.a = 1 }) const [rep2, ops] = change(rep1, (draft) => { expect(draft.a).toBe(1) draft.a = 1 expect(draft.a).toBe(1) }) expect(rep1).toBe(rep2) expect(ops).toEqual([]) }) test('overriding an existing prop', () => { const [rep1] = change({}, (draft) => { draft.a = 1 }) const [rep2, ops] = change(rep1, (draft) => { expect(draft.a).toBe(1) draft.a = 2 expect(draft.a).toBe(2) }) expect(jsonFromCell(rep2)).toEqual({a: 2}) expect(ops).toHaveLength(1) expect(ops).toMatchSnapshot() const [rep3] = fromOps(rep1, ops) expect(rep3).toEqual(rep2) }) test('setting a non-existing prop to an object', () => { const [rep, ops] = change({}, (draft) => { expect(draft.a).toBe(undefined) draft.a = {b: 1} expect(draft.a).toEqual(draft.a) expect(draft.a).toEqual({b: 1}) }) expect(jsonFromCell(rep)).toEqual({a: {b: 1}}) expect([rep, ops]).toMatchSnapshot() const [rep2] = fromOps({}, ops) expect(rep2).toEqual(rep) }) test('setting an existing prop to an object', () => { const [rep] = change({}, (draft) => { draft.a = {b: 1} }) expect(jsonFromCell(rep)).toEqual({a: {b: 1}}) const [rep2, ops2] = change(rep, (draft) => { expect(draft.a).toEqual({b: 1}) draft.a = {b: 2} expect(draft.a).toEqual({b: 2}) }) expect(jsonFromCell(rep2)).toEqual({a: {b: 2}}) const [rep3] = fromOps(rep, ops2) expect(rep3).toEqual(rep2) }) test('setting an existing prop from an object', () => { const [rep] = change({}, (draft) => { draft.a = {b: 1} }) expect(jsonFromCell(rep)).toEqual({a: {b: 1}}) const [rep2, ops2] = change(rep, (draft) => { expect(draft.a).toEqual({b: 1}) draft.a = {c: 1} expect(draft.a).toEqual({c: 1}) }) expect(jsonFromCell(rep2)).toEqual({a: {c: 1}}) const [rep3] = fromOps(rep, ops2) expect(rep3).toEqual(rep2) }) test(`merging defaults`, () => { const [, ops1_1] = change({}, (draft) => { draft.a = {aStep: 1, foo: 'setBy1', obj: {objA: 'true'}} }) const [, ops2_1] = change({}, (draft) => { draft.a = {bStep: 1, foo: 'setBy2', obj: {objB: 'true'}} }) const merge1 = fromOps({}, [...ops2_1, ...ops1_1])[0] const _11 = jsonFromCell(merge1) expect(_11).toMatchSnapshot() // console.log(_11) }) function scenario( name: string, steps: Record< string, (draft: any, lastSnapshot: any, lastOps: any[]) => void >, ) { describe(name, () => { type StepResult = { json: any ops: any[] backwardOps: any[] rep: any next?: StepResult prev?: StepResult } let last: StepResult = { json: {}, backwardOps: [], ops: [], rep: {}, } const byStep: Record = {} for (const [stepName, fn] of Object.entries(steps)) { test(stepName, () => { const prev: StepResult = {...last} const [rep, ops, backwardOps] = change(prev.rep, (draft) => { fn(draft, prev.json, prev.ops) }) const stepResult: StepResult = { rep, ops, backwardOps, json: jsonFromCell(rep), prev, } last.next = stepResult last = stepResult byStep[stepName] = stepResult }) } // i = 0 so that we skip the first step, which is the initial state for (let i = 1; i < Object.keys(steps).length; i++) { const prevStepName = Object.keys(steps)[i - 1] const stepName = Object.keys(steps)[i] test(`${prevStepName} => ${stepName}`, () => { const stepResult = byStep[stepName] const [rep] = fromOps(stepResult.prev!.rep, stepResult.ops) expect(rep).toEqual(stepResult.rep) }) test(`${prevStepName} <= ${stepName}`, () => { const stepResult = byStep[stepName] const [rep] = fromOps(stepResult.rep, stepResult.backwardOps) const s = jsonFromCell(rep) // note that as opposed to the previous test, we're not comparing cells, we're // comparing snapshots. This is because the cells are not guaranteed to be the // same when undoing a change, but the snapshots are. expect(s).toEqual(stepResult.prev!.json) }) } }) } scenario('scenario 1', { step1: (draft) => { expect(draft.a).toBe(undefined) draft.a = 1 expect(draft.a).toBe(1) }, step2: (_, snapshot, ops) => { expect(snapshot).toEqual({a: 1}) }, }) scenario('scenario 2', { step1: (draft) => { draft.a = {a1: {a11: 1}} expect(draft.a.a1).toEqual({a11: 1}) draft.a.a1.a11 = 2 expect(draft.a).toEqual({a1: {a11: 2}}) }, step2: (_, snapshot, ops) => { expect(snapshot).toEqual({a: {a1: {a11: 2}}}) }, }) scenario('scenario 3', { step1: (draft) => { draft.a = {a1: {a11: 1}} expect(draft.a.a1).toEqual({a11: 1}) draft.a = {b: 1} expect(draft.a).toEqual({b: 1}) }, step2: (draft, snapshot, ops) => { expect(draft.a).toEqual({b: 1}) draft.a = 1 }, step3: (draft, snapshot) => { expect(snapshot).toEqual({a: 1}) }, }) describe(`saaz integration`, () => { test(`test`, async () => { type State = { $schemaVersion: number count: number } const schema: Schema = { version: 1, // migrateOp(state: $IntentionalAny) {}, // migrateCell(s) {}, generators: {}, editors: { increaseBy(state: State, generators: {}, opts: {by: number}) { state.count += opts.by }, }, opShape: null as any as State, cellShape: null as any as {}, } const backend = new SaazBack({ schema, dbName: 'test', storageAdapter: new BackMemoryAdapter(), }) const saaz = new SaazFront({ schema, dbName: 'test', peerId: '1', storageAdapter: new FrontMemoryAdapter(), backend, }) saaz.tx((editors) => {}) saaz.teardown() }) }) }) ================================================ FILE: packages/saaz/src/rogue.ts ================================================ import deepEqual from '@theatre/utils/deepEqual' import type {$IntentionalAny} from './types' import * as immer from 'immer' import setDeep from 'lodash-es/set' import memoizeFn from '@theatre/utils/memoizeFn' type BranchName = 'base' | string type Branch = { $boxedValue?: any $mapProps?: { [key in string]?: Cell } } export type Cell = { $type: [type: 'map' | 'boxed' | 'deleted', branchName: BranchName] $branches?: { [asOf in BranchName]?: Branch } } type CellToJSON = T extends { $type: ['boxed', any] } ? CellBoxedToJSON : T extends {$type: ['map']} ? MapCellToJSON : never type CellBoxedToJSON = T['$branches'] extends { [key: string]: {$boxedValue: infer V} } ? V : never type MapCellToJSON = T['$branches'] extends { [key: string]: {$mapProps: infer V} } ? { [Key in keyof V]: V[Key] extends Cell ? CellToJSON : never } : never export type Root = Cell const NOT_DEFINED = {} type Transaction = {clock: number; ops: Ops} export type Ops = Op[] type Op = ChnangeTypeOp | SetBoxedValue type ChnangeTypeOp = { type: 'ChangeType' path: Array<[branchName: BranchName, mapProp: string]> value: Cell['$type'] } type SetBoxedValue = { type: 'SetBoxedValue' path: Array<[branchName: BranchName, mapProp: string]> branchName: BranchName value: any } type PathSegment = [branchName: BranchName, mapProp: string] type Path = PathSegment[] function isCell(v: unknown): v is Cell { if (typeof v !== 'object' || v === null) return false const $type = (v as any).$type if (!Array.isArray($type)) return false if ($type.length !== 2 && $type.length !== 1) return false if (typeof $type[0] !== 'string') return false const [type, branchName] = $type if (type === 'map' || type === 'boxed' || type === 'deleted') return true return false } export function makeDraft( base: any, ): [draft: any, finish: () => [cell: Cell, forwardOps: Ops, backwardOps: Ops]] { if (!isPlainObject(base)) { throw Error(`Base must be a plain object`) } base = isCell(base) ? base : ({ $type: ['map', 'base'], $branches: {base: {$mapProps: base}}, } as Cell) const immerDraft = immer.createDraft(base) const state: State = { imo: immerDraft, } const draft = new Proxy(state, traps) const finish = (): [cell: Cell, forwardOps: Ops, backwardOps: Ops] => { const cell = immer.finishDraft(immerDraft) const forwardOps = compare(base, cell) const backwardOps = compare(cell, base) return [cell, forwardOps, backwardOps] } return [draft, finish] } export function change( base: any, fn: (draft: any) => void, ): [cell: any, ops: Ops, backwardOps: Ops] { const [draft, finish] = makeDraft(base) fn(draft) return finish() } export function fromOps(base: any, ops: Ops): [cell: any] { if (!isPlainObject(base)) { throw Error(`Base must be a plain object`) } base = isCell(base) ? base : ({ $type: ['map', 'base'], $branches: {base: {$mapProps: base}}, } as Cell) const immerDraft = immer.createDraft(base) const state: State = { imo: immerDraft, } for (const op of ops) { const flatPath = op.path .map(([branchName, mapProp]) => [ '$branches', branchName, '$mapProps', mapProp, ]) .flat() if (op.type === 'ChangeType') { const [type, branchName] = op.value setDeep(immerDraft, [...flatPath, '$type'], [type, branchName]) } else if (op.type === 'SetBoxedValue') { setDeep( immerDraft, [...flatPath, '$branches', op.branchName, '$boxedValue'], op.value, ) } else { throw Error(`Unrecognized op type: ${(op as $IntentionalAny).type}`) } } return [immer.finishDraft(immerDraft)] } function compare(before: Cell, after: Cell): Ops { const ops: Ops = [] compareCell(before, after, [], ops) return ops } function compareCell( before: Cell | undefined, after: Cell, path: Path, ops: Ops, ) { if (before === after) return if (!deepEqual(before?.$type, after.$type)) { const beforeType = before ? [before.$type[0], before.$type[1] ?? 'base'] : null const afterType = [after.$type[0], after.$type[1] ?? 'base'] if (!deepEqual(beforeType, afterType)) { ops.push({ type: 'ChangeType', path, value: after.$type, }) } } const [type, branchName] = [after.$type[0], after.$type[1] ?? 'base'] const afterBranch = after.$branches?.[branchName] const beforeBranch = before?.$branches?.[branchName] if (afterBranch === beforeBranch) return if (!afterBranch) return if (type === 'deleted') return if (type === 'boxed') { if (afterBranch.$boxedValue !== beforeBranch?.$boxedValue) { ops.push({ type: 'SetBoxedValue', path, branchName, value: afterBranch.$boxedValue, }) } return } if (type === 'map') { const beforeMapProps = beforeBranch?.$mapProps ?? {} const afterMapProps = afterBranch.$mapProps ?? {} const afterKeys = Object.keys(afterMapProps) for (const prop of afterKeys) { compareCell( Object.hasOwn(beforeMapProps, prop) ? beforeMapProps[prop] : undefined, afterMapProps[prop]!, [...path, [branchName, prop]], ops, ) } const beforeKeys = Object.keys(beforeMapProps) for (const prop of beforeKeys) { if (!Object.hasOwn(afterMapProps, prop)) { ops.push({ type: 'ChangeType', path: [...path, [branchName, prop]], value: ['deleted', generateBranchName()], }) } } return } throw Error(`Unrecognized type: ${type}`) } interface State { imo: immer.Draft parent?: State } let _lastBranchName = 0 const generateBranchName = () => { _lastBranchName++ return _lastBranchName.toString() } const traps: ProxyHandler = { get(state: State, prop) { if (prop === DRAFT_STATE) return state if (typeof prop !== 'string') return undefined const imo = state.imo const type = imo.$type[0] const branchName = imo.$type[1] ?? 'base' if (type === 'deleted') throw new Error(`This value is marked as deleted and cannot be accessed`) if (type === 'boxed') throw new Error(`Implement getting inside a boxed value`) if (type === 'map') { const mapProps = imo.$branches?.[branchName]?.$mapProps if (!mapProps) return undefined if (Object.hasOwn(mapProps, prop)) { const value = mapProps[prop] if (!isCell(value)) { throw Error( `mapProps[${prop}] is not an ahistoric cell. this is a bug.`, ) } if (value.$type[0] === 'deleted') return undefined if (value.$type[0] === 'boxed') { const boxedValue = value.$branches?.[value.$type[1] ?? 'base']?.$boxedValue if (isPlainObject(boxedValue)) { throw Error(`Implement getting a mapProp that is a boxed object`) } else { return boxedValue } } else if (value.$type[0] === 'map') { const subState: State = { imo: value, parent: state, } return new Proxy(subState, traps) } } else { return undefined } } throw new Error(`Unrecognized type: ${type}`) }, set(state: State, prop, _value: unknown): boolean { if (prop === DRAFT_STATE) throw Error(`Unallowed`) if (typeof prop !== 'string') throw Error(`Non-string props are not allowed`) const value = valueType(_value) const imo = state.imo const cellType = imo.$type[0] const branchName = imo.$type[1] ?? 'base' // setting self.a=value, when self is deleted if (cellType === 'deleted') throw new Error(`This value is marked as deleted and cannot be changed`) // setting self.a=value, when self is a boxed value if (cellType === 'boxed') throw new Error(`Implement setting inside a boxed value`) // setting self.a=value when self is a map if (cellType === 'map') { let branches = imo.$branches if (!branches) { branches = {} imo.$branches = branches } let branch = branches[branchName] if (!branch) { branch = {} branches[branchName] = branch } let mapProps = branch.$mapProps if (!mapProps) { mapProps = {} branch.$mapProps = mapProps } // setting self.a=value when self.a is defined if (Object.hasOwn(mapProps, prop)) { if (!isCell(mapProps[prop])) throw Error( `mapProps[${prop}] is not an ahistoric cell. this is a bug.`, ) const currentPropCell = mapProps[prop]! // setting self.a={} if (value.type === 'map') { let currentBranch!: Branch // setting self.a={} when self.a is not a map if (currentPropCell.$type[0] !== 'map') { // we're switching from a non-map to a map, which means if a map was previously set, it was // already deleted/overridden to be a boxed value, and the current user hasn't _seen_ the previous // map yet. So we should generate a new branchName for the new map. const newBranchName = generateBranchName() currentPropCell.$type = ['map', newBranchName] currentPropCell.$branches ??= {} const newBranch: Branch = {$mapProps: {}} currentPropCell.$branches[newBranchName] = newBranch currentBranch = newBranch } else { currentPropCell.$branches ??= {} currentPropCell.$branches[currentPropCell.$type[1] ?? 'base'] ??= {} currentBranch = currentPropCell.$branches[currentPropCell.$type[1] ?? 'base']! } const subState: State = { imo: currentPropCell, parent: state, } const proxy = new Proxy(subState, traps) const existingProps = Object.keys(proxy) // let's delete existing props that are not in the new value for (const key of existingProps) { if (!Object.hasOwn(value.value, key)) { delete (proxy as $IntentionalAny)[key] } } for (const key of Object.keys(value.value)) { ;(proxy as $IntentionalAny)[key] = value.value[key] } return true } else if (value.type === 'boxed') { if (currentPropCell.$type[0] === 'boxed') { currentPropCell.$branches ??= {} const branches = currentPropCell.$branches! const branchName = currentPropCell.$type[1] ?? 'base' branches[branchName] ??= {} const branch = branches[branchName]! branch.$boxedValue = value.value return true } else { const branchName = generateBranchName() currentPropCell.$type = ['boxed', branchName] currentPropCell.$branches ??= {} const branches = currentPropCell.$branches! branches[branchName] ??= {} const branch = branches[branchName]! branch.$boxedValue = value.value return true } } throw new Error(`Unrecognized type: ${currentPropCell.$type[0]}`) } else { if (value.type === 'boxed') { mapProps[prop] = { $type: ['boxed', 'base'], $branches: { base: { $boxedValue: value.value, }, }, } return true } else if (value.type === 'map') { mapProps[prop] = { $type: ['map', 'base'], } const subState: State = { imo: mapProps[prop]!, parent: state, } const proxy = new Proxy(subState, traps) for (const [k, v] of Object.entries(value.value)) { ;(proxy as $IntentionalAny)[k] = v } return true } throw Error(`Unrecognized type: ${(value as $IntentionalAny).type}`) } } throw new Error(`Unrecognized type: ${cellType}`) }, has(state: State, prop) { throw Error(`Implement has()`) }, ownKeys(state: State) { const type = state.imo.$type[0] if (type === 'boxed') { const value = state.imo.$branches?.[state.imo.$type[1] ?? 'base']?.$boxedValue if (isPlainObject(value)) { return Reflect.ownKeys(value) } else { return [] } } else if (type === 'deleted') { return [] } else if (type === 'map') { const props = state.imo.$branches?.[state.imo.$type[1] ?? 'base']?.$mapProps ?? {} return Reflect.ownKeys(props).filter( (key) => props[key as $IntentionalAny]!.$type[0] !== 'deleted', ) } else { throw Error(`Unrecognized type: ${type}`) } }, deleteProperty(state: State, prop) { if (prop === DRAFT_STATE) throw Error(`Unallowed`) if (typeof prop !== 'string') throw Error(`Non-string props are not allowed`) const imo = state.imo const type = imo.$type[0] const branchName = imo.$type[1] ?? 'base' if (type === 'deleted') throw new Error(`This value is marked as deleted and cannot be changed`) if (type === 'boxed') throw new Error(`Implement deleting inside a boxed value`) if (type === 'map') { const mapProps = imo.$branches?.[branchName]?.$mapProps if (!mapProps) return false if (!Object.hasOwn(mapProps, prop)) return false if (!isCell(mapProps[prop])) return false const currentPropCell = mapProps[prop]! if (currentPropCell.$type[0] === 'deleted') return false currentPropCell.$type = ['deleted', generateBranchName()] return true } throw new Error(`Unrecognized type: ${type}`) }, getOwnPropertyDescriptor(state: State, prop) { const type = state.imo.$type[0] if (type === 'boxed') { const $boxedValue = state.imo.$branches?.[state.imo.$type[1] ?? 'base']?.$boxedValue if (isPlainObject($boxedValue)) { return Reflect.getOwnPropertyDescriptor($boxedValue, prop) } else { return undefined } } else if (type === 'deleted') { return undefined } else if (type === 'map') { const props = state.imo.$branches?.[state.imo.$type[1] ?? 'base']?.$mapProps ?? {} if (Object.hasOwn(props, prop)) { return { writable: true, configurable: true, enumerable: true, value: (traps as $IntentionalAny).get(state, prop, {}), } } else { return undefined } } else { throw Error(`Unrecognized type: ${type}`) } }, defineProperty(state: State, prop, descriptor) { throw Error(`Implement defineProperty()`) }, getPrototypeOf(state: State) { const type = state.imo.$type[0] if (type === 'boxed') { return Object.getPrototypeOf( state.imo.$branches?.[state.imo.$type[1] ?? 'base']?.$boxedValue, ) } else if (type === 'deleted') { return undefined } else if (type === 'map') { return Object.getPrototypeOf({}) } else { throw Error(`Unrecognized type: ${type}`) } }, setPrototypeOf(state: State, prototype) { throw Error(`Implement setPrototypeOf()`) }, } export const current = (draft: T): T => { if (typeof draft !== 'object' || draft === null) { return draft } const state = (draft as $IntentionalAny)[DRAFT_STATE] as State if (!state) return draft const currentImo = immer.current(state.imo) return jsonFromCell(currentImo) as T } function valueType( v: V, ): | {type: 'boxed'; value: V} | {type: 'map'; value: {[key: string | number | symbol]: unknown}} { if (typeof v === 'object' && v) { if (Array.isArray(v)) { return {type: 'boxed', value: v} } return {type: 'map', value: v as $IntentionalAny} } if ( typeof v === 'string' || typeof v !== 'number' || typeof v !== 'boolean' || typeof v === 'undefined' || v === null ) { return {type: 'boxed', value: v} } throw Error(`Unrecognized value type: ${typeof v}`) } const DRAFT_STATE: unique symbol = Symbol.for('draft-state') const objectCtorString = Object.prototype.constructor.toString() export function isPlainObject(value: any): boolean { if (!value || typeof value !== 'object') return false const proto = Object.getPrototypeOf(value) if (proto === null) { return true } const Ctor = Object.hasOwnProperty.call(proto, 'constructor') && proto.constructor if (Ctor === Object) return true return ( typeof Ctor == 'function' && Function.toString.call(Ctor) === objectCtorString ) } const BOXED: unique symbol = Symbol.for('boxed') export function boxed(value: V): {[BOXED]: true; value: V} { return {[BOXED]: true, value} } function isBoxed(value: unknown): value is {[BOXED]: true; value: unknown} { return typeof value === 'object' && value && (value as $IntentionalAny)[BOXED] === true ? true : false } const RESET: unique symbol = Symbol.for('reset') export function reset(value: V): {[RESET]: true; value: V} { return {[RESET]: true, value} } function isReset(value: unknown): value is {[RESET]: true; value: unknown} { return typeof value === 'object' && value && (value as $IntentionalAny)[RESET] === true ? true : false } function is(x: any, y: any): boolean { // Copied from https://github.com/immerjs/immer/blob/f6736a4beef727c6e5b41c312ce1b202ad3afb23/src/utils/common.ts#L115 // Originally from: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js if (x === y) { return x !== 0 || 1 / x === 1 / y } else { return x !== x && y !== y } } export function jsonFromCell( v: V, ): V extends Cell ? CellToJSON : typeof NOT_DEFINED { if (typeof v !== 'object' || v === null) { return NOT_DEFINED as $IntentionalAny } return _jsonFromCell(v as $IntentionalAny) as $IntentionalAny } const _jsonFromCell = memoizeFn( (v: V): CellToJSON | typeof NOT_DEFINED => { const type = v.$type?.[0] const branchName = v.$branches?.[v.$type?.[1] ?? 'base'] if (typeof type !== 'string') { return NOT_DEFINED } if (type === 'deleted') { return NOT_DEFINED } if (type === 'boxed') { return branchName?.$boxedValue } if (v.$type[0] === 'map') { const props: {[key: string]: unknown} = {} for (const [k, _value] of Object.entries(branchName?.$mapProps || {})) { const value = jsonFromCell(_value as $IntentionalAny) if (value !== NOT_DEFINED) { props[k] = value } } return props } return NOT_DEFINED }, ) ================================================ FILE: packages/saaz/src/shared/GeneratorSpy.ts ================================================ import type {$IntentionalAny, GeneratorRecordings} from '../types' import {cloneDeep} from 'lodash-es' import type {SerializableValue} from '../types' import type {ValidGenerators} from '../types' import {stableValueHash} from '@theatre/utils/stableJsonStringify' export function createGeneratorsSpy( generators: ValidGenerators, prevREcordings: GeneratorRecordings = {}, playbackOnly: boolean = false, ): [spy: Generators, recordings: GeneratorRecordings] { const recordings: GeneratorRecordings = cloneDeep(prevREcordings) const calls: Record = {} const spy: Generators = Object.fromEntries( Object.entries(generators).map(([fnName, fn]) => { if (typeof fn !== 'function') { throw new Error(`Generator method "${fnName}" is not a function`) } if (typeof fnName !== 'string') { throw new Error('key is not a string') } const spyFn = (...args: SerializableValue[]) => { const key = stableValueHash([fnName, args]) if (!calls[key]) { calls[key] = 0 } else { calls[key]!++ } const callIndex = calls[key]! if ( playbackOnly && (!Array.isArray(recordings[key]) || recordings[key]!.length < callIndex) ) { throw new Error( `Generator method "${fnName}" was called with arguments that were not recorded: ${JSON.stringify( args, )}`, ) } if (!recordings[key]) { recordings[key] = [] } if (recordings[key]!.length > callIndex) { return recordings[key]![callIndex]! } else { const ret = fn(args[0]) recordings[key]![callIndex] = ret return ret } } return [fnName, spyFn] as const }), ) as $IntentionalAny return [spy, recordings] } ================================================ FILE: packages/saaz/src/shared/transactions.ts ================================================ import * as GeneratorSpy from './GeneratorSpy' import type {Draft} from 'immer' import {createDraft, finishDraft} from 'immer' import {get} from 'lodash-es' import type { $FixMe, EditorDefinitionToEditorInvocable, Invokations, Transaction, EditorDefinitionFn, $IntentionalAny, EditorDefinitions, Schema, ValidOpSnapshot as ValidOpSnapshot, ValidGenerators, GeneratorRecordings, FullSnapshot, } from '../types' import {fromOps} from '../rogue' export function applyOptimisticUpdateToState< OpSnapshot extends ValidOpSnapshot, >( { invokations, generatorRecordings, draftOps, }: Pick, before: FullSnapshot, schema: Schema, playbackOnly: boolean = false, testDeterminism: boolean = true, ): [after: FullSnapshot, generatorRecordings: GeneratorRecordings] { const draft = createDraft(before.op) const [generatorSpy, newRecordings] = GeneratorSpy.createGeneratorsSpy( schema.generators, generatorRecordings, playbackOnly, ) runInvokations(schema, draft, invokations, generatorSpy) const opSnapshotAfter = finishDraft(draft) as $FixMe as OpSnapshot const [cellAfter] = fromOps(before.cell, draftOps) return [{op: opSnapshotAfter, cell: cellAfter}, newRecordings] } export function recordInvokations( editors: Editors, fn: (editors: EditorDefinitionToEditorInvocable) => void, ): Invokations { const {invokableEditors, release, invokations} = invokables.getInvokables(editors) let released = false try { fn(invokableEditors) release() released = true } catch (error) { throw error } finally { if (!released) { release() } } return invokations } function runInvokations( schema: Schema, prevState: Draft, invokations: Invokations, generatorSpy: ValidGenerators, ): void { for (const [fnPath, opts] of invokations) { const fn = get(schema.editors, fnPath.split('.')) as EditorDefinitionFn if (typeof fn !== 'function') { throw new Error( `editor ${fnPath} not found. Transaction may be corrupt, or the editor names may have changed`, ) } fn(prevState, generatorSpy, opts) } } namespace invokables { class Pool { private _items: PoolItem[] = [] constructor(readonly _factory: () => PoolItem) {} /** * Get an item from the pool. If the pool is empty, a new item is created. Also starts a timer to ensure that the item is released back into the pool. * * @returns A tuple of the item and a function to release the item back into the pool. * */ get(releaseTimeoutms: number = 0): [PoolItem, () => void] { const item = this._items.length === 0 ? this._factory() : this._items.pop()! let released = false // if the invokables are not released within 0ms, it's probably a bug + memory leak. const releaseTimeout = setTimeout(() => { if (!released) { throw new Error('Release timeout exceeded.') } }, releaseTimeoutms) const release = () => { released = true clearTimeout(releaseTimeout) this._items.push(item) } return [item, release] } } type InvokationContext = { snapshot: Draft<{}> invokations: Invokations } type PoolItem = { context: InvokationContext invokableEditors: EditorDefinitionToEditorInvocable<$IntentionalAny> } const pools = new WeakMap<{}, Pool>() function getPool(editorDefinition: {}): Pool { if (!pools.has(editorDefinition)) { function createPoolItem( editors: Editors, ): PoolItem { const context = { snapshot: {}, invokations: [], } const invokableEditors = createInvokables(editors, context) return {context, invokableEditors} } const pool = new Pool(() => createPoolItem(editorDefinition)) pools.set(editorDefinition, pool) return pool } else { return pools.get(editorDefinition)! } } function createInvokables( editors: Editors, context: InvokationContext, pathSoFar: string[] = [], ): EditorDefinitionToEditorInvocable { const cache: $IntentionalAny = {} const proxy = new Proxy(new Function(), { get(_, prop: string) { if (prop in cache) { return cache[prop] } else if (!(prop in editors)) { const path = [...pathSoFar, prop] throw new Error(`editor "${path.join('.')}" not found`) } else { const path = [...pathSoFar, prop] const sub = createInvokables( (editors as $IntentionalAny)[prop], context, path, ) cache[prop] = sub return sub } }, apply(_, __, args: [$IntentionalAny]) { const opts = args[0] if (typeof editors !== 'function') { throw new Error(`editor "${pathSoFar.join('.')}" is not a function`) } else { context.invokations.push([pathSoFar.join('.'), opts]) } }, }) return proxy as $IntentionalAny } export function getInvokables( editorDefinitions: Defs, ): { invokableEditors: EditorDefinitionToEditorInvocable release: () => void invokations: Invokations } { const pool = getPool(editorDefinitions) const [poolItem, release] = pool.get() const {context, invokableEditors} = poolItem context.invokations = [] return { invokableEditors: invokableEditors as $IntentionalAny, release, invokations: context.invokations, } } } ================================================ FILE: packages/saaz/src/shared/utils.ts ================================================ import type {$IntentionalAny, FullSnapshot, Schema} from '../types' const empty = {op: {}, cell: {}} export function ensureStateIsUptodate( original: FullSnapshot | null, schema: Schema, ): FullSnapshot { if (original === null) { return empty as $IntentionalAny } return original as $IntentionalAny // if ( // !original || // typeof original.op.$schemaVersion !== 'number' || // original.op.$schemaVersion < schema.version // ) { // return { // op: produce((original?.op ?? {}) as {}, (originalDraft) => { // schema.migrateOp(originalDraft) // }) as S, // cell: original?.cell ?? {}, // } // } else { // return original // } } ================================================ FILE: packages/saaz/src/types.ts ================================================ import type {Cell, Ops} from './rogue' export type $IntentionalAny = any export type $FixMe = any // Primitive values that are serializable to JSON. type SerializablePrimitive = null | string | boolean | number // Primitive and complex values that are serializable to JSON. export type SerializableValue = | SerializablePrimitive | ReadonlyArray | SerialzableMap // A map of serializable values. export type SerialzableMap = { readonly [key: string]: SerializableValue | undefined } // like: (state, ctx, opts) => {} export type EditorDefinitionFn = ( state: $IntentionalAny, generators: $IntentionalAny, opts: $IntentionalAny, ) => $IntentionalAny /** * An editor definition is either a function or an object of editor definitions. * ```ts * { * foo: (state, ctx, opts) => {}, * nested: { * a: { * b: {c: (state, ctx, opts) => {}}, * }, * }, * } * ``` */ export type EditorDefinitions = | EditorDefinitionFn | {[key: string]: EditorDefinitions} /** * Takes an editor definition and returns a function that can be used to invoke it. * ```ts * // input * (state, ctx, opts) => {} * // output * (opts) => {} * * // OR: * // input * {foo: (state, ctx, opts) => {}} * // output * {foo: (opts) => {}} * ``` */ export type EditorDefinitionToEditorInvocable< Editors extends EditorDefinitions, > = Editors extends EditorDefinitionFn ? (opts: Parameters[2]) => void : Editors extends {[key: string]: EditorDefinitions} ? { [K in keyof Editors]: EditorDefinitionToEditorInvocable } : never export type ValidGenerators = { [key: string]: | (() => SerializableValue) | ((opts: SerializableValue) => SerializableValue) } export type Invokations = Array<[fn: string, opts: SerialzableMap]> export type Transaction = { invokations: Invokations generatorRecordings: GeneratorRecordings peerId: string peerClock: number draftOps: Ops } export type GeneratorRecordings = { [key in string]?: SerializableValue[] } export type OnDiskSnapshot = { // the url of the backend that this snapshot was taken from origin: string // the name of the database that this snapshot was taken from dbName: string // the clock of the server when this snapshot was taken. A positive integer. clock: number snapshot: FullSnapshot } export type SessionState = { /** * Unix timestamp of the last time the client synced with backend. Timestamp is produced on * the client, so it may be inaccurate. Null means never synced. */ lastSyncTime: number | null /** * The clock of the backend. null means unknown. */ backendClock: number | null /** * The clock of the last optimistic update the backend has applied from this peer. * The state (below) is calculated after this optimistic update has run. */ lastIncorporatedPeerClock: number | null /** * The state of the backend. */ snapshot: FullSnapshot | null peerId: string } export type BackStateUpdateDescriptor = { clock: number snapshot: | {type: 'Snapshot'; value: FullSnapshot<$IntentionalAny>} | {type: 'Diff'; diff: 'todo'} lastIncorporatedPeerClock: number | null peerId: string } export type BackApplyUpdateOps = { peerId: string backendClock: number | null updates: Transaction[] } export type BackGetUpdateSinceClockResult = | ({ hasUpdates: false } & Omit) | ({ hasUpdates: true } & BackStateUpdateDescriptor) export type PeerPresenceState = { tempTransactions: Array> } export type AllPeersPresenceState = {[peerId in string]?: PeerPresenceState} export type PeerSubscribeCallback = (opts: { presence: AllPeersPresenceState shouldCheckForUpdates: boolean }) => void export interface SaazBackInterface { getUpdatesSinceClock(opts: { clock: number | null peerId: string }): Promise getLastIncorporatedPeerClock(opts: { peerId: string }): Promise<{lastIncorporatedPeerClock: null | number; peerId: string}> closePeer(opts: {peerId: string}): Promise<{ok: boolean}> applyUpdates( opts: BackApplyUpdateOps, ): Promise< ({ok: true} & BackGetUpdateSinceClockResult) | {ok: false; error: unknown} > updatePresence(opts: { peerId: string presence: PeerPresenceState }): Promise<{ok: true} | {ok: false; error: unknown}> subscribe( opts: { peerId: string }, onUpdate: PeerSubscribeCallback, ): Promise<() => void> } export type FrontStorageAdapterTransaction = { /** * Gets a singular value. */ get(key: string, session: string): Promise /** * Like `get()`, but returns the value for each session */ getAll(key: string): Promise> /** * Sets a singular value. */ set(key: string, value: T, session: string): Promise deleteSession(session: string): Promise /** * Pushes one or more rows to a list. */ pushToList< T extends { id: string }, >( key: string, rows: T[], session: string, ): Promise /** * Reads all the rows from a list. */ getList< T extends { id: string }, >( key: string, session: string, ): Promise /** * Removes one or more rows from a list. */ pluckFromList< T extends { id: string }, >( key: string, ids: Array, session: string, ): Promise> } export interface FrontStorageAdapter { /** * Transaction. * Example: * ```ts * const newCount = await s.transaction(async ({get, set}) => { * const count = await get('count') * await set('count', count + 1) * return count + 1 * }) * ``` */ transaction( fn: (opts: FrontStorageAdapterTransaction) => Promise, ): Promise /** * Gets a singular value. */ get(key: string, session: string): Promise /** * Sets a singular value. */ getList(key: string, session: string): Promise } export interface BackStorageAdapter {} export type Schema< OpSnapshot extends {$schemaVersion: number}, Editors extends {} = {}, Generators extends ValidGenerators = {}, CellShape extends {} = {}, > = { editors: Editors generators: Generators opShape: OpSnapshot cellShape: CellShape version: number } export type ValidOpSnapshot = { $schemaVersion: number } export type TempTransaction = Omit & { tempId: number backwardOps: Ops } export type TempTransactionApi = { commit: () => void discard: () => void recapture: ( editorFn?: (editors: EditorDefinitionToEditorInvocable) => void, draftFn?: (draft: CellShape) => void, ) => void reset: () => void } export type FullSnapshot = {op: OpSnapshot; cell: Cell | {}} ================================================ FILE: packages/saaz/tsconfig.json ================================================ { "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "dist", "lib": ["ESNext", "DOM"], "rootDir": "src", "types": ["jest", "node"], "target": "es6", "composite": true }, "references": [{"path": "../dataverse"}, {"path": "../utils"}], "include": ["./src/**/*"] } ================================================ FILE: packages/saaz/typedoc.json ================================================ { "visibilityFilters": { "protected": false, "private": false, "inherited": true, "external": false, "@alpha": false, "@beta": false }, "excludeTags": [ "@override", "@virtual", "@privateRemarks", "@satisfies", "@overload", "@remarks" ], "categorizeByGroup": false, "excludeInternal": true, "excludeProtected": true, "excludePrivate": true, "sourceLinkTemplate": "https://github.com/theatre-js/theatre/blob/main/{path}#L{line}" } ================================================ FILE: packages/studio/.eslintrc.js ================================================ const path = require('path') module.exports = { rules: { 'no-relative-imports': [ 'warn', { aliases: [ {name: '@theatre/core', path: path.resolve(__dirname, '../core/src')}, { name: '@theatre/studio', path: path.resolve(__dirname, './src'), }, ], }, ], }, } ================================================ FILE: packages/studio/.gitignore ================================================ development.env.json production.env.json ================================================ FILE: packages/studio/.npmignore ================================================ /node_modules /xeno /.vscode /src /devEnv *.log /.* /*.env.json /tsconfig.json /.temp ================================================ FILE: packages/studio/LICENSE ================================================ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . ================================================ FILE: packages/studio/README.md ================================================ # Theatre.js - Studio Theatre.js is an animation library for high-fidelity motion graphics. It is designed to help you express detailed animation, enabling you to create intricate movement, and convey nuance. Theatre.js can be used both programmatically _and_ visually. You can use Theatre.js to: * Animate 3D objects made with THREE.js or other 3D libraries ![s](https://raw.githubusercontent.com/AriaMinaei/theatre-docs/main/docs/.vuepress/public/preview-3d-short.gif) Art by [drei.lu](https://sketchfab.com/models/91964c1ce1a34c3985b6257441efa500) * Animate HTML/SVG via React or other libraries ![s](https://raw.githubusercontent.com/AriaMinaei/theatre-docs/main/docs/.vuepress/public/preview-dom.gif) * Design micro-interactions ![s](https://raw.githubusercontent.com/AriaMinaei/theatre-docs/main/docs/.vuepress/public/preview-micro-interaction.gif) * Choreograph generative interactive art ![s](https://raw.githubusercontent.com/AriaMinaei/theatre-docs/main/docs/.vuepress/public/preview-generative.gif) * Or animate any other JS variable ![s](https://raw.githubusercontent.com/AriaMinaei/theatre-docs/main/docs/.vuepress/public/preview-console.gif) ## Documentation and Tutorials You can find the documentation and video tutorials [here](https://www.theatrejs.com/docs/latest). ## Community Join us on [Discord](https://discord.gg/bm9f8F9Y9N), follow the updates on [twitter](https://twitter.com/AriaMinaei) or write us an [email](mailto:hello@theatrejs.com). ## `@theatre/studio` Theatre.js comes in two packages: `@theatre/core` (the library) and `@theatre/studio` (the editor). This package is the editor, which is only used during design/development. ## License Your use of Theatre.js is governed under the Apache License Version 2.0: * Theatre's core (`@theatre/core`) is released under the Apache License. * The studio (`@theatre/studio`) is released under the AGPL 3.0 License. This is the package that you use to edit your animations, setup your scenes, etc. You only use the studio during design/development. Your project's final bundle only includes `@theatre/core`, so only the Apache License applies. ================================================ FILE: packages/studio/devEnv/cli.ts ================================================ import sade from 'sade' import {path} from '@cspotcode/zx' import * as esbuild from 'esbuild' // eslint-disable-next-line no-relative-imports import {definedGlobals} from '../../core/devEnv/definedGlobals' const prog = sade('cli') prog .command('build js', 'Generate the .js bundle') .option('--watch', 'Watch') .action(async (opts) => { await bundle(opts.watch) }) // prog.command('build ts', 'Generate the .d.ts bundle').action(async () => { // await bundleTypes() // }) prog.command('build', 'Generate the .js and .d.ts bundles').action(async () => { await Promise.all([bundle(false)]) }) prog.parse(process.argv) // async function bundleTypes() { // await $`tsc --build` // await $`rollup -c devEnv/declarations-bundler/rollup.config.js --bundleConfigAsCjs` // } async function bundle(watch: boolean) { const pathToPackage = path.join(__dirname, '..') const esbuildConfig: Parameters[0] = { entryPoints: [path.join(pathToPackage, 'src/index.ts')], target: ['es6'], loader: {'.png': 'dataurl', '.svg': 'dataurl'}, bundle: true, sourcemap: true, supported: { // 'unicode-escapes': false, 'template-literal': false, }, define: { ...definedGlobals, __IS_VISUAL_REGRESSION_TESTING: 'false', 'process.env.NODE_ENV': '"production"', }, external: ['@theatre/dataverse', '@theatre/core'], minify: true, } const ctx = await esbuild.context({ ...esbuildConfig, outfile: path.join(pathToPackage, 'dist/index.js'), format: 'cjs', }) if (watch) { await ctx.watch() } else { await ctx.rebuild() await ctx.dispose() } } ================================================ FILE: packages/studio/globals.d.ts ================================================ // the global env of @theatre/studio. Note that tsconfig also uses ../core/globals.d.ts ================================================ FILE: packages/studio/package.json ================================================ { "name": "@theatre/studio", "version": "0.7.0", "license": "AGPL-3.0-only", "description": "Motion design editor for the web", "repository": { "type": "git", "url": "https://github.com/AriaMinaei/theatre", "directory": "theatre/studio" }, "author": { "name": "TheaterJS Oy", "email": "hello@theatrejs.com", "url": "https://www.theatrejs.com" }, "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist/*" ], "scripts": { "prepublish": "node ../../devEnv/ensurePublishing.js", "typecheck": "tsc --build", "cli": "tsx devEnv/cli.ts", "build": "yarn cli build" }, "sideEffects": true, "peerDependencies": { "@theatre/core": "*" }, "dependencies": { "@theatre/dataverse": "workspace:*" }, "devDependencies": { "@react-spring/web": "^9.7.3", "@styled/typescript-styled-plugin": "^1.0.1", "@theatre/app": "workspace:*", "@theatre/react": "workspace:*", "@theatre/saaz": "workspace:*", "@theatre/sync-server": "workspace:*", "@theatre/utils": "workspace:*", "@trpc/client": "^10.43.0", "@types/jest": "^26.0.24", "@types/marked": "^4.0.7", "@types/node": "^20.10.5", "@types/react": "18.2.18", "@types/react-dom": "18.2.7", "@types/react-icons": "3.0.0", "@types/shallowequal": "^1.1.1", "@types/uuid": "^8.3.0", "blob-compare": "1.1.0", "esbuild": "^0.18.17", "fuzzy": "^0.1.3", "idb-keyval": "^6.2.0", "identity-obj-proxy": "^3.0.0", "immer": "9.0.6", "jose": "4.14.4", "jszip": "3.10.1", "lodash-es": "4.17.21", "marked": "^4.1.1", "nanoid": "3.3.1", "oauth4webapi": "2.4.0", "polished": "4.1.3", "react": "18.2.0", "react-colorful": "^5.5.1", "react-dom": "18.2.0", "react-error-boundary": "3.1.3", "react-hot-toast": "2.4.0", "react-icons": "4.12.0", "react-merge-refs": "2.0.2", "react-shadow": "20.4.0", "react-use": "17.2.4", "reakit": "1.3.8", "sade": "^1.8.1", "shallowequal": "1.1.0", "styled-components": "^5.3.11", "superjson": "1.13.1", "timing-function": "^0.2.3", "tsx": "4.7.0", "typescript": "5.1.6", "typescript-styled-plugin": "^0.18.3" }, "//": "Add packages here to make them externals of studio. Add them to theatre/package.json if you want to bundle them with studio." } ================================================ FILE: packages/studio/src/.eslintrc.js ================================================ module.exports = { rules: { 'no-restricted-syntax': [ 'error', { selector: `ImportDeclaration[importKind!='type'][source.value=/@theatre\\u002Fcore\\u002F/]`, message: '@theatre/studio may not import @theatre/core/* modules except via type imports.', }, ], }, } ================================================ FILE: packages/studio/src/Auth.ts ================================================ import type {Prism} from '@theatre/dataverse' import {Atom, prism, val} from '@theatre/dataverse' import {TRPCClientError} from '@trpc/client' import delay from '@theatre/utils/delay' import type {$FixMe, $IntentionalAny} from '@theatre/core/types/public' import {defer} from '@theatre/utils/defer' import { generateRandomCodeVerifier, calculatePKCECodeChallenge, } from 'oauth4webapi' import type {Studio} from './Studio' import getStudio from './getStudio' import {decodeJwt} from 'jose' import type {studioAccessScopes, studioAuthTokens} from '@theatre/app/types' import type {AtomPersistor} from '@theatre/utils/persistAtom' import {persistAtom} from '@theatre/utils/persistAtom' type PersistentState = | {loggedIn: false} | {loggedIn: true; idToken: string; accessToken: string} type ProcedureState = | { type: 'authorize' deviceTokenFlowState: DeviceTokenFlowState | undefined } | { type: 'expandScope' additionalScope: studioAccessScopes.Scopes deviceTokenFlowState: DeviceTokenFlowState | undefined } | { type: 'refreshTokens' } | { type: 'destroyIdToken' } type CurrentProcedure = { abortController: AbortController promise: Promise procedureState: ProcedureState } type EphemeralState = { loaded: boolean currentProcedure: undefined | CurrentProcedure } type DeviceTokenFlowState = | { type: 'waitingForDeviceCode' // codeChallenge: string // codeVerifier: string } | { type: 'codeReady' verificationUriComplete: string // codeChallenge: string // codeVerifier: string // userCode: string // deviceCode: string // lastTokenRequestTime: number | undefined // interval: number } const bcId = Math.random().toString(36).slice(2) export type AuthDerivedState = | 'loading' | { loggedIn: false procedureState: undefined | ProcedureState } | { loggedIn: true user: {email: string} procedureState: undefined | ProcedureState } export default class Auth { private _persistentState = new Atom({loggedIn: false}) private _atomPersistor: AtomPersistor | undefined private _broadcastChannel: BroadcastChannel | undefined private _ephemeralState = new Atom({ currentProcedure: undefined, loaded: false, }) private _readyDeferred = defer() readonly derivedState: Prism constructor(readonly studio: Studio) { const persistAtomDeferred = defer() void this.studio._optsPromise.then((o) => { if (o.usePersistentStorage === true) { this._atomPersistor = persistAtom( this._persistentState as $IntentionalAny as Atom<{}>, this._persistentState.pointer as $IntentionalAny, () => persistAtomDeferred.resolve(), o.persistenceKey + 'auth', ) const bc = new BroadcastChannel(`theatrejs-auth-${o.persistenceKey}`) this._broadcastChannel = bc // listen to changes from other tabs bc.addEventListener('message', (e) => { if (e.data !== bcId) { this._atomPersistor?.refresh() } }) } else { persistAtomDeferred.resolve() } }) void persistAtomDeferred.promise.then(() => { this._readyDeferred.resolve() }) void this._readyDeferred.promise.then(() => { this._ephemeralState.setByPointer((p) => p.loaded, true) }) this.derivedState = prism((): AuthDerivedState => { const ephemeralState = val(this._ephemeralState.pointer) if (!ephemeralState.loaded) { return 'loading' } const persistentState = val(this._persistentState.pointer) if (persistentState.loggedIn) { return { loggedIn: true, user: getIdTokenClaimsWithoutVerifying(persistentState.idToken) ?? { email: 'unknown', }, procedureState: ephemeralState.currentProcedure?.procedureState, } } else { return { loggedIn: false, procedureState: ephemeralState.currentProcedure?.procedureState, } } }) } get ready() { return this._readyDeferred.promise } private async _acquireLock( abortController: AbortController, initialState: ProcedureState, cb: ( abortSignal: AbortSignal, setState: (procedureState: ProcedureState) => void, ) => Promise, ): Promise { if (this._readyDeferred.status !== 'resolved') { throw new Error('Not ready') } if (this._ephemeralState.get().currentProcedure) { throw new Error('Already running a procedure') } const d = defer() try { await navigator.locks.request( 'theatrejs-auth', {mode: 'exclusive', ifAvailable: true}, async (possibleLock) => { if (!possibleLock) { d.reject(new Error('Failed to acquire lock')) return } if (abortController.signal.aborted) { d.reject(new Error('Aborted')) return } const currentProcedure: CurrentProcedure = { abortController, promise: d.promise.then(() => {}), procedureState: initialState, } this._ephemeralState.setByPointer( (p) => p.currentProcedure, currentProcedure, ) this._atomPersistor?.refresh() const setProcedureState = (procedureState: ProcedureState) => { this._ephemeralState.setByPointer((p) => p.currentProcedure, { ...currentProcedure, procedureState, }) } try { const ret = await cb(abortController.signal, setProcedureState) this._atomPersistor?.flush() this._broadcastChannel?.postMessage(bcId) d.resolve(ret) } catch (err) { d.reject(err) } }, ) } finally { this._ephemeralState.setByPointer((p) => p.currentProcedure, undefined) } return d.promise } /** * Runs the authorization procedure. Will error if already authorized, or if another procedure is already running. */ async authorize() { const abortController = new AbortController() const initialState: ProcedureState = { type: 'authorize', deviceTokenFlowState: undefined, } await this._acquireLock( abortController, initialState, async (abortSignal, setState) => { const persistentState = this._persistentState.get() if (persistentState.loggedIn) { throw new Error('Already authorized') } const result = await tokenProcedures.deviceAuthorizationFlow( ['workspaces-list'], undefined, (state) => { setState({...initialState, deviceTokenFlowState: state}) }, abortSignal, ) if (!result.success) { throw new Error(`Failed to authorize: ${result.error}: ${result.msg}`) } const {accessToken, idToken} = result this._persistentState.set({loggedIn: true, accessToken, idToken}) }, ) } /** * Runs the authorization procedure. Will if another procedure is already running. */ async deauthorize() { const abortController = new AbortController() const initialState: ProcedureState = { type: 'destroyIdToken', } await this._acquireLock( abortController, initialState, async (abortSignal) => { const persistentState = this._persistentState.get() if (!persistentState.loggedIn) { return } const result = await tokenProcedures.destroyIdToken( persistentState.idToken, abortSignal, ) if (!result.success) { throw new Error( `Failed to deauthorize: ${result.error}: ${result.msg}`, ) } this._persistentState.set({loggedIn: false}) }, ) } // /** // * Resolves with the access token. If no token is set, it'll wait until one is. Note that this will _NOT_ trigger authentication, // * so if this function is called and the user is not authenticated, it'll wait forever. // */ // private async _getValidAccessToken(): Promise { // // no ongoing authentication atm // if (!this._ephemeralState.get().authFlowState) { // const authState = this._persistentState.get() // // and the access token is available // if (authState) { // return authState.accessToken // } else { // throw new Error('Not authenticated') // } // } else { // const accessToken = await this._waitForAccessToken() // return accessToken // } // } /** * If logged in, returns the access token. If not, it'll wait until the user * initiaites a login flow, and then return the access token. */ // private async _waitForAccessToken(): Promise { // await this._readyDeferred.promise // const notReady = {} // const accessToken = await waitForPrism( // prism(() => { // const s = val(this._mishmashState.pointer) // if (s.type === 'loggedIn') { // return s.accessToken // } // return notReady // }), // (v): v is string => v !== notReady, // ) // return accessToken as $IntentionalAny // } private _getAccessToken(): string | undefined { const s = val(this._persistentState.pointer) if (s.loggedIn) { return s.accessToken } return undefined } async _refreshTokens() { // TODO } async wrapTrpcProcedureWithAuth< Input, Ret, Fn extends ( input: Input & {studioAuth: {accessToken: string}}, opts: $IntentionalAny, ) => Promise, >( fn: Fn, args: [Input, $IntentionalAny], path: string[], retriesLeft: number = 3, ): Promise { await this.ready const accessToken = this._getAccessToken() if (!accessToken) { throw new Error('Not authenticated') } const [input, opts] = args try { const isSubscribe = path[path.length - 1] === 'subscribe' if (isSubscribe) { const response = fn({...input, studioAuth: {accessToken}}, opts) return response } else { const response = await fn({...input, studioAuth: {accessToken}}, opts) return response } } catch (err) { console.log('err', err) if (err instanceof TRPCClientError && err.data.code === 'UNAUTHORIZED') { console.log('is unaothorized error') if (retriesLeft <= 0) { return Promise.reject(err) } // this is a 401, which means as long as we have a valid accessToken, we should be able to retry the request await this._refreshTokens() return this.wrapTrpcProcedureWithAuth(fn, args, path, retriesLeft - 1) } else { return Promise.reject(err) } } } } namespace tokenProcedures { /** * Runs a device authorization flow, and returns the access token and id token if successful. * * @param scope - The scopes to request. If originalIdToken is provided, the scopes will be expanded to include the scopes of the original token. * @param originalIdToken - The original id token, if any. If provided, the scopes will be expanded to include the scopes of the original token. * @param stateChange - A callback that will be called whenever the state of the flow changes. * @param abortSignal - The abort signal to use for the flow. * @returns */ export async function deviceAuthorizationFlow( scope: studioAccessScopes.Scopes, originalIdToken: string | undefined, stateChange: (s: DeviceTokenFlowState) => void, abortSignal?: AbortSignal, ): Promise< | {success: true; accessToken: string; idToken: string} | {success: false; error: 'userDenied' | 'unknown'; msg?: string} > { const appLink = await getStudio()._rawLinks.app let outerTries = 0 outer: while (outerTries < 2) { outerTries++ if (abortSignal?.aborted) { return {success: false, error: 'unknown', msg: 'Aborted'} } const nounce = generateRandomCodeVerifier() const codeVerifier = generateRandomCodeVerifier() const codeChallenge = await calculatePKCECodeChallenge(codeVerifier) stateChange({ type: 'waitingForDeviceCode', // codeChallenge, // codeVerifier, }) const flowInitResult = await appLink.api.studioAuth.deviceCode.mutate( { nounce, codeChallenge, codeChallengeMethod: 'S256', scopes: scope, originalIdToken, }, {signal: abortSignal}, ) stateChange({ type: 'codeReady', // codeChallenge, // codeVerifier, // deviceCode: flowInitResult.deviceCode, verificationUriComplete: flowInitResult.verificationUriComplete, // userCode: '', // interval: flowInitResult.interval, // lastTokenRequestTime: undefined, }) // window.open(flowInitResult.verificationUriComplete, '_blank') inner: while (true) { if (abortSignal?.aborted) { return {success: false, error: 'unknown', msg: 'Aborted'} } await delay(flowInitResult.interval + 1000, abortSignal) try { const result = await appLink.api.studioAuth.tokens.mutate( { deviceCode: flowInitResult.deviceCode, codeVerifier, }, {signal: abortSignal}, ) if (result.isError) { if (result.error === 'invalidDeviceCode') { console.error(result) continue outer } else if (result.error === 'userDeniedAuth') { return {success: false, error: 'userDenied'} } else if (result.error === 'notYetReady') { continue inner } else { const msg = `Unknown error returned from app-studio-trpc: ${result.error} - ${result.errorMessage}` console.error(msg) return {success: false, error: 'unknown', msg} } } else { const {idToken, accessToken} = result // TODO verify that the refresh token has the correct nounce if (false) { console.warn('The request returned an invalid nounce') continue outer } return {success: true, accessToken, idToken} } } catch (err) { console.error(err) return { success: false, error: 'unknown', msg: (err as $IntentionalAny).message, } } } } return {success: false, error: 'unknown'} } export async function refreshTokens( originalIdToken: string, abortSignal?: AbortSignal, ): Promise< | {success: true; accessToken: string; idToken: string} | {success: false; error: 'invalidIdToken' | 'unknown'; msg: string} > { const appLink = await getStudio()._rawLinks.app let tries = 0 const MAX_TRIES = 8 while (true) { if (abortSignal?.aborted) { return {success: false, error: 'unknown', msg: 'Aborted'} } tries++ if (tries > MAX_TRIES) { return {success: false, error: 'unknown', msg: 'Too many tries'} } const result = await appLink.api.studioAuth.refreshAccessToken.mutate( {refreshToken: originalIdToken}, {signal: abortSignal}, ) if (result.isError) { if (result.error === 'invalidRefreshToken') { throw new Error('Invalid refresh token') } else { if (tries > MAX_TRIES) { return {success: false, error: 'unknown', msg: result.errorMessage} } continue } } else { return { accessToken: result.accessToken, idToken: result.refreshToken, success: true, } } } } export async function destroyIdToken( idToken: string, abortSignal?: AbortSignal, ): Promise< | {success: true} | {success: false; error: 'invalidIdToken' | 'unknown'; msg: string} > { const appLink = await getStudio()._rawLinks.app const result = await appLink.api.studioAuth.destroyIdToken.mutate( {idToken: idToken}, {signal: abortSignal}, ) if (result.isError) { return {success: false, error: 'unknown', msg: result.errorMessage} } else { return {success: true} } } } function getIdTokenClaimsWithoutVerifying( idToken: string, ): undefined | studioAuthTokens.IdTokenPayload { try { const s = decodeJwt(idToken) return s as $FixMe } catch (err) { console.log(`getIdTokenClaimsWithoutVerifying failed:`, err) return undefined } } ================================================ FILE: packages/studio/src/IDBStorage.ts ================================================ import * as idb from 'idb-keyval' /** * Custom IDB keyval storage creator. Right now this exists solely as a more convenient way to use idb-keyval with a custom db name. * It also automatically prefixes the provided name with `theatrejs-` to avoid conflicts with other libraries. * * @param name - The name of the database * @returns An object with the same methods as idb-keyval, but with a custom database name */ export const createStore = (name: string) => { const customStore = idb.createStore(`theatrejs-${name}`, 'default-store') return { set: (key: string, value: any) => idb.set(key, value, customStore), get: (key: string) => idb.get(key, customStore), del: (key: string) => idb.del(key, customStore), keys: () => idb.keys(customStore), entries: () => idb.entries(customStore), values: () => idb.values(customStore), } } ================================================ FILE: packages/studio/src/PaneManager.ts ================================================ import {prism, val} from '@theatre/dataverse' import SimpleCache from '@theatre/utils/SimpleCache' import type {$IntentionalAny, StrictRecord} from '@theatre/core/types/public' import type {Studio} from './Studio' import type {PaneInstanceId, PaneInstance} from '@theatre/core/types/public' import {emptyObject} from '@theatre/utils' export default class PaneManager { private readonly _cache = new SimpleCache() constructor(private readonly _studio: Studio) { this._instantiatePanesAsTheyComeIn() } private _instantiatePanesAsTheyComeIn() { const allPanesD = this._getAllPanes() allPanesD.onStale(() => { allPanesD.getValue() }) } private _getAllPanes() { return this._cache.get('_getAllPanels()', () => prism((): StrictRecord> => { const core = val(this._studio.coreP) if (!core) return {} const instanceDescriptors = val(this._studio.atomP.historic.panelInstanceDesceriptors)! ?? emptyObject const paneClasses = val(this._studio.ephemeralAtom.pointer.extensions.paneClasses) ?? emptyObject const instances: StrictRecord> = {} for (const instanceDescriptor of Object.values(instanceDescriptors)) { if (!instanceDescriptor) continue const panelClass = paneClasses[instanceDescriptor.paneClass] if (!panelClass) continue const {instanceId} = instanceDescriptor const {extensionId, classDefinition: definition} = panelClass const instance = prism.memo( `instance-${instanceDescriptor.instanceId}`, () => { const inst: PaneInstance<$IntentionalAny> = { extensionId, instanceId, definition, } return inst }, [definition], ) instances[instanceId] = instance } return instances }), ) } get allPanesD() { return this._getAllPanes() } createPane( paneClass: PaneClass, ): PaneInstance { const core = this._studio.core if (!core) { throw new Error( `Can't create a pane because @theatre/core is not yet loaded`, ) } const extensionId = val( this._studio.ephemeralAtom.pointer.extensions.paneClasses[paneClass] .extensionId, ) const allPaneInstances = val(this._studio.atomP.historic.panelInstanceDesceriptors)! ?? emptyObject let instanceId!: PaneInstanceId for (let i = 1; i < 1000; i++) { instanceId = `${paneClass} #${i}` as PaneInstanceId if (!allPaneInstances[instanceId]) break } if (!extensionId) { throw new Error(`Pane class "${paneClass}" is not registered.`) } this._studio.transaction(({stateEditors}) => { stateEditors.studio.historic.panelInstanceDescriptors.setDescriptor({ instanceId, paneClass, }) }) return this._getAllPanes().getValue()[instanceId]! } destroyPane(instanceId: PaneInstanceId): void { const core = this._studio.core if (!core) { throw new Error( `Can't do this yet because @theatre/core is not yet loaded`, ) } this._studio.transaction(({stateEditors}) => { stateEditors.studio.historic.panelInstanceDescriptors.remove({instanceId}) }) } } ================================================ FILE: packages/studio/src/Scrub.ts ================================================ import type {$FixMe} from '@theatre/core/types/public' import type {Pointer} from '@theatre/dataverse' import {getPointerParts} from '@theatre/dataverse' import type {Studio} from './Studio' import type {CommitOrDiscardOrRecapture} from './StudioStore/StudioStore' import type {IScrub, IScrubApi} from '@theatre/core' import {__private} from '@theatre/core' const {forEachPropDeep} = __private.propTypeUtils const {isSheetObject} = __private.instanceTypes type State_Captured = { type: 'Captured' transaction: CommitOrDiscardOrRecapture flagsTransaction: CommitOrDiscardOrRecapture } type State = | {type: 'Ready'} | State_Captured | {type: 'Committed'} | {type: 'Discarded'} let lastScrubIdAsNumber = 0 export default class Scrub implements IScrub { private readonly _id: string private _state: State = {type: 'Ready'} // private readonly _scrubApi: IScrubApi get status() { return this._state.type } constructor(private readonly _studio: Studio) { this._id = String(lastScrubIdAsNumber++) } reset(): void { const {_state: state} = this if (state.type === 'Ready') { return } else if (state.type === 'Captured') { this._state = {type: 'Ready'} state.transaction.reset() state.flagsTransaction.reset() } else if (state.type === 'Committed') { throw new Error(`This scrub is already committed and can't be reset.`) } else { throw new Error(`This scrub is already discarded and can't be reset.`) } } commit(): void { const {_state: state} = this if (state.type === 'Captured') { state.transaction.commit() state.flagsTransaction.discard() this._state = {type: 'Committed'} } else if (state.type === 'Ready') { console.warn(`Scrub is empty. Nothing to commit.`) return } else if (state.type === 'Committed') { throw new Error(`This scrub is already committed.`) } else { throw new Error(`This scrub is already discarded and can't be comitted.`) } } capture(fn: (api: IScrubApi) => void): void { if (this._state.type === 'Ready' || this._state.type === 'Captured') { let errored = true try { this._state = { type: 'Captured', ...this._capture( fn, this._state.type === 'Captured' ? this._state : undefined, ), } errored = false } finally { if (errored) { console.error( `This scrub's callback threw an error. We're undo-ing all of the changes made by this scrub, and marking it as discarded.`, ) this._state = {type: 'Discarded'} } } } else { if (this._state.type === 'Committed') { throw new Error( `This scrub is already committed and cannot capture again. ` + `If you wish to capture more, you can start a new studio.scrub() or do so before scrub.commit()`, ) } else { throw new Error( `This scrub is already discarded and cannot capture again. ` + `If you wish to capture more, you can start a new studio.scrub() or do so before scrub.discard()`, ) } } } private _capture( fn: (api: IScrubApi) => void, existingTransactions?: { transaction: CommitOrDiscardOrRecapture flagsTransaction: CommitOrDiscardOrRecapture }, ): { transaction: CommitOrDiscardOrRecapture flagsTransaction: CommitOrDiscardOrRecapture } { const sets: Array> = [] const transaction = this._studio.tempTransaction( (transactionApi) => { let running = true const api: IScrubApi = { set: (pointer, value) => { if (!running) { throw new Error( `You seem to have called the scrub api after scrub.capture()`, ) } const {root, path} = getPointerParts(pointer) if (!isSheetObject(root)) { throw new Error( `We can only scrub props of Sheet Objects for now`, ) } transactionApi.set(pointer, value) sets.push(pointer as Pointer<$FixMe>) }, } try { fn(api) } finally { running = false } }, existingTransactions?.transaction, ) const flagsTransaction = this._studio.tempTransaction( ({stateEditors}) => { sets.forEach((pointer) => { const {root, path} = getPointerParts(pointer) if (!isSheetObject(root)) { return } const defaultValueOfProp = root.template.getDefaultsAtPointer(pointer) forEachPropDeep( defaultValueOfProp, (val, pathToProp) => { stateEditors.studio.ephemeral.projects.stateByProjectId.stateBySheetId.stateByObjectKey.propsBeingScrubbed.flag( {...root.address, pathToProp}, ) }, path, ) }) }, existingTransactions?.flagsTransaction, ) return {transaction, flagsTransaction} } discard(): void { const {_state: state} = this if (state.type === 'Captured' || state.type === 'Ready') { if (state.type === 'Captured') { state.transaction.discard() state.flagsTransaction.discard() } this._state = {type: 'Discarded'} } else if (state.type === 'Committed') { throw new Error(`This scrub is already committed and can't be discarded.`) } else { throw new Error(`This scrub is already discarded`) } } } ================================================ FILE: packages/studio/src/Storno/Storno.ts ================================================ import type {Prism} from '@theatre/dataverse' import {Atom, prism, val} from '@theatre/dataverse' import type {$FixMe} from '@theatre/core/types/public' type State = { loginState: | {loggedIn: false} | {loggedIn: true; accessToken: string; refreshToken: string} authenticateProcessState: | { // the authentication flow is not started, becuase either the user is already logged in, or the user hasn't tried to log in yet type: 'idle' } | { // the user tried to log in, and we're waiting for the server to generate a checkToken, after which we'll redirect the user to the authentication page of the server type: 'autnenticating/waiting-for-checkToken' // the time at which the user tried to log in time: number // a random string generated on the client, which will be used to identify the authentication flow clientFlowToken: string // the time at which waiting for checkToken will expire expiresAt: number } | { // the user tried to log in, but the server failed to generate a checkToken. We should display an error message to the user type: 'authenticating/waiting-for-checkToken/error' error: {code: string; message: string} // the time at which the error was received time: number } | { // we've received a checkToken type: 'autnenticating/waiting-for-accessToken' checkToken: string // the url to which we should redirect the user userAuthUrl: string // the interval at which we should poll the server for the access token interval: number // the clientFlowToken that we sent to the server clientFlowToken: string // the time at which waiting for accessToken will expire expiresAt: number } | { // for some reason, the server did not send us an access token. We should display an error message to the user type: 'autnenticating/waiting-for-accessToken/error' error: | { // user denied this session access code: 0 message: string } | { // other error code: 1 message: string } } | { // we've received an access/refreshtoken (saved to loginState). This state is used to display a success message to the user, after which we'll switch to idle type: 'success' time: number } } type UserInfo = { email: string displayName: string avatarUrl: string id: string } export default class Storno { protected _atom: Atom protected _userInfo: Prism constructor() { this._atom = new Atom({ loginState: {loggedIn: false}, authenticateProcessState: {type: 'idle'}, }) this._userInfo = prism(() => { const loginState = val(this._atom.pointer.loginState) if (!loginState.loggedIn) { return null } const {accessToken} = loginState const payload = accessToken as $FixMe // TODO: decode payload // let's trust that the payload is correct return payload.userInfo }) } get userInfoPr(): Prism { return null as $FixMe } } ================================================ FILE: packages/studio/src/Studio.ts ================================================ import Scrub from '@theatre/studio/Scrub' import type {StudioHistoricState} from '@theatre/core/types/private/studio' import UI from '@theatre/studio/UI/UI' import type {Pointer, Ticker} from '@theatre/dataverse' import {Atom, PointerProxy, pointerToPrism} from '@theatre/dataverse' import type { CommitOrDiscardOrRecapture, ITransactionPrivateApi, } from './StudioStore/StudioStore' import StudioStore from './StudioStore/StudioStore' import type { IExtension, IStudio, PaneClassDefinition, InitOpts, } from '@theatre/core/types/public' import TheatreStudio from './TheatreStudio' import {nanoid} from 'nanoid/non-secure' import type Project from '@theatre/core/projects/Project' import type {CoreBits} from '@theatre/core/CoreBundle' import SimpleCache from '@theatre/utils/SimpleCache' import type {IProject, ISheet, ProjectId} from '@theatre/core' import PaneManager from './PaneManager' import type * as _coreExports from '@theatre/core/coreExports' import type { OnDiskState, ProjectEphemeralState, } from '@theatre/core/types/private/core' import type {Deferred} from '@theatre/utils/defer' import {defer} from '@theatre/utils/defer' import checkForUpdates from './checkForUpdates' import shallowEqual from 'shallowequal' import {createStore} from './IDBStorage' import {getAllPossibleAssetIDs} from '@theatre/studio/utils/assets' import {notify} from './notify' import type {RafDriverPrivateAPI} from '@theatre/core/rafDrivers' import {persistAtom} from '@theatre/utils/persistAtom' import produce from 'immer' import Storno from './Storno/Storno' import Auth from './Auth' import type {$IntentionalAny} from '@theatre/core/types/public' import AppLink from './SyncStore/AppLink' import SyncServerLink from './SyncStore/SyncServerLink' import type {TrpcClientWrapped} from './SyncStore/utils' import {wrapTrpcClientWithAuth} from './SyncStore/utils' import {env} from './env' const DEFAULT_PERSISTENCE_KEY = 'theatre-0.4' export type CoreExports = typeof _coreExports const STUDIO_NOT_INITIALIZED_MESSAGE = `You seem to have imported '@theatre/studio' but haven't initialized it. You can initialize the studio by: \`\`\` import theatre from '@theatre/core' theatre.init({studio: true}) \`\`\` * If you didn't mean to import '@theatre/studio', this means that your bundler is not tree-shaking it. This is most likely a bundler misconfiguration. * If you meant to import '@theatre/studio' without showing its UI, you can do that by running: \`\`\` import theatre from '@theatre/core' theatre.init({studio: true}) studio.ui.hide() \`\`\` ` const STUDIO_INITIALIZED_LATE_MSG = `You seem to have imported '@theatre/studio' but called \`studio.initialize()\` after some delay. Theatre.js projects remain in pending mode (won't play their sequences) until the studio is initialized, so you should place the \`studio.initialize()\` line right after the import line: \`\`\` import theatre from '@theatre/core' // ... and other imports studio.initialize() \`\`\` ` export type StudioOpts = { serverUrl: string persistenceKey: string usePersistentStorage: boolean rafDriver: RafDriverPrivateAPI } export type UpdateCheckerResponse = | {hasUpdates: true; newVersion: string; releasePage: string} | {hasUpdates: false} export class Studio { readonly ui: UI // this._uiInitDeferred.promise will resolve once this._ui is set readonly publicApi: IStudio readonly address: {studioId: string} readonly _projectsProxy: PointerProxy> = new PointerProxy(new Atom({}).pointer) readonly projectsP: Pointer> = this._projectsProxy.pointer readonly _store: StudioStore readonly auth!: Auth private readonly _storno = new Storno() private _corePrivateApi: CoreBits['privateAPI'] | undefined private readonly _cache = new SimpleCache() readonly paneManager: PaneManager /** * An atom holding the exports of '\@theatre/core'. Will be undefined if '\@theatre/core' is never imported */ private _coreAtom = new Atom<{core?: CoreExports}>({}) readonly ephemeralAtom = new Atom<{ // reflects the value of _initializedDeferred.promise. Since it's in an atom, it can be accessed via a pointer initialized: boolean coreByProject: {[projectId in string]: ProjectEphemeralState} extensions: { byId: {[extensionId in string]?: IExtension} paneClasses: { [paneClassName in string]?: { extensionId: string classDefinition: PaneClassDefinition } } } }>({ initialized: false, coreByProject: {}, extensions: { byId: {}, paneClasses: {}, }, }) readonly ahistoricAtom = new Atom<{ updateChecker?: { // timestamp of the last time we checked for updates lastChecked: number result: UpdateCheckerResponse | 'error' } }>({}) /** * Tracks whether studio.initialize() is called. */ private _initializeFnCalled = false /** * Will be set to true if studio.initialize() isn't called after 100ms. */ private _didWarnAboutNotInitializing = false /** * This will be set as soon as `@theatre/core` registers itself on `@theatre/studio` */ private _coreBits: CoreBits | undefined private _optsDeferred: Deferred private readonly _initializedPromise: Promise readonly _rawLinks: { app: Promise syncServer: Promise } readonly authedLinks!: { app: TrpcClientWrapped syncServer: TrpcClientWrapped } get ticker(): Ticker { if (!this._rafDriver) { throw new Error( '`studio.ticker` was read before studio.initialize() was called.', ) } return this._rafDriver.ticker } private _rafDriver: RafDriverPrivateAPI | undefined get atomP() { return this._store.atomP } get _optsPromise() { return this._optsDeferred.promise } constructor() { this.address = {studioId: nanoid(10)} this._optsDeferred = defer() const syncServerLinkDeferred = defer() const syncServerLink = syncServerLinkDeferred.promise const appLink = this._optsPromise.then( ({serverUrl}): AppLink => typeof window === 'undefined' ? (null as $IntentionalAny) : new AppLink(serverUrl), ) if (typeof window !== 'undefined') { void appLink .then((appLink) => { return appLink.api.syncServerUrl.query().then((url) => { syncServerLinkDeferred.resolve(new SyncServerLink(url)) }) }) .catch((err) => { syncServerLinkDeferred.reject(err) console.error(err) }) } else { syncServerLinkDeferred.resolve(null as $IntentionalAny) } this._rawLinks = {app: appLink, syncServer: syncServerLink} if (typeof window !== 'undefined' && process.env.NODE_ENV !== 'test') { this.auth = new Auth(this) this.authedLinks = { syncServer: wrapTrpcClientWithAuth( this._rawLinks.syncServer.then((s) => s.api), (fn: any, args: any[], path): any => { return this.auth.wrapTrpcProcedureWithAuth( fn, args as $IntentionalAny, path, ) }, ), app: wrapTrpcClientWithAuth( this._rawLinks.app.then((s) => s.api), (fn: any, args: any[], path): any => this.auth.wrapTrpcProcedureWithAuth( fn, args as $IntentionalAny, path, ), ), } } this._store = new StudioStore(this) this.publicApi = new TheatreStudio(this) this.ui = new UI(this) this._attachToIncomingProjects() this.paneManager = new PaneManager(this) // check whether studio.initialize() is called, but only if we're in the browser if (typeof window !== 'undefined') { setTimeout(() => { if (!this._initializeFnCalled) { console.error(STUDIO_NOT_INITIALIZED_MESSAGE) this._didWarnAboutNotInitializing = true } }, 100) } this._initializedPromise = this._init() this._initializedPromise.catch((reason) => { console.error(reason) return Promise.reject(reason) }) } async _init() { const storeOpts = await this._optsDeferred.promise const ahistoricAtomInitializedD = defer() if (storeOpts.usePersistentStorage) { persistAtom( this.ahistoricAtom, this.ahistoricAtom.pointer, () => { ahistoricAtomInitializedD.resolve() }, 'theatrejs-studio-ahistoric', ) } else { ahistoricAtomInitializedD.resolve() } this._rafDriver = storeOpts.rafDriver if (process.env.NODE_ENV !== 'test' && typeof window !== 'undefined') { await this.ui.ready } await ahistoricAtomInitializedD.promise this.ephemeralAtom.setByPointer((p) => p.initialized, true) if (process.env.NODE_ENV !== 'test') { this.ui.render() if (navigator.onLine) { checkForUpdates().catch((err) => { console.error(err) }) } } } async initialize(opts?: InitOpts) { if (!this._coreBits) { throw new Error( `You seem to have imported \`@theatre/studio\` without importing \`@theatre/core\`. Make sure to include an import of \`@theatre/core\` before calling \`studio.initializer()\`.`, ) } if (this._initializeFnCalled) { return this._initializedPromise } this._initializeFnCalled = true if (this._didWarnAboutNotInitializing) { console.warn(STUDIO_INITIALIZED_LATE_MSG) } if (this._optsDeferred.status === 'pending') { this._optsDeferred.resolve(sanitizeOpts(opts, this._coreBits)) } return this._initializedPromise } get initialized(): Promise { return this._initializedPromise } get initializedP(): Pointer { return this.ephemeralAtom.pointer.initialized } _attachToIncomingProjects() { const projectsD = pointerToPrism(this.projectsP) const attachToProjects = (projects: Record) => { for (const project of Object.values(projects)) { if (!project.isAttachedToStudio) { project.attachToStudio(this) } } } projectsD.onStale(() => { attachToProjects(projectsD.getValue()) }) attachToProjects(projectsD.getValue()) } setCoreBits(coreBits: CoreBits) { this._coreBits = coreBits this._corePrivateApi = coreBits.privateAPI this._coreAtom.setByPointer((p) => p.core, coreBits.coreExports) this._setProjectsP(coreBits.projectsP) } private _setProjectsP(projectsP: Pointer>) { this._projectsProxy.setPointer(projectsP) } scrub() { return new Scrub(this) } tempTransaction( fn: (api: ITransactionPrivateApi) => void, existingTransaction: CommitOrDiscardOrRecapture | undefined = undefined, ): CommitOrDiscardOrRecapture { return this._store.tempTransaction(fn, existingTransaction) } transaction( fn: (api: ITransactionPrivateApi) => void, undoable: boolean = true, ): unknown { return this._store.transaction(fn, undoable) } __dev_startHistoryFromScratch(newHistoricPart: StudioHistoricState) { return this._store.__dev_startHistoryFromScratch(newHistoricPart) } get corePrivateAPI() { return this._corePrivateApi } get core() { return this._coreAtom.get().core } get coreP() { return this._coreAtom.pointer.core } extend(extension: IExtension, opts?: {__experimental_reconfigure?: boolean}) { if (!extension || typeof extension !== 'object') { throw new Error(`Extensions must be JS objects`) } if (typeof extension.id !== 'string') { throw new Error(`extension.id must be a string`) } const reconfigure = opts?.__experimental_reconfigure === true const extensionId = extension.id const prevExtension = this.ephemeralAtom.get().extensions.byId[extensionId] if (prevExtension) { if (reconfigure) { } else { if ( extension === prevExtension || shallowEqual(extension, prevExtension) ) { // probably running studio.extend() several times because of hot reload. // as long as it's the same extension, we can safely ignore. return } throw new Error( `Extension id "${extension.id}" is already defined. If you mean to re-configure the extension, do it like this: studio.extend(extension, {__experimental_reconfigure: true})})`, ) } } this.ephemeralAtom.reduceByPointer( (p) => p.extensions, (extensions) => { return produce(extensions, (draft) => { draft.byId[extension.id] = extension const allPaneClasses = draft.paneClasses if (reconfigure && prevExtension) { // remove all pane classes that were set by the previous version of the extension prevExtension.panes?.forEach((classDefinition) => { delete allPaneClasses[classDefinition.class] }) } // if the extension defines pane classes, add them to the list of all pane classes extension.panes?.forEach((classDefinition) => { if (typeof classDefinition.class !== 'string') { throw new Error(`pane.class must be a string`) } if (classDefinition.class.length < 3) { throw new Error( `pane.class should be a string with 3 or more characters`, ) } const existing = allPaneClasses[classDefinition.class] if (existing) { if (reconfigure && existing.extensionId === extension.id) { // well this should never happen because we already deleted the pane class above console.warn( `Pane class "${classDefinition.class}" already exists. This is a bug in Theatre.js. Please report it at https://github.com/theatre-js/theatre/issues/new`, ) } else { throw new Error( `Pane class "${classDefinition.class}" already exists and is supplied by extension ${existing}`, ) } } allPaneClasses[classDefinition.class] = { extensionId: extension.id, classDefinition: classDefinition, } }) }) }, ) } getStudioProject(core: CoreExports): IProject { return this._cache.get('getStudioProject', () => core.getProject('Studio')) } getExtensionSheet(extensionId: string, core: CoreExports): ISheet { return this._cache.get('extensionSheet-' + extensionId, () => this.getStudioProject(core)!.sheet('Extension ' + extensionId), ) } undo() { this._store.undo() } redo() { this._store.redo() } createContentOfSaveFile(projectId: string): OnDiskState { return this._store.createContentOfSaveFile(projectId as ProjectId) } /** A function that returns a promise to an object containing asset storage methods for a project to be used by studio. */ async createAssetStorage(project: Project, baseUrl?: string) { // in SSR we bail out and return a dummy asset manager if (typeof window === 'undefined') { return { getAssetUrl: () => '', createAsset: () => Promise.resolve(null), } } // Check for support. if (!('indexedDB' in window)) { if (process.env.NODE_ENV !== 'test') console.log("This browser doesn't support IndexedDB.") return { getAssetUrl: (assetId: string) => { throw new Error( `IndexedDB is required by the default asset manager, but it's not supported by this browser. To use assets, please provide your own asset manager to the project config.`, ) }, createAsset: (asset: Blob) => { throw new Error( `IndexedDB is required by the default asset manager, but it's not supported by this browser. To use assets, please provide your own asset manager to the project config.`, ) }, } } const idb = createStore(`${project.address.projectId}-assets`) // get all possible asset ids referenced by either static props or keyframes const possibleAssetIDs = getAllPossibleAssetIDs(project) // Clean up assets not referenced by the project. We can only do this at the start because otherwise // we'd break undo/redo. const idbKeys = await idb.keys() await Promise.all( idbKeys.map(async (key) => { if (!possibleAssetIDs.includes(key)) { await idb.del(key) } }), ) // Clean up idb entries exported to disk await Promise.all( idbKeys.map(async (key) => { const assetUrl = `${baseUrl}/${key}` try { const response = await fetch(assetUrl, {method: 'HEAD'}) if (response.ok) { await idb.del(key) } } catch (e) { notify.error( 'Failed to access assets', `Failed to access assets at ${ project.config.assets?.baseUrl ?? '/' }. This is likely due to a CORS issue.`, ) } }), ) // A map for caching the assets outside of the db. We also need this to be able to retrieve idb asset urls synchronously. const assetsMap = new Map(await idb.entries()) // A map for caching the object urls created from idb assets. const urlCache = new Map() /** Gets idb aset url from asset blob */ const getUrlForAsset = (asset: Blob) => { if (urlCache.has(asset)) { return urlCache.get(asset)! } else { const url = URL.createObjectURL(asset) urlCache.set(asset, url) return url } } /** Gets idb asset url from id */ const getUrlForId = (assetId: string) => { const asset = assetsMap.get(assetId) if (!asset) { throw new Error(`Asset with id ${assetId} not found`) } return getUrlForAsset(asset) } return { getAssetUrl: (assetId: string) => { return assetsMap.has(assetId) ? getUrlForId(assetId) : `${baseUrl}/${assetId}` }, createAsset: async (asset: File) => { const existingIDs = getAllPossibleAssetIDs(project) let sameSame = false if (existingIDs.includes(asset.name)) { let existingAsset: Blob | undefined try { existingAsset = assetsMap.get(asset.name) ?? (await fetch(`${baseUrl}/${asset.name}`).then((r) => r.ok ? r.blob() : undefined, )) } catch (e) { notify.error( 'Failed to access assets', `Failed to access assets at ${ project.config.assets?.baseUrl ?? '/' }. This is likely due to a CORS issue.`, ) return Promise.resolve(null) } if (existingAsset) { const blobCompare = (await import('blob-compare')).default // @ts-ignore sameSame = await blobCompare.isEqual(asset, existingAsset) // if same same, we do nothing if (sameSame) { return asset.name // if different, we ask the user to pls rename } else { /** Initiates rename using a dialog. Returns a boolean indicating if the rename was succesful. */ const renameAsset = (text: string): boolean => { const newAssetName = prompt(text, asset.name) if (newAssetName === null) { // asset creation canceled return false } else if (newAssetName === '') { return renameAsset( 'Asset name cannot be empty. Please choose a different file name for this asset.', ) } else if (existingIDs.includes(newAssetName)) { console.log(existingIDs) return renameAsset( 'An asset with this name already exists. Please choose a different file name for this asset.', ) } // rename asset asset = new File([asset], newAssetName, {type: asset.type}) return true } // rename asset returns false if the user cancels the rename const success = renameAsset( 'An asset with this name already exists. Please choose a different file name for this asset.', ) if (!success) { return null } } } } assetsMap.set(asset.name, asset) await idb.set(asset.name, asset) return asset.name }, } } clearPersistentStorage(persistenceKey = DEFAULT_PERSISTENCE_KEY) { this._store.__experimental_clearPersistentStorage(persistenceKey) } } function sanitizeOpts( opts: InitOpts | undefined, coreBits: CoreBits, ): StudioOpts { const storeOpts: StudioOpts = { persistenceKey: DEFAULT_PERSISTENCE_KEY, usePersistentStorage: true, serverUrl: env.BACKEND_URL ?? 'https://app.theatrejs.com', rafDriver: coreBits.getCoreRafDriver(), } if (typeof opts?.serverUrl == 'string') { if ( // a fully formed url opts.serverUrl.match(/^(https?:)?\/\//) && // not ending with a slash !opts.serverUrl.endsWith('/') ) { storeOpts.serverUrl = opts.serverUrl } else { throw new Error( 'parameter `serverUrl` in `theatre.init({studio: true, serverUrl})` must be either undefined or a fully formed url (e.g. `https://app.theatrejs.com`)', ) } } if (typeof opts?.persistenceKey === 'string') { storeOpts.persistenceKey = opts.persistenceKey } if (opts?.usePersistentStorage === false || typeof window === 'undefined') { storeOpts.usePersistentStorage = false } if (opts?.__experimental_rafDriver) { if (opts?.__experimental_rafDriver.type !== 'Theatre_RafDriver_PublicAPI') { throw new Error( 'parameter `rafDriver` in `theatre.init({studio: true, __experimental_rafDriver})` must be either be undefined, or the return type of core.createRafDriver()', ) } const rafDriverPrivateApi = coreBits.privateAPI( opts.__experimental_rafDriver, ) if (!rafDriverPrivateApi) { // TODO - need to educate the user about this edge case throw new Error( 'parameter `rafDriver` in `theatre.init({studio: true, __experimental_rafDriver})` seems to come from a different version of `@theatre/core` than the version that is attached to `@theatre/studio`', ) } storeOpts.rafDriver = rafDriverPrivateApi } return storeOpts } ================================================ FILE: packages/studio/src/StudioBundle.ts ================================================ import type CoreBundle from '@theatre/core/CoreBundle' import type {CoreBits} from '@theatre/core/CoreBundle' import type {Studio} from './Studio' export default class StudioBundle { private _coreBundle: undefined | CoreBundle constructor(private readonly _studio: Studio) {} get type(): 'Theatre_StudioBundle' { return 'Theatre_StudioBundle' } registerCoreBundle(coreBundle: CoreBundle) { if (this._coreBundle) { throw new Error( `StudioBundle.coreBundle is already registered. This is a bug.`, ) } this._coreBundle = coreBundle let coreBits!: CoreBits coreBundle.getBitsForStudio(this._studio, (bits) => { coreBits = bits }) this._studio.setCoreBits(coreBits) } } ================================================ FILE: packages/studio/src/StudioStore/StudioStore.ts ================================================ import type { StudioAhistoricState, StudioEphemeralState, StudioHistoricState, StudioState, } from '@theatre/core/types/private' import type {$FixMe, $IntentionalAny, VoidFn} from '@theatre/core/types/public' import {Atom} from '@theatre/dataverse' import type {Pointer} from '@theatre/dataverse' import type {Draft} from 'immer' import type {OnDiskState} from '@theatre/core/types/private' import * as Saaz from '@theatre/saaz' import type {ProjectId} from '@theatre/core/types/public' import {schema} from '@theatre/sync-server/state/schema' import type { IInvokableDraftEditors, IStateEditors, StateEditorsAPI, } from '@theatre/sync-server/state/schema' import createTransactionPrivateApi from './createTransactionPrivateApi' import {SaazBack} from '@theatre/saaz' import type {Studio} from '@theatre/studio/Studio' import getStudio from '@theatre/studio/getStudio' export type Drafts = { historic: Draft ahistoric: Draft ephemeral: Draft } export interface ITransactionPrivateApi { set(pointer: Pointer, value: T): void unset(pointer: Pointer): void stateEditors: IInvokableDraftEditors } export type CommitOrDiscardOrRecapture = { commit: (undoable?: boolean) => void discard: VoidFn recapture: (fn: (api: ITransactionPrivateApi) => void) => void reset: VoidFn } export default class StudioStore { private readonly _atom: Atom readonly atomP: Pointer private _saaz: Saaz.SaazFront< {$schemaVersion: number}, IStateEditors, StateEditorsAPI, StudioState > constructor(readonly studio: Studio) { const backend = typeof window === 'undefined' ? new SaazBack({ storageAdapter: new Saaz.BackMemoryAdapter(), dbName: 'test', schema, }) : createTrpcBackend( this.studio._optsPromise.then((opts) => opts.persistenceKey), ) const saaz = new Saaz.SaazFront({ schema, dbName: 'test', storageAdapter: typeof window === 'undefined' || process.env.NODE_ENV === 'test' || true ? new Saaz.FrontMemoryAdapter() : new Saaz.FrontIDBAdapter('blah', 'test'), backend, }) this._saaz = saaz as $IntentionalAny this._atom = new Atom({} as StudioState) this._atom.set(saaz.state.cell as $FixMe) saaz.subscribe((state) => { this._atom.set(state.cell as $IntentionalAny) }) this.atomP = this._atom.pointer } getState(): StudioState { return this._atom.get() // return this._reduxStore.getState() } __experimental_clearPersistentStorage(persistenceKey: string): StudioState { throw new Error(`Implement me?`) // __experimental_clearPersistentStorage(this._reduxStore, persistenceKey) return this.getState() } /** * This method causes the store to start the history from scratch. This is useful * for testing and development where you want to explicitly provide a state to the * store. */ __dev_startHistoryFromScratch(newHistoricPart: StudioHistoricState) { throw new Error(`Implement me?`) // this._reduxStore.dispatch( // studioActions.historic.startHistoryFromScratch( // studioActions.reduceParts((s) => ({...s, historic: newHistoricPart})), // ), // ) } transaction( fn: (api: ITransactionPrivateApi) => void, undoable: boolean = true, ) { this._saaz.tx( () => {}, (draft) => { let running = true let ensureRunning = () => { if (!running) { throw new Error( `You seem to have called the transaction api after studio.transaction() has finished running`, ) } } const transactionApi = createTransactionPrivateApi(ensureRunning, draft) const ret = fn(transactionApi) running = false return ret }, undoable, ) return } tempTransaction( fn: (api: ITransactionPrivateApi) => void, existingTransaction: CommitOrDiscardOrRecapture | undefined = undefined, ): CommitOrDiscardOrRecapture { if (existingTransaction) { existingTransaction.recapture(fn) return existingTransaction } const t = this._saaz.tempTx( () => {}, (draft) => { let running = true let ensureRunning = () => { if (!running) { throw new Error( `You seem to have called the transaction api after studio.transaction() has finished running`, ) } } const transactionApi = createTransactionPrivateApi(ensureRunning, draft) const ret = fn(transactionApi) running = false return ret }, ) return { commit: t.commit, discard: t.discard, reset: t.reset, recapture: (fn: (api: ITransactionPrivateApi) => void): void => { t.recapture( () => {}, (draft) => { let running = true let ensureRunning = () => { if (!running) { throw new Error( `You seem to have called the transaction api after studio.transaction() has finished running`, ) } } const transactionApi = createTransactionPrivateApi( ensureRunning, draft, ) const ret = fn(transactionApi) running = false }, ) }, } } undo() { this._saaz.undo() } redo() { this._saaz.redo() } createContentOfSaveFile(projectId: ProjectId): OnDiskState { throw new Error(`Implement me`) // const projectState = // this._reduxStore.getState().$persistent.historic.innerState.coreByProject[ // projectId // ] // if (!projectState) { // throw new Error(`Project ${projectId} has not been initialized.`) // } // const revision = generateDiskStateRevision() // this.tempTransaction(({stateEditors}) => { // stateEditors.coreByProject.historic.revisionHistory.add({ // projectId, // revision, // }) // }).commit() // const projectHistoricState = // this._reduxStore.getState().$persistent.historic.innerState.coreByProject[ // projectId // ] // const generatedOnDiskState: OnDiskState = { // ...projectHistoricState, // } // return generatedOnDiskState } // get appApi(): TrpcClientWrapped { // return this.auth.appApi // } // get syncServerApi(): TrpcClientWrapped { // return this.auth.syncServerApi // } } function createTrpcBackend( dbNamePromise: Promise, ): Saaz.SaazBackInterface { const applyUpdates: Saaz.SaazBackInterface['applyUpdates'] = async (opts) => { const syncServerApi = getStudio().authedLinks.syncServer const dbName = await dbNamePromise return await syncServerApi.projectState.saaz_applyUpdates.mutate({ dbName, opts, }) } const updatePresence: Saaz.SaazBackInterface['updatePresence'] = async ( opts, ) => { const dbName = await dbNamePromise const syncServerApi = getStudio().authedLinks.syncServer return await syncServerApi.projectState.saaz_updatePresence.mutate({ dbName, opts, }) } const getUpdatesSinceClock: Saaz.SaazBackInterface['getUpdatesSinceClock'] = async (opts) => { const dbName = await dbNamePromise const syncServerApi = getStudio().authedLinks.syncServer return await syncServerApi.projectState.saaz_getUpdatesSinceClock.query({ dbName, opts, }) } const getLastIncorporatedPeerClock: Saaz.SaazBackInterface['getLastIncorporatedPeerClock'] = async (opts) => { const dbName = await dbNamePromise const syncServerApi = getStudio().authedLinks.syncServer return await syncServerApi.projectState.saaz_getLastIncorporatedPeerClock.query( { dbName, opts, }, ) } const closePeer: Saaz.SaazBackInterface['closePeer'] = async (opts) => { const dbName = await dbNamePromise const syncServerApi = getStudio().authedLinks.syncServer return await syncServerApi.projectState.saaz_closePeer.mutate({ dbName, opts, }) } const subscribe: Saaz.SaazBackInterface['subscribe'] = async ( opts, onUpdate, ) => { const dbName = await dbNamePromise const syncServerApi = getStudio().authedLinks.syncServer const subscription = syncServerApi.projectState.saaz_subscribe.subscribe( { dbName, opts, }, { onData(d) { onUpdate(d as $FixMe) }, }, ) return subscription.unsubscribe } return { applyUpdates, getUpdatesSinceClock, subscribe, updatePresence, closePeer, getLastIncorporatedPeerClock, } } ================================================ FILE: packages/studio/src/StudioStore/createTransactionPrivateApi.ts ================================================ import type {Pointer} from '@theatre/dataverse' import type {$FixMe, $IntentionalAny} from '@theatre/core/types/public' import get from 'lodash-es/get' import type {ITransactionPrivateApi} from './StudioStore' import getDeep from '@theatre/utils/getDeep' import type {SequenceTrackId} from '@theatre/core/types/public' import {getPointerParts} from '@theatre/dataverse' import type { PropTypeConfig, PropTypeConfig_AllSimples, PropTypeConfig_Compound, } from '@theatre/core/types/public' import type {PathToProp} from '@theatre/utils/pathToProp' import {__private} from '@theatre/core' import {isPlainObject} from 'lodash-es' import userReadableTypeOfValue from '@theatre/utils/userReadableTypeOfValue' import type {StudioState} from '@theatre/core/types/private' import type {IInvokableDraftEditors} from '@theatre/sync-server/state/schema' import {stateEditors} from '@theatre/sync-server/state/schema' const {getPropConfigByPath, forEachPropDeep} = __private.propTypeUtils const {isSheetObject} = __private.instanceTypes /** * Deep-clones a plain JS object or a `string | number | boolean`. In case of a plain * object, all its sub-props that aren't `string | number | boolean` get pruned. Also, * all empty objects (i.e. `{}`) get pruned. * * This is only used by {@link ITransactionPrivateApi.set} and it follows the global rule * that values pointed to by `object.props[...]` are never `null | undefined` or an empty object. */ function cloneDeepSerializableAndPrune(v: T): T | undefined { if ( typeof v === 'boolean' || typeof v === 'string' || typeof v === 'number' ) { return v } else if (isPlainObject(v)) { const cloned: $IntentionalAny = {} let clonedAtLeastOneProp = false for (const [key, val] of Object.entries(v as {})) { const clonedVal = cloneDeepSerializableAndPrune(val) if (clonedVal !== undefined) { cloned[key] = val clonedAtLeastOneProp = true } } if (clonedAtLeastOneProp) { return cloned } } else { return undefined } } /** * TODO replace with {@link iteratePropType} */ function forEachDeepSimplePropOfCompoundProp( propType: PropTypeConfig_Compound<$IntentionalAny>, path: Array, callback: ( propType: PropTypeConfig_AllSimples, path: Array, ) => void, ) { for (const [key, subType] of Object.entries(propType.props)) { if (subType.type === 'compound') { forEachDeepSimplePropOfCompoundProp(subType, [...path, key], callback) } else if (subType.type === 'enum') { throw new Error(`Not yet implemented`) } else { callback(subType, [...path, key]) } } } export default function createTransactionPrivateApi( ensureRunning: () => void, draft: StudioState, ): ITransactionPrivateApi { const _invokableStateEditors = proxyStateEditors( draft, stateEditors, ensureRunning, ) as IInvokableDraftEditors return { set: (pointer, value) => { ensureRunning() const _value = cloneDeepSerializableAndPrune(value) if (typeof _value === 'undefined') return const {root, path} = getPointerParts(pointer as Pointer<$FixMe>) if (isSheetObject(root)) { const sequenceTracksTree = root.template .getMapOfValidSequenceTracks_forStudio() .getValue() const propConfig = getPropConfigByPath(root.template.staticConfig, path) if (!propConfig) { throw new Error( `Object ${ root.address.objectKey } does not have a prop at ${JSON.stringify(path)}`, ) } const setStaticOrKeyframeProp = ( value: T, propConfig: PropTypeConfig_AllSimples, path: PathToProp, ) => { if (value === undefined || value === null) { return } const deserialized = cloneDeepSerializableAndPrune( propConfig.deserializeAndSanitize(value), ) if (deserialized === undefined) { throw new Error( `Invalid value ${userReadableTypeOfValue( value, )} for object.props${path .map((key) => `[${JSON.stringify(key)}]`) .join('')} is invalid`, ) } const propAddress = {...root.address, pathToProp: path} const trackId = get(sequenceTracksTree, path) as $FixMe as | SequenceTrackId | undefined if (typeof trackId === 'string') { const seq = root.sheet.getSequence() seq.position = seq.closestGridPosition(seq.position) _invokableStateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition( { ...propAddress, trackId, position: seq.position, value: value as $FixMe, snappingFunction: seq.closestGridPosition, type: 'bezier', }, ) } else { _invokableStateEditors.coreByProject.historic.sheetsById.staticOverrides.byObject.setValueOfPrimitiveProp( {...propAddress, value: value as $FixMe}, ) } } if (propConfig.type === 'compound') { const pathToTopPointer = getPointerParts( pointer as $IntentionalAny, ).path const lengthOfTopPointer = pathToTopPointer.length // If we are dealing with a compound prop, we recurse through its // nested properties. forEachDeepSimplePropOfCompoundProp( propConfig, pathToTopPointer, (primitivePropConfig, pathToProp) => { const pathToPropInProvidedValue = pathToProp.slice(lengthOfTopPointer) const v = getDeep(_value as {}, pathToPropInProvidedValue) if (typeof v !== 'undefined') { setStaticOrKeyframeProp(v, primitivePropConfig, pathToProp) } else { throw new Error( `Property object.props${pathToProp .map((key) => `[${JSON.stringify(key)}]`) .join('')} is required but not provided`, ) } }, ) } else if (propConfig.type === 'enum') { throw new Error(`Enums aren't implemented yet`) } else { setStaticOrKeyframeProp(_value, propConfig, path) } } else { throw new Error( 'Only setting props of SheetObject-s is supported in a transaction so far', ) } }, unset: (pointer) => { ensureRunning() const {root, path} = getPointerParts(pointer as Pointer<$FixMe>) if (isSheetObject(root)) { const sequenceTracksTree = root.template .getMapOfValidSequenceTracks_forStudio() .getValue() const defaultValue = getDeep( root.template.getDefaultValues().getValue(), path, ) const propConfig = getPropConfigByPath( root.template.staticConfig, path, ) as PropTypeConfig const unsetStaticOrKeyframeProp = (value: T, path: PathToProp) => { const propAddress = {...root.address, pathToProp: path} const trackId = get(sequenceTracksTree, path) as $FixMe as | SequenceTrackId | undefined if (typeof trackId === 'string') { _invokableStateEditors.coreByProject.historic.sheetsById.sequence.unsetKeyframeAtPosition( { ...propAddress, trackId, position: root.sheet.getSequence().positionSnappedToGrid, }, ) } else if (propConfig !== undefined) { _invokableStateEditors.coreByProject.historic.sheetsById.staticOverrides.byObject.unsetValueOfPrimitiveProp( propAddress, ) } } if (propConfig.type === 'compound') { forEachPropDeep( defaultValue, (v, pathToProp) => { unsetStaticOrKeyframeProp(v, pathToProp) }, getPointerParts(pointer).path, ) } else { unsetStaticOrKeyframeProp(defaultValue, path) } } else { throw new Error( 'Only setting props of SheetObject-s is supported in a transaction so far', ) } }, get stateEditors() { return _invokableStateEditors }, } } function proxyStateEditors( draft: StudioState, part: $IntentionalAny = stateEditors, ensureRunning: () => void, ): {} { return new Proxy(part, { get(_, prop) { ensureRunning() if (Object.hasOwn(part, prop)) { const v = part[prop as $IntentionalAny] if (typeof v === 'function') { return (opts: {}) => { ensureRunning() return v(draft, {}, opts) } } else if (typeof v === 'object' && v !== null) { return proxyStateEditors(draft, v, ensureRunning) } else { return v } } return undefined }, }) } ================================================ FILE: packages/studio/src/StudioStore/generateDiskStateRevision.ts ================================================ import {nanoid} from 'nanoid' export function generateDiskStateRevision() { return nanoid(16) } ================================================ FILE: packages/studio/src/SyncStore/AppLink.ts ================================================ import type {StudioTRPCRouter} from '@theatre/app/server/studio-api/root' import type {CreateTRPCProxyClient} from '@trpc/client' import { createTRPCProxyClient, loggerLink, unstable_httpBatchStreamLink, } from '@trpc/client' import superjson from 'superjson' export default class AppLink { private _client!: CreateTRPCProxyClient constructor(private _webAppUrl: string) { if (process.env.NODE_ENV === 'test') return this._client = createTRPCProxyClient({ links: [ loggerLink({ console: { log: (arg0, ...rest) => console.info('AppLink ' + arg0, ...rest), error: (arg0, ...rest) => console.error('AppLink ' + arg0, ...rest), }, enabled: (opts) => (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') || (opts.direction === 'down' && opts.result instanceof Error), }), unstable_httpBatchStreamLink({ url: _webAppUrl + '/api/studio-trpc', }), ], transformer: superjson, }) } get api() { return this._client } } ================================================ FILE: packages/studio/src/SyncStore/SyncServerLink.ts ================================================ import type {SyncServerRootRouter} from '@theatre/sync-server/trpc/routes' import superjson from 'superjson' import type {CreateTRPCProxyClient} from '@trpc/client' import { createTRPCProxyClient, createWSClient, loggerLink, wsLink, } from '@trpc/client' export default class SyncServerLink { private _client: CreateTRPCProxyClient constructor(private _url: string) { const wsClient = createWSClient({ url: _url, }) this._client = createTRPCProxyClient({ links: [ loggerLink({ console: { log: (arg0, ...rest) => console.info('SyncServerLink ' + arg0, ...rest), error: (arg0, ...rest) => console.error('SyncServerLink ' + arg0, ...rest), }, enabled: (opts) => (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') || (opts.direction === 'down' && opts.result instanceof Error), }), wsLink({client: wsClient}), ], transformer: superjson, }) if (process.env.NODE_ENV === 'development') { void this._client.healthcheck.query({}).then((res) => { console.log('syncServer/healthCheck', res) }) } } get api(): CreateTRPCProxyClient { return this._client } } ================================================ FILE: packages/studio/src/SyncStore/enhancedTrpcWsClient.ts ================================================ export default function enhancedTrpcWsClient< Client extends {}, GetOpts extends () => any, >( clinet: Client, getOpts: GetOpts, ): EnhancedTrpcWsClient> { const handlers = { get: (target: any, prop: string): any => { console.log(`prop`, prop) if (prop === 'query' || prop === 'mutation' || prop === 'subscription') { return proxyProcedure(target, prop) } else if (target[prop] && typeof target[prop] === 'object') { return new Proxy(target[prop], handlers) } else { return target[prop] } }, } const proxyProcedure = (target: any, prop: string) => { return new Proxy(target[prop], { apply: (_target: any, thisArg: any, argArray: any) => { const [originalOpts, ...restArgs] = argArray const opts = {...getOpts(), ...originalOpts} console.log(`prx`, target) return target[prop](opts, ...restArgs) }, ...handlers, }) } const pr = new Proxy(clinet, handlers) return pr } export type EnhancedTrpcWsClient = { [K in keyof Client]: K extends 'query' | 'mutation' | 'subscription' ? EnhancedProcedure : Client[K] extends {} ? EnhancedTrpcWsClient : Client[K] } type EnhancedProcedure< Procedure extends any, EnhancedOpts extends {}, > = Procedure extends ( opts: infer OriginalOpts, ...rest: infer Rest ) => infer Ret ? (opts: Omit, ...rest: Rest) => Ret : Procedure ================================================ FILE: packages/studio/src/SyncStore/utils.ts ================================================ import {get} from 'lodash-es' import type {$IntentionalAny} from '@theatre/core/types/public' type AnyFn = (...args: any[]) => any export function wrapTrpcClientWithAuth( clientPromise: Promise, enhancer: (originalFn: AnyFn, args: any[], path: string[]) => any, ): TrpcClientWrapped { const handlers = { get: (target: PathedTarget, prop: string): any => { const subTarget: PathedTarget = (() => {}) as $IntentionalAny subTarget.path = [...target.path, prop] if (prop === 'query' || prop === 'mutate' || prop === 'subscribe') { return proxyProcedure(subTarget, prop) } else { return new Proxy(subTarget, handlers) } }, } const proxyProcedure = (target: PathedTarget, prop: string) => { return new Proxy(target, { apply: async (_target: PathedTarget, thisArg: any, argArray: any) => { const client = await clientPromise const fn = get(client, target.path) return await enhancer(fn, argArray, target.path) }, ...handlers, }) } type PathedTarget = {path: string[]} & (() => {}) const rootTarget: PathedTarget = (() => {}) as $IntentionalAny rootTarget.path = [] const pr = new Proxy(rootTarget, handlers) return pr as $IntentionalAny } export type TrpcClientWrapped = { [K in keyof Client]: K extends 'query' | 'mutate' | 'subscribe' ? TrpcProcedureWrapped : Client[K] extends {} ? TrpcClientWrapped : Client[K] } type TrpcProcedureWrapped = Procedure extends ( input: infer OriginalInput, ) => infer Ret ? (input: Omit) => Ret : Procedure extends ( input: infer OriginalInput, secondArg: infer SecondArg, ) => infer Ret ? (input: Omit, secondArg: SecondArg) => Ret : Procedure ================================================ FILE: packages/studio/src/TheatreStudio.ts ================================================ import type {ISheet, ISheetObject} from '@theatre/core' import type {Prism, Pointer} from '@theatre/dataverse' import {getPointerParts, prism} from '@theatre/dataverse' import SimpleCache from '@theatre/utils/SimpleCache' import type {$IntentionalAny, VoidFn} from '@theatre/core/types/public' import type {IScrub} from '@theatre/core/types/public' import type {Studio} from '@theatre/studio/Studio' import {outlineSelection} from './selectors' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import getStudio from './getStudio' import {debounce, uniq} from 'lodash-es' import type Sheet from '@theatre/core/sheets/Sheet' import type { IExtension, IStudio, ITransactionAPI, PaneInstance, ProjectId, } from '@theatre/core/types/public' import { __experimental_disablePlayPauseKeyboardShortcut, __experimental_enablePlayPauseKeyboardShortcut, } from './UIRoot/useKeyboardShortcuts' import type TheatreSheetObject from '@theatre/core/sheetObjects/TheatreSheetObject' import type TheatreSheet from '@theatre/core/sheets/TheatreSheet' import type {__UNSTABLE_Project_OnDiskState} from '@theatre/core' import type {OutlineSelectionState} from '@theatre/core/types/private/studio' import type Project from '@theatre/core/projects/Project' import type SheetTemplate from '@theatre/core/sheets/SheetTemplate' import type SheetObjectTemplate from '@theatre/core/sheetObjects/SheetObjectTemplate' import type {PaneInstanceId} from '@theatre/core/types/public' import {__private} from '@theatre/core' const { isSheet, isProject, isSheetPublicAPI, isSheetObject, isSheetObjectPublicAPI, isSheetObjectTemplate, isSheetTemplate, } = __private.instanceTypes export default class TheatreStudio implements IStudio { readonly ui = { hide() { getStudio().ui.hide() }, get isHidden(): boolean { return getStudio().ui.isHidden }, restore() { getStudio().ui.restore() }, renderToolset(toolsetId: string, htmlNode: HTMLElement) { return getStudio().ui.renderToolset(toolsetId, htmlNode) }, } private readonly _cache = new SimpleCache() __experimental = { __experimental_disablePlayPauseKeyboardShortcut(): void { // This is an experimental API to respond to this issue: https://discord.com/channels/870988717190426644/870988717190426647/1067906775602430062 // Ideally we need a coherent way for the user to control keyboard inputs, so we will remove this method in the future. // Here is the procedure for removing it: // 1. Replace this code with a `throw new Error("This is experimental method is now deprecated, and here is how to migrate: ...")` // 2. Then keep it for a few months, and then remove it. __experimental_disablePlayPauseKeyboardShortcut() }, __experimental_enablePlayPauseKeyboardShortcut(): void { // see __experimental_disablePlayPauseKeyboardShortcut() __experimental_enablePlayPauseKeyboardShortcut() }, __experimental_clearPersistentStorage(persistenceKey?: string): void { return getStudio().clearPersistentStorage(persistenceKey) }, __experimental_createContentOfSaveFileTyped( projectId: string, ): __UNSTABLE_Project_OnDiskState { return getStudio().createContentOfSaveFile(projectId) as $IntentionalAny }, } /** * @internal */ constructor(internals: Studio) {} extend( extension: IExtension, opts?: {__experimental_reconfigure?: boolean}, ): void { getStudio().extend(extension, opts) } transaction(fn: (api: ITransactionAPI) => void) { getStudio().transaction(({set, unset, stateEditors}) => { const __experimental_forgetObject = (object: TheatreSheetObject) => { if (!isSheetObjectPublicAPI(object)) { throw new Error( `object in transactionApi.__experimental_forgetObject(object) must be the return type of sheet.object(...)`, ) } stateEditors.coreByProject.historic.sheetsById.forgetObject( object.address, ) } const __experimental_forgetSheet = (sheet: TheatreSheet) => { if (!isSheetPublicAPI(sheet)) { throw new Error( `sheet in transactionApi.__experimental_forgetSheet(sheet) must be the return type of project.sheet()`, ) } stateEditors.coreByProject.historic.sheetsById.forgetSheet( sheet.address, ) } const __experimental_sequenceProp = (prop: Pointer) => { const {path, root} = getPointerParts(prop) if (!isSheetObject(root)) { throw new Error( 'Argument prop must be a pointer to a SheetObject property', ) } const propAdress = {...root.address, pathToProp: path} stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced( propAdress, ) } return fn({ set, unset, __experimental_forgetObject, __experimental_forgetSheet, __experimental_sequenceProp, }) }) } private _getSelectionPrism(): Prism<(ISheetObject | ISheet)[]> { return this._cache.get('_getSelectionPrism()', () => prism((): (ISheetObject | ISheet)[] => { return outlineSelection .getValue() .filter( (s): s is SheetObject | Sheet => s.type === 'Theatre_SheetObject' || s.type === 'Theatre_Sheet', ) .map((s) => s.publicApi) }), ) } private _getSelection(): (ISheetObject | ISheet)[] { return this._getSelectionPrism().getValue() } setSelection(selection: Array): void { const sanitizedSelection: Array< Project | Sheet | SheetObject | SheetTemplate | SheetObjectTemplate > = [...selection] .filter((s) => isSheetObjectPublicAPI(s) || isSheetPublicAPI(s)) .map((s) => getStudio().corePrivateAPI!(s)) getStudio().transaction(({stateEditors}) => { const newSelectionState: OutlineSelectionState[] = [] for (const item of uniq(sanitizedSelection)) { if (isProject(item)) { newSelectionState.push({type: 'Project', ...item.address}) } else if (isSheet(item)) { newSelectionState.push({ type: 'Sheet', ...item.template.address, }) stateEditors.studio.historic.projects.stateByProjectId.stateBySheetId.setSelectedInstanceId( item.address, ) } else if (isSheetTemplate(item)) { newSelectionState.push({type: 'Sheet', ...item.address}) } else if (isSheetObject(item)) { newSelectionState.push({ type: 'SheetObject', ...item.template.address, }) stateEditors.studio.historic.projects.stateByProjectId.stateBySheetId.setSelectedInstanceId( item.sheet.address, ) } else if (isSheetObjectTemplate(item)) { newSelectionState.push({type: 'SheetObject', ...item.address}) } } stateEditors.studio.historic.panels.outline.selection.set( newSelectionState, ) }) } onSelectionChange(fn: (s: (ISheetObject | ISheet)[]) => void): VoidFn { const studio = getStudio() return this._getSelectionPrism().onChange(studio.ticker, fn, true) } get selection(): Array { return this._getSelection() } scrub(): IScrub { return getStudio().scrub() } getStudioProject() { const core = getStudio().core if (!core) { throw new Error(`You're calling studio.getStudioProject() before \`@theatre/core\` is loaded. To fix this: 1. Check if \`@theatre/core\` is import/required in your bundle. 2. Check the stack trace of this error and make sure the funciton that calls getStudioProject() is run after \`@theatre/core\` is loaded.`) } return getStudio().getStudioProject(core) } debouncedScrub(threshold: number = 1000): Pick { let currentScrub: IScrub | undefined const scheduleCommit = debounce(() => { const s = currentScrub if (!s) return currentScrub = undefined s.commit() }, threshold) const capture = (arg: $IntentionalAny) => { if (!currentScrub) { currentScrub = this.scrub() } let errored = true try { currentScrub.capture(arg) errored = false } finally { if (errored) { const s = currentScrub currentScrub = undefined s.discard() } else { scheduleCommit() } } } return {capture} } createPane( paneClass: PaneClass, ): PaneInstance { return getStudio().paneManager.createPane(paneClass) } destroyPane(paneId: string): void { return getStudio().paneManager.destroyPane(paneId as PaneInstanceId) } createContentOfSaveFile(projectId: string): Record { return getStudio().createContentOfSaveFile( projectId as ProjectId, ) as $IntentionalAny } } ================================================ FILE: packages/studio/src/UI/UI.ts ================================================ import type {Studio} from '@theatre/studio/Studio' import {val} from '@theatre/dataverse' const NonSSRBitsClass = typeof window !== 'undefined' ? import('./UINonSSRBits').then((M) => M.default) : null export default class UI { private _rendered = false private _nonSSRBits = NonSSRBitsClass ? NonSSRBitsClass.then((NonSSRBitsClass) => new NonSSRBitsClass()) : Promise.reject() readonly ready: Promise = this._nonSSRBits.then( () => undefined, () => undefined, ) constructor(readonly studio: Studio) {} render() { if (this._rendered) { return } this._rendered = true this._nonSSRBits .then((b) => { b.render() }) .catch((err) => { console.error(err) throw err }) } hide() { this.studio.transaction(({stateEditors}) => { stateEditors.studio.ahistoric.setVisibilityState('everythingIsHidden') }) } restore() { this.render() this.studio.transaction(({stateEditors}) => { stateEditors.studio.ahistoric.setVisibilityState('everythingIsVisible') }) } get isHidden() { return ( val(this.studio.atomP.ahistoric.visibilityState) === 'everythingIsHidden' ) } renderToolset(toolsetId: string, htmlNode: HTMLElement) { let shouldUnmount = false let unmount: null | (() => void) = null this._nonSSRBits .then((nonSSRBits) => { if (shouldUnmount) return // unmount requested before the toolset is mounted, so, abort unmount = nonSSRBits.renderToolset(toolsetId, htmlNode) }) .catch((err) => { console.error(err) }) return () => { if (unmount) { unmount() return } if (shouldUnmount) return shouldUnmount = true } } } ================================================ FILE: packages/studio/src/UI/UINonSSRBits.ts ================================================ import UIRoot from '@theatre/studio/UIRoot/UIRoot' import type {$IntentionalAny} from '@theatre/core/types/public' import React from 'react' import ReactDOM from 'react-dom/client' import {getMounter} from '@theatre/studio/utils/renderInPortalInContext' import {withStyledShadow} from '@theatre/studio/css' import ExtensionToolbar from '@theatre/studio/toolbars/ExtensionToolbar/ExtensionToolbar' export default class UINonSSRBits { readonly containerEl = document.createElement('div') private _renderTimeout: NodeJS.Timer | undefined = undefined private _documentBodyUIIsRenderedIn: HTMLElement | undefined = undefined readonly containerShadow: ShadowRoot & HTMLElement constructor() { // @todo we can't bootstrap Theatre.js (as in, to design Theatre.js using theatre), if we rely on IDed elements this.containerEl.id = 'theatrejs-studio-root' this.containerEl.style.cssText = ` position: fixed; top: 0; right: 0; bottom: 0; left: 0; pointer-events: none; z-index: 100; ` const createShadowRoot = (): ShadowRoot & HTMLElement => { if (window.__IS_VISUAL_REGRESSION_TESTING === true) { const fauxRoot = document.createElement('div') fauxRoot.id = 'theatrejs-faux-shadow-root' document.body.appendChild(fauxRoot) return fauxRoot as $IntentionalAny } else { return this.containerEl.attachShadow({ mode: 'open', // To see why I had to cast this value to HTMLElement, take a look at its // references of this prop. There are a few functions that actually work // with a ShadowRoot but are typed to accept HTMLElement }) as $IntentionalAny } } this.containerShadow = createShadowRoot() } render() { const renderCallback = () => { if (!document.body) { this._renderTimeout = setTimeout(renderCallback, 5) return } this._renderTimeout = undefined this._documentBodyUIIsRenderedIn = document.body this._documentBodyUIIsRenderedIn.appendChild(this.containerEl) ReactDOM.createRoot(this.containerShadow).render( React.createElement(UIRoot, {containerShadow: this.containerShadow}), ) } this._renderTimeout = setTimeout(renderCallback, 10) } renderToolset(toolsetId: string, htmlNode: HTMLElement) { const s = getMounter() s.mountOrRender( withStyledShadow(ExtensionToolbar), {toolbarId: toolsetId}, htmlNode, ) return s.unmount } } ================================================ FILE: packages/studio/src/UIRoot/PanelsRoot.tsx ================================================ import OutlinePanel from '@theatre/studio/panels/OutlinePanel/OutlinePanel' import DetailPanel from '@theatre/studio/panels/DetailPanel/DetailPanel' import React from 'react' import getStudio from '@theatre/studio/getStudio' import {useVal} from '@theatre/react' import ExtensionPaneWrapper from '@theatre/studio/panels/BasePanel/ExtensionPaneWrapper' import SequenceEditorPanel from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel' const PanelsRoot: React.FC = () => { const panes = useVal(getStudio().paneManager.allPanesD) const paneEls = Object.entries(panes).map(([instanceId, paneInstance]) => { return ( ) }) return ( <> {paneEls} ) } export default PanelsRoot ================================================ FILE: packages/studio/src/UIRoot/PointerCapturing.tsx ================================================ import {useEffect, useMemo} from 'react' /** See {@link PointerCapturing} */ export type CapturedPointer = { release(): void /** Double check that you still have the current capture and weren't forcibly released */ isCapturing(): boolean } /** * Introduced `PointerCapturing` for addressing issues with over-shooting easing curves closing the popup preset modal. * * Goal is to be able to determine if the pointer is being captured somewhere in studio (e.g. dragging). * * Some other ideas we considered before going with the PointerCapturing provider and context * - provider: `onPointerCaptureChanged` * - `onDragging={isMouseActive = true}` / `onMouseActive={isMouseActive = true}` * - dragging tracked application wide (ephemeral state) in popover * * Caveats: I wonder if there's a shared abstraction we should use for "releasing" e.g. unsubscribe / untap in rxjs / tapable patterns. */ export type PointerCapturing = { isPointerBeingCaptured(): boolean capturePointer(debugReason: string): CapturedPointer } type CaptureInfo = { debugOwnerName: string debugReason: string } let currentCapture: null | CaptureInfo = null const isPointerBeingCaptured = () => currentCapture != null /** * @deprecated Once all the `usePopover()`/`useDrag()` calls are removed, we should move this to one of the actors under `useChordial()` */ export function createPointerCapturing(forDebugName: string) { /** keep track of the captures being made by this user of {@link usePointerCapturing} */ let localCapture: CaptureInfo | null const updateCapture = (to: CaptureInfo | null): CaptureInfo | null => { localCapture = to currentCapture = to return to } const capturing: PointerCapturing = { capturePointer(reason) { if (currentCapture != null) { throw new Error( `"${forDebugName}" attempted capturing pointer for "${reason}" while already captured by "${currentCapture.debugOwnerName}" for "${currentCapture.debugReason}"`, ) } const releaseCapture = updateCapture({ debugOwnerName: forDebugName, debugReason: reason, }) return { isCapturing() { return releaseCapture === currentCapture }, release() { if (releaseCapture === currentCapture) { updateCapture(null) return true } return false }, } }, isPointerBeingCaptured, } return { capturing, forceRelease() { if (localCapture && currentCapture === localCapture) { updateCapture(null) } }, } } /** * @deprecated Once all the `usePopover()`/`useDrag()` calls are removed, we should move this to one of the actors under `useChordial()` * Used to ensure we're locking drag and pointer events to a single place in the UI logic. * Without this, we can much more easily accidentally create multiple drag handlers on * child / parent dom elements which both `useDrag`, for example. * * An example of this helping us was when we first started building the Curve editor popover. * In that activity, we were experiencing a weird issue where the popover would unmount while * dragging away from the popover, and the drag end listener would not be called. * By having "Pointer Capturing" we're able to identify that the pointer was not being properly * released, because there would be a lock contention when trying to drag something else. */ export function usePointerCapturing(forDebugName: string): PointerCapturing { const control = useMemo(() => { return createPointerCapturing(forDebugName) }, [forDebugName, createPointerCapturing]) useEffect(() => { return () => { // force release on unmount control.forceRelease() } }, [control]) return control.capturing } ================================================ FILE: packages/studio/src/UIRoot/ProvideTheme.tsx ================================================ import React from 'react' import styled from 'styled-components' const Container = styled.div` --colors-panel-1: red; ` const ProvideTheme: React.FC<{children?: React.ReactNode}> = (props) => { return {props.children} } export default ProvideTheme ================================================ FILE: packages/studio/src/UIRoot/UIRoot.tsx ================================================ import getStudio from '@theatre/studio/getStudio' import {usePrism, useVal} from '@theatre/react' import {val} from '@theatre/dataverse' import React, {useEffect} from 'react' import styled, {createGlobalStyle} from 'styled-components' import PanelsRoot from './PanelsRoot' import GlobalToolbar from '@theatre/studio/toolbars/GlobalToolbar/GlobalToolbar' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {PortalContext} from 'reakit' import type {$IntentionalAny} from '@theatre/core/types/public' import useKeyboardShortcuts from './useKeyboardShortcuts' import PointerEventsHandler from '@theatre/studio/uiComponents/PointerEventsHandler' import {MountAll} from '@theatre/studio/utils/renderInPortalInContext' import {PortalLayer, ProvideStyles} from '@theatre/studio/css' import { createTheatreInternalLogger, TheatreLoggerLevel, } from '@theatre/utils/logger' import {ProvideLogger} from '@theatre/studio/uiComponents/useLogger' import {Notifier} from '@theatre/studio/notify' import {useChordialCaptureEvents} from '@theatre/studio/uiComponents/chordial/useChodrial' import {ChordialOverlay} from '@theatre/studio/uiComponents/chordial/ChordialOverlay' const MakeRootHostContainStatic = typeof window !== 'undefined' ? createGlobalStyle` :host { contain: strict; } ` : ({} as ReturnType) const Container = styled(PointerEventsHandler)` z-index: 50; position: fixed; inset: 0; &.invisible { pointer-events: none !important; opacity: 0; transform: translateX(1000000px); } ` const INTERNAL_LOGGING = /Playground.+Theatre\.js/.test( (typeof document !== 'undefined' ? document?.title : null) ?? '', ) export default function UIRoot(props: { containerShadow: ShadowRoot & HTMLElement }) { const studio = getStudio() const [portalLayerRef, portalLayer] = useRefAndState( undefined as $IntentionalAny, ) const uiRootLogger = createTheatreInternalLogger() uiRootLogger.configureLogging({ min: TheatreLoggerLevel.DEBUG, dev: INTERNAL_LOGGING, internal: INTERNAL_LOGGING, }) const logger = uiRootLogger.getLogger().named('Theatre.js UIRoot') useKeyboardShortcuts() const visiblityState = useVal(studio.atomP.ahistoric.visibilityState) useEffect(() => { if (visiblityState === 'everythingIsHidden') { console.warn( `Theatre.js Studio is hidden. Use the keyboard shortcut 'alt + \\' to restore the studio, or call studio.ui.restore().`, ) } return () => {} }, [visiblityState]) const chordialRootRef = useChordialCaptureEvents() const inside = usePrism(() => { const visiblityState = val(studio.atomP.ahistoric.visibilityState) const initialised = val(studio.initializedP) return !initialised ? null : ( <> ) }, [studio, portalLayerRef, portalLayer]) return inside } const MountExtensionComponents: React.FC<{}> = () => { return } ================================================ FILE: packages/studio/src/UIRoot/useKeyboardShortcuts.ts ================================================ import {useEffect} from 'react' import getStudio from '@theatre/studio/getStudio' import {cmdIsDown} from '@theatre/utils/keyboardUtils' import {getSelectedSequence} from '@theatre/studio/selectors' import type {$IntentionalAny} from '@theatre/core/types/public' import type {Prism} from '@theatre/dataverse' import {Atom, prism, val} from '@theatre/dataverse' import type Sequence from '@theatre/core/sequences/Sequence' import memoizeFn from '@theatre/utils/memoizeFn' import type {IPlaybackRange} from '@theatre/core/types/public' let playPauseKeyboardShortcutIsEnabled = true export function __experimental_disablePlayPauseKeyboardShortcut() { playPauseKeyboardShortcutIsEnabled = false } export function __experimental_enablePlayPauseKeyboardShortcut() { playPauseKeyboardShortcutIsEnabled = true } export default function useKeyboardShortcuts() { const studio = getStudio() useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { const target: null | HTMLElement = e.composedPath()[0] as unknown as $IntentionalAny if ( target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') ) { return } if (e.key === 'z' || e.key === 'Z' || e.code === 'KeyZ') { if (cmdIsDown(e)) { if (e.shiftKey === true) { studio.redo() } else { studio.undo() } } else { return } } else if ( e.code === 'Space' && !e.shiftKey && !e.metaKey && !e.altKey && !e.ctrlKey ) { if (!playPauseKeyboardShortcutIsEnabled) return // Control the playback using the `Space` key const seq = getSelectedSequence() if (seq) { if (seq.playing) { seq.pause() } else { /* * The sequence will be played in its whole length unless all of the * following conditions are met: * 1. the focus range is set and enabled * 2. the playback starts within the focus range. */ const {projectId, sheetId} = seq.address /* * The value of this prism is an array that contains the * range of the playback (start and end), and a boolean that is * `true` if the playback should be played within that range. */ const controlledPlaybackStateD = prism( (): {range: IPlaybackRange; isFollowingARange: boolean} => { const focusRange = val( getStudio().atomP.ahistoric.projects.stateByProjectId[ projectId ].stateBySheetId[sheetId].sequence.focusRange, ) // Determines whether the playback should be played // within the focus range. const shouldFollowFocusRange = prism.memo( 'shouldFollowFocusRange', (): boolean => { const posBeforePlay = seq.position if (focusRange) { const withinRange = posBeforePlay >= focusRange.range[0] && posBeforePlay <= focusRange.range[1] if (focusRange.enabled) { if (withinRange) { return true } else { return false } } else { return true } } else { return true } }, [], ) if ( shouldFollowFocusRange && focusRange && focusRange.enabled ) { return { range: [focusRange.range[0], focusRange.range[1]], isFollowingARange: true, } } else { const sequenceLength = val(seq.pointer.length) return {range: [0, sequenceLength], isFollowingARange: false} } }, ) const playbackPromise = seq.playDynamicRange( prism(() => val(controlledPlaybackStateD).range), getStudio().ticker, ) const playbackStateBox = getPlaybackStateBox(seq) void playbackPromise.finally(() => { playbackStateBox.set(undefined) }) playbackStateBox.set(controlledPlaybackStateD) } } else { return } } // alt + \ else if ( e.altKey && (e.key === '\\' || e.code === 'Backslash' || e.code === 'IntlBackslash') ) { const prev = val(studio.atomP.ahistoric.visibilityState) studio.transaction(({stateEditors}) => { stateEditors.studio.ahistoric.setVisibilityState( prev === 'everythingIsHidden' ? 'everythingIsVisible' : 'everythingIsHidden', ) }) } else { return } e.preventDefault() e.stopPropagation() } window.addEventListener('keydown', handleKeyDown) return () => { window.removeEventListener('keydown', handleKeyDown) } }, []) } type ControlledPlaybackStateBox = Atom< undefined | Prism<{range: IPlaybackRange; isFollowingARange: boolean}> > const getPlaybackStateBox = memoizeFn( (sequence: Sequence): ControlledPlaybackStateBox => { const box = new Atom(undefined) as ControlledPlaybackStateBox return box }, ) /* * A memoized function that returns a prism with a boolean value. * This value is set to `true` if: * 1. the playback is playing and using the focus range instead of the whole sequence * 2. the playback is stopped, but would use the focus range if it were started. */ export const getIsPlayheadAttachedToFocusRange = memoizeFn( (sequence: Sequence) => prism(() => { const controlledPlaybackState = getPlaybackStateBox(sequence).prism.getValue() if (controlledPlaybackState) { return controlledPlaybackState.getValue().isFollowingARange } else { const {projectId, sheetId} = sequence.address const focusRange = val( getStudio().atomP.ahistoric.projects.stateByProjectId[projectId] .stateBySheetId[sheetId].sequence.focusRange, ) if (!focusRange || !focusRange.enabled) return false const pos = val(sequence.pointer.position) const withinRange = pos >= focusRange.range[0] && pos <= focusRange.range[1] return withinRange } }), ) ================================================ FILE: packages/studio/src/checkForUpdates.ts ================================================ import {pointerToPrism, val} from '@theatre/dataverse' import {defer} from '@theatre/utils/defer' import type {$IntentionalAny} from '@theatre/core/types/public' import getStudio from './getStudio' import type {UpdateCheckerResponse} from './Studio' import {env} from './env' const UPDATE_CHECK_INTERVAL = 30 * 60 * 1000 // check for updates every 30 minutes const TIME_TO_WAIT_ON_ERROR = 1000 * 60 * 60 // an hour /** * Returns a promise that will resolve when the UI is visible. If the UI is hidden, it'll * wait for it to become visible. */ async function waitTilUIIsVisible(): Promise { const visibilityStatePt = getStudio().atomP.ahistoric.visibilityState if (val(visibilityStatePt) === 'everythingIsVisible') return const deferred = defer() const unsub = pointerToPrism(visibilityStatePt).onStale(() => { const newVal = val(visibilityStatePt) if (newVal === 'everythingIsVisible') { unsub() deferred.resolve(undefined) } }) return deferred.promise } export default async function checkForUpdates() { if (env.BUILT_FOR_PLAYGROUND === 'true') { // Build for playground. Skipping update check return } if (env.THEATRE_VERSION?.match(/COMPAT/)) { // Built for compat tests. Skipping update check return } // let's wait a bit in case the user has called for the UI to be hidden. await wait(500) await waitTilUIIsVisible() while (true) { const state = val(getStudio().ahistoricAtom.pointer.updateChecker) if (state) { if (state.result !== 'error') { const lastChecked = state.lastChecked const now = Date.now() const timeElapsedSinceLastCheckedForUpdate = Math.abs(now - lastChecked) // doing Math.max in case the clock has shifted if (timeElapsedSinceLastCheckedForUpdate < UPDATE_CHECK_INTERVAL) { await wait( UPDATE_CHECK_INTERVAL - timeElapsedSinceLastCheckedForUpdate, ) } } } try { const response = await fetch( new Request( `https://updates.theatrejs.com/updates/${env.THEATRE_VERSION}`, ), ) if (response.ok) { const json = await response.json() if (!isValidUpdateCheckerResponse(json)) { throw new Error(`Bad response`) } getStudio().ahistoricAtom.setByPointer((p) => p.updateChecker, { lastChecked: Date.now(), result: {...json}, }) await wait(1000) } else { throw new Error(`HTTP Error ${response.statusText}`) } } catch (error) { // TODO log an error here await wait(TIME_TO_WAIT_ON_ERROR) } } } const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) function isValidUpdateCheckerResponse( json: unknown, ): json is UpdateCheckerResponse { if (typeof json !== 'object') return false const obj = json as $IntentionalAny if (typeof obj['hasUpdates'] !== 'boolean') return false // could use a runtime type checker but not important yet return ( (obj.hasUpdates === true && typeof obj.newVersion === 'string' && typeof obj.releasePage === 'string') || obj.hasUpdates === false ) } ================================================ FILE: packages/studio/src/css.tsx ================================================ import {lighten} from 'polished' import {css} from 'styled-components' import styled, {createGlobalStyle, StyleSheetManager} from 'styled-components' import React, {useLayoutEffect, useState} from 'react' import ReactDOM from 'react-dom' import type {$IntentionalAny} from '@theatre/core/types/public' import {PortalContext} from 'reakit' import useRefAndState from './utils/useRefAndState' /** * This CSS string is used to correctly set pointer-events on an element * when the pointer is dragging something. * Naming explanation: "NormalMode" as opposed to dragging mode. * * @see PointerEventsHandler - the place that sets `.normal` on #pointer-root */ export const pointerEventsAutoInNormalMode = css` #pointer-root & { pointer-events: none; } #pointer-root.normal & { pointer-events: auto; } ` export const theme = { panel: { bg: `#282b2f`, head: { title: { color: `#bbb`, }, punctuation: { color: `#808080`, }, }, body: { compoudThing: { label: { get color(): string { return lighten(0.6, theme.panel.bg) }, }, }, }, }, } export const panelUtils = { panelBorder: `2px solid #1f1f1f`, } const GlobalStyle = typeof window !== 'undefined' ? createGlobalStyle` :host { all: initial; color: white; font: 11px -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe Editor, HelveticaNeue-Light, Ubuntu, Droid Sans, sans-serif; // external links a[href^='http'] { text-decoration: none; text-decoration-line: underline; text-decoration-color: #888; position: relative; display: inline-block; margin-left: 0.4em; &:hover, &:active { text-decoration-color: #ccc; } } // from tailwind .text-xs { font-size: 0.75rem; /* 12px */ line-height: 1rem; /* 16px */ } .text-sm { font-size: 0.875rem; /* 14px */ line-height: 1.25rem; /* 20px */ } .text-base { font-size: 1rem; /* 16px */ line-height: 1.5rem; /* 24px */ } .text-lg { font-size: 1.125rem; /* 18px */ line-height: 1.75rem; /* 28px */ } .text-xl { font-size: 1.25rem; /* 20px */ line-height: 1.75rem; /* 28px */ } .text-2xl { font-size: 1.5rem; /* 24px */ line-height: 2rem; /* 32px */ } .text-3xl { font-size: 1.875rem; /* 30px */ line-height: 2.25rem; /* 36px */ } .text-4xl { font-size: 2.25rem; /* 36px */ line-height: 2.5rem; /* 40px */ } .text-5xl { font-size: 3rem; /* 48px */ line-height: 1; } .text-6xl { font-size: 3.75rem; /* 60px */ line-height: 1; } .text-7xl { font-size: 4.5rem; /* 72px */ line-height: 1; } .text-8xl { font-size: 6rem; /* 96px */ line-height: 1; } .text-9xl { font-size: 8rem; /* 128px */ line-height: 1; } .font-thin { font-weight: 100; } .font-extralight { font-weight: 200; } .font-light { font-weight: 300; } .font-normal { font-weight: 400; } .font-medium { font-weight: 500; } .font-semibold { font-weight: 600; } .font-bold { font-weight: 700; } .font-extrabold { font-weight: 800; } .font-black { font-weight: 900; } .text-left { text-align: left; } .text-center { text-align: center; } .text-right { text-align: right; } .text-color-pale { color: #CCC; } } * { padding: 0; margin: 0; font-size: 100%; font: inherit; vertical-align: baseline; list-style: none; } ` : ({} as ReturnType) export const PortalLayer = styled.div` z-index: 51; position: fixed; top: 0px; right: 0px; bottom: 0px; left: 0px; pointer-events: none; ` export const ProvideStyles: React.FC<{ target: undefined | HTMLElement children: React.ReactNode }> = (props) => { return ( <> {props.children} ) } export function withStyledShadow( Comp: React.ComponentType, ): React.ComponentType { return (props) => ( ) } const ProvideStyledShadow: React.FC<{ children: React.ReactNode }> = (props) => { const [template, ref] = useState(null) const [shadowRoot, setShadowRoot] = useState(null) useLayoutEffect(() => { if (!template) return const {parentNode} = template if (!parentNode) return const hadShadowRoot = // @ts-ignore !!parentNode.shadowRoot const shadowRoot = hadShadowRoot ? // @ts-ignore parent.shadowRoot : (parentNode as HTMLElement).attachShadow({ mode: 'open', }) setShadowRoot(shadowRoot) // no need to cleanup. The parent will be removed anyway if cleanup // is needed. }, [template]) const [portalLayerRef, portalLayer] = useRefAndState( undefined as $IntentionalAny, ) if (!shadowRoot) { return ( ) } return ReactDOM.createPortal( <> {props.children} , shadowRoot as $IntentionalAny as HTMLElement, ) } ================================================ FILE: packages/studio/src/env.ts ================================================ import type {Env} from '@theatre/core/envSchema' import type {$IntentionalAny} from '@theatre/utils/types' // process.env is guaranteed to be of type Env, because we validate it in `devEnv/cli` export const env = process.env as $IntentionalAny as Env ================================================ FILE: packages/studio/src/getStudio.ts ================================================ import type {Studio} from './Studio' let studio: Studio export function setStudio(s: Studio) { studio = s } /** * This may only be called from modules inside the studio bundle. */ export default function getStudio(): Studio { return studio } ================================================ FILE: packages/studio/src/index.ts ================================================ /** * The library providing the editor components of Theatre.js. * * @packageDocumentation */ import {setStudio} from '@theatre/studio/getStudio' import {Studio} from '@theatre/studio/Studio' import type {GlobalVariableNames} from '@theatre/core/globals' import type {$FixMe} from '@theatre/core/types/public' import StudioBundle from './StudioBundle' import type CoreBundle from '@theatre/core/CoreBundle' import type {IStudio} from '@theatre/core/types/public' const globalVariableNames: GlobalVariableNames = { StudioBundle: '__TheatreJS_StudioBundle', coreBundle: '__TheatreJS_CoreBundle', notifications: '__TheatreJS_Notifications', } const studioPrivateAPI = new Studio() setStudio(studioPrivateAPI) /** * The main instance of Studio. Read more at {@link IStudio} */ const studio: IStudio = studioPrivateAPI.publicApi export {} registerStudioBundle() function registerStudioBundle() { if ( typeof window == 'undefined' && global.__THEATREJS__FORCE_CONNECT_CORE_AND_STUDIO !== true ) return const globalContext = typeof window !== 'undefined' ? window : global const existingStudioBundle = (globalContext as $FixMe)[ globalVariableNames.StudioBundle ] if (typeof existingStudioBundle !== 'undefined') { if ( typeof existingStudioBundle === 'object' && existingStudioBundle && typeof existingStudioBundle.version === 'string' ) { throw new Error( `It seems that the module '@theatre/studio' is loaded more than once. This could have two possible causes:\n` + `1. You might have two separate versions of Theatre.js in node_modules.\n` + `2. Or this might be a bundling misconfiguration, in case you're using a bundler like Webpack/ESBuild/Rollup.\n\n` + `Note that it **is okay** to import '@theatre/studio' multiple times. But those imports should point to the same module.`, ) } else { throw new Error( `The variable window.${globalVariableNames.StudioBundle} seems to be already set by a module other than @theatre/core.`, ) } } const studioBundle = new StudioBundle(studioPrivateAPI) // @ts-ignore ignore globalContext[globalVariableNames.StudioBundle] = studioBundle const possibleCoreBundle: undefined | CoreBundle = // @ts-ignore ignore globalContext[globalVariableNames.coreBundle] if ( possibleCoreBundle && possibleCoreBundle !== null && possibleCoreBundle.type === 'Theatre_CoreBundle' ) { studioBundle.registerCoreBundle(possibleCoreBundle) } } import {notify} from '@theatre/studio/notify' if (typeof window !== 'undefined') { // @ts-ignore window[globalVariableNames.notifications] = { notify, } } ================================================ FILE: packages/studio/src/integration-tests/Sequence.test.ts ================================================ import {setupTestSheet} from '@theatre/studio/integration-tests/testUtils' import {encodePathToProp} from '@theatre/utils/pathToProp' import type { ObjectAddressKey, SequenceTrackId, } from '@theatre/core/types/public' import {__private} from '@theatre/core' const {keyframeUtils} = __private const {asKeyframeId, asSequenceTrackId} = __private.ids describe(`Sequence`, () => { test('sequence.getKeyframesOfSimpleProp()', async () => { const {objPublicAPI, sheet} = await setupTestSheet({ staticOverrides: { byObject: {}, }, sequence: { type: 'PositionalSequence', // length: 20, // subUnitsPerUnit: 30, tracksByObject: { ['obj' as ObjectAddressKey]: { trackIdByPropPath: { [encodePathToProp(['position', 'y'])]: asSequenceTrackId('1'), }, trackData: { ['1' as SequenceTrackId]: { type: 'BasicKeyframedTrack', keyframes: keyframeUtils.fromArray([ { id: asKeyframeId('0'), position: 10, connectedRight: true, handles: [0.5, 0.5, 0.5, 0.5], type: 'bezier', value: 3, }, { id: asKeyframeId('1'), position: 20, connectedRight: false, handles: [0.5, 0.5, 0.5, 0.5], type: 'bezier', value: 6, }, ]), }, }, }, }, }, }) const seq = sheet.publicApi.sequence const keyframes = seq.__experimental_getKeyframes( objPublicAPI.props.position.y, ) expect(keyframes).toHaveLength(2) expect(keyframes[0].value).toEqual(3) expect(keyframes[1].value).toEqual(6) expect(keyframes[0].position).toEqual(10) }) }) ================================================ FILE: packages/studio/src/integration-tests/SheetObject.test.ts ================================================ import {setupTestSheet} from '@theatre/studio/integration-tests/testUtils' import {encodePathToProp} from '@theatre/utils/pathToProp' import type { ObjectAddressKey, SequenceTrackId, } from '@theatre/core/types/public' import {iterateOver, prism} from '@theatre/dataverse' import type {SheetState_Historic} from '@theatre/core/types/private/core' import {__private} from '@theatre/core' const {keyframeUtils} = __private const {asKeyframeId, asSequenceTrackId} = __private.ids describe(`SheetObject`, () => { describe('static overrides', () => { const setup = async ( staticOverrides: SheetState_Historic['staticOverrides']['byObject'][ObjectAddressKey] = {}, ) => { const {studio, objPublicAPI} = await setupTestSheet({ staticOverrides: { byObject: { ['obj' as ObjectAddressKey]: staticOverrides, }, }, }) const objValues = iterateOver(prism(() => objPublicAPI.value)) const teardown = () => objValues.return() return {studio, objPublicAPI, objValues, teardown} } describe(`conformance`, () => { test(`invalid static overrides should get ignored`, async () => { const {teardown, objValues} = await setup({ nonExistentProp: 1, position: { // valid x: 10, // invalid y: '20', }, // invalid color: 'ss', deeply: { nested: { // invalid checkbox: 0, }, }, }) const {value} = objValues.next() expect(value).toMatchObject({ position: {x: 10, y: 0, z: 0}, color: {r: 0, g: 0, b: 0, a: 1}, deeply: { nested: { checkbox: true, }, }, }) expect(value).not.toHaveProperty('nonExistentProp') teardown() }) test(`setting a compound prop should only work if all its sub-props are present`, async () => { const {teardown, objValues, objPublicAPI, studio} = await setup({}) expect(() => { studio.transaction(({set}) => { set(objPublicAPI.props.position, {x: 1, y: 2} as any as { x: number y: number z: number }) }) }).toThrow() }) test(`setting a compound prop should only work if all its sub-props are valid`, async () => { const {teardown, objValues, objPublicAPI, studio} = await setup({}) expect(() => { studio.transaction(({set}) => { set(objPublicAPI.props.position, {x: 1, y: 2, z: 'bad'} as any as { x: number y: number z: number }) }) }).toThrow() }) test(`setting a simple prop should only work if it is valid`, async () => { const {teardown, objValues, objPublicAPI, studio} = await setup({}) expect(() => { studio.transaction(({set}) => { set(objPublicAPI.props.position.x, 'bad' as any as number) }) }).toThrow() }) }) test(`should be a deep merge of default values and static overrides`, async () => { const {teardown, objValues} = await setup({position: {x: 10}}) expect(objValues.next().value).toMatchObject({ position: {x: 10, y: 0, z: 0}, }) teardown() }) test(`should allow introducing a static override to a simple prop`, async () => { const {teardown, objValues, studio, objPublicAPI} = await setup({ position: {x: 10}, }) studio.transaction(({set}) => { set(objPublicAPI.props.position.y, 5) }) expect(objValues.next().value).toMatchObject({ position: {x: 10, y: 5, z: 0}, }) teardown() }) test(`should allow introducing a static override to a compound prop`, async () => { const {teardown, objValues, studio, objPublicAPI} = await setup() studio.transaction(({set}) => { set(objPublicAPI.props.position, {x: 1, y: 2, z: 3}) }) expect(objValues.next().value).toMatchObject({ position: {x: 1, y: 2, z: 3}, }) teardown() }) test(`should allow removing a static override to a simple prop`, async () => { const {teardown, objValues, studio, objPublicAPI} = await setup() studio.transaction(({set}) => { set(objPublicAPI.props.position, {x: 1, y: 2, z: 3}) }) studio.transaction(({unset}) => { unset(objPublicAPI.props.position.z) }) expect(objValues.next().value).toMatchObject({ position: {x: 1, y: 2, z: 0}, }) teardown() }) test(`should allow removing a static override to a compound prop`, async () => { const {teardown, objValues, studio, objPublicAPI} = await setup() studio.transaction(({set}) => { set(objPublicAPI.props.position, {x: 1, y: 2, z: 3}) }) studio.transaction(({unset}) => { unset(objPublicAPI.props.position) }) expect(objValues.next().value).toMatchObject({ position: {x: 0, y: 0, z: 0}, }) teardown() }) describe(`simple props as json objects`, () => { test(`with no overrides`, async () => { const {teardown, objValues, studio, objPublicAPI} = await setup() expect(objValues.next().value).toMatchObject({ color: {r: 0, g: 0, b: 0, a: 1}, }) teardown() }) describe(`setting overrides`, () => { test(`should allow setting an override`, async () => { const {teardown, objValues, studio, objPublicAPI} = await setup() studio.transaction(({set}) => { set(objPublicAPI.props.color, {r: 0.1, g: 0.2, b: 0.3, a: 0.5}) }) expect(objValues.next().value).toMatchObject({ color: {r: 0.1, g: 0.2, b: 0.3, a: 0.5}, }) teardown() }) test(`should disallow setting an override on json sub-props`, async () => { const {teardown, objValues, studio, objPublicAPI} = await setup() // TODO also disallow in typescript expect(() => { studio.transaction(({set}) => { set(objPublicAPI.props.color.r, 1) }) }).toThrow() expect(objValues.next().value).toMatchObject({ color: {r: 0, g: 0, b: 0, a: 1}, }) teardown() }) }) describe(`unsetting overrides`, () => { test(`should allow unsetting an override`, async () => { const {teardown, objValues, studio, objPublicAPI} = await setup() studio.transaction(({set}) => { set(objPublicAPI.props.color, {r: 0.1, g: 0.2, b: 0.3, a: 0.5}) }) studio.transaction(({unset}) => { unset(objPublicAPI.props.color) }) expect(objValues.next().value).toMatchObject({ color: {r: 0, g: 0, b: 0, a: 1}, }) teardown() }) test(`should disallow unsetting an override on sub-props`, async () => { const {teardown, objValues, studio, objPublicAPI} = await setup() studio.transaction(({set}) => { set(objPublicAPI.props.color, {r: 0.1, g: 0.2, b: 0.3, a: 0.5}) }) // TODO: also disallow in types expect(() => { studio.transaction(({unset}) => { unset(objPublicAPI.props.color.r) }) }).toThrow() expect(objValues.next().value).toMatchObject({ color: {r: 0.1, g: 0.2, b: 0.3, a: 0.5}, }) teardown() }) }) }) }) describe(`sequenced overrides`, () => { test('calculation of sequenced overrides', async () => { const {objPublicAPI, sheet} = await setupTestSheet({ staticOverrides: { byObject: {}, }, sequence: { type: 'PositionalSequence', length: 20, subUnitsPerUnit: 30, tracksByObject: { ['obj' as ObjectAddressKey]: { trackIdByPropPath: { [encodePathToProp(['position', 'y'])]: asSequenceTrackId('1'), }, trackData: { ['1' as SequenceTrackId]: { type: 'BasicKeyframedTrack', keyframes: keyframeUtils.fromArray([ { id: asKeyframeId('0'), position: 10, connectedRight: true, handles: [0.5, 0.5, 0.5, 0.5], type: 'bezier', value: 3, }, { id: asKeyframeId('1'), position: 20, connectedRight: false, handles: [0.5, 0.5, 0.5, 0.5], type: 'bezier', value: 6, }, ]), }, }, }, }, }, }) const seq = sheet.publicApi.sequence const objValues = iterateOver(prism(() => objPublicAPI.value)) expect(seq.position).toEqual(0) expect(objValues.next().value).toMatchObject({ position: {x: 0, y: 3, z: 0}, }) seq.position = 5 expect(seq.position).toEqual(5) expect(objValues.next().value).toMatchObject({ position: {x: 0, y: 3, z: 0}, }) seq.position = 11 expect(objValues.next().value).toMatchObject({ position: {x: 0, y: 3.29999747758308, z: 0}, }) seq.position = 15 expect(objValues.next().value).toMatchObject({ position: {x: 0, y: 4.5, z: 0}, }) seq.position = 22 expect(objValues.next().value).toMatchObject({ position: {x: 0, y: 6, z: 0}, }) objValues.return() }) }) }) ================================================ FILE: packages/studio/src/integration-tests/SheetObjectTemplate.test.ts ================================================ import {setupTestSheet} from '@theatre/studio/integration-tests/testUtils' import {encodePathToProp} from '@theatre/utils/pathToProp' import type { ObjectAddressKey, SequenceTrackId, } from '@theatre/core/types/public' import type {$IntentionalAny} from '@theatre/core/types/public' import {iterateOver} from '@theatre/dataverse' import {__private} from '@theatre/core' const {asSequenceTrackId} = __private.ids describe(`SheetObjectTemplate`, () => { describe(`getArrayOfValidSequenceTracks()`, () => { it('should only include valid tracks', async () => { const {obj} = await setupTestSheet({ staticOverrides: { byObject: {}, }, sequence: { type: 'PositionalSequence', subUnitsPerUnit: 30, // length: 10, tracksByObject: { ['obj' as ObjectAddressKey]: { trackIdByPropPath: { [encodePathToProp(['position', 'x'])]: asSequenceTrackId('x'), [encodePathToProp(['position', 'invalid'])]: asSequenceTrackId('invalidTrack'), }, trackData: { ['x' as SequenceTrackId]: null as $IntentionalAny, ['invalid' as SequenceTrackId]: null as $IntentionalAny, }, }, }, }, }) const iter = iterateOver(obj.template.getArrayOfValidSequenceTracks()) const validTracks = iter.next().value expect(validTracks).toHaveLength(1) expect(validTracks).toMatchObject([ { pathToProp: ['position', 'x'], trackId: 'x', }, ]) }) it('should return empty array when no tracks are set up', async () => { const {obj} = await setupTestSheet({ staticOverrides: { byObject: {}, }, sequence: { type: 'PositionalSequence', tracksByObject: {}, }, }) const iter = iterateOver(obj.template.getArrayOfValidSequenceTracks()) expect(iter.next().value).toHaveLength(0) }) }) describe(`getMapOfValidSequenceTracks_forStudio()`, () => { it('should return valid sequences in map form', async () => { const {obj} = await setupTestSheet({ staticOverrides: { byObject: {}, }, sequence: { type: 'PositionalSequence', subUnitsPerUnit: 30, length: 10, tracksByObject: { ['obj' as ObjectAddressKey]: { trackIdByPropPath: { [encodePathToProp(['position', 'x'])]: asSequenceTrackId('x'), [encodePathToProp(['position', 'invalid'])]: asSequenceTrackId('invalidTrack'), }, trackData: { ['x' as SequenceTrackId]: null as $IntentionalAny, ['invalid' as SequenceTrackId]: null as $IntentionalAny, }, }, }, }, }) const iter = iterateOver( obj.template.getMapOfValidSequenceTracks_forStudio(), ) const validTracks = iter.next().value expect(validTracks).toMatchObject({ position: { x: 'x', }, }) }) }) }) ================================================ FILE: packages/studio/src/integration-tests/setupIntegrationTestEnv.ts ================================================ export {} global.__THEATREJS__FORCE_CONNECT_CORE_AND_STUDIO = true ================================================ FILE: packages/studio/src/integration-tests/testUtils.ts ================================================ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable no-restricted-syntax */ import '@theatre/studio' import type {SheetId} from '@theatre/core' import {getProject} from '@theatre/core' import {privateAPI} from '@theatre/core/privateAPIs' import type {ProjectState_Historic} from '@theatre/core/types/private/core' import type {SheetState_Historic} from '@theatre/core/types/private/core' import * as t from '@theatre/core/propTypes' import getStudio from '@theatre/studio/getStudio' import {getCoreTicker} from '@theatre/core/coreTicker' import {globals} from '@theatre/core/globals' import theatre from '@theatre/core' const defaultProps = { position: { x: 0, y: 0, z: 0, }, color: t.rgba(), deeply: { nested: { checkbox: true, }, }, } let lastProjectN = 0 const studio = getStudio()! void theatre.init({studio: true, usePersistentStorage: false}) export async function setupTestSheet(sheetState: SheetState_Historic) { const projectState: ProjectState_Historic = { definitionVersion: globals.currentProjectStateDefinitionVersion, sheetsById: { ['Sheet' as SheetId]: sheetState, }, revisionHistory: [], } const project = getProject('Test Project ' + lastProjectN++, { state: projectState, }) const ticker = getCoreTicker() ticker.tick() await project.ready const sheetPublicAPI = project.sheet('Sheet') const objPublicAPI = sheetPublicAPI.object('obj', defaultProps) const obj = privateAPI(objPublicAPI) return { obj, objPublicAPI, sheet: privateAPI(sheetPublicAPI), project, ticker, studio, } } ================================================ FILE: packages/studio/src/notify.tsx ================================================ import React, {Fragment} from 'react' import toast, {useToaster} from 'react-hot-toast/headless' import styled from 'styled-components' import {pointerEventsAutoInNormalMode} from './css' import type { Notification, NotificationType, Notify, Notifiers, } from '@theatre/core/utils/notify' import {useVal} from '@theatre/react' import getStudio from './getStudio' import {marked} from 'marked' import useTooltip from './uiComponents/Popover/useTooltip' import MinimalTooltip from './uiComponents/Popover/MinimalTooltip' /** * Creates a string key unique to a notification with a certain title and message. */ const hashNotification = ({title, message}: Notification) => `${title} ${message}` /** * Used to check if a notification with a certain title and message is already displayed. */ const notificationUniquenessChecker = (() => { const map = new Map() return { add: (notification: Notification) => { const key = hashNotification(notification) if (map.has(key)) { map.set(key, map.get(key)! + 1) } else { map.set(key, 1) } }, delete: (notification: Notification) => { const key = hashNotification(notification) if (map.has(key) && map.get(key)! > 1) { map.set(key, map.get(key)! - 1) } else { map.delete(key) } }, clear: () => { map.clear() }, check: (notification: Notification) => map.has(hashNotification(notification)), } })() /** * Used to check if a notification with a certain type is already displayed. * * Massive hack, we should be able to attach this info to toasts. */ const notificationTypeChecker = (() => { const map = new Map() return { add: (type: NotificationType) => { if (map.has(type)) { map.set(type, map.get(type)! + 1) } else { map.set(type, 1) } }, delete: (type: NotificationType) => { if (map.has(type) && map.get(type)! > 1) { map.set(type, map.get(type)! - 1) } else { map.delete(type) } }, clear: () => { map.clear() }, check: (type: NotificationType) => map.has(type), get types() { return Array.of(...map.keys()) }, } })() //region Styles const NotificationContainer = styled.div` width: 100%; border-radius: 4px; display: flex; gap: 12px; ${pointerEventsAutoInNormalMode}; background-color: rgba(40, 43, 47, 0.8); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.25), 0 2px 6px rgba(0, 0, 0, 0.15); backdrop-filter: blur(14px); @supports not (backdrop-filter: blur()) { background: rgba(40, 43, 47, 0.95); } ` const NotificationTitle = styled.div` font-size: 14px; font-weight: bold; color: #fff; ` const NotificationMain = styled.div` flex: 1; flex-direction: column; width: 0; display: flex; padding: 16px 0; gap: 12px; ` const NotificationMessage = styled.div` color: #b4b4b4; font-size: 12px; line-height: 1.4; a { color: rgba(255, 255, 255, 0.9); } em { font-style: italic; } strong { font-weight: bold; color: #d5d5d5; } p { margin-bottom: 8px; } code { font-family: monospace; background: rgba(0, 0, 0, 0.3); padding: 1px 1px 2px; border-radius: 4px; border: 1px solid rgba(255, 255, 255, 0.08); white-space: pre-wrap; } pre > code { white-space: pre; display: block; overflow: auto; padding: 4px; } pre { white-space: pre-wrap; margin-bottom: 8px; } ` const DismissButton = styled.button` color: rgba(255, 255, 255, 0.9); font-weight: 500; display: flex; align-items: center; justify-content: center; background: none; border: none; padding-left: 12px; padding-right: 12px; border-left: 1px solid rgba(255, 255, 255, 0.05); &:hover { background: rgba(255, 255, 255, 0.05); } ` const COLORS = { info: '#3b82f6', success: '#10b981', warning: '#f59e0b', error: '#ef4444', } const IndicatorDot = styled.div<{type: NotificationType}>` display: flex; justify-content: center; margin-left: 12px; padding-top: 21px; ::before { content: ''; width: 8px; height: 8px; border-radius: 999999px; background-color: ${({type}) => COLORS[type]}; } ` //endregion /** * Creates handlers for different types of notifications. */ const createHandler = (type: NotificationType): Notify => (title, message, docs = [], allowDuplicates = false) => { // We can disallow duplicates. We do this through checking the notification contents // against a registry of already displayed notifications. if ( allowDuplicates || !notificationUniquenessChecker.check({title, message}) ) { notificationUniquenessChecker.add({title, message}) // We have not way sadly to attach custom notification types to react-hot-toast toasts, // so we use our own data structure for it. notificationTypeChecker.add(type) toast.custom( (t) => ( {title} {docs.length > 0 && ( Docs:{' '} {docs.map((doc, i) => ( {i > 0 && ', '} {doc.title} ))} )} { toast.remove(t.id) notificationUniquenessChecker.delete({title, message}) notificationTypeChecker.delete(type) }} > Close ), {duration: Infinity}, ) } } export const notify: Notifiers = { warning: createHandler('warning'), success: createHandler('success'), info: createHandler('info'), error: createHandler('error'), } //region Styles const ButtonContainer = styled.div<{ align: 'center' | 'side' danger?: boolean }>` display: flex; justify-content: ${({align}) => (align === 'center' ? 'center' : 'flex-end')}; gap: 12px; ` const Button = styled.button<{danger?: boolean}>` position: relative; border-radius: 4px; display: flex; align-items: center; gap: 12px; ${pointerEventsAutoInNormalMode}; background-color: rgba(40, 43, 47, 0.8); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.25), 0 2px 6px rgba(0, 0, 0, 0.15); backdrop-filter: blur(14px); border: none; padding: 12px; color: #fff; overflow: hidden; ::before { content: ''; position: absolute; inset: 0; } :hover::before { background: ${({danger}) => danger ? 'rgba(255, 0, 0, 0.1)' : 'rgba(255, 255, 255, 0.1)'}; } @supports not (backdrop-filter: blur()) { background: rgba(40, 43, 47, 0.95); } ` const NotifierContainer = styled.div` z-index: 10; display: flex; flex-direction: column; gap: 8px; position: fixed; right: 92px; top: 50px; width: 500px; height: 85vh; min-height: 400px; ` const NotificationScroller = styled.div` overflow: hidden; pointer-events: auto; border-radius: 4px; & > div { display: flex; flex-direction: column-reverse; gap: 8px; overflow: scroll; height: 100%; } ` const EmptyState = styled.div` width: fit-content; padding: 8px; border-radius: 4px; display: flex; flex-direction: column; gap: 12px; color: #b4b4b4; font-size: 12px; line-height: 1.4; ` //endregion export const useEmptyNotificationsTooltip = () => { const {hasNotifications} = useNotifications() return useTooltip({enabled: !hasNotifications}, () => ( No notifications Notifications will appear here when you get them. )) } /** * The component responsible for rendering the notifications. */ export const Notifier = () => { const {toasts, handlers} = useToaster() const {startPause, endPause} = handlers const pinNotifications = useVal(getStudio().atomP.ahistoric.pinNotifications) ?? false return ( {!pinNotifications ? null : toasts.length > 0 && (
{toasts.map((toast) => { return (
{/* message is always a function in our case */} {/* @ts-ignore */} {toast.message(toast)}
) })}
)} {pinNotifications && toasts.length > 0 && ( )}
) } export const useNotifications = () => { const {toasts} = useToaster() return { hasNotifications: toasts.length > 0, } } ================================================ FILE: packages/studio/src/panels/BasePanel/BasePanel.tsx ================================================ import {prism, val} from '@theatre/dataverse' import {usePrism} from '@theatre/react' import type {$IntentionalAny, VoidFn} from '@theatre/core/types/public' import getStudio from '@theatre/studio/getStudio' import type {PanelPosition} from '@theatre/core/types/private' import useLockSet from '@theatre/studio/uiComponents/useLockSet' import React, {useContext} from 'react' import useWindowSize from 'react-use/esm/useWindowSize' import type {UIPanelId} from '@theatre/core/types/private' type PanelStuff = { panelId: UIPanelId dims: { width: number height: number top: number left: number } minDims: { width: number height: number } boundsHighlighted: boolean addBoundsHighlightLock: () => VoidFn } export const panelDimsToPanelPosition = ( dims: PanelStuff['dims'], windowDims: {height: number; width: number}, ): PanelPosition => { const left = dims.left / windowDims.width const right = (dims.left + dims.width) / windowDims.width const top = dims.top / windowDims.height const bottom = (dims.height + dims.top) / windowDims.height const position: PanelPosition = { edges: { left: left <= 0.5 ? {from: 'screenLeft', distance: left} : {from: 'screenRight', distance: 1 - left}, right: right <= 0.5 ? {from: 'screenLeft', distance: right} : {from: 'screenRight', distance: 1 - right}, top: top <= 0.5 ? {from: 'screenTop', distance: top} : {from: 'screenBottom', distance: 1 - top}, bottom: bottom <= 0.5 ? {from: 'screenTop', distance: bottom} : {from: 'screenBottom', distance: 1 - bottom}, }, } return position } const PanelContext = React.createContext(null as $IntentionalAny) export const usePanel = () => useContext(PanelContext) const BasePanel: React.FC<{ panelId: UIPanelId defaultPosition: PanelPosition minDims: {width: number; height: number} children: React.ReactNode }> = ({panelId, children, defaultPosition, minDims}) => { const windowSize = useWindowSize(800, 200) const [boundsHighlighted, addBoundsHighlightLock] = useLockSet() const {stuff} = usePrism(() => { const {edges} = val(getStudio()!.atomP.historic.panelPositions[panelId]) ?? defaultPosition const left = Math.floor( windowSize.width * (edges.left.from === 'screenLeft' ? edges.left.distance : 1 - edges.left.distance), ) const right = Math.floor( windowSize.width * (edges.right.from === 'screenLeft' ? edges.right.distance : 1 - edges.right.distance), ) const top = Math.floor( windowSize.height * (edges.top.from === 'screenTop' ? edges.top.distance : 1 - edges.top.distance), ) const bottom = Math.floor( windowSize.height * (edges.bottom.from === 'screenTop' ? edges.bottom.distance : 1 - edges.bottom.distance), ) const width = Math.max(right - left, minDims.width) const height = Math.max(bottom - top, minDims.height) // memo-ing dims so its ref can be used as a cache key const dims = prism.memo( 'dims', () => ({ width, left, top, height, }), [width, left, top, height], ) const stuff: PanelStuff = { dims: dims, panelId, minDims, boundsHighlighted, addBoundsHighlightLock, } return {stuff} }, [panelId, windowSize, boundsHighlighted, addBoundsHighlightLock]) return {children} } export default BasePanel ================================================ FILE: packages/studio/src/panels/BasePanel/ExtensionPaneWrapper.tsx ================================================ import type {$FixMe} from '@theatre/core/types/public' import type {PanelPosition, UIPanelId} from '@theatre/core/types/private' import type {PaneInstance} from '@theatre/core/types/public' import React, {useCallback, useLayoutEffect, useState} from 'react' import styled from 'styled-components' import {F2 as F2Impl, TitleBar} from './common' import BasePanel from './BasePanel' import PanelDragZone from './PanelDragZone' import PanelWrapper from './PanelWrapper' import {ErrorBoundary} from 'react-error-boundary' import {IoClose} from 'react-icons/io5' import getStudio from '@theatre/studio/getStudio' import {panelZIndexes} from '@theatre/studio/panels/BasePanel/common' import type {PaneInstanceId} from '@theatre/core/types/public' const defaultPosition: PanelPosition = { edges: { left: {from: 'screenLeft', distance: 0.3}, right: {from: 'screenRight', distance: 0.3}, top: {from: 'screenTop', distance: 0.3}, bottom: {from: 'screenBottom', distance: 0.3}, }, } const minDims = {width: 300, height: 300} const ExtensionPaneWrapper: React.FC<{ paneInstance: PaneInstance<$FixMe> }> = ({paneInstance}) => { return ( ) } const Container = styled(PanelWrapper)` display: flex; flex-direction: column; box-shadow: 0px 5px 12px -4px rgb(0 0 0 / 22%); z-index: ${panelZIndexes.pluginPanes}; ` const Title = styled.div` width: 100%; ` const PaneTools = styled.div` display: flex; align-items: center; opacity: 1; position: absolute; right: 4px; top: 0; bottom: 0; ` const ClosePanelButton = styled.button` display: flex; align-items: center; justify-content: center; border-radius: 2px; font-size: 11px; height: 10px; width: 18px; color: #adadadb3; background: transparent; border: none; cursor: pointer; &:hover { color: white; } ` /** * The &:after part blocks pointer events from reaching the content of the * pane when a drag gesture is active in theatre's UI. It's a hack and its downside * is that pane content cannot interact with the rest of theatre's UI while a drag * gesture is active. * TODO find a less hacky way? */ const F2 = styled(F2Impl)` position: relative; overflow: hidden; &:after { z-index: 10; position: absolute; inset: 0; display: block; content: ' '; pointer-events: none; #pointer-root:not(.normal) & { pointer-events: auto; } } ` const ErrorContainer = styled.div` padding: 12px; & > pre { border: 1px solid #ff62624f; background-color: rgb(255 0 0 / 5%); margin: 8px 0; padding: 8px; font-family: monospace; overflow: scroll; color: #ff9896; } ` const ErrorFallback: React.FC<{error: Error}> = (props) => { return ( An Error occurred rendering this pane. Open the console for more info.
        {JSON.stringify(
          {message: props.error.message, stack: props.error.stack},
          null,
          2,
        )}
      
) } const Content: React.FC<{paneInstance: PaneInstance<$FixMe>}> = ({ paneInstance, }) => { const [mountingPoint, setMountingPoint] = useState(null) const mount = paneInstance.definition.mount useLayoutEffect(() => { if (!mountingPoint) return const unmount = mount({ paneId: paneInstance.instanceId, node: mountingPoint!, }) if (typeof unmount === 'function') { return unmount } }, [mountingPoint, mount, paneInstance.instanceId]) const closePane = useCallback(() => { getStudio().paneManager.destroyPane( paneInstance.instanceId as PaneInstanceId, ) }, [paneInstance]) return ( {paneInstance.instanceId} ) } export default ExtensionPaneWrapper ================================================ FILE: packages/studio/src/panels/BasePanel/PanelDragZone.tsx ================================================ import useRefAndState from '@theatre/studio/utils/useRefAndState' import type {$IntentionalAny, VoidFn} from '@theatre/core/types/public' import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import useDrag from '@theatre/studio/uiComponents/useDrag' import React, {useMemo, useRef} from 'react' import styled from 'styled-components' import {panelDimsToPanelPosition, usePanel} from './BasePanel' import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler' import {clamp} from 'lodash-es' import {minVisibleSize} from './common' const Container = styled.div` cursor: move; ` const PanelDragZone: React.FC< React.DetailedHTMLProps, HTMLDivElement> > = (props) => { const panelStuff = usePanel() const panelStuffRef = useRef(panelStuff) panelStuffRef.current = panelStuff const [ref, node] = useRefAndState(null as $IntentionalAny) const dragOpts: Parameters[1] = useMemo(() => { return { debugName: 'PanelDragZone', lockCursorTo: 'move', onDragStart() { const stuffBeforeDrag = panelStuffRef.current let tempTransaction: CommitOrDiscardOrRecapture | undefined const unlock = panelStuff.addBoundsHighlightLock() return { onDrag(dx, dy) { const newDims: (typeof panelStuff)['dims'] = { ...stuffBeforeDrag.dims, top: clamp( stuffBeforeDrag.dims.top + dy, 0, window.innerHeight - minVisibleSize, ), left: clamp( stuffBeforeDrag.dims.left + dx, -stuffBeforeDrag.dims.width + minVisibleSize, window.innerWidth - minVisibleSize, ), } const position = panelDimsToPanelPosition(newDims, { width: window.innerWidth, height: window.innerHeight, }) tempTransaction?.discard() tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { stateEditors.studio.historic.panelPositions.setPanelPosition({ position, panelId: stuffBeforeDrag.panelId, }) }) }, onDragEnd(dragHappened) { unlock() if (dragHappened) { tempTransaction?.commit() } else { tempTransaction?.discard() } }, } }, } }, []) const [isDragging] = useDrag(node, dragOpts) useCssCursorLock(isDragging, 'dragging', 'move') const [onMouseEnter, onMouseLeave] = useMemo(() => { let unlock: VoidFn | undefined return [ function onMouseEnter() { if (unlock) { const u = unlock unlock = undefined u() } unlock = panelStuff.addBoundsHighlightLock() }, function onMouseLeave() { if (unlock) { const u = unlock unlock = undefined u() } }, ] }, []) return ( ) } export default PanelDragZone ================================================ FILE: packages/studio/src/panels/BasePanel/PanelResizeHandle.tsx ================================================ import useRefAndState from '@theatre/studio/utils/useRefAndState' import type {$IntentionalAny} from '@theatre/core/types/public' import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import useDrag from '@theatre/studio/uiComponents/useDrag' import {lighten} from 'polished' import React, {useMemo, useRef} from 'react' import styled from 'styled-components' import {panelDimsToPanelPosition, usePanel} from './BasePanel' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {clamp} from 'lodash-es' import {minVisibleSize} from './common' const Base = styled.div` position: absolute; ${pointerEventsAutoInNormalMode}; &:after { position: absolute; inset: -5px; display: block; content: ' '; } opacity: 0; background-color: #478698; &.isHighlighted { opacity: 0.7; } &.isDragging { opacity: 1; /* background-color: ${lighten(0.2, '#478698')}; */ } &:hover { opacity: 1; } ` const Side = styled(Base)` /** The horizintal/vertical resize handles have z-index:-1 and are offset 1px outside of the panel to make sure they don't occlude any element that pops out of the panel (like the Playhead in SequenceEditorPanel). This means that panels will always need an extra 1px margin for their resize handles to be visible, but that's not a problem that we have to deal with right now (if it is at all a problem). */ z-index: -1; ` const Horizontal = styled(Side)` left: 0px; right: 0px; height: 1px; ` const Top = styled(Horizontal)` top: -1px; ` const Bottom = styled(Horizontal)` bottom: -1px; ` const Vertical = styled(Side)` z-index: -1; top: -1px; bottom: -1px; width: 1px; ` const Left = styled(Vertical)` left: -1px; ` const Right = styled(Vertical)` right: -1px; ` const Angle = styled(Base)` // The angles have z-index: 10 to make sure they _do_ occlude other elements in the panel. z-index: 10; width: 8px; height: 8px; ` const TopLeft = styled(Angle)` top: 0; left: 0; ` const TopRight = styled(Angle)` top: 0; right: 0; ` const BottomLeft = styled(Angle)` bottom: 0; left: 0; ` const BottomRight = styled(Angle)` bottom: 0; right: 0; ` const els = { Top, TopLeft, TopRight, Bottom, BottomLeft, BottomRight, Left, Right, } type Which = | 'Top' | 'Bottom' | 'Left' | 'Right' | 'TopLeft' | 'TopRight' | 'BottomLeft' | 'BottomRight' const cursors: {[which in Which]: string} = { Top: 'ns-resize', Bottom: 'ns-resize', Left: 'ew-resize', Right: 'ew-resize', TopLeft: 'nw-resize', TopRight: 'ne-resize', BottomLeft: 'sw-resize', BottomRight: 'se-resize', } const PanelResizeHandle: React.FC<{ which: Which }> = ({which}) => { const panelStuff = usePanel() const panelStuffRef = useRef(panelStuff) panelStuffRef.current = panelStuff const [ref, node] = useRefAndState(null as $IntentionalAny) const dragOpts: Parameters[1] = useMemo(() => { return { debugName: 'PanelResizeHandle', lockCursorTo: cursors[which], onDragStart() { let tempTransaction: CommitOrDiscardOrRecapture | undefined const stuffBeforeDrag = panelStuffRef.current const unlock = panelStuff.addBoundsHighlightLock() return { onDrag(dx, dy) { const newDims: (typeof panelStuff)['dims'] = { ...stuffBeforeDrag.dims, } if (which.startsWith('Bottom')) { newDims.height = Math.max( newDims.height + dy, stuffBeforeDrag.minDims.height, ) } else if (which.startsWith('Top')) { const bottom = newDims.top + newDims.height const top = clamp( newDims.top + dy, 0, Math.min( bottom - stuffBeforeDrag.minDims.height, window.innerHeight - minVisibleSize, ), ) const height = bottom - top newDims.height = height newDims.top = top } if (which.endsWith('Left')) { const right = newDims.left + newDims.width const left = Math.min( newDims.left + dx, Math.min( right - stuffBeforeDrag.minDims.width, window.innerWidth - minVisibleSize, ), ) const width = right - left newDims.width = width newDims.left = left } else if (which.endsWith('Right')) { newDims.width = Math.max( newDims.width + dx, Math.max( stuffBeforeDrag.minDims.width, minVisibleSize - stuffBeforeDrag.dims.left, ), ) } const position = panelDimsToPanelPosition(newDims, { width: window.innerWidth, height: window.innerHeight, }) tempTransaction?.discard() tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { stateEditors.studio.historic.panelPositions.setPanelPosition({ position, panelId: stuffBeforeDrag.panelId, }) }) }, onDragEnd(dragHappened) { unlock() if (dragHappened) { tempTransaction?.commit() } else { tempTransaction?.discard() } }, } }, } }, [which]) const [isDragging] = useDrag(node, dragOpts) const Comp = els[which] const isOnCorner = which.length <= 6 return ( ) } export default PanelResizeHandle ================================================ FILE: packages/studio/src/panels/BasePanel/PanelResizers.tsx ================================================ import React from 'react' import PanelResizeHandle from './PanelResizeHandle' const PanelResizers: React.FC<{}> = (props) => { return ( <> ) } export default PanelResizers ================================================ FILE: packages/studio/src/panels/BasePanel/PanelWrapper.tsx ================================================ import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import React from 'react' import styled from 'styled-components' import {usePanel} from './BasePanel' import PanelResizers from './PanelResizers' const Container = styled.div` position: absolute; user-select: none; box-sizing: border-box; ${pointerEventsAutoInNormalMode}; /* box-shadow: 1px 2px 10px -5px black; */ z-index: 1000; ` const PanelWrapper = React.forwardRef( ( props: React.DetailedHTMLProps< React.HTMLAttributes, HTMLDivElement >, ref, ) => { const stuff = usePanel() const {style, children, ...otherProps} = props return ( // @ts-ignore {children} ) }, ) export default PanelWrapper ================================================ FILE: packages/studio/src/panels/BasePanel/common.tsx ================================================ import {theme} from '@theatre/studio/css' import styled from 'styled-components' export const panelZIndexes = { get outlinePanel() { return 1 }, get propsPanel() { return panelZIndexes.outlinePanel }, get sequenceEditorPanel() { return this.outlinePanel - 1 }, get toolbar() { return this.outlinePanel + 1 }, get pluginPanes() { return this.sequenceEditorPanel - 1 }, } export const propsEditorBackground = theme.panel.bg export const TitleBar_Piece = styled.span` white-space: nowrap; ` export const TitleBar_Punctuation = styled.span` white-space: nowrap; color: ${theme.panel.head.punctuation.color}; ` export const F2 = styled.div` background: ${propsEditorBackground}; flex-grow: 1; overflow-y: scroll; padding: 0; ` export const titleBarHeight = 18 export const TitleBar = styled.div` height: ${titleBarHeight}px; box-sizing: border-box; display: flex; align-items: center; padding: 0 10px; position: relative; color: #adadadb3; border-bottom: 1px solid rgb(0 0 0 / 13%); background-color: #25272b; font-size: 10px; font-weight: 500; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; ` // the minimum visible width or height when the panel is partially offscreen export const minVisibleSize = 100 ================================================ FILE: packages/studio/src/panels/DetailPanel/DetailPanel.tsx ================================================ import {outlineSelection} from '@theatre/studio/selectors' import {usePrism, useVal} from '@theatre/react' import React, { createContext, useContext, useEffect, useLayoutEffect, useState, } from 'react' import styled from 'styled-components' import { panelZIndexes, TitleBar_Piece, TitleBar_Punctuation, } from '@theatre/studio/panels/BasePanel/common' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import ObjectDetails from './ObjectDetails' import ProjectDetails from './ProjectDetails' import getStudio from '@theatre/studio/getStudio' import useHotspot from '@theatre/studio/uiComponents/useHotspot' import {Atom, prism, val} from '@theatre/dataverse' import EmptyState from './EmptyState' import useLockSet from '@theatre/studio/uiComponents/useLockSet' import {usePresenceListenersOnRootElement} from '@theatre/studio/uiComponents/usePresence' import {__private} from '@theatre/core' const {isProject, isSheetObject} = __private.instanceTypes const headerHeight = `32px` const Container = styled.div<{pin: boolean}>` ${pointerEventsAutoInNormalMode}; background-color: rgba(40, 43, 47, 0.8); position: fixed; right: 8px; top: 50px; // Temporary, see comment about CSS grid in SingleRowPropEditor. width: 280px; height: fit-content; z-index: ${panelZIndexes.propsPanel}; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.25), 0 2px 6px rgba(0, 0, 0, 0.15); backdrop-filter: blur(14px); border-radius: 2px; display: ${({pin}) => (pin ? 'block' : 'none')}; &:hover { display: block; } @supports not (backdrop-filter: blur()) { background: rgba(40, 43, 47, 0.95); } ` const Title = styled.div` margin: 0 10px; color: #919191; font-weight: 500; font-size: 10px; user-select: none; ${pointerEventsAutoInNormalMode}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; ` const Header = styled.div` height: ${headerHeight}; display: flex; align-items: center; ` const Body = styled.div` ${pointerEventsAutoInNormalMode}; max-height: calc(100vh - 100px); overflow-y: scroll; &::-webkit-scrollbar { display: none; } scrollbar-width: none; padding: 0; user-select: none; /* Set the font-size for input values in the detail panel */ font-size: 12px; ` export const contextMenuShownContext = createContext< ReturnType >([false, () => () => {}]) const DetailPanel: React.FC<{}> = (props) => { const pin = useVal(getStudio().atomP.ahistoric.pinDetails) !== false const hotspotActive = useHotspot('right') useLayoutEffect(() => { isDetailPanelHotspotActiveB.set(hotspotActive) }, [hotspotActive]) // cleanup useEffect(() => { return () => { isDetailPanelHoveredB.set(false) isDetailPanelHotspotActiveB.set(false) } }, []) const [isContextMenuShown] = useContext(contextMenuShownContext) const showDetailsPanel = pin || hotspotActive || isContextMenuShown const [containerElt, setContainerElt] = useState(null) usePresenceListenersOnRootElement(containerElt) return usePrism(() => { const selection = outlineSelection.getValue() const obj = selection.find(isSheetObject) if (obj) { return ( { isDetailPanelHoveredB.set(true) }} onMouseLeave={() => { isDetailPanelHoveredB.set(false) }} >
${obj.address.objectKey}`} > <TitleBar_Piece>{obj.sheet.address.sheetId} </TitleBar_Piece> <TitleBar_Punctuation>{':'} </TitleBar_Punctuation> <TitleBar_Piece> {obj.sheet.address.sheetInstanceId}{' '} </TitleBar_Piece> <TitleBar_Punctuation> → </TitleBar_Punctuation> <TitleBar_Piece>{obj.address.objectKey}</TitleBar_Piece>
) } const project = selection.find(isProject) if (project) { return (
<TitleBar_Piece>{project.address.projectId} </TitleBar_Piece>
) } return ( { isDetailPanelHoveredB.set(true) }} onMouseLeave={() => { isDetailPanelHoveredB.set(false) }} > ) }, [showDetailsPanel]) } export default () => { const lockSet = useLockSet() return ( ) } const isDetailPanelHotspotActiveB = new Atom(false) const isDetailPanelHoveredB = new Atom(false) export const shouldShowDetailD = prism(() => { const isHovered = val(isDetailPanelHoveredB.prism) const isHotspotActive = val(isDetailPanelHotspotActiveB.prism) return isHovered || isHotspotActive }) ================================================ FILE: packages/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailCompoundPropEditor.tsx ================================================ import type { PropTypeConfig_Compound, PropTypeConfig_Number, } from '@theatre/core/types/public' import type {$FixMe} from '@theatre/core/types/public' import {Atom, getPointerParts} from '@theatre/dataverse' import type {Pointer} from '@theatre/dataverse' import last from 'lodash-es/last' import {darken, transparentize} from 'polished' import React, {useMemo} from 'react' import styled from 'styled-components' import {rowIndentationFormulaCSS} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/rowIndentationFormulaCSS' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import DeterminePropEditorForDetail from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import {useEditingToolsForCompoundProp} from '@theatre/studio/propEditors/useEditingToolsForCompoundProp' import type {PropHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted' import {whatPropIsHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted' import {deriver} from '@theatre/studio/utils/derive-utils' import NumberPropEditor from '@theatre/studio/propEditors/simpleEditors/NumberPropEditor' import type {IDetailSimplePropEditorProps} from './DetailSimplePropEditor' import {useEditingToolsForSimplePropInDetailsPanel} from '@theatre/studio/propEditors/useEditingToolsForSimpleProp' import {usePrism} from '@theatre/react' import {val} from '@theatre/dataverse' import {HiOutlineChevronRight} from 'react-icons/hi' import memoizeFn from '@theatre/utils/memoizeFn' import {collapsedMap} from './collapsedMap' import useChordial from '@theatre/studio/uiComponents/chordial/useChodrial' import {__private} from '@theatre/core' const {isPropConfigComposite} = __private.propTypeUtils const Container = styled.div` --step: 15px; --left-pad: 10px; ${pointerEventsAutoInNormalMode}; --right-width: 60%; ` const Header = styled.div<{isHighlighted: PropHighlighted}>` height: 30px; display: flex; align-items: stretch; position: relative; ` const Padding = styled.div<{isVectorProp: boolean}>` padding-left: ${rowIndentationFormulaCSS}; display: flex; align-items: center; overflow: hidden; ${({isVectorProp}) => isVectorProp ? 'width: calc(100% - var(--right-width))' : ''}; ` const ControlIndicators = styled.div` flexshrink: 0; ` const PropName = deriver(styled.div<{isHighlighted: PropHighlighted}>` margin-left: 4px; cursor: default; height: 100%; display: flex; align-items: center; gap: 4px; user-select: none; &:hover { color: white; } overflow: hidden; ${() => propNameTextCSS}; `) const CollapseIcon = styled.span<{isCollapsed: boolean; isVector: boolean}>` width: 28px; height: 28px; font-size: 9px; display: flex; align-items: center; justify-content: center; transition: transform 0.05s ease-out, color 0.1s ease-out; transform: rotateZ(${(props) => (props.isCollapsed ? 0 : 90)}deg); color: #66686a; visibility: ${(props) => // If it's a vector, show the collapse icon only when it's expanded (!props.isVector && props.isCollapsed) || // If it's a regular compond prop, show the collapse icon only when it's collapsed (props.isVector && !props.isCollapsed) ? 'visible' : 'hidden'}; ${Header}:hover & { visibility: visible; } &:hover { transform: rotateZ(${(props) => (props.isCollapsed ? 15 : 75)}deg); color: #c0c4c9; } ` const color = transparentize(0.05, `#282b2f`) const SubProps = styled.div<{depth: number; lastSubIsComposite: boolean}>` /* background: ${({depth}) => darken(depth * 0.03, color)}; */ /* padding: ${(props) => (props.lastSubIsComposite ? 0 : '4px')} 0; */ ` const isVectorProp = memoizeFn((propConfig: PropTypeConfig_Compound) => { const props = Object.entries(propConfig.props) return ( props.length <= 3 && props.every( ([name, conf]) => conf.type === 'number' && ['x', 'y', 'z'].includes(name), ) ) }) function VectorComponentEditor({ propConfig, pointerToProp, obj, SimpleEditorComponent: EditorComponent, }: IDetailSimplePropEditorProps) { const editingTools = useEditingToolsForSimplePropInDetailsPanel( pointerToProp, obj, propConfig, ) return ( ) } const InputContainer = styled.div` display: flex; align-items: center; justify-content: stretch; padding: 0 8px 0 2px; box-sizing: border-box; height: 100%; width: var(--right-width); flex-shrink: 0; flex-grow: 0; ` export type ICompoundPropDetailEditorProps< TPropTypeConfig extends PropTypeConfig_Compound, > = { propConfig: TPropTypeConfig pointerToProp: Pointer obj: SheetObject visualIndentation: number } function DetailCompoundPropEditor< TPropTypeConfig extends PropTypeConfig_Compound, >({ pointerToProp, obj, propConfig, visualIndentation, }: ICompoundPropDetailEditorProps) { const propName = propConfig.label ?? (last(getPointerParts(pointerToProp).path) as string) const allSubs = Object.entries(propConfig.props) const compositeSubs = allSubs.filter(([_, conf]) => isPropConfigComposite(conf), ) const nonCompositeSubs = allSubs.filter( ([_, conf]) => !isPropConfigComposite(conf), ) const tools = useEditingToolsForCompoundProp( pointerToProp as $FixMe, obj, propConfig, ) const label: string = propName || 'Props' const lastSubPropIsComposite = compositeSubs.length > 0 const isPropHighlightedD = useMemo( () => whatPropIsHighlighted.getIsPropHighlightedD({ ...obj.address, pathToProp: getPointerParts(pointerToProp).path, }), [pointerToProp], ) // isVectorProp is already memoized, so no need to wrap this in `useMemo()` const isVector = isVectorProp(propConfig) const isCollapsedAtom = useMemo(() => { if (!collapsedMap.has(pointerToProp)) { collapsedMap.set(pointerToProp, new Atom(isVector)) } return collapsedMap.get(pointerToProp)! }, [pointerToProp]) const isCollapsed = usePrism(() => { return isCollapsedAtom ? val(isCollapsedAtom.pointer) : isVector }, [isCollapsedAtom, isVector]) const {targetRef} = useChordial(() => { const title = ['obj', 'props', ...getPointerParts(pointerToProp).path].join( '.', ) return {title, items: tools.contextMenuItems} }) return (
{tools.controlIndicators} {label} { isCollapsedAtom.set(!isCollapsedAtom.get()) }} > {isVector && isCollapsed && ( {[...allSubs].map(([subPropKey, subPropConfig]) => { return ( } obj={obj} /> ) })} )}
{!isCollapsed && ( {[...nonCompositeSubs, ...compositeSubs].map( ([subPropKey, subPropConfig]) => { return ( } obj={obj} visualIndentation={visualIndentation + 1} /> ) }, )} )}
) } export default React.memo(DetailCompoundPropEditor) ================================================ FILE: packages/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/DetailSimplePropEditor.tsx ================================================ import type { IBasePropType, PropTypeConfig_AllSimples, } from '@theatre/core/types/public' import React, {useMemo} from 'react' import {useEditingToolsForSimplePropInDetailsPanel} from '@theatre/studio/propEditors/useEditingToolsForSimpleProp' import {SingleRowPropEditor} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor' import type {Pointer} from '@theatre/dataverse' import {getPointerParts} from '@theatre/dataverse' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type {ISimplePropEditorReactProps} from '@theatre/studio/propEditors/simpleEditors/ISimplePropEditorReactProps' import {whatPropIsHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted' export type IDetailSimplePropEditorProps< TPropTypeConfig extends IBasePropType, > = { propConfig: TPropTypeConfig pointerToProp: Pointer obj: SheetObject visualIndentation: number SimpleEditorComponent: React.VFC> } /** * Shown in the Object details panel, changes to this editor are usually reflected at either * the playhead position (the `sequence.position`) or if static, the static override value. */ function DetailSimplePropEditor< TPropTypeConfig extends PropTypeConfig_AllSimples, >({ propConfig, pointerToProp, obj, SimpleEditorComponent: EditorComponent, }: IDetailSimplePropEditorProps) { const editingTools = useEditingToolsForSimplePropInDetailsPanel( pointerToProp, obj, propConfig, ) const isPropHighlightedD = useMemo( () => whatPropIsHighlighted.getIsPropHighlightedD({ ...obj.address, pathToProp: getPointerParts(pointerToProp).path, }), [pointerToProp], ) return ( ) } export default React.memo(DetailSimplePropEditor) ================================================ FILE: packages/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor.tsx ================================================ import type * as propTypes from '@theatre/core/types/public' import {getPointerParts} from '@theatre/dataverse' import type {Pointer, Prism} from '@theatre/dataverse' import {last} from 'lodash-es' import React from 'react' import type {useEditingToolsForSimplePropInDetailsPanel} from '@theatre/studio/propEditors/useEditingToolsForSimpleProp' import styled from 'styled-components' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' import type {PropHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted' import {rowIndentationFormulaCSS} from './rowIndentationFormulaCSS' import {getDetailRowHighlightBackground} from './getDetailRowHighlightBackground' import {useVal} from '@theatre/react' import useChordial from '@theatre/studio/uiComponents/chordial/useChodrial' import type {$FixMe} from '@theatre/core/types/public' const Container = styled.div<{ isHighlighted: PropHighlighted }>` display: flex; height: 30px; justify-content: flex-start; align-items: stretch; // We cannot calculate both the container (details panel) width and the descendant // (this) width dynamically. This leads to the container width being calculated // without this percentage being taken into consideration leads to horizontal // clipping/scrolling--the same way as if we explicitly fixed either the container // width, or the descendant width. // The correct solution for tabulated UIs with dynamic container widths is to use // CSS grid. For now I fixed this issue by just giving a great enough width // to the details panel so most things don't break. --right-width: 60%; position: relative; ${pointerEventsAutoInNormalMode}; /* background-color: ${getDetailRowHighlightBackground}; */ ` const Left = styled.div` box-sizing: border-box; padding-left: ${rowIndentationFormulaCSS}; padding-right: 4px; display: flex; flex-direction: row; justify-content: flex-start; align-items: stretch; gap: 4px; flex-grow: 0; flex-shrink: 0; width: calc(100% - var(--right-width)); ` const PropNameContainer = styled.div<{ isHighlighted: PropHighlighted }>` text-align: left; flex: 1 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; user-select: none; cursor: default; ${propNameTextCSS}; &:hover { color: white; } ` const ControlsContainer = styled.div` flex-basis: 8px; flex: 0 0; display: flex; align-items: center; ` const InputContainer = styled.div` display: flex; align-items: center; justify-content: stretch; padding: 0 8px 0 2px; box-sizing: border-box; height: 100%; width: var(--right-width); flex-shrink: 0; flex-grow: 0; ` type ISingleRowPropEditorProps = { propConfig: propTypes.PropTypeConfig pointerToProp: Pointer editingTools: ReturnType isPropHighlightedD: Prism } export function SingleRowPropEditor({ propConfig, pointerToProp, editingTools, children, isPropHighlightedD, }: React.PropsWithChildren>): React.ReactElement< any, any > | null { const label = propConfig.label ?? last(getPointerParts(pointerToProp).path) const title = ['obj', 'props', ...getPointerParts(pointerToProp).path].join( '.', ) const isHighlighted = useVal(isPropHighlightedD) const {targetRef} = useChordial(() => { return { title, items: editingTools.contextMenuItems, } }) return ( {editingTools.controlIndicators} {label} {children} ) } ================================================ FILE: packages/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/collapsedMap.tsx ================================================ import type {Atom, Pointer} from '@theatre/dataverse' export const collapsedMap = new WeakMap, Atom>() ================================================ FILE: packages/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/getDetailRowHighlightBackground.tsx ================================================ import type {PropHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted' export function getDetailRowHighlightBackground({ isHighlighted, }: { isHighlighted: PropHighlighted }): string { return isHighlighted === 'self' ? '#1857a4' : isHighlighted === 'descendent' ? '#0a2f5c' : 'initial' } ================================================ FILE: packages/studio/src/panels/DetailPanel/DeterminePropEditorForDetail/rowIndentationFormulaCSS.tsx ================================================ export const rowIndentationFormulaCSS = `calc(var(--left-pad) + var(--depth) * var(--step))` ================================================ FILE: packages/studio/src/panels/DetailPanel/DeterminePropEditorForDetail.tsx ================================================ import React from 'react' import type {Pointer} from '@theatre/dataverse' import type { PropTypeConfig, PropTypeConfig_AllSimples, } from '@theatre/core/types/public' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import {simplePropEditorByPropType} from '@theatre/studio/propEditors/simpleEditors/simplePropEditorByPropType' import type {PropConfigForType} from '@theatre/studio/propEditors/utils/PropConfigForType' import type {ISimplePropEditorReactProps} from '@theatre/studio/propEditors/simpleEditors/ISimplePropEditorReactProps' import DetailCompoundPropEditor from './DeterminePropEditorForDetail/DetailCompoundPropEditor' import DetailSimplePropEditor from './DeterminePropEditorForDetail/DetailSimplePropEditor' /** * Given a propConfig, this function gives the corresponding prop editor for * use in the details panel. {@link DeterminePropEditorForKeyframe} does the * same thing for the dope sheet inline prop editor on a keyframe. The main difference * between this function and {@link DeterminePropEditorForKeyframe} is that this * one shows prop editors *with* keyframe navigation controls (that look * like `< ・ >`). * * @param p - propConfig object for any type of prop. */ const DeterminePropEditorForDetail: React.VFC< IDeterminePropEditorForDetailProps > = ({propConfig, visualIndentation, pointerToProp, obj}) => { if (propConfig.type === 'compound') { return ( ) } else if (propConfig.type === 'enum') { // notice: enums are not implemented, yet. return <> } else { const PropEditor = simplePropEditorByPropType[propConfig.type] return ( > } obj={obj} visualIndentation={visualIndentation} pointerToProp={pointerToProp} propConfig={propConfig} /> ) } } export default DeterminePropEditorForDetail type IDeterminePropEditorForDetailProps = IDetailEditablePropertyProps & { visualIndentation: number } type IDetailEditablePropertyProps = { obj: SheetObject pointerToProp: Pointer['valueType']> propConfig: PropConfigForType } ================================================ FILE: packages/studio/src/panels/DetailPanel/EmptyState.tsx ================================================ import type {FC} from 'react' import React from 'react' import styled from 'styled-components' import {Outline} from '@theatre/studio/uiComponents/icons' const Container = styled.div` padding: 16px; display: flex; flex-direction: column; gap: 24px; ` const Message = styled.div` display: flex; flex-direction: column; gap: 11px; color: rgba(255, 255, 255, 0.9); ` const Icon = styled.div` color: rgba(145, 145, 145, 0.8); ` const LinkToDoc = styled.a` color: #919191; font-size: 10px; text-decoration-color: #40434a; text-underline-offset: 3px; ` const EmptyState: FC = () => { return (
Please select an object from the Outline Menu to see its properties.
{/* Links like this should probably be managed centrally so that we can have a process for updating them when the docs change. */} Learn more about Objects
) } export default EmptyState ================================================ FILE: packages/studio/src/panels/DetailPanel/ObjectDetails.tsx ================================================ import React from 'react' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type {Pointer} from '@theatre/dataverse' import type {$FixMe} from '@theatre/core/types/public' import DeterminePropEditorForDetail from './DeterminePropEditorForDetail' import {useVal} from '@theatre/react' import uniqueKeyForAnyObject from '@theatre/utils/uniqueKeyForAnyObject' import styled from 'styled-components' const ActionButtonContainer = styled.div` display: flex; flex-direction: column; gap: 4px; padding: 8px; ` const ActionButton = styled.button` display: flex; align-items: center; justify-content: center; outline: none; border-radius: 2px; color: #a8a8a9; background: rgba(255, 255, 255, 0.1); border: none; height: 28px; &:hover { background: rgba(255, 255, 255, 0.15); } &:active { background: rgba(255, 255, 255, 0.2); } ` const ObjectDetails: React.FC<{ /** TODO: add support for multiple objects (it would show their common props) */ objects: [SheetObject] }> = ({objects}) => { const obj = objects[0] const config = useVal(obj.template.configPointer) const actions = useVal(obj.template._temp_actionsPointer) return ( <> } propConfig={config} visualIndentation={1} /> {actions && Object.entries(actions).map(([actionName, action]) => { return ( { action(obj.publicApi) }} > {actionName} ) })} ) } export default ObjectDetails ================================================ FILE: packages/studio/src/panels/DetailPanel/ProjectDetails/StateConflictRow.tsx ================================================ import {useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import React from 'react' import styled from 'styled-components' import {generateDiskStateRevision} from '@theatre/studio/StudioStore/generateDiskStateRevision' import type {ProjectEphemeralState} from '@theatre/core/types/private/core' import useTooltip from '@theatre/studio/uiComponents/Popover/useTooltip' import BasicTooltip from '@theatre/studio/uiComponents/Popover/BasicTooltip' import type {$FixMe} from '@theatre/core/types/public' import DetailPanelButton from '@theatre/studio/uiComponents/DetailPanelButton' import type {ProjectId} from '@theatre/core/types/public' const a = 'hi' const Container = styled.div` padding: 8px 10px; position: relative; background-color: #6d232352; &:before { position: absolute; content: ' '; display: block; left: 0; top: 0; bottom: 0; width: 2px; background-color: #ff000070; } ` const Message = styled.div` margin-bottom: 1em; & a { color: inherit; } ` const ChooseStateRow = styled.div` display: flex; gap: 8px; ` const StateConflictRow: React.FC<{projectId: ProjectId}> = ({projectId}) => { const loadingState = useVal( getStudio().ephemeralAtom.pointer.coreByProject[projectId].loadingState, ) if (!loadingState) return null if (loadingState.type === 'browserStateIsNotBasedOnDiskState') { return } else { return null } } const InConflict: React.FC<{ projectId: ProjectId loadingState: Extract< ProjectEphemeralState['loadingState'], {type: 'browserStateIsNotBasedOnDiskState'} > }> = ({projectId, loadingState}) => { /** * This stuff is not undo-safe, but once we switch to the new persistence * scheme, these will be unnecessary anyway. */ const useBrowserState = () => { getStudio().transaction(({stateEditors}) => { stateEditors.coreByProject.historic.revisionHistory.add({ projectId, revision: loadingState.onDiskState.revisionHistory[0], }) stateEditors.coreByProject.historic.revisionHistory.add({ projectId, revision: generateDiskStateRevision(), }) }) getStudio().ephemeralAtom.setByPointer( (p) => p.coreByProject[projectId]!.loadingState, { type: 'loaded', }, ) } const useOnDiskState = () => { getStudio().transaction(({stateEditors}) => { stateEditors.coreByProject.historic.setProjectState({ projectId, state: loadingState.onDiskState, }) // drafts.historic.coreByProject[projectId] = loadingState.onDiskState }) getStudio().ephemeralAtom.setByPointer( (p) => p.coreByProject[projectId]!.loadingState, { type: 'loaded', }, ) } const [browserStateNode, browserStateRef] = useTooltip({}, () => ( The browser's state will override the disk state. )) const [diskStateNode, diskStateRef] = useTooltip({}, () => ( The disk's state will override the browser's state. )) return ( Browser state is not based on disk state.{' '}
Learn more. {browserStateNode} Use browser's state {diskStateNode} Use disk state ) } export default StateConflictRow ================================================ FILE: packages/studio/src/panels/DetailPanel/ProjectDetails.tsx ================================================ import type Project from '@theatre/core/projects/Project' import getStudio from '@theatre/studio/getStudio' import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import React, {useCallback, useState} from 'react' import styled from 'styled-components' import DetailPanelButton from '@theatre/studio/uiComponents/DetailPanelButton' import StateConflictRow from './ProjectDetails/StateConflictRow' import JSZip from 'jszip' import {notify} from '@theatre/studio/notify' import {getAllPossibleAssetIDs} from '@theatre/studio/utils/assets' const Container = styled.div`` const TheExportRow = styled.div` padding: 8px 10px; display: flex; flex-direction: column; align-items: stretch; ` const ExportTooltip = styled(BasicPopover)` display: flex; flex-direction: column; gap: 1em; width: 280px; padding: 1em; ` /** * Initiates a file download for the provided data with the provided file name * * @param content - The content to save * @param fileName - The name of the file to save */ function saveFile(content: string | Blob, fileName: string) { const file = new File([content], fileName) const objUrl = URL.createObjectURL(file) const a = Object.assign(document.createElement('a'), { href: objUrl, target: '_blank', rel: 'noopener', }) a.setAttribute('download', fileName) a.click() setTimeout(() => { URL.revokeObjectURL(objUrl) }, 40000) } const ProjectDetails: React.FC<{ projects: Project[] }> = ({projects}) => { const project = projects[0] const projectId = project.address.projectId const slugifiedProjectId = projectId.replace(/[^\w\d'_\-]+/g, ' ').trim() // const [dateString, _timeString] = new Date().toISOString().split('T') // e.g. `Butterfly.theatre-project-state.json` const suggestedFileName = `${slugifiedProjectId}.theatre-project-state.json` const [downloaded, setDownloaded] = useState(false) const exportProject = useCallback(async () => { // get all possible asset ids referenced by either static props or keyframes const allValues = getAllPossibleAssetIDs(project) const blobs = new Map() try { // only export assets that are referenced by the project await Promise.all( allValues.map(async (value) => { const assetUrl = project.assetStorage.getAssetUrl(value) const response = await fetch(assetUrl) if (response.ok) { blobs.set(value, await response.blob()) } }), ) } catch (e) { notify.error( `Failed to access assets`, `Export aborted. Failed to access assets at ${ project.config.assets?.baseUrl ?? '/' }. This is likely due to a CORS issue.`, ) // abort the export return } if (blobs.size > 0) { const zip = new JSZip() for (const [assetID, blob] of blobs) { zip.file(assetID, blob) } const assetsFile = await zip.generateAsync({type: 'blob'}) saveFile(assetsFile, `${slugifiedProjectId}.assets.zip`) } const str = JSON.stringify( getStudio().createContentOfSaveFile(project.address.projectId), null, 2, ) saveFile(str, suggestedFileName) setDownloaded(true) setTimeout(() => { setDownloaded(false) }, 2000) }, [project, suggestedFileName]) const exportTooltip = usePopover( {debugName: 'ProjectDetails', pointerDistanceThreshold: 50}, () => (

This will create a JSON file with the state of your project. You can commit this file to your git repo and include it in your production bundle.

If your project uses assets, this will also create a zip file with all the assets that you can unpack in your public folder.

Here is a quick guide on how to export to production.
), ) return ( <> {exportTooltip.node} exportTooltip.open(e, e.target as unknown as HTMLButtonElement) } onClick={!downloaded ? exportProject : undefined} disabled={downloaded} > {downloaded ? '(Exported)' : `Export ${projectId} to JSON`} ) } export default ProjectDetails ================================================ FILE: packages/studio/src/panels/OutlinePanel/BaseItem.tsx ================================================ import type {$FixMe, VoidFn} from '@theatre/core/types/public' import React from 'react' import styled, {css} from 'styled-components' import noop from '@theatre/utils/noop' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {ChevronDown, Package} from '@theatre/studio/uiComponents/icons' export const Container = styled.li` margin: 0; padding: 0; list-style: none; display: flex; justify-content: flex-start; flex-direction: column; align-items: flex-start; ` export const BaseHeader = styled.div`` const Header = styled(BaseHeader)` position: relative; margin-top: 2px; margin-bottom: 2px; margin-left: calc(4px + var(--depth) * 16px); padding-left: 4px; padding-right: 8px; gap: 4px; height: 21px; line-height: 0; box-sizing: border-box; display: flex; flex-wrap: nowrap; align-items: center; pointer-events: none; white-space: nowrap; border-radius: 2px; box-shadow: 0 3px 4px -1px rgba(0, 0, 0, 0.48); color: rgba(255, 255, 255, 0.9); background: rgba(40, 43, 47, 0.65); backdrop-filter: blur(14px); border-bottom: 1px solid rgba(255, 255, 255, 0.08); &.descendant-is-selected { background: rgba(29, 53, 59, 0.7); } ${pointerEventsAutoInNormalMode}; &:not(.not-selectable):not(.selected):hover { background: rgba(59, 63, 69, 0.9); border-bottom: 1px solid rgba(255, 255, 255, 0.24); } &:not(.not-selectable):not(.selected):active { background: rgba(82, 88, 96, 0.9); border-bottom: 1px solid rgba(255, 255, 255, 0.24); } &.selected { background: rgba(30, 88, 102, 0.7); border-bottom: 1px solid rgba(255, 255, 255, 0.08); } @supports not (backdrop-filter: blur()) { background: rgba(40, 43, 47, 0.95); } ` export const outlineItemFont = css` font-weight: 500; font-size: 11px; & { } ` const Head_Label = styled.span` ${outlineItemFont}; ${pointerEventsAutoInNormalMode}; position: relative; // Compensate for border bottom top: 0.5px; display: flex; height: 20px; align-items: center; box-sizing: border-box; ` const Head_IconContainer = styled.div` font-weight: 500; display: flex; justify-content: center; align-items: center; position: relative; opacity: 0.99; ` const Head_Icon_WithDescendants = styled.span` font-size: 9px; position: relative; display: block; transition: transform 0.1s ease-out; &:hover { transform: rotate(-20deg); } ${Container}.collapsed & { transform: rotate(-90deg); &:hover { transform: rotate(-70deg); } } ` const ChildrenContainer = styled.ul` margin: 0; padding: 0; list-style: none; ${Container}.collapsed & { display: none; } ` type SelectionStatus = | 'not-selectable' | 'not-selected' | 'selected' | 'descendant-is-selected' const BaseItem: React.FC<{ label: React.ReactNode select?: VoidFn depth: number selectionStatus: SelectionStatus labelDecoration?: React.ReactNode children?: React.ReactNode | undefined collapsed?: boolean setIsCollapsed?: (v: boolean) => void headerRef?: React.MutableRefObject<$FixMe> }> = ({ label, children, depth, select, selectionStatus, labelDecoration, collapsed = false, setIsCollapsed, headerRef, }) => { const canContainChildren = children !== undefined return (
{canContainChildren ? ( { evt.stopPropagation() evt.preventDefault() setIsCollapsed?.(!collapsed) }} > ) : ( )} {label} {labelDecoration}
{canContainChildren && {children}}
) } export default BaseItem ================================================ FILE: packages/studio/src/panels/OutlinePanel/ObjectsList/ObjectItem.tsx ================================================ import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import getStudio from '@theatre/studio/getStudio' import React from 'react' import BaseItem from '@theatre/studio/panels/OutlinePanel/BaseItem' import {useVal} from '@theatre/react' import {outlineSelection} from '@theatre/studio/selectors' import useChordial from '@theatre/studio/uiComponents/chordial/useChodrial' export const ObjectItem: React.VFC<{ sheetObject: SheetObject depth: number overrideLabel?: string }> = ({sheetObject, depth, overrideLabel}) => { const select = () => { getStudio()!.transaction(({stateEditors}) => { stateEditors.studio.historic.panels.outline.selection.set([ {...sheetObject.address, type: 'SheetObject'}, ]) }) } const selection = useVal(outlineSelection) const {targetRef} = useChordial(() => { return { title: `Object: ${sheetObject.address.objectKey}`, items: [], } }) return ( ) } ================================================ FILE: packages/studio/src/panels/OutlinePanel/ObjectsList/ObjectsList.tsx ================================================ import type Sheet from '@theatre/core/sheets/Sheet' import {usePrism} from '@theatre/react' import {val} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import {ObjectItem} from './ObjectItem' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import BaseItem from '@theatre/studio/panels/OutlinePanel/BaseItem' import {useCollapseStateInOutlinePanel} from '@theatre/studio/panels/OutlinePanel/outlinePanelUtils' export const Li = styled.li<{isSelected: boolean}>` color: ${(props) => (props.isSelected ? 'white' : 'hsl(1, 1%, 80%)')}; ` const ObjectsList: React.FC<{ depth: number sheet: Sheet }> = ({sheet, depth}) => { return usePrism(() => { const objectsMap = val(sheet.objectsP) const objects = Object.values(objectsMap).filter( (a): a is SheetObject => a != null, ) const rootObject: NamespacedObjects = new Map() objects.forEach((object) => { addToNamespace(rootObject, object) }) return ( ) }, [sheet, depth]) } function NamespaceTree(props: { namespace: NamespacedObjects visualIndentation: number path: string[] sheet: Sheet }) { return ( <> {[...props.namespace.entries()].map(([label, {object, nested}]) => { return ( ) })} ) } function Namespace(props: { nested?: NamespacedObjects label: string object?: SheetObject visualIndentation: number path: string[] sheet: Sheet }) { const {nested, label, object, sheet} = props const {collapsed, setCollapsed} = useCollapseStateInOutlinePanel({ type: 'namespace', sheet, path: [...props.path, label], }) const nestedChildrenElt = nested && ( ) const sameNameElt = object && ( ) return ( {sameNameElt} {nestedChildrenElt && ( )} ) } export default ObjectsList /** See {@link addToNamespace} for adding to the namespace, easily. */ type NamespacedObjects = Map< string, { object?: SheetObject nested?: NamespacedObjects path: string[] } > function addToNamespace( mutObjects: NamespacedObjects, object: SheetObject, path = getObjectNamespacePath(object), ) { const [next, ...rest] = path let existing = mutObjects.get(next) if (!existing) { existing = { nested: undefined, object: undefined, path: [...path], } mutObjects.set(next, existing) } if (rest.length === 0) { console.assert( !existing.object, 'expect not to have existing object with same name', {existing, object}, ) existing.object = object } else { if (!existing.nested) { existing.nested = new Map() } addToNamespace(existing.nested, object, rest) } } function getObjectNamespacePath(object: SheetObject): string[] { let existing = OBJECT_SPLITS_MEMO.get(object) if (!existing) { existing = object.address.objectKey.split( RE_SPLIT_BY_SLASH_WITHOUT_WHITESPACE, ) console.assert(existing.length > 0, 'expected not empty') OBJECT_SPLITS_MEMO.set(object, existing) } return existing } /** * Relying on the fact we try to "sanitize paths" earlier. * Go look for `sanifySlashedPath` in a `utils/slashedPaths.ts`. */ const RE_SPLIT_BY_SLASH_WITHOUT_WHITESPACE = /\s*\/\s*/g const OBJECT_SPLITS_MEMO = new WeakMap() ================================================ FILE: packages/studio/src/panels/OutlinePanel/OutlinePanel.tsx ================================================ import React, {useEffect, useLayoutEffect} from 'react' import styled from 'styled-components' import {panelZIndexes} from '@theatre/studio/panels/BasePanel/common' import ProjectsList from './ProjectsList/ProjectsList' import {useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import useHotspot from '@theatre/studio/uiComponents/useHotspot' import {Atom, prism, val} from '@theatre/dataverse' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' const headerHeight = `44px` const Container = styled.div<{pin: boolean}>` ${pointerEventsAutoInNormalMode}; background-color: transparent; position: absolute; left: 8px; z-index: ${panelZIndexes.outlinePanel}; top: calc(${headerHeight} + 8px); height: fit-content; max-height: calc(100% - ${headerHeight}); overflow-y: scroll; overflow-x: hidden; padding: 0; user-select: none; &::-webkit-scrollbar { display: none; } scrollbar-width: none; display: ${({pin}) => (pin ? 'block' : 'none')}; &:hover { display: block; } // Create a small buffer on the bottom to aid selecting the bottom item in a long, scrolling list &::after { content: ''; display: block; height: 20px; } ` const OutlinePanel: React.FC<{}> = () => { const pin = useVal(getStudio().atomP.ahistoric.pinOutline) ?? true const show = useVal(shouldShowOutlineD) const active = useHotspot('left') useLayoutEffect(() => { isOutlinePanelHotspotActiveB.set(active) }, [active]) // cleanup useEffect(() => { return () => { isOutlinePanelHoveredB.set(false) isOutlinePanelHotspotActiveB.set(false) } }, []) return ( { isOutlinePanelHoveredB.set(true) }} onMouseLeave={() => { isOutlinePanelHoveredB.set(false) }} > ) } export default OutlinePanel const isOutlinePanelHotspotActiveB = new Atom(false) const isOutlinePanelHoveredB = new Atom(false) export const shouldShowOutlineD = prism(() => { const isHovered = val(isOutlinePanelHoveredB.prism) const isHotspotActive = val(isOutlinePanelHotspotActiveB.prism) return isHovered || isHotspotActive }) ================================================ FILE: packages/studio/src/panels/OutlinePanel/ProjectsList/ProjectListItem.tsx ================================================ import type Project from '@theatre/core/projects/Project' import React, {useCallback} from 'react' import BaseItem from '@theatre/studio/panels/OutlinePanel/BaseItem' import SheetsList from '@theatre/studio/panels/OutlinePanel/SheetsList/SheetsList' import getStudio from '@theatre/studio/getStudio' import {usePrism, useVal} from '@theatre/react' import {outlineSelection} from '@theatre/studio/selectors' import {val} from '@theatre/dataverse' import styled from 'styled-components' import {useCollapseStateInOutlinePanel} from '@theatre/studio/panels/OutlinePanel/outlinePanelUtils' import useChordial from '@theatre/studio/uiComponents/chordial/useChodrial' const ConflictNotice = styled.div` color: #ff6363; margin-left: 11px; background: #4c282d; padding: 2px 8px; border-radius: 2px; font-size: 10px; box-shadow: 0 2px 8px -4px black; ` const ProjectListItem: React.FC<{ depth: number project: Project }> = ({depth, project}) => { const selection = useVal(outlineSelection) const hasConflict = usePrism(() => { const projectId = project.address.projectId const loadingState = val( getStudio().ephemeralAtom.pointer.coreByProject[projectId].loadingState, ) return loadingState?.type === 'browserStateIsNotBasedOnDiskState' }, [project]) const select = useCallback(() => { getStudio().transaction(({stateEditors}) => { stateEditors.studio.historic.panels.outline.selection.set([ {...project.address, type: 'Project'}, ]) }) }, [project]) const {collapsed, setCollapsed} = useCollapseStateInOutlinePanel(project) const {targetRef} = useChordial(() => { return { title: `Project: ${project.address.projectId}`, items: [], } }) return ( Has Conflicts : null } children={} selectionStatus={ selection.includes(project) ? 'selected' : selection.some( (s) => s.address.projectId === project.address.projectId, ) ? 'descendant-is-selected' : 'not-selected' } select={select} /> ) } export default ProjectListItem ================================================ FILE: packages/studio/src/panels/OutlinePanel/ProjectsList/ProjectsList.tsx ================================================ import {val} from '@theatre/dataverse' import {usePrism} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import React from 'react' import styled from 'styled-components' import ProjectListItem from './ProjectListItem' const Container = styled.ul` list-style: none; margin: 0; padding: 0; padding-right: 4px; ` const ProjectsList: React.FC<{}> = (props) => { return usePrism(() => { const projects = val(getStudio().projectsP) return ( {Object.keys(projects).map((projectId) => { const project = projects[projectId] return ( ) })} ) }, []) } export default ProjectsList ================================================ FILE: packages/studio/src/panels/OutlinePanel/SheetsList/SheetInstanceItem.tsx ================================================ import getStudio from '@theatre/studio/getStudio' import {outlineSelection} from '@theatre/studio/selectors' import {useVal} from '@theatre/react' import React, {useCallback} from 'react' import styled from 'styled-components' import ObjectsList from '@theatre/studio/panels/OutlinePanel/ObjectsList/ObjectsList' import BaseItem from '@theatre/studio/panels/OutlinePanel/BaseItem' import type Sheet from '@theatre/core/sheets/Sheet' import {useCollapseStateInOutlinePanel} from '@theatre/studio/panels/OutlinePanel/outlinePanelUtils' import useChordial from '@theatre/studio/uiComponents/chordial/useChodrial' const Head = styled.div` display: flex; ` const Body = styled.div`` export const SheetInstanceItem: React.FC<{ depth: number sheet: Sheet }> = ({sheet, depth}) => { const {collapsed, setCollapsed} = useCollapseStateInOutlinePanel(sheet) const setSelectedSheet = useCallback(() => { getStudio()!.transaction(({stateEditors}) => { stateEditors.studio.historic.panels.outline.selection.set([ {...sheet.address, type: 'Sheet'}, ]) }) }, [sheet]) const selection = useVal(outlineSelection) const {targetRef} = useChordial(() => { return { title: `Sheet: ${sheet.address.sheetId} (instance: ${sheet.address.sheetInstanceId})`, items: [], } }) return ( s === sheet) ? 'selected' : selection.some( (s) => s.type === 'Theatre_SheetObject' && s.sheet === sheet, ) ? 'descendant-is-selected' : 'not-selected' } label={ {sheet.address.sheetId}: {sheet.address.sheetInstanceId} } > ) } ================================================ FILE: packages/studio/src/panels/OutlinePanel/SheetsList/SheetItem.tsx ================================================ import type Project from '@theatre/core/projects/Project' import {usePrism} from '@theatre/react' import {val} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import {SheetInstanceItem} from './SheetInstanceItem' const Head = styled.div` display: flex; ` const Container = styled.li<{isSelected: boolean}>` color: ${(props) => (props.isSelected ? 'white' : 'hsl(1, 1%, 80%)')}; ` const Body = styled.div`` export const SheetItem: React.FC<{ depth: number sheetId: string project: Project }> = ({sheetId, depth, project}) => { return usePrism(() => { const template = val(project.sheetTemplatesP[sheetId]) if (!template) return <> const allInstances = val(template.instancesP) return ( <> {Object.entries(allInstances).map(([_, inst]) => { return ( ) })} ) }, [depth, sheetId, project]) } ================================================ FILE: packages/studio/src/panels/OutlinePanel/SheetsList/SheetsList.tsx ================================================ import {getRegisteredSheetIds} from '@theatre/studio/selectors' import {usePrism} from '@theatre/react' import React from 'react' import {SheetItem} from './SheetItem' import type Project from '@theatre/core/projects/Project' const SheetsList: React.FC<{ project: Project depth: number }> = ({project, depth}) => { return usePrism(() => { if (!project) return null const registeredSheetIds = getRegisteredSheetIds(project) return ( <> {registeredSheetIds.map((sheetId) => { return ( ) })} ) }, [project, depth]) } export default SheetsList ================================================ FILE: packages/studio/src/panels/OutlinePanel/outlinePanelUtils.ts ================================================ import type Project from '@theatre/core/projects/Project' import {useCallback} from 'react' import getStudio from '@theatre/studio/getStudio' import {useVal} from '@theatre/react' import type Sheet from '@theatre/core/sheets/Sheet' export function useCollapseStateInOutlinePanel( item: Project | Sheet | {type: 'namespace'; sheet: Sheet; path: string[]}, ): { collapsed: boolean setCollapsed: (collapsed: boolean) => void } { const itemKey = item.type === 'namespace' ? `namespace:${item.sheet.address.sheetId}:${item.path.join('/')}` : item.type === 'Theatre_Project' ? 'project' : item.type === 'Theatre_Sheet' ? `sheetInstance:${item.address.sheetId}:${item.address.sheetInstanceId}` : 'unknown' const projectId = item.type === 'namespace' ? item.sheet.address.projectId : item.address.projectId const isCollapsed = useVal( getStudio().atomP.ahistoric.projects.stateByProjectId[projectId] .collapsedItemsInOutline[itemKey], ) ?? false const setCollapsed = useCallback( (isCollapsed: boolean) => { getStudio().transaction(({stateEditors}) => { stateEditors.studio.ahistoric.projects.stateByProjectId.collapsedItemsInOutline.set( {projectId, isCollapsed, itemKey: itemKey}, ) }) }, [itemKey], ) return {collapsed: isCollapsed, setCollapsed} } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/AGGREGATE_COPY_PASTE.md ================================================ ## The keyframe copy/paste algorithm The copy and paste algorithms are specified below. Note that the copy algorithm is written with some capital letters to emphasize organization, its not an actual language or anything. The copy algorithm changed recently and the examples are more up-to-date than the paste algorithm because pasting stayed the same. ``` ALGORITHM copy: LET PATH = CASE copy selection / single track THEN the path relative to the closest common ancestor for the tracks selected CASE copy aggregate track THEN the path relative the aggregate track compoundProp/sheetObject/sheet FOR EXAMPLE CASE copy selection / single track: - obj1.props.transform.position.x => x - obj1.props.transform.position.{x, z} => {x, z} - obj1.props.transform.position.{x, z} + obj1.props.transform.rotation.z => {position: {x, z}, rotation: {z}} FOR EXAMPLE CASE copy aggregate track: - sheet.obj1.props.transform.position => {x, y, z} - sheet.obj1.props.transform => {position: {x, y, z}, rotation: {x, y, z}} - sheet => { obj1: { props: { transform: {position: {x, y, z}, rotation: {x, y, z}}}}} ALGORITHM: paste: - simple => simple => 1-1 - simple => {x, y} => {x: simple, y: simple} (distribute to all) - compound => simple => compound[0] (the first simple property of the comopund, recursively) - compound => compound => - if they match perfectly, then we know what to do - if they match partially, then we paste partially - {x, y, z} => {x, z} => {x, z} - {x, y} => {x, d} => {x} - if they don't match at all - {x, y} => {a, b} => nothing - {x, y} => {transforms: {position: {x, y, z}}} => nothing - {x, y} => {object(not a prop): {x, y}} => {x, y} - What this means is that, in case of objects and sheets, we do a forEach at each object, then try pasting onto its object.props ``` ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/DopeSheet.tsx ================================================ import {useVal} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import Left from './Left/Left' import DopeSheetBackground from './Right/DopeSheetBackground' import Right from './Right/Right' import VerticalScrollContainer from '@theatre/studio/panels/SequenceEditorPanel/VerticalScrollContainer' const Container = styled.div` position: absolute; left: 0; right: 0; ` const DopeSheet: React.VFC<{layoutP: Pointer}> = ({ layoutP, }) => { const height = useVal(layoutP.dopeSheetDims.height) return ( ) } export default DopeSheet ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/AnyCompositeRow.tsx ================================================ import {theme} from '@theatre/studio/css' import type { SequenceEditorTree_PrimitiveProp, SequenceEditorTree_PropWithChildren, SequenceEditorTree_Sheet, SequenceEditorTree_SheetObject, } from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import type {VoidFn} from '@theatre/core/types/public' import React, {useRef} from 'react' import {HiOutlineChevronRight} from 'react-icons/hi' import styled from 'styled-components' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' import {usePropHighlightMouseEnter} from './usePropHighlightMouseEnter' export const LeftRowContainer = styled.li<{depth: number}>` --depth: ${(props) => props.depth}; margin: 0; padding: 0; list-style: none; ` export const BaseHeader = styled.div<{isEven: boolean}>` border-bottom: 1px solid #7695b705; ` const LeftRowHeader = styled(BaseHeader)<{ isSelectable: boolean isSelected: boolean }>` padding-left: calc(0px + var(--depth) * 20px); display: flex; align-items: stretch; color: ${theme.panel.body.compoudThing.label.color}; box-sizing: border-box; ${(props) => props.isSelected && `background: blue`}; ` const LeftRowHead_Label = styled.span` ${propNameTextCSS}; overflow-x: hidden; text-overflow: ellipsis; white-space: nowrap; padding-right: 4px; line-height: 26px; flex-wrap: nowrap; ${LeftRowHeader}:hover & { color: #ccc; } ` const LeftRowHead_Icon = styled.span<{isCollapsed: boolean}>` width: 12px; padding: 8px; font-size: 9px; display: flex; align-items: center; transition: transform 0.05s ease-out, color 0.1s ease-out; transform: rotateZ(${(props) => (props.isCollapsed ? 0 : 90)}deg); color: #66686a; &:hover { transform: rotateZ(${(props) => (props.isCollapsed ? 15 : 75)}deg); color: #c0c4c9; } ` const LeftRowChildren = styled.ul` margin: 0; padding: 0; list-style: none; ` const AnyCompositeRow: React.FC<{ leaf: | SequenceEditorTree_Sheet | SequenceEditorTree_PrimitiveProp | SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject label: React.ReactNode toggleSelect?: VoidFn toggleCollapsed: VoidFn isSelected?: boolean isSelectable?: boolean isCollapsed: boolean children?: React.ReactNode }> = ({ leaf, label, children, isSelectable, isSelected, toggleSelect, toggleCollapsed, isCollapsed, }) => { const hasChildren = Array.isArray(children) && children.length > 0 const rowHeaderRef = useRef(null) usePropHighlightMouseEnter(rowHeaderRef.current, leaf) return leaf.shouldRender ? ( {label} {hasChildren && {children}} ) : null } export default AnyCompositeRow ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/Left.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {usePrism} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import SheetRow from './SheetRow' const Container = styled.div` position: absolute; left: 0; overflow-x: visible; ` const ListContainer = styled.ul` margin: 0; padding: 0; list-style: none; ` const Left: React.VFC<{ layoutP: Pointer }> = ({layoutP}) => { return usePrism(() => { const tree = val(layoutP.tree) const width = val(layoutP.leftDims.width) return ( ) }, [layoutP]) } export default Left ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/PrimitivePropRow.tsx ================================================ import type {SequenceEditorTree_PrimitiveProp} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import getStudio from '@theatre/studio/getStudio' import {encodePathToProp} from '@theatre/utils/pathToProp' import pointerDeep from '@theatre/utils/pointerDeep' import {usePrism} from '@theatre/react' import type {$IntentionalAny} from '@theatre/core/types/public' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React, {useCallback, useRef} from 'react' import styled from 'styled-components' import {useEditingToolsForSimplePropInDetailsPanel} from '@theatre/studio/propEditors/useEditingToolsForSimpleProp' import {nextPrevCursorsTheme} from '@theatre/studio/propEditors/NextPrevKeyframeCursors' import {BaseHeader, LeftRowContainer as BaseContainer} from './AnyCompositeRow' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' import {usePropHighlightMouseEnter} from './usePropHighlightMouseEnter' import type {GraphEditorColors} from '@theatre/core/types/private' import {graphEditorColors} from '@theatre/sync-server/state/schema' const theme = { label: { color: `#9a9a9a`, }, } const PrimitivePropRowContainer = styled(BaseContainer)<{}>`` const PrimitivePropRowHead = styled(BaseHeader)<{ isSelected: boolean isEven: boolean }>` display: flex; color: ${theme.label.color}; padding-right: 12px; align-items: center; justify-content: flex-end; box-sizing: border-box; ` const PrimitivePropRowIconContainer = styled.button<{ isSelected: boolean graphEditorColor: keyof GraphEditorColors }>` background: none; border: none; outline: none; display: flex; box-sizing: border-box; font-size: 14px; align-items: center; height: 100%; margin-left: 12px; color: ${(props) => props.isSelected ? graphEditorColors[props.graphEditorColor].iconColor : nextPrevCursorsTheme.offColor}; &:not([disabled]):hover { color: white; } ` const GraphIcon = () => ( ) const PrimitivePropRowHead_Label = styled.span` margin-right: 4px; ${propNameTextCSS}; ${PrimitivePropRowHead}:hover & { color: #ccc; } ` const PrimitivePropRow: React.FC<{ leaf: SequenceEditorTree_PrimitiveProp }> = ({leaf}) => { const pointerToProp = pointerDeep( leaf.sheetObject.propsP, leaf.pathToProp, ) as Pointer<$IntentionalAny> const obj = leaf.sheetObject const {controlIndicators} = useEditingToolsForSimplePropInDetailsPanel( pointerToProp, obj, leaf.propConf, ) const possibleColor = usePrism(() => { const c = leaf.sheetObject.address const encodedPathToProp = encodePathToProp(leaf.pathToProp) return val( getStudio()!.atomP.historic.projects.stateByProjectId[c.projectId] .stateBySheetId[c.sheetId].sequenceEditor.selectedPropsByObject[ c.objectKey ][encodedPathToProp], ) }, [leaf]) const isSelectedRef = useRef(false) const isSelected = typeof possibleColor === 'string' isSelectedRef.current = isSelected const toggleSelect = useCallback(() => { const c = leaf.sheetObject.address getStudio()!.transaction(({stateEditors}) => { if (isSelectedRef.current) { stateEditors.studio.historic.projects.stateByProjectId.stateBySheetId.sequenceEditor.removePropFromGraphEditor( {...c, pathToProp: leaf.pathToProp}, ) } else { stateEditors.studio.historic.projects.stateByProjectId.stateBySheetId.sequenceEditor.addPropToGraphEditor( {...c, pathToProp: leaf.pathToProp}, ) stateEditors.studio.historic.panels.sequenceEditor.graphEditor.setIsOpen( { isOpen: true, }, ) } }) }, [leaf]) const label = leaf.propConf.label ?? leaf.pathToProp[leaf.pathToProp.length - 1] const isSelectable = true const headRef = useRef(null) usePropHighlightMouseEnter(headRef.current, leaf) return ( {label} {controlIndicators} ) } export default PrimitivePropRow ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/PropWithChildrenRow.tsx ================================================ import type { SequenceEditorTree_PrimitiveProp, SequenceEditorTree_PropWithChildren, } from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import React from 'react' import AnyCompositeRow from './AnyCompositeRow' import PrimitivePropRow from './PrimitivePropRow' import {setCollapsedSheetItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp' export const decideRowByPropType = ( leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp, ): React.ReactElement => { const key = 'prop' + leaf.pathToProp[leaf.pathToProp.length - 1] return leaf.shouldRender ? ( leaf.type === 'propWithChildren' ? ( ) : ( ) ) : ( ) } const PropWithChildrenRow: React.VFC<{ leaf: SequenceEditorTree_PropWithChildren }> = ({leaf}) => { return ( setCollapsedSheetItem(!leaf.isCollapsed, { sheetAddress: leaf.sheetObject.address, sheetItemKey: leaf.sheetItemKey, }) } > {leaf.children.map((propLeaf) => decideRowByPropType(propLeaf))} ) } export default PropWithChildrenRow ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/SheetObjectRow.tsx ================================================ import type {SequenceEditorTree_SheetObject} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import React from 'react' import AnyCompositeRow from './AnyCompositeRow' import {decideRowByPropType} from './PropWithChildrenRow' import {setCollapsedSheetItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp' import getStudio from '@theatre/studio/getStudio' const LeftSheetObjectRow: React.VFC<{ leaf: SequenceEditorTree_SheetObject }> = ({leaf}) => { return ( { // set selection to this sheet object on click getStudio().transaction(({stateEditors}) => { stateEditors.studio.historic.panels.outline.selection.set([ {type: 'SheetObject', ...leaf.sheetObject.address}, ]) }) }} toggleCollapsed={() => setCollapsedSheetItem(!leaf.isCollapsed, { sheetAddress: leaf.sheetObject.address, sheetItemKey: leaf.sheetItemKey, }) } > {leaf.children.map((leaf) => decideRowByPropType(leaf))} ) } export default LeftSheetObjectRow ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/SheetRow.tsx ================================================ import type {SequenceEditorTree_Sheet} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import {usePrism} from '@theatre/react' import React from 'react' import LeftSheetObjectRow from './SheetObjectRow' import AnyCompositeRow from './AnyCompositeRow' import {setCollapsedSheetItem} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp' import uniqueKeyForAnyObject from '@theatre/utils/uniqueKeyForAnyObject' const SheetRow: React.VFC<{ leaf: SequenceEditorTree_Sheet }> = ({leaf}) => { return usePrism(() => { return ( { setCollapsedSheetItem(!leaf.isCollapsed, { sheetAddress: leaf.sheet.address, sheetItemKey: leaf.sheetItemKey, }) }} > {leaf.children.map((sheetObjectLeaf) => ( ))} ) }, [leaf]) } export default SheetRow ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Left/usePropHighlightMouseEnter.tsx ================================================ import type {SequenceEditorTree_AllRowTypes} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import type {PropAddress} from '@theatre/core/types/public' import {useLayoutEffect} from 'react' import {whatPropIsHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted' /** This should ignore if */ export function usePropHighlightMouseEnter( node: HTMLElement | null, leaf: SequenceEditorTree_AllRowTypes, ) { useLayoutEffect(() => { if (!node) return if ( leaf.type !== 'propWithChildren' && leaf.type !== 'primitiveProp' && leaf.type !== 'sheetObject' ) return let unlock: null | (() => void) = null const propAddress: PropAddress = { ...leaf.sheetObject.address, pathToProp: leaf.type === 'sheetObject' ? [] : leaf.pathToProp, } function onMouseEnter() { unlock = whatPropIsHighlighted.replaceLock(propAddress, () => { // cleanup on forced unlock }) } function onMouseLeave() { unlock?.() } node.addEventListener('mouseenter', onMouseEnter) node.addEventListener('mouseleave', onMouseLeave) return () => { unlock?.() node.removeEventListener('mouseenter', onMouseEnter) node.removeEventListener('mouseleave', onMouseLeave) } }, [node]) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeConnector.tsx ================================================ import {val} from '@theatre/dataverse' import React, {useMemo, useRef} from 'react' import {ConnectorLine} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/keyframeRowUI/ConnectorLine' import {AggregateKeyframePositionIsSelected} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import useRefAndState from '@theatre/studio/utils/useRefAndState' import type {DragOpts} from '@theatre/studio/uiComponents/useDrag' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import getStudio from '@theatre/studio/getStudio' import useDrag from '@theatre/studio/uiComponents/useDrag' import type {IAggregateKeyframeEditorUtils} from './useAggregateKeyframeEditorUtils' import CurveEditorPopover from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/CurveEditorPopover' import {useAggregateKeyframeEditorUtils} from './useAggregateKeyframeEditorUtils' import type {IAggregateKeyframeEditorProps} from './AggregateKeyframeEditor' import styled from 'styled-components' import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover' import { copyableKeyframesFromSelection, keyframesWithPaths, } from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import {commonRootOfPathsToProps} from '@theatre/utils/pathToProp' import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/core/types/private' import {__private} from '@theatre/core' const {keyframeUtils} = __private const POPOVER_MARGIN_PX = 5 const EasingPopoverWrapper = styled(BasicPopover)` --popover-outer-stroke: transparent; --popover-inner-stroke: rgba(26, 28, 30, 0.97); ` export const AggregateCurveEditorPopover: React.FC< IAggregateKeyframeEditorProps & {closePopover: (reason: string) => void} > = React.forwardRef((props, ref) => { const {allConnections} = useAggregateKeyframeEditorUtils(props) return ( ) }) export const AggregateKeyframeConnector: React.VFC< IAggregateKeyframeConnectorProps > = (props) => { const [nodeRef, node] = useRefAndState(null) const {editorProps} = props const [contextMenu] = useConnectorContextMenu(props, node) const [isDragging] = useDragKeyframe(node, props.editorProps) const { node: popoverNode, toggle: togglePopover, close: closePopover, } = usePopover( () => { const rightDims = val(editorProps.layoutP.rightDims) return { debugName: 'Connector', constraints: { minX: rightDims.screenX + POPOVER_MARGIN_PX, maxX: rightDims.screenX + rightDims.width - POPOVER_MARGIN_PX, }, } }, () => { return ( ) }, ) const {connected, isAggregateEditingInCurvePopover} = props.utils // We don't want to interrupt an existing drag, so in order to persist the dragged // html node, we just set the connector length to 0, but we don't remove it yet. return connected || isDragging ? ( <> { if (node) togglePopover(e, node) }} /> {popoverNode} {contextMenu} ) : ( <> ) } type IAggregateKeyframeConnectorProps = { utils: IAggregateKeyframeEditorUtils editorProps: IAggregateKeyframeEditorProps } function useDragKeyframe( node: HTMLDivElement | null, editorProps: IAggregateKeyframeEditorProps, ) { const propsRef = useRef(editorProps) propsRef.current = editorProps const gestureHandlers = useMemo(() => { return { debugName: 'useDragKeyframe', lockCSSCursorTo: 'ew-resize', onDragStart(event) { const props = propsRef.current let tempTransaction: CommitOrDiscardOrRecapture | undefined const keyframes = props.aggregateKeyframes[props.index].keyframes const {selection, viewModel} = props const address = viewModel.type === 'sheet' ? viewModel.sheet.address : viewModel.sheetObject.address if ( selection && props.aggregateKeyframes[props.index].selected === AggregateKeyframePositionIsSelected.AllSelected ) { return selection .getDragHandlers({ ...address, domNode: node!, positionAtStartOfDrag: props.aggregateKeyframes[props.index].position, }) .onDragStart(event) } const propsAtStartOfDrag = props const sequence = val(propsAtStartOfDrag.layoutP.sheet).getSequence() const toUnitSpace = val( propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, ) return { onDrag(dx, dy, event) { const delta = toUnitSpace(dx) if (tempTransaction) { tempTransaction.discard() tempTransaction = undefined } tempTransaction = getStudio().tempTransaction(({stateEditors}) => { for (const keyframe of keyframes) { const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( keyframe.track.data.keyframes, ) stateEditors.coreByProject.historic.sheetsById.sequence.transformKeyframes( { ...keyframe.track.sheetObject.address, trackId: keyframe.track.id, keyframeIds: [ keyframe.kf.id, sortedKeyframes[sortedKeyframes.indexOf(keyframe.kf) + 1] .id, ], translate: delta, scale: 1, origin: 0, snappingFunction: sequence.closestGridPosition, }, ) } }) }, onDragEnd(dragHappened) { if (dragHappened) { if (tempTransaction) { tempTransaction.commit() } } else { if (tempTransaction) { tempTransaction.discard() } } }, } }, } }, []) return useDrag(node, gestureHandlers) } function useConnectorContextMenu( props: IAggregateKeyframeConnectorProps, node: HTMLDivElement | null, ) { return useContextMenu(node, { displayName: 'Aggregate Tween', items: () => { // see AGGREGATE_COPY_PASTE.md for explanation of this // code that makes some keyframes with paths for copying // to clipboard const kfs = props.utils.allConnections.reduce( (acc, con) => acc.concat( keyframesWithPaths({ ...con, keyframeIds: [con.left.id, con.right.id], }) ?? [], ), [] as KeyframeWithPathToPropFromCommonRoot[], ) const commonPath = commonRootOfPathsToProps( kfs.map((kf) => kf.pathToProp), ) const keyframesWithCommonRootPath = kfs.map(({keyframe, pathToProp}) => ({ keyframe, pathToProp: pathToProp.slice(commonPath.length), })) const viewModel = props.editorProps.viewModel const address = viewModel.type === 'sheet' ? viewModel.sheet.address : viewModel.sheetObject.address return [ { type: 'normal', label: 'Copy', callback: () => { if (props.editorProps.selection) { const copyableKeyframes = copyableKeyframesFromSelection( address.projectId, address.sheetId, props.editorProps.selection, ) getStudio().transaction((api) => { api.stateEditors.studio.ahistoric.setClipboardKeyframes( copyableKeyframes, ) }) } else { getStudio().transaction((api) => { api.stateEditors.studio.ahistoric.setClipboardKeyframes( keyframesWithCommonRootPath, ) }) } }, }, { type: 'normal', label: 'Delete', callback: () => { if (props.editorProps.selection) { props.editorProps.selection.delete() } else { getStudio().transaction(({stateEditors}) => { for (const con of props.utils.allConnections) { stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes( { ...address, objectKey: con.objectKey, keyframeIds: [con.left.id, con.right.id], trackId: con.trackId, }, ) } }) } }, }, ] }, }) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeDot.tsx ================================================ import React from 'react' import useRefAndState from '@theatre/studio/utils/useRefAndState' import usePresence, { PresenceFlag, } from '@theatre/studio/uiComponents/usePresence' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import type {IAggregateKeyframeEditorProps} from './AggregateKeyframeEditor' import type {IAggregateKeyframeEditorUtils} from './useAggregateKeyframeEditorUtils' import {AggregateKeyframeVisualDot, HitZone} from './AggregateKeyframeVisualDot' import getStudio from '@theatre/studio/getStudio' import { copyableKeyframesFromSelection, keyframesWithPaths, } from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections' import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/core/types/private' import {commonRootOfPathsToProps} from '@theatre/utils/pathToProp' import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap' import type { PrimitivePropEditingOptions, PropWithChildrenEditingOptionsTree, SheetObjectEditingOptionsTree, } from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover' import {useKeyframeInlineEditorPopover} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover' import type { SequenceEditorTree_PrimitiveProp, SequenceEditorTree_PropWithChildren, SequenceEditorTree_SheetObject, } from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import type {KeyframeWithTrack} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes' type IAggregateKeyframeDotProps = { editorProps: IAggregateKeyframeEditorProps utils: IAggregateKeyframeEditorUtils } const isOptionsTreeNodeNotNull = ( a: PropWithChildrenEditingOptionsTree | PrimitivePropEditingOptions | null, ): a is PropWithChildrenEditingOptionsTree | PrimitivePropEditingOptions => a !== null function sheetObjectBuild( viewModel: SequenceEditorTree_SheetObject, keyframes: KeyframeWithTrack[], ): SheetObjectEditingOptionsTree | null { const children = viewModel.children .map((a) => a.type === 'propWithChildren' ? propWithChildrenBuild(a, keyframes) : primitivePropBuild(a, keyframes), ) .filter(isOptionsTreeNodeNotNull) if (children.length === 0) return null return { type: 'sheetObject', sheetObject: viewModel.sheetObject, children, } } function propWithChildrenBuild( viewModel: SequenceEditorTree_PropWithChildren, keyframes: KeyframeWithTrack[], ): PropWithChildrenEditingOptionsTree | null { const children = viewModel.children .map((a) => a.type === 'propWithChildren' ? propWithChildrenBuild(a, keyframes) : primitivePropBuild(a, keyframes), ) .filter(isOptionsTreeNodeNotNull) if (children.length === 0) return null return { type: 'propWithChildren', pathToProp: viewModel.pathToProp, propConfig: viewModel.propConf, children, } } function primitivePropBuild( viewModelLeaf: SequenceEditorTree_PrimitiveProp, keyframes: KeyframeWithTrack[], ): PrimitivePropEditingOptions | null { const keyframe = keyframes.find((kf) => kf.track.id === viewModelLeaf.trackId) if (!keyframe) return null return { type: 'primitiveProp', keyframe: keyframe.kf, pathToProp: viewModelLeaf.pathToProp, propConfig: viewModelLeaf.propConf, sheetObject: viewModelLeaf.sheetObject, trackId: viewModelLeaf.trackId, } } export function AggregateKeyframeDot( props: React.PropsWithChildren, ) { const {cur} = props.utils const inlineEditorPopover = useKeyframeInlineEditorPopover( props.editorProps.viewModel.type === 'sheet' ? null : props.editorProps.viewModel.type === 'sheetObject' ? sheetObjectBuild(props.editorProps.viewModel, cur.keyframes) ?.children ?? null : propWithChildrenBuild(props.editorProps.viewModel, cur.keyframes) ?.children ?? null, ) const presence = usePresence(props.utils.itemKey) presence.useRelations( () => cur.keyframes.map((kf) => ({ affects: kf.itemKey, flag: PresenceFlag.Primary, })), [ // Hmm: Is this a valid fix for the changing size of the useEffect's dependency array? // also: does it work properly with selections? cur.keyframes .map((keyframeWithTrack) => keyframeWithTrack.track.id) .join('-'), ], ) const [ref, node] = useRefAndState(null) const [contextMenu] = useAggregateKeyframeContextMenu(props, node) return ( <> props.editorProps.viewModel.type !== 'sheet' ? inlineEditorPopover.open(e, ref.current!) : null } /> {contextMenu} {inlineEditorPopover.node} ) } function useAggregateKeyframeContextMenu( props: IAggregateKeyframeDotProps, target: HTMLDivElement | null, ) { return useContextMenu(target, { displayName: 'Aggregate Keyframe', items: () => { const viewModel = props.editorProps.viewModel const selection = props.editorProps.selection return [ { type: 'normal', label: selection ? 'Copy (selection)' : 'Copy', callback: () => { // see AGGREGATE_COPY_PASTE.md for explanation of this // code that makes some keyframes with paths for copying // to clipboard if (selection) { const {projectId, sheetId} = viewModel.type === 'sheet' ? viewModel.sheet.address : viewModel.sheetObject.address const copyableKeyframes = copyableKeyframesFromSelection( projectId, sheetId, selection, ) getStudio().transaction((api) => { api.stateEditors.studio.ahistoric.setClipboardKeyframes( copyableKeyframes, ) }) } else { const kfs: KeyframeWithPathToPropFromCommonRoot[] = props.utils.cur.keyframes.flatMap( (kfWithTrack) => keyframesWithPaths({ ...kfWithTrack.track.sheetObject.address, trackId: kfWithTrack.track.id, keyframeIds: [kfWithTrack.kf.id], }) ?? [], ) const basePathRelativeToSheet = viewModel.type === 'sheet' ? [] : viewModel.type === 'sheetObject' ? [viewModel.sheetObject.address.objectKey] : viewModel.type === 'propWithChildren' ? [ viewModel.sheetObject.address.objectKey, ...viewModel.pathToProp, ] : [] // should be unreachable unless new viewModel/leaf types are added const commonPath = commonRootOfPathsToProps([ basePathRelativeToSheet, ...kfs.map((kf) => kf.pathToProp), ]) const keyframesWithCommonRootPath = kfs.map( ({keyframe, pathToProp}) => ({ keyframe, pathToProp: pathToProp.slice(commonPath.length), }), ) getStudio().transaction((api) => { api.stateEditors.studio.ahistoric.setClipboardKeyframes( keyframesWithCommonRootPath, ) }) } }, }, { type: 'normal', label: selection ? 'Delete (selection)' : 'Delete', callback: () => { if (selection) { selection.delete() } else { getStudio().transaction(({stateEditors}) => { for (const kfWithTrack of props.utils.cur.keyframes) { stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes( { ...kfWithTrack.track.sheetObject.address, keyframeIds: [kfWithTrack.kf.id], trackId: kfWithTrack.track.id, }, ) } }) } }, }, ] }, }) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeEditor.tsx ================================================ import type {BasicKeyframe} from '@theatre/core/types/public' import type { DopeSheetSelection, SequenceEditorPanelLayout, } from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type { SequenceEditorTree_PropWithChildren, SequenceEditorTree_Sheet, SequenceEditorTree_SheetObject, } from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import type {SequenceTrackId} from '@theatre/core/types/public' import type {AggregateKeyframePositionIsSelected} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack' import type {KeyframeWithTrack} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes' import type {SheetObjectAddress} from '@theatre/core/types/public' import {AggregateKeyframeConnector} from './AggregateKeyframeConnector' import {useAggregateKeyframeEditorUtils} from './useAggregateKeyframeEditorUtils' import {AggregateKeyframeDot} from './AggregateKeyframeDot' const AggregateKeyframeEditorContainer = styled.div` position: absolute; ` export type IAggregateKeyframesAtPosition = { position: number /** all tracks have a keyframe for this position (otherwise, false means 'partial') */ allHere: boolean selected: AggregateKeyframePositionIsSelected | undefined keyframes: KeyframeWithTrack[] } export type AggregatedKeyframeConnection = SheetObjectAddress & { trackId: SequenceTrackId left: BasicKeyframe right: BasicKeyframe } export type IAggregateKeyframeEditorProps = { index: number aggregateKeyframes: IAggregateKeyframesAtPosition[] layoutP: Pointer viewModel: | SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject | SequenceEditorTree_Sheet selection: undefined | DopeSheetSelection } /** * TODO we're spending a lot of cycles on each render of each aggreagte keyframes. * * Each keyframe node is doing O(N) operations, N being the number of underlying * keyframes it represetns. * * The biggest example is the `isConnectionEditingInCurvePopover()` call which is run * for every underlying keyframe, every time this component is rendered. * * We can optimize this away by doing all of this work _once_ when a curve editor popover * is open. This would require having some kind of stable identity for each aggregate row. * Let's defer that work until other interactive keyframe editing PRs are merged in. */ const AggregateKeyframeEditor: React.VFC = React.memo((props) => { const utils = useAggregateKeyframeEditorUtils(props) return ( ) }) export default AggregateKeyframeEditor ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeVisualDot.tsx ================================================ import React from 'react' import {AggregateKeyframePositionIsSelected} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack' import {PresenceFlag} from '@theatre/studio/uiComponents/usePresence' import styled from 'styled-components' import {absoluteDims} from '@theatre/studio/utils/absoluteDims' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' const DOT_SIZE_PX = 16 const DOT_HOVER_SIZE_PX = DOT_SIZE_PX + 2 /** The keyframe diamond ◆ */ const DotContainer = styled.div` position: absolute; ${absoluteDims(DOT_SIZE_PX)} z-index: 1; ` // hmm kinda weird to organize like this (exporting `HitZone`). Maybe there's a way to re-use // this interpolation of `DotContainer` using something like extended components or something. export const HitZone = styled.div` z-index: 2; cursor: ew-resize; position: absolute; ${absoluteDims(12)}; ${pointerEventsAutoInNormalMode}; &:hover + ${DotContainer}, #pointer-root.draggingPositionInSequenceEditor &:hover + ${DotContainer} { ${absoluteDims(DOT_HOVER_SIZE_PX)} } ` export function AggregateKeyframeVisualDot(props: { flag: PresenceFlag | undefined isSelected: AggregateKeyframePositionIsSelected | undefined isAllHere: boolean }) { const theme: IDotThemeValues = { isSelected: props.isSelected, flag: props.flag, } return ( {props.isAllHere ? ( ) : ( )} ) } type IDotThemeValues = { isSelected: AggregateKeyframePositionIsSelected | undefined flag: PresenceFlag | undefined } const SELECTED_COLOR = '#F2C95C' const DEFAULT_PRIMARY_COLOR = '#40AAA4' const DEFAULT_SECONDARY_COLOR = '#45747C' const selectionColorAll = (theme: IDotThemeValues) => theme.isSelected === AggregateKeyframePositionIsSelected.AllSelected ? SELECTED_COLOR : theme.isSelected === AggregateKeyframePositionIsSelected.AtLeastOneUnselected ? DEFAULT_PRIMARY_COLOR : DEFAULT_SECONDARY_COLOR const selectionColorSome = (theme: IDotThemeValues) => theme.isSelected === AggregateKeyframePositionIsSelected.AllSelected ? SELECTED_COLOR : theme.isSelected === AggregateKeyframePositionIsSelected.AtLeastOneUnselected ? DEFAULT_PRIMARY_COLOR : DEFAULT_SECONDARY_COLOR const AggregateDotAllHereSvg = (theme: IDotThemeValues) => ( ) // when the aggregate keyframes are sparse across tracks at this position const AggregateDotSomeHereSvg = (theme: IDotThemeValues) => ( ) ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/iif.tsx ================================================ export function iif any>(fn: F): ReturnType { return fn() } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/useAggregateKeyframeEditorUtils.tsx ================================================ import {prism} from '@theatre/dataverse' import {createStudioSheetItemKey} from '@theatre/studio/utils/createStudioSheetItemKey' import {AggregateKeyframePositionIsSelected} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack' import {isConnectionEditingInCurvePopover} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/CurveEditorPopover' import {usePrism} from '@theatre/react' import {selectedKeyframeConnections} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections' import type { IAggregateKeyframeEditorProps, AggregatedKeyframeConnection, } from './AggregateKeyframeEditor' import {iif} from './iif' export type IAggregateKeyframeEditorUtils = ReturnType< typeof useAggregateKeyframeEditorUtils > // I think this was pulled out for performance // 1/10: Not sure this is properly split up export function useAggregateKeyframeEditorUtils( props: Pick< IAggregateKeyframeEditorProps, 'index' | 'aggregateKeyframes' | 'selection' | 'viewModel' >, ) { const {index, aggregateKeyframes, selection} = props return usePrism(getAggregateKeyframeEditorUtilsPrismFn(props), [ index, aggregateKeyframes, selection, props.viewModel, ]) } // I think this was pulled out for performance // 1/10: Not sure this is properly split up export function getAggregateKeyframeEditorUtilsPrismFn( props: Pick< IAggregateKeyframeEditorProps, 'index' | 'aggregateKeyframes' | 'selection' | 'viewModel' >, ) { const {index, aggregateKeyframes, selection} = props const {projectId, sheetId} = props.viewModel.type === 'sheet' ? props.viewModel.sheet.address : props.viewModel.sheetObject.address return () => { const cur = aggregateKeyframes[index] const next = aggregateKeyframes[index + 1] const curAndNextAggregateKeyframesMatch = next && cur.keyframes.length === next.keyframes.length && cur.keyframes.every(({track}, ind) => next.keyframes[ind].track === track) const connected = curAndNextAggregateKeyframesMatch ? { length: next.position - cur.position, selected: cur.selected === AggregateKeyframePositionIsSelected.AllSelected && next.selected === AggregateKeyframePositionIsSelected.AllSelected, } : null const aggregatedConnections: AggregatedKeyframeConnection[] = !connected ? [] : cur.keyframes.map(({kf, track}, i) => ({ ...track.sheetObject.address, trackId: track.id, left: kf, right: next.keyframes[i].kf, })) const allConnections = iif(() => { const selectedConnections = prism .memo( 'selectedConnections', () => selectedKeyframeConnections(projectId, sheetId, selection), [projectId, sheetId, selection], ) .getValue() return [...aggregatedConnections, ...selectedConnections] }) const isAggregateEditingInCurvePopover = aggregatedConnections.every( (con) => isConnectionEditingInCurvePopover(con), ) const itemKey = prism.memo( 'itemKey', () => { if (props.viewModel.type === 'sheet') { return createStudioSheetItemKey.forSheetAggregateKeyframe( props.viewModel.sheet, cur.position, ) } else if (props.viewModel.type === 'sheetObject') { return createStudioSheetItemKey.forSheetObjectAggregateKeyframe( props.viewModel.sheetObject, cur.position, ) } else { return createStudioSheetItemKey.forCompoundPropAggregateKeyframe( props.viewModel.sheetObject, props.viewModel.pathToProp, cur.position, ) } }, [props.viewModel, cur.position], ) return { itemKey, cur, connected, isAggregateEditingInCurvePopover, allConnections, } } } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack.tsx ================================================ import type { DopeSheetSelection, SequenceEditorPanelLayout, } from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type { SequenceEditorTree_PropWithChildren, SequenceEditorTree_Sheet, SequenceEditorTree_SheetObject, } from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import {usePrism, useVal} from '@theatre/react' import type {Prism, Pointer} from '@theatre/dataverse' import {prism, val, pointerToPrism} from '@theatre/dataverse' import React, {useMemo, Fragment} from 'react' import styled from 'styled-components' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useRefAndState from '@theatre/studio/utils/useRefAndState' import type { IAggregateKeyframesAtPosition, IAggregateKeyframeEditorProps, } from './AggregateKeyframeEditor/AggregateKeyframeEditor' import AggregateKeyframeEditor from './AggregateKeyframeEditor/AggregateKeyframeEditor' import type { AggregatedKeyframes, KeyframeWithTrack, } from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes' import {collectAggregateSnapPositionsObjectOrCompound} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes' import {useLogger} from '@theatre/studio/uiComponents/useLogger' import {getAggregateKeyframeEditorUtilsPrismFn} from './AggregateKeyframeEditor/useAggregateKeyframeEditorUtils' import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap' import type {DragOpts} from '@theatre/studio/uiComponents/useDrag' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import useDrag from '@theatre/studio/uiComponents/useDrag' import {useLockFrameStampPositionRef} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler' import getStudio from '@theatre/studio/getStudio' import type {SheetObjectAddress} from '@theatre/core/types/public' import { decodePathToProp, encodePathToProp, doesPathStartWith, } from '@theatre/utils/pathToProp' import type { ObjectAddressKey, SequenceTrackId, } from '@theatre/core/types/public' import type Sequence from '@theatre/core/sequences/Sequence' import KeyframeSnapTarget, { snapPositionsStateD, } from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget' import {emptyObject} from '@theatre/utils' import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/core/types/private' import { collectKeyframeSnapPositions, snapToNone, snapToSome, } from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget' import {collectAggregateSnapPositionsSheet} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes' import type {BasicKeyframe} from '@theatre/core/types/public' import type {ContextMenuItem} from '@theatre/studio/uiComponents/chordial/chordialInternals' const AggregatedKeyframeTrackContainer = styled.div` position: relative; height: 100%; width: 100%; ` type IAggregatedKeyframeTracksProps = { viewModel: | SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject | SequenceEditorTree_Sheet aggregatedKeyframes: AggregatedKeyframes layoutP: Pointer } type _AggSelection = { selectedPositions: Map selection: DopeSheetSelection | undefined } const EMPTY_SELECTION: _AggSelection = Object.freeze({ selectedPositions: new Map(), selection: undefined, }) function AggregatedKeyframeTrack_memo(props: IAggregatedKeyframeTracksProps) { const {layoutP, aggregatedKeyframes, viewModel} = props const logger = useLogger('AggregatedKeyframeTrack') const [containerRef, containerNode] = useRefAndState( null, ) const {selectedPositions, selection} = useCollectedSelectedPositions( layoutP, aggregatedKeyframes, ) const [contextMenu, _, isOpen] = useAggregatedKeyframeTrackContextMenu( containerNode, props, () => logger._debug('see aggregatedKeyframes', props.aggregatedKeyframes), ) const posKfs: IAggregateKeyframesAtPosition[] = useMemo( () => [...aggregatedKeyframes.byPosition.entries()] .sort((a, b) => a[0] - b[0]) .map( ([position, keyframes]): IAggregateKeyframesAtPosition => ({ position, keyframes, selected: selectedPositions.get(position), allHere: keyframes.length === aggregatedKeyframes.tracks.length, }), ), [aggregatedKeyframes, selectedPositions], ) const snapPositionsState = useVal(snapPositionsStateD) const snapToAllKeyframes = snapPositionsState.mode === 'snapToAll' const snapPositions = snapPositionsState.mode === 'snapToSome' ? snapPositionsState.positions : emptyObject const aggregateSnapPositions = useMemo( () => viewModel.type === 'sheet' ? collectAggregateSnapPositionsSheet(viewModel, snapPositions) : collectAggregateSnapPositionsObjectOrCompound( viewModel, snapPositions, ), [snapPositions], ) const snapTargets = aggregateSnapPositions.map((position) => ( )) const keyframeEditorProps = useMemo( () => posKfs.map( ( {position, keyframes}, index, ): {editorProps: IAggregateKeyframeEditorProps; position: number} => ({ position, editorProps: { index, layoutP, viewModel, aggregateKeyframes: posKfs, selection: selectedPositions.has(position) ? selection : undefined, }, }), ), [posKfs, viewModel, selectedPositions], ) const [isDragging] = useDragForAggregateKeyframeDot( containerNode, (position) => { return keyframeEditorProps.find( (editorProp) => editorProp.position === position, )?.editorProps }, { onClickFromDrag(dragStartEvent) { // TODO Aggregate inline keyframe editor // openEditor(dragStartEvent, ref.current!) }, }, ) const keyframeEditors = keyframeEditorProps.map((props, i) => ( {snapToAllKeyframes && ( )} )) return ( {keyframeEditors} {snapTargets} {contextMenu} ) } const AggregatedKeyframeTrack = React.memo(AggregatedKeyframeTrack_memo) export default AggregatedKeyframeTrack export enum AggregateKeyframePositionIsSelected { AllSelected, AtLeastOneUnselected, NoneSelected, } const {AllSelected, AtLeastOneUnselected, NoneSelected} = AggregateKeyframePositionIsSelected /** Helper to put together the selected positions */ function useCollectedSelectedPositions( layoutP: Pointer, aggregatedKeyframes: AggregatedKeyframes, ): _AggSelection { return usePrism( () => val(collectedSelectedPositions(layoutP, aggregatedKeyframes)), [layoutP, aggregatedKeyframes], ) } function collectedSelectedPositions( layoutP: Pointer, aggregatedKeyframes: AggregatedKeyframes, ): Prism<_AggSelection> { return prism(() => { const selectionAtom = val(layoutP.selectionAtom) const selection = val(selectionAtom.pointer.current) if (!selection) return EMPTY_SELECTION const selectedAtPositions = new Map< number, AggregateKeyframePositionIsSelected >() for (const [position, kfsWithTrack] of aggregatedKeyframes.byPosition) { const positionIsSelected = allOrSomeOrNoneSelected( kfsWithTrack, selection, ) if ( positionIsSelected !== undefined && positionIsSelected !== NoneSelected ) { selectedAtPositions.set(position, positionIsSelected) } } return { selectedPositions: selectedAtPositions, selection: val(selectionAtom.pointer.current), } }) } function allOrSomeOrNoneSelected( keyframeWithTracks: KeyframeWithTrack[], selection: DopeSheetSelection, ): AggregateKeyframePositionIsSelected | undefined { let positionIsSelected: undefined | AggregateKeyframePositionIsSelected = undefined for (const {track, kf} of keyframeWithTracks) { const kfIsSelected = selection.byObjectKey[track.sheetObject.address.objectKey]?.byTrackId[ track.id ]?.byKeyframeId?.[kf.id] === true if (positionIsSelected === undefined) { if (kfIsSelected) { positionIsSelected = AllSelected } else { positionIsSelected = NoneSelected } } else if (kfIsSelected) { if (positionIsSelected === NoneSelected) { positionIsSelected = AtLeastOneUnselected } } else { if (positionIsSelected === AllSelected) { positionIsSelected = AtLeastOneUnselected } } } return positionIsSelected } function useAggregatedKeyframeTrackContextMenu( node: HTMLDivElement | null, props: IAggregatedKeyframeTracksProps, debugOnOpen: () => void, ) { return useContextMenu(node, { onOpen: debugOnOpen, displayName: 'Aggregate Keyframe Track', items: () => { const selectionKeyframes = pointerToPrism( getStudio()!.atomP.ahistoric.clipboard.keyframesWithRelativePaths, ).getValue() ?? [] return [pasteKeyframesContextMenuItem(props, selectionKeyframes)] }, }) } function pasteKeyframesContextMenuItem( props: IAggregatedKeyframeTracksProps, keyframes: KeyframeWithPathToPropFromCommonRoot[], ): ContextMenuItem { return { type: 'normal', label: 'Paste Keyframes', enabled: keyframes.length > 0, callback: () => { const sheet = val(props.layoutP.sheet) const sequence = sheet.getSequence() if (props.viewModel.type === 'sheet') { pasteKeyframesSheet(props.viewModel, keyframes, sequence) } else { pasteKeyframesObjectOrCompound(props.viewModel, keyframes, sequence) } }, } } /** * Given a list of keyframes that contain paths relative to a common root, * (see `copyableKeyframesFromSelection`) this function pastes those keyframes * into tracks on either the object (if viewModel.type === 'sheetObject') or * the compound prop (if viewModel.type === 'propWithChildren'). * * Our copy & paste behavior is currently roughly described in AGGREGATE_COPY_PASTE.md * * @see StudioAhistoricState.clipboard * @see setClipboardNestedKeyframes */ function pasteKeyframesSheet( viewModel: SequenceEditorTree_Sheet, keyframes: KeyframeWithPathToPropFromCommonRoot[], sequence: Sequence, ) { const {projectId, sheetId, sheetInstanceId} = viewModel.sheet.address const areKeyframesAllOnSingleTrack = keyframes.every( ({pathToProp}) => pathToProp.length === 0, ) if (areKeyframesAllOnSingleTrack) { for (const object of viewModel.children.map((child) => child.sheetObject)) { const tracksByObject = pointerToPrism( getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId] .sequence.tracksByObject[object.address.objectKey], ).getValue() const trackIdsOnObject = Object.keys(tracksByObject?.trackData ?? {}) pasteKeyframesToMultipleTracks( object.address, trackIdsOnObject, keyframes, sequence, ) } } else { const tracksByObject = pointerToPrism( getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId] .sequence.tracksByObject, ).getValue() const placeableKeyframes = keyframes .map(({keyframe, pathToProp}) => { const objectKey = pathToProp[0] as ObjectAddressKey const relativePathToProp = pathToProp.slice(1) const pathToPropEncoded = encodePathToProp([...relativePathToProp]) const trackIdByPropPath = tracksByObject?.[objectKey]?.trackIdByPropPath ?? {} const maybeTrackId = trackIdByPropPath[pathToPropEncoded] return maybeTrackId ? { keyframe, trackId: maybeTrackId, address: { objectKey, projectId, sheetId, sheetInstanceId, }, } : null }) .filter((result) => result !== null) as { keyframe: BasicKeyframe trackId: SequenceTrackId address: SheetObjectAddress }[] pasteKeyframesToSpecificTracks(placeableKeyframes, sequence) } } function pasteKeyframesObjectOrCompound( viewModel: | SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject, keyframes: KeyframeWithPathToPropFromCommonRoot[], sequence: Sequence, ) { const {projectId, sheetId, objectKey} = viewModel.sheetObject.address const trackRecords = pointerToPrism( getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId] .sequence.tracksByObject[objectKey], ).getValue() const areKeyframesAllOnSingleTrack = keyframes.every( ({pathToProp}) => pathToProp.length === 0, ) if (areKeyframesAllOnSingleTrack) { const trackIdsOnObject = Object.keys(trackRecords?.trackData ?? {}) if (viewModel.type === 'sheetObject') { pasteKeyframesToMultipleTracks( viewModel.sheetObject.address, trackIdsOnObject, keyframes, sequence, ) } else { const trackIdByPropPath = trackRecords?.trackIdByPropPath || {} const trackIdsOnCompoundProp = Object.entries(trackIdByPropPath) .filter( ([encodedPath, trackId]) => trackId !== undefined && doesPathStartWith( // e.g. a track with path `['position', 'x']` is under the compound track with path `['position']` decodePathToProp(encodedPath), viewModel.pathToProp, ), ) .map(([encodedPath, trackId]) => trackId) as SequenceTrackId[] pasteKeyframesToMultipleTracks( viewModel.sheetObject.address, trackIdsOnCompoundProp, keyframes, sequence, ) } } else { const trackIdByPropPath = trackRecords?.trackIdByPropPath || {} const rootPath = viewModel.type === 'propWithChildren' ? viewModel.pathToProp : [] const placeableKeyframes = keyframes .map(({keyframe, pathToProp: relativePathToProp}) => { const pathToPropEncoded = encodePathToProp([ ...rootPath, ...relativePathToProp, ]) const maybeTrackId = trackIdByPropPath[pathToPropEncoded] return maybeTrackId ? { keyframe, trackId: maybeTrackId, address: viewModel.sheetObject.address, } : null }) .filter((result) => result !== null) as { keyframe: BasicKeyframe trackId: SequenceTrackId address: SheetObjectAddress }[] pasteKeyframesToSpecificTracks(placeableKeyframes, sequence) } } function pasteKeyframesToMultipleTracks( address: SheetObjectAddress, trackIds: SequenceTrackId[], keyframes: KeyframeWithPathToPropFromCommonRoot[], sequence: Sequence, ) { sequence.position = sequence.closestGridPosition(sequence.position) const keyframeOffset = earliestKeyframe( keyframes.map(({keyframe}) => keyframe), )?.position! getStudio()!.transaction(({stateEditors}) => { for (const trackId of trackIds) { for (const {keyframe} of keyframes) { stateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition( { ...address, trackId, position: sequence.position + keyframe.position - keyframeOffset, handles: keyframe.handles, value: keyframe.value, snappingFunction: sequence.closestGridPosition, type: keyframe.type, }, ) } } }) } function pasteKeyframesToSpecificTracks( keyframesWithTracksToPlaceThemIn: { keyframe: BasicKeyframe trackId: SequenceTrackId address: SheetObjectAddress }[], sequence: Sequence, ) { sequence.position = sequence.closestGridPosition(sequence.position) const keyframeOffset = earliestKeyframe( keyframesWithTracksToPlaceThemIn.map(({keyframe}) => keyframe), )?.position! getStudio()!.transaction(({stateEditors}) => { for (const { keyframe, trackId, address, } of keyframesWithTracksToPlaceThemIn) { stateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition( { ...address, trackId, position: sequence.position + keyframe.position - keyframeOffset, handles: keyframe.handles, value: keyframe.value, snappingFunction: sequence.closestGridPosition, type: keyframe.type, }, ) } }) } function earliestKeyframe(keyframes: BasicKeyframe[]) { let curEarliest: BasicKeyframe | null = null for (const keyframe of keyframes) { if (curEarliest === null || keyframe.position < curEarliest.position) { curEarliest = keyframe } } return curEarliest } function useDragForAggregateKeyframeDot( containerNode: HTMLDivElement | null, getPropsForPosition: ( position: number, ) => IAggregateKeyframeEditorProps | undefined, options: { /** * hmm: this is a hack so we can actually receive the * {@link MouseEvent} from the drag event handler and use * it for positioning the popup. */ onClickFromDrag(dragStartEvent: MouseEvent): void }, ): [isDragging: boolean] { const logger = useLogger('useDragForAggregateKeyframeDot') const frameStampLock = useLockFrameStampPositionRef() const useDragOpts = useMemo(() => { return { debugName: 'AggregateKeyframeDot/useDragKeyframe', onDragStart(event) { logger._debug('onDragStart', {target: event.target}) const positionToFind = Number((event.target as HTMLElement).dataset.pos) const props = getPropsForPosition(positionToFind) if (!props) { logger._debug('no props found for ', {positionToFind}) return false } frameStampLock(true, positionToFind) const keyframes = prism( getAggregateKeyframeEditorUtilsPrismFn(props), ).getValue().cur.keyframes const address = props.viewModel.type === 'sheet' ? props.viewModel.sheet.address : props.viewModel.sheetObject.address const tracksByObject = val( getStudio()!.atomP.historic.coreByProject[address.projectId] .sheetsById[address.sheetId].sequence.tracksByObject, )! // Calculate all the valid snap positions in the sequence editor, // excluding the child keyframes of this aggregate, and any selection it is part of. const snapPositions = collectKeyframeSnapPositions( tracksByObject, function shouldIncludeKeyfram(keyframe, {trackId, objectKey}) { return ( // we exclude all the child keyframes of this aggregate keyframe from being a snap target keyframes.every( (kfWithTrack) => keyframe.id !== kfWithTrack.kf.id, ) && !( // if all of the children of the current aggregate keyframe are in a selection, ( props.selection && // then we exclude them and all other keyframes in the selection from being snap targets props.selection.byObjectKey[objectKey]?.byTrackId[trackId] ?.byKeyframeId[keyframe.id] ) ) ) }, ) snapToSome(snapPositions) if ( props.selection && props.aggregateKeyframes[props.index].selected === AggregateKeyframePositionIsSelected.AllSelected ) { const {selection, viewModel} = props const handlers = selection .getDragHandlers({ ...address, domNode: containerNode!, positionAtStartOfDrag: keyframes[0].kf.position, }) .onDragStart(event) return ( handlers && { ...handlers, onClick: options.onClickFromDrag, onDragEnd: (...args) => { handlers.onDragEnd?.(...args) snapToNone() }, } ) } const propsAtStartOfDrag = props const toUnitSpace = val( propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, ) let tempTransaction: CommitOrDiscardOrRecapture | undefined return { onDrag(dx, dy, event) { const newPosition = Math.max( // check if our event hovers over a [data-pos] element DopeSnap.checkIfMouseEventSnapToPos(event, { // ignore: node, }) ?? // if we don't find snapping target, check the distance dragged + original position keyframes[0].kf.position + toUnitSpace(dx), // sanitize to minimum of zero 0, ) frameStampLock(true, newPosition) tempTransaction?.discard() tempTransaction = undefined tempTransaction = getStudio().tempTransaction(({stateEditors}) => { for (const keyframe of keyframes) { const original = keyframe.kf stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes( { ...keyframe.track.sheetObject.address, trackId: keyframe.track.id, keyframes: [{...original, position: newPosition}], snappingFunction: val( propsAtStartOfDrag.layoutP.sheet, ).getSequence().closestGridPosition, }, ) } }) }, onDragEnd(dragHappened) { frameStampLock(false, -1) if (dragHappened) { tempTransaction?.commit() } else { tempTransaction?.discard() options.onClickFromDrag(event) } snapToNone() }, onClick(ev) { options.onClickFromDrag(ev) }, } }, } }, [getPropsForPosition, options.onClickFromDrag]) const [isDragging] = useDrag(containerNode, useDragOpts) useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize') return [isDragging] } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx ================================================ import type {TrackData} from '@theatre/core/types/private/core' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type {SequenceEditorTree_PrimitiveProp} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import {usePrism, useVal} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React, {useMemo} from 'react' import styled from 'styled-components' import SingleKeyframeEditor from './KeyframeEditor/SingleKeyframeEditor' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useRefAndState from '@theatre/studio/utils/useRefAndState' import getStudio from '@theatre/studio/getStudio' import {arePathsEqual} from '@theatre/utils/pathToProp' import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/core/types/private' import KeyframeSnapTarget, { snapPositionsStateD, } from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget' import {createStudioSheetItemKey} from '@theatre/studio/utils/createStudioSheetItemKey' import type {BasicKeyframe} from '@theatre/core' import {__private} from '@theatre/core' const {keyframeUtils} = __private import type {ContextMenuItem} from '@theatre/studio/uiComponents/chordial/chordialInternals' const Container = styled.div` position: relative; height: 100%; width: 100%; ` type BasicKeyframedTracksProps = { leaf: SequenceEditorTree_PrimitiveProp layoutP: Pointer trackData: TrackData } const BasicKeyframedTrack: React.VFC = React.memo( (props) => { const {layoutP, trackData, leaf} = props const [containerRef, containerNode] = useRefAndState( null, ) const {selectedKeyframeIds, selection} = usePrism(() => { const selectionAtom = val(layoutP.selectionAtom) const selectedKeyframeIds = val( selectionAtom.pointer.current.byObjectKey[ leaf.sheetObject.address.objectKey ].byTrackId[leaf.trackId].byKeyframeId, ) if (selectedKeyframeIds) { return { selectedKeyframeIds, selection: val(selectionAtom.pointer.current), } } else { return { selectedKeyframeIds: {}, selection: undefined, } } }, [layoutP, leaf.trackId]) const [contextMenu, _, isOpen] = useBasicKeyframedTrackContextMenu( containerNode, props, ) const snapPositionsState = useVal(snapPositionsStateD) const snapPositions = snapPositionsState.mode === 'snapToSome' ? snapPositionsState.positions[leaf.sheetObject.address.objectKey]?.[ leaf.trackId ] : [] ?? [] const snapToAllKeyframes = snapPositionsState.mode === 'snapToAll' const track = useMemo( () => ({ data: trackData, id: leaf.trackId, sheetObject: props.leaf.sheetObject, }), [trackData, leaf.trackId], ) const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( trackData.keyframes, ) const keyframeEditors = sortedKeyframes.map((kf, index) => ( )) const snapTargets = snapPositions.map((position) => ( )) const additionalSnapTargets = !snapToAllKeyframes ? null : sortedKeyframes.map((kf) => ( )) return ( {keyframeEditors} {snapTargets} <>{additionalSnapTargets} {contextMenu} ) }, ) BasicKeyframedTrack.displayName = `BasicKeyframedTrack` export default BasicKeyframedTrack function useBasicKeyframedTrackContextMenu( node: HTMLDivElement | null, props: BasicKeyframedTracksProps, ) { return useContextMenu(node, { displayName: 'Keyframe Track', items: () => { const selectionKeyframes = val( getStudio()!.atomP.ahistoric.clipboard.keyframesWithRelativePaths, ) ?? [] return [pasteKeyframesContextMenuItem(props, selectionKeyframes)] }, }) } function pasteKeyframesContextMenuItem( props: BasicKeyframedTracksProps, keyframes: KeyframeWithPathToPropFromCommonRoot[], ): ContextMenuItem { return { type: 'normal', label: 'Paste Keyframes', enabled: keyframes.length > 0, callback: () => { const sheet = val(props.layoutP.sheet) const sequence = sheet.getSequence() const firstPath = keyframes[0]?.pathToProp const singleTrackKeyframes = keyframes .filter(({keyframe, pathToProp}) => arePathsEqual(firstPath, pathToProp), ) .map(({keyframe, pathToProp}) => keyframe) getStudio()!.transaction(({stateEditors}) => { sequence.position = sequence.closestGridPosition(sequence.position) const keyframeOffset = earliestKeyframe(singleTrackKeyframes)?.position! for (const keyframe of singleTrackKeyframes) { stateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition( { ...props.leaf.sheetObject.address, trackId: props.leaf.trackId, position: sequence.position + keyframe.position - keyframeOffset, handles: keyframe.handles, value: keyframe.value, snappingFunction: sequence.closestGridPosition, type: keyframe.type, }, ) } }) }, } } function earliestKeyframe(keyframes: BasicKeyframe[]) { let curEarliest: BasicKeyframe | null = null for (const keyframe of keyframes) { if (curEarliest === null || keyframe.position < curEarliest.position) { curEarliest = keyframe } } return curEarliest } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/BasicKeyframeConnector.tsx ================================================ import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {val} from '@theatre/dataverse' import React from 'react' import {useMemo, useRef} from 'react' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover' import CurveEditorPopover, { isConnectionEditingInCurvePopover, } from './CurveEditorPopover/CurveEditorPopover' import type {ISingleKeyframeEditorProps} from './SingleKeyframeEditor' import type {IConnectorThemeValues} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/keyframeRowUI/ConnectorLine' import {ConnectorLine} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/keyframeRowUI/ConnectorLine' import {COLOR_POPOVER_BACK} from './CurveEditorPopover/colors' import {usePrism} from '@theatre/react' import type {KeyframeConnectionWithAddress} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections' import {copyableKeyframesFromSelection} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections' import {selectedKeyframeConnections} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections' import styled from 'styled-components' import type {BasicKeyframe} from '@theatre/core' import {__private} from '@theatre/core' const {keyframeUtils} = __private const POPOVER_MARGIN = 5 const EasingPopover = styled(BasicPopover)` --popover-outer-stroke: transparent; --popover-inner-stroke: ${COLOR_POPOVER_BACK}; border-radius: 2px; padding: 0; ` type IBasicKeyframeConnectorProps = ISingleKeyframeEditorProps const BasicKeyframeConnector: React.VFC = ( props, ) => { const {index, track} = props const cur = keyframeUtils.getSortedKeyframesCached(track.data.keyframes)[ index ] const next = keyframeUtils.getSortedKeyframesCached(track.data.keyframes)[ index + 1 ] const [nodeRef, node] = useRefAndState(null) const { node: popoverNode, toggle: togglePopover, close: closePopover, } = usePopover( () => { const rightDims = val(props.layoutP.rightDims) return { debugName: 'Connector', constraints: { minX: rightDims.screenX + POPOVER_MARGIN, maxX: rightDims.screenX + rightDims.width - POPOVER_MARGIN, }, } }, () => , ) const [contextMenu] = useConnectorContextMenu(props, node, cur, next) useDragKeyframe(node, props) const connectorLengthInUnitSpace = next.position - cur.position const isInCurveEditorPopoverSelection = usePrism( () => isConnectionEditingInCurvePopover({ ...props.leaf.sheetObject.address, trackId: props.leaf.trackId, left: cur, right: next, }), [props.leaf.sheetObject.address, props.leaf.trackId, cur, next], ) const themeValues: IConnectorThemeValues = { isPopoverOpen: isInCurveEditorPopoverSelection, isSelected: props.selection !== undefined, } return ( <> { if (node) togglePopover(e, node) }} > {popoverNode} {/* contextMenu is placed outside of the ConnectorLine so that clicking on the contextMenu does not count as clicking on the ConnectorLine */} {contextMenu} ) } export default BasicKeyframeConnector const SingleCurveEditorPopover: React.FC< IBasicKeyframeConnectorProps & {closePopover: (reason: string) => void} > = React.forwardRef((props, ref) => { const { index, track: {data: trackData}, selection, } = props const cur = keyframeUtils.getSortedKeyframesCached(trackData.keyframes)[index] const next = keyframeUtils.getSortedKeyframesCached(trackData.keyframes)[ index + 1 ] const trackId = props.leaf.trackId const address = props.leaf.sheetObject.address const selectedConnections = usePrism( () => selectedKeyframeConnections( address.projectId, address.sheetId, selection, ).getValue(), [address, selection], ) const curveConnection: KeyframeConnectionWithAddress = { left: cur, right: next, trackId, ...address, } return ( ) }) function useDragKeyframe( node: HTMLDivElement | null, props: IBasicKeyframeConnectorProps, ) { const propsRef = useRef(props) propsRef.current = props const gestureHandlers = useMemo[1]>(() => { return { debugName: 'useDragKeyframe', lockCSSCursorTo: 'ew-resize', onDragStart(event) { const props = propsRef.current let tempTransaction: CommitOrDiscardOrRecapture | undefined if (props.selection) { const {selection, leaf} = props const {sheetObject} = leaf return selection .getDragHandlers({ ...sheetObject.address, domNode: node!, positionAtStartOfDrag: keyframeUtils.getSortedKeyframesCached( props.track.data.keyframes, )[props.index].position, }) .onDragStart(event) } const propsAtStartOfDrag = props const sequence = val(propsAtStartOfDrag.layoutP.sheet).getSequence() const toUnitSpace = val( propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, ) return { onDrag(dx, dy, event) { const delta = toUnitSpace(dx) if (tempTransaction) { tempTransaction.discard() tempTransaction = undefined } tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.transformKeyframes( { ...propsAtStartOfDrag.leaf.sheetObject.address, trackId: propsAtStartOfDrag.leaf.trackId, keyframeIds: [ propsAtStartOfDrag.keyframe.id, keyframeUtils.getSortedKeyframesCached( propsAtStartOfDrag.track.data.keyframes, )[propsAtStartOfDrag.index + 1].id, ], translate: delta, scale: 1, origin: 0, snappingFunction: sequence.closestGridPosition, }, ) }) }, onDragEnd(dragHappened) { if (dragHappened) { if (tempTransaction) { tempTransaction.commit() } } else { if (tempTransaction) { tempTransaction.discard() } } }, } }, } }, []) useDrag(node, gestureHandlers) } function useConnectorContextMenu( props: IBasicKeyframeConnectorProps, node: HTMLDivElement | null, cur: BasicKeyframe, next: BasicKeyframe, ) { // TODO?: props.selection is undefined if only one of the connected keyframes is selected return useContextMenu(node, { displayName: 'Tween', items: () => { const copyableKeyframes = copyableKeyframesFromSelection( props.leaf.sheetObject.address.projectId, props.leaf.sheetObject.address.sheetId, props.selection, ) return [ { type: 'normal', label: copyableKeyframes.length > 0 ? 'Copy (selection)' : 'Copy', callback: () => { if (copyableKeyframes.length > 0) { getStudio().transaction((api) => { api.stateEditors.studio.ahistoric.setClipboardKeyframes( copyableKeyframes, ) }) } else { getStudio().transaction((api) => { api.stateEditors.studio.ahistoric.setClipboardKeyframes([ {keyframe: cur, pathToProp: props.leaf.pathToProp}, {keyframe: next, pathToProp: props.leaf.pathToProp}, ]) }) } }, }, { type: 'normal', label: props.selection ? 'Delete (selection)' : 'Delete', callback: () => { if (props.selection) { props.selection.delete() } else { getStudio().transaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes( { ...props.leaf.sheetObject.address, keyframeIds: [cur.id, next.id], trackId: props.leaf.trackId, }, ) }) } }, }, ] }, }) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/CurveEditorPopover.tsx ================================================ import {Atom, prism} from '@theatre/dataverse' import type {KeyboardEvent} from 'react' import React, { useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react' import styled from 'styled-components' import fuzzy from 'fuzzy' import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import CurveSegmentEditor from './CurveSegmentEditor' import EasingOption from './EasingOption' import type {CSSCubicBezierArgsString, CubicBezierHandles} from './shared' import { cssCubicBezierArgsFromHandles, handlesFromCssCubicBezierArgs, EASING_PRESETS, areEasingsSimilar, } from './shared' import {COLOR_BASE, COLOR_POPOVER_BACK} from './colors' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {useUIOptionGrid, Outcome} from './useUIOptionGrid' import type {KeyframeConnectionWithAddress} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections' import type {BasicKeyframe} from '@theatre/core/types/public' const PRESET_COLUMNS = 3 const PRESET_SIZE = 53 const APPROX_TOOLTIP_HEIGHT = 25 const Grid = styled.div` background: ${COLOR_POPOVER_BACK}; display: grid; grid-template-areas: 'search tween' 'presets tween'; grid-template-rows: 32px 1fr; grid-template-columns: ${PRESET_COLUMNS * PRESET_SIZE}px 120px; gap: 1px; height: 120px; ` const OptionsContainer = styled.div` overflow: auto; grid-area: presets; display: grid; grid-template-columns: repeat(${PRESET_COLUMNS}, 1fr); grid-auto-rows: min-content; gap: 1px; overflow-y: scroll; scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* Internet Explorer 10+ */ &::-webkit-scrollbar { /* WebKit */ width: 0; height: 0; } ` const SearchBox = styled.input.attrs({type: 'text'})` background-color: ${COLOR_BASE}; border: none; border-radius: 2px; color: rgba(255, 255, 255, 0.8); padding: 6px; font-size: 12px; outline: none; cursor: text; text-align: left; width: 100%; height: 100%; box-sizing: border-box; grid-area: search; &:hover { background-color: #212121; } &:focus { background-color: rgba(16, 16, 16, 0.26); outline: 1px solid rgba(0, 0, 0, 0.35); } ` const CurveEditorContainer = styled.div` grid-area: tween; background: ${COLOR_BASE}; ` const NoResultsFoundContainer = styled.div` grid-column: 1 / 4; padding: 6px; color: #888888; ` /** * Tracking for what kinds of events are allowed to change the input's value. */ enum TextInputMode { /** * Initial mode, don't try to override the value. */ init, /** * In `user` mode, the text input field does not update when the curve * changes so that the user's search is preserved. */ user, /** * In `auto` mode, the text input field is continually updated to * a CSS cubic bezier args string to reflect the state of the curve. */ auto, multipleValues, } type ICurveEditorPopoverProps = { /** * Called when user hits enter/escape */ onRequestClose: (reason: string) => void curveConnection: KeyframeConnectionWithAddress additionalConnections: Array } const CurveEditorPopover: React.FC = (props) => { const allConnections = useMemo( () => [props.curveConnection, ...props.additionalConnections], [props.curveConnection, ...props.additionalConnections], ) ////// `tempTransaction` ////// /* * `tempTransaction` is used for all edits in this popover. The transaction * is discared if the user presses escape, otherwise it is committed when the * popover closes. */ const tempTransaction = useRef(null) useEffect(() => { const unlock = getLock(allConnections) // Clean-up function, called when this React component unmounts. // When it unmounts, we want to commit edits that are outstanding return () => { unlock() tempTransaction.current?.commit() } }, [tempTransaction]) ////// Keyframe and trackdata ////// const easing: CubicBezierHandles = [ props.curveConnection.left.handles[2], props.curveConnection.left.handles[3], props.curveConnection.right.handles[0], props.curveConnection.right.handles[1], ] ////// Text input data and reactivity ////// const inputRef = useRef(null) // Select the easing string on popover open for quick copy&paste useLayoutEffect(() => { inputRef.current?.select() inputRef.current?.focus() }, [inputRef.current]) const [inputValue, setInputValue] = useState( cssCubicBezierArgsFromHandles(easing), ) const onInputChange = (e?: React.ChangeEvent) => { if (e === undefined) return setTextInputMode(TextInputMode.user) setInputValue(e.target.value) const maybeHandles = handlesFromCssCubicBezierArgs(e.target.value) if (maybeHandles) setEdit(e.target.value) } const onSearchKeyDown = (e: KeyboardEvent) => { setTextInputMode(TextInputMode.user) // Prevent scrolling on arrow key press if (e.key === 'ArrowDown' || e.key === 'ArrowUp') e.preventDefault() if (e.key === 'ArrowDown') { grid.focusFirstItem() optionsRef.current[displayedPresets[0].label]?.current?.focus() } else if (e.key === 'Escape') { discardTempValue(tempTransaction) props.onRequestClose('key Escape') } else if (e.key === 'Enter') { props.onRequestClose('key Enter') } } const [textInputMode, setTextInputMode] = useState( TextInputMode.init, ) useEffect(() => { if (textInputMode === TextInputMode.auto) { setInputValue(cssCubicBezierArgsFromHandles(easing)) } else if (textInputMode === TextInputMode.multipleValues) { if (inputValue !== '') setInputValue('') } }, allConnections) // `edit` keeps track of the current edited state of the curve. const [edit, setEdit] = useState( cssCubicBezierArgsFromHandles(easing), ) // `preview` is used when hovering over a curve to preview it. const [preview, setPreview] = useState(null) // When `preview` or `edit` change, use the `tempTransaction` to change the // curve in Theate's data. useEffect(() => { if ( textInputMode !== TextInputMode.init && textInputMode !== TextInputMode.multipleValues ) setTempValue(tempTransaction, allConnections, preview ?? edit ?? '') }, [preview, edit, textInputMode]) ////// selection stuff ////// if ( allConnections.some( areConnectedKeyframesTheSameAs(props.curveConnection), ) && textInputMode === TextInputMode.init ) { setTextInputMode(TextInputMode.multipleValues) } ////// Curve editing reactivity ////// const onCurveChange = (newHandles: CubicBezierHandles) => { setTextInputMode(TextInputMode.auto) const value = cssCubicBezierArgsFromHandles(newHandles) setInputValue(value) setEdit(value) // ensure that the text input is selected when curve is changing. inputRef.current?.select() inputRef.current?.focus() } const onCancelCurveChange = () => {} ////// Preset reactivity ////// const displayedPresets = useMemo(() => { const isInputValueAQuery = /^[A-Za-z]/.test(inputValue) if (isInputValueAQuery) { return fuzzy .filter(inputValue, EASING_PRESETS, { extract: (el) => el.label, }) .map((result) => result.original) } else { return EASING_PRESETS } }, [inputValue]) // Use the first preset in the search when the displayed presets change useEffect(() => { if (textInputMode === TextInputMode.user && displayedPresets[0]) setEdit(displayedPresets[0].value) }, [displayedPresets]) ////// Option grid specification and reactivity ////// const onEasingOptionKeydown = (e: KeyboardEvent) => { if (e.key === 'Escape') { discardTempValue(tempTransaction) props.onRequestClose('key Escape') e.stopPropagation() } else if (e.key === 'Enter') { props.onRequestClose('key Enter') e.stopPropagation() } } const onEasingOptionMouseOver = (item: {label: string; value: string}) => { // Set the `textInputMode` to `auto` if it was `init` before // to enable the easing previews if (textInputMode === TextInputMode.init) setTextInputMode(TextInputMode.auto) setPreview(item.value) } const onEasingOptionMouseOut = () => setPreview(null) const onSelectEasingOption = (item: {label: string; value: string}) => { setTempValue(tempTransaction, allConnections, item.value) props.onRequestClose('selected easing option') return Outcome.Handled } // A map to store all html elements corresponding to easing options const optionsRef = useRef( EASING_PRESETS.reduce( (acc, curr) => { acc[curr.label] = {current: null} return acc }, {} as {[key: string]: {current: HTMLDivElement | null}}, ), ) const [optionsContainerRef, optionsContainer] = useRefAndState(null) // Keep track of option container scroll position const [optionsScrollPosition, setOptionsScrollPosition] = useState(0) useEffect(() => { const listener = () => { setOptionsScrollPosition(optionsContainer?.scrollTop ?? 0) } optionsContainer?.addEventListener('scroll', listener) return () => optionsContainer?.removeEventListener('scroll', listener) }, [optionsContainer]) const grid = useUIOptionGrid({ items: displayedPresets, uiColumns: 3, onSelectItem: onSelectEasingOption, canVerticleExit(exitSide) { if (exitSide === 'top') { inputRef.current?.select() inputRef.current?.focus() return Outcome.Handled } return Outcome.Passthrough }, renderItem: ({item: preset, select}) => ( onEasingOptionMouseOver(preset)} onMouseOut={onEasingOptionMouseOut} onClick={select} tooltipPlacement={ (optionsRef.current[preset.label].current?.offsetTop ?? 0) - (optionsScrollPosition ?? 0) < PRESET_SIZE + APPROX_TOOLTIP_HEIGHT ? 'bottom' : 'top' } isSelected={areEasingsSimilar( easing, handlesFromCssCubicBezierArgs(preset.value), )} /> ), }) // When the user navigates highlight between presets, focus the preset el and set the // easing data to match the highlighted preset useLayoutEffect(() => { if ( grid.currentSelection !== null && document.activeElement !== inputRef.current // prevents taking focus away from input ) { const maybePresetEl = optionsRef.current?.[grid.currentSelection.label]?.current maybePresetEl?.focus() setEdit(grid.currentSelection.value) const isInputValueAQuery = /^[A-Za-z]/.test(inputValue) if (!isInputValueAQuery) { setInputValue(grid.currentSelection.value) } } }, [grid.currentSelection]) return ( grid.onParentEltKeyDown(evt)} > {grid.gridItems} {grid.gridItems.length === 0 ? ( No results found ) : undefined} inputRef.current?.focus()}> ) } export default CurveEditorPopover function setTempValue( tempTransaction: React.MutableRefObject, keyframeConnections: Array, newCurveCssCubicBezier: string, ): void { tempTransaction.current?.discard() tempTransaction.current = null const handles = handlesFromCssCubicBezierArgs(newCurveCssCubicBezier) if (handles === null) { tempTransaction.current = transactionSetHold(keyframeConnections) } else { tempTransaction.current = transactionSetCubicBezier( keyframeConnections, handles, ) } } function discardTempValue( tempTransaction: React.MutableRefObject, ): void { tempTransaction.current?.discard() tempTransaction.current = null } function transactionSetCubicBezier( keyframeConnections: Array, handles: CubicBezierHandles, ): CommitOrDiscardOrRecapture { return getStudio().tempTransaction(({stateEditors}) => { const {setHandlesForKeyframe, setKeyframeType: setKeyframeType} = stateEditors.coreByProject.historic.sheetsById.sequence for (const { projectId, sheetId, objectKey, trackId, left, right, } of keyframeConnections) { setHandlesForKeyframe({ projectId, sheetId, objectKey, trackId, keyframeId: left.id, start: [handles[0], handles[1]], }) setHandlesForKeyframe({ projectId, sheetId, objectKey, trackId, keyframeId: right.id, end: [handles[2], handles[3]], }) setKeyframeType({ projectId, sheetId, objectKey, trackId, keyframeId: left.id, keyframeType: 'bezier', }) } }) } function transactionSetHold( keyframeConnections: Array, ): CommitOrDiscardOrRecapture { return getStudio().tempTransaction(({stateEditors}) => { const {setKeyframeType: setKeyframeType} = stateEditors.coreByProject.historic.sheetsById.sequence for (const { projectId, sheetId, objectKey, trackId, left, } of keyframeConnections) { setKeyframeType({ projectId, sheetId, objectKey, trackId, keyframeId: left.id, keyframeType: 'hold', }) } }) } /** * n mod m without negative results e.g. `mod(-1,5) = 4` contrasted with `-1 % 5 = -1`. * * ref: https://web.archive.org/web/20090717035140if_/javascript.about.com/od/problemsolving/a/modulobug.htm */ export function mod(n: number, m: number) { return ((n % m) + m) % m } function setTimeoutFunction(f: Function, timeout?: number) { return () => setTimeout(f, timeout) } function areConnectedKeyframesTheSameAs({ left: left1, right: right1, }: { left: BasicKeyframe right: BasicKeyframe }) { return ({ left: left2, right: right2, }: { left: BasicKeyframe right: BasicKeyframe }) => left1.handles[2] !== left2.handles[2] || left1.handles[3] !== left2.handles[3] || right1.handles[0] !== right2.handles[0] || right1.handles[1] !== right2.handles[1] } const {isCurveEditorOpenD, isConnectionEditingInCurvePopover, getLock} = (() => { const connectionsInCurvePopoverEdit = new Atom< Array >([]) return { getLock(connections: Array) { connectionsInCurvePopoverEdit.set(connections) return function unlock() { connectionsInCurvePopoverEdit.set([]) } }, isCurveEditorOpenD: prism(() => { return connectionsInCurvePopoverEdit.prism.getValue().length > 0 }), // must be run in a prism isConnectionEditingInCurvePopover(con: KeyframeConnectionWithAddress) { prism.ensurePrism() return connectionsInCurvePopoverEdit.prism .getValue() .some( ({left, right}) => con.left.id === left.id && con.right.id === right.id, ) }, } })() export {isCurveEditorOpenD, isConnectionEditingInCurvePopover} ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/CurveSegmentEditor.tsx ================================================ import React from 'react' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' import clamp from 'lodash-es/clamp' import styled from 'styled-components' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import type {CubicBezierHandles} from './shared' import {useFreezableMemo} from './useFreezableMemo' import {COLOR_BASE} from './colors' import type {KeyframeConnectionWithAddress} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections' // Defines the dimensions of the SVG viewbox space const VIEWBOX_PADDING = 0.12 const VIEWBOX_SIZE = 1 + VIEWBOX_PADDING * 2 const PATTERN_DOT_SIZE = 0.01 const PATTERN_DOT_COUNT = 8 const PATTERN_GRID_SIZE = (1 - PATTERN_DOT_SIZE) / (PATTERN_DOT_COUNT - 1) // The curve supports a gradient but currently is solid cyan const CURVE_START_OVERSHOOT_COLOR = '#3EAAA4' const CURVE_START_COLOR = '#3EAAA4' const CURVE_MID_START_COLOR = '#3EAAA4' const CURVE_MID_COLOR = '#3EAAA4' const CURVE_MID_END_COLOR = '#3EAAA4' const CURVE_END_COLOR = '#3EAAA4' const CURVE_END_OVERSHOOT_COLOR = '#3EAAA4' const CONTROL_COLOR = '#B3B3B3' const HANDLE_COLOR = '#3eaaa4' const HANDLE_HOVER_COLOR = '#67dfd8' const BACKGROUND_CURVE_COLORS = [ 'goldenrod', 'cornflowerblue', 'dodgerblue', 'lawngreen', ] const Circle = styled.circle` stroke-width: 0.1px; vector-effect: non-scaling-stroke; r: 0.04px; pointer-events: none; transition: r 0.15s; fill: ${HANDLE_COLOR}; ` const HitZone = styled.circle` stroke-width: 0.1px; vector-effect: non-scaling-stroke; r: 0.09px; cursor: move; ${pointerEventsAutoInNormalMode}; &:hover { opacity: 0.4; } &:hover + ${Circle} { fill: ${HANDLE_HOVER_COLOR}; } ` type ICurveSegmentEditorProps = { onCurveChange: (newHandles: CubicBezierHandles) => void onCancelCurveChange: () => void curveConnection: KeyframeConnectionWithAddress backgroundConnections: Array } const CurveSegmentEditor: React.VFC = (props) => { const { curveConnection: {left, right}, backgroundConnections, } = props // Calculations towards keeping the handles in the viewbox. The extremum space // of this editor vertically scales to keep the handles in the viewbox of the // SVG. This produces a nice "stretching space" effect while you are dragging // the handles. // Demo: https://user-images.githubusercontent.com/11082236/164542544-f1f66de2-f62e-44dd-b4cb-05b5f6e73a52.mp4 const minY = Math.min(0, 1 - right.handles[1], 1 - left.handles[3]) const maxY = Math.max(1, 1 - right.handles[1], 1 - left.handles[3]) const h = Math.max(1, maxY - minY) const toExtremumSpace = (y: number) => (y - minY) / h const [refSVG, nodeSVG] = useRefAndState(null) const viewboxToElWidthRatio = VIEWBOX_SIZE / (nodeSVG?.clientWidth || 1) const viewboxToElHeightRatio = VIEWBOX_SIZE / (nodeSVG?.clientHeight || 1) const [refLeft, nodeLeft] = useRefAndState(null) useKeyframeDrag(nodeSVG, nodeLeft, props, (dx, dy) => { // TODO - document this const handleX = clamp(left.handles[2] + dx * viewboxToElWidthRatio, 0, 1) const handleY = left.handles[3] - dy * viewboxToElHeightRatio return [handleX, handleY, right.handles[0], right.handles[1]] }) const [refRight, nodeRight] = useRefAndState(null) useKeyframeDrag(nodeSVG, nodeRight, props, (dx, dy) => { // TODO - document this const handleX = clamp(right.handles[0] + dx * viewboxToElWidthRatio, 0, 1) const handleY = right.handles[1] - dy * viewboxToElHeightRatio return [left.handles[2], left.handles[3], handleX, handleY] }) const curvePathDAttrValue = (connection: KeyframeConnectionWithAddress) => `M0 ${toExtremumSpace(1)} C${connection.left.handles[2]} ${toExtremumSpace( 1 - connection.left.handles[3], )} ${connection.right.handles[0]} ${toExtremumSpace( 1 - connection.right.handles[1], )} 1 ${toExtremumSpace(0)}` const holdPointsAttrValue = `0,100 100,100 100,0` return ( {/* Unit space, opaque white dot pattern */} {/* Fills the whole vertical extremum space, gray dot pattern */} {!left.type || left.type === 'bezier' ? ( <> {/* Line from right end of curve to right handle */} {/* Line from left end of curve to left handle */} {/* Curve "shadow": the low-opacity filled area between the curve and the diagonal */} {/* The background curves (e.g. multiple different values) */} {backgroundConnections.map((connection, i) => ( ))} {/* The curve */} {/* Right end of curve */} {/* Left end of curve */} {/* Right handle and hit zone */} {/* Left handle and hit zone */} ) : ( <> )} ) } export default CurveSegmentEditor function useKeyframeDrag( svgNode: SVGSVGElement | null, node: SVGCircleElement | null, props: ICurveSegmentEditorProps, setHandles: (dx: number, dy: number) => CubicBezierHandles, ): void { const handlers = useFreezableMemo[1]>( (setFrozen) => ({ debugName: 'CurveSegmentEditor/useKeyframeDrag', lockCSSCursorTo: 'move', onDragStart() { setFrozen(true) return { onDrag(dx, dy) { if (!svgNode) return props.onCurveChange(setHandles(dx, dy)) }, onDragEnd(dragHappened) { setFrozen(false) props.onCancelCurveChange() }, } }, }), [svgNode, props.onCurveChange, props.onCancelCurveChange], ) useDrag(node, handlers) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/EasingOption.tsx ================================================ import useTooltip from '@theatre/studio/uiComponents/Popover/useTooltip' import React from 'react' import styled, {css} from 'styled-components' import {handlesFromCssCubicBezierArgs} from './shared' import SVGCurveSegment from './SVGCurveSegment' import {mergeRefs} from 'react-merge-refs' import {COLOR_BASE} from './colors' import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover' const Wrapper = styled.div<{isSelected: boolean}>` position: relative; display: flex; align-items: center; justify-content: center; overflow: hidden; aspect-ratio: 1; transition: background-color 0.15s; background-color: ${COLOR_BASE}; border-radius: 2px; cursor: pointer; outline: none; ${({isSelected}) => isSelected && css` background-color: #383d42; `} &:hover { background-color: #31353a; } &:focus { background-color: #383d42; } ` const EasingTooltip = styled(BasicPopover)` padding: 0.5em; color: white; max-width: 240px; pointer-events: none !important; --popover-bg: black; --popover-outer-stroke: transparent; --popover-inner-stroke: transparent; box-shadow: none; ` type IProps = { easing: { label: string value: string } tooltipPlacement: 'top' | 'bottom' isSelected: boolean } & Parameters[0] const EasingOption: React.FC = React.forwardRef((props, ref) => { const [tooltip, tooltipHostRef] = useTooltip( {enabled: true, verticalPlacement: props.tooltipPlacement, verticalGap: 0}, () => ( {props.easing.label} ), ) return ( {tooltip} {/* In the past we used `dangerouslySetInnerHTML={{ _html: fuzzySort.highlight(presetSearchResults[index])}}` to display the name of the easing option, including an underline for the parts of it matching the search query. */} ) }) export default EasingOption ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/SVGCurveSegment.tsx ================================================ import React from 'react' import type {CubicBezierHandles} from './shared' const VIEWBOX_PADDING = 0.75 const SVG_CIRCLE_RADIUS = 0.1 const VIEWBOX_SIZE = 1 + VIEWBOX_PADDING * 2 const SELECTED_CURVE_COLOR = '#F5F5F5' const CURVE_COLOR = '#888888' const CONTROL_COLOR = '#4f4f4f' const CONTROL_HITZONE_COLOR = 'rgba(255, 255, 255, 0.1)' // SVG's y coordinates go from top to bottom, e.g. 1 is vertically lower than 0, // but easing points go from bottom to top. const toVerticalSVGSpace = (y: number) => 1 - y type IProps = { easing: CubicBezierHandles | null isSelected: boolean } const SVGCurveSegment: React.FC = (props) => { const {easing, isSelected} = props const curveColor = isSelected ? SELECTED_CURVE_COLOR : CURVE_COLOR // With a padding of 0, this results in a "unit viewbox" i.e. `0 0 1 1`. // With padding e.g. VIEWBOX_PADDING=0.1, this results in a viewbox of `-0.1 -0,1 1.2 1.2`, // i.e. a viewbox with a top left coordinate of -0.1,-0.1 and a width and height of 1.2, // resulting in bottom right coordinate of 1.1,1.1 const SVG_VIEWBOX_ATTR = `${-VIEWBOX_PADDING} ${-VIEWBOX_PADDING} ${VIEWBOX_SIZE} ${VIEWBOX_SIZE}` // Bezier SVG if (easing) { const leftControlPoint = [easing[0], toVerticalSVGSpace(easing[1])] const rightControlPoint = [easing[2], toVerticalSVGSpace(easing[3])] return ( {/* Control lines */} {/* Control point hitzonecircles */} {/* Control point circles */} {/* Bezier curve */} ) } // "Hold" SVG return ( ) } export default SVGCurveSegment ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/colors.ts ================================================ export const COLOR_POPOVER_BACK = 'rgba(26, 28, 30, 0.97);' export const COLOR_BASE = '#272B2F' export const COLOR_FOCUS_OUTLINE = '#0A4540' ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/shared.ts ================================================ /** * This 4-tuple defines the start control point x1,y1 and the end control point x2,y2 * of a cubic bezier curve. It is assumed that the start of the curve is fixed at 0,0 * and the end is fixed at 1,1. X values must be constrained to `0 <= x1 <= 1 and 0 <= x2 <= 1`. * * to get a feel for it: https://cubic-bezier.com/ **/ export type CubicBezierHandles = [ x1: number, y1: number, x2: number, y2: number, ] /** * A full CSS cubic bezier string looks like `cubic-bezier(0, 0, 1, 1)`. * the "args" part of the name refers specifically to the comma separated substring * inside the parentheses of the CSS cubic bezier string i.e. `0, 0, 1, 1`. */ export type CSSCubicBezierArgsString = string const CSS_BEZIER_ARGS_DECIMAL_POINTS = 3 // Doesn't have to be 3, but it matches our preset data /** Returns e.g. `"0, 0, 1, 1"`. See {@link CSSCubicBezierArgsString} docs for more context. */ export function cssCubicBezierArgsFromHandles( points: CubicBezierHandles, ): CSSCubicBezierArgsString { return points.map((p) => p.toFixed(CSS_BEZIER_ARGS_DECIMAL_POINTS)).join(', ') } const MAX_REASONABLE_BEZIER_STRING = 128 export function handlesFromCssCubicBezierArgs( str: CSSCubicBezierArgsString | undefined | null, ): null | CubicBezierHandles { if (!str || str?.length > MAX_REASONABLE_BEZIER_STRING) return null const args = str.split(',') if (args.length !== 4) return null const nums = args.map((arg) => { return Number(arg.trim()) }) if (!nums.every((v) => isFinite(v))) return null if (nums[0] < 0 || nums[0] > 1 || nums[2] < 0 || nums[2] > 1) return null return nums as CubicBezierHandles } /** * A collection of cubic-bezier approximations of common easing functions * - ref: https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function * - ref: [GitHub issue 28 comment "michaeltheory's suggested default easing presets"](https://github.com/theatre-js/theatre/issues/28#issuecomment-938752916) **/ export const EASING_PRESETS = [ {label: 'Quad Out', value: '0.250, 0.460, 0.450, 0.940'}, {label: 'Quad In Out', value: '0.455, 0.030, 0.515, 0.955'}, {label: 'Quad In', value: '0.550, 0.085, 0.680, 0.530'}, {label: 'Cubic Out', value: '0.215, 0.610, 0.355, 1.000'}, {label: 'Cubic In Out', value: '0.645, 0.045, 0.355, 1.000'}, {label: 'Cubic In', value: '0.550, 0.055, 0.675, 0.190'}, {label: 'Quart Out', value: '0.165, 0.840, 0.440, 1.000'}, {label: 'Quart In Out', value: '0.770, 0.000, 0.175, 1.000'}, {label: 'Quart In', value: '0.895, 0.030, 0.685, 0.220'}, {label: 'Quint Out', value: '0.230, 1.000, 0.320, 1.000'}, {label: 'Quint In Out', value: '0.860, 0.000, 0.070, 1.000'}, {label: 'Quint In', value: '0.755, 0.050, 0.855, 0.060'}, {label: 'Sine Out', value: '0.390, 0.575, 0.565, 1.000'}, {label: 'Sine In Out', value: '0.445, 0.050, 0.550, 0.950'}, {label: 'Sine In', value: '0.470, 0.000, 0.745, 0.715'}, {label: 'Expo Out', value: '0.190, 1.000, 0.220, 1.000'}, {label: 'Expo In Out', value: '1.000, 0.000, 0.000, 1.000'}, {label: 'Expo In', value: '0.780, 0.000, 0.810, 0.00'}, {label: 'Circ Out', value: '0.075, 0.820, 0.165, 1.000'}, {label: 'Circ In Out', value: '0.785, 0.135, 0.150, 0.860'}, {label: 'Circ In', value: '0.600, 0.040, 0.980, 0.335'}, {label: 'Back Out', value: '0.175, 0.885, 0.320, 1.275'}, {label: 'Back In Out', value: '0.680, -0.550, 0.265, 1.550'}, {label: 'Back In', value: '0.600, -0.280, 0.735, 0.045'}, {label: 'linear', value: '0.5, 0.5, 0.5, 0.5'}, {label: 'In Out', value: '0.42,0,0.58,1'}, {label: 'Hold', value: '0, 0, Infinity, Infinity'}, /* These easings are not being included initially in order to simplify the choices */ // {label: 'Back In Out', value: '0.680, -0.550, 0.265, 1.550'}, // {label: 'Back In', value: '0.600, -0.280, 0.735, 0.045'}, // {label: 'Back Out', value: '0.175, 0.885, 0.320, 1.275'}, // {label: 'Circ In Out', value: '0.785, 0.135, 0.150, 0.860'}, // {label: 'Circ In', value: '0.600, 0.040, 0.980, 0.335'}, // {label: 'Circ Out', value: '0.075, 0.820, 0.165, 1'}, // {label: 'Quad In Out', value: '0.455, 0.030, 0.515, 0.955'}, // {label: 'Quad In', value: '0.550, 0.085, 0.680, 0.530'}, // {label: 'Quad Out', value: '0.250, 0.460, 0.450, 0.940'}, // {label: 'Ease Out In', value: '.42, 0, .58, 1'}, ] /** * Compares two easings and returns true iff they are similar up to a threshold * * @param easing1 - first easing to compare * @param easing2 - second easing to compare * @param options - optionally pass an object with a threshold that determines how similar the easings should be * @returns boolean if the easings are similar */ export function areEasingsSimilar( easing1: CubicBezierHandles | null | undefined, easing2: CubicBezierHandles | null | undefined, options: { threshold: number } = {threshold: 0.02}, ) { if (!easing1 || !easing2) return false let totalDiff = 0 for (let i = 0; i < 4; i++) { totalDiff += Math.abs(easing1[i] - easing2[i]) } return totalDiff < options.threshold } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/useFreezableMemo.ts ================================================ import {useMemo, useRef, useState} from 'react' /** * The same as useMemo except that it can be frozen so that * the memoized function is not recomputed even if the dependencies * change. It can also be unfrozen. * * An unfrozen useFreezableMemo is the same as useMemo. * */ export function useFreezableMemo( fn: (setFreeze: (isFrozen: boolean) => void) => T, deps: any[], ): T { const [isFrozen, setFreeze] = useState(false) const freezableDeps = useRef(deps) if (!isFrozen) freezableDeps.current = deps return useMemo(() => fn(setFreeze), freezableDeps.current) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/useUIOptionGrid.tsx ================================================ import type {KeyboardEvent} from 'react' import type React from 'react' import {useState} from 'react' import {mod} from './CurveEditorPopover' export enum Outcome { Handled = 1, Passthrough = 0, } type UIOptionGridOptions = { /** affect behavior of keyboard navigation */ uiColumns: number /** each item in the grid */ items: Item[] /** display of items */ renderItem: (value: { select(e?: Event): void /** data item */ item: Item /** arrow key nav */ isSelected: boolean }) => React.ReactNode onSelectItem(item: Item): Outcome /** Set a callback for what to do if we try to leave the grid */ canVerticleExit?: (exitSide: 'top' | 'bottom') => Outcome } type UIOptionGrid = { focusFirstItem(): void onParentEltKeyDown(evt: KeyboardEvent): Outcome gridItems: React.ReactNode[] currentSelection: Item | null } export function useUIOptionGrid( options: UIOptionGridOptions, ): UIOptionGrid { // Helper functions for moving the highlight in the grid of presets const [selectionIndex, setSelectionIndex] = useState(null) const moveCursorVertical = (vdir: number) => { if (selectionIndex === null) { if (options.items.length > 0) { // start at the top first one setSelectionIndex(0) } else { // no items } return } const nextSelectionIndex = selectionIndex + vdir * options.uiColumns const exitsTop = nextSelectionIndex < 0 const exitsBottom = nextSelectionIndex > options.items.length - 1 if (exitsTop || exitsBottom) { // up and out if (options.canVerticleExit) { if (options.canVerticleExit(exitsTop ? 'top' : 'bottom')) { // exited and handled setSelectionIndex(null) return } } // block the cursor from leaving (don't do anything) return } // we know this highlight is in bounds now setSelectionIndex(nextSelectionIndex) } const moveCursorHorizontal = (hdir: number) => { if (selectionIndex === null) setSelectionIndex(mod(hdir, options.items.length)) else if (selectionIndex + hdir < 0) { // Don't exit top on potentially a left arrow, bc that might feel like I should be able to exit right on right arrow. // Also, maybe cursor selection management in inputs is *lame*. setSelectionIndex(null) } else setSelectionIndex( Math.min(selectionIndex + hdir, options.items.length - 1), ) } const onParentKeydown = (e: KeyboardEvent) => { if (e.key === 'ArrowRight') moveCursorHorizontal(1) else if (e.key === 'ArrowLeft') moveCursorHorizontal(-1) else if (e.key === 'ArrowUp') moveCursorVertical(-1) else if (e.key === 'ArrowDown') moveCursorVertical(1) else return Outcome.Passthrough // so sorry, plz make this not terrible return Outcome.Handled } return { focusFirstItem() { setSelectionIndex(0) }, onParentEltKeyDown: onParentKeydown, gridItems: options.items.map((item, idx) => options.renderItem({ isSelected: idx === selectionIndex, item, select(e) { setSelectionIndex(idx) if (options.onSelectItem(item) === Outcome.Handled) { e?.preventDefault() e?.stopPropagation() } }, }), ), currentSelection: options.items[selectionIndex ?? -1] ?? null, } } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/DeterminePropEditorForSingleKeyframe.tsx ================================================ import React from 'react' import styled from 'styled-components' import type {PropTypeConfig_AllSimples} from '@theatre/core/types/public' import type {ISimplePropEditorReactProps} from '@theatre/studio/propEditors/simpleEditors/ISimplePropEditorReactProps' import {simplePropEditorByPropType} from '@theatre/studio/propEditors/simpleEditors/simplePropEditorByPropType' import type { EditingOptionsTree, PrimitivePropEditingOptions, } from './useSingleKeyframeInlineEditorPopover' import last from 'lodash-es/last' import {useTempTransactionEditingTools} from './useTempTransactionEditingTools' import {__private} from '@theatre/core' const {valueInProp} = __private.propTypeUtils const SingleKeyframePropEditorContainer = styled.div` display: flex; align-items: stretch; min-width: 200px; select { min-width: 100px; } ` const SingleKeyframePropLabel = styled.div` font-style: normal; font-weight: 400; font-size: 11px; line-height: 13px; letter-spacing: 0.01em; padding: 6px 6px 6px 0; width: 40%; color: #919191; overflow: hidden; ` const INDENT_PX = 10 /** * Given a propConfig, this function gives the corresponding prop editor for * use in the dope sheet inline prop editor on a keyframe. * {@link DeterminePropEditorForDetail} does the same thing for the details panel. The main difference * between this function and {@link DeterminePropEditorForDetail} is that this * one shows prop editors *without* keyframe navigation controls (that look * like `< ・ >`). * * @param p - propConfig object for any type of prop. */ export function DeterminePropEditorForKeyframeTree( p: EditingOptionsTree & {autoFocusInput?: boolean; indent: number}, ) { if (p.type === 'sheetObject') { return ( <> {p.sheetObject.address.objectKey} {p.children.map((c, i) => ( ))} ) } else if (p.type === 'propWithChildren') { const label = p.propConfig.label ?? last(p.pathToProp) return ( <> {label} {p.children.map((c, i) => ( ))} ) } else { return ( ) } } const SingleKeyframeSimplePropEditorContainer = styled.div` display: flex; align-items: center; width: 60%; ` function PrimitivePropEditor( p: PrimitivePropEditingOptions & {autoFocusInput?: boolean; indent: number}, ) { const label = p.propConfig.label ?? last(p.pathToProp) const editingTools = useEditingToolsForKeyframeEditorPopover(p) if (p.propConfig.type === 'enum') { // notice: enums are not implemented, yet. return <> } else { const PropEditor = simplePropEditorByPropType[ p.propConfig.type ] as React.VFC> return ( {label} ) } } // These editing tools are distinct from the editing tools used in the // prop editors in the details panel: These editing tools edit the value of a keyframe // while the details editing tools edit the value of the sequence at the playhead // (potentially creating a new keyframe). function useEditingToolsForKeyframeEditorPopover( props: PrimitivePropEditingOptions, ) { const obj = props.sheetObject return useTempTransactionEditingTools(({stateEditors}, value) => { const newKeyframe = {...props.keyframe, value} stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes({ ...obj.address, trackId: props.trackId, keyframes: [newKeyframe], snappingFunction: obj.sheet.getSequence().closestGridPosition, }) }, obj) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/SingleKeyframeDot.tsx ================================================ import React, {useMemo, useRef} from 'react' import styled from 'styled-components' import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import type {DragOpts} from '@theatre/studio/uiComponents/useDrag' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {val} from '@theatre/dataverse' import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler' import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap' import type {ISingleKeyframeEditorProps} from './SingleKeyframeEditor' import {absoluteDims} from '@theatre/studio/utils/absoluteDims' import {useLogger} from '@theatre/studio/uiComponents/useLogger' import type {ILogger} from '@theatre/utils/logger' import {copyableKeyframesFromSelection} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import { collectKeyframeSnapPositions, snapToNone, snapToSome, } from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget' import {useKeyframeInlineEditorPopover} from './useSingleKeyframeInlineEditorPopover' import usePresence, { PresenceFlag, } from '@theatre/studio/uiComponents/usePresence' import {__private} from '@theatre/core' const {keyframeUtils} = __private export const DOT_SIZE_PX = 6 const DOT_HOVER_SIZE_PX = DOT_SIZE_PX + 2 const dotTheme = { normalColor: '#40AAA4', selectedColor: '#F2C95C', inlineEditorOpenColor: '#FCF3DC', selectedAndInlineEditorOpenColor: '#CBEBEA', } const selectBackgroundForDiamond = ({ isSelected, isInlineEditorPopoverOpen, }: IDiamond) => { if (isSelected && isInlineEditorPopoverOpen) { return dotTheme.inlineEditorOpenColor } else if (isSelected) { return dotTheme.selectedColor } else if (isInlineEditorPopoverOpen) { return dotTheme.selectedAndInlineEditorOpenColor } else { return dotTheme.normalColor } } type IDiamond = { isSelected: boolean isInlineEditorPopoverOpen: boolean flag: PresenceFlag | undefined } /** The keyframe diamond ◆ */ const Diamond = styled.div` position: absolute; ${absoluteDims(DOT_SIZE_PX)} background: ${(props) => selectBackgroundForDiamond(props)}; transform: rotateZ(45deg); ${(props) => props.flag === PresenceFlag.Primary ? 'outline: 2px solid white;' : ''}; z-index: 1; pointer-events: none; ` const Square = styled.div` position: absolute; ${absoluteDims(DOT_SIZE_PX * 1.5)} background: ${(props) => selectBackgroundForDiamond(props)}; ${(props) => props.flag === PresenceFlag.Primary ? 'outline: 2px solid white;' : ''}; z-index: 1; pointer-events: none; ` const HitZone = styled.div<{isInlineEditorPopoverOpen: boolean}>` z-index: 1; cursor: ew-resize; position: absolute; ${absoluteDims(12)}; ${pointerEventsAutoInNormalMode}; & + ${Diamond} { ${(props) => props.isInlineEditorPopoverOpen ? absoluteDims(DOT_HOVER_SIZE_PX) : ''} } &:hover + ${Diamond} { ${absoluteDims(DOT_HOVER_SIZE_PX)} } ` type ISingleKeyframeDotProps = ISingleKeyframeEditorProps /** The ◆ you can grab onto in "keyframe editor" (aka "dope sheet" in other programs) */ const SingleKeyframeDot: React.VFC = (props) => { const logger = useLogger('SingleKeyframeDot', props.keyframe.id) const presence = usePresence(props.itemKey) const [ref, node] = useRefAndState(null) const [contextMenu] = useSingleKeyframeContextMenu(node, logger, props) const { node: inlineEditorPopover, toggle: toggleEditor, isOpen: isInlineEditorPopoverOpen, } = useKeyframeInlineEditorPopover([ { type: 'primitiveProp', keyframe: props.keyframe, pathToProp: props.leaf.pathToProp, propConfig: props.leaf.propConf, sheetObject: props.leaf.sheetObject, trackId: props.leaf.trackId, }, ]) const [isDragging] = useDragForSingleKeyframeDot(node, props, { onClickFromDrag(dragStartEvent) { toggleEditor(dragStartEvent, ref.current!) }, }) const showDiamond = !props.keyframe.type || props.keyframe.type === 'bezier' return ( <> {showDiamond ? ( ) : ( )} {inlineEditorPopover} {contextMenu} ) } export default SingleKeyframeDot function useSingleKeyframeContextMenu( target: HTMLDivElement | null, logger: ILogger, props: ISingleKeyframeDotProps, ) { return useContextMenu(target, { displayName: 'Keyframe', items: () => { const copyableKeyframes = copyableKeyframesFromSelection( props.leaf.sheetObject.address.projectId, props.leaf.sheetObject.address.sheetId, props.selection, ) return [ { type: 'normal', label: copyableKeyframes.length > 0 ? 'Copy (selection)' : 'Copy', callback: () => { if (copyableKeyframes.length > 0) { getStudio!().transaction((api) => { api.stateEditors.studio.ahistoric.setClipboardKeyframes( copyableKeyframes, ) }) } else { getStudio!().transaction((api) => { api.stateEditors.studio.ahistoric.setClipboardKeyframes([ {keyframe: props.keyframe, pathToProp: props.leaf.pathToProp}, ]) }) } }, }, { type: 'normal', label: props.selection !== undefined ? 'Delete (selection)' : 'Delete', callback: () => { if (props.selection) { props.selection.delete() } else { getStudio()!.transaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes( { ...props.leaf.sheetObject.address, keyframeIds: [props.keyframe.id], trackId: props.leaf.trackId, }, ) }) } }, }, ] }, onOpen() { logger._debug('Show keyframe', props) }, }) } function useDragForSingleKeyframeDot( node: HTMLDivElement | null, props: ISingleKeyframeDotProps, options: { /** * hmm: this is a hack so we can actually receive the * {@link MouseEvent} from the drag event handler and use * it for positioning the popup. */ onClickFromDrag(dragStartEvent: MouseEvent): void }, ): [isDragging: boolean] { const propsRef = useRef(props) propsRef.current = props const {onClickFromDrag} = options const useDragOpts = useMemo(() => { return { debugName: 'KeyframeDot/useDragKeyframe', onDragStart(event) { const props = propsRef.current const tracksByObject = val( getStudio()!.atomP.historic.coreByProject[ props.leaf.sheetObject.address.projectId ].sheetsById[props.leaf.sheetObject.address.sheetId].sequence .tracksByObject, )! const snapPositions = collectKeyframeSnapPositions( tracksByObject, // Calculate all the valid snap positions in the sequence editor, // excluding this keyframe, and any selection it is part of. function shouldIncludeKeyfram(keyframe, {trackId, objectKey}) { return ( // we exclude this keyframe from being a snap target keyframe.id !== props.keyframe.id && !( // if the current dragged keyframe is in the selection, ( props.selection && // then we exclude it and all other keyframes in the selection from being snap targets props.selection.byObjectKey[objectKey]?.byTrackId[trackId] ?.byKeyframeId[keyframe.id] ) ) ) }, ) snapToSome(snapPositions) if (props.selection) { const {selection, leaf} = props const {sheetObject} = leaf const handlers = selection .getDragHandlers({ ...sheetObject.address, domNode: node!, positionAtStartOfDrag: keyframeUtils.getSortedKeyframesCached( props.track.data.keyframes, )[props.index].position, }) .onDragStart(event) // this opens the regular inline keyframe editor on click. // in the future, we may want to show an multi-editor, like in the // single tween editor, so that selected keyframes' values can be changed // together return ( handlers && { ...handlers, onClick: onClickFromDrag, onDragEnd: (...args) => { handlers.onDragEnd?.(...args) snapToNone() }, } ) } const propsAtStartOfDrag = props const toUnitSpace = val( propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, ) let tempTransaction: CommitOrDiscardOrRecapture | undefined return { onDrag(dx, dy, event) { const original = keyframeUtils.getSortedKeyframesCached( propsAtStartOfDrag.track.data.keyframes, )[propsAtStartOfDrag.index] const newPosition = Math.max( // check if our event hoversover a [data-pos] element DopeSnap.checkIfMouseEventSnapToPos(event, { ignore: node, }) ?? // if we don't find snapping target, check the distance dragged + original position original.position + toUnitSpace(dx), // sanitize to minimum of zero 0, ) tempTransaction?.discard() tempTransaction = undefined tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes( { ...propsAtStartOfDrag.leaf.sheetObject.address, trackId: propsAtStartOfDrag.leaf.trackId, keyframes: [{...original, position: newPosition}], snappingFunction: val( propsAtStartOfDrag.layoutP.sheet, ).getSequence().closestGridPosition, }, ) }) }, onDragEnd(dragHappened) { if (dragHappened) { tempTransaction?.commit() } else { tempTransaction?.discard() } snapToNone() }, onClick(ev) { onClickFromDrag(ev) }, } }, } }, [onClickFromDrag]) const [isDragging] = useDrag(node, useDragOpts) // Lock frame stamp to the current position of the dragged keyframe instead of // the mouse position, so that it appears centered above the keyframe even // regardless of where in the hit zone of the keyframe the mouse is located. useLockFrameStampPosition(isDragging, props.keyframe.position) useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize') return [isDragging] } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/SingleKeyframeEditor.tsx ================================================ import type { DopeSheetSelection, SequenceEditorPanelLayout, } from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type {SequenceEditorTree_PrimitiveProp} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import SingleKeyframeConnector from './BasicKeyframeConnector' import SingleKeyframeDot from './SingleKeyframeDot' import type {TrackWithId} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes' import type {StudioSheetItemKey} from '@theatre/core/types/private' import type {BasicKeyframe} from '@theatre/core' import {__private} from '@theatre/core' const {keyframeUtils} = __private const SingleKeyframeEditorContainer = styled.div` position: absolute; ` const noConnector = <> export type ISingleKeyframeEditorProps = { index: number keyframe: BasicKeyframe track: TrackWithId itemKey: StudioSheetItemKey layoutP: Pointer leaf: SequenceEditorTree_PrimitiveProp selection: undefined | DopeSheetSelection } const SingleKeyframeEditor: React.VFC = React.memo( (props) => { const { index, track: {data: trackData}, } = props const cur = keyframeUtils.getSortedKeyframesCached(trackData.keyframes)[ index ] const next = keyframeUtils.getSortedKeyframesCached(trackData.keyframes)[ index + 1 ] const connected = cur.connectedRight && !!next return ( {connected ? : noConnector} ) }, ) export default SingleKeyframeEditor ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover.tsx ================================================ import React from 'react' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover' import {DeterminePropEditorForKeyframeTree} from './DeterminePropEditorForSingleKeyframe' import type {SequenceTrackId} from '@theatre/core/types/public' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type { PropTypeConfig_AllSimples, PropTypeConfig_Compound, PropTypeConfig_Enum, UnknownValidCompoundProps, BasicKeyframe, } from '@theatre/core/types/public' import type {PathToProp} from '@theatre/utils/pathToProp' /** The editor that pops up when directly clicking a Keyframe. */ export function useKeyframeInlineEditorPopover( props: EditingOptionsTree[] | null, ) { return usePopover({debugName: 'useKeyframeInlineEditorPopover'}, () => ( {!Array.isArray(props) ? undefined : props.map((prop, i) => ( ))} )) } export type EditingOptionsTree = | SheetObjectEditingOptionsTree | PropWithChildrenEditingOptionsTree | PrimitivePropEditingOptions export type SheetObjectEditingOptionsTree = { type: 'sheetObject' sheetObject: SheetObject children: EditingOptionsTree[] } export type PropWithChildrenEditingOptionsTree = { type: 'propWithChildren' propConfig: PropTypeConfig_Compound pathToProp: PathToProp children: EditingOptionsTree[] } export type PrimitivePropEditingOptions = { type: 'primitiveProp' keyframe: BasicKeyframe propConfig: PropTypeConfig_AllSimples | PropTypeConfig_Enum // note: enums are not implemented yet sheetObject: SheetObject trackId: SequenceTrackId pathToProp: PathToProp } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useTempTransactionEditingTools.tsx ================================================ import getStudio from '@theatre/studio/getStudio' import type {SerializableValue} from '@theatre/core/types/public' import type { CommitOrDiscardOrRecapture, ITransactionPrivateApi, } from '@theatre/studio/StudioStore/StudioStore' import type {IEditingTools} from '@theatre/studio/propEditors/utils/IEditingTools' import {useMemo} from 'react' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type {Asset} from '@theatre/core/types/public' /** * This function takes a function `writeTx` that sets a value in the private Studio API and * returns a memoized editingTools object which contains three functions: * - `temporarilySetValue` - uses `writeTx` to set a value that can be discarded * - `discardTemporaryValue` - if `temporarilySetValue` was called, discards the value it set * - `permanentlySetValue` - uses `writeTx` to set a value * * @param writeTx - a function that uses a value to perform an action using the * private Studio API. * @returns an editingTools object that can be passed to `DeterminePropEditorForKeyframe` or * `DetailDeterminePropEditor` and is used by the prop editors in `simplePropEditorByPropType`. */ export function useTempTransactionEditingTools( writeTx: (api: ITransactionPrivateApi, value: T) => void, obj: SheetObject, ): IEditingTools { return useMemo(() => createTempTransactionEditingTools(writeTx, obj), []) } function createTempTransactionEditingTools( writeTx: (api: ITransactionPrivateApi, value: T) => void, obj: SheetObject, ) { let currentTransaction: CommitOrDiscardOrRecapture | null = null const createTempTx = (value: T) => getStudio().tempTransaction((api) => writeTx(api, value)) function discardTemporaryValue() { currentTransaction?.discard() currentTransaction = null } const editAssets = { createAsset: obj.sheet.project.assetStorage.createAsset, getAssetUrl: (asset: Asset) => asset.id ? obj.sheet.project.assetStorage.getAssetUrl(asset.id) : undefined, } return { temporarilySetValue(value: T): void { discardTemporaryValue() currentTransaction = createTempTx(value) }, discardTemporaryValue, permanentlySetValue(value: T): void { discardTemporaryValue() createTempTx(value).commit() }, ...editAssets, } } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/DopeSheetBackground.tsx ================================================ import {theme} from '@theatre/studio/css' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel' import {useVal} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import {darken, transparentize} from 'polished' import React from 'react' import styled from 'styled-components' import FrameGrid from '@theatre/studio/panels/SequenceEditorPanel/FrameGrid/FrameGrid' const Container = styled.div` position: absolute; top: 0; right: 0; bottom: 0; z-index: ${() => zIndexes.rightBackground}; overflow: hidden; background: ${transparentize(0.01, darken(1 * 0.03, theme.panel.bg))}; pointer-events: none; ` const DopeSheetBackground: React.FC<{ layoutP: Pointer }> = ({layoutP}) => { const width = useVal(layoutP.rightDims.width) const height = useVal(layoutP.panelDims.height) return ( ) } export default DopeSheetBackground ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/DopeSheetSelectionView.tsx ================================================ import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import useDrag from '@theatre/studio/uiComponents/useDrag' import useKeyDown from '@theatre/studio/uiComponents/useKeyDown' import useValToAtom from '@theatre/studio/uiComponents/useValToAtom' import mutableSetDeep from '@theatre/utils/mutableSetDeep' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {usePrism} from '@theatre/react' import type {$IntentionalAny} from '@theatre/core/types/public' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React, {useMemo, useRef} from 'react' import styled from 'styled-components' import type { DopeSheetSelection, SequenceEditorPanelLayout, } from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type { SequenceEditorTree_AllRowTypes, SequenceEditorTree_PropWithChildren, SequenceEditorTree_Sheet, SequenceEditorTree_SheetObject, } from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap' import {collectAggregateKeyframesInPrism} from './collectAggregateKeyframes' import type {ILogger, IUtilLogger} from '@theatre/utils/logger' import {useLogger} from '@theatre/studio/uiComponents/useLogger' import {__private} from '@theatre/core' const {keyframeUtils} = __private const HITBOX_SIZE_PX = 5 const Container = styled.div<{isShiftDown: boolean}>` cursor: ${(props) => (props.isShiftDown ? 'cell' : 'default')}; ` const DopeSheetSelectionView: React.FC<{ layoutP: Pointer height: number children: React.ReactNode }> = ({layoutP, children, height}) => { const [containerRef, containerNode] = useRefAndState( null, ) const isShiftDown = useKeyDown('Shift') const selectionBounds = useCaptureSelection(layoutP, containerNode) const selectionBoundsRef = useRef(selectionBounds) selectionBoundsRef.current = selectionBounds return ( {selectionBounds && ( )} {children} ) } /** * The horizontal and vertical bounds of the selection, each represented by a tuple in the form of [from, to]. */ type SelectionBounds = { /** * The horizontal bounds of the selection as a tuple of "from" and "to" coordinates, "from" representing the start of the drag. * * TODO - use nominal types here to clarify which space these numbers are in */ h: [from: number, to: number] /** * The vertical bounds of the selection as a tuple of "from" and "to" coordinates, "from" representing the start of the drag. */ v: [from: number, to: number] } function useCaptureSelection( layoutP: Pointer, containerNode: HTMLDivElement | null, ) { const [ref, state] = useRefAndState(null) const logger = useLogger('useCaptureSelection') useDrag( containerNode, useMemo((): Parameters[1] => { return { debugName: 'DopeSheetSelectionView/useCaptureSelection', dontBlockMouseDown: true, lockCSSCursorTo: 'cell', onDragStart(event) { if (!event.shiftKey || event.target instanceof HTMLInputElement) { return false } const rect = containerNode!.getBoundingClientRect() // all the `val()` calls here are meant to be read cold const posInScaledSpace = event.clientX - rect.left - // selection is happening in left padded space, convert it to normal space val(layoutP.scaledSpace.leftPadding) const posInUnitSpace = val(layoutP.scaledSpace.toUnitSpace)( posInScaledSpace, ) ref.current = { h: [posInUnitSpace, posInUnitSpace], v: [event.clientY - rect.top, event.clientY - rect.top], } val(layoutP.selectionAtom).set({current: undefined}) return { onDrag(_dx, _dy, event) { // const state = ref.current! const rect = containerNode!.getBoundingClientRect() const posInScaledSpace = event.clientX - rect.left - // selection is happening in left padded space, convert it to normal space val(layoutP.scaledSpace.leftPadding) const posInUnitSpace = val(layoutP.scaledSpace.toUnitSpace)( posInScaledSpace, ) ref.current = { h: [ref.current!.h[0], posInUnitSpace], v: [ref.current!.v[0], event.clientY - rect.top], } const selection = utils.boundsToSelection( logger, val(layoutP), ref.current, ) val(layoutP.selectionAtom).set({current: selection}) }, onDragEnd(_dragHappened) { ref.current = null }, } }, } }, [layoutP, containerNode, ref]), ) return state } namespace utils { const collectForAggregatedChildren = ( logger: IUtilLogger, layout: SequenceEditorPanelLayout, leaf: | SequenceEditorTree_SheetObject | SequenceEditorTree_PropWithChildren | SequenceEditorTree_Sheet, bounds: SelectionBounds, selectionByObjectKey: DopeSheetSelection['byObjectKey'], ) => { const aggregatedKeyframes = collectAggregateKeyframesInPrism(leaf) if ( leaf.top + leaf.nodeHeight / 2 + HITBOX_SIZE_PX > bounds.v[0] && leaf.top + leaf.nodeHeight / 2 - HITBOX_SIZE_PX < bounds.v[1] ) { for (const [position, keyframes] of aggregatedKeyframes.byPosition) { const hitboxWidth = layout.scaledSpace.toUnitSpace(HITBOX_SIZE_PX) const isHitboxOutsideSelection = position + hitboxWidth <= bounds.h[0] || position - hitboxWidth >= bounds.h[1] if (isHitboxOutsideSelection) continue for (const keyframeWithTrack of keyframes) { mutableSetDeep( selectionByObjectKey, (selectionByObjectKeyP) => // convenience for accessing a deep path which might not actually exist // through the use of pointer proxy (so we don't have to deal with undeifned ) selectionByObjectKeyP[ keyframeWithTrack.track.sheetObject.address.objectKey ].byTrackId[keyframeWithTrack.track.id].byKeyframeId[ keyframeWithTrack.kf.id ], true, ) } } } collectChildren(logger, layout, leaf, bounds, selectionByObjectKey) } const collectorByLeafType: { [K in SequenceEditorTree_AllRowTypes['type']]?: ( logger: IUtilLogger, layout: SequenceEditorPanelLayout, leaf: Extract, bounds: SelectionBounds, selectionByObjectKey: DopeSheetSelection['byObjectKey'], ) => void } = { sheet(logger, layout, leaf, bounds, selectionByObjectKey) { collectForAggregatedChildren( logger, layout, leaf, bounds, selectionByObjectKey, ) }, propWithChildren(logger, layout, leaf, bounds, selectionByObjectKey) { collectForAggregatedChildren( logger, layout, leaf, bounds, selectionByObjectKey, ) }, sheetObject(logger, layout, leaf, bounds, selectionByObjectKey) { collectForAggregatedChildren( logger, layout, leaf, bounds, selectionByObjectKey, ) }, primitiveProp(logger, layout, leaf, bounds, selectionByObjectKey) { const {sheetObject, trackId} = leaf const trackData = val( getStudio().atomP.historic.coreByProject[sheetObject.address.projectId] .sheetsById[sheetObject.address.sheetId].sequence.tracksByObject[ sheetObject.address.objectKey ].trackData[trackId], )! if ( bounds.v[0] > leaf.top + leaf.heightIncludingChildren / 2 + HITBOX_SIZE_PX || leaf.top + leaf.heightIncludingChildren / 2 - HITBOX_SIZE_PX > bounds.v[1] ) { return } for (const kf of keyframeUtils.getSortedKeyframesCached( trackData.keyframes, )) { if ( kf.position + layout.scaledSpace.toUnitSpace(HITBOX_SIZE_PX) <= bounds.h[0] ) continue if ( kf.position - layout.scaledSpace.toUnitSpace(HITBOX_SIZE_PX) >= bounds.h[1] ) break mutableSetDeep( selectionByObjectKey, (selectionByObjectKeyP) => // convenience for accessing a deep path which might not actually exist // through the use of pointer proxy (so we don't have to deal with undeifned ) selectionByObjectKeyP[sheetObject.address.objectKey].byTrackId[ trackId ].byKeyframeId[kf.id], true, ) } }, } const collectChildren = ( logger: IUtilLogger, layout: SequenceEditorPanelLayout, leaf: SequenceEditorTree_AllRowTypes, bounds: SelectionBounds, selectionByObjectKey: DopeSheetSelection['byObjectKey'], ) => { if ('children' in leaf) { for (const sub of leaf.children) { collectFromAnyLeaf(logger, layout, sub, bounds, selectionByObjectKey) } } } function collectFromAnyLeaf( logger: IUtilLogger, layout: SequenceEditorPanelLayout, leaf: SequenceEditorTree_AllRowTypes, bounds: SelectionBounds, selectionByObjectKey: DopeSheetSelection['byObjectKey'], ) { // don't collect from non rendered if (!leaf.shouldRender) return if ( bounds.v[0] > leaf.top + leaf.heightIncludingChildren || leaf.top > bounds.v[1] ) { return } const collector = collectorByLeafType[leaf.type] if (collector) { collector( logger, layout, leaf as $IntentionalAny, bounds, selectionByObjectKey, ) } else { collectChildren(logger, layout, leaf, bounds, selectionByObjectKey) } } export function boundsToSelection( logger: ILogger, layout: SequenceEditorPanelLayout, bounds: SelectionBounds, ): DopeSheetSelection { const selectionByObjectKey: DopeSheetSelection['byObjectKey'] = {} bounds = sortBounds(bounds) const tree = layout.tree collectFromAnyLeaf( logger.utilFor.internal(), layout, tree, bounds, selectionByObjectKey, ) const sheet = layout.tree.sheet return { type: 'DopeSheetSelection', byObjectKey: selectionByObjectKey, getDragHandlers(origin) { return { debugName: 'DopeSheetSelectionView/boundsToSelection', onDragStart() { let tempTransaction: CommitOrDiscardOrRecapture | undefined const toUnitSpace = layout.scaledSpace.toUnitSpace return { onDrag(dx, _, event) { if (tempTransaction) { tempTransaction.discard() tempTransaction = undefined } const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, { ignore: origin.domNode, }) const delta = snapPos != null ? snapPos - origin.positionAtStartOfDrag : toUnitSpace(dx) tempTransaction = getStudio().tempTransaction( ({stateEditors}) => { const transformKeyframes = stateEditors.coreByProject.historic.sheetsById.sequence .transformKeyframes for (const objectKey of Object.keys(selectionByObjectKey)) { const {byTrackId} = selectionByObjectKey[objectKey]! for (const trackId of Object.keys(byTrackId)) { const {byKeyframeId} = byTrackId[trackId]! transformKeyframes({ trackId, keyframeIds: Object.keys(byKeyframeId), translate: delta, scale: 1, origin: 0, snappingFunction: sheet.getSequence().closestGridPosition, objectKey, projectId: origin.projectId, sheetId: origin.sheetId, }) } } }, ) }, onDragEnd(dragHappened) { if (dragHappened) tempTransaction?.commit() else tempTransaction?.discard() }, } }, } }, delete() { getStudio().transaction(({stateEditors}) => { const deleteKeyframes = stateEditors.coreByProject.historic.sheetsById.sequence .deleteKeyframes for (const objectKey of Object.keys(selectionByObjectKey)) { const {byTrackId} = selectionByObjectKey[objectKey]! for (const trackId of Object.keys(byTrackId)) { const {byKeyframeId} = byTrackId[trackId]! deleteKeyframes({ ...sheet.address, objectKey, trackId, keyframeIds: Object.keys(byKeyframeId), }) } } }) }, } } } const SelectionRectangleDiv = styled.div` position: absolute; background: rgba(255, 255, 255, 0.1); border: 1px dashed rgba(255, 255, 255, 0.4); box-sizing: border-box; ` const sortBounds = (b: SelectionBounds): SelectionBounds => { return { h: [...b.h].sort((a, b) => a - b) as SelectionBounds['h'], v: [...b.v].sort((a, b) => a - b) as SelectionBounds['v'], } } const SelectionRectangle: React.VFC<{ state: SelectionBounds layoutP: Pointer }> = ({state, layoutP}) => { const atom = useValToAtom(state) return usePrism(() => { const state = val(atom.pointer) const sorted = sortBounds(state) const unitSpaceToScaledSpace = val(layoutP.scaledSpace.fromUnitSpace) const leftPadding = val(layoutP.scaledSpace.leftPadding) const positionsInScaledSpace = sorted.h .map(unitSpaceToScaledSpace) // bounds are in normal space, convert them left-padded space .map((coord) => coord + leftPadding) const top = sorted.v[0] const height = sorted.v[1] - sorted.v[0] const left = positionsInScaledSpace[0] const width = positionsInScaledSpace[1] - positionsInScaledSpace[0] return ( ) }, [layoutP, atom]) } export default DopeSheetSelectionView ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/FocusRangeCurtains.tsx ================================================ import type {Pointer} from '@theatre/dataverse' import {prism, val} from '@theatre/dataverse' import {usePrism} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {topStripHeight} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/TopStrip' import React, {useMemo} from 'react' import styled from 'styled-components' const divWidth = 1000 const Curtain = styled.div<{enabled: boolean}>` position: absolute; top: ${topStripHeight}px; left: 0; opacity: 0.15; width: ${divWidth}px; transform-origin: top left; pointer-events: none; background-color: ${(props) => (props.enabled ? '#000000' : 'transparent')}; ` const FocusRangeCurtains: React.FC<{ layoutP: Pointer }> = ({layoutP}) => { const existingRangeD = useMemo( () => prism(() => { const {projectId, sheetId} = val(layoutP.sheet).address const existingRange = val( getStudio().atomP.ahistoric.projects.stateByProjectId[projectId] .stateBySheetId[sheetId].sequence.focusRange, ) return existingRange }), [layoutP], ) return usePrism(() => { const existingRange = existingRangeD.getValue() if (!existingRange || !existingRange.enabled) return null const {range} = existingRange const height = val(layoutP.rightDims.height) - topStripHeight const unitSpaceToClippedSpace = val(layoutP.clippedSpace.fromUnitSpace) const clippedSpaceWidth = val(layoutP.clippedSpace.width) const els: Array<{translateX: number; scaleX: number}> = [] { // the left (start) curtain // starts from 0px let startX = 0 // ends in the start of the range let endX = unitSpaceToClippedSpace(existingRange.range[0]) let scaleX: number, translateX: number // hide the curtain if: if ( // endX would be larger than startX, which means the curtain is to the left of the RightOverlay startX > endX ) { // fully hide it then with scaleX = 0 translateX = 0 scaleX = 0 } else { // clip the end of the curtain if it's going over the right side of RightOverlay if (endX > clippedSpaceWidth) { // endX = clippedSpaceWidth } translateX = startX scaleX = (endX - startX) / divWidth } els.push({translateX, scaleX}) } { // the right (end) curtain // starts at the end of the range let startX = unitSpaceToClippedSpace(existingRange.range[1]) // and ends at the right edge of RightOverlay (which is clippedSpaceWidth) let endX = clippedSpaceWidth let scaleX: number, translateX: number // if the whole curtain falls to the right of RightOverlay, hide it if (startX > endX) { translateX = 0 scaleX = 0 } else { // if the left of the curtain falls on the left of RightOverlay, clip it if (startX < 0) { startX = 0 } translateX = startX scaleX = (endX - startX) / divWidth } els.push({translateX, scaleX}) } return ( <> {els.map(({translateX, scaleX}, i) => ( ))} ) }, [layoutP, existingRangeD]) } export default FocusRangeCurtains ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/HorizontallyScrollableArea.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {usePrism} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import {prism, val} from '@theatre/dataverse' import {clamp} from 'lodash-es' import React, {useLayoutEffect, useMemo} from 'react' import styled from 'styled-components' import {useReceiveVerticalWheelEvent} from '@theatre/studio/panels/SequenceEditorPanel/VerticalScrollContainer' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler' import type {IRange} from '@theatre/core/types/public' import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap' import {snapToAll, snapToNone} from './KeyframeSnapTarget' const Container = styled.div` position: absolute; right: 0; overflow-x: scroll; overflow-y: hidden; ${pointerEventsAutoInNormalMode}; // hide the scrollbar on Gecko scrollbar-width: none; // hide the scrollbar on Webkit/Blink &::-webkit-scrollbar { display: none; } ` const HorizontallyScrollableArea: React.FC<{ layoutP: Pointer height: number children: React.ReactNode }> = React.memo(({layoutP, children, height}) => { const {width, unitSpaceToScaledSpaceMultiplier} = usePrism( () => ({ width: val(layoutP.rightDims.width), unitSpaceToScaledSpaceMultiplier: val(layoutP.scaledSpace.fromUnitSpace)( 1, ), }), [layoutP], ) const [containerRef, containerNode] = useRefAndState( null, ) useHandlePanAndZoom(layoutP, containerNode) useDragPlayheadHandlers(layoutP, containerNode) useUpdateScrollFromClippedSpaceRange(layoutP, containerNode) return ( {children} ) }) export default HorizontallyScrollableArea function useDragPlayheadHandlers( layoutP: Pointer, containerEl: HTMLDivElement | null, ) { const handlers = useMemo((): Parameters[1] => { return { debugName: 'HorizontallyScrollableArea', onDragStart(event) { if (event.target instanceof HTMLInputElement) { // editing some value return false } if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) { // e.g. marquee selection has shiftKey return false } if ( event .composedPath() .some((el) => el instanceof HTMLElement && el.draggable === true) ) { // Question: I think to check if we want another descendent element // to be able to take control of this drag event. // Question: e.g. for `useDragKeyframe`? return false } const initialPositionInClippedSpace = event.clientX - containerEl!.getBoundingClientRect().left const initialPositionInUnitSpace = clamp( val(layoutP.clippedSpace.toUnitSpace)(initialPositionInClippedSpace), 0, Infinity, ) const setIsSeeking = val(layoutP.seeker.setIsSeeking) const sequence = val(layoutP.sheet).getSequence() sequence.position = initialPositionInUnitSpace const posBeforeSeek = initialPositionInUnitSpace const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) setIsSeeking(true) snapToAll() return { onDrag(dx: number, _, event) { const deltaPos = scaledSpaceToUnitSpace(dx) const unsnappedPos = clamp( posBeforeSeek + deltaPos, 0, sequence.length, ) let newPosition = unsnappedPos const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {}) if (snapPos != null) { newPosition = snapPos } sequence.position = newPosition }, onDragEnd() { setIsSeeking(false) snapToNone() }, } }, } }, [layoutP, containerEl]) const [isDragging] = useDrag(containerEl, handlers) useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize') } function useHandlePanAndZoom( layoutP: Pointer, node: HTMLDivElement | null, ) { const receiveVerticalWheelEvent = useReceiveVerticalWheelEvent() useLayoutEffect(() => { if (!node) return const receiveWheelEvent = (event: WheelEvent) => { // pinch if (event.ctrlKey) { event.preventDefault() event.stopPropagation() const pivotPointInClippedSpace = event.clientX - node.getBoundingClientRect().left const pivotPointInUnitSpace = val(layoutP.clippedSpace.toUnitSpace)( pivotPointInClippedSpace, ) const oldRange = val(layoutP.clippedSpace.range) const delta = normalize(event.deltaY, [-50, 50]) const scaleFactor = 1 + delta * 0.03 const newRange = oldRange.map((originalPos) => { return ( (originalPos - pivotPointInUnitSpace) * scaleFactor + pivotPointInUnitSpace ) }) as IRange // Set maximum scroll points based on the sequence length. // This is to avoid zooming out to infinity. const sequenceLength = val(layoutP.sheet).getSequence().length const maxEnd = sequenceLength + sequenceLength * 0.25 val(layoutP.clippedSpace.setRange)( normalizeRange(newRange, [0, maxEnd]), ) return } // panning else if (event.shiftKey) { event.preventDefault() event.stopPropagation() const sequenceLength = val(layoutP.sheet).getSequence().length const oldRange = val(layoutP.clippedSpace.range) const windowSize = oldRange[1] - oldRange[0] const speed = windowSize / sequenceLength // if there's no deltaY, the browser is probably assigning to deltaX because of the shiftKey // it appeared that Safari + Chrome continue to use deltaY with shiftKey, while FF on macOS // updates the deltaX with deltaY unchanged. // this is a little awkward with track pads + shift on macOS FF, but that's not a big deal // since scrolling horizontally with macOS track pads is not necessary to hold shift. const delta = normalize(event.deltaY || event.deltaX, [-50, 50]) const scaleFactor = delta * 0.05 * speed const newRange = oldRange.map( (originalPos) => originalPos + scaleFactor, ) as IRange val(layoutP.clippedSpace.setRange)(newRange) return } else { receiveVerticalWheelEvent(event) event.preventDefault() event.stopPropagation() const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) const deltaPos = scaledSpaceToUnitSpace(event.deltaX * 1) const oldRange = val(layoutP.clippedSpace.range) const newRange = oldRange.map((p) => p + deltaPos) as IRange const setRange = val(layoutP.clippedSpace.setRange) setRange(newRange) return } } const listenerOptions = { capture: true, passive: false, } node.addEventListener('wheel', receiveWheelEvent, listenerOptions) return () => { node.removeEventListener('wheel', receiveWheelEvent, listenerOptions) } }, [node, layoutP]) useDrag( node, useMemo[1]>(() => { return { onDragStart(e) { const oldRange = val(layoutP.clippedSpace.range) const setRange = val(layoutP.clippedSpace.setRange) const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) e.preventDefault() e.stopPropagation() return { onDrag(dx, dy, _, __, deltaYFromLastEvent) { receiveVerticalWheelEvent({deltaY: -deltaYFromLastEvent}) const delta = -scaledSpaceToUnitSpace(dx) const newRange = oldRange.map( (originalPos) => originalPos + delta, ) as IRange setRange(newRange) }, } }, debugName: 'HorizontallyScrollableArea Middle Button Drag', buttons: [1], lockCSSCursorTo: 'grabbing', } }, [layoutP]), ) } function normalize(value: number, [min, max]: [min: number, max: number]) { return Math.max(Math.min(value, max), min) } function normalizeRange( range: IRange, minMax: [min: number, max: number], ): IRange { return [normalize(range[0], minMax), normalize(range[1], minMax)] } function useUpdateScrollFromClippedSpaceRange( layoutP: Pointer, node: HTMLDivElement | null, ) { useLayoutEffect(() => { if (!node) return const d = prism(() => { const range = val(layoutP.clippedSpace.range) const rangeStartInScaledSpace = val(layoutP.scaledSpace.fromUnitSpace)( range[0], ) return rangeStartInScaledSpace }) const update = () => { const rangeStartInScaledSpace = d.getValue() node.scrollLeft = rangeStartInScaledSpace } const untap = d.onStale(update) update() const timeout = setTimeout(update, 100) return () => { clearTimeout(timeout) untap() } }, [layoutP, node]) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type {Pointer} from '@theatre/dataverse' import {Atom} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import {DopeSnapHitZoneUI} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnapHitZoneUI' import type { BasicKeyframe, ObjectAddressKey, SequenceTrackId, } from '@theatre/core/types/public' import type { BasicKeyframedTrack, HistoricPositionalSequence, } from '@theatre/core/types/private/core' import {__private} from '@theatre/core' const {keyframeUtils} = __private const HitZone = styled.div` z-index: 1; cursor: ew-resize; ${DopeSnapHitZoneUI.CSS} #pointer-root.draggingPositionInSequenceEditor & { ${DopeSnapHitZoneUI.CSS_WHEN_SOMETHING_DRAGGING} } ` const Container = styled.div` position: absolute; ` export type ISnapTargetPRops = { layoutP: Pointer leaf: {nodeHeight: number} position: number } const KeyframeSnapTarget: React.VFC = (props) => { return ( ) } export default KeyframeSnapTarget export type KeyframeSnapPositions = { [objectKey: ObjectAddressKey]: { [trackId: SequenceTrackId]: number[] } } const stateB = new Atom< | { // all keyframes must be snap targets mode: 'snapToAll' } | { // only these keyframes must be snap targets mode: 'snapToSome' positions: KeyframeSnapPositions } | { // no keyframe should be a snap target mode: 'snapToNone' } >({mode: 'snapToNone'}) export const snapPositionsStateD = stateB.prism export function snapToAll() { stateB.set({mode: 'snapToAll'}) } export function snapToNone() { stateB.set({mode: 'snapToNone'}) } export function snapToSome(positions: KeyframeSnapPositions) { stateB.set({mode: 'snapToSome', positions}) } export function collectKeyframeSnapPositions( tracksByObject: HistoricPositionalSequence['tracksByObject'], shouldIncludeKeyframe: ( kf: BasicKeyframe, track: { trackId: SequenceTrackId trackData: BasicKeyframedTrack objectKey: ObjectAddressKey }, ) => boolean, ): KeyframeSnapPositions { return Object.fromEntries( Object.entries(tracksByObject).map( ([objectKey, trackDataAndTrackIdByPropPath]) => [ objectKey, Object.fromEntries( Object.entries(trackDataAndTrackIdByPropPath!.trackData).map( ([trackId, track]) => [ trackId, keyframeUtils .getSortedKeyframesCached(track!.keyframes) .filter((kf) => shouldIncludeKeyframe(kf, { trackId, trackData: track!, objectKey, }), ) .map((keyframe) => keyframe.position), ], ), ), ], ), ) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthEditorPopover.tsx ================================================ import type {Pointer} from '@theatre/dataverse' import React, {useLayoutEffect, useMemo, useRef} from 'react' import styled from 'styled-components' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {usePrism, useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import type {BasicNumberInputNudgeFn} from '@theatre/studio/uiComponents/form/BasicNumberInput' import BasicNumberInput from '@theatre/studio/uiComponents/form/BasicNumberInput' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' const greaterThanZero = (v: number) => isFinite(v) && v > 0 const Container = styled.div` display: flex; gap: 8px; height: 28px; align-items: center; ` const Label = styled.div` ${propNameTextCSS}; white-space: nowrap; ` const nudge: BasicNumberInputNudgeFn = ({deltaX}) => deltaX * 0.25 const LengthEditorPopover: React.FC<{ layoutP: Pointer /** * Called when user hits enter/escape */ onRequestClose: (reason: string) => void }> = ({layoutP}) => { const sheet = useVal(layoutP.sheet) const fns = useMemo(() => { let tempTransaction: CommitOrDiscardOrRecapture | undefined return { temporarilySetValue(newLength: number): void { if (tempTransaction) { tempTransaction.discard() tempTransaction = undefined } tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.setLength({ ...sheet.address, length: newLength, }) }) }, discardTemporaryValue(): void { if (tempTransaction) { tempTransaction.discard() tempTransaction = undefined } }, permanentlySetValue(newLength: number): void { if (tempTransaction) { tempTransaction.discard() tempTransaction = undefined } getStudio()!.transaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.setLength({ ...sheet.address, length: newLength, }) }) }, } }, [layoutP, sheet]) const inputRef = useRef(null) useLayoutEffect(() => { inputRef.current!.focus() }, []) return usePrism(() => { const sequence = sheet.getSequence() const sequenceLength = sequence.length return ( ) }, [sheet, fns, inputRef]) } export default LengthEditorPopover ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthIndicator.tsx ================================================ import {usePrism} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React, {useMemo, useRef} from 'react' import styled from 'styled-components' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel' import {topStripHeight} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/TopStrip' import useRefAndState from '@theatre/studio/utils/useRefAndState' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import useDrag from '@theatre/studio/uiComponents/useDrag' import getStudio from '@theatre/studio/getStudio' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import { includeLockFrameStampAttrs, useLockFrameStampPosition, } from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {GoChevronLeft, GoChevronRight} from 'react-icons/go' import LengthEditorPopover from './LengthEditorPopover' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover' const coverWidth = 1000 const colors = { stripNormal: `#0000006c`, stripActive: `#000000`, } const Strip = styled.div` position: absolute; top: 0; left: 0; width: 4px; z-index: ${() => zIndexes.lengthIndicatorStrip}; pointer-events: none; &:after { display: block; content: ' '; position: absolute; /* top: ${topStripHeight}px; */ top: 0; bottom: 0; left: -1px; width: 1px; background-color: ${colors.stripNormal}; } &:hover:after, &.dragging:after { background-color: ${colors.stripActive}; } ` const ThumbContainer = styled.div` position: absolute; top: ${topStripHeight - 15}px; width: 100px; left: -50px; pointer-events: none; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 1; ` const Tooltip = styled.div` margin-top: 8px; font-size: 10px; white-space: nowrap; padding: 2px 8px; border-radius: 2px; ${pointerEventsAutoInNormalMode}; cursor: ew-resize; color: #464646; background-color: #0000004d; display: none; ${Strip}:hover &, ${Strip}.dragging & { display: block; color: white; background-color: ${colors.stripActive}; } ` const Tumb = styled.div` font-size: 10px; white-space: nowrap; padding: 1px 2px; border-radius: 2px; ${pointerEventsAutoInNormalMode}; justify-content: center; align-items: center; cursor: ew-resize; color: #5d5d5d; background-color: #191919; ${Strip}:hover &, ${Strip}.dragging & { color: white; background-color: ${colors.stripActive}; & > svg:first-child { margin-right: -1px; } } & > svg:first-child { margin-right: -4px; } ` const Cover = styled.div` position: absolute; top: 0; left: 0; background-color: rgb(23 23 23 / 43%); width: ${coverWidth}px; z-index: ${() => zIndexes.lengthIndicatorCover}; transform-origin: left top; ${Strip}.dragging ~ &, ${Strip}:hover ~ & { background-color: rgb(23 23 23 / 60%); } ` type IProps = { layoutP: Pointer } const RENDER_OUT_OF_VIEW_X = -10000 /** * This appears at the end of the sequence where you can adjust the length of the sequence. * Kinda looks like `< >` at the top bar at end of the sequence editor. */ const LengthIndicator: React.FC = ({layoutP}) => { const [nodeRef, node] = useRefAndState(null) const [isDragging] = useDragBulge(node, {layoutP}) const { node: popoverNode, toggle: togglePopover, close: closePopover, } = usePopover({debugName: 'LengthIndicator'}, () => { return ( ) }) return usePrism(() => { const sheet = val(layoutP.sheet) const height = val(layoutP.rightDims.height) const sequence = sheet.getSequence() const sequenceLength = sequence.length const startInUnitSpace = sequenceLength let startX = val(layoutP.clippedSpace.fromUnitSpace)(startInUnitSpace) let endX = val(layoutP.clippedSpace.width) let scaleX: number, translateX: number if (startX > endX) { translateX = 0 scaleX = 0 } else { if (startX < 0) { startX = 0 } translateX = startX scaleX = (endX - startX) / coverWidth } return ( <> {popoverNode} { togglePopover(e, node!) }} {...includeLockFrameStampAttrs('hide')} > Sequence length:{' '} {sequence.positionFormatter.formatBasic(sequenceLength)} ) }, [layoutP, nodeRef, isDragging, popoverNode]) } function useDragBulge( node: HTMLDivElement | null, props: IProps, ): [isDragging: boolean] { const propsRef = useRef(props) propsRef.current = props const gestureHandlers = useMemo[1]>(() => { return { debugName: 'LengthIndicator/useDragBulge', lockCSSCursorTo: 'ew-resize', onDragStart(event) { let tempTransaction: CommitOrDiscardOrRecapture | undefined const propsAtStartOfDrag = propsRef.current const sheet = val(propsRef.current.layoutP.sheet) const initialLength = sheet.getSequence().length const toUnitSpace = val( propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, ) return { onDrag(dx, dy, event) { const delta = toUnitSpace(dx) if (tempTransaction) { tempTransaction.discard() tempTransaction = undefined } tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.setLength( { ...sheet.address, length: initialLength + delta, }, ) }) }, onDragEnd(dragHappened) { if (dragHappened) { if (tempTransaction) { tempTransaction.commit() } } else { if (tempTransaction) { tempTransaction.discard() } } }, } }, } }, []) const [isDragging] = useDrag(node, gestureHandlers) useLockFrameStampPosition(isDragging, -1) return [isDragging] } export default LengthIndicator ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/PrimitivePropRow.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type {SequenceEditorTree_PrimitiveProp} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import getStudio from '@theatre/studio/getStudio' import {usePrism} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React from 'react' import RightRow from './Row' import BasicKeyframedTrack from './BasicKeyframedTrack/BasicKeyframedTrack' import {useLogger} from '@theatre/studio/uiComponents/useLogger' const PrimitivePropRow: React.VFC<{ leaf: SequenceEditorTree_PrimitiveProp layoutP: Pointer }> = ({leaf, layoutP}) => { const logger = useLogger('PrimitivePropRow', leaf.pathToProp.join()) return usePrism(() => { const {sheetObject} = leaf const {trackId} = leaf const trackData = val( getStudio()!.atomP.historic.coreByProject[sheetObject.address.projectId] .sheetsById[sheetObject.address.sheetId].sequence.tracksByObject[ sheetObject.address.objectKey ].trackData[trackId], ) if (trackData?.type !== 'BasicKeyframedTrack') { logger.errorDev( `trackData type ${trackData?.type} is not yet supported on the sequence editor`, ) return ( }> ) } else { const node = ( ) return } }, [leaf, layoutP]) } export default PrimitivePropRow ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/PropWithChildrenRow.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type { SequenceEditorTree_PrimitiveProp, SequenceEditorTree_PropWithChildren, } from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import {usePrism} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import React from 'react' import PrimitivePropRow from './PrimitivePropRow' import RightRow from './Row' import AggregatedKeyframeTrack from './AggregatedKeyframeTrack/AggregatedKeyframeTrack' import {collectAggregateKeyframesInPrism} from './collectAggregateKeyframes' import {ProvideLogger, useLogger} from '@theatre/studio/uiComponents/useLogger' export const decideRowByPropType = ( leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp, layoutP: Pointer, ): React.ReactElement => leaf.type === 'propWithChildren' ? ( ) : ( ) const RightPropWithChildrenRow: React.VFC<{ viewModel: SequenceEditorTree_PropWithChildren layoutP: Pointer }> = ({viewModel, layoutP}) => { const logger = useLogger( 'RightPropWithChildrenRow', viewModel.pathToProp.join(), ) return usePrism(() => { const aggregatedKeyframes = collectAggregateKeyframesInPrism(viewModel) const node = ( ) return ( {viewModel.children.map((propLeaf) => decideRowByPropType(propLeaf, layoutP), )} ) }, [viewModel, layoutP]) } export default RightPropWithChildrenRow ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/Right.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {usePrism} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import DopeSheetSelectionView from './DopeSheetSelectionView' import HorizontallyScrollableArea from './HorizontallyScrollableArea' import SheetRow from './SheetRow' export const contentWidth = 1000000 const ListContainer = styled.ul` margin: 0; padding: 0; list-style: none; position: absolute; left: 0; width: ${contentWidth}px; ` const Right: React.FC<{ layoutP: Pointer }> = ({layoutP}) => { return usePrism(() => { const tree = val(layoutP.tree) const height = val(layoutP.tree.top) + // stretch the height of the dope sheet in case the rows don't cover its whole vertical space Math.max( val(layoutP.tree.heightIncludingChildren), val(layoutP.dopeSheetDims.height), ) return ( <> ) }, [layoutP]) } export default Right ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/Row.tsx ================================================ import type {SequenceEditorTree_Row} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import React from 'react' import styled from 'styled-components' const RightRowContainer = styled.li<{}>` margin: 0; padding: 0; list-style: none; box-sizing: border-box; position: relative; ` const RightRowNodeWrapper = styled.div<{isEven: boolean}>` box-sizing: border-box; width: 100%; position: relative; &:before { position: absolute; display: block; content: ' '; left: -40px; top: 0; bottom: 0; right: 0; box-sizing: border-box; border-bottom: 1px solid #252b3869; background: ${(props) => (props.isEven ? 'transparent' : '#6b8fb505')}; } ` const RightRowChildren = styled.ul` margin: 0; padding: 0; list-style: none; ` /** * @remarks * Right now, we're rendering a hierarchical dom tree that reflects the hierarchy of * objects, compound props, and their subs. This is not necessary and makes styling complicated. * Instead of this, we can simply render a list. This should be easy to do, since the view model * in {@link calculateSequenceEditorTree} already includes all the vertical placement information * (height and top) we need to render the nodes as a list. * * Note that we don't need to change {@link calculateSequenceEditorTree} to be list-based. It can * retain its hierarchy. It's just the DOM tree that should be list-based. */ const RightRow: React.FC<{ leaf: SequenceEditorTree_Row node: React.ReactElement isCollapsed: boolean children?: React.ReactNode | undefined }> = ({leaf, children, node, isCollapsed}) => { const hasChildren = Array.isArray(children) && children.length > 0 return leaf.shouldRender ? ( {node} {hasChildren && {children}} ) : null } export default RightRow ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/SheetObjectRow.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type {SequenceEditorTree_SheetObject} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import {usePrism} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import React from 'react' import {decideRowByPropType} from './PropWithChildrenRow' import RightRow from './Row' import {collectAggregateKeyframesInPrism} from './collectAggregateKeyframes' import AggregatedKeyframeTrack from './AggregatedKeyframeTrack/AggregatedKeyframeTrack' const RightSheetObjectRow: React.VFC<{ leaf: SequenceEditorTree_SheetObject layoutP: Pointer }> = ({leaf, layoutP}) => { return usePrism(() => { const aggregatedKeyframes = collectAggregateKeyframesInPrism(leaf) const node = ( ) return ( {leaf.children.map((leaf) => decideRowByPropType(leaf, layoutP))} ) }, [leaf, layoutP]) } export default RightSheetObjectRow ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/SheetRow.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type {SequenceEditorTree_Sheet} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import {usePrism} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import React from 'react' import RightSheetObjectRow from './SheetObjectRow' import RightRow from './Row' import {collectAggregateKeyframesInPrism} from './collectAggregateKeyframes' import AggregatedKeyframeTrack from './AggregatedKeyframeTrack/AggregatedKeyframeTrack' const SheetRow: React.FC<{ leaf: SequenceEditorTree_Sheet layoutP: Pointer }> = ({leaf, layoutP}) => { return usePrism(() => { const aggregatedKeyframes = collectAggregateKeyframesInPrism(leaf) const node = ( ) return ( {leaf.children.map((sheetObjectLeaf) => ( ))} ) }, [leaf, layoutP]) } export default SheetRow ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes.tsx ================================================ import getStudio from '@theatre/studio/getStudio' import {val} from '@theatre/dataverse' import type { SequenceEditorTree_PrimitiveProp, SequenceEditorTree_PropWithChildren, SequenceEditorTree_Sheet, SequenceEditorTree_SheetObject, } from '@theatre/studio/panels/SequenceEditorPanel/layout/tree' import type {BasicKeyframe, SequenceTrackId} from '@theatre/core/types/public' import type {TrackData} from '@theatre/core/types/private/core' import {encodePathToProp} from '@theatre/utils/pathToProp' import {uniq} from 'lodash-es' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type {StudioSheetItemKey} from '@theatre/core/types/private' import {createStudioSheetItemKey} from '@theatre/studio/utils/createStudioSheetItemKey' import {__private} from '@theatre/core' const {keyframeUtils} = __private /** * An index over a series of keyframes that have been collected from different tracks. * * Usually constructed via {@link collectAggregateKeyframesInPrism}. */ export type AggregatedKeyframes = { byPosition: Map tracks: TrackWithId[] } export type TrackWithId = { id: SequenceTrackId data: TrackData sheetObject: SheetObject } export type KeyframeWithTrack = { kf: BasicKeyframe track: TrackWithId itemKey: StudioSheetItemKey } /** * Collect {@link AggregatedKeyframes} information from the given tree row with children. * * Must be called within a `prism` context. * * Implementation progress 2/10: * - This currently does a lot of duplicate work for each compound rows' compound rows. * - This appears to have O(N) complexity with N being the number of "things" in the * tree, thus we don't see an immediate need to cache it further. * - If concerned, consider making a playground with a lot of objects to test this kind of thing. * * Note that we do not need to filter to only tracks that should be displayed, because we * do not do anything counting or iterating over all tracks. * * Furthermore, we _could_ have been traversing the tree of the sheet and producing * an aggreagte from that, but _that_ aggregate would not take into account * things like filters in the `SequenceEditorPanel`, where the filter would exclude * certain objects and props from the tree. * */ export function collectAggregateKeyframesInPrism( leaf: | SequenceEditorTree_Sheet | SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject, ): AggregatedKeyframes { const tracks = leaf.type === 'sheet' ? collectAggregateKeyframesSheet(leaf) : collectAggregateKeyframesCompoundOrObject(leaf) return { byPosition: keyframesByPositionFromTrackWithIds(tracks), tracks, } } function keyframesByPositionFromTrackWithIds(tracks: TrackWithId[]) { const byPosition = new Map() for (const track of tracks) { const keyframes = keyframeUtils.getSortedKeyframesCached( track.data.keyframes, ) for (const kf of keyframes) { let existing = byPosition.get(kf.position) if (!existing) { existing = [] byPosition.set(kf.position, existing) } existing.push({ kf, track, itemKey: createStudioSheetItemKey.forTrackKeyframe( track.sheetObject, track.id, kf.id, ), }) } } return byPosition } function collectAggregateKeyframesSheet( leaf: SequenceEditorTree_Sheet, ): TrackWithId[] { return leaf.children.flatMap(collectAggregateKeyframesCompoundOrObject) } function collectAggregateKeyframesCompoundOrObject( leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject, ): TrackWithId[] { return leaf.children.flatMap((childLeaf) => childLeaf.type === 'propWithChildren' ? collectAggregateKeyframesCompoundOrObject(childLeaf) : collectAggregateKeyframesPrimitiveProp(childLeaf), ) } function collectAggregateKeyframesPrimitiveProp( leaf: SequenceEditorTree_PrimitiveProp, ): TrackWithId[] { const sheetObject = leaf.sheetObject const projectId = sheetObject.address.projectId const sheetObjectTracksP = getStudio().atomP.historic.coreByProject[projectId].sheetsById[ sheetObject.address.sheetId ].sequence.tracksByObject[sheetObject.address.objectKey] const trackId = val( sheetObjectTracksP.trackIdByPropPath[encodePathToProp(leaf.pathToProp)], ) if (!trackId) return [] const trackData = val(sheetObjectTracksP.trackData[trackId]) if (!trackData) return [] return [{id: trackId, data: trackData, sheetObject}] } /** * Collects all the snap positions for an aggregate track. */ export function collectAggregateSnapPositionsSheet( leaf: SequenceEditorTree_Sheet, snapTargetPositions: {[key: string]: {[key: string]: number[]}}, ): number[] { return uniq( leaf.children.flatMap((childLeaf) => collectAggregateSnapPositionsObjectOrCompound( childLeaf, snapTargetPositions, ), ), ) } export function collectAggregateSnapPositionsObjectOrCompound( leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject, snapTargetPositions: {[key: string]: {[key: string]: number[]}}, ): number[] { return uniq( leaf.children.flatMap((childLeaf) => childLeaf.type === 'propWithChildren' ? collectAggregateSnapPositionsObjectOrCompound( childLeaf, snapTargetPositions, ) : collectAggregateSnapPositionsPrimitiveProp( childLeaf, snapTargetPositions, ), ), ) } function collectAggregateSnapPositionsPrimitiveProp( leaf: SequenceEditorTree_PrimitiveProp, snapTargetPositions: {[key: string]: {[key: string]: number[]}}, ): number[] { const sheetObject = leaf.sheetObject const projectId = sheetObject.address.projectId const sheetObjectTracksP = getStudio().atomP.historic.coreByProject[projectId].sheetsById[ sheetObject.address.sheetId ].sequence.tracksByObject[sheetObject.address.objectKey] const trackId = val( sheetObjectTracksP.trackIdByPropPath[encodePathToProp(leaf.pathToProp)], ) if (!trackId) return [] return snapTargetPositions[sheetObject.address.objectKey]?.[trackId] ?? [] } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/keyframeRowUI/ConnectorLine.tsx ================================================ import {lighten, saturate} from 'polished' import React from 'react' import styled from 'styled-components' import {DOT_SIZE_PX} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/SingleKeyframeDot' const CONNECTOR_HEIGHT = DOT_SIZE_PX / 2 + 1 const CONNECTOR_WIDTH_UNSCALED = 1000 export type IConnectorThemeValues = { isPopoverOpen: boolean isSelected: boolean } export const CONNECTOR_THEME = { normalColor: `#365b59`, // (greenish-blueish)ish selectedColor: `#8A7842`, barColor: (values: IConnectorThemeValues) => { const base = values.isSelected ? CONNECTOR_THEME.selectedColor : CONNECTOR_THEME.normalColor return values.isPopoverOpen ? saturate(0.2, lighten(0.2, base)) : base }, hoverColor: (values: IConnectorThemeValues) => { const base = values.isSelected ? CONNECTOR_THEME.selectedColor : CONNECTOR_THEME.normalColor return values.isPopoverOpen ? saturate(0.2, lighten(0.2, base)) : saturate(0.1, lighten(0.1, base)) }, } const Container = styled.div` position: absolute; background: ${CONNECTOR_THEME.barColor}; height: ${CONNECTOR_HEIGHT}px; width: ${CONNECTOR_WIDTH_UNSCALED}px; left: 0; top: -${CONNECTOR_HEIGHT / 2}px; transform-origin: top left; z-index: 0; cursor: ew-resize; &:after { display: block; position: absolute; content: ' '; top: -4px; bottom: -4px; left: 0; right: 0; } &:hover { background: ${CONNECTOR_THEME.hoverColor}; } ` type IConnectorLineProps = React.PropsWithChildren<{ isPopoverOpen: boolean openPopover?: (event: React.MouseEvent) => void isSelected: boolean connectorLengthInUnitSpace: number }> export const ConnectorLine = React.forwardRef< HTMLDivElement, IConnectorLineProps >((props, ref) => { const themeValues: IConnectorThemeValues = { isPopoverOpen: props.isPopoverOpen, isSelected: props.isSelected, } return ( { props.openPopover?.(e) }} > {props.children} ) }) ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/selections.ts ================================================ import type {Prism} from '@theatre/dataverse' import {prism, val} from '@theatre/dataverse' import type { KeyframeId, ObjectAddressKey, ProjectId, SequenceTrackId, SheetId, BasicKeyframe, } from '@theatre/core/types/public' import getStudio from '@theatre/studio/getStudio' import type {DopeSheetSelection} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import { commonRootOfPathsToProps, decodePathToProp, } from '@theatre/utils/pathToProp' import type {StrictRecord} from '@theatre/core/types/public' import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/core/types/private' import {__private} from '@theatre/core' const {keyframeUtils} = __private /** * Keyframe connections are considered to be selected if the first * keyframe in the connection is selected */ export function isKeyframeConnectionInSelection( keyframeConnection: {left: BasicKeyframe; right: BasicKeyframe}, selection: DopeSheetSelection, ): boolean { for (const {keyframeId} of flatSelectionKeyframeIds(selection)) { if (keyframeConnection.left.id === keyframeId) return true } return false } export type KeyframeConnectionWithAddress = { projectId: ProjectId sheetId: SheetId objectKey: ObjectAddressKey trackId: SequenceTrackId left: BasicKeyframe right: BasicKeyframe } /** * Returns an array of all the selected keyframes * that are connected to one another. Useful for changing * the tweening in between keyframes. * * TODO - rename to selectedKeyframeConnectionsD(), or better yet, * make it a `prism.ensurePrism()` function, rather than returning * a prism. */ export function selectedKeyframeConnections( projectId: ProjectId, sheetId: SheetId, selection: DopeSheetSelection | undefined, ): Prism> { return prism(() => { if (selection === undefined) return [] let ckfs: Array = [] for (const {objectKey, trackId} of flatSelectionTrackIds(selection)) { const track = val( getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId] .sequence.tracksByObject[objectKey].trackData[trackId], ) if (track) { ckfs = ckfs.concat( keyframeConnections( keyframeUtils.getSortedKeyframesCached(track.keyframes), ) .filter((kfc) => isKeyframeConnectionInSelection(kfc, selection)) .map(({left, right}) => ({ left, right, trackId, objectKey, sheetId, projectId, })), ) } } return ckfs }) } /** * Given a selection, returns a list of keyframes and paths * that are relative to a common root path. For example, if * the selection contains a keyframe on both the following tracks: * - exObject.transform.position.x * - exObject.transform.position.y * then the result will be * ``` * [{ keyframe, pathToProp: ['x']}, { keyframe, pathToProp: ['y']}] * ``` * * If the selection contains a keyframe on * all the following tracks: * - exObject.transform.position.x * - exObject.transform.position.y * - exObject.transform.scale.x * then the result will be * ``` * [ * {keyframe, pathToProp: ['position', 'x']}, * {keyframe, pathToProp: ['position', 'y']}, * {keyframe, pathToProp: ['scale', 'x']}, * ] * ``` */ export function copyableKeyframesFromSelection( projectId: ProjectId, sheetId: SheetId, selection: DopeSheetSelection | undefined, ): KeyframeWithPathToPropFromCommonRoot[] { if (selection === undefined) return [] let kfs: KeyframeWithPathToPropFromCommonRoot[] = [] for (const {objectKey, trackId, keyframeIds} of flatSelectionTrackIds( selection, )) { kfs = kfs.concat( keyframesWithPaths({ projectId, sheetId, objectKey, trackId, keyframeIds, }) ?? [], ) } const commonPath = commonRootOfPathsToProps(kfs.map((kf) => kf.pathToProp)) const keyframesWithCommonRootPath = kfs.map(({keyframe, pathToProp}) => ({ keyframe, pathToProp: pathToProp.slice(commonPath.length), })) return keyframesWithCommonRootPath } /** * @see copyableKeyframesFromSelection */ export function keyframesWithPaths({ projectId, sheetId, objectKey, trackId, keyframeIds, }: { projectId: ProjectId sheetId: SheetId objectKey: ObjectAddressKey trackId: SequenceTrackId keyframeIds: KeyframeId[] }): KeyframeWithPathToPropFromCommonRoot[] | null { const tracksByObject = val( getStudio().atomP.historic.coreByProject[projectId].sheetsById[sheetId] .sequence.tracksByObject[objectKey], ) const track = tracksByObject?.trackData[trackId] if (!track) return null const propPathByTrackId = swapKeyAndValue( tracksByObject?.trackIdByPropPath || {}, ) const encodedPropPath = propPathByTrackId[trackId] if (!encodedPropPath) return null const pathToProp = [objectKey, ...decodePathToProp(encodedPropPath)] return keyframeIds .map((keyframeId) => ({ keyframe: keyframeUtils .getSortedKeyframesCached(track.keyframes) .find((keyframe) => keyframe.id === keyframeId), pathToProp, })) .filter( ({keyframe}) => keyframe !== undefined, ) as KeyframeWithPathToPropFromCommonRoot[] } function swapKeyAndValue( obj: StrictRecord, ): StrictRecord { const result: StrictRecord = {} for (const [key, value] of Object.entries(obj)) { result[value as V] = key } return result } export function keyframeConnections( keyframes: Array, ): Array<{left: BasicKeyframe; right: BasicKeyframe}> { return keyframes .map((kf, i) => ({left: kf, right: keyframes[i + 1]})) .slice(0, -1) // remmove the last entry because it is { left: kf, right: undefined } } export function flatSelectionKeyframeIds(selection: DopeSheetSelection): Array<{ objectKey: ObjectAddressKey trackId: SequenceTrackId keyframeId: KeyframeId }> { const result = [] for (const [objectKey, maybeObjectRecord] of Object.entries( selection?.byObjectKey ?? {}, )) { for (const [trackId, maybeTrackRecord] of Object.entries( maybeObjectRecord?.byTrackId ?? {}, )) { for (const keyframeId of Object.keys( maybeTrackRecord?.byKeyframeId ?? {}, )) { result.push({objectKey, trackId, keyframeId}) } } } return result } export function flatSelectionTrackIds(selection: DopeSheetSelection): Array<{ objectKey: ObjectAddressKey trackId: SequenceTrackId keyframeIds: Array }> { const result = [] for (const [objectKey, maybeObjectRecord] of Object.entries( selection?.byObjectKey ?? {}, )) { for (const [trackId, maybeTrackRecord] of Object.entries( maybeObjectRecord?.byTrackId ?? {}, )) { result.push({ objectKey, trackId, keyframeIds: Object.keys(maybeTrackRecord?.byKeyframeId ?? {}), }) } } return result } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/DopeSheet/setCollapsedSheetObjectOrCompoundProp.tsx ================================================ import getStudio from '@theatre/studio/getStudio' import type { SheetAddress, WithoutSheetInstance, } from '@theatre/core/types/public' import type {StudioSheetItemKey} from '@theatre/core/types/private' export function setCollapsedSheetItem( isCollapsed: boolean, toCollapse: { sheetAddress: WithoutSheetInstance sheetItemKey: StudioSheetItemKey }, ) { getStudio().transaction(({stateEditors}) => { stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.sequenceEditorCollapsableItems.set( { ...toCollapse.sheetAddress, studioSheetItemKey: toCollapse.sheetItemKey, isCollapsed, }, ) }) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/FrameGrid/FrameGrid.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type {$FixMe} from '@theatre/core/types/public' import type {Pointer} from '@theatre/dataverse' import {prism, val} from '@theatre/dataverse' import React, {useLayoutEffect, useMemo, useRef, useState} from 'react' import styled from 'styled-components' import createGrid from './createGrid' import getStudio from '@theatre/studio/getStudio' const Container = styled.div` position: absolute; top: 0; left: 0; height: 100%; pointer-events: none; ` const TheCanvas = styled.canvas` position: relative; left: 0; ` /** * from https://github.com/jonobr1/two.js/blob/758672c280278da2980b57e42ecb96eab4fe7a95/src/utils/get-ratio.js#L20 */ const getBackingStoreRatio = (ctx: CanvasRenderingContext2D): number => { const _ctx = ctx as $FixMe return ( _ctx.webkitBackingStorePixelRatio || _ctx.mozBackingStorePixelRatio || _ctx.msBackingStorePixelRatio || _ctx.oBackingStorePixelRatio || _ctx.backingStorePixelRatio || 1 ) } const getDevicePixelRatio = () => window.devicePixelRatio || 1 const getRatio = (ctx: CanvasRenderingContext2D) => { return getDevicePixelRatio() / getBackingStoreRatio(ctx) } const FrameGrid: React.FC<{ layoutP: Pointer width: number height: number }> = ({layoutP, width, height}) => { const containerRef = useRef(null) const [canvas, canvasRef] = useState(null) const {ctx, ratio} = useMemo(() => { if (!canvas) return {} const ctx = canvas.getContext('2d')! const ratio = getRatio(ctx) return {ctx, ratio} }, [canvas]) useLayoutEffect(() => { if (!ctx) return canvas!.width = width * ratio! canvas!.height = height * ratio! const untap = prism(() => { const sequence = val(layoutP.sheet).getSequence() return { ctx, clippedSpaceRange: val(layoutP.clippedSpace.range), clippedSpaceWidth: val(layoutP.clippedSpace.width), unitSpaceToClippedSpace: val(layoutP.clippedSpace.fromUnitSpace), height, leftPadding: val(layoutP.scaledSpace.leftPadding), fps: sequence.subUnitsPerUnit, snapToGrid: (n: number) => sequence.closestGridPosition(n), } }).onChange( getStudio().ticker, (p) => { ctx.save() ctx.scale(ratio!, ratio!) drawGrid(p) ctx.restore() }, true, ) return () => { untap() } }, [ctx, width, height, layoutP]) return ( ) } export default FrameGrid function drawGrid( opts: { clippedSpaceWidth: number height: number ctx: CanvasRenderingContext2D leftPadding: number unitSpaceToClippedSpace: SequenceEditorPanelLayout['clippedSpace']['fromUnitSpace'] snapToGrid: (posInUnitSpace: number) => number } & Parameters[0], ) { const {clippedSpaceWidth, height, ctx, unitSpaceToClippedSpace, snapToGrid} = opts ctx.clearRect(0, 0, clippedSpaceWidth, height) createGrid(opts, (_posInUnitSpace, isFullSecond) => { const posInUnitSpace = snapToGrid(_posInUnitSpace) const posInClippedSpace = Math.floor( unitSpaceToClippedSpace(posInUnitSpace), ) ctx.strokeStyle = isFullSecond ? 'rgba(225, 225, 225, 0.04)' : 'rgba(255, 255, 255, 0.01)' ctx.beginPath() ctx.moveTo(posInClippedSpace, 0) ctx.lineTo(posInClippedSpace, height) ctx.stroke() ctx.closePath() }) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/FrameGrid/StampsGrid.tsx ================================================ import type {ISequencePositionFormatter} from '@theatre/core/sequences/Sequence' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type {Pointer} from '@theatre/dataverse' import {prism, val} from '@theatre/dataverse' import {darken} from 'polished' import React, {useLayoutEffect, useRef, useState} from 'react' import styled from 'styled-components' import createGrid from './createGrid' import getStudio from '@theatre/studio/getStudio' const Container = styled.div` position: absolute; top: 0; left: 0; height: 100%; pointer-events: none; ` export const stampsGridTheme = { fullUnitStampColor: `#6a6a6a`, stampFontSize: '10px', get subUnitStampColor(): string { return darken(0.2, stampsGridTheme.fullUnitStampColor) }, } const TheStamps = styled.div` position: absolute; top: 0; height: 100%; left: 0; overflow: hidden; z-index: 2; will-change: transform; pointer-events: none; ` const FullSecondStampsContainer = styled.div` position: absolute; top: 0; left: 0; & > span { position: absolute; display: block; top: 9px; left: -10px; color: ${stampsGridTheme.fullUnitStampColor}; text-align: center; font-size: ${stampsGridTheme.stampFontSize}; width: 20px; &.full-unit { color: ${stampsGridTheme.fullUnitStampColor}; } &.sub-unit { color: ${stampsGridTheme.subUnitStampColor}; } } pointer-events: none; ` const StampsGrid: React.FC<{ layoutP: Pointer width: number height: number }> = ({layoutP, width}) => { const containerRef = useRef(null) const [fullSecondStampsContainer, fullSecondStampsContainerRef] = useState(null) useLayoutEffect(() => { if (!fullSecondStampsContainer) return return prism(() => { const sequence = val(layoutP.sheet).getSequence() return { fullSecondStampsContainer, clippedSpaceRange: val(layoutP.clippedSpace.range), clippedSpaceWidth: val(layoutP.clippedSpace.width), unitSpaceToClippedSpace: val(layoutP.clippedSpace.fromUnitSpace), leftPadding: val(layoutP.scaledSpace.leftPadding), fps: sequence.subUnitsPerUnit, sequencePositionFormatter: sequence.positionFormatter, snapToGrid: (n: number) => sequence.closestGridPosition(n), } }).onChange(getStudio().ticker, drawStamps, true) }, [fullSecondStampsContainer, width, layoutP]) return ( ) } export default StampsGrid function drawStamps( opts: { fullSecondStampsContainer: HTMLDivElement sequencePositionFormatter: ISequencePositionFormatter snapToGrid: (posInUnitSpace: number) => number unitSpaceToClippedSpace: SequenceEditorPanelLayout['clippedSpace']['fromUnitSpace'] } & Parameters[0], ) { const { fullSecondStampsContainer, sequencePositionFormatter, snapToGrid, unitSpaceToClippedSpace, } = opts let innerHTML = '' createGrid(opts, (_posInUnitSpace, isFullUnit) => { const posInUnitSpace = snapToGrid(_posInUnitSpace) const posInClippedSpace = unitSpaceToClippedSpace(posInUnitSpace) if (isFullUnit) { innerHTML += createStampClass( sequencePositionFormatter.formatFullUnitForGrid(posInUnitSpace), posInClippedSpace, 'full-unit', ) } else { innerHTML += createStampClass( sequencePositionFormatter.formatSubUnitForGrid(posInUnitSpace), posInClippedSpace, 'sub-unit', ) } }) fullSecondStampsContainer.innerHTML = innerHTML } function createStampClass( pos: string, x: number, type: 'full-unit' | 'sub-unit', ) { return `${pos}` } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/FrameGrid/createGrid.ts ================================================ import type {IRange} from '@theatre/core/types/public' import {memoize} from 'lodash-es' const getFactorsOfNumber = memoize((divisionsPerUnit: number): number[] => { const factors = [] for (let i = 1; i <= divisionsPerUnit; i++) { if (divisionsPerUnit % i === 0) { factors.push(i) } } return factors }) /** * Calls cb() for every grid line that must be drawn. * * @remarks * For the sake of simplicity, I've named the variables as if the sequence's * length is counted in seconds, and the sub-unit is called fps * (frames per second). But the algorithm should work for any fps rate, and also * non-time-based sequences. */ export default function createGrid( { clippedSpaceRange, clippedSpaceWidth, fps, gapWidth = 120, }: { /** * the width of the canvas, in pixels */ clippedSpaceWidth: number clippedSpaceRange: IRange fps: number /** * the minimum amount of space between two grid lines */ gapWidth?: number }, cb: (posInUnitSpace: number, isFullUnit: boolean) => void, ): void { // If fps is 60, then frameLengthInSeconeds would be 1/60 => 0.033 const frameLengthInSeconeds = 1 / fps // how much of the timeline is visible. const clippedSpaceLengthInSeconds = clippedSpaceRange[1] - clippedSpaceRange[0] // eg: if start: 1 AND end: 3 THEN length = 2 // how many pixels of space does one frame take const frameWidthInScreenSpace = clippedSpaceWidth / (fps * clippedSpaceLengthInSeconds) // Number of frames that fit in the smallest cell possible. // a cell is basically the space between two grid lines const numberOfFramesFittingInMinimumCellWidth = Math.floor( gapWidth / frameWidthInScreenSpace, ) // Number of frames in each cell, so that lines would be drawn at full seconds const numberOfFramesPerCell = // if we can't fit a full 60 frames in a cell (or a multiple of 60 frames), numberOfFramesFittingInMinimumCellWidth < fps ? (getFactorsOfNumber(fps).find( // then try fitting 30 frames, or 20, or 15, and other factors of 60 (factor) => factor >= numberOfFramesFittingInMinimumCellWidth, ) as number) : // otherwise, determine how many full seconds we can fit in a cell fps * Math.floor(numberOfFramesFittingInMinimumCellWidth / fps) const cellLengthInSeconds = numberOfFramesPerCell * frameLengthInSeconeds // the number of the first cell we'll draw const startCell = Math.floor(clippedSpaceRange[0] / cellLengthInSeconds) // and the last one const endCell = Math.ceil(clippedSpaceRange[1] / cellLengthInSeconds) for (let cell = startCell; cell <= endCell; cell++) { const posInUnitSpace = cell * cellLengthInSeconds const isFullSecond = posInUnitSpace % 1 === 0 cb(posInUnitSpace, isFullSecond) } } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/FrameStampPositionProvider.tsx ================================================ import type {Prism, Pointer} from '@theatre/dataverse' import {Atom, prism, val} from '@theatre/dataverse' import mousePositionD from '@theatre/studio/utils/mousePositionD' import type {$IntentionalAny} from '@theatre/core/types/public' import {inRange, last} from 'lodash-es' import React, { createContext, useCallback, useContext, useLayoutEffect, useMemo, useRef, } from 'react' import type {SequenceEditorPanelLayout} from './layout/layout' type FrameStampPositionLock = { unlock: () => void set: (pointerPositonInUnitSpace: number) => void } export enum FrameStampPositionType { hidden, locked, snapped, free, } const context = createContext<{ currentD: Prism<[pos: number, posType: FrameStampPositionType]> getLock(): FrameStampPositionLock }>(null as $IntentionalAny) type LockItem = { position: [ pos: number, posType: FrameStampPositionType.locked | FrameStampPositionType.hidden, ] id: number } let lastLockId = 0 /** * Provides snapping positions to "stamps". * * One example of a stamp includes the "Keyframe Dot" which show a `⌜⌞⌝⌟` kinda UI * around the dot when dragged over. */ const FrameStampPositionProvider: React.FC<{ layoutP: Pointer children: React.ReactNode }> = ({children, layoutP}) => { const locksAtom = useMemo(() => new Atom([]), []) const currentD = useMemo( () => prism(() => { const pointerPos = prism .memo('p', () => pointerPositionInUnitSpace(layoutP), [layoutP]) .getValue() const locks = val(locksAtom.pointer) if (locks.length > 0) { return last(locks)!.position } else { return pointerPos } }), [layoutP], ) const getLock = useCallback(() => { const id = lastLockId++ locksAtom.reduce((list) => [ ...list, { id, position: [-1, FrameStampPositionType.hidden], }, ]) const unlock = () => { locksAtom.reduce((list) => list.filter((lock) => lock.id !== id)) } const set = (posInUnitSpace: number) => { locksAtom.reduce((list) => { const index = list.findIndex((lock) => lock.id === id) if (index === -1) { console.warn(`Lock is already freed. This is a bug.`) return list } const newList = [...list] newList.splice(index, 1, { id, position: [ posInUnitSpace, posInUnitSpace === -1 ? FrameStampPositionType.hidden : FrameStampPositionType.locked, ], }) return newList }) } return { set, unlock, } }, []) const value = { currentD, getLock, } return {children} } export const useFrameStampPositionD = () => useContext(context).currentD /** Version of {@link useLockFrameStampPosition} which allows you to directly set status of a lock. */ export const useLockFrameStampPositionRef = () => { const {getLock} = useContext(context) const lockRef = useRef>() useLayoutEffect(() => { return () => { lockRef.current?.unlock() } }, []) return useMemo(() => { let prevLock: {shouldLock: boolean; pos: number} | undefined = undefined return (shouldLock: boolean, posValue: number) => { // Do if shouldLock changed if (prevLock?.shouldLock !== shouldLock) { if (shouldLock) { lockRef.current = getLock() } else { lockRef.current?.unlock() } } // Do if position changed if (prevLock?.pos !== posValue) { if (shouldLock) { lockRef.current?.set(posValue) } } // Set arguments we are going to diff against next time prevLock = {shouldLock, pos: posValue} } }, [getLock]) } export const useLockFrameStampPosition = (shouldLock: boolean, val: number) => { const {getLock} = useContext(context) const lockRef = useRef>() useLayoutEffect(() => { if (!shouldLock) return lockRef.current = getLock() return () => { lockRef.current!.unlock() } }, [shouldLock, getLock]) useLayoutEffect(() => { if (shouldLock) { lockRef.current!.set(val) } }, [val, shouldLock]) } /** * This attribute is used so that when the cursor hovers over a keyframe, * the framestamp snaps to the position of that keyframe. * * Use as a spread in a React element. * * @example * ```tsx *
* ``` * * @remarks * Elements that need this behavior must set a data attribute like so: *
* Setting this attribute to "hide" hides the stamp. * * @see lockedCursorCssVarName - CSS variable used to set the cursor on an element that * should lock the framestamp. Look for usages. * @see pointerEventsAutoInNormalMode - CSS snippet used to correctly set * `pointer-events` on an element that should lock the framestamp. * * See {@link FrameStampPositionProvider} * */ export const includeLockFrameStampAttrs = (value: number | 'hide') => ({ [ATTR_LOCK_FRAMESTAMP]: value === 'hide' ? value : value.toFixed(3), }) const ATTR_LOCK_FRAMESTAMP = 'data-theatre-lock-framestamp-to' const pointerPositionInUnitSpace = ( layoutP: Pointer, ): Prism<[pos: number, posType: FrameStampPositionType]> => { return prism(() => { const rightDims = val(layoutP.rightDims) const clippedSpaceToUnitSpace = val(layoutP.clippedSpace.toUnitSpace) const mousePos = val(mousePositionD) if (!mousePos) return [-1, FrameStampPositionType.hidden] for (const el of mousePos.composedPath()) { if (!(el instanceof HTMLElement || el instanceof SVGElement)) break if (el.hasAttribute(ATTR_LOCK_FRAMESTAMP)) { const val = el.getAttribute(ATTR_LOCK_FRAMESTAMP) if (typeof val !== 'string') continue if (val === 'hide') return [-1, FrameStampPositionType.hidden] const double = parseFloat(val) if (isFinite(double) && double >= 0) return [double, FrameStampPositionType.snapped] } } const {clientX, clientY} = mousePos const {screenX: x, screenY: y, width: rightWidth, height} = rightDims if ( inRange(clientX, x, x + rightWidth) && inRange( clientY, y + 16 /* leaving a bit of space for the top stip here */, y + height, ) ) { const posInRightDims = clientX - x const posInUnitSpace = clippedSpaceToUnitSpace(posInRightDims) return [posInUnitSpace, FrameStampPositionType.free] } else { return [-1, FrameStampPositionType.hidden] } }) } export default FrameStampPositionProvider ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack.tsx ================================================ import type {TrackData} from '@theatre/core/types/private/core' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type {PathToProp} from '@theatre/utils/pathToProp' import {createStudioSheetItemKey} from '@theatre/studio/utils/createStudioSheetItemKey' import type { $IntentionalAny, BasicKeyframe, VoidFn, } from '@theatre/core/types/public' import type {Pointer} from '@theatre/dataverse' import React, {useMemo, useRef, useState} from 'react' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import KeyframeEditor from './KeyframeEditor/KeyframeEditor' import {__private} from '@theatre/core' import type { PropTypeConfig_AllSimples, SequenceTrackId, } from '@theatre/core/types/public' import {useVal} from '@theatre/react' import type {GraphEditorColors} from '@theatre/core/types/private' import {graphEditorColors} from '@theatre/sync-server/state/schema' const {getPropConfigByPath, isPropConfigComposite, valueInProp} = __private.propTypeUtils const {keyframeUtils} = __private export type ExtremumSpace = { fromValueSpace: (v: number) => number toValueSpace: (v: number) => number deltaToValueSpace: (v: number) => number lock(): VoidFn } const BasicKeyframedTrack: React.VFC<{ layoutP: Pointer sheetObject: SheetObject pathToProp: PathToProp trackId: SequenceTrackId trackData: TrackData color: keyof GraphEditorColors }> = React.memo( ({layoutP, trackData, sheetObject, trackId, color, pathToProp}) => { const propConfig = getPropConfigByPath( useVal(sheetObject.template.configPointer), pathToProp, )! as PropTypeConfig_AllSimples if (isPropConfigComposite(propConfig)) { console.error(`Composite prop types cannot be keyframed`) return <> } const [areExtremumsLocked, setAreExtremumsLocked] = useState(false) const lockExtremums = useMemo(() => { const locks = new Set() return function lockExtremums() { const shouldLock = locks.size === 0 locks.add(unlock) if (shouldLock) setAreExtremumsLocked(true) function unlock() { const wasLocked = locks.size > 0 locks.delete(unlock) if (wasLocked && locks.size === 0) setAreExtremumsLocked(false) } return unlock } }, []) const extremumSpace: ExtremumSpace = useMemo(() => { const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( trackData.keyframes, ) const extremums = propConfig.type === 'number' ? calculateScalarExtremums(sortedKeyframes, propConfig) : calculateNonScalarExtremums(sortedKeyframes) const fromValueSpace = (val: number): number => (val - extremums[0]) / (extremums[1] - extremums[0]) const toValueSpace = (ex: number): number => extremums[0] + deltaToValueSpace(ex) const deltaToValueSpace = (ex: number): number => ex * (extremums[1] - extremums[0]) return { fromValueSpace, toValueSpace, deltaToValueSpace, lock: lockExtremums, } }, [trackData.keyframes]) const cachedExtremumSpace = useRef( undefined as $IntentionalAny, ) if (!areExtremumsLocked) { cachedExtremumSpace.current = extremumSpace } const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( trackData.keyframes, ) const keyframeEditors = sortedKeyframes.map((kf, index) => ( )) const iconColor = graphEditorColors[color].iconColor return ( {keyframeEditors} ) }, ) export default BasicKeyframedTrack type Extremums = [min: number, max: number] function calculateScalarExtremums( keyframes: BasicKeyframe[], propConfig: PropTypeConfig_AllSimples, ): Extremums { let min = Infinity, max = -Infinity function check(n: number): void { min = Math.min(n, min) max = Math.max(n, max) } keyframes.forEach((cur, i) => { const curVal = valueInProp(cur.value, propConfig) as number check(curVal) if (!cur.connectedRight) return const next = keyframes[i + 1] if (!next) return const diff = (typeof next.value === 'number' ? next.value : 1) - curVal check(curVal + cur.handles[3] * diff) check(curVal + next.handles[1] * diff) }) return [min, max] } function calculateNonScalarExtremums(keyframes: BasicKeyframe[]): Extremums { let min = 0, max = 1 function check(n: number): void { min = Math.min(n, min) max = Math.max(n, max) } keyframes.forEach((cur, i) => { if (!cur.connectedRight) return const next = keyframes[i + 1] if (!next) return check(cur.handles[3]) check(next.handles[1]) }) return [min, max] } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/Curve.tsx ================================================ import {__private} from '@theatre/core' import getStudio from '@theatre/studio/getStudio' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useRefAndState from '@theatre/studio/utils/useRefAndState' import React from 'react' import styled from 'styled-components' import type KeyframeEditor from './KeyframeEditor' const {valueInProp} = __private.propTypeUtils const {keyframeUtils} = __private const SVGPath = styled.path` stroke-width: 2; stroke: var(--main-color); fill: none; vector-effect: non-scaling-stroke; ` type IProps = Parameters[0] // for keyframe.type === 'hold' const pathForHoldType = `M 0 0 L 1 0 L 1 1` const Curve: React.VFC = (props) => { const {index, trackData} = props const cur = keyframeUtils.getSortedKeyframesCached(trackData.keyframes)[index] const next = keyframeUtils.getSortedKeyframesCached(trackData.keyframes)[ index + 1 ] const connectorLengthInUnitSpace = next.position - cur.position const [nodeRef, node] = useRefAndState(null) const [contextMenu] = useConnectorContextMenu(node, props) const curValue = props.isScalar ? (valueInProp(cur.value, props.propConfig) as number) : 0 const nextValue = props.isScalar ? (valueInProp(next.value, props.propConfig) as number) : 1 const leftYInExtremumSpace = props.extremumSpace.fromValueSpace(curValue) const rightYInExtremumSpace = props.extremumSpace.fromValueSpace(nextValue) const heightInExtremumSpace = rightYInExtremumSpace - leftYInExtremumSpace const transform = transformBox( cur.position, leftYInExtremumSpace, connectorLengthInUnitSpace, heightInExtremumSpace, ) const x1 = cur.handles[2] const y1 = cur.handles[3] const x2 = next.handles[0] const y2 = next.handles[1] const pathD = `M 0 0 C ${x1} ${y1} ${x2} ${y2} 1 1` return ( <> {contextMenu} ) } /** * Assuming a box such that: `{x: 0, y: 0, width: 1px, height: 1px}` * and given the desired coordinates of: * `{x: xInUnitSpace, y: yInExtremumSpace, width: widthInUnitSpace, height: heightInExtremumSpace}`, * `transformBox()` returns a CSS transform that transforms the box into its right dimensions * in the GraphEditor space. */ export function transformBox( xInUnitSpace: number, yInExtremumSpace: number, widthInUnitSpace: number, heightInExtremumSpace: number, ): string { const translateX = `calc(var(--unitSpaceToScaledSpaceMultiplier) * ${xInUnitSpace}px)` const translateY = `calc((var(--graphEditorVerticalSpace) - var(--graphEditorVerticalSpace) * ${yInExtremumSpace}) * 1px)` if (widthInUnitSpace === 0) { widthInUnitSpace = 0.0001 } const scaleX = `calc(var(--unitSpaceToScaledSpaceMultiplier) * ${widthInUnitSpace})` if (heightInExtremumSpace === 0) { heightInExtremumSpace = 0.001 } const scaleY = `calc(var(--graphEditorVerticalSpace) * ${ heightInExtremumSpace * -1 })` return `translate(${translateX}, ${translateY}) scale(${scaleX}, ${scaleY})` } export default Curve function useConnectorContextMenu(node: SVGElement | null, props: IProps) { const {index, trackData} = props const cur = keyframeUtils.getSortedKeyframesCached(trackData.keyframes)[index] const next = keyframeUtils.getSortedKeyframesCached(trackData.keyframes)[ index + 1 ] return useContextMenu(node, { items: () => { return [ { type: 'normal', label: 'Delete', callback: () => { getStudio()!.transaction(({stateEditors}) => { const {deleteKeyframes} = stateEditors.coreByProject.historic.sheetsById.sequence deleteKeyframes({ ...props.sheetObject.address, trackId: props.trackId, keyframeIds: [cur.id, next.id], }) }) }, }, ] }, }) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/CurveHandle.tsx ================================================ import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {val} from '@theatre/dataverse' import {clamp} from 'lodash-es' import React, {useMemo, useRef} from 'react' import styled from 'styled-components' import {transformBox} from './Curve' import type KeyframeEditor from './KeyframeEditor' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {__private} from '@theatre/core' const {keyframeUtils} = __private export const dotSize = 6 const Circle = styled.circle` stroke-width: 1px; vector-effect: non-scaling-stroke; fill: var(--main-color); r: 2px; pointer-events: none; ` const HitZone = styled.circle` stroke-width: 6px; vector-effect: non-scaling-stroke; r: 6px; fill: transparent; cursor: move; ${pointerEventsAutoInNormalMode}; &:hover { } &:hover + ${Circle} { r: 6px; } ` const Line = styled.path` stroke-width: 1; stroke: var(--main-color); /* stroke: gray; */ fill: none; vector-effect: non-scaling-stroke; ` type Which = 'left' | 'right' type IProps = Parameters[0] & {which: Which} const CurveHandle: React.VFC = (props) => { const [ref, node] = useRefAndState(null) const {index, trackData} = props const cur = keyframeUtils.getSortedKeyframesCached(trackData.keyframes)[index] const next = keyframeUtils.getSortedKeyframesCached(trackData.keyframes)[ index + 1 ] const [contextMenu] = useOurContextMenu(node, props) useOurDrags(node, props) const posInDiffSpace = props.which === 'left' ? cur.handles[2] : next.handles[0] const posInUnitSpace = cur.position + (next.position - cur.position) * posInDiffSpace const valInDiffSpace = props.which === 'left' ? cur.handles[3] : next.handles[1] const curValue = props.isScalar ? (cur.value as number) : 0 const nextValue = props.isScalar ? (next.value as number) : 1 const value = curValue + (nextValue - curValue) * valInDiffSpace const valInExtremumSpace = props.extremumSpace.fromValueSpace(value) const heightInExtremumSpace = valInExtremumSpace - props.extremumSpace.fromValueSpace( props.which === 'left' ? curValue : nextValue, ) const lineTransform = transformBox( props.which === 'left' ? cur.position : next.position, props.extremumSpace.fromValueSpace( props.which === 'left' ? curValue : nextValue, ), posInUnitSpace - (props.which === 'left' ? cur.position : next.position), heightInExtremumSpace, ) return ( {contextMenu} ) } export default CurveHandle function useOurDrags(node: SVGCircleElement | null, props: IProps): void { const propsRef = useRef(props) propsRef.current = props const handlers = useMemo[1]>(() => { return { debugName: 'CurveHandler/useOurDrags', lockCSSCursorTo: 'move', onDragStart() { let tempTransaction: CommitOrDiscardOrRecapture | undefined const propsAtStartOfDrag = propsRef.current const scaledToUnitSpace = val( propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, ) const verticalToExtremumSpace = val( propsAtStartOfDrag.layoutP.graphEditorVerticalSpace.toExtremumSpace, ) const unlockExtremums = propsAtStartOfDrag.extremumSpace.lock() return { onDrag(dxInScaledSpace, dy) { if (tempTransaction) { tempTransaction.discard() tempTransaction = undefined } const {index, trackData} = propsAtStartOfDrag const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( trackData.keyframes, ) const cur = sortedKeyframes[index] const next = sortedKeyframes[index + 1] const dPosInUnitSpace = scaledToUnitSpace(dxInScaledSpace) let dPosInKeyframeDiffSpace = dPosInUnitSpace / (next.position - cur.position) const dyInVerticalSpace = -dy const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace) const dYInValueSpace = propsAtStartOfDrag.extremumSpace.deltaToValueSpace( dYInExtremumSpace, ) const curValue = props.isScalar ? (cur.value as number) : 0 const nextValue = props.isScalar ? (next.value as number) : 1 const dyInKeyframeDiffSpace = dYInValueSpace / (nextValue - curValue) if (propsAtStartOfDrag.which === 'left') { const handleX = clamp( cur.handles[2] + dPosInKeyframeDiffSpace, 0, 1, ) const handleY = cur.handles[3] + dyInKeyframeDiffSpace tempTransaction = getStudio()!.tempTransaction( ({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes( { ...propsAtStartOfDrag.sheetObject.address, snappingFunction: val( propsAtStartOfDrag.layoutP.sheet, ).getSequence().closestGridPosition, trackId: propsAtStartOfDrag.trackId, keyframes: [ { ...cur, handles: [ cur.handles[0], cur.handles[1], handleX, handleY, ], }, ], }, ) }, ) } else { const handleX = clamp( next.handles[0] + dPosInKeyframeDiffSpace, 0, 1, ) const handleY = next.handles[1] + dyInKeyframeDiffSpace tempTransaction = getStudio()!.tempTransaction( ({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes( { ...propsAtStartOfDrag.sheetObject.address, trackId: propsAtStartOfDrag.trackId, snappingFunction: val( propsAtStartOfDrag.layoutP.sheet, ).getSequence().closestGridPosition, keyframes: [ { ...next, handles: [ handleX, handleY, next.handles[2], next.handles[3], ], }, ], }, ) }, ) } }, onDragEnd(dragHappened) { unlockExtremums() if (dragHappened) { if (tempTransaction) { tempTransaction.commit() } } else { if (tempTransaction) { tempTransaction.discard() } } }, } }, } }, []) useDrag(node, handlers) } function useOurContextMenu(node: SVGCircleElement | null, props: IProps) { return useContextMenu(node, { items: () => { return [ { type: 'normal', label: 'Delete', callback: () => { getStudio()!.transaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes( { ...props.sheetObject.address, keyframeIds: [props.keyframe.id], trackId: props.trackId, }, ) }) }, }, ] }, }) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/GraphEditorDotNonScalar.tsx ================================================ import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {val} from '@theatre/dataverse' import React, {useMemo, useRef, useState} from 'react' import styled from 'styled-components' import type KeyframeEditor from './KeyframeEditor' import type {BasicKeyframe} from '@theatre/core/types/public' import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import { lockedCursorCssVarName, useCssCursorLock, } from '@theatre/studio/uiComponents/PointerEventsHandler' import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap' import {useKeyframeInlineEditorPopover} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover' import usePresence, { PresenceFlag, } from '@theatre/studio/uiComponents/usePresence' import {__private} from '@theatre/core' const {keyframeUtils} = __private export const dotSize = 6 const Circle = styled.circle` fill: var(--main-color); stroke-width: 1px; vector-effect: non-scaling-stroke; r: 2px; ` const HitZone = styled.circle` stroke-width: 6px; vector-effect: non-scaling-stroke; r: 6px; fill: transparent; ${pointerEventsAutoInNormalMode}; &:hover + ${Circle} { r: 6px; } #pointer-root.normal & { cursor: ew-resize; } #pointer-root.draggingPositionInSequenceEditor & { pointer-events: auto; cursor: var(${lockedCursorCssVarName}); } &.beingDragged { pointer-events: none !important; } ` type IProps = Parameters[0] & {which: 'left' | 'right'} const GraphEditorDotNonScalar: React.VFC = (props) => { const [ref, node] = useRefAndState(null) const {index, trackData, itemKey} = props const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( trackData.keyframes, ) const cur = sortedKeyframes[index] const [contextMenu] = useKeyframeContextMenu(node, props) const presence = usePresence(itemKey) const curValue = props.which === 'left' ? 0 : 1 const inlineEditorPopover = useKeyframeInlineEditorPopover([ { type: 'primitiveProp', keyframe: props.keyframe, pathToProp: props.pathToProp, propConfig: props.propConfig, sheetObject: props.sheetObject, trackId: props.trackId, }, ]) const isDragging = useDragKeyframe({ node, props, // dragging does not work with also having a click listener onDetectedClick: (event) => inlineEditorPopover.toggle( event, event.target instanceof Element ? event.target : node!, ), }) const cyInExtremumSpace = props.extremumSpace.fromValueSpace(curValue) return ( <> {inlineEditorPopover.node} {contextMenu} ) } export default GraphEditorDotNonScalar function useDragKeyframe(options: { node: SVGCircleElement | null props: IProps onDetectedClick: (event: MouseEvent) => void }): boolean { const [isDragging, setIsDragging] = useState(false) useLockFrameStampPosition(isDragging, options.props.keyframe.position) const propsRef = useRef(options.props) propsRef.current = options.props const gestureHandlers = useMemo[1]>(() => { return { debugName: 'GraphEditorDotNonScalar/useDragKeyframe', lockCSSCursorTo: 'ew-resize', onDragStart(event) { setIsDragging(true) const propsAtStartOfDrag = propsRef.current const toUnitSpace = val( propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, ) const unlockExtremums = propsAtStartOfDrag.extremumSpace.lock() let tempTransaction: CommitOrDiscardOrRecapture | undefined return { onDrag(dx, dy) { const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( propsAtStartOfDrag.trackData.keyframes, ) const original = sortedKeyframes[propsAtStartOfDrag.index] const deltaPos = toUnitSpace(dx) const updatedKeyframes: BasicKeyframe[] = [] const cur: BasicKeyframe = { ...original, position: original.position + deltaPos, value: original.value, handles: [...original.handles], } updatedKeyframes.push(cur) tempTransaction?.discard() tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes( { ...propsAtStartOfDrag.sheetObject.address, trackId: propsAtStartOfDrag.trackId, keyframes: updatedKeyframes, snappingFunction: val( propsAtStartOfDrag.layoutP.sheet, ).getSequence().closestGridPosition, }, ) }) }, onDragEnd(dragHappened) { setIsDragging(false) unlockExtremums() if (dragHappened) { tempTransaction?.commit() } else { tempTransaction?.discard() options.onDetectedClick(event) } }, } }, } }, []) useDrag(options.node, gestureHandlers) useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize') return isDragging } function useKeyframeContextMenu(node: SVGCircleElement | null, props: IProps) { return useContextMenu(node, { items: () => { return [ { type: 'normal', label: 'Delete', callback: () => { getStudio()!.transaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes( { ...props.sheetObject.address, keyframeIds: [props.keyframe.id], trackId: props.trackId, }, ) }) }, }, ] }, }) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/GraphEditorDotScalar.tsx ================================================ import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {val} from '@theatre/dataverse' import React, {useMemo, useRef, useState} from 'react' import styled from 'styled-components' import type KeyframeEditor from './KeyframeEditor' import type {BasicKeyframe} from '@theatre/core/types/public' import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import { lockedCursorCssVarName, useCssCursorLock, } from '@theatre/studio/uiComponents/PointerEventsHandler' import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap' import {useKeyframeInlineEditorPopover} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/useSingleKeyframeInlineEditorPopover' import usePresence, { PresenceFlag, } from '@theatre/studio/uiComponents/usePresence' import {__private} from '@theatre/core' const {keyframeUtils} = __private export const dotSize = 6 const Circle = styled.circle` fill: var(--main-color); stroke-width: 1px; vector-effect: non-scaling-stroke; r: 2px; ` const HitZone = styled.circle` stroke-width: 6px; vector-effect: non-scaling-stroke; r: 6px; fill: transparent; ${pointerEventsAutoInNormalMode}; &:hover + ${Circle} { r: 6px; } #pointer-root.normal & { cursor: move; } #pointer-root.draggingPositionInSequenceEditor & { pointer-events: auto; cursor: var(${lockedCursorCssVarName}); } &.beingDragged { pointer-events: none !important; } ` type IProps = Parameters[0] const GraphEditorDotScalar: React.VFC = (props) => { const [ref, node] = useRefAndState(null) const {index, trackData} = props const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( trackData.keyframes, ) const cur = sortedKeyframes[index] const [contextMenu] = useKeyframeContextMenu(node, props) const presence = usePresence(props.itemKey) const curValue = cur.value as number const cyInExtremumSpace = props.extremumSpace.fromValueSpace(curValue) const inlineEditorPopover = useKeyframeInlineEditorPopover([ { type: 'primitiveProp', keyframe: props.keyframe, pathToProp: props.pathToProp, propConfig: props.propConfig, sheetObject: props.sheetObject, trackId: props.trackId, }, ]) const isDragging = useDragKeyframe({ node, props, // dragging does not work with also having a click listener onDetectedClick: (event) => inlineEditorPopover.toggle( event, event.target instanceof Element ? event.target : node!, ), }) return ( <> {inlineEditorPopover.node} {contextMenu} ) } export default GraphEditorDotScalar function useDragKeyframe(options: { node: SVGCircleElement | null props: IProps onDetectedClick: (event: MouseEvent) => void }): boolean { const [isDragging, setIsDragging] = useState(false) useLockFrameStampPosition(isDragging, options.props.keyframe.position) const propsRef = useRef(options.props) propsRef.current = options.props const gestureHandlers = useMemo[1]>(() => { return { debugName: 'GraphEditorDotScalar/useDragKeyframe', lockCSSCursorTo: 'move', onDragStart(event) { setIsDragging(true) const keepSpeeds = !!event.altKey const propsAtStartOfDrag = propsRef.current const toUnitSpace = val( propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, ) const verticalToExtremumSpace = val( propsAtStartOfDrag.layoutP.graphEditorVerticalSpace.toExtremumSpace, ) const unlockExtremums = propsAtStartOfDrag.extremumSpace.lock() let tempTransaction: CommitOrDiscardOrRecapture | undefined return { onDrag(dx, dy) { const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( propsAtStartOfDrag.trackData.keyframes, ) const original = sortedKeyframes[propsAtStartOfDrag.index] const deltaPos = toUnitSpace(dx) const dyInVerticalSpace = -dy const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace) const dYInValueSpace = propsAtStartOfDrag.extremumSpace.deltaToValueSpace( dYInExtremumSpace, ) const updatedKeyframes: BasicKeyframe[] = [] const cur: BasicKeyframe = { ...original, position: original.position + deltaPos, value: (original.value as number) + dYInValueSpace, handles: [...original.handles], } updatedKeyframes.push(cur) if (keepSpeeds) { const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( propsAtStartOfDrag.trackData.keyframes, ) const prev = sortedKeyframes[propsAtStartOfDrag.index - 1] if ( prev && Math.abs((original.value as number) - (prev.value as number)) > 0 ) { const newPrev: BasicKeyframe = { ...prev, handles: [...prev.handles], } updatedKeyframes.push(newPrev) newPrev.handles[3] = preserveRightHandle( prev.handles[3], prev.value as number, prev.value as number, original.value as number, cur.value as number, ) } const next = sortedKeyframes[propsAtStartOfDrag.index + 1] if ( next && Math.abs((original.value as number) - (next.value as number)) > 0 ) { const newNext: BasicKeyframe = { ...next, handles: [...next.handles], } updatedKeyframes.push(newNext) newNext.handles[1] = preserveLeftHandle( newNext.handles[1], newNext.value as number, newNext.value as number, original.value as number, cur.value as number, ) } } tempTransaction?.discard() tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes( { ...propsAtStartOfDrag.sheetObject.address, trackId: propsAtStartOfDrag.trackId, keyframes: updatedKeyframes, snappingFunction: val( propsAtStartOfDrag.layoutP.sheet, ).getSequence().closestGridPosition, }, ) }) }, onDragEnd(dragHappened) { setIsDragging(false) unlockExtremums() if (dragHappened) { tempTransaction?.commit() } else { tempTransaction?.discard() options.onDetectedClick(event) } }, } }, } }, []) useDrag(options.node, gestureHandlers) useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'move') return isDragging } function useKeyframeContextMenu(node: SVGCircleElement | null, props: IProps) { return useContextMenu(node, { items: () => { return [ { type: 'normal', label: 'Delete', callback: () => { getStudio()!.transaction(({stateEditors}) => { stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes( { ...props.sheetObject.address, keyframeIds: [props.keyframe.id], trackId: props.trackId, }, ) }) }, }, ] }, }) } function preserveRightHandle( rightHandleInKeyframeDeltaSpace: number, originalValueOfMovedKeyframe: number, newValueOfMovedKeyframe: number, originalValueOfNeighbouringKeyframe: number, newValueOfNeighbouringKeyframe: number, ): number { const diffOfHandleYToMovingKeyframeInValueSpace = (originalValueOfNeighbouringKeyframe - originalValueOfMovedKeyframe) * rightHandleInKeyframeDeltaSpace const newHandleYInKeyframeDeltaSpace = diffOfHandleYToMovingKeyframeInValueSpace / (newValueOfNeighbouringKeyframe - newValueOfMovedKeyframe) return newHandleYInKeyframeDeltaSpace } function preserveLeftHandle( leftHandleInKeyframeDeltaSpace: number, originalValueOfMovedKeyframe: number, newValueOfMovedKeyframe: number, originalValueOfNeighbouringKeyframe: number, newValueOfNeighbouringKeyframe: number, ): number { const handleYInValueSpace = (originalValueOfMovedKeyframe - originalValueOfNeighbouringKeyframe) * leftHandleInKeyframeDeltaSpace + originalValueOfNeighbouringKeyframe const diffOfHandleYToMovingKeyframeInValueSpace = handleYInValueSpace - originalValueOfMovedKeyframe const newHandleYInValueSpace = diffOfHandleYToMovingKeyframeInValueSpace + newValueOfMovedKeyframe const diffOfNewHandleYToNeighbouringKeyframe = newHandleYInValueSpace - newValueOfNeighbouringKeyframe const newHandleYInKeyframeDeltaSpace = diffOfNewHandleYToNeighbouringKeyframe / (newValueOfMovedKeyframe - newValueOfNeighbouringKeyframe) return newHandleYInKeyframeDeltaSpace } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/GraphEditorNonScalarDash.tsx ================================================ import React from 'react' import styled from 'styled-components' import type KeyframeEditor from './KeyframeEditor' import {transformBox} from './Curve' import {__private} from '@theatre/core' const {keyframeUtils} = __private export const dotSize = 6 const SVGPath = styled.path` stroke-width: 2; stroke: var(--main-color); stroke-dasharray: 3 2; fill: none; vector-effect: non-scaling-stroke; opacity: 0.3; ` type IProps = Parameters[0] const GraphEditorNonScalarDash: React.VFC = (props) => { const {index, trackData} = props const pathD = `M 0 0 L 1 1` const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( trackData.keyframes, ) const transform = transformBox( sortedKeyframes[index].position, props.extremumSpace.fromValueSpace(0), 0, props.extremumSpace.fromValueSpace(1) - props.extremumSpace.fromValueSpace(0), ) return ( <> ) } export default GraphEditorNonScalarDash ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/KeyframeEditor.tsx ================================================ import type {TrackData} from '@theatre/core/types/private/core' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type {BasicKeyframe, SequenceTrackId} from '@theatre/core/types/public' import type {Pointer} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import type {ExtremumSpace} from '@theatre/studio/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack' import Curve from './Curve' import CurveHandle from './CurveHandle' import GraphEditorDotScalar from './GraphEditorDotScalar' import GraphEditorDotNonScalar from './GraphEditorDotNonScalar' import GraphEditorNonScalarDash from './GraphEditorNonScalarDash' import type {PropTypeConfig_AllSimples} from '@theatre/core/types/public' import type {PathToProp} from '@theatre/utils/pathToProp' import type { GraphEditorColors, StudioSheetItemKey, } from '@theatre/core/types/private' import {__private} from '@theatre/core' const {keyframeUtils} = __private const Container = styled.g` /* position: absolute; */ ` const noConnector = <> type IKeyframeEditorProps = { index: number keyframe: BasicKeyframe trackData: TrackData itemKey: StudioSheetItemKey layoutP: Pointer trackId: SequenceTrackId sheetObject: SheetObject pathToProp: PathToProp extremumSpace: ExtremumSpace isScalar: boolean color: keyof GraphEditorColors propConfig: PropTypeConfig_AllSimples } const KeyframeEditor: React.VFC = (props) => { const {index, trackData, isScalar} = props const sortedKeyframes = keyframeUtils.getSortedKeyframesCached( trackData.keyframes, ) const cur = sortedKeyframes[index] const next = sortedKeyframes[index + 1] const connected = cur.connectedRight && !!next const shouldShowCurve = connected && next.value !== cur.value return ( {shouldShowCurve ? ( <> {!cur.type || (cur.type === 'bezier' && ( <> ))} ) : ( noConnector )} {isScalar ? ( ) : ( <> )} ) } export default KeyframeEditor ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/GraphEditor/GraphEditor.tsx ================================================ import getStudio from '@theatre/studio/getStudio' import {decodePathToProp} from '@theatre/utils/pathToProp' import getDeep from '@theatre/utils/getDeep' import type {SequenceTrackId} from '@theatre/core/types/public' import {usePrism} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {contentWidth} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/Right' import HorizontallyScrollableArea from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/HorizontallyScrollableArea' import PrimitivePropGraph from './PrimitivePropGraph' import FrameGrid from '@theatre/studio/panels/SequenceEditorPanel/FrameGrid/FrameGrid' import {transparentize} from 'polished' const Container = styled.div` position: absolute; right: 0; bottom: 0; background: ${transparentize(0.03, '#1a1c1e')}; ` const SVGContainer = styled.svg` position: absolute; top: 0; left: 0; margin: 0; pointer-events: none; ` const GraphEditor: React.FC<{ layoutP: Pointer }> = ({layoutP}) => { return usePrism(() => { const sheet = val(layoutP.sheet) const selectedPropsByObject = val( getStudio()!.atomP.historic.projects.stateByProjectId[ sheet.address.projectId ].stateBySheetId[sheet.address.sheetId].sequenceEditor .selectedPropsByObject, ) const height = val(layoutP.graphEditorDims.height) const unitSpaceToScaledSpaceMultiplier = val( layoutP.scaledSpace.fromUnitSpace, )(1) const graphs: Array = [] if (selectedPropsByObject) { for (const [objectKey, props] of Object.entries(selectedPropsByObject)) { const sheetObject = sheet.getObject(objectKey) if (!sheetObject) continue const validSequenceTracks = val( sheetObject.template.getMapOfValidSequenceTracks_forStudio(), ) for (const [encodedPathToProp, graphEditorColor] of Object.entries( props!, )) { const pathToProp = decodePathToProp(encodedPathToProp) const possibleSequenceTrackId = getDeep( validSequenceTracks, pathToProp, ) as undefined | SequenceTrackId if (!possibleSequenceTrackId) continue graphs.push( , ) } } } const width = val(layoutP.rightDims.width) return ( {graphs} ) }, [layoutP]) } export default GraphEditor ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/GraphEditor/PrimitivePropGraph.tsx ================================================ import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import getStudio from '@theatre/studio/getStudio' import type {PathToProp} from '@theatre/utils/pathToProp' import type {SequenceTrackId} from '@theatre/core/types/public' import {usePrism} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React from 'react' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import BasicKeyframedTrack from './BasicKeyframedTrack/BasicKeyframedTrack' import type {GraphEditorColors} from '@theatre/core/types/private' const PrimitivePropGraph: React.FC<{ layoutP: Pointer sheetObject: SheetObject pathToProp: PathToProp trackId: SequenceTrackId color: keyof GraphEditorColors }> = (props) => { return usePrism(() => { const {sheetObject, trackId} = props const trackData = val( getStudio()!.atomP.historic.coreByProject[sheetObject.address.projectId] .sheetsById[sheetObject.address.sheetId].sequence.tracksByObject[ sheetObject.address.objectKey ].trackData[trackId], ) if (trackData?.type !== 'BasicKeyframedTrack') { console.error( `trackData type ${trackData?.type} is not yet supported on the graph editor`, ) return <> } else { return } }, [props.trackId, props.layoutP]) } export default PrimitivePropGraph ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/GraphEditorToggle.tsx ================================================ import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import {useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import React, {useCallback} from 'react' import styled from 'styled-components' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {VscTriangleUp} from 'react-icons/vsc' import {includeLockFrameStampAttrs} from './FrameStampPositionProvider' const Container = styled.button` outline: none; background-color: #1c1d21; border: 1px solid #191919; border-radius: 2px; display: flex; bottom: 14px; right: 8px; z-index: 1; position: absolute; padding: 4px 8px; display: flex; color: #656d77; line-height: 20px; font-size: 10px; &:hover { color: white; } & > svg { transition: transform 0.3s; transform: rotateZ(0deg); } &:hover > svg { transform: rotateZ(-20deg); } &.open > svg { transform: rotateZ(-180deg); } &.open:hover > svg { transform: rotateZ(-160deg); } ` const GraphEditorToggle: React.FC<{ layoutP: Pointer }> = ({layoutP}) => { const isOpen = useVal(layoutP.graphEditorDims.isOpen) const toggle = useCallback(() => { const isOpen = val(layoutP.graphEditorDims.isOpen) getStudio()!.transaction(({stateEditors}) => { stateEditors.studio.historic.panels.sequenceEditor.graphEditor.setIsOpen({ isOpen: !isOpen, }) }) }, [layoutP]) return ( ) } export default GraphEditorToggle ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/DopeSnap.tsx ================================================ // Pretty much same code as for keyframe and similar for playhead. // Consider if we should unify the implementations. // - See "useLockFrameStampPosition" // - Also see "pointerPositionInUnitSpace" for a related impl (for different problem) const POSITION_SNAP_ATTR = 'data-pos' /** * Uses `[data-pos]` attribute to understand potential snap targets. */ const DopeSnap = { checkIfMouseEventSnapToPos( event: MouseEvent, options?: {ignore?: Element | null}, ): number | null { const snapTarget = event .composedPath() .find( (el): el is Element => el instanceof Element && el !== options?.ignore && el.hasAttribute(POSITION_SNAP_ATTR), ) if (snapTarget) { const snapPos = parseFloat(snapTarget.getAttribute(POSITION_SNAP_ATTR)!) if (isFinite(snapPos)) { return snapPos } } return null }, /** * Use as a spread in a React element * * @example * ```tsx *
* ``` */ includePositionSnapAttrs(position: number) { return {[POSITION_SNAP_ATTR]: position} }, } export default DopeSnap ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/DopeSnapHitZoneUI.tsx ================================================ import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {lockedCursorCssVarName} from '@theatre/studio/uiComponents/PointerEventsHandler' import {css} from 'styled-components' import SnapCursor from './SnapCursor.svg' import {absoluteDims} from '@theatre/studio/utils/absoluteDims' import DopeSnap from './DopeSnap' import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' const HIT_ZONE_SIZE_PX = 12 const SNAP_CURSOR_SIZE_PX = 34 const BEING_DRAGGED_CLASS = 'beingDragged' /** * Helper CSS for consistent display of the `⸢⸤⸣⸥` thing */ export const DopeSnapHitZoneUI = { BEING_DRAGGED_CLASS, CSS: css` position: absolute; ${absoluteDims(HIT_ZONE_SIZE_PX)}; ${pointerEventsAutoInNormalMode}; &.${BEING_DRAGGED_CLASS} { pointer-events: none !important; } `, CSS_WHEN_SOMETHING_DRAGGING: css` pointer-events: auto; cursor: var(${lockedCursorCssVarName}); // ⸢⸤⸣⸥ thing // This box extends the hitzone so the user does not // accidentally leave the hitzone &:hover:after { position: absolute; top: calc(50% - ${SNAP_CURSOR_SIZE_PX / 2}px); left: calc(50% - ${SNAP_CURSOR_SIZE_PX / 2}px); width: ${SNAP_CURSOR_SIZE_PX}px; height: ${SNAP_CURSOR_SIZE_PX}px; display: block; content: ' '; background: url(${SnapCursor}) no-repeat 100% 100%; // This icon might also fit: GiConvergenceTarget } `, /** Intrinsic element props for ``s */ reactProps(config: {position: number; isDragging: boolean}) { return { // `data-pos` and `includeLockFrameStampAttrs` are used by FrameStampPositionProvider // in order to handle snapping the playhead. Adding these props effectively // causes the playhead to "snap" to the marker on mouse over. // `pointerEventsAutoInNormalMode` and `lockedCursorCssVarName` in the CSS above are also // used to make this behave correctly. ...includeLockFrameStampAttrs(config.position), ...DopeSnap.includePositionSnapAttrs(config.position), className: config.isDragging ? DopeSnapHitZoneUI.BEING_DRAGGED_CLASS : '', } }, } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeStrip.tsx ================================================ import type {Pointer} from '@theatre/dataverse' import {prism, val} from '@theatre/dataverse' import {usePrism, useVal} from '@theatre/react' import type {$IntentionalAny} from '@theatre/core/types/public' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import getStudio from '@theatre/studio/getStudio' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {topStripHeight} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/TopStrip' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' import React, {useMemo} from 'react' import styled from 'styled-components' import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' export const focusRangeStripTheme = { enabled: { backgroundColor: '#2C2F34', stroke: '#646568', }, disabled: { backgroundColor: '#282a2cc5', stroke: '#595a5d', }, hover: { backgroundColor: '#34373D', stroke: '#C8CAC0', }, dragging: { backgroundColor: '#3F444A', stroke: '#C8CAC0', }, thumbWidth: 9, hitZoneWidth: 26, rangeStripMinWidth: 30, } const stripWidth = 1000 export const RangeStrip = styled.div<{enabled: boolean}>` position: absolute; height: ${() => topStripHeight - 1}px; background-color: ${(props) => props.enabled ? focusRangeStripTheme.enabled.backgroundColor : focusRangeStripTheme.disabled.backgroundColor}; cursor: grab; top: 0; left: 0; width: ${stripWidth}px; transform-origin: left top; &:hover { background-color: ${focusRangeStripTheme.hover.backgroundColor}; } &.dragging { background-color: ${focusRangeStripTheme.dragging.backgroundColor}; cursor: grabbing !important; } ${pointerEventsAutoInNormalMode}; /* covers the one pixel space between the focus range strip and the top strip of the sequence editor panel, which would have caused that one pixel to act like a panel drag zone */ &:after { display: block; content: ' '; position: absolute; bottom: -1px; height: 1px; left: 0; right: 0; background: transparent; pointer-events: normal; z-index: -1; } ` /** * Clamps the lower and upper bounds of a range to the lower and upper bounds of the reference range, while maintaining the original width of the range. If the range to be clamped has a greater width than the reference range, then the reference range is returned. * * @param range - The range bounds to be clamped * @param referenceRange - The reference range * * @returns The clamped bounds. * * @example * ```ts * clampRange([-1, 4], [2, 3]) // returns [2, 3] * clampRange([-1, 2.5], [2, 3]) // returns [2, 2.5] * ``` */ function clampRange( range: [number, number], referenceRange: [number, number], ): [number, number] { let overflow = 0 const [start, end] = range const [lower, upper] = referenceRange if (end - start > upper - lower) return [lower, upper] if (start < lower) { overflow = 0 - start } if (end > upper) { overflow = upper - end } return [start + overflow, end + overflow] } const FocusRangeStrip: React.FC<{ layoutP: Pointer }> = ({layoutP}) => { const existingRangeD = useMemo( () => prism(() => { const {projectId, sheetId} = val(layoutP.sheet).address const existingRange = val( getStudio().atomP.ahistoric.projects.stateByProjectId[projectId] .stateBySheetId[sheetId].sequence.focusRange, ) return existingRange }), [layoutP], ) const [rangeStripRef, rangeStripNode] = useRefAndState( null, ) const [contextMenu] = useContextMenu(rangeStripNode, { items: () => { const sheet = val(layoutP.sheet) const existingRange = existingRangeD.getValue() return [ { type: 'normal', label: 'Delete focus range', callback: () => { getStudio() .tempTransaction(({stateEditors}) => { stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.unset( { ...sheet.address, }, ) }) .commit() }, }, { type: 'normal', label: existingRange?.enabled ? 'Disable focus range' : 'Enable focus range', callback: () => { if (existingRange !== undefined) { getStudio() .tempTransaction(({stateEditors}) => { stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set( { ...sheet.address, range: existingRange.range, enabled: !existingRange.enabled, }, ) }) .commit() } }, }, ] }, }) const scaledSpaceToUnitSpace = useVal(layoutP.scaledSpace.toUnitSpace) const sheet = useVal(layoutP.sheet) const gestureHandlers = useMemo((): Parameters[1] => { let newStartPosition: number, newEndPosition: number return { debugName: 'FocusRangeStrip', onDragStart(event) { let tempTransaction: CommitOrDiscardOrRecapture | undefined let existingRange = existingRangeD.getValue() if (!existingRange) return false const startPosBeforeDrag = existingRange.range[0] const endPosBeforeDrag = existingRange.range[1] let dragHappened = false const sequence = val(layoutP.sheet).getSequence() return { onDrag(dx) { existingRange = existingRangeD.getValue() if (existingRange) { dragHappened = true const deltaPos = scaledSpaceToUnitSpace(dx) const start = startPosBeforeDrag + deltaPos let end = endPosBeforeDrag + deltaPos if (end < start) { end = start } ;[newStartPosition, newEndPosition] = clampRange( [start, end], [0, sequence.length], ).map((pos) => sequence.closestGridPosition(pos)) if (tempTransaction) { tempTransaction.discard() } tempTransaction = getStudio().tempTransaction( ({stateEditors}) => { stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set( { ...sheet.address, range: [newStartPosition, newEndPosition], enabled: existingRange?.enabled ?? true, }, ) }, ) } }, onDragEnd() { if (existingRange) { if (dragHappened && tempTransaction !== undefined) { tempTransaction.commit() } else if (tempTransaction) { tempTransaction.discard() } } }, } }, lockCSSCursorTo: 'grabbing', } }, [sheet, scaledSpaceToUnitSpace]) const [isDragging] = useDrag(rangeStripNode, gestureHandlers) useLockFrameStampPosition(isDragging, -1) return usePrism(() => { const existingRange = existingRangeD.getValue() const range = existingRange?.range || [0, 0] let startX = val(layoutP.clippedSpace.fromUnitSpace)(range[0]) let endX = val(layoutP.clippedSpace.fromUnitSpace)(range[1]) let scaleX: number, translateX: number if (startX < 0) { startX = 0 } if (endX > val(layoutP.clippedSpace.width)) { endX = val(layoutP.clippedSpace.width) } if (startX > endX) { translateX = 0 scaleX = 0 } else { translateX = startX scaleX = (endX - startX) / stripWidth } if (!existingRange) return <> return ( <> {contextMenu} ) }, [layoutP, rangeStripRef, existingRangeD, contextMenu, isDragging]) } export default FocusRangeStrip ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeThumb.tsx ================================================ import type {Pointer} from '@theatre/dataverse' import {prism, val} from '@theatre/dataverse' import {usePrism, useVal} from '@theatre/react' import type {$IntentionalAny, IRange} from '@theatre/core/types/public' import getStudio from '@theatre/studio/getStudio' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import { topStripHeight, topStripTheme, } from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/TopStrip' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import { lockedCursorCssVarName, useCssCursorLock, } from '@theatre/studio/uiComponents/PointerEventsHandler' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' import React, {useMemo} from 'react' import styled from 'styled-components' import { includeLockFrameStampAttrs, useLockFrameStampPosition, } from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {focusRangeStripTheme, RangeStrip} from './FocusRangeStrip' import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap' const TheDiv = styled.div<{enabled: boolean; type: ThumbType}>` position: absolute; top: 0; // the right handle has to be pulled back by its width since its right side indicates its position, not its left side left: ${(props) => props.type === ThumbType.start ? 0 : -focusRangeStripTheme.thumbWidth}px; transform-origin: left top; width: ${focusRangeStripTheme.thumbWidth}px; height: ${() => topStripHeight - 1}px; z-index: 3; --bg: ${({enabled}) => enabled ? focusRangeStripTheme.enabled.backgroundColor : focusRangeStripTheme.disabled.backgroundColor}; stroke: ${focusRangeStripTheme.enabled.stroke}; user-select: none; cursor: ${(props) => props.type === ThumbType.start ? 'w-resize' : 'e-resize'}; // no pointer events unless pointer-root is in normal mode _and_ the // focus range is enabled #pointer-root & { pointer-events: none; } #pointer-root.normal & { pointer-events: auto; } #pointer-root.draggingPositionInSequenceEditor & { pointer-events: auto; cursor: var(${lockedCursorCssVarName}); } &.dragging { pointer-events: none !important; } // highlight the handle if it's hovered, or the whole strip is hovverd ${() => RangeStrip}:hover ~ &, &:hover { --bg: ${focusRangeStripTheme.hover.backgroundColor}; stroke: ${focusRangeStripTheme.hover.stroke}; } // highlight the handle when it's being dragged or the whole strip is being dragged. // using dragging.dragging to give this selector priority, as it seems to be overridden // by the hover selector above &.dragging, ${() => RangeStrip}.dragging.dragging ~ & { --bg: ${focusRangeStripTheme.dragging.backgroundColor}; stroke: ${focusRangeStripTheme.dragging.stroke}; } #pointer-root.draggingPositionInSequenceEditor &:hover { --bg: ${focusRangeStripTheme.dragging.backgroundColor}; stroke: #40aaa4; } background-color: var(--bg); // a larger hit zone &:before { display: block; content: ' '; position: absolute; inset: -8px; } ` /** * This acts as a bit of a horizontal shadow that covers the frame numbers that show up * right next to the thumb, making the appearance of the focus range more tidy. */ const ColoredMargin = styled.div<{type: ThumbType; enabled: boolean}>` position: absolute; top: 0; bottom: 0; pointer-events: none; background: linear-gradient( ${(props) => (props.type === ThumbType.start ? 90 : -90)}deg, var(--bg) 0%, #ffffff00 100% ); width: 12px; left: ${(props) => props.type === ThumbType.start ? focusRangeStripTheme.thumbWidth : // pushing the right-side thumb's margin 1px to the right to make sure there is no space // between it and the thumb -focusRangeStripTheme.thumbWidth + 1}px; ` const OuterColoredMargin = styled.div<{ type: ThumbType }>` position: absolute; top: 0; bottom: 0; pointer-events: none; background: linear-gradient( ${(props) => (props.type === ThumbType.start ? -90 : 90)}deg, ${() => topStripTheme.backgroundColor} 0%, #ffffff00 100% ); width: 12px; left: ${(props) => props.type === ThumbType.start ? -12 : focusRangeStripTheme.thumbWidth}px; ` enum ThumbType { start = 0, end = 1, } const FocusRangeThumb: React.FC<{ layoutP: Pointer thumbType: ThumbType }> = ({layoutP, thumbType}) => { const [hitZoneRef, hitZoneNode] = useRefAndState(null) const existingRangeD = useMemo( () => prism(() => { const {projectId, sheetId} = val(layoutP.sheet).address const existingRange = val( getStudio().atomP.ahistoric.projects.stateByProjectId[projectId] .stateBySheetId[sheetId].sequence.focusRange, ) return existingRange }), [layoutP], ) const gestureHandlers = useMemo((): Parameters[1] => { return { debugName: 'FocusRangeThumb', onDragStart() { let tempTransaction: CommitOrDiscardOrRecapture | undefined let range: IRange const sheet = val(layoutP.sheet) const sequence = sheet.getSequence() const defaultRange: IRange = [0, sequence.length] let existingRange = existingRangeD.getValue() || { range: defaultRange, enabled: false, } const focusRangeEnabled = existingRange.enabled const posBeforeDrag = existingRange.range[thumbType] const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) const minFocusRangeStripWidth = scaledSpaceToUnitSpace( focusRangeStripTheme.rangeStripMinWidth, ) return { onDrag(dx, _, event) { let newPosition: number const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, { ignore: hitZoneNode, }) if (snapPos == null) { const deltaPos = scaledSpaceToUnitSpace(dx) const oldPosPlusDeltaPos = posBeforeDrag + deltaPos newPosition = oldPosPlusDeltaPos } else { newPosition = snapPos } range = existingRangeD.getValue()?.range || defaultRange // Make sure that the focus range has a minimal width if (thumbType === ThumbType.start) { // Prevent the start thumb from going below 0 newPosition = Math.max( Math.min(newPosition, range[1] - minFocusRangeStripWidth), 0, ) } else { // Prevent the start thumb from going over the length of the sequence newPosition = Math.min( Math.max(newPosition, range[0] + minFocusRangeStripWidth), sheet.getSequence().length, ) } const newPositionInFrame = sheet .getSequence() .closestGridPosition(newPosition) if (tempTransaction !== undefined) { tempTransaction.discard() } tempTransaction = getStudio().tempTransaction(({stateEditors}) => { stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set( { ...sheet.address, range: thumbType === 0 ? [newPosition, range[1]] : [range[0], newPosition], enabled: focusRangeEnabled, }, ) }) }, onDragEnd(dragHappened) { if (dragHappened) tempTransaction?.commit() else tempTransaction?.discard() }, } }, } }, [layoutP]) const [isDragging] = useDrag(hitZoneNode, gestureHandlers) useCssCursorLock( isDragging, 'draggingPositionInSequenceEditor', thumbType === ThumbType.start ? 'w-resize' : 'e-resize', ) const existingRange = useVal(existingRangeD) useLockFrameStampPosition(isDragging, existingRange?.range[thumbType] ?? 0) return usePrism(() => { const existingRange = existingRangeD.getValue() if (!existingRange) return null const {enabled} = existingRange const position = existingRange.range[thumbType] let posInClippedSpace: number = val(layoutP.clippedSpace.fromUnitSpace)( position, ) if ( posInClippedSpace < 0 || val(layoutP.clippedSpace.width) < posInClippedSpace ) { posInClippedSpace = -10000 } return ( ) }, [layoutP, hitZoneRef, existingRangeD, isDragging]) } export default FocusRangeThumb ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeZone.tsx ================================================ import type {Pointer} from '@theatre/dataverse' import {prism, val} from '@theatre/dataverse' import {usePrism} from '@theatre/react' import type {$IntentionalAny} from '@theatre/core/types/public' import getStudio from '@theatre/studio/getStudio' import { panelDimsToPanelPosition, usePanel, } from '@theatre/studio/panels/BasePanel/BasePanel' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {topStripHeight} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/TopStrip' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler' import useDrag from '@theatre/studio/uiComponents/useDrag' import useHoverWithoutDescendants from '@theatre/studio/uiComponents/useHoverWithoutDescendants' import useKeyDown from '@theatre/studio/uiComponents/useKeyDown' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {clamp} from 'lodash-es' import React, {useEffect, useMemo, useRef, useState} from 'react' import styled from 'styled-components' import FocusRangeStrip, {focusRangeStripTheme} from './FocusRangeStrip' import FocusRangeThumb from './FocusRangeThumb' import {minVisibleSize} from '@theatre/studio/panels/BasePanel/common' const Container = styled.div<{isShiftDown: boolean}>` position: absolute; height: ${() => topStripHeight}px; left: 0; right: 0; box-sizing: border-box; /* Use the "grab" cursor if the shift key is up, which is the one used on the top strip of the sequence editor */ cursor: ${(props) => (props.isShiftDown ? 'ew-resize' : 'move')}; ` const FocusRangeZone: React.FC<{ layoutP: Pointer }> = ({layoutP}) => { const [containerRef, containerNode] = useRefAndState(null) const panelStuff = usePanel() const panelStuffRef = useRef(panelStuff) panelStuffRef.current = panelStuff const existingRangeD = useMemo( () => prism(() => { const {projectId, sheetId} = val(layoutP.sheet).address const existingRange = val( getStudio().atomP.ahistoric.projects.stateByProjectId[projectId] .stateBySheetId[sheetId].sequence.focusRange, ) return existingRange }), [layoutP], ) useDrag( containerNode, usePanelDragZoneGestureHandlers(layoutP, panelStuffRef), ) const isShiftDown = useKeyDown('Shift') const isPointerHovering = useHoverWithoutDescendants(containerNode) useEffect(() => { if (!isShiftDown && isPointerHovering) { const unlock = panelStuffRef.current.addBoundsHighlightLock() return unlock } }, [!isShiftDown && isPointerHovering]) return usePrism(() => { return ( ) }, [layoutP, existingRangeD, isShiftDown]) } export default FocusRangeZone function usePanelDragZoneGestureHandlers( layoutP: Pointer, panelStuffRef: React.MutableRefObject>, ) { const [mode, setMode] = useState<'none' | 'creating' | 'moving-panel'>('none') useCssCursorLock( mode !== 'none', 'dragging', mode === 'creating' ? 'ew-resize' : 'move', ) return useMemo((): Parameters[1] => { const focusRangeCreationGestureHandlers = (): Parameters< typeof useDrag >[1] => { return { debugName: 'FocusRangeZone/focusRangeCreationGestureHandlers', onDragStart(event) { let tempTransaction: CommitOrDiscardOrRecapture | undefined const clippedSpaceToUnitSpace = val(layoutP.clippedSpace.toUnitSpace) const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) const sheet = val(layoutP.sheet) const sequence = sheet.getSequence() const targetElement: HTMLElement = event.target as HTMLElement const rect = targetElement!.getBoundingClientRect() const startPosInUnitSpace = clippedSpaceToUnitSpace( event.clientX - rect.left, ) const minFocusRangeStripWidth = scaledSpaceToUnitSpace( focusRangeStripTheme.rangeStripMinWidth, ) return { onDrag(dx) { const deltaPos = scaledSpaceToUnitSpace(dx) let start = startPosInUnitSpace let end = startPosInUnitSpace + deltaPos ;[start, end] = [ clamp(start, 0, sequence.length), clamp(end, 0, sequence.length), ].map((pos) => sequence.closestGridPosition(pos)) if (end < start) { ;[start, end] = [ Math.max(Math.min(end, start - minFocusRangeStripWidth), 0), start, ] } else if (dx > 0) { end = Math.min( Math.max(end, start + minFocusRangeStripWidth), sequence.length, ) } if (tempTransaction) { tempTransaction.discard() } tempTransaction = getStudio().tempTransaction( ({stateEditors}) => { stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set( { ...sheet.address, range: [start, end], enabled: true, }, ) }, ) }, onDragEnd(dragHappened) { if (dragHappened && tempTransaction !== undefined) { tempTransaction.commit() } else if (tempTransaction) { tempTransaction.discard() } }, } }, lockCSSCursorTo: 'ew-resize', } } const panelMoveGestureHandlers = (): Parameters[1] => { return { debugName: 'FocusRangeZone/panelMoveGestureHandlers', onDragStart() { let tempTransaction: CommitOrDiscardOrRecapture | undefined const stuffBeforeDrag = panelStuffRef.current const unlock = panelStuffRef.current.addBoundsHighlightLock() return { onDrag(dx, dy) { const newDims: (typeof panelStuffRef.current)['dims'] = { ...stuffBeforeDrag.dims, top: clamp( stuffBeforeDrag.dims.top + dy, 0, window.innerHeight - minVisibleSize, ), left: clamp( stuffBeforeDrag.dims.left + dx, -stuffBeforeDrag.dims.width + minVisibleSize, window.innerWidth - minVisibleSize, ), } const position = panelDimsToPanelPosition(newDims, { width: window.innerWidth, height: window.innerHeight, }) tempTransaction?.discard() tempTransaction = getStudio()!.tempTransaction( ({stateEditors}) => { stateEditors.studio.historic.panelPositions.setPanelPosition({ position, panelId: stuffBeforeDrag.panelId, }) }, ) }, onDragEnd(dragHappened) { unlock() if (dragHappened) { tempTransaction?.commit() } else { tempTransaction?.discard() } }, } }, lockCSSCursorTo: 'move', } } return { debugName: 'FocusRangeZone', onDragStart(event) { const [_mode, currentGestureHandlers] = event.shiftKey ? [ 'creating' as 'creating', focusRangeCreationGestureHandlers().onDragStart(event), ] : [ 'moving-panel' as 'moving-panel', panelMoveGestureHandlers().onDragStart(event), ] setMode(_mode) if (currentGestureHandlers === false) return false return { onDrag(dx, dy, event, ddx, ddy) { currentGestureHandlers.onDrag(dx, dy, event, ddx, ddy) }, onDragEnd(dragHappened) { setMode('none') currentGestureHandlers.onDragEnd?.(dragHappened) }, } }, } }, [layoutP, panelStuffRef]) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/FrameStamp.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {usePrism, useVal} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import {stampsGridTheme} from '@theatre/studio/panels/SequenceEditorPanel/FrameGrid/StampsGrid' import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel' import {topStripTheme} from './TopStrip' import { FrameStampPositionType, useFrameStampPositionD, } from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' const Container = styled.div` position: absolute; top: 0; left: 0; margin-top: 0px; ` const Label = styled.div` position: absolute; top: 16px; font-size: ${stampsGridTheme.stampFontSize}; color: ${stampsGridTheme.fullUnitStampColor}; text-align: center; transform: translateX(-50%); background: ${topStripTheme.backgroundColor}; padding: 1px 8px; font-variant-numeric: tabular-nums; pointer-events: none; z-index: ${() => zIndexes.currentFrameStamp}; ` const Line = styled.div<{posType: FrameStampPositionType}>` position: absolute; top: 5px; left: -0px; bottom: 0; width: 0.5px; background: rgba(100, 100, 100, 0.2); pointer-events: none; ` const FrameStamp: React.FC<{ layoutP: Pointer }> = React.memo(({layoutP}) => { const [posInUnitSpace, posType] = useVal(useFrameStampPositionD()) const unitSpaceToClippedSpace = useVal(layoutP.clippedSpace.fromUnitSpace) const {sequence, formatter, clippedSpaceWidth} = usePrism(() => { const sequence = val(layoutP.sheet).getSequence() const clippedSpaceWidth = val(layoutP.clippedSpace.width) return {sequence, formatter: sequence.positionFormatter, clippedSpaceWidth} }, [layoutP]) if (posInUnitSpace == -1) { return <> } const snappedPosInUnitSpace = posType === FrameStampPositionType.free ? sequence.closestGridPosition(posInUnitSpace) : posInUnitSpace const posInClippedSpace = unitSpaceToClippedSpace(snappedPosInUnitSpace) const isVisible = posInClippedSpace >= 0 && posInClippedSpace <= clippedSpaceWidth return ( <> {' '} ) }) export default FrameStamp ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/HorizontalScrollbar.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {useVal} from '@theatre/react' import type {IRange} from '@theatre/core/types/public' import type {Pointer} from '@theatre/dataverse' import {prism, val} from '@theatre/dataverse' import {position} from 'polished' import React, {useCallback, useMemo, useState} from 'react' import styled from 'styled-components' import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel' import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import useDrag from '@theatre/studio/uiComponents/useDrag' const Container = styled.div` --threadHeight: 6px; --bg-inactive: #32353b; --bg-active: #5b5c5d; position: absolute; height: 0; width: 100%; left: 12px; /* bottom: 8px; */ z-index: ${() => zIndexes.horizontalScrollbar}; ${pointerEventsAutoInNormalMode} ` const TimeThread = styled.div` position: relative; top: 0; left: 0; width: 100%; height: var(--threadHeight); ` const RangeBar = styled.div` position: absolute; height: 5px; background: var(--bg-inactive); cursor: ew-resize; z-index: 2; &:hover, &:active { background: var(--bg-active); } &:after { ${position('absolute', '-4px')}; display: block; content: ' '; } ` const RangeHandle = styled.div` position: absolute; height: 5px; width: 7px; left: 0; z-index: 2; top: 0; bottom: 0; display: block; &:hover:before { background: var(--bg-active); } &:before { ${position('absolute', '0')}; display: block; content: ' '; background: var(--bg-inactive); border-radius: 0 2px 2px 0; } &:after { ${position('absolute', '-4px')}; display: block; content: ' '; } ` const RangeStartHandle = styled(RangeHandle)` left: calc(-1 * 7px); cursor: w-resize; &:before { transform: scaleX(-1); } ` const RangeEndHandle = styled(RangeHandle)` cursor: e-resize; left: 0px; ` const Tooltip = styled.div<{active: boolean}>` display: ${(props) => (props.active ? 'block' : 'none')}; position: absolute; top: -20px; left: 4px; padding: 0 4px; transform: translateX(-50%); background: #131d1f; border-radius: 4px; color: #fff; font-size: 10px; line-height: 18px; text-align: center; ${RangeStartHandle}:hover &, ${RangeEndHandle}:hover &, ${RangeBar}:hover ~ ${RangeStartHandle} &, ${RangeBar}:hover ~ ${RangeEndHandle} & { display: block; } ` /** * The little scrollbar on the bottom of the Right side */ const HorizontalScrollbar: React.FC<{ layoutP: Pointer }> = ({layoutP}) => { const unitPosToHumanReadablePos = useCallback((n: number) => n.toFixed(2), []) // const dd = usePrism(() => val(layoutP.sheet).getSequence().positionFormatter.formatForPlayhead, [layoutP]) const relevantValuesD = useMemo( () => prism(() => { const rightWidth = val(layoutP.rightDims.width) - 25 const clippedSpaceRange = val(layoutP.clippedSpace.range) const sequenceLength = val(layoutP.sheet).getSequence().length const assumedLengthOfSequence = Math.max( clippedSpaceRange[1], sequenceLength, ) const rangeStartX = (clippedSpaceRange[0] / assumedLengthOfSequence) * rightWidth const rangeEndX = (clippedSpaceRange[1] / assumedLengthOfSequence) * rightWidth return { rightWidth, clippedSpaceRange, sequenceLength, assumedLengthOfSequence, rangeStartX, rangeEndX, bottom: val(layoutP.horizontalScrollbarDims.bottom), } }), [layoutP], ) const {rangeStartX, rangeEndX, clippedSpaceRange, bottom} = useVal(relevantValuesD) const [beingDragged, setBeingDragged] = useState< 'nothing' | 'both' | 'start' | 'end' >('nothing') const handles = useMemo(() => { let valuesBeforeDrag = val(relevantValuesD) let noteValuesBeforeDrag = () => { valuesBeforeDrag = val(relevantValuesD) } const deltaXToDeltaPos = (dx: number): number => { const asAFractionOfRightWidth = dx / valuesBeforeDrag.rightWidth return asAFractionOfRightWidth * valuesBeforeDrag.assumedLengthOfSequence } const self = { onRangeDragStart() { noteValuesBeforeDrag() return { onDrag(dx: number) { setBeingDragged('both') const deltaPosInUnitSpace = deltaXToDeltaPos(dx) const newRange = valuesBeforeDrag.clippedSpaceRange.map( (p) => p + deltaPosInUnitSpace, ) as IRange val(layoutP.clippedSpace.setRange)(newRange) }, onDragEnd() { setBeingDragged('nothing') }, } }, onRangeStartDragStart() { noteValuesBeforeDrag() return { onDrag(dx: number) { setBeingDragged('start') const deltaPosInUnitSpace = deltaXToDeltaPos(dx) const newRange: IRange = [ valuesBeforeDrag.clippedSpaceRange[0] + deltaPosInUnitSpace, valuesBeforeDrag.clippedSpaceRange[1], ] if (newRange[0] > newRange[1] - 1) { newRange[0] = newRange[1] - 1 } if (newRange[0] <= 0) { newRange[0] = 0 } val(layoutP.clippedSpace.setRange)(newRange) }, onDragEnd() { setBeingDragged('nothing') }, } }, onRangeEndDragStart() { noteValuesBeforeDrag() return { onDrag(dx: number) { setBeingDragged('end') const deltaPosInUnitSpace = deltaXToDeltaPos(dx) const newRange: IRange = [ valuesBeforeDrag.clippedSpaceRange[0], valuesBeforeDrag.clippedSpaceRange[1] + deltaPosInUnitSpace, ] if (newRange[1] < newRange[0] + 1) { newRange[1] = newRange[0] + 1 } if (newRange[1] >= valuesBeforeDrag.assumedLengthOfSequence) { newRange[1] = valuesBeforeDrag.assumedLengthOfSequence } val(layoutP.clippedSpace.setRange)(newRange) }, onDragEnd() { setBeingDragged('nothing') }, } }, } return self }, [layoutP, relevantValuesD]) const [rangeDragNode, setRangeDragNode] = useState( null, ) useDrag(rangeDragNode, { debugName: 'HorizontalScrollbar/onRangeDrag', onDragStart: handles.onRangeDragStart, lockCSSCursorTo: 'ew-resize', }) const [rangeStartDragNode, setRangeStartDragNode] = useState(null) useDrag(rangeStartDragNode, { debugName: 'HorizontalScrollbar/onRangeStartDrag', onDragStart: handles.onRangeStartDragStart, lockCSSCursorTo: 'w-resize', }) const [rangeEndDragNode, setRangeEndDragNode] = useState(null) useDrag(rangeEndDragNode, { debugName: 'HorizontalScrollbar/onRangeEndDrag', onDragStart: handles.onRangeEndDragStart, lockCSSCursorTo: 'e-resize', }) return ( {unitPosToHumanReadablePos(clippedSpaceRange[0])} {unitPosToHumanReadablePos(clippedSpaceRange[1])} ) } export default HorizontalScrollbar ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/Markers/MarkerDot.tsx ================================================ import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import {useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import { lockedCursorCssVarName, useCssCursorLock, } from '@theatre/studio/uiComponents/PointerEventsHandler' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useRefAndState from '@theatre/studio/utils/useRefAndState' import React, {useMemo, useRef} from 'react' import styled from 'styled-components' import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import type {SheetAddress} from '@theatre/core/types/public' import useDrag from '@theatre/studio/uiComponents/useDrag' import type {DragOpts} from '@theatre/studio/uiComponents/useDrag' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import type {StudioHistoricStateSequenceEditorMarker} from '@theatre/core/types/private' import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel' import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap' import {absoluteDims} from '@theatre/studio/utils/absoluteDims' import {DopeSnapHitZoneUI} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnapHitZoneUI' import { snapToAll, snapToNone, } from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover' import MarkerEditorPopover from './MarkerEditorPopover' import type {SequenceMarkerId} from '@theatre/core/types/public' const MARKER_SIZE_W_PX = 12 const MARKER_SIZE_H_PX = 12 const MARKER_HOVER_SIZE_W_PX = MARKER_SIZE_W_PX * 2 const MARKER_HOVER_SIZE_H_PX = MARKER_SIZE_H_PX * 2 const MarkerDotContainer = styled.div` position: absolute; // below the sequence ruler "top bar" top: 18px; z-index: ${() => zIndexes.marker}; ` const MarkerVisualDotSVGContainer = styled.div` position: absolute; ${absoluteDims(MARKER_SIZE_W_PX, MARKER_SIZE_H_PX)} pointer-events: none; ` // Attempted to optimize via memo + inline svg rather than background-url const MarkerVisualDot = React.memo(() => ( } /> )) const HitZone = styled.div` z-index: 1; cursor: ew-resize; ${DopeSnapHitZoneUI.CSS} // :not dragging marker to ensure that markers don't snap to other markers // this works because only one marker track (so this technique is not used by keyframes...) #pointer-root.draggingPositionInSequenceEditor:not(.draggingMarker) & { ${DopeSnapHitZoneUI.CSS_WHEN_SOMETHING_DRAGGING} } // "All instances of this component inside #pointer-root when it has the .draggingPositionInSequenceEditor class" // ref: https://styled-components.com/docs/basics#pseudoelements-pseudoselectors-and-nesting #pointer-root.draggingPositionInSequenceEditor:not(.draggingMarker) &, #pointer-root.draggingPositionInSequenceEditor &.${DopeSnapHitZoneUI.BEING_DRAGGED_CLASS} { pointer-events: auto; cursor: var(${lockedCursorCssVarName}); } &:hover + ${MarkerVisualDotSVGContainer}, // notice , "or" in CSS &.${DopeSnapHitZoneUI.BEING_DRAGGED_CLASS} + ${MarkerVisualDotSVGContainer} { ${absoluteDims(MARKER_HOVER_SIZE_W_PX, MARKER_HOVER_SIZE_H_PX)} } ` type IMarkerDotProps = { layoutP: Pointer markerId: SequenceMarkerId } const MarkerDot: React.VFC = ({layoutP, markerId}) => { const sheetAddress = useVal(layoutP.sheet.address) const marker = useVal( getStudio().atomP.historic.projects.stateByProjectId[sheetAddress.projectId] .stateBySheetId[sheetAddress.sheetId].sequenceEditor.markerSet.byId[ markerId ], ) if (!marker) { // 1/10 maybe this is normal if React tries to re-render this with // out of date data. (e.g. Suspense / Transition stuff?) return null } // check marker in viewable bounds const clippedSpaceWidth = useVal(layoutP.clippedSpace.width) const clippedSpaceFromUnitSpace = useVal(layoutP.clippedSpace.fromUnitSpace) const clippedSpaceMarkerX = clippedSpaceFromUnitSpace(marker.position) const outsideClipDims = clippedSpaceMarkerX <= 0 || clippedSpaceMarkerX > clippedSpaceWidth // If outside the clip space, we want to hide the marker dot. We // hide the dot by translating it far away and scaling it to 0. // This method of hiding does not cause reflow/repaint. const translateX = outsideClipDims ? -10000 : clippedSpaceMarkerX const scale = outsideClipDims ? 0 : 1 return ( ) } export default MarkerDot type IMarkerDotVisibleProps = { layoutP: Pointer marker: StudioHistoricStateSequenceEditorMarker } const MarkerDotVisible: React.VFC = ({ layoutP, marker, }) => { const sheetAddress = useVal(layoutP.sheet.address) const [markRef, markNode] = useRefAndState(null) const [contextMenu] = useMarkerContextMenu(markNode, { sheetAddress, markerId: marker.id, }) const [isDragging] = useDragMarker(markNode, { layoutP, marker, }) const { node: popoverNode, toggle: togglePopover, close: closePopover, } = usePopover({debugName: 'MarkerPopover'}, () => { return ( ) }) return ( <> {contextMenu} {popoverNode} { togglePopover(e, markRef.current!) }} {...DopeSnapHitZoneUI.reactProps({ isDragging, position: marker.position, })} /> ) } function useMarkerContextMenu( node: HTMLElement | null, options: { sheetAddress: SheetAddress markerId: SequenceMarkerId }, ) { return useContextMenu(node, { items() { return [ { type: 'normal', label: 'Remove marker', callback: () => { getStudio().transaction(({stateEditors}) => { stateEditors.studio.historic.projects.stateByProjectId.stateBySheetId.sequenceEditor.removeMarker( { sheetAddress: options.sheetAddress, markerId: options.markerId, }, ) }) }, }, ] }, }) } function useDragMarker( node: HTMLDivElement | null, props: { layoutP: Pointer marker: StudioHistoricStateSequenceEditorMarker }, ): [isDragging: boolean] { const propsRef = useRef(props) propsRef.current = props const useDragOpts = useMemo(() => { return { debugName: `MarkerDot/useDragMarker (${props.marker.id})`, onDragStart(_event) { const markerAtStartOfDrag = propsRef.current.marker const toUnitSpace = val(props.layoutP.scaledSpace.toUnitSpace) let tempTransaction: CommitOrDiscardOrRecapture | undefined snapToAll() return { onDrag(dx, _dy, event) { const original = markerAtStartOfDrag const newPosition = Math.max( // check if our event hoversover a [data-pos] element DopeSnap.checkIfMouseEventSnapToPos(event, { ignore: node, }) ?? // if we don't find snapping target, check the distance dragged + original position original.position + toUnitSpace(dx), // sanitize to minimum of zero 0, ) tempTransaction?.discard() tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { stateEditors.studio.historic.projects.stateByProjectId.stateBySheetId.sequenceEditor.replaceMarkers( { sheetAddress: val(props.layoutP.sheet.address), markers: [{...original, position: newPosition}], snappingFunction: val(props.layoutP.sheet).getSequence() .closestGridPosition, }, ) }) }, onDragEnd(dragHappened) { if (dragHappened) tempTransaction?.commit() else tempTransaction?.discard() snapToNone() }, } }, } }, []) const [isDragging] = useDrag(node, useDragOpts) useLockFrameStampPosition(isDragging, props.marker.position) useCssCursorLock( isDragging, 'draggingPositionInSequenceEditor draggingMarker', 'ew-resize', ) return [isDragging] } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/Markers/MarkerEditorPopover.tsx ================================================ import type {Pointer} from '@theatre/dataverse' import React, {useLayoutEffect, useMemo, useRef} from 'react' import styled from 'styled-components' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import type {BasicNumberInputNudgeFn} from '@theatre/studio/uiComponents/form/BasicNumberInput' import type {CommitOrDiscardOrRecapture} from '@theatre/studio/StudioStore/StudioStore' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' import type {StudioHistoricStateSequenceEditorMarker} from '@theatre/core/types/private' import BasicStringInput from '@theatre/studio/uiComponents/form/BasicStringInput' const Container = styled.div` display: flex; gap: 8px; height: 28px; align-items: center; ` const Label = styled.div` ${propNameTextCSS}; white-space: nowrap; ` const nudge: BasicNumberInputNudgeFn = ({deltaX}) => deltaX * 0.25 const MarkerEditorPopover: React.FC<{ layoutP: Pointer marker: StudioHistoricStateSequenceEditorMarker /** * Called when user hits enter/escape */ onRequestClose: (reason: string) => void }> = ({layoutP, marker}) => { const sheet = useVal(layoutP.sheet) const fns = useMemo(() => { let tempTransaction: CommitOrDiscardOrRecapture | undefined return { temporarilySetValue(newLabel: string): void { if (tempTransaction) { tempTransaction.discard() tempTransaction = undefined } tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { stateEditors.studio.historic.projects.stateByProjectId.stateBySheetId.sequenceEditor.updateMarker( { sheetAddress: sheet.address, markerId: marker.id, label: newLabel, }, ) }) }, discardTemporaryValue(): void { if (tempTransaction) { tempTransaction.discard() tempTransaction = undefined } }, permanentlySetValue(newLabel: string): void { if (tempTransaction) { tempTransaction.discard() tempTransaction = undefined } getStudio()!.transaction(({stateEditors}) => { stateEditors.studio.historic.projects.stateByProjectId.stateBySheetId.sequenceEditor.updateMarker( { sheetAddress: sheet.address, markerId: marker.id, label: newLabel, }, ) }) }, } }, [layoutP, sheet]) const inputRef = useRef(null) useLayoutEffect(() => { inputRef.current!.focus() }, []) return ( true} inputRef={inputRef} /> ) } export default MarkerEditorPopover ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/Markers/Markers.tsx ================================================ import type {Pointer} from '@theatre/dataverse' import {useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import React from 'react' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import MarkerDot from './MarkerDot' const Markers: React.VFC<{layoutP: Pointer}> = ({ layoutP, }) => { const sheetAddress = useVal(layoutP.sheet.address) const markerSetP = getStudio().atomP.historic.projects.stateByProjectId[sheetAddress.projectId] .stateBySheetId[sheetAddress.sheetId].sequenceEditor.markerSet const markerAllIds = useVal(markerSetP.allIds) return ( <> {markerAllIds && Object.keys(markerAllIds).map((markerId) => ( ))} ) } export default Markers ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/Playhead.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import RoomToClick from '@theatre/studio/uiComponents/RoomToClick' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {usePrism, useVal} from '@theatre/react' import type {$IntentionalAny} from '@theatre/core/types/public' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import clamp from 'lodash-es/clamp' import React, {useState} from 'react' import styled from 'styled-components' import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel' import { includeLockFrameStampAttrs, useLockFrameStampPosition, } from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover' import PlayheadPositionPopover from './PlayheadPositionPopover' import {getIsPlayheadAttachedToFocusRange} from '@theatre/studio/UIRoot/useKeyboardShortcuts' import { lockedCursorCssVarName, useCssCursorLock, } from '@theatre/studio/uiComponents/PointerEventsHandler' import getStudio from '@theatre/studio/getStudio' import DopeSnap from './DopeSnap' import { snapToAll, snapToNone, } from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget' import useChordial from '@theatre/studio/uiComponents/chordial/useChodrial' import {mergeRefs} from 'react-merge-refs' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import {generateSequenceMarkerId} from '@theatre/studio/utils/ids' const Container = styled.div<{isVisible: boolean}>` --thumbColor: #00e0ff; position: absolute; top: 0; left: 0; width: 5px; height: 100%; z-index: ${() => zIndexes.playhead}; pointer-events: none; display: ${(props) => (props.isVisible ? 'block' : 'none')}; ` const Rod = styled.div` position: absolute; top: 8px; width: 0; height: calc(100% - 8px); border-left: 1px solid #27e0fd; z-index: 10; pointer-events: none; #pointer-root.draggingPositionInSequenceEditor &:not(.seeking) { /* pointer-events: auto; */ /* cursor: var(${lockedCursorCssVarName}); */ &:after { position: absolute; inset: -8px; display: block; content: ' '; } } ` const Thumb = styled.div` background-color: var(--thumbColor); position: absolute; width: 5px; height: 13px; top: -4px; left: -2px; z-index: 11; cursor: ew-resize; --sunblock-color: #1f2b2b; ${pointerEventsAutoInNormalMode}; ${Container}.seeking > &, ${Container}.popoverOpen > & { pointer-events: none !important; } #pointer-root.draggingPositionInSequenceEditor ${Container}:not(.seeking) > & { pointer-events: auto; cursor: var(${lockedCursorCssVarName}); } ${Container}.playheadattachedtofocusrange > & { top: -8px; --sunblock-color: #005662; &:before, &:after { border-bottom-width: 8px; } } &:before { position: absolute; display: block; content: ' '; left: -2px; width: 0; height: 0; border-bottom: 4px solid var(--sunblock-color); border-left: 2px solid transparent; } &:after { position: absolute; display: block; content: ' '; right: -2px; width: 0; height: 0; border-bottom: 4px solid var(--sunblock-color); border-right: 2px solid transparent; } ` const Squinch = styled.div` position: absolute; left: 1px; right: 1px; top: 13px; border-top: 3px solid var(--thumbColor); border-right: 1px solid transparent; border-left: 1px solid transparent; pointer-events: none; /* ${Container}.playheadattachedtofocusrange & { top: 10px; &:before, &:after { height: 15px; } } */ &:before { position: absolute; display: block; content: ' '; top: -4px; left: -2px; height: 8px; width: 2px; background: none; border-radius: 0 100% 0 0; border-top: 1px solid var(--thumbColor); border-right: 1px solid var(--thumbColor); } &:after { position: absolute; display: block; content: ' '; top: -4px; right: -2px; height: 8px; width: 2px; background: none; border-radius: 100% 0 0 0; border-top: 1px solid var(--thumbColor); border-left: 1px solid var(--thumbColor); } ` const Playhead: React.FC<{layoutP: Pointer}> = ({ layoutP, }) => { const [thumbRef, thumbNode] = useRefAndState(null) const { isVisible, posInClippedSpace, isSeeking, isPlayheadAttachedToFocusRange, posInUnitSpace, sequence, } = usePrism(() => { const isSeeking = val(layoutP.seeker.isSeeking) const sequence = val(layoutP.sheet).getSequence() const isPlayheadAttachedToFocusRange = val( getIsPlayheadAttachedToFocusRange(sequence), ) const posInUnitSpace = sequence.positionPrism.getValue() const posInClippedSpace = val(layoutP.clippedSpace.fromUnitSpace)( posInUnitSpace, ) const isVisible = posInClippedSpace >= 0 && posInClippedSpace <= val(layoutP.clippedSpace.width) return { isVisible, posInClippedSpace, isSeeking, isPlayheadAttachedToFocusRange, posInUnitSpace, sequence, } }, [layoutP]) const [isDragging, setIsDragging] = useState(false) const c = useChordial(() => { return { title: sequence.positionFormatter.formatForPlayhead( sequence.closestGridPosition(posInUnitSpace), ), menuTitle: 'Playhead', invoke: (e) => { if (e?.type === 'MouseEvent') { popover.open(e.event, thumbRef.current!) } }, items: [ { type: 'normal', label: 'Place marker', callback: () => { getStudio().transaction(({stateEditors}) => { // only retrieve val on callback to reduce unnecessary work on every use const sheet = val(layoutP.sheet) const sheetSequence = sheet.getSequence() stateEditors.studio.historic.projects.stateByProjectId.stateBySheetId.sequenceEditor.replaceMarkers( { sheetAddress: sheet.address, markers: [ { id: generateSequenceMarkerId(), position: sheetSequence.position, }, ], snappingFunction: sheetSequence.closestGridPosition, }, ) }) }, }, ], drag: { debugName: 'RightOverlay/Playhead', onDragStart() { const sequence = val(layoutP.sheet).getSequence() const posBeforeSeek = sequence.position const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) const setIsSeeking = val(layoutP.seeker.setIsSeeking) setIsSeeking(true) setIsDragging(true) snapToAll() return { onDrag(dx, _, event) { const deltaPos = scaledSpaceToUnitSpace(dx) sequence.position = DopeSnap.checkIfMouseEventSnapToPos(event, { ignore: thumbNode, }) ?? // unsnapped clamp(posBeforeSeek + deltaPos, 0, sequence.length) }, onDragEnd(dragHappened) { setIsSeeking(false) setIsDragging(false) snapToNone() }, } }, }, } }) useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize') // hide the frame stamp when seeking useLockFrameStampPosition(useVal(layoutP.seeker.isSeeking) || isDragging, -1) const popover = usePopover({debugName: 'Playhead'}, () => { return ( ) }) c.useDisableTooltip(popover.isOpen) return ( <> {popover.node} ) } export default Playhead ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/PlayheadPositionPopover.tsx ================================================ import styled from 'styled-components' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {usePrism} from '@theatre/react' import type {BasicNumberInputNudgeFn} from '@theatre/studio/uiComponents/form/BasicNumberInput' import BasicNumberInput from '@theatre/studio/uiComponents/form/BasicNumberInput' import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS' import {useLayoutEffect, useMemo, useRef} from 'react' import React from 'react' import {val} from '@theatre/dataverse' import type {Pointer} from '@theatre/dataverse' import clamp from 'lodash-es/clamp' const greaterThanOrEqualToZero = (v: number) => isFinite(v) && v >= 0 const Container = styled.div` display: flex; gap: 8px; height: 28px; align-items: center; ` const Label = styled.div` ${propNameTextCSS}; white-space: nowrap; ` const nudge: BasicNumberInputNudgeFn = ({deltaX}) => deltaX * 0.25 const PlayheadPositionPopover: React.FC<{ layoutP: Pointer /** * Called when user hits enter/escape */ onRequestClose: (reason: string) => void }> = ({layoutP, onRequestClose}) => { const sheet = val(layoutP.sheet) const sequence = sheet.getSequence() const fns = useMemo(() => { let tempPosition: number | undefined const originalPosition = sequence.position return { temporarilySetValue(newPosition: number): void { if (tempPosition) { tempPosition = undefined } tempPosition = clamp(newPosition, 0, sequence.length) sequence.position = tempPosition }, discardTemporaryValue(): void { if (tempPosition) { tempPosition = undefined sequence.position = originalPosition onRequestClose('discardTemporaryValue') } }, permanentlySetValue(newPosition: number): void { if (tempPosition) { tempPosition = undefined } sequence.position = clamp(newPosition, 0, sequence.length) onRequestClose('permanentlySetValue') }, } }, [layoutP, sequence]) const inputRef = useRef(null) useLayoutEffect(() => { inputRef.current!.focus() }, []) return usePrism(() => { const sequence = sheet.getSequence() const value = Number(val(sequence.pointer.position).toFixed(3)) return ( ) }, [sheet, fns, inputRef]) } export default PlayheadPositionPopover ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/RightOverlay.tsx ================================================ import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel' import {usePrism} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import LengthIndicator from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthIndicator' import FrameStamp from './FrameStamp' import HorizontalScrollbar from './HorizontalScrollbar' import Playhead from './Playhead' import TopStrip from './TopStrip' import FocusRangeCurtains from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/FocusRangeCurtains' import Markers from './Markers/Markers' const Container = styled.div` position: absolute; top: 0; right: 0; bottom: 0; z-index: ${() => zIndexes.rightOverlay}; overflow: visible; pointer-events: none; ` const RightOverlay: React.FC<{ layoutP: Pointer }> = ({layoutP}) => { return usePrism(() => { const width = val(layoutP.rightDims.width) return ( ) }, [layoutP]) } export default RightOverlay ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/RightOverlay/TopStrip.tsx ================================================ import {useVal} from '@theatre/react' import type {Pointer} from '@theatre/dataverse' import React from 'react' import styled from 'styled-components' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import StampsGrid from '@theatre/studio/panels/SequenceEditorPanel/FrameGrid/StampsGrid' import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import FocusRangeZone from './FocusRangeZone/FocusRangeZone' export const topStripHeight = 18 export const topStripTheme = { backgroundColor: `#1f2120eb`, borderColor: `#1c1e21`, } const Container = styled.div` position: absolute; top: 0; left: 0; right: 0; height: ${topStripHeight}px; box-sizing: border-box; background: ${topStripTheme.backgroundColor}; border-bottom: 1px solid ${topStripTheme.borderColor}; ${pointerEventsAutoInNormalMode}; ` const TopStrip: React.FC<{layoutP: Pointer}> = ({ layoutP, }) => { const width = useVal(layoutP.rightDims.width) return ( <> ) } export default TopStrip ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/SequenceEditorPanel.tsx ================================================ import {outlineSelection} from '@theatre/studio/selectors' import {usePrism} from '@theatre/react' import {valToAtom} from '@theatre/utils/valToAtom' import type {Pointer} from '@theatre/dataverse' import {prism, val} from '@theatre/dataverse' import React, {useState} from 'react' import styled from 'styled-components' import DopeSheet from './DopeSheet/DopeSheet' import GraphEditor from './GraphEditor/GraphEditor' import type {PanelDims, SequenceEditorPanelLayout} from './layout/layout' import {sequenceEditorPanelLayout} from './layout/layout' import RightOverlay from './RightOverlay/RightOverlay' import BasePanel, {usePanel} from '@theatre/studio/panels/BasePanel/BasePanel' import type {PanelPosition} from '@theatre/core/types/private' import PanelDragZone from '@theatre/studio/panels/BasePanel/PanelDragZone' import PanelWrapper from '@theatre/studio/panels/BasePanel/PanelWrapper' import FrameStampPositionProvider from './FrameStampPositionProvider' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type Sheet from '@theatre/core/sheets/Sheet' import {uniq} from 'lodash-es' import GraphEditorToggle from './GraphEditorToggle' import { panelZIndexes, TitleBar, TitleBar_Piece, TitleBar_Punctuation, } from '@theatre/studio/panels/BasePanel/common' import {usePresenceListenersOnRootElement} from '@theatre/studio/uiComponents/usePresence' import type {UIPanelId} from '@theatre/core/types/private' import {__private} from '@theatre/core' const {isSheet, isSheetObject} = __private.instanceTypes const Container = styled(PanelWrapper)` z-index: ${panelZIndexes.sequenceEditorPanel}; box-shadow: 2px 2px 0 rgb(0 0 0 / 11%); ` const LeftBackground = styled.div` background-color: rgba(40, 43, 47, 0.99); position: absolute; left: 0; top: 0; bottom: 0; z-index: -1; pointer-events: none; ` export const zIndexes = (() => { const s = { rightBackground: 0, scrollableArea: 0, rightOverlay: 0, lengthIndicatorCover: 0, lengthIndicatorStrip: 0, playhead: 0, currentFrameStamp: 0, marker: 0, horizontalScrollbar: 0, } // sort the z-indexes let i = -1 for (const key of Object.keys(s)) { s[key] = i i++ } return s })() const Header_Container = styled(PanelDragZone)` position: absolute; left: 0; top: 0; z-index: 1; ` const defaultPosition: PanelPosition = { edges: { left: {from: 'screenLeft', distance: 0.1}, right: {from: 'screenRight', distance: 0.2}, top: {from: 'screenBottom', distance: 0.4}, bottom: {from: 'screenBottom', distance: 0.01}, }, } const minDims = {width: 800, height: 200} const SequenceEditorPanel: React.VFC<{}> = (props) => { return ( ) } const Content: React.VFC<{}> = () => { const {dims} = usePanel() const [containerNode, setContainerNode] = useState( null, ) usePresenceListenersOnRootElement(containerNode) return usePrism(() => { const panelSize = prism.memo( 'panelSize', (): PanelDims => { const width = dims.width const height = dims.height return { width: width, height: height, widthWithoutBorder: width - 2, heightWithoutBorder: height - 4, screenX: dims.left, screenY: dims.top, } }, [dims], ) const selectedSheets = uniq( outlineSelection .getValue() .filter((s): s is SheetObject | Sheet => isSheet(s) || isSheetObject(s)) .map((s) => (isSheetObject(s) ? s.sheet : s)), ) const selectedTemplates = uniq(selectedSheets.map((s) => s.template)) if (selectedTemplates.length !== 1) return <> const sheet = selectedSheets[0] if (!sheet) return <> const panelSizeP = valToAtom('panelSizeP', panelSize).pointer // We make a unique key based on the sheet's address, so that // and // don't have to listen to changes in sheet const key = prism.memo('key', () => JSON.stringify(sheet.address), [sheet]) const layoutP = prism .memo( 'layout', () => { return sequenceEditorPanelLayout(sheet, panelSizeP) }, [sheet, panelSizeP], ) .getValue() if (val(layoutP.tree.children).length === 0) return <> const containerRef = prism.memo( 'containerRef', preventHorizontalWheelEvents, [], ) const graphEditorAvailable = val(layoutP.graphEditorDims.isAvailable) const graphEditorOpen = val(layoutP.graphEditorDims.isOpen) return ( { containerRef(elt as HTMLDivElement) if (elt !== containerNode) { setContainerNode(elt as HTMLDivElement) } }} >
{graphEditorOpen && ( )} {graphEditorAvailable && } ) }, [dims, containerNode]) } const Header: React.FC<{layoutP: Pointer}> = ({ layoutP, }) => { return usePrism(() => { const sheet = val(layoutP.sheet) return ( {sheet.address.sheetId} {':'}  {sheet.address.sheetInstanceId}  {'>'}  Sequence ) }, [layoutP]) } export default SequenceEditorPanel const preventHorizontalWheelEvents = () => { let lastNode: null | HTMLElement = null const listenerOptions = { passive: false, capture: false, } const receiveWheelEvent = (event: WheelEvent) => { if (Math.abs(event.deltaY) < Math.abs(event.deltaX)) { event.preventDefault() event.stopPropagation() } } return (node: HTMLElement | null) => { if (lastNode !== node && lastNode) { lastNode.removeEventListener('wheel', receiveWheelEvent, listenerOptions) } lastNode = node if (node) { node.addEventListener('wheel', receiveWheelEvent, listenerOptions) } } } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/VerticalScrollContainer.tsx ================================================ import noop from '@theatre/utils/noop' import React, {createContext, useCallback, useContext, useRef} from 'react' import styled from 'styled-components' import {zIndexes} from './SequenceEditorPanel' const Container = styled.div` position: absolute; top: 0; right: 0; left: 0; bottom: 0; overflow-x: hidden; overflow-y: scroll; z-index: ${() => zIndexes.scrollableArea}; &::-webkit-scrollbar { display: none; } scrollbar-width: none; ` type ReceiveVerticalWheelEventFn = (ev: Pick) => void const ctx = createContext(noop) /** * See {@link VerticalScrollContainer} and references for how to use this. */ export const useReceiveVerticalWheelEvent = (): ReceiveVerticalWheelEventFn => useContext(ctx) /** * This is used in the sequence editor where we block wheel events to handle * pan/zoom on the time axis. The issue this solves, is that when blocking those * wheel events, we prevent the vertical scroll events from being fired. This container * comes with a context and a hook (see {@link useReceiveVerticalWheelEvent}) that allows * the code that traps the wheel events to pass them to the vertical scroller root, which * we then use to manually dispatch scroll events. */ const VerticalScrollContainer: React.FC<{ children: React.ReactNode }> = (props) => { const ref = useRef(null) const receiveVerticalWheelEvent = useCallback( (event) => { ref.current!.scrollBy(0, event.deltaY) }, [], ) return ( {props.children} ) } export default VerticalScrollContainer ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/layout/layout.ts ================================================ import type Sheet from '@theatre/core/sheets/Sheet' import getStudio from '@theatre/studio/getStudio' import type useDrag from '@theatre/studio/uiComponents/useDrag' import type {SheetAddress} from '@theatre/core/types/public' import subPrism from '@theatre/utils/subPrism' import type { IRange, PositionInScreenSpace, StrictRecord, } from '@theatre/core/types/public' import {valToAtom} from '@theatre/utils/valToAtom' import type {Prism, Pointer} from '@theatre/dataverse' import {Atom, prism, val} from '@theatre/dataverse' import type {SequenceEditorTree} from './tree' import {calculateSequenceEditorTree} from './tree' import {clamp} from 'lodash-es' import type { KeyframeId, ObjectAddressKey, SequenceTrackId, } from '@theatre/core/types/public' // A Side is either the left side of the panel or the right side type DimsOfPanelPart = { width: number height: number /** * In absolute pixels, relative to getBoundingClientRect() */ screenX: PositionInScreenSpace /** * In absolute pixels, relative to getBoundingClientRect() */ screenY: PositionInScreenSpace } export type PanelDims = { width: number height: number widthWithoutBorder: number heightWithoutBorder: number screenX: PositionInScreenSpace screenY: PositionInScreenSpace } export type DopeSheetSelection = { type: 'DopeSheetSelection' byObjectKey: StrictRecord< ObjectAddressKey, { byTrackId: StrictRecord< SequenceTrackId, { byKeyframeId: StrictRecord } > } > getDragHandlers( origin: SheetAddress & { positionAtStartOfDrag: number domNode: Element }, ): Omit[1], 'onClick'> delete(): void } /** * @remarks * In order to lay out the playhead and the keyframes on the sequence editor, * we map their position to different spaces based on the zoom and scroll. * * These spaces are called `unitSpace`, `scaledSpace`, and `clippedSpace`. * * * `unitSpace` is the space the sequence is in. So, `5 seconds` in unitSpace * would equal `5`. * * * `scaledSpace` basically takes into account zoom level, but not scroll. * * * `clippedSpace` is just like `scaledSpace`, but also accounts for scroll. * * * 2 seconds ─┐ # * ▼ # 2 seconds would represent as `2` in unitSpace * `unitSpace` 00 01 02 03 04 05 # regardless of zoom or scroll. * | # * ─────────────────────────────────────┼─────────────────────────────────────────────────────────────────────── * | # * ▼ # If zoom=1, then scaledSpace acts the same as * `scaledSpace` 00 01 02 03 04 05 # unitSpace. * (zoom=1) | # * ─────────────────────────────────────┼─────────────────────────────────────────────────────────────────────── * ┌─────┘ # * ▼ | # We're zoomed out (zoom=0.5), so 2 seconds * `scaledSpace` 00 01 02 03 04 05 06 07 08 09 10 # falls on the position of 1 second (2 * 0.5 = 1). * (zoom=0.5) | | # * ───────────────────────────────┼─────┼─────────────────────────────────────────────────────────────────────── * └─────┼───────────┐ # * | ▼ # We're zoomed in (zoom=2), so 2 seconds * `scaledSpace` 00 01 02 # falls on the position of 4 seconds (2 * 2 = 4). * (zoom=2) | | # * ─────────────────────────────────────┼───────────┼─────────────────────────────────────────────────────────── * ┌───────────┘ # * │ # With no zoom or scroll, clippedSpace is * `clippedSpace` ▼ # just like unitSpace. * (zoom=1, scroll=0) 00 01 02 03 04 05 # * ─────────────────────────────────────┼─────────────────────────────────────────────────────────────────────── * | # * ┌─────┘ # No zoom, but we're scrolled in by 1 seconds, * `clippedSpace` ▼ | # so everything shifts 1s back. * (zoom=1, scroll=1) 01 02 03 04 05 06 # * ────────────────────────────────┼────┼─────────────────────────────────────────────────────────────────────── * └────┐ # * ▼ # Zoomed in 2x with 1s of scroll. * `clippedSpace` (zoom=2, 01 02 03 # * scroll=1) # * ───────────────────────────────────────────────────────────────────────────────────────────────────────────── * */ export type SequenceEditorPanelLayout = { sheet: Sheet tree: SequenceEditorTree panelDims: PanelDims leftDims: DimsOfPanelPart rightDims: DimsOfPanelPart dopeSheetDims: DimsOfPanelPart graphEditorDims: DimsOfPanelPart & { isAvailable: boolean isOpen: boolean padding: {top: number; bottom: number} } horizontalScrollbarDims: {bottom: number} graphEditorVerticalSpace: { space: number fromExtremumSpace(e: number): number toExtremumSpace(e: number): number } seeker: { isSeeking: boolean setIsSeeking: (isSeeking: boolean) => void } unitSpace: {} scaledSpace: { /** * TODO - scaledSpace with and without leftPadding are two different spaces. See if we can divide them so */ leftPadding: number fromUnitSpace(u: number): number toUnitSpace(s: number): number } clippedSpace: { /** * The width of the visible area of the sequence (pretty much the right side of the panel) */ width: number fromUnitSpace(u: number): number toUnitSpace(c: number): number range: IRange setRange(range: IRange): void } selectionAtom: Atom<{current?: DopeSheetSelection}> } // type UnitSpaceProression = number // type ClippedSpaceProgression = number /** * This means the left side of the panel is 20% of its width, and the * right side is 80% */ const panelSplitRatio = 0.2 const initialClippedSpaceRange: IRange = [0, 10] export function sequenceEditorPanelLayout( sheet: Sheet, panelDimsP: Pointer, ): Prism> { const studio = getStudio()! const ahistoricStateP = studio.atomP.ahistoric.projects.stateByProjectId[sheet.address.projectId] .stateBySheetId[sheet.address.sheetId] const historicStateP = studio.atomP.historic.projects.stateByProjectId[sheet.address.projectId] .stateBySheetId[sheet.address.sheetId] return prism(() => { const tree = subPrism( 'tree', () => calculateSequenceEditorTree(sheet, studio), [], ) const panelDims = val(panelDimsP) const graphEditorState = val( studio.atomP.historic.panels.sequenceEditor.graphEditor, ) const selectedPropsByObject = val( historicStateP.sequenceEditor.selectedPropsByObject, ) const graphEditorAvailable = !!selectedPropsByObject && Object.keys(selectedPropsByObject).length > 0 const { leftDims, rightDims, graphEditorDims, dopeSheetDims, horizontalScrollbarDims, } = prism.memo( 'leftDims', () => { const leftDims: DimsOfPanelPart = { width: Math.floor(panelDims.width * panelSplitRatio), height: panelDims.height, screenX: panelDims.screenX, screenY: panelDims.screenY, } const rightDims: DimsOfPanelPart = { width: panelDims.width - leftDims.width, height: panelDims.height, screenX: (panelDims.screenX + leftDims.width) as PositionInScreenSpace, screenY: panelDims.screenY, } const graphEditorOpen = graphEditorAvailable && graphEditorState?.isOpen === true const graphEditorHeight = Math.floor( (graphEditorOpen ? clamp(graphEditorState?.height ?? 0.5, 0.1, 0.7) : 0) * panelDims.heightWithoutBorder, ) const bottomHeight = 0 + graphEditorHeight const dopeSheetHeight = panelDims.height - bottomHeight const dopeSheetDims: SequenceEditorPanelLayout['dopeSheetDims'] = { width: panelDims.width, height: dopeSheetHeight, screenX: panelDims.screenX, screenY: panelDims.screenY, } // const graphEditorHeight = panelDims.height - dopeSheetDims.height const graphEditorDims: SequenceEditorPanelLayout['graphEditorDims'] = { isAvailable: graphEditorAvailable, isOpen: graphEditorOpen, width: rightDims.width, height: graphEditorHeight, screenX: panelDims.screenX, screenY: panelDims.screenY + dopeSheetHeight, padding: { top: 20, bottom: 20, }, } const horizontalScrollbarDims: SequenceEditorPanelLayout['horizontalScrollbarDims'] = { bottom: graphEditorOpen ? 0 : 0, } return { leftDims, rightDims, graphEditorDims, dopeSheetDims, horizontalScrollbarDims, } }, [panelDims, graphEditorState, graphEditorAvailable], ) const graphEditorVerticalSpace = prism.memo( 'graphEditorVerticalSpace', (): SequenceEditorPanelLayout['graphEditorVerticalSpace'] => { const space = graphEditorDims.height - graphEditorDims.padding.top - graphEditorDims.padding.bottom return { space, fromExtremumSpace(ex: number): number { return ex * space }, toExtremumSpace(s: number): number { return s / space }, } }, [graphEditorDims], ) const [isSeeking, setIsSeeking] = prism.state('isSeeking', false) const seeker = { isSeeking, setIsSeeking, } const unitSpace = {} const clippedSpaceRange = val(ahistoricStateP.sequence.clippedSpaceRange) ?? initialClippedSpaceRange const scaledSpace: SequenceEditorPanelLayout['scaledSpace'] = prism.memo( 'scaledSpace', () => { const unitsShownInClippedSpace = clippedSpaceRange[1] - clippedSpaceRange[0] const pixelsShownInClippedSpace = rightDims.width const unitToPixelRatio = unitsShownInClippedSpace / pixelsShownInClippedSpace const pixelToUnitRatio = pixelsShownInClippedSpace / unitsShownInClippedSpace return { fromUnitSpace(u: number): number { return u * pixelToUnitRatio }, toUnitSpace(s: number): number { return s * unitToPixelRatio }, leftPadding: 10, } }, [clippedSpaceRange, rightDims.width], ) const setClippedSpaceRange = prism.memo( 'setClippedSpaceRange', () => { return function setClippedSpaceRange(_range: IRange): void { studio.transaction(({stateEditors}) => { const range: IRange = [..._range] if (range[1] <= range[0]) { range[1] = range[0] + 1 } if (range[0] < 0) { const length = range[1] - range[0] range[0] = 0 range[1] = length } stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.clippedSpaceRange.set( {...sheet.address, range}, ) }) } }, [], ) const clippedSpace: SequenceEditorPanelLayout['clippedSpace'] = prism.memo( 'clippedSpace', () => { return { range: clippedSpaceRange, width: rightDims.width, fromUnitSpace(u: number): number { return ( scaledSpace.fromUnitSpace(u - clippedSpaceRange[0]) + scaledSpace.leftPadding ) }, toUnitSpace(c: number): number { return ( scaledSpace.toUnitSpace(c - scaledSpace.leftPadding) + clippedSpaceRange[0] ) }, setRange: setClippedSpaceRange, } }, [clippedSpaceRange, rightDims.width, scaledSpace, setClippedSpaceRange], ) const selectionAtom = prism.memo( 'selection.current', (): SequenceEditorPanelLayout['selectionAtom'] => { return new Atom({}) }, [], ) const finalAtom = valToAtom('finalAtom', { sheet, tree, panelDims, leftDims, rightDims, dopeSheetDims, horizontalScrollbarDims, seeker, unitSpace, scaledSpace, clippedSpace, graphEditorDims, graphEditorVerticalSpace, selectionAtom, }) return finalAtom.pointer }) } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/layout/tree.ts ================================================ import type { PropTypeConfig, PropTypeConfig_AllSimples, PropTypeConfig_Compound, UnknownValidCompoundProps, } from '@theatre/core/types/public' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type {IPropPathToTrackIdTree} from '@theatre/core/sheetObjects/SheetObjectTemplate' import type Sheet from '@theatre/core/sheets/Sheet' import type {PathToProp} from '@theatre/utils/pathToProp' import type {StudioSheetItemKey} from '@theatre/core/types/private' import {createStudioSheetItemKey} from '@theatre/studio/utils/createStudioSheetItemKey' import type {$FixMe, $IntentionalAny} from '@theatre/core/types/public' import {prism, val, pointerToPrism} from '@theatre/dataverse' import logger from '@theatre/utils/logger' import {titleBarHeight} from '@theatre/studio/panels/BasePanel/common' import type {Studio} from '@theatre/studio/Studio' import type {SequenceTrackId} from '@theatre/core/types/public' /** * Base "view model" for each row with common * required information such as row heights & depth. */ export type SequenceEditorTree_Row = { /** type of this row, e.g. `"sheet"` or `"sheetObject"` */ type: TypeName /** Height of just the row in pixels */ nodeHeight: number /** Height of the row + height with children in pixels */ heightIncludingChildren: number /** Visual indentation */ depth: number /** A convenient studio sheet localized identifier for managing presence and ephemeral visual effects. */ sheetItemKey: StudioSheetItemKey /** * This is a part of the tree, but it is not rendered at all, * and it doesn't contribute to height. * * In the future, if we have a filtering mechanism like "show only position props", * this would not be the place to make false, that node should just not be included * in the tree at all, so it doesn't affect aggregate keyframes. */ shouldRender: boolean /** * Distance in pixels from the top of this row to the row container's top * This can be used to help figure out what's being box selected (marquee). */ top: number /** Row number (e.g. for correctly styling even / odd alternating styles) */ n: number } export type SequenceEditorTree = SequenceEditorTree_Sheet export type SequenceEditorTree_Sheet = SequenceEditorTree_Row<'sheet'> & { sheet: Sheet isCollapsed: boolean children: SequenceEditorTree_SheetObject[] } export type SequenceEditorTree_SheetObject = SequenceEditorTree_Row<'sheetObject'> & { isCollapsed: boolean sheetObject: SheetObject children: Array< SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp > } export type SequenceEditorTree_PropWithChildren = SequenceEditorTree_Row<'propWithChildren'> & { isCollapsed: boolean sheetObject: SheetObject propConf: PropTypeConfig_Compound pathToProp: PathToProp children: Array< SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp > trackMapping: IPropPathToTrackIdTree } export type SequenceEditorTree_PrimitiveProp = SequenceEditorTree_Row<'primitiveProp'> & { sheetObject: SheetObject pathToProp: PathToProp trackId: SequenceTrackId propConf: PropTypeConfig_AllSimples } export type SequenceEditorTree_AllRowTypes = | SequenceEditorTree_Sheet | SequenceEditorTree_SheetObject | SequenceEditorTree_PropWithChildren | SequenceEditorTree_PrimitiveProp const HEIGHT_OF_ANY_TITLE = 28 /** * Must run inside prism() */ export const calculateSequenceEditorTree = ( sheet: Sheet, studio: Studio, ): SequenceEditorTree => { prism.ensurePrism() const rootShouldRender = true let topSoFar = titleBarHeight + (rootShouldRender ? HEIGHT_OF_ANY_TITLE : 0) let nSoFar = 0 const collapsableItemSetP = studio.atomP.ahistoric.projects.stateByProjectId[sheet.address.projectId] .stateBySheetId[sheet.address.sheetId].sequence.collapsableItems const isCollapsedP = collapsableItemSetP.byId[createStudioSheetItemKey.forSheet()].isCollapsed const isCollapsed = pointerToPrism(isCollapsedP).getValue() ?? false const tree: SequenceEditorTree = { type: 'sheet', isCollapsed, sheet, children: [], sheetItemKey: createStudioSheetItemKey.forSheet(), shouldRender: rootShouldRender, top: titleBarHeight, depth: 0, n: nSoFar, nodeHeight: rootShouldRender ? HEIGHT_OF_ANY_TITLE : 0, heightIncludingChildren: -1, // calculated below } if (rootShouldRender) { nSoFar += 1 } for (const sheetObject of Object.values(val(sheet.objectsP))) { if (sheetObject) { addObject( sheetObject, tree.children, tree.depth + 1, rootShouldRender && !isCollapsed, ) } } tree.heightIncludingChildren = topSoFar - tree.top function addObject( sheetObject: SheetObject, arrayOfChildren: Array, level: number, shouldRender: boolean, ) { const trackSetups = val( sheetObject.template.getMapOfValidSequenceTracks_forStudio(), ) const objectConfig = val(sheetObject.template.configPointer) if (Object.keys(trackSetups).length === 0) return const isCollapsedP = collapsableItemSetP.byId[ createStudioSheetItemKey.forSheetObject(sheetObject) ].isCollapsed const isCollapsed = pointerToPrism(isCollapsedP).getValue() ?? false const row: SequenceEditorTree_SheetObject = { type: 'sheetObject', isCollapsed, sheetItemKey: createStudioSheetItemKey.forSheetObject(sheetObject), shouldRender, top: topSoFar, children: [], depth: level, n: nSoFar, sheetObject: sheetObject, nodeHeight: shouldRender ? HEIGHT_OF_ANY_TITLE : 0, heightIncludingChildren: -1, // calculated below } arrayOfChildren.push(row) if (shouldRender) { nSoFar += 1 // As we add rows to the tree, top to bottom, we accumulate the pixel // distance to the top of the tree from the bottom of the current row: topSoFar += row.nodeHeight } addProps( sheetObject, trackSetups, [], objectConfig, row.children, level + 1, shouldRender && !isCollapsed, ) row.heightIncludingChildren = topSoFar - row.top } function addProps( sheetObject: SheetObject, trackSetups: IPropPathToTrackIdTree, pathSoFar: PathToProp, parentPropConfig: PropTypeConfig_Compound<$IntentionalAny>, arrayOfChildren: Array< SequenceEditorTree_PrimitiveProp | SequenceEditorTree_PropWithChildren >, level: number, shouldRender: boolean, ) { for (const [propKey, setupOrSetups] of Object.entries(trackSetups)) { const propConfig = parentPropConfig.props[propKey] addProp( sheetObject, setupOrSetups!, [...pathSoFar, propKey], propConfig, arrayOfChildren, level, shouldRender, ) } } function addProp( sheetObject: SheetObject, trackIdOrMapping: SequenceTrackId | IPropPathToTrackIdTree, pathToProp: PathToProp, conf: PropTypeConfig, arrayOfChildren: Array< SequenceEditorTree_PrimitiveProp | SequenceEditorTree_PropWithChildren >, level: number, shouldRender: boolean, ) { if (conf.type === 'compound') { const trackMapping = trackIdOrMapping as $IntentionalAny as IPropPathToTrackIdTree addProp_compound( sheetObject, trackMapping, conf, pathToProp, conf, arrayOfChildren, level, shouldRender, ) } else if (conf.type === 'enum') { logger.warn('Prop type enum is not yet supported in the sequence editor') } else { const trackId = trackIdOrMapping as $IntentionalAny as SequenceTrackId addProp_primitive( sheetObject, trackId, pathToProp, conf, arrayOfChildren, level, shouldRender, ) } } function addProp_compound( sheetObject: SheetObject, trackMapping: IPropPathToTrackIdTree, propConf: PropTypeConfig_Compound, pathToProp: PathToProp, conf: PropTypeConfig_Compound<$FixMe>, arrayOfChildren: Array< SequenceEditorTree_PrimitiveProp | SequenceEditorTree_PropWithChildren >, level: number, shouldRender: boolean, ) { const isCollapsedP = collapsableItemSetP.byId[ createStudioSheetItemKey.forSheetObjectProp(sheetObject, pathToProp) ].isCollapsed const isCollapsed = pointerToPrism(isCollapsedP).getValue() ?? false const row: SequenceEditorTree_PropWithChildren = { type: 'propWithChildren', isCollapsed, propConf, pathToProp, sheetItemKey: createStudioSheetItemKey.forSheetObjectProp( sheetObject, pathToProp, ), sheetObject: sheetObject, shouldRender, top: topSoFar, children: [], nodeHeight: shouldRender ? HEIGHT_OF_ANY_TITLE : 0, heightIncludingChildren: -1, depth: level, trackMapping, n: nSoFar, } arrayOfChildren.push(row) if (shouldRender) { topSoFar += row.nodeHeight nSoFar += 1 } addProps( sheetObject, trackMapping, pathToProp, conf, row.children, level + 1, // collapsed shouldn't render child props shouldRender && !isCollapsed, ) // } row.heightIncludingChildren = topSoFar - row.top } function addProp_primitive( sheetObject: SheetObject, trackId: SequenceTrackId, pathToProp: PathToProp, propConf: PropTypeConfig_AllSimples, arrayOfChildren: Array< SequenceEditorTree_PrimitiveProp | SequenceEditorTree_PropWithChildren >, level: number, shouldRender: boolean, ) { const row: SequenceEditorTree_PrimitiveProp = { type: 'primitiveProp', propConf: propConf, depth: level, sheetItemKey: createStudioSheetItemKey.forSheetObjectProp( sheetObject, pathToProp, ), sheetObject: sheetObject, pathToProp, shouldRender, top: topSoFar, nodeHeight: shouldRender ? HEIGHT_OF_ANY_TITLE : 0, heightIncludingChildren: shouldRender ? HEIGHT_OF_ANY_TITLE : 0, trackId, n: nSoFar, } arrayOfChildren.push(row) nSoFar += 1 topSoFar += row.nodeHeight } return tree } ================================================ FILE: packages/studio/src/panels/SequenceEditorPanel/whatPropIsHighlighted.ts ================================================ import type {Prism} from '@theatre/dataverse' import {val} from '@theatre/dataverse' import {Atom} from '@theatre/dataverse' import {prism} from '@theatre/dataverse' import type { PropAddress, WithoutSheetInstance, } from '@theatre/core/types/public' import pointerDeep from '@theatre/utils/pointerDeep' import type {$IntentionalAny, VoidFn} from '@theatre/core/types/public' import lodashSet from 'lodash-es/set' /** constant global manager */ export const whatPropIsHighlighted = createWhatPropIsHighlightedState() export type PropHighlighted = 'self' | 'descendent' | null /** Only used in prop highlighting with boolean. */ type PathToPropAsDeepObject = { [key in string]: T | PathToPropAsDeepObject } function createWhatPropIsHighlightedState() { let lastLockId = 0 const whatIsHighlighted = new Atom< | {hasLock: false; deepPath?: undefined} | { hasLock: true lockId: number cleanup: () => void deepPath: PathToPropAsDeepObject } >({hasLock: false}) return { replaceLock(address: WithoutSheetInstance, cleanup: VoidFn) { const lockId = lastLockId++ const existingState = whatIsHighlighted.get() if (existingState.hasLock) existingState.cleanup() whatIsHighlighted.set({ hasLock: true, lockId, cleanup, deepPath: arrayToDeepObject(addressToArray(address)), }) return function unlock() { const curr = whatIsHighlighted.get() if (curr.hasLock && curr.lockId === lockId) { curr.cleanup() whatIsHighlighted.set({hasLock: false}) } } }, getIsPropHighlightedD( address: WithoutSheetInstance, ): Prism { const highlightedP = pointerDeep( whatIsHighlighted.pointer.deepPath, addressToArray(address), ) return prism(() => { const value = val(highlightedP) return value === true ? 'self' : // obj continues deep path prop from here value ? 'descendent' : // some other prop or no lock null }) }, } } function addressToArray( address: WithoutSheetInstance, ): Array { return [ address.projectId, address.sheetId, address.objectKey, ...address.pathToProp, ] } function arrayToDeepObject( arr: Array, ): PathToPropAsDeepObject { const obj = {} lodashSet(obj, arr, true) return obj as $IntentionalAny } ================================================ FILE: packages/studio/src/propEditors/DefaultValueIndicator.tsx ================================================ import {transparentize} from 'polished' import React from 'react' import styled from 'styled-components' import getStudio from '@theatre/studio/getStudio' import type {PathToProp} from '@theatre/utils/pathToProp' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type {PropTypeConfig} from '@theatre/core/types/public' import {nextPrevCursorsTheme} from './NextPrevKeyframeCursors' import {__private} from '@theatre/core' const {isPropConfigComposite, iteratePropType} = __private.propTypeUtils const theme = { defaultState: { color: transparentize(0.95, `#C4C4C4`), hoverColor: transparentize(0.15, nextPrevCursorsTheme.onColor), }, withStaticOverride: { color: transparentize(0.85, `#C4C4C4`), hoverColor: transparentize(0.15, nextPrevCursorsTheme.onColor), }, } const Container = styled.div<{ hasStaticOverride: boolean }>` width: 16px; margin: 0 0px 0 2px; display: flex; justify-content: center; align-items: center; cursor: pointer; color: ${(props) => props.hasStaticOverride ? theme.withStaticOverride.color : theme.defaultState.color}; &:hover { color: ${(props) => props.hasStaticOverride ? theme.withStaticOverride.hoverColor : theme.defaultState.hoverColor}; } ` const DefaultIcon = styled.div` width: 5px; height: 5px; border-radius: 1px; transform: rotate(45deg); /* border: 1px solid currentColor; */ background-color: currentColor; ` const FilledIcon = styled.div` width: 5px; height: 5px; background-color: currentColor; border-radius: 1px; transform: rotate(45deg); ` const DefaultOrStaticValueIndicator: React.FC<{ hasStaticOverride: boolean pathToProp: PathToProp obj: SheetObject propConfig: PropTypeConfig }> = (props) => { const {hasStaticOverride, obj, propConfig, pathToProp} = props const sequenceCb = () => { getStudio()!.transaction(({stateEditors}) => { for (const {path, conf} of iteratePropType(propConfig, pathToProp)) { if (isPropConfigComposite(conf)) continue const propAddress = {...obj.address, pathToProp: path} stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced( propAddress, ) } }) } return ( {hasStaticOverride ? ( ) : ( )} ) } export default DefaultOrStaticValueIndicator ================================================ FILE: packages/studio/src/propEditors/NextPrevKeyframeCursors.tsx ================================================ import type {VoidFn} from '@theatre/core/types/public' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {transparentize} from 'polished' import React from 'react' import styled, {css} from 'styled-components' import {PresenceFlag} from '@theatre/studio/uiComponents/usePresence' import usePresence from '@theatre/studio/uiComponents/usePresence' import type {StudioSheetItemKey} from '@theatre/core/types/private' import type {BasicKeyframe} from '@theatre/core' export type NearbyKeyframesControls = { prev?: Pick & { jump: VoidFn itemKey: StudioSheetItemKey } cur: | {type: 'on'; toggle: VoidFn; itemKey: StudioSheetItemKey} | {type: 'off'; toggle: VoidFn} next?: Pick & { jump: VoidFn itemKey: StudioSheetItemKey } } const Container = styled.div` display: flex; justify-content: center; align-items: center; width: 16px; margin: 0 0px 0 2px; position: relative; z-index: 0; opacity: 0.7; &:after { position: absolute; left: -14px; right: -14px; top: -2px; bottom: -2px; content: ' '; display: none; z-index: -1; background: ${transparentize(0.2, 'black')}; } &:hover { opacity: 1; &:after { display: block; } } ` const Button = styled.div` background: none; position: relative; border: 0; transition: transform 0.1s ease-out; z-index: 0; outline: none; cursor: pointer; &:after { display: none; ${Container}:hover & { display: block; } position: absolute; left: -4px; right: -4px; top: -4px; bottom: -4px; content: ' '; z-index: -1; } ` export const nextPrevCursorsTheme = { offColor: '#555', onColor: '#e0c917', } const CurButton = styled(Button)<{ isOn: boolean presence: PresenceFlag | undefined }>` &:hover { color: #e0c917; } color: ${(props) => props.presence === PresenceFlag.Primary ? 'white' : props.isOn ? nextPrevCursorsTheme.onColor : nextPrevCursorsTheme.offColor}; ` const pointerEventsNone = css` pointer-events: none !important; ` const PrevOrNextButton = styled(Button)<{ available: boolean flag: PresenceFlag | undefined }>` color: ${(props) => props.flag === PresenceFlag.Primary ? 'white' : props.available ? nextPrevCursorsTheme.onColor : nextPrevCursorsTheme.offColor}; ${(props) => props.available ? pointerEventsAutoInNormalMode : pointerEventsNone}; ` const Prev = styled(PrevOrNextButton)<{ available: boolean flag: PresenceFlag | undefined }>` transform: translateX(2px); ${Container}:hover & { transform: translateX(-7px); } ` const Next = styled(PrevOrNextButton)<{ available: boolean flag: PresenceFlag | undefined }>` transform: translateX(-2px); ${Container}:hover & { transform: translateX(7px); } ` namespace Icons { const Chevron_Group = styled.g` stroke-width: 1; ${PrevOrNextButton}:hover & path { stroke-width: 3; } ` export const Prev = () => ( ) export const Next = () => ( ) const Cur_Group = styled.g` stroke-width: 0; ${CurButton}:hover & path { stroke: currentColor; stroke-width: 2; } ` export const Cur = () => ( ) } const NextPrevKeyframeCursors: React.VFC = (props) => { const prevPresence = usePresence(props.prev?.itemKey) const curPresence = usePresence( props.cur?.type === 'on' ? props.cur.itemKey : undefined, ) const nextPresence = usePresence(props.next?.itemKey) return ( ) } export default NextPrevKeyframeCursors ================================================ FILE: packages/studio/src/propEditors/getNearbyKeyframesOfTrack.tsx ================================================ import type {TrackData} from '@theatre/core/types/private/core' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import {createStudioSheetItemKey} from '@theatre/studio/utils/createStudioSheetItemKey' import type { KeyframeWithTrack, TrackWithId, } from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes' import {__private} from '@theatre/core' const {keyframeUtils} = __private const cache = new WeakMap< TrackData, [seqPosition: number, nearbyKeyframes: NearbyKeyframes] >() const noKeyframes: NearbyKeyframes = {} export function getNearbyKeyframesOfTrack( obj: SheetObject, track: TrackWithId | undefined, sequencePosition: number, ): NearbyKeyframes { if ( !track || keyframeUtils.getSortedKeyframesCached(track.data.keyframes).length === 0 ) return noKeyframes const cachedItem = cache.get(track.data) if (cachedItem && cachedItem[0] === sequencePosition) { return cachedItem[1] } const sorted = keyframeUtils.getSortedKeyframesCached(track.data.keyframes) function getKeyframeWithTrackId(idx: number): KeyframeWithTrack | undefined { if (!track) return const found = sorted[idx] return ( found && { kf: found, track, itemKey: createStudioSheetItemKey.forTrackKeyframe( obj, track.id, found.id, ), } ) } const calculate = (): NearbyKeyframes => { const nextOrCurIdx = sorted.findIndex( (kf) => kf.position >= sequencePosition, ) if (nextOrCurIdx === -1) { return { prev: getKeyframeWithTrackId(sorted.length - 1), } } const nextOrCur = getKeyframeWithTrackId(nextOrCurIdx)! if (nextOrCur.kf.position === sequencePosition) { return { prev: getKeyframeWithTrackId(nextOrCurIdx - 1), cur: nextOrCur, next: getKeyframeWithTrackId(nextOrCurIdx + 1), } } else { return { next: nextOrCur, prev: getKeyframeWithTrackId(nextOrCurIdx - 1), } } } const result = calculate() cache.set(track.data, [sequencePosition, result]) return result } export type NearbyKeyframes = { prev?: KeyframeWithTrack cur?: KeyframeWithTrack next?: KeyframeWithTrack } ================================================ FILE: packages/studio/src/propEditors/simpleEditors/BooleanPropEditor.tsx ================================================ import type {PropTypeConfig_Boolean} from '@theatre/core/types/public' import React, {useCallback} from 'react' import styled from 'styled-components' import BasicCheckbox from '@theatre/studio/uiComponents/form/BasicCheckbox' import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps' const Input = styled(BasicCheckbox)` margin-left: 6px; :focus { outline: 1px solid #555; } ` function BooleanPropEditor({ propConfig, editingTools, value, autoFocus, }: ISimplePropEditorReactProps) { const onChange = useCallback( (el: React.ChangeEvent) => { editingTools.permanentlySetValue(Boolean(el.target.checked)) }, [propConfig, editingTools], ) return } export default BooleanPropEditor ================================================ FILE: packages/studio/src/propEditors/simpleEditors/FilePropEditor.tsx ================================================ import type {PropTypeConfig_File} from '@theatre/core/types/public' import {Package, Trash} from '@theatre/studio/uiComponents/icons' import React, {useCallback, useEffect} from 'react' import styled, {css} from 'styled-components' import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps' import type {$FixMe} from '@theatre/core/types/public' const Container = styled.div<{empty: boolean}>` display: flex; align-items: center; height: 100%; gap: 4px; ` const AddFile = styled.div` position: absolute; inset: -5px; // rotate 45deg transform: rotate(45deg); --checker-color: #ededed36; &:hover { --checker-color: #ededed77; } // checkerboard background with 4px squares background-image: linear-gradient( 45deg, var(--checker-color) 25%, transparent 25% ), linear-gradient(-45deg, var(--checker-color) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--checker-color) 75%), linear-gradient(-45deg, transparent 75%, var(--checker-color) 75%); background-size: 5px 5px; ` const InputLabel = styled.label<{empty: boolean}>` position: relative; cursor: default; box-sizing: border-box; height: 18px; aspect-ratio: 1; display: flex; justify-content: center; align-items: center; font-size: 16px; overflow: hidden; color: #ccc; &:hover { color: white; } border-radius: 99999px; border: 1px solid hwb(220deg 40% 52%); &:hover { border-color: hwb(220deg 45% 52%); } ${(props) => (props.empty ? css`` : css``)} ` // file input const Input = styled.input.attrs({type: 'file'})` display: none; ` const DeleteButton = styled.button` display: flex; align-items: center; justify-content: center; outline: none; background: transparent; color: #a8a8a9; border: none; height: 100%; aspect-ratio: 1/1; opacity: 0; ${Container}:hover & { opacity: 0.8; } &:hover { opacity: 1; color: white; } ` function FilePropEditor({ propConfig, editingTools, value, autoFocus, }: ISimplePropEditorReactProps) { const [previewUrl, setPreviewUrl] = React.useState() useEffect(() => { if (value) { setPreviewUrl(editingTools.getAssetUrl(value)) } else { setPreviewUrl(undefined) } }, [value]) const onChange = useCallback( async (event: React.ChangeEvent<$FixMe>) => { const file = event.target.files[0] editingTools.permanentlySetValue({type: 'file', id: undefined}) const fileId = await editingTools.createAsset(file) if (!fileId) { editingTools.permanentlySetValue(value) } else { editingTools.permanentlySetValue({ type: 'file', id: fileId, }) } event.target.value = null }, [editingTools, value], ) const empty = !value?.id return ( {previewUrl ? : } {!empty && ( { editingTools.permanentlySetValue({type: 'file', id: undefined}) }} > )} ) } export default FilePropEditor ================================================ FILE: packages/studio/src/propEditors/simpleEditors/ISimplePropEditorReactProps.ts ================================================ import type {IBasePropType} from '@theatre/core/types/public' import type {IEditingTools} from '@theatre/studio/propEditors/utils/IEditingTools' /** Helper for defining consistent prop editor components */ export type ISimplePropEditorReactProps< TPropTypeConfig extends IBasePropType, > = { propConfig: TPropTypeConfig editingTools: IEditingTools value: TPropTypeConfig['valueType'] autoFocus?: boolean } ================================================ FILE: packages/studio/src/propEditors/simpleEditors/ImagePropEditor.tsx ================================================ import type {PropTypeConfig_Image} from '@theatre/core/types/public' import type {$FixMe} from '@theatre/core/types/public' import {Trash} from '@theatre/studio/uiComponents/icons' import React, {useCallback, useEffect} from 'react' import styled, {css} from 'styled-components' import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps' const Container = styled.div<{empty: boolean}>` display: flex; align-items: center; height: 100%; gap: 4px; ` const AddImage = styled.div` position: absolute; inset: -5px; // rotate 45deg transform: rotate(45deg); --checker-color: #ededed36; &:hover { --checker-color: #ededed77; } // checkerboard background with 4px squares background-image: linear-gradient( 45deg, var(--checker-color) 25%, transparent 25% ), linear-gradient(-45deg, var(--checker-color) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, var(--checker-color) 75%), linear-gradient(-45deg, transparent 75%, var(--checker-color) 75%); background-size: 5px 5px; ` const InputLabel = styled.label<{empty: boolean}>` position: relative; cursor: default; box-sizing: border-box; height: 18px; aspect-ratio: 1; display: flex; justify-content: center; align-items: center; font-size: 16px; overflow: hidden; color: #ccc; &:hover { color: white; } border-radius: 99999px; border: 1px solid hwb(220deg 40% 52%); &:hover { border-color: hwb(220deg 45% 52%); } ${(props) => (props.empty ? css`` : css``)} ` // file input const Input = styled.input.attrs({type: 'file'})` display: none; ` const Preview = styled.img` position: absolute; inset: 0; height: 100%; aspect-ratio: 1; object-fit: cover; ` const DeleteButton = styled.button` display: flex; align-items: center; justify-content: center; outline: none; background: transparent; color: #a8a8a9; border: none; height: 100%; aspect-ratio: 1/1; opacity: 0; ${Container}:hover & { opacity: 0.8; } &:hover { opacity: 1; color: white; } ` function ImagePropEditor({ propConfig, editingTools, value, autoFocus, }: ISimplePropEditorReactProps) { const [previewUrl, setPreviewUrl] = React.useState() useEffect(() => { if (value) { setPreviewUrl(editingTools.getAssetUrl(value)) } else { setPreviewUrl(undefined) } }, [value]) const onChange = useCallback( async (event: React.ChangeEvent<$FixMe>) => { const file = event.target.files[0] editingTools.permanentlySetValue({type: 'image', id: undefined}) const imageId = await editingTools.createAsset(file) if (!imageId) { editingTools.permanentlySetValue(value) } else { editingTools.permanentlySetValue({ type: 'image', id: imageId, }) } event.target.value = null }, [editingTools, value], ) const empty = !value?.id return ( {previewUrl ? : } {!empty && ( { editingTools.permanentlySetValue({type: 'image', id: undefined}) }} > )} ) } export default ImagePropEditor ================================================ FILE: packages/studio/src/propEditors/simpleEditors/NumberPropEditor.tsx ================================================ import type {PropTypeConfig_Number} from '@theatre/core/types/public' import BasicNumberInput from '@theatre/studio/uiComponents/form/BasicNumberInput' import React, {useCallback} from 'react' import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps' function NumberPropEditor({ propConfig, editingTools, value, autoFocus, }: ISimplePropEditorReactProps) { const nudge = useCallback( (params: {deltaX: number; deltaFraction: number; magnitude: number}) => { return propConfig.nudgeFn({...params, config: propConfig}) }, [propConfig], ) return ( ) } export default NumberPropEditor ================================================ FILE: packages/studio/src/propEditors/simpleEditors/RgbaPropEditor.tsx ================================================ import type {PropTypeConfig_Rgba, Rgba} from '@theatre/core/types/public' import {validHexRegExp} from '@theatre/utils/color' import {decorateRgba, rgba2hex, parseRgbaFromHex} from '@theatre/utils/color' import React, {useCallback, useRef} from 'react' import {RgbaColorPicker} from '@theatre/studio/uiComponents/colorPicker' import styled from 'styled-components' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import BasicStringInput from '@theatre/studio/uiComponents/form/BasicStringInput' import {popoverBackgroundColor} from '@theatre/studio/uiComponents/Popover/BasicPopover' import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps' const RowContainer = styled.div` display: flex; align-items: center; height: 100%; gap: 4px; ` interface ColorPreviewPuckProps { rgbaColor: Rgba } const ColorPreviewPuck = styled.div.attrs((props) => ({ style: { // weirdly, rgba2hex is needed to ensure initial render was correct background? // huge head scratcher. background: rgba2hex(props.rgbaColor), }, }))` height: 18px; aspect-ratio: 1; border-radius: 99999px; ` const HexInput = styled(BasicStringInput)` flex: 1; ` const noop = () => {} const RgbaPopover = styled.div` position: absolute; background-color: ${popoverBackgroundColor}; color: white; margin: 0; cursor: default; border-radius: 3px; z-index: 10000; backdrop-filter: blur(8px); padding: 4px; pointer-events: all; border: none; box-shadow: none; ` function RgbaPropEditor({ editingTools, value, autoFocus, }: ISimplePropEditorReactProps) { const containerRef = useRef(null!) const onChange = useCallback( (color: string) => { const rgba = decorateRgba(parseRgbaFromHex(color)) editingTools.permanentlySetValue(rgba) }, [editingTools], ) const popover = usePopover({debugName: 'RgbaPropEditor'}, () => ( { const rgba = decorateRgba(color) editingTools.temporarilySetValue(rgba) }} permanentlySetValue={(color) => { const rgba = decorateRgba(color) editingTools.permanentlySetValue(rgba) }} discardTemporaryValue={editingTools.discardTemporaryValue} /> )) return ( <> { popover.toggle(e, containerRef.current) }} /> !!v.match(validHexRegExp)} autoFocus={autoFocus} /> {popover.node} ) } export default RgbaPropEditor ================================================ FILE: packages/studio/src/propEditors/simpleEditors/StringLiteralPropEditor.tsx ================================================ import type {PropTypeConfig_StringLiteral} from '@theatre/core/types/public' import React, {useCallback} from 'react' import BasicSwitch from '@theatre/studio/uiComponents/form/BasicSwitch' import BasicSelect from '@theatre/studio/uiComponents/form/BasicSelect' import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps' function StringLiteralPropEditor({ propConfig, editingTools, value, autoFocus, }: ISimplePropEditorReactProps>) { const onChange = useCallback( (val: TLiteralOptions) => { editingTools.permanentlySetValue(val) }, [propConfig, editingTools], ) return propConfig.as === 'menu' ? ( ) : ( ) } export default StringLiteralPropEditor ================================================ FILE: packages/studio/src/propEditors/simpleEditors/StringPropEditor.tsx ================================================ import React from 'react' import type {PropTypeConfig_String} from '@theatre/core/types/public' import BasicStringInput from '@theatre/studio/uiComponents/form/BasicStringInput' import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps' function StringPropEditor({ editingTools, value, autoFocus, }: ISimplePropEditorReactProps) { return ( ) } export default StringPropEditor ================================================ FILE: packages/studio/src/propEditors/simpleEditors/simplePropEditorByPropType.ts ================================================ import type {PropTypeConfig_AllSimples} from '@theatre/core/types/public' import type React from 'react' import BooleanPropEditor from './BooleanPropEditor' import NumberPropEditor from './NumberPropEditor' import StringLiteralPropEditor from './StringLiteralPropEditor' import StringPropEditor from './StringPropEditor' import RgbaPropEditor from './RgbaPropEditor' import type {ISimplePropEditorReactProps} from './ISimplePropEditorReactProps' import type {PropConfigForType} from '@theatre/studio/propEditors/utils/PropConfigForType' import ImagePropEditor from './ImagePropEditor' import FilePropEditor from './FilePropEditor' export const simplePropEditorByPropType: ISimplePropEditorByPropType = { number: NumberPropEditor, string: StringPropEditor, boolean: BooleanPropEditor, stringLiteral: StringLiteralPropEditor, rgba: RgbaPropEditor, image: ImagePropEditor, file: FilePropEditor, } type ISimplePropEditorByPropType = { [K in PropTypeConfig_AllSimples['type']]: React.VFC< ISimplePropEditorReactProps> > } ================================================ FILE: packages/studio/src/propEditors/useEditingToolsForCompoundProp.tsx ================================================ import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import getStudio from '@theatre/studio/getStudio' import getDeep from '@theatre/utils/getDeep' import {usePrism} from '@theatre/react' import type {$IntentionalAny} from '@theatre/core/types/public' import {getPointerParts, prism, val} from '@theatre/dataverse' import type {Pointer} from '@theatre/dataverse' import get from 'lodash-es/get' import React from 'react' import DefaultOrStaticValueIndicator from './DefaultValueIndicator' import type {PropTypeConfig_Compound} from '@theatre/core/types/public' import type {SequenceTrackId} from '@theatre/core/types/public' import type {IPropPathToTrackIdTree} from '@theatre/core/sheetObjects/SheetObjectTemplate' import pointerDeep from '@theatre/utils/pointerDeep' import type {NearbyKeyframesControls} from './NextPrevKeyframeCursors' import NextPrevKeyframeCursors from './NextPrevKeyframeCursors' import {getNearbyKeyframesOfTrack} from './getNearbyKeyframesOfTrack' import type {KeyframeWithTrack} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes' import {emptyObject} from '@theatre/utils' import {createStudioSheetItemKey} from '@theatre/studio/utils/createStudioSheetItemKey' import type {ContextMenuItem} from '@theatre/studio/uiComponents/chordial/chordialInternals' import {__private} from '@theatre/core' const {iteratePropType, compoundHasSimpleDescendants, isPropConfigComposite} = __private.propTypeUtils interface CommonStuff { beingScrubbed: boolean contextMenuItems: Array controlIndicators: React.ReactElement } /** * For compounds that have _no_ sequenced track in all of their descendants */ interface AllStatic extends CommonStuff { type: 'AllStatic' } /** * For compounds that have at least one sequenced track in their descendants */ interface HasSequences extends CommonStuff { type: 'HasSequences' } type Stuff = AllStatic | HasSequences export function useEditingToolsForCompoundProp( pointerToProp: Pointer<{}>, obj: SheetObject, propConfig: PropTypeConfig_Compound<{}>, ): Stuff { const pathToProp = getPointerParts(pointerToProp).path return usePrism((): Stuff => { // if the compound has no simple descendants, then there isn't much the user can do with it if (!compoundHasSimpleDescendants(propConfig)) { return { type: 'AllStatic', beingScrubbed: false, contextMenuItems: [], controlIndicators: ( ), } } /** * TODO This implementation is wrong because {@link stateEditors.studio.ephemeral.projects.stateByProjectId.stateBySheetId.stateByObjectKey.propsBeingScrubbed.flag} * does not prune empty objects */ const someDescendantsBeingScrubbed = !!val( get( getStudio()!.atomP.ephemeral.projects.stateByProjectId[ obj.address.projectId ].stateBySheetId[obj.address.sheetId].stateByObjectKey[ obj.address.objectKey ].valuesBeingScrubbed, getPointerParts(pointerToProp).path, ), ) const contextMenuItems: ContextMenuItem[] = [] const common: CommonStuff = { beingScrubbed: someDescendantsBeingScrubbed, contextMenuItems, controlIndicators: <>, } const validSequencedTracks = val( obj.template.getMapOfValidSequenceTracks_forStudio(), ) const possibleSequenceTrackIds = getDeep( validSequencedTracks, pathToProp, ) as undefined | IPropPathToTrackIdTree const hasOneOrMoreSequencedTracks = possibleSequenceTrackIds !== undefined && Object.keys(possibleSequenceTrackIds).length !== 0 // check if object is empty or undefined const listOfDescendantTrackIds: SequenceTrackId[] = [] const allStaticOverrides = val( obj.template.getStaticButNotSequencedOverrides(), ) const staticOverrides = getDeep( allStaticOverrides ?? emptyObject, pathToProp, ) let hasStatics = staticOverrides !== undefined if (hasOneOrMoreSequencedTracks) { for (const descendant of iteratePropType(propConfig, [])) { if (isPropConfigComposite(descendant.conf)) continue const sequencedTrackIdBelongingToDescendant = getDeep( possibleSequenceTrackIds, descendant.path, ) as SequenceTrackId | undefined if (typeof sequencedTrackIdBelongingToDescendant !== 'string') { hasStatics = true } else { listOfDescendantTrackIds.push(sequencedTrackIdBelongingToDescendant) } } } if (hasStatics || hasOneOrMoreSequencedTracks) { contextMenuItems.push({ type: 'normal', label: 'Reset all to default', callback: () => { getStudio()!.transaction(({unset}) => { unset(pointerToProp) }) }, }) } if (hasOneOrMoreSequencedTracks) { contextMenuItems.push({ type: 'normal', label: 'Make all static', callback: () => { getStudio()!.transaction(({stateEditors}) => { for (const {path: subPath, conf} of iteratePropType( propConfig, [], )) { if (isPropConfigComposite(conf)) continue const propAddress = { ...obj.address, pathToProp: [...pathToProp, ...subPath], } const pointerToSub = pointerDeep(pointerToProp, subPath) stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsStatic( { ...propAddress, value: obj.getValueByPointer(pointerToSub as $IntentionalAny), }, ) } }) }, }) } if ( !hasOneOrMoreSequencedTracks || (hasOneOrMoreSequencedTracks && hasStatics) ) { contextMenuItems.push({ type: 'normal', label: 'Sequence all', callback: () => { getStudio()!.transaction(({stateEditors}) => { for (const {path, conf} of iteratePropType( propConfig, pathToProp, )) { if (isPropConfigComposite(conf)) continue const propAddress = {...obj.address, pathToProp: path} stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced( propAddress, ) } }) }, }) } if (hasOneOrMoreSequencedTracks) { const controlIndicators = prism.memo( `controlIndicators`, () => ( ), [possibleSequenceTrackIds, listOfDescendantTrackIds], ) const ret: HasSequences = { ...common, type: 'HasSequences', controlIndicators, } return ret } else { return { ...common, type: 'AllStatic', controlIndicators: ( ), } } }, []) } function ControlIndicators({ pointerToProp, obj, possibleSequenceTrackIds, listOfDescendantTrackIds, }: { pointerToProp: Pointer<{}> obj: SheetObject possibleSequenceTrackIds: IPropPathToTrackIdTree listOfDescendantTrackIds: SequenceTrackId[] }) { return usePrism(() => { const pathToProp = getPointerParts(pointerToProp).path const sequencePosition = val(obj.sheet.getSequence().positionPrism) /* 2/10 perf concern: When displaying a hierarchy like {props: {transform: {position: {x, y, z}}}}, we'd be recalculating this variable for both `position` and `transform`. While we _could_ be re-using the calculation of `transform` in `position`, I think it's unlikely that this optimization would matter. */ const nearbyKeyframesInEachTrack = listOfDescendantTrackIds .map((trackId) => ({ trackId, track: val( obj.template.project.pointers.historic.sheetsById[obj.address.sheetId] .sequence.tracksByObject[obj.address.objectKey].trackData[trackId], ), })) .filter(({track}) => !!track) .map((s) => ({ ...s, nearbies: getNearbyKeyframesOfTrack( obj, {id: s.trackId, data: s.track!, sheetObject: obj}, sequencePosition, ), })) const hasCur = nearbyKeyframesInEachTrack.find( ({nearbies}) => !!nearbies.cur, ) const allCur = nearbyKeyframesInEachTrack.every( ({nearbies}) => !!nearbies.cur, ) const closestPrev = nearbyKeyframesInEachTrack.reduce< undefined | KeyframeWithTrack >((acc, s) => { if (s.nearbies.prev) { if ( acc === undefined || s.nearbies.prev.kf.position > acc.kf.position ) { return s.nearbies.prev } else { return acc } } else { return acc } }, undefined) const closestNext = nearbyKeyframesInEachTrack.reduce< undefined | KeyframeWithTrack >((acc, s) => { if (s.nearbies.next) { if ( acc === undefined || s.nearbies.next.kf.position < acc.kf.position ) { return s.nearbies.next } else { return acc } } else { return acc } }, undefined) const toggle = () => { if (allCur) { getStudio().transaction((api) => { api.unset(pointerToProp) }) } else if (hasCur) { getStudio().transaction((api) => { api.set(pointerToProp, val(pointerToProp)) }) } else { getStudio().transaction((api) => { api.set(pointerToProp, val(pointerToProp)) }) } } const pr: NearbyKeyframesControls = { cur: hasCur ? { type: 'on', itemKey: createStudioSheetItemKey.forCompoundPropAggregateKeyframe( obj, pathToProp, sequencePosition, ), toggle, } : { toggle, type: 'off', }, prev: closestPrev !== undefined ? { position: closestPrev.kf.position, itemKey: createStudioSheetItemKey.forCompoundPropAggregateKeyframe( obj, pathToProp, closestPrev.kf.position, ), jump: () => { obj.sheet.getSequence().position = closestPrev.kf.position }, } : undefined, next: closestNext !== undefined ? { position: closestNext.kf.position, itemKey: createStudioSheetItemKey.forCompoundPropAggregateKeyframe( obj, pathToProp, closestNext.kf.position, ), jump: () => { obj.sheet.getSequence().position = closestNext.kf.position }, } : undefined, } return }, [pointerToProp, obj, possibleSequenceTrackIds, listOfDescendantTrackIds]) } ================================================ FILE: packages/studio/src/propEditors/useEditingToolsForSimpleProp.tsx ================================================ import get from 'lodash-es/get' import React from 'react' import type {Prism, Pointer} from '@theatre/dataverse' import {getPointerParts, prism, val} from '@theatre/dataverse' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import getStudio from '@theatre/studio/getStudio' import type Scrub from '@theatre/studio/Scrub' import getDeep from '@theatre/utils/getDeep' import {usePrismInstance} from '@theatre/react' import type { SerializablePrimitive as SerializablePrimitive, Asset, File as AssetFile, } from '@theatre/core/types/public' import type {PropTypeConfig_AllSimples} from '@theatre/core/types/public' import type {SequenceTrackId} from '@theatre/core/types/public' import DefaultOrStaticValueIndicator from './DefaultValueIndicator' import type {NearbyKeyframes} from './getNearbyKeyframesOfTrack' import {getNearbyKeyframesOfTrack} from './getNearbyKeyframesOfTrack' import type {NearbyKeyframesControls} from './NextPrevKeyframeCursors' import NextPrevKeyframeCursors from './NextPrevKeyframeCursors' import type {ContextMenuItem} from '@theatre/studio/uiComponents/chordial/chordialInternals' import {__private} from '@theatre/core' import type {$IntentionalAny} from '@theatre/core/types/public' const {isPropConfSequencable} = __private.propTypeUtils interface EditingToolsCommon { value: T beingScrubbed: boolean contextMenuItems: Array /** e.g. `< • >` or `< >` for {@link EditingToolsSequenced} */ controlIndicators: React.ReactElement temporarilySetValue(v: T): void discardTemporaryValue(): void permanentlySetValue(v: T): void getAssetUrl: (asset: Asset | AssetFile) => string | undefined createAsset(asset: File): Promise } interface EditingToolsDefault extends EditingToolsCommon { type: 'Default' shade: Shade } interface EditingToolsStatic extends EditingToolsCommon { type: 'Static' shade: Shade } interface EditingToolsSequenced extends EditingToolsCommon { type: 'Sequenced' shade: Shade /** based on the position of the playhead */ nearbyKeyframes: NearbyKeyframes } type EditingTools = | EditingToolsDefault | EditingToolsStatic | EditingToolsSequenced const cache = new WeakMap<{}, Prism>>() /** * Note: we're able to get `obj` and `propConfig` from `pointerToProp`, * so the only reason they're still in the arguments list is that */ function createPrism( pointerToProp: Pointer, obj: SheetObject, propConfig: PropTypeConfig_AllSimples, ): Prism> { return prism(() => { const pathToProp = getPointerParts(pointerToProp).path const final = obj.getValueByPointer(pointerToProp) as T const editPropValue = prism.memo( 'editPropValue', () => { let currentScrub: Scrub | null = null return { temporarilySetValue(v: T): void { if (!currentScrub) { currentScrub = getStudio()!.scrub() } currentScrub.capture((api) => { api.set(pointerToProp, v) }) }, discardTemporaryValue(): void { if (currentScrub) { currentScrub.discard() currentScrub = null } }, permanentlySetValue(v: T): void { if (currentScrub) { currentScrub.capture((api) => { api.set(pointerToProp, v) }) currentScrub.commit() currentScrub = null } else { getStudio()!.transaction((api) => { api.set(pointerToProp, v) }) } }, } }, [], ) const editAssets = { createAsset: (asset: File): Promise => obj.sheet.project.assetStorage.createAsset(asset), getAssetUrl: (asset: Asset | AssetFile) => asset.id ? obj.sheet.project.assetStorage.getAssetUrl(asset.id) : undefined, } const beingScrubbed = val( get( getStudio()!.atomP.ephemeral.projects.stateByProjectId[ obj.address.projectId ].stateBySheetId[obj.address.sheetId].stateByObjectKey[ obj.address.objectKey ].valuesBeingScrubbed, getPointerParts(pointerToProp).path, ), ) === true const contextMenuItems: ContextMenuItem[] = [] const common: EditingToolsCommon = { ...editPropValue, ...editAssets, value: final, beingScrubbed, contextMenuItems, controlIndicators: <>, } const isSequencable = isPropConfSequencable(propConfig) if (isSequencable) { const validSequencedTracks = val( obj.template.getMapOfValidSequenceTracks_forStudio(), ) const possibleSequenceTrackId = getDeep(validSequencedTracks, pathToProp) const isSequenced = typeof possibleSequenceTrackId === 'string' if (isSequenced) { contextMenuItems.push({ type: 'normal', label: 'Make static', callback: () => { getStudio()!.transaction(({stateEditors}) => { const propAddress = {...obj.address, pathToProp} stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsStatic( { ...propAddress, value: obj.getValueByPointer(pointerToProp) as T, }, ) }) }, }) const sequenceTrackId = possibleSequenceTrackId as SequenceTrackId const nearbyKeyframes = prism.sub( 'lcr', (): NearbyKeyframes => { const track = val( obj.template.project.pointers.historic.sheetsById[ obj.address.sheetId ].sequence.tracksByObject[obj.address.objectKey].trackData[ sequenceTrackId ], ) const sequencePosition = val(obj.sheet.getSequence().positionPrism) return getNearbyKeyframesOfTrack( obj, track && { data: track, id: sequenceTrackId, sheetObject: obj, }, sequencePosition, ) }, [sequenceTrackId], ) let shade: Shade if (common.beingScrubbed) { shade = 'Sequenced_OnKeyframe_BeingScrubbed' } else { if (nearbyKeyframes.cur) { shade = 'Sequenced_OnKeyframe' } else if (nearbyKeyframes.prev?.kf.connectedRight === true) { shade = 'Sequenced_BeingInterpolated' } else { shade = 'Sequened_NotBeingInterpolated' } } const toggle = () => { if (nearbyKeyframes.cur) { getStudio()!.transaction((api) => { api.unset(pointerToProp) }) } else { getStudio()!.transaction((api) => { api.set(pointerToProp, common.value) }) } } const controls: NearbyKeyframesControls = { cur: nearbyKeyframes.cur ? { type: 'on', itemKey: nearbyKeyframes.cur.itemKey, toggle, } : { type: 'off', toggle, }, prev: nearbyKeyframes.prev !== undefined ? { itemKey: nearbyKeyframes.prev.itemKey, position: nearbyKeyframes.prev.kf.position, jump: () => { obj.sheet.getSequence().position = nearbyKeyframes.prev!.kf.position }, } : undefined, next: nearbyKeyframes.next !== undefined ? { itemKey: nearbyKeyframes.next.itemKey, position: nearbyKeyframes.next.kf.position, jump: () => { obj.sheet.getSequence().position = nearbyKeyframes.next!.kf.position }, } : undefined, } const nextPrevKeyframeCursors = ( ) const ret: EditingToolsSequenced = { ...common, type: 'Sequenced', shade, nearbyKeyframes, controlIndicators: nextPrevKeyframeCursors, } return ret } } const allStaticOverrides = val(obj.template.getStaticValues()) const staticOverride = getDeep(allStaticOverrides, pathToProp) if (typeof staticOverride !== 'undefined') { contextMenuItems.push({ type: 'normal', label: 'Reset to default', callback: () => { getStudio()!.transaction(({unset: unset}) => { unset(pointerToProp) }) }, }) } if (isSequencable) { contextMenuItems.push({ type: 'normal', label: 'Sequence', callback: () => { getStudio()!.transaction(({stateEditors}) => { const propAddress = {...obj.address, pathToProp} stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced( propAddress, ) }) }, }) } if (typeof staticOverride !== 'undefined') { const ret: EditingToolsStatic = { ...common, type: 'Static', shade: common.beingScrubbed ? 'Static_BeingScrubbed' : 'Static', controlIndicators: ( ), } return ret } const ret: EditingToolsDefault = { ...common, type: 'Default', shade: 'Default', controlIndicators: ( ), } return ret }) } function getPrism( pointerToProp: Pointer, obj: SheetObject, propConfig: PropTypeConfig_AllSimples, ): Prism> { if (cache.has(pointerToProp)) { return cache.get(pointerToProp)! } else { const d = createPrism(pointerToProp, obj, propConfig) cache.set(pointerToProp, d) return d } } /** * Notably, this uses the {@link Scrub} API to support * indicating in the UI which pointers (values/props) are being * scrubbed. See how impl of {@link Scrub} manages * `state.flagsTransaction` to keep a list of these touched paths * for the UI to be able to recognize. (e.g. to highlight the * item in r3f as you change its scale). */ export function useEditingToolsForSimplePropInDetailsPanel< T extends SerializablePrimitive, >( pointerToProp: Pointer, obj: SheetObject, propConfig: PropTypeConfig_AllSimples, ): EditingTools { const der = getPrism(pointerToProp, obj, propConfig) return usePrismInstance(der) } type Shade = | 'Default' | 'Static' | 'Static_BeingScrubbed' | 'Sequenced_OnKeyframe' | 'Sequenced_OnKeyframe_BeingScrubbed' | 'Sequenced_BeingInterpolated' | 'Sequened_NotBeingInterpolated' ================================================ FILE: packages/studio/src/propEditors/utils/IEditingTools.tsx ================================================ import type {Asset, File} from '@theatre/core/types/public' export interface IEditingTools { temporarilySetValue(v: T): void discardTemporaryValue(): void permanentlySetValue(v: T): void getAssetUrl(asset: Asset | File): string | undefined createAsset(asset: Blob): Promise } ================================================ FILE: packages/studio/src/propEditors/utils/PropConfigForType.ts ================================================ import type {PropTypeConfig} from '@theatre/core/types/public' export type PropConfigForType = Extract< PropTypeConfig, {type: K} > ================================================ FILE: packages/studio/src/propEditors/utils/getPropTypeByPointer.tsx ================================================ import type {PropTypeConfig} from '@theatre/core/types/public' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import {getPointerParts} from '@theatre/dataverse' /** * @deprecated because it uses obj.template.staticConfig * * Returns the PropTypeConfig by path. Assumes `path` is a valid prop path and that * it exists in obj. * * Example usage: * ``` * const propConfig = getPropTypeByPointer(propP, sheetObject) * * if (propConfig.type === 'number') { * //... etc. * } * ``` */ export function getPropTypeByPointer( pointerToProp: SheetObject['propsP'], obj: SheetObject, ): PropTypeConfig { const rootConf = obj.template.staticConfig const p = getPointerParts(pointerToProp).path let conf = rootConf as PropTypeConfig while (p.length !== 0) { const key = p.shift() if (typeof key === 'string') { if (conf.type === 'compound') { conf = conf.props[key] if (!conf) { throw new Error( `getPropTypeConfigByPath() is called with an invalid path.`, ) } } else if (conf.type === 'enum') { conf = conf.cases[key] if (!conf) { throw new Error( `getPropTypeConfigByPath() is called with an invalid path.`, ) } } else { throw new Error( `getPropTypeConfigByPath() is called with an invalid path.`, ) } } else if (typeof key === 'number') { throw new Error(`Number indexes are not implemented yet. @todo`) } else { throw new Error( `getPropTypeConfigByPath() is called with an invalid path.`, ) } } return conf } ================================================ FILE: packages/studio/src/propEditors/utils/propNameTextCSS.tsx ================================================ import type {PropHighlighted} from '@theatre/studio/panels/SequenceEditorPanel/whatPropIsHighlighted' import {css} from 'styled-components' export const propNameTextCSS = css<{isHighlighted?: PropHighlighted}>` font-weight: 300; font-size: 11px; color: ${(props) => (props.isHighlighted === 'self' ? '#CCC' : '#919191')}; text-shadow: 0.5px 0.5px 2px rgba(0, 0, 0, 0.3); ` ================================================ FILE: packages/studio/src/selectors.ts ================================================ import type Project from '@theatre/core/projects/Project' import type Sequence from '@theatre/core/sequences/Sequence' import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type Sheet from '@theatre/core/sheets/Sheet' import {prism, val} from '@theatre/dataverse' import type {$IntentionalAny} from '@theatre/dataverse/src/types' import type {SheetId} from '@theatre/core/types/public' import {uniq} from 'lodash-es' import getStudio from './getStudio' import {__private} from '@theatre/core' const {isSheet, isSheetObject} = __private.instanceTypes export type OutlineSelectable = Project | Sheet | SheetObject export type OutlineSelection = OutlineSelectable[] export const outlineSelection = prism((): OutlineSelection => { const projects = val(getStudio().projectsP) const mapped: (OutlineSelectable | undefined)[] = ( val(getStudio().atomP.historic.panels.outlinePanel.selection) ?? [] ).map((s) => { const project = projects[s.projectId] if (!project) return if (s.type === 'Project') return project const sheetTemplate = val(project.sheetTemplatesP[s.sheetId]) if (!sheetTemplate) { return } const sheetInstance = getSelectedInstanceOfSheetId(project, s.sheetId) if (!sheetInstance) return if (s.type === 'Sheet') { return sheetInstance } const obj = val(sheetInstance.objectsP[s.objectKey]) if (!obj) return return obj }) return uniq( mapped.filter((s): s is OutlineSelectable => typeof s !== 'undefined'), ) }) export const getSelectedInstanceOfSheetId = ( project: Project, selectedSheetId: string, ): Sheet | undefined => { const projectStateP = getStudio()!.atomP.historic.projects.stateByProjectId[ project.address.projectId ] const instanceId = val( projectStateP.stateBySheetId[selectedSheetId as SheetId].selectedInstanceId, ) const template = val(project.sheetTemplatesP[selectedSheetId]) if (!template) return undefined if (instanceId) { return val(template.instancesP[instanceId]) } else { // @todo #perf this will update every time an instance is added/removed. const allInstances = val(template.instancesP) return allInstances[keys(allInstances)[0]] } } function keys(obj: T): Exclude[] { return Object.keys(obj) as $IntentionalAny } /** * component instances could come and go all the time. This hook * makes sure we don't cause re-renders */ export function getRegisteredSheetIds(project: Project): string[] { return Object.keys(val(project.sheetTemplatesP)) } export function getSelectedSequence(): undefined | Sequence { const selectedSheets = uniq( outlineSelection .getValue() .filter((s): s is SheetObject | Sheet => isSheet(s) || isSheetObject(s)) .map((s) => (isSheetObject(s) ? s.sheet : s)), ) const sheet = selectedSheets[0] if (!sheet) return return sheet.getSequence() } ================================================ FILE: packages/studio/src/toolbars/ExtensionToolbar/ExtensionToolbar.tsx ================================================ import {Atom} from '@theatre/dataverse' import {useVal} from '@theatre/react' /* eslint-disable import/no-extraneous-dependencies */ import type {IExtension} from '@theatre/core/types/public' import getStudio from '@theatre/studio/getStudio' import type {ToolsetConfig} from '@theatre/core/types/public' import React, {useLayoutEffect, useMemo} from 'react' import styled from 'styled-components' import Toolset from './Toolset' const Container = styled.div` height: 36px; /* pointer-events: none; */ display: flex; gap: 0.5rem; justify-content: center; ` const GroupDivider = styled.div` position: abolute; height: 32px; width: 1px; background: #373b40; opacity: 0.4; ` const ExtensionToolsetRender: React.FC<{ extension: IExtension toolbarId: string }> = ({extension, toolbarId}) => { const toolsetConfigBox = useMemo(() => new Atom([]), []) const attachFn = extension.toolbars?.[toolbarId] useLayoutEffect(() => { const detach = attachFn?.( toolsetConfigBox.set.bind(toolsetConfigBox), getStudio()!.publicApi, ) if (typeof detach === 'function') return detach }, [extension, toolbarId, attachFn]) const config = useVal(toolsetConfigBox.prism) return } export const ExtensionToolbar: React.FC<{ toolbarId: string showLeftDivider?: boolean }> = ({toolbarId, showLeftDivider}) => { const groups: Array = [] const extensionsById = useVal( getStudio().ephemeralAtom.pointer.extensions.byId, ) let isAfterFirstGroup = false for (const [, extension] of Object.entries(extensionsById)) { if (!extension || !extension.toolbars?.[toolbarId]) continue groups.push( {isAfterFirstGroup ? : undefined} , ) isAfterFirstGroup = true } if (groups.length === 0) return null return ( {showLeftDivider ? : undefined} {groups} ) } export default ExtensionToolbar ================================================ FILE: packages/studio/src/toolbars/ExtensionToolbar/Toolset.tsx ================================================ import didYouMean from '@theatre/utils/didYouMean' import type {$IntentionalAny} from '@theatre/core/types/public' import userReadableTypeOfValue from '@theatre/utils/userReadableTypeOfValue' import type {ToolConfig, ToolsetConfig} from '@theatre/core/types/public' import React from 'react' import IconButton from './tools/IconButton' import Switch from './tools/Switch' import ExtensionFlyoutMenu from './tools/ExtensionFlyoutMenu' const Toolset: React.FC<{ config: ToolsetConfig }> = (props) => { return ( <> {props.config.map((toolConfig, i) => { return })} ) } const toolByType: { [Key in ToolConfig['type']]: React.FC<{ config: Extract }> } = { Icon: IconButton, Switch: Switch, Flyout: ExtensionFlyoutMenu, } function getToolByType( type: Type, ): React.FC<{config: Extract}> { return toolByType[type] as $IntentionalAny } const Tool: React.FC<{config: ToolConfig}> = ({config}) => { const Comp = getToolByType(config.type) if (!Comp) { throw new Error( `No tool with tool.type ${userReadableTypeOfValue( config.type, )} exists. Did you mean ${didYouMean( config.type, Object.keys(toolByType), )}`, ) } return } export default Toolset ================================================ FILE: packages/studio/src/toolbars/ExtensionToolbar/tools/ExtensionFlyoutMenu.tsx ================================================ import React, {useRef} from 'react' import styled from 'styled-components' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import type { ToolConfigFlyoutMenu, ToolconfigFlyoutMenuItem, } from '@theatre/core/types/public' import ToolbarIconButton from '@theatre/studio/uiComponents/toolbar/ToolbarIconButton' import BaseMenu from '@theatre/studio/uiComponents/simpleContextMenu/ContextMenu/BaseMenu' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import type {$IntentionalAny} from '@theatre/core/types/public' const Container = styled.div` ${pointerEventsAutoInNormalMode}; & > svg { width: 1em; height: 1em; pointer-events: none; } ` const ExtensionFlyoutMenu: React.FC<{ config: ToolConfigFlyoutMenu }> = ({config}) => { const triggerRef = useRef(null) const popover = usePopover( () => { const triggerBounds = triggerRef.current!.getBoundingClientRect() return { debugName: 'ExtensionFlyoutMenu:' + config.label, constraints: { maxX: triggerBounds.right, maxY: 8, minX: triggerBounds.left, minY: 8, }, verticalGap: 2, } }, () => { return ( ({ label: option.label, callback: () => { // this is a user-defined function, so we need to wrap it in a try/catch try { option.onClick?.() } catch (e) { console.error(e) } }, }), )} onRequestClose={() => { popover.close('clicked') }} /> ) }, ) return ( {popover.node} { popover.open(e, triggerRef.current!) }} > {config.label} ) } export default ExtensionFlyoutMenu ================================================ FILE: packages/studio/src/toolbars/ExtensionToolbar/tools/IconButton.tsx ================================================ import React from 'react' import ToolbarIconButton from '@theatre/studio/uiComponents/toolbar/ToolbarIconButton' import styled from 'styled-components' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import type {ToolConfigIcon} from '@theatre/core/types/public' const Container = styled(ToolbarIconButton)` ${pointerEventsAutoInNormalMode}; & > svg { width: 1em; height: 1em; pointer-events: none; } ` const IconButton: React.FC<{ config: ToolConfigIcon testId?: string }> = ({config, testId}) => { return ( ) } export default IconButton ================================================ FILE: packages/studio/src/toolbars/ExtensionToolbar/tools/Switch.tsx ================================================ import React from 'react' import styled from 'styled-components' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import type {ToolConfigSwitch} from '@theatre/core/types/public' import ToolbarSwitchSelect from '@theatre/studio/uiComponents/toolbar/ToolbarSwitchSelect' const IconContainer = styled.div` ${pointerEventsAutoInNormalMode}; & > svg { width: 1em; height: 1em; pointer-events: none; } ` const Switch: React.FC<{ config: ToolConfigSwitch }> = ({config}) => { return ( ({ label, value, icon: , }))} value={config.value} /> ) } export default Switch ================================================ FILE: packages/studio/src/toolbars/GlobalToolbar/GlobalToolbar.tsx ================================================ import {useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import React from 'react' import styled from 'styled-components' import ExtensionToolbar from '@theatre/studio/toolbars/ExtensionToolbar/ExtensionToolbar' import { useNotifications, useEmptyNotificationsTooltip, } from '@theatre/studio/notify' import { uesConflicts, useOutlineTriggerTooltip, useMoreMenu, useShouldShowUpdatesBadge, } from './globalToolbarHooks' import LeftStrip from './LeftStrip/LeftStrip' import RightStrip from './RightStrip/RightStrip' const Container = styled.div` position: fixed; left: 0; right: 0; top: 12px; height: 42px; display: flex; justify-content: space-between; pointer-events: none; ` const NumberOfConflictsIndicator = styled.div` color: white; width: 14px; height: 14px; background: #d00; border-radius: 4px; text-align: center; line-height: 14px; font-weight: 600; font-size: 8px; position: relative; left: -6px; top: -11px; margin-right: -14px; box-shadow: 0 4px 6px -4px #00000059; ` const SubContainer = styled.div` display: flex; gap: 8px; ` const HasUpdatesBadge = styled.div<{type: 'info' | 'warning'}>` position: absolute; background: ${({type}) => (type === 'info' ? '#40aaa4' : '#f59e0b')}; width: 6px; height: 6px; border-radius: 50%; right: -2px; top: -2px; ` const GlobalToolbar: React.FC = () => { const conflicts = uesConflicts() const [outlineTriggerTooltip, outlineTriggerRef] = useOutlineTriggerTooltip(conflicts) const outlinePinned = useVal(getStudio().atomP.ahistoric.pinOutline) ?? true const detailsPinned = useVal(getStudio().atomP.ahistoric.pinDetails) ?? true const {moreMenu, moreMenuTriggerRef} = useMoreMenu() const showUpdatesBadge = useShouldShowUpdatesBadge() const {hasNotifications} = useNotifications() const [notificationsTooltip, notificationsTriggerRef] = useEmptyNotificationsTooltip() return ( {outlineTriggerTooltip} {/* { const prev = val(getStudio().atomP.ahistoric.pinOutline) getStudio().transaction(({stateEditors}) => { stateEditors.studio.ahistoric.setPinOutline(!(prev ?? true)) }) }} icon={} pinHintIcon={} unpinHintIcon={} pinned={outlinePinned} /> */} {/* */} {conflicts.length > 0 ? ( {conflicts.length} ) : null} {/* {notificationsTooltip} { const prev = val(getStudio().atomP.ahistoric.pinNotifications) getStudio().transaction(({stateEditors}) => { stateEditors.studio.ahistoric.setPinNotifications( !(prev ?? false), ) }) }} icon={} pinHintIcon={} unpinHintIcon={} pinned={useVal(getStudio().atomP.ahistoric.pinNotifications) ?? false} > {hasNotifications && } */} {/* {moreMenu.node} { moreMenu.toggle(e, moreMenuTriggerRef.current!) }} > {showUpdatesBadge && } */} {/* { const prev = val(getStudio().atomP.ahistoric.pinDetails) getStudio().transaction(({stateEditors}) => { stateEditors.studio.ahistoric.setPinDetails(!(prev ?? true)) }) }} icon={
} pinHintIcon={} unpinHintIcon={} pinned={detailsPinned} /> */} ) } export default GlobalToolbar ================================================ FILE: packages/studio/src/toolbars/GlobalToolbar/LeftStrip/AppButton/AppButton.tsx ================================================ import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import useChordial from '@theatre/studio/uiComponents/chordial/useChodrial' import React from 'react' import styled from 'styled-components' import logo from '@theatre/studio/assets/logo.png' import DropdownChevron from '@theatre/studio/uiComponents/icons/DropdownChevron' import BaseMenu from '@theatre/studio/uiComponents/simpleContextMenu/ContextMenu/BaseMenu' const Container = styled.div` height: 100%; width: 42px; display: flex; justify-content: center; align-items: center; position: relative; ${pointerEventsAutoInNormalMode}; &:hover { --chevron-down: 1; background: rgba(255, 255, 255, 0.08); } ` const Logo = styled.img` width: 16px; height: 15px; ` const appButtonTitle = 'Theatre.js 0.8' const AppButton: React.FC<{}> = (props) => { const s = useChordial(() => { return { items: [], title: appButtonTitle, invoke: { type: 'popover', render: ({close}) => { return ( {}}, {type: 'separator'}, {label: 'Chat with us', callback: () => {}}, {label: 'Help', callback: () => {}}, {label: 'Changelog', callback: () => {}}, {type: 'separator'}, {label: 'Github', callback: () => {}}, {label: 'Discord', callback: () => {}}, {label: 'Twitter', callback: () => {}}, {type: 'separator'}, {label: 'Settings', callback: () => {}}, ]} displayName={appButtonTitle} /> ) }, }, } }) return ( <> ) } export default AppButton ================================================ FILE: packages/studio/src/toolbars/GlobalToolbar/LeftStrip/LeftStrip.tsx ================================================ import React from 'react' import styled from 'styled-components' import AppButton from './AppButton/AppButton' import WorkspaceButton from './WorkspaceButton/WorkspaceButton' const Container = styled.div` height: 28px; flex-shrink: 0; flex-grow: 1; display: flex; align-items: center; flex-direction: row; margin-left: 13px; border-radius: 3px; background: rgba(47, 50, 53, 0.88); box-shadow: 0px 4px 4px -1px rgba(0, 0, 0, 0.2); backdrop-filter: blur(10px); ` const Separator = styled.div` background: rgba(0, 0, 0, 0.24); width: 1px; height: 100%; ` const LeftStrip: React.FC<{}> = (props) => { return ( ) } export default LeftStrip ================================================ FILE: packages/studio/src/toolbars/GlobalToolbar/LeftStrip/WorkspaceButton/WorkspaceButton.tsx ================================================ import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import DropdownChevron from '@theatre/studio/uiComponents/icons/DropdownChevron' import React from 'react' import styled from 'styled-components' const Container = styled.div` display: flex; align-items: center; gap: 4px; text-wrap: nowrap; padding: 0 10px; height: 100%; font-weight: 500; ${pointerEventsAutoInNormalMode}; cursor: default; &:hover { --chevron-down: 1; background: rgba(255, 255, 255, 0.08); } ` const Team = styled.span` color: rgba(255, 255, 255, 0.38); ` const Separator = styled.span` color: rgba(255, 255, 255, 0.38); ` const WorkspaceName = styled.span`` const WorkspaceButton: React.FC<{}> = (props) => { const team = `Team Freight` const wsName = `Solar Play` return ( {team} {`/`} {wsName} ) } export default WorkspaceButton ================================================ FILE: packages/studio/src/toolbars/GlobalToolbar/MoreMenu/MoreMenu.tsx ================================================ import {useVal} from '@theatre/react' import type {$IntentionalAny} from '@theatre/core/types/public' import getStudio from '@theatre/studio/getStudio' import React from 'react' import styled from 'styled-components' import {env} from '@theatre/studio/env' const Container = styled.div` width: 138px; border-radius: 2px; background-color: rgba(42, 45, 50, 0.9); position: absolute; display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start; box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.25), 0px 2px 6px rgba(0, 0, 0, 0.15); backdrop-filter: blur(14px); pointer-events: auto; // makes the edges of the item highlights match the rounded corners overflow: hidden; @supports not (backdrop-filter: blur()) { background-color: rgba(42, 45, 50, 0.98); } color: rgba(255, 255, 255, 0.9); & a { // Fix colors of links to not be default color: inherit; } ` const Item = styled.div` position: relative; padding: 0px 12px; font-weight: 400; font-size: 11px; height: 32px; text-decoration: none; user-select: none; display: flex; flex-direction: row; align-items: center; cursor: default; ` const Link = styled(Item)` &:before { position: absolute; display: block; content: ' '; inset: 3px; border-radius: 3px; background: rgba(255, 255, 255, 0.1); opacity: 0; } &.secondary { color: rgba(255, 255, 255, 0.5); } &:hover { /* background-color: #398995; */ color: white !important; &:before { opacity: 1; } } ` const VersionContainer = styled(Item)` height: auto; min-height: 32px; padding-top: 12px; padding-bottom: 10px; flex-direction: column; justify-content: flex-start; align-items: stretch; gap: 8px; color: rgba(255, 255, 255, 0.5); ` const VersionLabel = styled.div` font-weight: 600; ` const VersionValueRow = styled.div` display: flex; flex-direction: row; justify-content: space-between; ` const VersionValue = styled.div` margin-left: 2px; ` const Divider = styled.div` height: 1px; margin: 0 2px; background: rgba(255, 255, 255, 0.02); ` const UpdateDot = styled.div` position: absolute; width: 8px; height: 8px; background: #40aaa4; right: 14px; top: 12px; border-radius: 50%; ` const version: string = env.THEATRE_VERSION ?? '0.4.0' const untaggedVersion: string = version.match(/^[^\-]+/)![0] const MoreMenu = React.forwardRef((props: {}, ref) => { const hasUpdates = useVal( getStudio().ahistoricAtom.pointer.updateChecker.result.hasUpdates, ) return ( Docs Changelog Github Twitter Discord Version {version}{' '} {hasUpdates === true ? '(outdated)' : hasUpdates === false ? '(latest)' : ''} {hasUpdates === true && ( <> Update What's new? )} ) }) export default MoreMenu ================================================ FILE: packages/studio/src/toolbars/GlobalToolbar/RightStrip/AuthState/AuthState.tsx ================================================ import {useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import ToolbarButton from '@theatre/studio/uiComponents/toolbar/ToolbarButton' import React from 'react' import Unauthorized from './Unauthorized' import Avatar from './Avatar' const auth = getStudio().auth const AuthState: React.FC<{}> = (props) => { const authState = useVal(auth.derivedState) if (authState === 'loading') { return <> } if (!authState.loggedIn) { return } else { return ( <> {}}> Share ) } } export default AuthState ================================================ FILE: packages/studio/src/toolbars/GlobalToolbar/RightStrip/AuthState/Avatar.tsx ================================================ import type {AuthDerivedState} from '@theatre/studio/Auth' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import getStudio from '@theatre/studio/getStudio' import type {ContextMenuItem} from '@theatre/studio/uiComponents/chordial/chordialInternals' import useChordial from '@theatre/studio/uiComponents/chordial/useChodrial' import BaseMenu from '@theatre/studio/uiComponents/simpleContextMenu/ContextMenu/BaseMenu' import React from 'react' import styled from 'styled-components' const Container = styled.div` width: 28px; height: 28px; position: relative; ${pointerEventsAutoInNormalMode}; &:active { transform: translateY(1px); } ` const AvatarImage = styled.img` width: 100%; height: 100%; aspect-ratio: 1; border-radius: 50px; --box-shadow-color: rgba(0, 0, 0, 0.7); box-shadow: 0px 4px 4px -1px var(--box-shadow-color); ${Container}:hover & { --box-shadow-color: rgba(0, 0, 0, 1); } ` const auth = getStudio().auth const Stroke = styled.div` position: absolute; inset: -2px; border-radius: 50px; background: rgba(151, 208, 249, 0.6); ${Container}:hover & { background: rgba(151, 208, 249, 0.9); transform: scale(1.05); } ${Container}:active & { background: rgba(151, 208, 249, 0.9); transform: scale(1.05); } backdrop-filter: blur(2px); --gradient-cutoff: 62%; // add a mask, so that a circle is cut out of the stroke mask-image: radial-gradient( circle at center, transparent 0%, transparent var(--gradient-cutoff), black var(--gradient-cutoff) ); ` type Props = {authState: Extract} const Avatar: React.FC = (props) => { // const p = usePopover( // () => { // return {debugName: 'Avatar popover'} // }, // , // ) const c = useChordial(() => { const userFullName = 'Aria Minaei' return { title: `User: ${userFullName}`, menuTitle: 'User', items: [], invoke: { type: 'popover', render: ({close}) => { const items: ContextMenuItem[] = [ {label: 'My account', type: 'normal', callback: () => {}}, {type: 'separator'}, {label: 'Help', type: 'normal', callback: () => {}}, {label: 'Keyboard shortcuts', type: 'normal', callback: () => {}}, {type: 'separator'}, { label: 'Log out', type: 'normal', callback: () => auth.deauthorize(), }, ] return }, }, } }) const url = `https://pbs.twimg.com/profile_images/1367137683/aria_400x400.jpg` return ( <> ) } export default Avatar ================================================ FILE: packages/studio/src/toolbars/GlobalToolbar/RightStrip/AuthState/Unauthorized.tsx ================================================ import type {AuthDerivedState} from '@theatre/studio/Auth' import getStudio from '@theatre/studio/getStudio' import ToolbarButton from '@theatre/studio/uiComponents/toolbar/ToolbarButton' import React from 'react' import SimplePopover from '@theatre/studio/uiComponents/Popover/SimplePopover' import styled from 'styled-components' const ThePopover = styled(SimplePopover)` width: 380px; padding: 24px 14px; font-weight: 500; backdrop-filter: blur(8px) contrast(65%) brightness(59%); --popover-bg: rgb(58 59 67); --popover-outer-stroke: rgb(99 100 112); box-shadow: rgb(0 0 0 / 55%) 1px 8px 13px 6px; ` const P1 = styled.p` margin-bottom: 1em; ` const auth = getStudio().auth const Unauthorized: React.FC<{ authState: Extract }> = ({authState}) => { // const authState: Extract = { // loggedIn: false, // procedureState: { // type: 'authorize', // deviceTokenFlowState: { // type: 'waitingForDeviceCode', // // verificationUriComplete: 'https://google.com', // }, // }, // } const buttonRef = React.useRef(null) const {procedureState} = authState if (!procedureState) return ( auth.authorize()}> Log in ) let popoverBody = null if (procedureState.type === 'authorize') { const {deviceTokenFlowState} = procedureState if (deviceTokenFlowState?.type === 'waitingForDeviceCode') { popoverBody = ( <> Logging you in...

Waiting for OAuth token.

) } else if (deviceTokenFlowState?.type === 'codeReady') { popoverBody = ( <> Complete log in in the popup.

Popup didn't show up?{' '} Try this link.

) } else { popoverBody = `Logging in...` } } return ( <> {procedureState.type === 'authorize' ? 'Logging in' : 'Log in'} {popoverBody && ( {popoverBody} )} ) return <> } export default Unauthorized ================================================ FILE: packages/studio/src/toolbars/GlobalToolbar/RightStrip/AuthState/shared.tsx ================================================ export {} ================================================ FILE: packages/studio/src/toolbars/GlobalToolbar/RightStrip/RightStrip.tsx ================================================ import React from 'react' import styled from 'styled-components' import AuthState from './AuthState/AuthState' const Container = styled.div` margin-right: 12px; display: flex; align-items: flex-start; justify-content: flex-end; gap: 12px; flex-wrap: nowrap; ` const RightStrip: React.FC<{}> = (props) => { return ( ) } export default RightStrip ================================================ FILE: packages/studio/src/toolbars/GlobalToolbar/globalToolbarHooks.tsx ================================================ import {usePrism, useVal} from '@theatre/react' import getStudio from '@theatre/studio/getStudio' import React, {useMemo, useRef} from 'react' import useTooltip from '@theatre/studio/uiComponents/Popover/useTooltip' import ErrorTooltip from '@theatre/studio/uiComponents/Popover/ErrorTooltip' import BasicTooltip from '@theatre/studio/uiComponents/Popover/BasicTooltip' import {val} from '@theatre/dataverse' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import MoreMenu from './MoreMenu/MoreMenu' let showedVisualTestingWarning = false export function useOutlineTriggerTooltip( conflicts: ReturnType, ) { return useTooltip( {enabled: conflicts.length > 0, enterDelay: conflicts.length > 0 ? 0 : 200}, () => conflicts.length > 0 ? ( {conflicts.length === 1 ? `There is a state conflict in project "${conflicts[0].projectId}". Select the project in the outline below in order to fix it.` : `There are ${conflicts.length} projects that have state conflicts. They are highlighted in the outline below. `} ) : ( <>Outline ), ) } export function uesConflicts() { return usePrism(() => { const ephemeralStateOfAllProjects = val( getStudio().ephemeralAtom.pointer.coreByProject, ) return Object.entries(ephemeralStateOfAllProjects) .map(([projectId, state]) => ({projectId, state})) .filter( ({state}) => state.loadingState.type === 'browserStateIsNotBasedOnDiskState', ) }, []) } export function useMoreMenu() { const moreMenu = usePopover( () => { const triggerBounds = moreMenuTriggerRef.current!.getBoundingClientRect() return { debugName: 'More Menu', constraints: { maxX: triggerBounds.right, maxY: 8, // MVP: Don't render the more menu all the way to the left // when it doesn't fit on the screen height // See https://linear.app/theatre/issue/P-178/bug-broken-updater-ui-in-simple-html-page // 1/10 There's a better way to solve this. // 1/10 Perhaps consider separate constraint like "rightSideMinX" & for future: "bottomSideMinY" // 2/10 Or, consider constraints being a function of the dimensions of the box => constraints. minX: triggerBounds.left - 140, minY: 8, }, verticalGap: 2, } }, () => { return }, ) const moreMenuTriggerRef = useRef(null) return {moreMenu, moreMenuTriggerRef} } export function useShouldShowUpdatesBadge(): boolean { const hasUpdates = useVal( getStudio().ahistoricAtom.pointer.updateChecker.result.hasUpdates, ) === true return useMemo(() => { if (window.__IS_VISUAL_REGRESSION_TESTING) { if (!showedVisualTestingWarning) { showedVisualTestingWarning = true console.warn( "Visual regression testing enabled, so we're showing the updates badge unconditionally", ) } } if (hasUpdates || window.__IS_VISUAL_REGRESSION_TESTING) { return true } return hasUpdates }, [hasUpdates]) } ================================================ FILE: packages/studio/src/toolbars/PinButton.tsx ================================================ import styled from 'styled-components' import type {ComponentPropsWithRef, ReactNode} from 'react' import React, {forwardRef, useState} from 'react' import ToolbarIconButton from '@theatre/studio/uiComponents/toolbar/ToolbarIconButton' const Container = styled(ToolbarIconButton)<{pinned?: boolean}>` color: ${({pinned}) => (pinned ? 'rgba(255, 255, 255, 0.8)' : '#A8A8A9')}; border-bottom: 1px solid ${({pinned}) => pinned ? 'rgba(255, 255, 255, 0.7)' : 'rgba(255, 255, 255, 0.08)'}; ` interface PinButtonProps extends ComponentPropsWithRef<'button'> { icon: ReactNode pinHintIcon: ReactNode unpinHintIcon: ReactNode hint?: boolean pinned?: boolean } const PinButton = forwardRef( ( {children, hint, pinned, icon, pinHintIcon, unpinHintIcon, ...props}, ref, ) => { const [hovered, setHovered] = useState(false) const showHint = hovered || hint return ( setHovered(true)} onMouseOut={() => setHovered(false)} > {/* Necessary for hover to work properly. */}
{showHint && !pinned ? pinHintIcon : showHint && pinned ? unpinHintIcon : icon}
{children}
) }, ) export default PinButton ================================================ FILE: packages/studio/src/uiComponents/DetailPanelButton.tsx ================================================ import styled from 'styled-components' const DetailPanelButton = styled.button<{disabled?: boolean}>` text-align: center; padding: 8px; border-radius: 2px; border: 1px solid #627b7b87; background-color: #4b787d3d; color: #eaeaea; font-weight: 400; display: block; appearance: none; flex-grow: 1; cursor: ${(props) => (props.disabled ? 'none' : 'pointer')}; opacity: ${(props) => (props.disabled ? 0.4 : 1)}; &:hover { background-color: #7dc1c878; border-color: #9ebcbf; } ` export default DetailPanelButton ================================================ FILE: packages/studio/src/uiComponents/ExternalLink.tsx ================================================ import React from 'react' import {RxExternalLink} from 'react-icons/rx' import styled from 'styled-components' const A = styled.a` text-decoration: none; border-bottom: 1px solid #888; position: relative; display: inline-block; margin-left: 0.4em; &:hover, &:active { border-color: #ccc; } ` const IconContainer = styled.span` padding-right: 0.2em; fotn-size: 0.8em; position: relative; top: 2px; ` const ExternalLink = React.forwardRef< HTMLAnchorElement, React.ComponentProps >(({children, ...props}, ref) => { return ( {/* */} {children} ) }) export default ExternalLink ================================================ FILE: packages/studio/src/uiComponents/PointerEventsHandler.tsx ================================================ import type {$IntentionalAny} from '@theatre/core/types/public' import React, { createContext, forwardRef, useContext, useLayoutEffect, useMemo, useState, } from 'react' import styled from 'styled-components' // using an ID to make CSS selectors faster const elementId = 'pointer-root' /** * When the cursor is locked, this css var is added to #pointer-root * whose value will be the locked cursor (e.g. ew-resize). * * Look up references of this constant for examples of how it is used. * * See {@link useCssCursorLock} - code that locks the cursor */ export const lockedCursorCssVarName = '--lockedCursor' const Container = styled.div` pointer-events: auto; &.normal { pointer-events: none; } ` const CursorOverride = styled.div` position: absolute; inset: 0; pointer-events: none; #pointer-root:not(.normal) > & { pointer-events: auto; } ` type Context = { getLock: (className: string, cursor?: string) => () => void } type Lock = {className: string; cursor?: string} const context = createContext({} as $IntentionalAny) const PointerEventsHandler: React.FC<{ className?: string children?: React.ReactNode }> = forwardRef((props, ref) => { const [locks, setLocks] = useState([]) const contextValue = useMemo(() => { const getLock = (className: string, cursor?: string) => { const lock = {className, cursor} setLocks((s) => [...s, lock]) const unlock = () => { setLocks((s) => s.filter((l) => l !== lock)) } return unlock } return { getLock, } }, []) const lockedCursor = locks[0]?.cursor ?? '' return ( {props.children} ) }) /** * A "locking" mechanism for managing style.cursor values. * * Putting this behind a lock is important so we can properly manage * multiple features all coordinating to style the cursor. * * This will also track a stack of different cursor styles so that * adding a style to be the "foremost" cursor can override a previous style, * but then "unlocking" that style will again reveal the existing styles. * * It behaves a bit like a stack. * * See {@link lockedCursorCssVarName} */ export const useCssCursorLock = ( /** Whether to enable the provided cursor style */ enabled: boolean, className: string, /** e.g. `"ew"`, `"help"`, `"pointer"`, `"text"`, etc */ cursor?: string, ) => { const ctx = useContext(context) useLayoutEffect(() => { if (!enabled) return return ctx.getLock(className, cursor) }, [enabled, className, cursor]) } export default PointerEventsHandler ================================================ FILE: packages/studio/src/uiComponents/Popover/ArrowContext.tsx ================================================ import {createContext} from 'react' const ArrowContext = createContext>({}) export default ArrowContext ================================================ FILE: packages/studio/src/uiComponents/Popover/BasicPopover.tsx ================================================ import type {$IntentionalAny} from '@theatre/core/types/public' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import React from 'react' import styled from 'styled-components' import PopoverArrow from './PopoverArrow' export const popoverBackgroundColor = `rgb(51 45 66 / 40%)` const Container = styled.div` position: absolute; --popover-bg: ${popoverBackgroundColor}; --popover-inner-stroke: #505159; --popover-outer-stroke: rgb(86 100 110 / 46%); border-radius: 4px; box-shadow: rgb(0 0 0 / 25%) 0px 2px 4px; backdrop-filter: blur(8px) saturate(300%) contrast(65%) brightness(55%); /* background-color: rgb(45 46 66 / 50%); */ border: 0.5px solid var(--popover-outer-stroke); background: var(--popover-bg); /* border: 1px solid var(--popover-inner-stroke); */ color: white; padding: 1px 2px 1px 10px; margin: 0; cursor: default; ${pointerEventsAutoInNormalMode}; z-index: 10000; & a { color: inherit; } ` const BasicPopover: React.FC<{ className?: string showPopoverEdgeTriangle?: boolean children: React.ReactNode ref?: React.Ref }> = React.forwardRef( ( { children, className, showPopoverEdgeTriangle: showPopoverEdgeTriangle = false, }, ref, ) => { return ( {showPopoverEdgeTriangle ? : undefined} {children} ) }, ) export default BasicPopover ================================================ FILE: packages/studio/src/uiComponents/Popover/BasicTooltip.tsx ================================================ import styled from 'styled-components' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import React from 'react' import type {$IntentionalAny} from '@theatre/core/types/public' const Container = styled.div` position: absolute; color: white; padding: 0; margin: 0; cursor: default; ${pointerEventsAutoInNormalMode}; color: white; box-sizing: border-box; border-radius: 4px; box-shadow: rgb(0 0 0 / 25%) 0px 2px 4px; backdrop-filter: blur(8px) saturate(300%) contrast(65%) brightness(55%); background-color: rgb(45 46 66 / 50%); border: 0.5px solid rgb(86 100 110 / 46%); z-index: 10000; padding: 8px 8px; font-size: 10px; z-index: 10000; & a { color: inherit; } max-width: 240px; padding: 8px; pointer-events: none !important; ` const BasicTooltip = React.forwardRef( ( { children, className, }: { className?: string showPopoverEdgeTriangle?: boolean children: React.ReactNode }, ref, ) => { return ( {children} ) }, ) export default BasicTooltip ================================================ FILE: packages/studio/src/uiComponents/Popover/ErrorTooltip.tsx ================================================ import styled from 'styled-components' import BasicTooltip from './BasicTooltip' const ErrorTooltip = styled(BasicTooltip)` --popover-outer-stroke: #e11c1c; --popover-inner-stroke: #2c1c1c; --popover-bg: #2c1c1c; pointer-events: none !important; ` export default ErrorTooltip ================================================ FILE: packages/studio/src/uiComponents/Popover/MinimalTooltip.tsx ================================================ import styled from 'styled-components' import BasicTooltip from './BasicTooltip' const MinimalTooltip = styled(BasicTooltip)` padding: 6px; ` export default MinimalTooltip ================================================ FILE: packages/studio/src/uiComponents/Popover/PopoverArrow.tsx ================================================ import React, {forwardRef, useContext} from 'react' import styled from 'styled-components' import ArrowContext from './ArrowContext' const Container = styled.div` position: absolute; width: 0; height: 0; color: var(--popover-arrow-color); pointer-events: none; ` const Adjust = styled.div` width: 12px; height: 8px; position: absolute; left: -7px; top: -6px; text-align: center; line-height: 0; ` type Props = { className?: string color?: string } const InnerTriangle = styled.path` fill: var(--popover-bg); ` const InnerStroke = styled.path` fill: var(--popover-inner-stroke); ` const OuterStroke = styled.path` fill: var(--popover-outer-stroke); ` const PopoverArrow = forwardRef(({className}, ref) => { const arrowStyle = useContext(ArrowContext) return ( {/* */} ) }) export default PopoverArrow ================================================ FILE: packages/studio/src/uiComponents/Popover/PopoverPositioner.tsx ================================================ import React from 'react' import {cloneElement, useLayoutEffect, useState} from 'react' import useWindowSize from 'react-use/esm/useWindowSize' import useBoundingClientRect from '@theatre/studio/uiComponents/useBoundingClientRect' import ArrowContext from './ArrowContext' import useRefAndState from '@theatre/studio/utils/useRefAndState' import useOnClickOutside from '@theatre/studio/uiComponents/useOnClickOutside' import onPointerOutside from '@theatre/studio/uiComponents/onPointerOutside' import noop from '@theatre/utils/noop' import {clamp} from 'lodash-es' const minimumDistanceOfArrowToEdgeOfPopover = 8 export type AbsolutePlacementBoxConstraints = { minX?: number maxX?: number minY?: number maxY?: number } export type PopoverPositionerProps = { target: Element | React.MutableRefObject onClickOutside?: (e: MouseEvent) => void children: () => React.ReactElement onPointerOutside?: { threshold: number callback: (e: MouseEvent) => void } verticalPlacement?: 'top' | 'bottom' | 'overlay' verticalGap?: number // Has no effect if verticalPlacement === 'overlay' constraints?: AbsolutePlacementBoxConstraints } const PopoverPositioner: React.FC = (props) => { const originalElement = props.children() const [ref, container] = useRefAndState(null) const style: Record = originalElement.props.style ? {...originalElement.props.style} : {} style.position = 'absolute' const containerRect = useBoundingClientRect(container) const targetRect = useBoundingClientRect(props.target) const windowSize = useWindowSize() const [arrowContextValue, setArrowContextValue] = useState< Record >({}) useLayoutEffect(() => { if (!containerRect || !container || !targetRect) return const gap = props.verticalGap ?? 8 const arrowStyle: Record = {} let verticalPlacement: 'bottom' | 'top' | 'overlay' = props.verticalPlacement ?? 'bottom' let top = 0 let left = 0 if (verticalPlacement === 'bottom') { if (targetRect.bottom + containerRect.height + gap < windowSize.height) { verticalPlacement = 'bottom' top = targetRect.bottom + gap arrowStyle.top = '0px' } else if (targetRect.top > containerRect.height + gap) { verticalPlacement = 'top' top = targetRect.top - (containerRect.height + gap) arrowStyle.bottom = '0px' arrowStyle.transform = 'rotateZ(180deg)' } else { verticalPlacement = 'overlay' } } else if (verticalPlacement === 'top') { if (targetRect.top > containerRect.height + gap) { verticalPlacement = 'top' top = targetRect.top - (containerRect.height + gap) arrowStyle.bottom = '0px' arrowStyle.transform = 'rotateZ(180deg)' } else if ( targetRect.bottom + containerRect.height + gap < windowSize.height ) { verticalPlacement = 'bottom' top = targetRect.bottom + gap arrowStyle.top = '0px' } else { verticalPlacement = 'overlay' } } let arrowLeft = 0 if (verticalPlacement !== 'overlay') { const anchorLeft = targetRect.left + targetRect.width / 2 if (anchorLeft < containerRect.width / 2) { left = gap arrowLeft = Math.max( anchorLeft - gap, minimumDistanceOfArrowToEdgeOfPopover, ) } else if (anchorLeft + containerRect.width / 2 > windowSize.width) { left = windowSize.width - (gap + containerRect.width) arrowLeft = Math.min( anchorLeft - left, containerRect.width - minimumDistanceOfArrowToEdgeOfPopover, ) } else { left = anchorLeft - containerRect.width / 2 arrowLeft = containerRect.width / 2 } arrowStyle.left = arrowLeft + 'px' } const { minX = -Infinity, maxX = Infinity, minY = -Infinity, maxY = Infinity, } = props.constraints ?? {} const pos = { left: clamp(left, minX, maxX - containerRect.width), top: clamp(top, minY, maxY + containerRect.height), } container.style.left = pos.left + 'px' container.style.top = pos.top + 'px' setArrowContextValue(arrowStyle) if (props.onPointerOutside) { return onPointerOutside( container, props.onPointerOutside.threshold, props.onPointerOutside.callback, ) } }, [ containerRect, container, props.target, targetRect, windowSize, props.onPointerOutside, ]) useOnClickOutside( [container, props.target ?? null], props.onClickOutside ?? noop, ) return ( {cloneElement(originalElement, {ref, style})} ) } export default PopoverPositioner ================================================ FILE: packages/studio/src/uiComponents/Popover/SimplePopover.tsx ================================================ import React, {useContext} from 'react' import BasicPopover from './BasicPopover' import {mergeRefs} from 'react-merge-refs' import {createPortal} from 'react-dom' import {PortalContext} from 'reakit' import type {PopoverPositionerProps} from './PopoverPositioner' import PopoverPositioner from './PopoverPositioner' type Props = { className?: string children: React.ReactNode isOpen?: boolean } & Omit const SimplePopover = React.forwardRef<{}, Props>((props, ref) => { const portalLayer = useContext(PortalContext) if (!portalLayer) { return <> } return props.isOpen !== false ? ( createPortal( } target={props.target} onClickOutside={props.onClickOutside} onPointerOutside={props.onPointerOutside} constraints={props.constraints} verticalGap={props.verticalGap} />, portalLayer!, ) ) : ( <> ) }) export default SimplePopover ================================================ FILE: packages/studio/src/uiComponents/Popover/TooltipContext.tsx ================================================ import {Atom} from '@theatre/dataverse' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {useCallback, useEffect, useMemo} from 'react' let lastTooltipId = 0 export const useTooltipOpenState = (): [ isOpen: boolean, setIsOpen: (isOpen: boolean, delay: number) => void, ] => { const id = useMemo(() => lastTooltipId++, []) const [isOpenRef, isOpen] = useRefAndState(false) const setIsOpen = useCallback((shouldOpen: boolean, delay: number) => { setCurrentTooltipId(shouldOpen ? id : -1, delay) }, []) useEffect(() => { return currentTooltip.onStale(() => { const flag = currentTooltip.getValue() === id if (isOpenRef.current !== flag) isOpenRef.current = flag }) }, [currentTooltip, id]) return [isOpen, setIsOpen] } const currentTooltipId = new Atom(-1) let lastTimeout: NodeJS.Timeout | undefined = undefined const setCurrentTooltipId = (id: number, delay: number) => { const overridingPreviousTimeout = lastTimeout !== undefined if (lastTimeout !== undefined) { clearTimeout(lastTimeout) lastTimeout = undefined } if (delay === 0 || overridingPreviousTimeout) { currentTooltipId.set(id) } else { lastTimeout = setTimeout(() => { currentTooltipId.set(id) lastTimeout = undefined }, delay) } } const currentTooltip = currentTooltipId.prism export const closeAllTooltips = () => { setCurrentTooltipId(-1, 0) } ================================================ FILE: packages/studio/src/uiComponents/Popover/TooltipWithIcon.tsx ================================================ import styled from 'styled-components' import React from 'react' import type {$IntentionalAny} from '@theatre/core/types/public' import BasicTooltip from './BasicTooltip' const Container = styled(BasicTooltip)` display: flex; align-items: center; height: 30px; position: relative; ` const Title = styled.div` text-wrap: nowrap; ` const IconContainer = styled.div` background: #59595938; border-radius: 4px; border: 0.5px solid #ffffff1a; color: white; padding: 4px; font-size: 10px; /* margin: 0; */ margin-left: 12px; box-shadow: black 0px 2px 8px -4px; flex-wrap: nowrap; ` const TooltipWithIcon: React.FC<{ className?: string children: React.ReactNode icon: React.ReactNode }> = React.forwardRef(({children, icon, className}, ref) => { return ( {children} {icon} ) }) export default TooltipWithIcon ================================================ FILE: packages/studio/src/uiComponents/Popover/usePopover.tsx ================================================ import {usePointerCapturing} from '@theatre/studio/UIRoot/PointerCapturing' import useRefAndState from '@theatre/studio/utils/useRefAndState' import React, {useCallback, useContext, useEffect, useRef} from 'react' import {createPortal} from 'react-dom' import {PortalContext} from 'reakit' import type {AbsolutePlacementBoxConstraints} from './PopoverPositioner' import PopoverPositioner from './PopoverPositioner' import {contextMenuShownContext} from '@theatre/studio/panels/DetailPanel/DetailPanel' export type OpenFn = ( e: | React.MouseEvent | MouseEvent | {clientX: number; clientY: number} | undefined, target: HTMLElement | SVGElement | Element, ) => void type CloseFn = (reason: string) => void type State = | {isOpen: false} | { isOpen: true clickPoint: { clientX: number clientY: number } target: HTMLElement | SVGElement | Element opts: Opts onPointerOutside?: { threshold: number callback: (e: MouseEvent) => void } onClickOutside: () => void } const PopoverAutoCloseLock = React.createContext({ // defaults have no effects, since there would not be a // parent popover to worry about auto-closing. takeFocus() { return { releaseFocus() {}, } }, }) type Opts = { debugName: string closeWhenPointerIsDistant?: boolean pointerDistanceThreshold?: number closeOnClickOutside?: boolean constraints?: AbsolutePlacementBoxConstraints verticalGap?: number } export interface IPopover { /** * The React node of the popover. Insert into your JSX using \{node\}. Its state * will be managed automatically. */ node: React.ReactNode open: OpenFn close: CloseFn toggle: OpenFn isOpen: boolean } /** * @deprecated Use useChordial() instead. */ export default function usePopover( opts: Opts | (() => Opts), render: () => React.ReactElement, ): IPopover { const _debug = (...args: any) => {} // want to make sure that we don't close a popover when dragging something (like a curve editor handle) // I think this could be improved to handle closing after done dragging, better. const {isPointerBeingCaptured} = usePointerCapturing(`usePopover`) const [stateRef, state] = useRefAndState({ isOpen: false, }) const optsRef = useRef(opts) const close = useCallback((reason: string): void => { _debug(`closing due to "${reason}"`) stateRef.current = {isOpen: false} }, []) const open = useCallback((e, target) => { const opts = typeof optsRef.current === 'function' ? optsRef.current() : optsRef.current function onClickOutside(): void { if (lock.childHasFocusRef.current) return if (opts.closeOnClickOutside !== false) { close('clicked outside popover') } } stateRef.current = { isOpen: true, clickPoint: {clientX: e?.clientX ?? 0, clientY: e?.clientY ?? 0}, target, opts, onClickOutside: onClickOutside, onPointerOutside: opts.closeWhenPointerIsDistant === false ? undefined : { threshold: opts.pointerDistanceThreshold ?? 100, callback: () => { if (lock.childHasFocusRef.current) return // this is a bit weird, because when you stop capturing, then the popover can close on you... // TODO: Better fixes? if (isPointerBeingCaptured()) return close('pointer outside') }, }, } }, []) const toggle = useCallback((...args) => { if (stateRef.current.isOpen) { close('toggled') } else { open(...args) } }, []) /** * See doc comment on {@link useAutoCloseLockState}. * Used to ensure that moving far away from a parent popover doesn't * close a child popover. */ const lock = useAutoCloseLockState({ _debug, state, }) // TODO: this lock is now exported from the detail panel, do refactor it when you get the chance const [, addContextMenu] = useContext(contextMenuShownContext) useEffect(() => { let removeContextMenu: () => void | undefined if (state.isOpen) { removeContextMenu = addContextMenu() } return () => removeContextMenu?.() }, [state.isOpen]) const portalLayer = useContext(PortalContext) const node = state.isOpen ? ( createPortal( , portalLayer!, ) ) : ( <> ) return {node, open, close, toggle, isOpen: state.isOpen} } /** * Keep track of the current lock state, and provide * a lock that can be passed down to popover children. * * Used to ensure that moving far away from a parent popover doesn't * close a child popover. * When child popovers are opened, we want to suspend all auto-closing * behaviors for parenting popovers. */ function useAutoCloseLockState(options: { state: {isOpen: boolean} _debug: (message: string, args?: object) => void }) { const parentLock = useContext(PopoverAutoCloseLock) useEffect(() => { if (options.state.isOpen) { // when this "popover" is open, then take focus from parent const focused = parentLock.takeFocus() options._debug('take focus') return () => { // when closed / unmounted, release focus options._debug('release focus') focused.releaseFocus() } } }, [options.state.isOpen]) // child of us const childHasFocusRef = useRef(false) return { childHasFocusRef: childHasFocusRef, childPopoverLock: { takeFocus() { childHasFocusRef.current = true return { releaseFocus() { childHasFocusRef.current = false }, } }, }, } } ================================================ FILE: packages/studio/src/uiComponents/Popover/usePopoverPosition.ts ================================================ import type React from 'react' import {useLayoutEffect, useState} from 'react' import useWindowSize from 'react-use/esm/useWindowSize' import useBoundingClientRect from '@theatre/studio/uiComponents/useBoundingClientRect' import useRefAndState from '@theatre/studio/utils/useRefAndState' import {clamp} from 'lodash-es' const minimumDistanceOfArrowToEdgeOfPopover = 8 type AbsolutePlacementBoxConstraints = { minX?: number maxX?: number minY?: number maxY?: number } const usePopoverPosition = (props: { target: HTMLElement | SVGElement | Element | null | undefined verticalPlacement?: 'top' | 'bottom' | 'overlay' verticalGap?: number // Has no effect if verticalPlacement === 'overlay' constraints?: AbsolutePlacementBoxConstraints }): [ containerRef: React.MutableRefObject, position: undefined | {top: number; left: number}, ] => { const [containerRef, container] = useRefAndState< HTMLElement | SVGElement | null >(null) const containerRect = useBoundingClientRect(container) const targetRect = useBoundingClientRect(props.target) const windowSize = useWindowSize() const [arrowContextValue, setArrowContextValue] = useState< Record >({}) const [positionRef, position] = useRefAndState< undefined | {left: number; top: number} >(undefined) useLayoutEffect(() => { if (!containerRect || !targetRect) { positionRef.current = undefined return } const gap = props.verticalGap ?? 8 const arrowStyle: Record = {} let verticalPlacement: 'bottom' | 'top' | 'overlay' = props.verticalPlacement ?? 'bottom' let top = 0 let left = 0 if (verticalPlacement === 'bottom') { if (targetRect.bottom + containerRect.height + gap < windowSize.height) { verticalPlacement = 'bottom' top = targetRect.bottom + gap arrowStyle.top = '0px' } else if (targetRect.top > containerRect.height + gap) { verticalPlacement = 'top' top = targetRect.top - (containerRect.height + gap) arrowStyle.bottom = '0px' arrowStyle.transform = 'rotateZ(180deg)' } else { verticalPlacement = 'overlay' } } else if (verticalPlacement === 'top') { if (targetRect.top > containerRect.height + gap) { verticalPlacement = 'top' top = targetRect.top - (containerRect.height + gap) arrowStyle.bottom = '0px' arrowStyle.transform = 'rotateZ(180deg)' } else if ( targetRect.bottom + containerRect.height + gap < windowSize.height ) { verticalPlacement = 'bottom' top = targetRect.bottom + gap arrowStyle.top = '0px' } else { verticalPlacement = 'overlay' } } let arrowLeft = 0 if (verticalPlacement !== 'overlay') { const anchorLeft = targetRect.left + targetRect.width / 2 if (anchorLeft < containerRect.width / 2) { left = gap arrowLeft = Math.max( anchorLeft - gap, minimumDistanceOfArrowToEdgeOfPopover, ) } else if (anchorLeft + containerRect.width / 2 > windowSize.width) { left = windowSize.width - (gap + containerRect.width) arrowLeft = Math.min( anchorLeft - left, containerRect.width - minimumDistanceOfArrowToEdgeOfPopover, ) } else { left = anchorLeft - containerRect.width / 2 arrowLeft = containerRect.width / 2 } arrowStyle.left = arrowLeft + 'px' } const { minX = -Infinity, maxX = Infinity, minY = -Infinity, maxY = Infinity, } = props.constraints ?? {} const pos = { left: clamp(left, minX, maxX - containerRect.width), top: clamp(top, minY, maxY + containerRect.height), } positionRef.current = pos }, [containerRect, props.target, targetRect, windowSize]) return [containerRef, position] } export default usePopoverPosition ================================================ FILE: packages/studio/src/uiComponents/Popover/useTooltip.tsx ================================================ import useRefAndState from '@theatre/studio/utils/useRefAndState' import type {MutableRefObject} from 'react' import {useContext} from 'react' import React from 'react' import PopoverPositioner from './PopoverPositioner' import {createPortal} from 'react-dom' import {PortalContext} from 'reakit' import noop from '@theatre/utils/noop' import type {$IntentionalAny} from '@theatre/core/types/public' import {Atom} from '@theatre/dataverse' import {useCallback, useEffect, useMemo} from 'react' /** * Useful helper in development to prevent the tooltips from auto-closing, * so its easier to inspect the DOM / change the styles, etc. * * Call window.$disableAutoCloseTooltip() in the console to disable auto-close */ const shouldAutoCloseByDefault = process.env.NODE_ENV === 'development' ? (): boolean => (window as $IntentionalAny).__disableAutoCloseTooltip ?? true : (): boolean => true if (process.env.NODE_ENV === 'development') { ;(window as $IntentionalAny).$disableAutoCloseTooltip = () => { ;(window as $IntentionalAny).__disableAutoCloseTooltip = false } } export default function useTooltip( opts: { enabled?: boolean enterDelay?: number exitDelay?: number verticalPlacement?: 'top' | 'bottom' | 'overlay' verticalGap?: number }, render: () => React.ReactElement, ): [ node: React.ReactNode, targetRef: MutableRefObject, isOpen: boolean, ] { const enabled = opts.enabled !== false const [isOpen, setIsOpen] = useTooltipOpenState() const [targetRef, targetNode] = useRefAndState(null) useEffect(() => { if (!enabled) { return } const target = targetNode if (!target) return const onMouseEnter = () => setIsOpen(true, opts.enterDelay ?? 800) const onMouseLeave = () => { if (shouldAutoCloseByDefault()) setIsOpen(false, opts.exitDelay ?? 200) } target.addEventListener('mouseenter', onMouseEnter) target.addEventListener('mouseleave', onMouseLeave) return () => { target.removeEventListener('mouseenter', onMouseEnter) target.removeEventListener('mouseleave', onMouseLeave) } }, [targetNode, enabled, opts.enterDelay, opts.exitDelay]) const portalLayer = useContext(PortalContext) const node = enabled && isOpen && targetNode ? ( createPortal( , portalLayer!, ) ) : ( <> ) return [node, targetRef, isOpen] } let lastTooltipId = 0 export const useTooltipOpenState = (): [ isOpen: boolean, setIsOpen: (isOpen: boolean, delay: number) => void, ] => { const id = useMemo(() => lastTooltipId++, []) const [isOpenRef, isOpen] = useRefAndState(false) const setIsOpen = useCallback((shouldOpen: boolean, delay: number) => { setCurrentTooltipId(shouldOpen ? id : -1, delay) }, []) useEffect(() => { return currentTooltip.onStale(() => { const flag = currentTooltip.getValue() === id if (isOpenRef.current !== flag) isOpenRef.current = flag }) }, [currentTooltip, id]) return [isOpen, setIsOpen] } const currentTooltipId = new Atom(-1) let lastTimeout: NodeJS.Timeout | undefined = undefined const setCurrentTooltipId = (id: number, delay: number) => { const overridingPreviousTimeout = lastTimeout !== undefined if (lastTimeout !== undefined) { clearTimeout(lastTimeout) lastTimeout = undefined } if (delay === 0 || overridingPreviousTimeout) { currentTooltipId.set(id) } else { lastTimeout = setTimeout(() => { currentTooltipId.set(id) lastTimeout = undefined }, delay) } } const currentTooltip = currentTooltipId.prism export const closeAllTooltips = () => { setCurrentTooltipId(-1, 0) } ================================================ FILE: packages/studio/src/uiComponents/RoomToClick.tsx ================================================ import React from 'react' import styled from 'styled-components' const Container = styled.div<{room: number}>` position: absolute; inset: ${(props) => props.room * -1}px; ` const RoomToClick: React.FC<{room: number}> = (props) => { return } export default RoomToClick ================================================ FILE: packages/studio/src/uiComponents/SVGIcon.tsx ================================================ import React from 'react' import styled, {css} from 'styled-components' const Container = styled.div<{sizing: Sizing}>` width: ${(props) => (props.sizing === 'em' ? '1em' : '100%')}; ${(props) => props.sizing === 'absoluteFill' && css` & > svg { position: absolute; top: 0; left: 0; right: 0; bottom: 0; } `} ` type Sizing = 'em' | 'fill' | 'none' | 'absoluteFill' const SvgIcon: React.FC<{ src: string sizing?: Sizing }> = (props) => { return ( ) } export default SvgIcon ================================================ FILE: packages/studio/src/uiComponents/ShowMousePosition.tsx ================================================ import mousePositionD from '@theatre/studio/utils/mousePositionD' import {usePrism} from '@theatre/react' import {val} from '@theatre/dataverse' import React from 'react' import {createPortal} from 'react-dom' const ShowMousePosition: React.FC<{}> = (props) => { const pos = usePrism( () => val(mousePositionD) ?? {clientX: 0, clientY: 0}, [], ) return createPortal( <>
, document.body, ) } export default ShowMousePosition ================================================ FILE: packages/studio/src/uiComponents/chordial/ChordialOverlay.tsx ================================================ import React, {useContext} from 'react' import {TooltipOverlay} from './TooltipOverlay' import {ContextOverlay} from './ContextOverlay' import {createPortal} from 'react-dom' import {PortalContext} from 'reakit' import {PopoverOverlay} from './PopoverOverlay' export const ChordialOverlay = () => { const portalLayer = useContext(PortalContext) if (!portalLayer) return null return createPortal( <> , portalLayer, ) } ================================================ FILE: packages/studio/src/uiComponents/chordial/ContextOverlay.tsx ================================================ import React from 'react' import {usePrism, useVal} from '@theatre/react' import type {ChodrialElement, ChordialOpts} from './chordialInternals' import {contextActor, contextStatus} from './contextActor' import ContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/ContextMenu/ContextMenu' import {val} from '@theatre/dataverse' export const ContextOverlay: React.FC<{}> = () => { const currentStatus = useVal(contextStatus) const s = usePrism((): | undefined | { originalMoseEvent: MouseEvent opts: ChordialOpts element: ChodrialElement } => { const status = val(contextStatus) if (!status) return undefined const optsFn = val(status.element.atom.pointer.optsFn) const opts = optsFn() return { opts, originalMoseEvent: status.originalMouseEvent, element: status.element, } }, []) if (!s) return null return ( { contextActor.send({type: 'requestClose', element: s.element}) }} /> ) } ================================================ FILE: packages/studio/src/uiComponents/chordial/PopoverOverlay.tsx ================================================ import React from 'react' import {usePrism} from '@theatre/react' import type {ChodrialElement, InvokeTypePopover} from './chordialInternals' import {val} from '@theatre/dataverse' import {popoverActor} from './popoverActor' import PopoverPositioner from '@theatre/studio/uiComponents/Popover/PopoverPositioner' import {usePointerCapturing} from '@theatre/studio/UIRoot/PointerCapturing' export const PopoverOverlay: React.FC<{}> = () => { const s = usePrism((): | undefined | ({ originalTriggerEvent: MouseEvent | undefined element: ChodrialElement domEl: Element } & InvokeTypePopover) => { const status = val(popoverActor.pointer) if (!status) return undefined const domEl = status.element.target if (!(domEl instanceof Element)) { return undefined } const optsFn = val(status.element.atom.pointer.optsFn) const opts = optsFn() const {invoke} = opts if (invoke && typeof invoke !== 'function' && invoke.type === 'popover') { return { ...invoke, domEl, originalTriggerEvent: status.originalTriggerEvent, element: status.element, } } else { return undefined } }, []) const {isPointerBeingCaptured} = usePointerCapturing(`PopoverOverlay`) if (!s) return null const close = () => { popoverActor.send({type: 'close', el: s.element}) } const onPointerOutside = s.closeWhenPointerIsDistant === false ? undefined : { threshold: s.pointerDistanceThreshold ?? 100, callback: () => { // if (lock.childHasFocusRef.current) return // this is a bit weird, because when you stop capturing, then the popover can close on you... // TODO: Better fixes? if (isPointerBeingCaptured()) return close() }, } return ( s.render({close})} target={s.domEl} onClickOutside={close} onPointerOutside={onPointerOutside} constraints={s.constraints} verticalGap={s.verticalGap} /> ) } ================================================ FILE: packages/studio/src/uiComponents/chordial/TooltipOverlay.tsx ================================================ import React from 'react' import type {$IntentionalAny} from '@theatre/core/types/public' import {val} from '@theatre/dataverse' import {usePrism, useVal} from '@theatre/react' import styled from 'styled-components' import usePopoverPosition from '@theatre/studio/uiComponents/Popover/usePopoverPosition' import {useTransition, animated, easings} from '@react-spring/web' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {tooltipTarget} from './tooltipActor' export const TooltipOverlay: React.FC<{}> = () => { const currentTarget = useVal(tooltipTarget) const tooltipDisabled = useVal(currentTarget?.atom.pointer.tooltipDisabled) ?? false const title = usePrism((): React.ReactNode => { const chordial = currentTarget if (!chordial) return null const a = chordial.atom const optsFn = val(a.pointer.optsFn) const opts = optsFn() return opts.title }, [currentTarget]) const [popoverContainerRef, positioning] = usePopoverPosition({ target: currentTarget?.target, }) const data: Array<{ key: string title: React.ReactNode positioning: {left: number; top: number} }> = [] const chordial = currentTarget if (chordial && positioning && !tooltipDisabled) { data.push({ key: chordial.id, title, positioning, }) } const transitions = useTransition(data, { from: { opacity: 0.5, transform: `translateY(0px) perspective(200px) scale(0.95) rotateX(-45deg) `, }, enter: { opacity: 1, transform: `translateY(0px) perspective(200px) scale(1) rotateX(0deg) `, }, leave: { opacity: 0, transform: `translateY(0px) perspective(200px) scale(0.9) rotateX(-10deg) `, }, keys: (item) => item.key, config: (item, index, phase) => (key) => { return { // velocity: phase === 'leave' ? 0.5 : 6, duration: phase === 'leave' ? 33 * 3 : 33 * 4, easing: easings.easeOutCubic, } }, }) return ( <> {title && ( } style={{opacity: 0}} > {title} {theIcon} )} {transitions((style, item) => { return ( {item.title} {theIcon} ) })} ) } const theIcon = ( ) const Container = styled(animated.div)` display: flex; align-items: center; height: 30px; position: relative; position: absolute; transform-origin: top center; cursor: default; ${pointerEventsAutoInNormalMode}; color: white; box-sizing: border-box; border-radius: 4px; box-shadow: rgb(0 0 0 / 25%) 0px 2px 4px; backdrop-filter: blur(8px) saturate(300%) contrast(65%) brightness(55%); background-color: rgb(45 46 66 / 50%); border: 0.5px solid rgb(86 100 110 / 46%); z-index: 10000; padding: 8px 8px; font-size: 10px; z-index: 10000; & a { color: inherit; } max-width: 240px; padding: 8px; pointer-events: none !important; ` const Title = styled.div` text-wrap: nowrap; ` const IconContainer = styled.div` background: #59595938; border-radius: 4px; border: 0.5px solid #ffffff1a; color: white; padding: 4px; font-size: 10px; /* margin: 0; */ margin-left: 12px; box-shadow: black 0px 2px 8px -4px; flex-wrap: nowrap; ` ================================================ FILE: packages/studio/src/uiComponents/chordial/chordialInternals.ts ================================================ import {Atom} from '@theatre/dataverse' import type {$IntentionalAny} from '@theatre/core/types/public' import {useEffect, type ElementType, type MutableRefObject} from 'react' import type {DragOpts} from '@theatre/studio/uiComponents/useDrag' import type React from 'react' import type {AbsolutePlacementBoxConstraints} from '@theatre/studio/uiComponents/Popover/PopoverPositioner' export type InvokeTypePopover = { type: 'popover' render: (props: {close: () => void}) => React.ReactElement closeWhenPointerIsDistant?: boolean pointerDistanceThreshold?: number closeOnClickOutside?: boolean constraints?: AbsolutePlacementBoxConstraints verticalGap?: number } export type InvokeType = | InvokeTypePopover | (( e: | { type: 'MouseEvent' event: MouseEvent } | { type: 'KeyboardEvent' event: KeyboardEvent } | undefined, ) => void) export type ChordialOpts = { // shown on the tooltip title: string | React.ReactNode // shown as the top item in the menu menuTitle?: string | React.ReactNode items: Array invoke?: InvokeType drag?: DragOpts } export type ContextMenuItem = | { type: 'normal' label: string | ElementType callback?: (e: React.MouseEvent) => void focus?: () => void enabled?: boolean key?: string } | {type: 'separator'} export type ChordialOptsFn = () => ChordialOpts export type ChodrialElement = { id: string returnValue: { targetRef: MutableRefObject<$IntentionalAny> useDisableTooltip: (disable: boolean) => void } target: HTMLElement | null | undefined atom: Atom<{optsFn: ChordialOptsFn; tooltipDisabled: boolean}> } export type MaybeChodrialEl = ChodrialElement | undefined let lastId = 0 export function createChordialElement(optsFn: ChordialOptsFn): ChodrialElement { const id = (lastId++).toString() const atom = new Atom({optsFn, tooltipDisabled: false}) const chordialRef: ChodrialElement = { id, target: null, atom, returnValue: { useDisableTooltip: (disable: boolean) => { useEffect(() => { atom.setByPointer((p) => p.tooltipDisabled, disable) return () => atom.setByPointer((p) => p.tooltipDisabled, false) }, [disable]) }, targetRef: { get current() { return chordialRef.target }, set current(target: HTMLElement | null | undefined) { chordialRef.target = target if (!target) return targetsWeakmap.set(target, chordialRef) }, }, }, } return chordialRef } export function findChodrialByDomNode( el: EventTarget | null, ): ChodrialElement | undefined { if (!(el instanceof HTMLElement)) return undefined let current = el while (current) { if (targetsWeakmap.has(current)) { return targetsWeakmap.get(current) } current = current.parentElement! } return undefined } const targetsWeakmap = new WeakMap() ================================================ FILE: packages/studio/src/uiComponents/chordial/contextActor.ts ================================================ import {basicFSM} from '@theatre/utils/basicFSM' import type {ChodrialElement} from './chordialInternals' import {findChodrialByDomNode} from './chordialInternals' import {prism, val} from '@theatre/dataverse' export const contextActor = basicFSM< | {type: 'rclick'; mouseEvent: MouseEvent} | {type: 'requestClose'; element: ChodrialElement}, {element: ChodrialElement; originalMouseEvent: MouseEvent} | undefined >((t) => { function idle() { t('idle', undefined, (e) => { switch (e.type) { case 'rclick': const chordialEl = findChodrialByDomNode(e.mouseEvent.target) if (chordialEl) { active(e.mouseEvent, chordialEl) } break default: break } }) } function active(originalEvent: MouseEvent, originalEl: ChodrialElement) { originalEvent.preventDefault() originalEvent.stopPropagation() t( 'active', {element: originalEl, originalMouseEvent: originalEvent}, (e) => { switch (e.type) { case 'rclick': const newEl = findChodrialByDomNode(e.mouseEvent.target) if (!newEl) return idle() active(e.mouseEvent, newEl) break case 'requestClose': if (e.element === originalEl) { idle() } break } }, ) } idle() })({name: 'contextActor'}) export const contextStatus = prism(() => val(contextActor.pointer)) ================================================ FILE: packages/studio/src/uiComponents/chordial/gestureActor.ts ================================================ import {basicFSM} from '@theatre/utils/basicFSM' import type {DragHandlers, DragOpts} from '@theatre/studio/uiComponents/useDrag' import { DRAG_DETECTION_DISTANCE_THRESHOLD, MouseButton, didPointerLockCauseMovement, } from '@theatre/studio/uiComponents/useDrag' import {isSafari} from '@theatre/studio/uiComponents/isSafari' import type {CapturedPointer} from '@theatre/studio/UIRoot/PointerCapturing' import {createPointerCapturing} from '@theatre/studio/UIRoot/PointerCapturing' import type { ChodrialElement, ChordialOpts, InvokeType, } from './chordialInternals' import {findChodrialByDomNode} from './chordialInternals' import {popoverActor} from './popoverActor' function handleInvoke( invoke: undefined | InvokeType, el: ChodrialElement, mouseEvent: MouseEvent, ) { if (!invoke) return if (typeof invoke === 'function') { invoke({type: 'MouseEvent', event: mouseEvent}) } else if (invoke.type === 'popover') { popoverActor.send({type: 'open', el, triggerEvent: mouseEvent}) } } export const gestureActor = basicFSM< | {type: 'mousedown'; mouseEvent: MouseEvent} | {type: 'mouseup'; mouseEvent: MouseEvent} | {type: 'mousemove'; mouseEvent: MouseEvent} | {type: 'click'; mouseEvent: MouseEvent}, undefined | ChodrialElement >((t) => { function idle() { t('idle', undefined, (e) => { switch (e.type) { case 'click': { const el = findChodrialByDomNode(e.mouseEvent.target) if (!el) return const {invoke} = el.atom.get().optsFn() handleInvoke(invoke, el, e.mouseEvent) } break case 'mousedown': { const el = findChodrialByDomNode(e.mouseEvent.target) if (!el) return const opts = el.atom.get().optsFn() const {drag} = opts if (!drag || drag.disabled === true) return // defensively release // TIODO // capturedPointerRef.current?.release() const acceptedButtons: MouseButton[] = drag.buttons ?? [ MouseButton.Left, ] if (!acceptedButtons.includes(e.mouseEvent.button)) return const dragHandlers = drag.onDragStart(e.mouseEvent) if (dragHandlers === false) { // we should ignore the gesture return } // need to capture pointer after we know the provided handler wants to handle drag start const capturedPointer = createPointerCapturing('Drag').capturing.capturePointer( 'dragStart', ) if (!drag.dontBlockMouseDown) { e.mouseEvent.stopPropagation() e.mouseEvent.preventDefault() } beforeDetected( el, opts, drag, e.mouseEvent, dragHandlers, 0, capturedPointer, ) } break default: break } }) } function handleMouseup( el: ChodrialElement, opts: ChordialOpts, dragOpts: DragOpts, e: MouseEvent, handlers: DragHandlers, dragHappened: boolean, capturedPointer?: CapturedPointer, ) { capturedPointer?.release() if (dragOpts.shouldPointerLock && !isSafari) document.exitPointerLock() handlers.onDragEnd?.(dragHappened, e) // ensure that the window is focused after a successful drag // this fixes an issue where after dragging something like the playhead // through an iframe, you can immediately hit [space] and the animation // will play, even if you hadn't been focusing in the iframe at the start // of the drag. // Fixes https://linear.app/theatre/issue/P-177/beginners-scrubbing-the-playhead-from-within-an-iframe-then-[space] window.focus() if (!dragHappened) { handlers.onClick?.(e) handleInvoke(opts.invoke, el, e) // opts.invoke?.({type: 'MouseEvent', event: e}) } idle() } function beforeDetected( el: ChodrialElement, opts: ChordialOpts, dragOpts: DragOpts, mousedownEvent: MouseEvent, handlers: DragHandlers, originalTotalDistanceMoved: number, capturedPointer: CapturedPointer, ) { t('beforeDetected', el, (e) => { switch (e.mouseEvent.type) { case 'mouseup': handleMouseup( el, opts, dragOpts, e.mouseEvent, handlers, false, capturedPointer, ) break case 'mousemove': const isPointerLockUsed = dragOpts.shouldPointerLock && !isSafari if ( didPointerLockCauseMovement(e.mouseEvent, { detected: false, }) ) return const totalDistanceMoved = originalTotalDistanceMoved + Math.abs(e.mouseEvent.movementY) + Math.abs(e.mouseEvent.movementX) if (totalDistanceMoved > DRAG_DETECTION_DISTANCE_THRESHOLD) { if (isPointerLockUsed) { el.target!.requestPointerLock() } const stuff = { // detected: true, dragMovement: {x: 0, y: 0}, dragEventCount: 0, } afterDetected( el, opts, dragOpts, mousedownEvent, handlers, totalDistanceMoved, stuff.dragMovement, stuff.dragEventCount, capturedPointer, ) } break } }) } function afterDetected( el: ChodrialElement, opts: ChordialOpts, dragOpts: DragOpts, mousedownEvent: MouseEvent, handlers: DragHandlers, originalTotalDistanceMoved: number, originalDragMovement: {x: number; y: number}, dragEventCount: number, capturedPointer: CapturedPointer, ) { t('afterDetected', el, (e) => { switch (e.mouseEvent.type) { case 'mouseup': handleMouseup( el, opts, dragOpts, e.mouseEvent, handlers, true, capturedPointer, ) break case 'mousemove': const isPointerLockUsed = dragOpts.shouldPointerLock && !isSafari if ( didPointerLockCauseMovement(e.mouseEvent, { detected: true, dragEventCount, }) ) return const totalDistanceMoved = originalTotalDistanceMoved + Math.abs(e.mouseEvent.movementY) + Math.abs(e.mouseEvent.movementX) const dragMovement = isPointerLockUsed ? { // when locked, the pointer event screen position is going to be 0s, since the pointer can't move. // So, we use the movement on the event x: originalDragMovement.x + e.mouseEvent.movementX, y: originalDragMovement.y + e.mouseEvent.movementY, } : { x: e.mouseEvent.screenX - mousedownEvent.screenX, y: e.mouseEvent.screenY - mousedownEvent.screenY, } handlers.onDrag( dragMovement.x, dragMovement.y, e.mouseEvent, e.mouseEvent.movementX, e.mouseEvent.movementY, ) afterDetected( el, opts, dragOpts, mousedownEvent, handlers, totalDistanceMoved, dragMovement, dragEventCount + 1, capturedPointer, ) break } }) } idle() })({name: 'gestureActor'}) ================================================ FILE: packages/studio/src/uiComponents/chordial/hoverActor.ts ================================================ import {basicFSM} from '@theatre/utils/basicFSM' import type {ChodrialElement} from './chordialInternals' import {findChodrialByDomNode} from './chordialInternals' export const hoverActor = basicFSM< {type: 'mousemove'; mouseEvent: MouseEvent; source: 'window' | 'root'}, ChodrialElement | undefined >((transition) => { function idle() { transition('idle', undefined, (e) => { switch (e.type) { case 'mousemove': if (e.source === 'window') return const chordialEl = findChodrialByDomNode(e.mouseEvent.target) if (!chordialEl) return active(chordialEl) break } }) } function active(originalEl: ChodrialElement) { const activationTime = Date.now() transition('active', originalEl, (e) => { switch (e.type) { case 'mousemove': if (e.source === 'window') { if (Date.now() - activationTime > 100) { idle() } return } const newEl = findChodrialByDomNode(e.mouseEvent.target) if (newEl === originalEl) { active(originalEl) return } if (!newEl) { idle() return } active(newEl) break } }) } idle() })() ================================================ FILE: packages/studio/src/uiComponents/chordial/mousedownActor.ts ================================================ import {basicFSM} from '@theatre/utils/basicFSM' export const mousedownActor = basicFSM<[isDown: boolean], boolean>((t) => { function toggle(original: boolean) { t('down', original, ([isDown]) => { if (isDown !== original) toggle(isDown) }) } toggle(false) })({name: 'mouseDownActor'}) ================================================ FILE: packages/studio/src/uiComponents/chordial/popoverActor.ts ================================================ import {basicFSM} from '@theatre/utils/basicFSM' import type {ChodrialElement, InvokeTypePopover} from './chordialInternals' import {prism, val} from '@theatre/dataverse' export const popoverActor = basicFSM< | {type: 'open'; el: ChodrialElement; triggerEvent: MouseEvent | undefined} | {type: 'close'; el: ChodrialElement}, | ({ element: ChodrialElement originalTriggerEvent: MouseEvent | undefined } & InvokeTypePopover) | undefined >((transition) => { function idle() { transition('idle', undefined, (e) => { switch (e.type) { case 'open': const {el} = e const {invoke} = el.atom.get().optsFn() if ( invoke && typeof invoke !== 'function' && invoke.type === 'popover' ) { active(el, invoke, e.triggerEvent) } break case 'close': break } }) } function active( originalEl: ChodrialElement, invoke: InvokeTypePopover, triggerEvent: MouseEvent | undefined, ) { const activationTime = Date.now() transition( 'active', {element: originalEl, originalTriggerEvent: triggerEvent, ...invoke}, (e) => { switch (e.type) { case 'open': const {el} = e const {invoke} = el.atom.get().optsFn() if ( invoke && typeof invoke !== 'function' && invoke.type === 'popover' ) { active(el, invoke, e.triggerEvent) } break case 'close': if (e.el === originalEl) { idle() } break } }, ) } idle() })() export const popoverStatus = prism(() => val(popoverActor.pointer)) ================================================ FILE: packages/studio/src/uiComponents/chordial/tooltipActor.ts ================================================ import {basicFSM} from '@theatre/utils/basicFSM' import type {MaybeChodrialEl, ChodrialElement} from './chordialInternals' import {prism, val} from '@theatre/dataverse' import {contextActor} from './contextActor' import {hoverActor} from './hoverActor' import {mousedownActor} from './mousedownActor' /** * A state machine that determines which Chordial target should have a tooltip shown. */ export const tooltipActor = basicFSM< | {type: 'hoverTargetChange'; element: MaybeChodrialEl} // emitted when the mouse button is down, or some other gesture is active so that the tooltip must not be shown | {type: 'tooltipBlocked'} | {type: 'tooltipUnblocked'}, MaybeChodrialEl >((transition) => { function tooltipBlocked() { transition('tooltipBlocked', undefined, (e) => { switch (e.type) { case 'tooltipUnblocked': idle() break default: return } }) } const wrap = ( stateName: string, context: MaybeChodrialEl, take: (newHover: MaybeChodrialEl) => void, ): {isActive: boolean} => { const status = {isActive: true} transition(stateName, context, (e) => { switch (e.type) { case 'tooltipBlocked': status.isActive = false tooltipBlocked() break case 'hoverTargetChange': take(e.element) break default: // ? break } }) return status } function idle() { wrap('idle', undefined, (newHover: MaybeChodrialEl) => { if (!newHover) return waitingForActive(newHover) }) } // a 1s timeout before showing the tooltip function waitingForActive(originalHover: ChodrialElement) { const timeout = setTimeout(() => { if (status.isActive) active(originalHover) }, 1200) const status = wrap('waitingForActive', undefined, (newHover) => { clearTimeout(timeout) if (newHover) { waitingForActive(newHover) } else { idle() } }) } function active(currentHover: ChodrialElement) { wrap('active', currentHover, (newHover) => { if (newHover) { active(newHover) } else { waitingForIdle(currentHover) } }) } function waitingForIdle(currentHover: ChodrialElement) { const timeout = setTimeout(() => { if (status.isActive) waitingForRevive() }, 200) const status = wrap('waitingForIdle', currentHover, (newHover) => { if (!newHover) return clearTimeout(timeout) active(newHover) }) } function waitingForRevive() { const timeout = setTimeout(() => { if (status.isActive) idle() }, 300) const status = wrap('waitingForRevive', undefined, (newHover) => { if (!newHover) return clearTimeout(timeout) active(newHover) }) } idle() })({name: 'tooltipActor'}) const isTooltipBlocked = prism(() => { const isContextMenuOpen = !!val(contextActor.pointer) const isMouseDown = val(mousedownActor.pointer) return isContextMenuOpen || isMouseDown }) isTooltipBlocked.onStale(() => { tooltipActor.send({ type: val(isTooltipBlocked) ? 'tooltipBlocked' : 'tooltipUnblocked', }) }) const activeHoverTarget = prism(() => val(hoverActor.pointer)) activeHoverTarget.onStale(() => { tooltipActor.send({ type: 'hoverTargetChange', element: activeHoverTarget.getValue(), }) }) export const tooltipTarget = prism(() => val(tooltipActor.pointer)) ================================================ FILE: packages/studio/src/uiComponents/chordial/useChodrial.tsx ================================================ import {useEffect, useRef} from 'react' import type React from 'react' import type {ChordialOptsFn, ChodrialElement} from './chordialInternals' import {createChordialElement, findChodrialByDomNode} from './chordialInternals' import {hoverActor} from './hoverActor' import {contextActor} from './contextActor' import {gestureActor} from './gestureActor' import {mousedownActor} from './mousedownActor' export default function useChordial( optsFn: ChordialOptsFn, ): ChodrialElement['returnValue'] { const refs = useRef() if (!refs.current) { refs.current = createChordialElement(optsFn) } refs.current.atom.setByPointer((p) => p.optsFn, optsFn) return refs.current.returnValue } export const useChordialCaptureEvents = (): React.MutableRefObject => { const ref = useRef(null) useEffect(() => { const root = ref.current! if (!root) return window.addEventListener('mousemove', eventHandlers.windowMouseMove) root.addEventListener('contextmenu', eventHandlers.contextMenu) root.addEventListener('mousemove', eventHandlers.mouseMove) root.addEventListener('mousedown', eventHandlers.mouseDown) root.addEventListener('mouseup', eventHandlers.mouseUp) root.addEventListener('click', eventHandlers.click) return () => { root.removeEventListener('mousemove', eventHandlers.windowMouseMove) root.removeEventListener('contextmenu', eventHandlers.contextMenu) root.removeEventListener('mousemove', eventHandlers.mouseMove) root.removeEventListener('mousedown', eventHandlers.mouseDown) root.removeEventListener('mouseup', eventHandlers.mouseUp) root.removeEventListener('click', eventHandlers.click) } }, []) return ref } const eventHandlers = { windowMouseMove: (mouseEvent: MouseEvent) => { gestureActor.send({type: 'mousemove', mouseEvent}) hoverActor.send({type: 'mousemove', mouseEvent, source: 'window'}) }, mouseMove: (mouseEvent: MouseEvent) => { hoverActor.send({type: 'mousemove', mouseEvent, source: 'root'}) }, mouseDown: (e: MouseEvent) => { mousedownActor.send([true]) gestureActor.send({type: 'mousedown', mouseEvent: e}) const el = findChodrialByDomNode(e.target) if (!el) return }, mouseUp: (e: MouseEvent) => { mousedownActor.send([false]) gestureActor.send({type: 'mouseup', mouseEvent: e}) const el = findChodrialByDomNode(e.target) if (!el) return }, click: (e: MouseEvent) => { gestureActor.send({type: 'click', mouseEvent: e}) const el = findChodrialByDomNode(e.target) if (!el) return }, contextMenu: (e: MouseEvent) => { contextActor.send({type: 'rclick', mouseEvent: e}) }, } ================================================ FILE: packages/studio/src/uiComponents/colorPicker/components/EditingProvider.tsx ================================================ import type {FC} from 'react' import React, {createContext, useContext, useState} from 'react' const editingContext = createContext<{ editing: boolean setEditing: (editing: boolean) => void }>(undefined!) /** * Provides the current mode the color picker is in. When editing, the picker should be * stateful and disregard controlling props, while not editing, it should behave * in a controlled manner. */ export const EditingProvider: FC<{children: React.ReactNode}> = ({ children, }) => { const [editing, setEditing] = useState(false) return ( {children} ) } export const useEditing = () => useContext(editingContext) ================================================ FILE: packages/studio/src/uiComponents/colorPicker/components/RgbaColorPicker.tsx ================================================ import React from 'react' import {AlphaColorPicker} from './common/AlphaColorPicker' import type { ColorModel, ColorPickerBaseProps, HsvaColor, RgbaColor, } from '@theatre/studio/uiComponents/colorPicker/types' import {equalColorObjects} from '@theatre/studio/uiComponents/colorPicker/utils/compare' import { rgbaToHsva, hsvaToRgba, } from '@theatre/studio/uiComponents/colorPicker/utils/convert' import {EditingProvider} from './EditingProvider' const normalizeRgba = (rgba: RgbaColor) => { return { r: rgba.r / 255, g: rgba.g / 255, b: rgba.b / 255, a: rgba.a, } } const denormalizeRgba = (rgba: RgbaColor) => { return { r: rgba.r * 255, g: rgba.g * 255, b: rgba.b * 255, a: rgba.a, } } const colorModel: ColorModel = { defaultColor: {r: 0, g: 0, b: 0, a: 1}, toHsva: (rgba: RgbaColor) => rgbaToHsva(denormalizeRgba(rgba)), fromHsva: (hsva: HsvaColor) => normalizeRgba(hsvaToRgba(hsva)), equal: equalColorObjects, } export const RgbaColorPicker = ( props: ColorPickerBaseProps, ): JSX.Element => ( { props.permanentlySetValue!(newColor) }} colorModel={colorModel} /> ) ================================================ FILE: packages/studio/src/uiComponents/colorPicker/components/common/Alpha.tsx ================================================ import React from 'react' import type {Interaction} from './Interactive' import {Interactive} from './Interactive' import {Pointer} from './Pointer' import {hsvaToHslaString} from '@theatre/studio/uiComponents/colorPicker/utils/convert' import {clamp} from '@theatre/studio/uiComponents/colorPicker/utils/clamp' import {round} from '@theatre/studio/uiComponents/colorPicker/utils/round' import type {HsvaColor} from '@theatre/studio/uiComponents/colorPicker/types' import styled from 'styled-components' const Container = styled.div` position: relative; height: 16px; border-radius: 2px; // Checkerboard background-color: #fff; background-image: url('data:image/svg+xml,'); ` interface GradientProps { colorFrom: string colorTo: string } const Gradient = styled.div.attrs(({colorFrom, colorTo}) => ({ style: { backgroundImage: `linear-gradient(90deg, ${colorFrom}, ${colorTo})`, }, }))` content: ''; position: absolute; left: 0; top: 0; right: 0; bottom: 0; pointer-events: none; border-radius: inherit; // Improve rendering on light backgrounds box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05); ` const StyledPointer = styled(Pointer)` // Checkerboard background-color: #fff; background-image: url('data:image/svg+xml,'); ` interface Props { className?: string hsva: HsvaColor onChange: (newAlpha: {a: number}) => void } export const Alpha = ({className, hsva, onChange}: Props): JSX.Element => { const handleMove = (interaction: Interaction) => { onChange({a: interaction.left}) } const handleKey = (offset: Interaction) => { // Alpha always fit into [0, 1] range onChange({a: clamp(hsva.a + offset.left)}) } // We use `Object.assign` instead of the spread operator // to prevent adding the polyfill (about 150 bytes gzipped) const colorFrom = hsvaToHslaString(Object.assign({}, hsva, {a: 0})) const colorTo = hsvaToHslaString(Object.assign({}, hsva, {a: 1})) return ( ) } ================================================ FILE: packages/studio/src/uiComponents/colorPicker/components/common/AlphaColorPicker.tsx ================================================ import React, {useEffect} from 'react' import {Hue} from './Hue' import {Saturation} from './Saturation' import {Alpha} from './Alpha' import type { ColorModel, ColorPickerBaseProps, AnyColor, } from '@theatre/studio/uiComponents/colorPicker/types' import {useColorManipulation} from '@theatre/studio/uiComponents/colorPicker/hooks/useColorManipulation' import styled from 'styled-components' const Container = styled.div` position: relative; display: flex; gap: 4px; flex-direction: column; width: 200px; height: 200px; user-select: none; cursor: default; ` interface Props extends ColorPickerBaseProps { colorModel: ColorModel } export const AlphaColorPicker = ({ className, colorModel, color = colorModel.defaultColor, temporarilySetValue, permanentlySetValue, discardTemporaryValue, ...rest }: Props): JSX.Element => { const [tempHsva, updateHsva] = useColorManipulation( colorModel, color, temporarilySetValue, permanentlySetValue, ) useEffect(() => { return () => { discardTemporaryValue() } }, []) return ( ) } ================================================ FILE: packages/studio/src/uiComponents/colorPicker/components/common/Hue.tsx ================================================ import React from 'react' import type {Interaction} from './Interactive' import {Interactive} from './Interactive' import {Pointer} from './Pointer' import {hsvaToHslString} from '@theatre/studio/uiComponents/colorPicker/utils/convert' import {clamp} from '@theatre/studio/uiComponents/colorPicker/utils/clamp' import {round} from '@theatre/studio/uiComponents/colorPicker/utils/round' import styled from 'styled-components' const Container = styled.div` position: relative; height: 16px; border-radius: 2px; background: linear-gradient( to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100% ); ` const StyledPointer = styled(Pointer)` z-index: 2; ` interface Props { className?: string hue: number onChange: (newHue: {h: number}) => void } const HueBase = ({className, hue, onChange}: Props) => { const handleMove = (interaction: Interaction) => { onChange({h: 360 * interaction.left}) } const handleKey = (offset: Interaction) => { // Hue measured in degrees of the color circle ranging from 0 to 360 onChange({ h: clamp(hue + offset.left * 360, 0, 360), }) } return ( ) } export const Hue = React.memo(HueBase) ================================================ FILE: packages/studio/src/uiComponents/colorPicker/components/common/Interactive.tsx ================================================ import React, {useRef, useMemo, useEffect} from 'react' import {useEventCallback} from '@theatre/studio/uiComponents/colorPicker/hooks/useEventCallback' import {clamp} from '@theatre/studio/uiComponents/colorPicker/utils/clamp' import styled from 'styled-components' import {useEditing} from '@theatre/studio/uiComponents/colorPicker/components/EditingProvider' export interface Interaction { left: number top: number } // Check if an event was triggered by touch const isTouch = (event: MouseEvent | TouchEvent): event is TouchEvent => 'touches' in event // Finds a proper touch point by its identifier const getTouchPoint = (touches: TouchList, touchId: null | number): Touch => { for (let i = 0; i < touches.length; i++) { if (touches[i].identifier === touchId) return touches[i] } return touches[0] } // Finds the proper window object to fix iframe embedding issues const getParentWindow = (node?: HTMLDivElement | null): Window => { return (node && node.ownerDocument.defaultView) || self } // Returns a relative position of the pointer inside the node's bounding box const getRelativePosition = ( node: HTMLDivElement, event: MouseEvent | TouchEvent, touchId: null | number, ): Interaction => { const rect = node.getBoundingClientRect() // Get user's pointer position from `touches` array if it's a `TouchEvent` const pointer = isTouch(event) ? getTouchPoint(event.touches, touchId) : (event as MouseEvent) return { left: clamp( (pointer.pageX - (rect.left + getParentWindow(node).pageXOffset)) / rect.width, ), top: clamp( (pointer.pageY - (rect.top + getParentWindow(node).pageYOffset)) / rect.height, ), } } // Browsers introduced an intervention, making touch events passive by default. // This workaround removes `preventDefault` call from the touch handlers. // https://github.com/facebook/react/issues/19651 const preventDefaultMove = (event: MouseEvent | TouchEvent): void => { !isTouch(event) && event.preventDefault() } // Prevent mobile browsers from handling mouse events (conflicting with touch ones). // If we detected a touch interaction before, we prefer reacting to touch events only. const isInvalid = ( event: MouseEvent | TouchEvent, hasTouch: boolean, ): boolean => { return hasTouch && !isTouch(event) } interface Props { onMove: (interaction: Interaction) => void onKey: (offset: Interaction) => void children: React.ReactNode } const Container = styled.div` position: absolute; left: 0; top: 0; right: 0; bottom: 0; border-radius: inherit; outline: none; /* Don't trigger the default scrolling behavior when the event is originating from this element */ touch-action: none; ` const InteractiveBase = ({onMove, onKey, ...rest}: Props) => { const container = useRef(null) const onMoveCallback = useEventCallback(onMove) const onKeyCallback = useEventCallback(onKey) const touchId = useRef(null) const hasTouch = useRef(false) const {setEditing} = useEditing() const [handleMoveStart, handleKeyDown, toggleDocumentEvents] = useMemo(() => { const handleMoveStart = ({ nativeEvent, }: React.MouseEvent | React.TouchEvent) => { const el = container.current if (!el) return // Prevent text selection preventDefaultMove(nativeEvent) if (isInvalid(nativeEvent, hasTouch.current) || !el) return if (isTouch(nativeEvent)) { hasTouch.current = true const changedTouches = nativeEvent.changedTouches || [] if (changedTouches.length) touchId.current = changedTouches[0].identifier } el.focus() setEditing(true) onMoveCallback(getRelativePosition(el, nativeEvent, touchId.current)) toggleDocumentEvents(true) } const handleMove = (event: MouseEvent | TouchEvent) => { // Prevent text selection preventDefaultMove(event) // If user moves the pointer outside the window or iframe bounds and release it there, // `mouseup`/`touchend` won't be fired. In order to stop the picker from following the cursor // after the user has moved the mouse/finger back to the document, we check `event.buttons` // and `event.touches`. It allows us to detect that the user is just moving his pointer // without pressing it down // Note: we should use pointer events to fix this, since we don't have strict compatibility // requirements. const isDown = isTouch(event) ? event.touches.length > 0 : event.buttons > 0 if (isDown && container.current) { onMoveCallback( getRelativePosition(container.current, event, touchId.current), ) } else { setEditing(false) toggleDocumentEvents(false) } } // Use move-end anyway (see above) so we can terminate early if we receive one // instead of having to wait for the user to move the mouse, which they might not do. const handleMoveEnd = (event: MouseEvent | TouchEvent) => { setEditing(false) toggleDocumentEvents(false) } const handleKeyDown = (event: React.KeyboardEvent) => { const keyCode = event.which || event.keyCode // Ignore all keys except arrow ones if (keyCode < 37 || keyCode > 40) return // Do not scroll page by arrow keys when document is focused on the element event.preventDefault() // Send relative offset to the parent component. // We use codes (37←, 38↑, 39→, 40↓) instead of keys ('ArrowRight', 'ArrowDown', etc) // to reduce the size of the library onKeyCallback({ left: keyCode === 39 ? 0.05 : keyCode === 37 ? -0.05 : 0, top: keyCode === 40 ? 0.05 : keyCode === 38 ? -0.05 : 0, }) } function toggleDocumentEvents(state?: boolean) { const touch = hasTouch.current const el = container.current const parentWindow = getParentWindow(el) // Add or remove additional pointer event listeners const toggleEvent = state ? parentWindow.addEventListener : parentWindow.removeEventListener toggleEvent(touch ? 'touchmove' : 'mousemove', handleMove) toggleEvent(touch ? 'touchend' : 'mouseup', handleMoveEnd) } return [handleMoveStart, handleKeyDown, toggleDocumentEvents] }, [onKeyCallback, onMoveCallback]) // Remove window event listeners before unmounting useEffect(() => toggleDocumentEvents, [toggleDocumentEvents]) return ( ) } export const Interactive = React.memo(InteractiveBase) ================================================ FILE: packages/studio/src/uiComponents/colorPicker/components/common/Pointer.tsx ================================================ import React from 'react' import styled from 'styled-components' import {Interactive} from './Interactive' // Create an "empty" styled version, so we can reference it for contextual styling const StyledInteractive = styled(Interactive)`` const Container = styled.div` position: absolute; z-index: 1; box-sizing: border-box; width: 16px; height: 16px; transform: translate(-50%, -50%); background-color: #fff; border: 1px solid #ffffff00; border-radius: 2px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); ${StyledInteractive}:focus & { transform: translate(-50%, -50%) scale(1.1); } ` const Fill = styled.div` content: ''; position: absolute; left: 0; top: 0; right: 0; bottom: 0; pointer-events: none; border-radius: inherit; ` interface Props { className?: string top?: number left: number color: string } export const Pointer = ({ className, color, left, top = 0.5, }: Props): JSX.Element => { const style = { top: `${top * 100}%`, left: `${left * 100}%`, } return ( ) } ================================================ FILE: packages/studio/src/uiComponents/colorPicker/components/common/Saturation.tsx ================================================ import React from 'react' import type {Interaction} from './Interactive' import {Interactive} from './Interactive' import {Pointer} from './Pointer' import type {HsvaColor} from '@theatre/studio/uiComponents/colorPicker/types' import {hsvaToHslString} from '@theatre/studio/uiComponents/colorPicker/utils/convert' import {clamp} from '@theatre/studio/uiComponents/colorPicker/utils/clamp' import {round} from '@theatre/studio/uiComponents/colorPicker/utils/round' import styled from 'styled-components' const Container = styled.div` position: relative; flex-grow: 1; border-color: transparent; /* Fixes https://github.com/omgovich/react-colorful/issues/139 */ border-bottom: 12px solid #000; border-radius: 2px; background-image: linear-gradient(to top, #000, rgba(0, 0, 0, 0)), linear-gradient(to right, #fff, rgba(255, 255, 255, 0)); // Improve elements rendering on light backgrounds box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05); ` const StyledPointer = styled(Pointer)` z-index: 3; ` interface Props { hsva: HsvaColor onChange: (newColor: {s: number; v: number}) => void } const SaturationBase = ({hsva, onChange}: Props) => { const handleMove = (interaction: Interaction) => { onChange({ s: interaction.left * 100, v: 100 - interaction.top * 100, }) } const handleKey = (offset: Interaction) => { // Saturation and brightness always fit into [0, 100] range onChange({ s: clamp(hsva.s + offset.left * 100, 0, 100), v: clamp(hsva.v - offset.top * 100, 0, 100), }) } const containerStyle = { backgroundColor: hsvaToHslString({h: hsva.h, s: 100, v: 100, a: 1}), } return ( ) } export const Saturation = React.memo(SaturationBase) ================================================ FILE: packages/studio/src/uiComponents/colorPicker/hooks/useColorManipulation.ts ================================================ import {useState, useEffect, useCallback, useRef} from 'react' import type { ColorModel, AnyColor, HsvaColor, } from '@theatre/studio/uiComponents/colorPicker/types' import {equalColorObjects} from '@theatre/studio/uiComponents/colorPicker/utils/compare' import {useEventCallback} from './useEventCallback' import {useEditing} from '@theatre/studio/uiComponents/colorPicker/components/EditingProvider' export function useColorManipulation( colorModel: ColorModel, color: T, onTemporarilyChange: (color: T) => void, onPermanentlyChange: (color: T) => void, ): [HsvaColor, (color: Partial) => void] { const {editing} = useEditing() const [editingValue, setEditingValue] = useState(color) // Save onChange callbacks in refs for to avoid unnecessarily updating when parent doesn't use useCallback const onTemporarilyChangeCallback = useEventCallback(onTemporarilyChange) const onPermanentlyChangeCallback = useEventCallback(onPermanentlyChange) // If editing, be uncontrolled, if not editing, be controlled let value = editing ? editingValue : color // No matter which color model is used (HEX, RGB(A) or HSL(A)), // all internal calculations are in HSVA const [hsva, updateHsva] = useState(() => colorModel.toHsva(value)) // Use refs to prevent infinite update loops. They basically serve as a more // explicit hack around the rigidity of React hooks' dep lists, since we want // to do all color equality checks in HSVA, without breaking the roles of hooks. // We use separate refs for temporary updates and permanent updates, // since they are two independent update models. const tempCache = useRef({color: value, hsva}) const permCache = useRef({color: value, hsva}) // When entering editing mode, set the internal state of the uncontrolled mode // to the last value of the controlled mode. useEffect(() => { if (editing) { setEditingValue(tempCache.current.color) } }, [editing]) // Trigger `on*Change` callbacks only if an updated color is different from // the cached one; save the new color to the ref to prevent unnecessary updates. useEffect(() => { let newColor = colorModel.fromHsva(hsva) if (editing) { if ( !equalColorObjects(hsva, tempCache.current.hsva) && !colorModel.equal(newColor, tempCache.current.color) ) { tempCache.current = {hsva, color: newColor} setEditingValue(newColor) onTemporarilyChangeCallback(newColor) } } else { if ( !equalColorObjects(hsva, permCache.current.hsva) && !colorModel.equal(newColor, permCache.current.color) ) { permCache.current = {hsva, color: newColor} tempCache.current = {hsva, color: newColor} onPermanentlyChangeCallback(newColor) } } }, [ editing, hsva, colorModel, onTemporarilyChangeCallback, onPermanentlyChangeCallback, ]) // This has to come after the callback calling effect, so that the cache isn't // updated before the above effect checks for equality, otherwise no updates would // be issued. // Note: it doesn't make sense to have an editing version of this effect because // the callback calling effect already updates the caches. useEffect(() => { if (!editing) { if (!colorModel.equal(value, permCache.current.color)) { const newHsva = colorModel.toHsva(value) permCache.current = {hsva: newHsva, color: value} updateHsva(newHsva) } } }, [editing, value, colorModel]) // Merge the current HSVA color object with updated params. // For example, when a child component sends `h` or `s` only const handleChange = useCallback((params: Partial) => { updateHsva((current) => Object.assign({}, current, params)) }, []) return [hsva, handleChange] } ================================================ FILE: packages/studio/src/uiComponents/colorPicker/hooks/useEventCallback.ts ================================================ import {useRef} from 'react' // Saves incoming handler to the ref in order to avoid "useCallback hell" export function useEventCallback( handler?: (value: T) => void, ): (value: T) => void { const callbackRef = useRef(handler) const fn = useRef((value: T) => { callbackRef.current && callbackRef.current(value) }) callbackRef.current = handler return fn.current } ================================================ FILE: packages/studio/src/uiComponents/colorPicker/hooks/useIsomorphicLayoutEffect.ts ================================================ import {useLayoutEffect, useEffect} from 'react' // React currently throws a warning when using useLayoutEffect on the server. // To get around it, we can conditionally useEffect on the server (no-op) and // useLayoutEffect in the browser. export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect ================================================ FILE: packages/studio/src/uiComponents/colorPicker/index.ts ================================================ export {RgbaColorPicker} from './components/RgbaColorPicker' // Color model types export type { RgbColor, RgbaColor, HslColor, HslaColor, HsvColor, HsvaColor, } from './types' ================================================ FILE: packages/studio/src/uiComponents/colorPicker/types.ts ================================================ import type React from 'react' export interface RgbColor { r: number g: number b: number } export interface RgbaColor extends RgbColor { a: number } export interface HslColor { h: number s: number l: number } export interface HslaColor extends HslColor { a: number } export interface HsvColor { h: number s: number v: number } export interface HsvaColor extends HsvColor { a: number } export type ObjectColor = | RgbColor | HslColor | HsvColor | RgbaColor | HslaColor | HsvaColor export type AnyColor = string | ObjectColor export interface ColorModel { defaultColor: T toHsva: (defaultColor: T) => HsvaColor fromHsva: (hsva: HsvaColor) => T equal: (first: T, second: T) => boolean } type ColorPickerHTMLAttributes = Partial< Omit< React.HTMLAttributes, 'color' | 'onChange' | 'onChangeCapture' > > export interface ColorPickerBaseProps extends ColorPickerHTMLAttributes { color: T temporarilySetValue: (newColor: T) => void permanentlySetValue: (newColor: T) => void discardTemporaryValue: () => void } type ColorInputHTMLAttributes = Omit< React.InputHTMLAttributes, 'onChange' | 'value' > export interface ColorInputBaseProps extends ColorInputHTMLAttributes { color?: string onChange?: (newColor: string) => void } ================================================ FILE: packages/studio/src/uiComponents/colorPicker/utils/clamp.ts ================================================ // Clamps a value between an upper and lower bound. // We use ternary operators because it makes the minified code // 2 times shorter then `Math.min(Math.max(a,b),c)` export const clamp = (number: number, min = 0, max = 1): number => { return number > max ? max : number < min ? min : number } ================================================ FILE: packages/studio/src/uiComponents/colorPicker/utils/compare.ts ================================================ import {hexToRgba} from './convert' import type {ObjectColor} from '@theatre/studio/uiComponents/colorPicker/types' export const equalColorObjects = ( first: ObjectColor, second: ObjectColor, ): boolean => { if (first === second) return true for (const prop in first) { // The following allows for a type-safe calling of this function (first & second have to be HSL, HSV, or RGB) // with type-unsafe iterating over object keys. TS does not allow this without an index (`[key: string]: number`) // on an object to define how iteration is normally done. To ensure extra keys are not allowed on our types, // we must cast our object to unknown (as RGB demands `r` be a key, while `Record` does not care if // there is or not), and then as a type TS can iterate over. if ( (first as unknown as Record)[prop] !== (second as unknown as Record)[prop] ) return false } return true } export const equalColorString = (first: string, second: string): boolean => { return first.replace(/\s/g, '') === second.replace(/\s/g, '') } export const equalHex = (first: string, second: string): boolean => { if (first.toLowerCase() === second.toLowerCase()) return true // To compare colors like `#FFF` and `ffffff` we convert them into RGB objects return equalColorObjects(hexToRgba(first), hexToRgba(second)) } ================================================ FILE: packages/studio/src/uiComponents/colorPicker/utils/convert.ts ================================================ import {round} from './round' import type { RgbaColor, RgbColor, HslaColor, HslColor, HsvaColor, HsvColor, } from '@theatre/studio/uiComponents/colorPicker/types' /** * Valid CSS units. * https://developer.mozilla.org/en-US/docs/Web/CSS/angle */ const angleUnits: Record = { grad: 360 / 400, turn: 360, rad: 360 / (Math.PI * 2), } export const hexToHsva = (hex: string): HsvaColor => rgbaToHsva(hexToRgba(hex)) export const hexToRgba = (hex: string): RgbaColor => { if (hex[0] === '#') hex = hex.substr(1) if (hex.length < 6) { return { r: parseInt(hex[0] + hex[0], 16), g: parseInt(hex[1] + hex[1], 16), b: parseInt(hex[2] + hex[2], 16), a: 1, } } return { r: parseInt(hex.substr(0, 2), 16), g: parseInt(hex.substr(2, 2), 16), b: parseInt(hex.substr(4, 2), 16), a: 1, } } export const parseHue = (value: string, unit = 'deg'): number => { return Number(value) * (angleUnits[unit] || 1) } export const hslaStringToHsva = (hslString: string): HsvaColor => { const matcher = /hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i const match = matcher.exec(hslString) if (!match) return {h: 0, s: 0, v: 0, a: 1} return hslaToHsva({ h: parseHue(match[1], match[2]), s: Number(match[3]), l: Number(match[4]), a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1), }) } export const hslStringToHsva = hslaStringToHsva export const hslaToHsva = ({h, s, l, a}: HslaColor): HsvaColor => { s *= (l < 50 ? l : 100 - l) / 100 return { h: h, s: s > 0 ? ((2 * s) / (l + s)) * 100 : 0, v: l + s, a, } } export const hsvaToHex = (hsva: HsvaColor): string => rgbaToHex(hsvaToRgba(hsva)) export const hsvaToHsla = ({h, s, v, a}: HsvaColor): HslaColor => { const hh = ((200 - s) * v) / 100 return { h: round(h), s: round( hh > 0 && hh < 200 ? ((s * v) / 100 / (hh <= 100 ? hh : 200 - hh)) * 100 : 0, ), l: round(hh / 2), a: round(a, 2), } } export const hsvaToHslString = (hsva: HsvaColor): string => { const {h, s, l} = hsvaToHsla(hsva) return `hsl(${h}, ${s}%, ${l}%)` } export const hsvaToHsvString = (hsva: HsvaColor): string => { const {h, s, v} = roundHsva(hsva) return `hsv(${h}, ${s}%, ${v}%)` } export const hsvaToHsvaString = (hsva: HsvaColor): string => { const {h, s, v, a} = roundHsva(hsva) return `hsva(${h}, ${s}%, ${v}%, ${a})` } export const hsvaToHslaString = (hsva: HsvaColor): string => { const {h, s, l, a} = hsvaToHsla(hsva) return `hsla(${h}, ${s}%, ${l}%, ${a})` } export const hsvaToRgba = ({h, s, v, a}: HsvaColor): RgbaColor => { h = (h / 360) * 6 s = s / 100 v = v / 100 const hh = Math.floor(h), b = v * (1 - s), c = v * (1 - (h - hh) * s), d = v * (1 - (1 - h + hh) * s), module = hh % 6 return { r: round([v, c, b, b, d, v][module] * 255), g: round([d, v, v, c, b, b][module] * 255), b: round([b, b, d, v, v, c][module] * 255), a: round(a, 2), } } export const hsvaToRgbString = (hsva: HsvaColor): string => { const {r, g, b} = hsvaToRgba(hsva) return `rgb(${r}, ${g}, ${b})` } export const hsvaToRgbaString = (hsva: HsvaColor): string => { const {r, g, b, a} = hsvaToRgba(hsva) return `rgba(${r}, ${g}, ${b}, ${a})` } export const hsvaStringToHsva = (hsvString: string): HsvaColor => { const matcher = /hsva?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i const match = matcher.exec(hsvString) if (!match) return {h: 0, s: 0, v: 0, a: 1} return roundHsva({ h: parseHue(match[1], match[2]), s: Number(match[3]), v: Number(match[4]), a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1), }) } export const hsvStringToHsva = hsvaStringToHsva export const rgbaStringToHsva = (rgbaString: string): HsvaColor => { const matcher = /rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i const match = matcher.exec(rgbaString) if (!match) return {h: 0, s: 0, v: 0, a: 1} return rgbaToHsva({ r: Number(match[1]) / (match[2] ? 100 / 255 : 1), g: Number(match[3]) / (match[4] ? 100 / 255 : 1), b: Number(match[5]) / (match[6] ? 100 / 255 : 1), a: match[7] === undefined ? 1 : Number(match[7]) / (match[8] ? 100 : 1), }) } export const rgbStringToHsva = rgbaStringToHsva const format = (number: number) => { const hex = number.toString(16) return hex.length < 2 ? '0' + hex : hex } export const rgbaToHex = ({r, g, b}: RgbaColor): string => { return '#' + format(r) + format(g) + format(b) } export const rgbaToHsva = ({r, g, b, a}: RgbaColor): HsvaColor => { const max = Math.max(r, g, b) const delta = max - Math.min(r, g, b) // prettier-ignore const hh = delta ? max === r ? (g - b) / delta : max === g ? 2 + (b - r) / delta : 4 + (r - g) / delta : 0; return { h: round(60 * (hh < 0 ? hh + 6 : hh)), s: round(max ? (delta / max) * 100 : 0), v: round((max / 255) * 100), a, } } export const roundHsva = (hsva: HsvaColor): HsvaColor => ({ h: round(hsva.h), s: round(hsva.s), v: round(hsva.v), a: round(hsva.a, 2), }) export const rgbaToRgb = ({r, g, b}: RgbaColor): RgbColor => ({r, g, b}) export const hslaToHsl = ({h, s, l}: HslaColor): HslColor => ({h, s, l}) export const hsvaToHsv = (hsva: HsvaColor): HsvColor => { const {h, s, v} = roundHsva(hsva) return {h, s, v} } ================================================ FILE: packages/studio/src/uiComponents/colorPicker/utils/round.ts ================================================ export const round = ( number: number, digits = 0, base = Math.pow(10, digits), ): number => { return Math.round(base * number) / base } ================================================ FILE: packages/studio/src/uiComponents/colorPicker/utils/validate.ts ================================================ const matcher = /^#?([0-9A-F]{3,8})$/i export const validHex = (value: string, alpha?: boolean): boolean => { const match = matcher.exec(value) const length = match ? match[1].length : 0 return ( length === 3 || // '#rgb' format length === 6 || // '#rrggbb' format (!!alpha && length === 4) || // '#rgba' format (!!alpha && length === 8) // '#rrggbbaa' format ) } ================================================ FILE: packages/studio/src/uiComponents/createCursorLock.ts ================================================ // import getStudio from '@theatre/studio/getStudio' // export function createCursorLock(cursor: string) { // const el = getStudio()!.ui.containerShadow.getElementById( // 'pointer-events-root', // )! as HTMLDivElement // el.style.cursor = cursor // el.classList.remove('pointer-events-mode-normal') // el.classList.add('pointer-events-mode-locked-for-drag') // const relinquish = () => { // el.style.cursor = '' // el.classList.add('pointer-events-mode-normal') // el.classList.remove('pointer-events-mode-locked-for-drag') // } // return relinquish // } export {} ================================================ FILE: packages/studio/src/uiComponents/form/BasicCheckbox.tsx ================================================ import styled from 'styled-components' const BasicCheckbox = styled.input.attrs({type: 'checkbox'})` outline: none; ` export default BasicCheckbox ================================================ FILE: packages/studio/src/uiComponents/form/BasicNumberInput.tsx ================================================ import {clamp, isInteger, round} from 'lodash-es' import type {MutableRefObject} from 'react' import {useEffect} from 'react' import {useState} from 'react' import React, {useMemo, useRef} from 'react' import styled from 'styled-components' import {mergeRefs} from 'react-merge-refs' import useRefAndState from '@theatre/studio/utils/useRefAndState' import useOnClickOutside from '@theatre/studio/uiComponents/useOnClickOutside' import useDrag from '@theatre/studio/uiComponents/useDrag' const Container = styled.div` height: 100%; width: 100%; position: relative; z-index: 0; box-sizing: border-box; display: flex; align-items: center; &:after { position: absolute; inset: 1px 0 2px; display: block; content: ' '; background-color: transparent; border: 1px solid transparent; z-index: -2; box-sizing: border-box; border-radius: 1px; } &:hover, &.dragging, &.editingViaKeyboard { &:after { background-color: #10101042; border-color: #00000059; } } ` const Input = styled.input` background: transparent; border: 1px solid transparent; color: rgba(255, 255, 255, 0.9); padding: 1px 6px; font: inherit; outline: none; cursor: ew-resize; text-align: left; width: 100%; height: calc(100% - 4px); border-radius: 2px; &:focus { cursor: text; } ` const FillIndicator = styled.div` position: absolute; inset: 3px 2px 4px; transform: scale(var(--percentage), 1); transform-origin: top left; background-color: #2d5561; z-index: -1; border-radius: 2px; pointer-events: none; ${Container}.dragging &, ${Container}.noFocus:hover & { background-color: #338198; } ` const DragWrap = styled.div` display: contents; ` type IState_NoFocus = { mode: 'noFocus' } type IState_EditingViaKeyboard = { mode: 'editingViaKeyboard' currentEditedValueInString: string valueBeforeEditing: number } type IState_Dragging = { mode: 'dragging' } type IState = IState_NoFocus | IState_EditingViaKeyboard | IState_Dragging const alwaysValid = (v: number) => true export type BasicNumberInputNudgeFn = (params: { deltaX: number deltaFraction: number magnitude: number }) => number const BasicNumberInput: React.FC<{ value: number temporarilySetValue: (v: number) => void discardTemporaryValue: () => void permanentlySetValue: (v: number) => void className?: string range?: [min: number, max: number] isValid?: (v: number) => boolean inputRef?: MutableRefObject /** * Called when the user hits Enter. One of the *SetValue() callbacks will be called * before this, so use this for UI purposes such as closing a popover. */ onBlur?: () => void nudge: BasicNumberInputNudgeFn autoFocus?: boolean }> = (propsA) => { const [stateRef] = useRefAndState({mode: 'noFocus'}) const isValid = propsA.isValid ?? alwaysValid const propsRef = useRef(propsA) propsRef.current = propsA const inputRef = useRef(null) useOnClickOutside( inputRef.current, () => { inputRef.current!.blur() }, stateRef.current.mode === 'editingViaKeyboard', ) const bodyCursorBeforeDrag = useRef(null) const callbacks = useMemo(() => { const inputChange = (e: React.ChangeEvent) => { const target = e.target as HTMLInputElement const {value} = target const curState = stateRef.current as IState_EditingViaKeyboard stateRef.current = {...curState, currentEditedValueInString: value} const valInFloat = parseFloat(value) if (!isFinite(valInFloat) || !isValid(valInFloat)) return propsRef.current.temporarilySetValue(valInFloat) } const onBlur = () => { if (stateRef.current.mode === 'editingViaKeyboard') { commitKeyboardInput() stateRef.current = {mode: 'noFocus'} } if (propsA.onBlur) propsA.onBlur() } const commitKeyboardInput = () => { const curState = stateRef.current as IState_EditingViaKeyboard const value = parseFloat(curState.currentEditedValueInString) if (!isFinite(value) || !isValid(value)) { propsRef.current.discardTemporaryValue() } else { if (curState.valueBeforeEditing === value) { propsRef.current.discardTemporaryValue() } else { propsRef.current.permanentlySetValue(value) } } } const onInputKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Escape') { propsRef.current.discardTemporaryValue() stateRef.current = {mode: 'noFocus'} inputRef.current!.blur() } else if (e.key === 'Enter' || e.key === 'Tab') { commitKeyboardInput() inputRef.current!.blur() } } const onClick = (e: React.MouseEvent) => { if (stateRef.current.mode === 'noFocus') { const c = inputRef.current! c.focus() e.preventDefault() e.stopPropagation() } else { e.stopPropagation() } } const onFocus = () => { if (stateRef.current.mode === 'noFocus') { transitionToEditingViaKeyboardMode() } else if (stateRef.current.mode === 'editingViaKeyboard') { } } const transitionToEditingViaKeyboardMode = () => { const curValue = propsRef.current.value stateRef.current = { mode: 'editingViaKeyboard', currentEditedValueInString: String(curValue), valueBeforeEditing: curValue, } setTimeout(() => { inputRef.current!.focus() inputRef.current!.setSelectionRange(0, 100) }) } let inputWidth: number const transitionToDraggingMode = () => { const curValue = propsRef.current.value inputWidth = inputRef.current?.getBoundingClientRect().width! stateRef.current = { mode: 'dragging', } let valueBeforeDragging = curValue let valueDuringDragging = curValue bodyCursorBeforeDrag.current = document.body.style.cursor return { onDrag(_dx: number, _dy: number, e: MouseEvent, mx: number) { // We use `mx` here because it allows us to offer better UX when dragging // a value beyond its range. If we were to use `_dx`, and the number had a range, // and the user nudged the number beyond its range, they would have to un-nudge all // the way back until the number's value is within its range. But with `mx`, // as soon as they reverse their mouse drag, the number will jump back to its range. const deltaX = e.altKey ? mx / 10 : mx const newValue = valueDuringDragging + propsA.nudge({ deltaX, deltaFraction: deltaX / inputWidth, magnitude: 1, }) valueDuringDragging = propsA.range ? clamp(newValue, propsA.range[0], propsA.range[1]) : newValue propsRef.current.temporarilySetValue(valueDuringDragging) }, onDragEnd(happened: boolean) { if (!happened) { propsRef.current.discardTemporaryValue() stateRef.current = {mode: 'noFocus'} } else { if (valueBeforeDragging === valueDuringDragging) { propsRef.current.discardTemporaryValue() } else { propsRef.current.permanentlySetValue(valueDuringDragging) } stateRef.current = {mode: 'noFocus'} } }, onClick() { inputRef.current!.focus() inputRef.current!.setSelectionRange(0, 100) }, } } return { inputChange, onBlur, transitionToDraggingMode, onInputKeyDown, onClick, onFocus, } }, []) // Call onBlur on unmount. Because technically it _is_ a blur, but also, otherwise edits wouldn't be committed. useEffect(() => { return () => { callbacks.onBlur() } }, []) let value = stateRef.current.mode !== 'editingViaKeyboard' ? format(propsA.value) : stateRef.current.currentEditedValueInString if (typeof value === 'number' && isNaN(value)) { value = 'NaN' } const _refs = [inputRef] if (propsA.inputRef) _refs.push(propsA.inputRef) const theInput = ( { e.stopPropagation() }} onDoubleClick={(e: React.MouseEvent) => { e.preventDefault() e.stopPropagation() }} autoFocus={propsA.autoFocus} /> ) const {range} = propsA const num = parseFloat(value) const fillIndicator = range ? ( ) : null const [dragNode, setDragNode] = useState(null) useDrag(dragNode, { debugName: 'form/BasicNumberInput', onDragStart: callbacks.transitionToDraggingMode, lockCSSCursorTo: 'ew-resize', shouldPointerLock: true, disabled: stateRef.current.mode === 'editingViaKeyboard', }) return ( {theInput} {fillIndicator} ) } function format(v: number): string { return isNaN(v) ? 'NaN' : isInteger(v) ? v.toFixed(0) : round(v, 3).toString() } export default BasicNumberInput ================================================ FILE: packages/studio/src/uiComponents/form/BasicSelect.tsx ================================================ import React, {useCallback} from 'react' import styled from 'styled-components' import {CgSelect} from 'react-icons/cg' const Container = styled.div` width: 100%; position: relative; ` const IconContainer = styled.div` position: absolute; right: 0px; top: 0; bottom: 0; width: 1.5em; font-size: 14px; display: flex; align-items: center; color: #6b7280; pointer-events: none; ` const Select = styled.select` appearance: none; background-color: transparent; box-sizing: border-box; border: 1px solid transparent; color: rgba(255, 255, 255, 0.85); padding: 1px 6px; font: inherit; outline: none; text-align: left; width: 100%; border-radius: 2px; /* looks like putting percentages in the height of a select box doesn't work in Firefox. Not sure why. So we're hard-coding the height to 26px, unlike all other inputs that use a relative height. */ height: 26px /* calc(100% - 4px); */; @supports (-moz-appearance: none) { /* Ugly hack to remove the extra left padding that shows up only in Firefox */ text-indent: -2px; } &:hover, &:focus { background-color: #10101042; border-color: #00000059; } ` function BasicSelect({ value, onChange, options, className, autoFocus, }: { value: TLiteralOptions onChange: (val: TLiteralOptions) => void options: Record className?: string autoFocus?: boolean }) { const _onChange = useCallback( (el: React.ChangeEvent) => { onChange(String(el.target.value) as TLiteralOptions) }, [onChange], ) return ( ) } export default BasicSelect ================================================ FILE: packages/studio/src/uiComponents/form/BasicStringInput.tsx ================================================ import styled from 'styled-components' import type {MutableRefObject} from 'react' import {useEffect} from 'react' import React, {useMemo, useRef} from 'react' import {mergeRefs} from 'react-merge-refs' import useRefAndState from '@theatre/studio/utils/useRefAndState' import useOnClickOutside from '@theatre/studio/uiComponents/useOnClickOutside' const Input = styled.input.attrs({type: 'text'})` background: transparent; border: 1px solid transparent; color: rgba(255, 255, 255, 0.9); padding: 1px 6px; font: inherit; outline: none; cursor: text; text-align: left; width: 100%; height: calc(100% - 4px); border-radius: 2px; border: 1px solid transparent; box-sizing: border-box; &:hover { background-color: #10101042; border-color: #00000059; } &:hover, &:focus { cursor: text; background-color: #10101042; border-color: #00000059; } &.invalid { border-color: red; } ` type IState_NoFocus = { mode: 'noFocus' } type IState_EditingViaKeyboard = { mode: 'editingViaKeyboard' currentEditedValueInString: string valueBeforeEditing: string } type IState = IState_NoFocus | IState_EditingViaKeyboard const alwaysValid = (v: string) => true const BasicStringInput: React.FC<{ value: string temporarilySetValue: (v: string) => void discardTemporaryValue: () => void permanentlySetValue: (v: string) => void className?: string isValid?: (v: string) => boolean inputRef?: MutableRefObject /** * Called when the user hits Enter. One of the *SetValue() callbacks will be called * before this, so use this for UI purposes such as closing a popover. */ onBlur?: () => void autoFocus?: boolean }> = (props) => { const [stateRef] = useRefAndState({mode: 'noFocus'}) const isValid = props.isValid ?? alwaysValid const propsRef = useRef(props) propsRef.current = props const inputRef = useRef(null) useOnClickOutside( inputRef.current, () => { inputRef.current!.blur() }, stateRef.current.mode === 'editingViaKeyboard', ) const callbacks = useMemo(() => { const inputChange = (e: React.ChangeEvent) => { const target = e.target as HTMLInputElement const {value} = target const curState = stateRef.current as IState_EditingViaKeyboard stateRef.current = {...curState, currentEditedValueInString: value} if (!isValid(value)) return propsRef.current.temporarilySetValue(value) } const onBlur = () => { if (stateRef.current.mode === 'editingViaKeyboard') { commitKeyboardInput() stateRef.current = {mode: 'noFocus'} } propsRef.current.onBlur?.() } const commitKeyboardInput = () => { const curState = stateRef.current as IState_EditingViaKeyboard const value = curState.currentEditedValueInString if (!isValid(value)) { propsRef.current.discardTemporaryValue() } else { if (curState.valueBeforeEditing === value) { propsRef.current.discardTemporaryValue() } else { propsRef.current.permanentlySetValue(value) } } } const onInputKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Escape') { propsRef.current.discardTemporaryValue() stateRef.current = {mode: 'noFocus'} inputRef.current!.blur() } else if (e.key === 'Enter' || e.key === 'Tab') { commitKeyboardInput() inputRef.current!.blur() } } const onClick = (e: React.MouseEvent) => { if (stateRef.current.mode === 'noFocus') { const c = inputRef.current! c.focus() e.preventDefault() e.stopPropagation() } else { e.stopPropagation() } } const onFocus = () => { if (stateRef.current.mode === 'noFocus') { transitionToEditingViaKeyboardMode() } else if (stateRef.current.mode === 'editingViaKeyboard') { } } const transitionToEditingViaKeyboardMode = () => { const curValue = propsRef.current.value stateRef.current = { mode: 'editingViaKeyboard', currentEditedValueInString: String(curValue), valueBeforeEditing: curValue, } setTimeout(() => { inputRef.current!.focus() }) } return { inputChange, onBlur, onInputKeyDown, onClick, onFocus, } }, []) // Call onBlur on unmount. Because technically it _is_ a blur, but also, otherwise edits wouldn't be committed. useEffect(() => { return () => { callbacks.onBlur() } }, []) let value = stateRef.current.mode !== 'editingViaKeyboard' ? format(props.value) : stateRef.current.currentEditedValueInString const _refs = [inputRef] if (props.inputRef) _refs.push(props.inputRef) const theInput = ( { e.stopPropagation() }} onDoubleClick={(e: React.MouseEvent) => { e.preventDefault() e.stopPropagation() }} autoFocus={props.autoFocus} /> ) return theInput } function format(v: string): string { return v } export default BasicStringInput ================================================ FILE: packages/studio/src/uiComponents/form/BasicSwitch.tsx ================================================ import {darken} from 'polished' import React, {useCallback} from 'react' import styled from 'styled-components' const Container = styled.form` display: flex; flex-direction: row; align-items: stretch; vertical-align: middle; justify-content: stretch; height: 24px; width: 100%; ` const Label = styled.label` padding: 0 0.5em; background: transparent; /* background: #373748; */ display: flex; align-items: center; justify-content: center; flex-grow: 1; color: #a7a7a7; border: 1px solid transparent; box-sizing: border-box; border-right-width: 0px; & + &:last-child { border-right-width: 1px; } ${Container}:hover > & { border-color: #1c2123; /* background-color: #373748; */ /* color: ${darken(0.1, 'white')}; */ } &&:hover { background-color: #464654; } &&[data-checked='true'] { color: white; background: #3f3f4c; } ` const Input = styled.input` position: absolute; opacity: 0; pointer-events: none; width: 0; height: 0; ` function BasicSwitch({ value, onChange, options, autoFocus, }: { value: TLiteralOptions onChange: (val: TLiteralOptions) => void options: Record autoFocus?: boolean }) { const _onChange = useCallback( (el: React.ChangeEvent) => { onChange(String(el.target.value) as TLiteralOptions) }, [onChange], ) return ( {Object.keys(options).map((key, i) => ( ))} ) } export default BasicSwitch ================================================ FILE: packages/studio/src/uiComponents/icons/AddImage.tsx ================================================ import * as React from 'react' function AddImage(props: React.SVGProps) { return ( ) } export default AddImage ================================================ FILE: packages/studio/src/uiComponents/icons/ArrowClockwise.tsx ================================================ import * as React from 'react' function ArrowClockwise(props: React.SVGProps) { return ( ) } export default ArrowClockwise ================================================ FILE: packages/studio/src/uiComponents/icons/ArrowsOutCardinal.tsx ================================================ import * as React from 'react' function ArrowsOutCardinal(props: React.SVGProps) { return ( ) } export default ArrowsOutCardinal ================================================ FILE: packages/studio/src/uiComponents/icons/Bell.tsx ================================================ import * as React from 'react' function Bell(props: React.SVGProps) { return ( ) } export default Bell ================================================ FILE: packages/studio/src/uiComponents/icons/Camera.tsx ================================================ import * as React from 'react' function Camera(props: React.SVGProps) { return ( ) } export default Camera ================================================ FILE: packages/studio/src/uiComponents/icons/ChevronDown.tsx ================================================ import * as React from 'react' function ChevronDown(props: React.SVGProps) { return ( ) } export default ChevronDown ================================================ FILE: packages/studio/src/uiComponents/icons/ChevronLeft.tsx ================================================ import * as React from 'react' function ChevronLeft(props: React.SVGProps) { return ( ) } export default ChevronLeft ================================================ FILE: packages/studio/src/uiComponents/icons/ChevronRight.tsx ================================================ import * as React from 'react' function ChevronRight(props: React.SVGProps) { return ( ) } export default ChevronRight ================================================ FILE: packages/studio/src/uiComponents/icons/Cube.tsx ================================================ import * as React from 'react' function Cube(props: React.SVGProps) { return ( ) } export default Cube ================================================ FILE: packages/studio/src/uiComponents/icons/CubeFull.tsx ================================================ import * as React from 'react' function CubeFull(props: React.SVGProps) { return ( ) } export default CubeFull ================================================ FILE: packages/studio/src/uiComponents/icons/CubeHalf.tsx ================================================ import * as React from 'react' function CubeHalf(props: React.SVGProps) { return ( ) } export default CubeHalf ================================================ FILE: packages/studio/src/uiComponents/icons/CubeRendered.tsx ================================================ import * as React from 'react' function CubeRendered(props: React.SVGProps) { return ( ) } export default CubeRendered ================================================ FILE: packages/studio/src/uiComponents/icons/Details.tsx ================================================ import * as React from 'react' function Details(props: React.SVGProps) { return ( ) } export default Details ================================================ FILE: packages/studio/src/uiComponents/icons/DoubleChevronLeft.tsx ================================================ import * as React from 'react' function DoubleChevronLeft(props: React.SVGProps) { return ( ) } export default DoubleChevronLeft ================================================ FILE: packages/studio/src/uiComponents/icons/DoubleChevronRight.tsx ================================================ import * as React from 'react' function DoubleChevronRight(props: React.SVGProps) { return ( ) } export default DoubleChevronRight ================================================ FILE: packages/studio/src/uiComponents/icons/DropdownChevron.tsx ================================================ import React from 'react' import styled from 'styled-components' /** * A chevron icon specifically for dropdowns and elements that open a menu. * If you want the chevron to shift down on hover, set `--chevron-down: 1` on the parent element like: * * ```tsx * const Container = styled.div` * &:hover { * --chevron-down: 1; * } * ` * ``` */ const DropdownChevron = React.forwardRef( function DropdownChevron(props, ref) { return ( {icon} ) }, ) const Container = styled.div` color: #aaaaaa; transition: all 0.12s; transform: translateY(calc(2px * var(--chevron-down, 0))); ` const icon = ( ) export default DropdownChevron ================================================ FILE: packages/studio/src/uiComponents/icons/Ellipsis.tsx ================================================ import * as React from 'react' function Ellipsis(props: React.SVGProps) { return ( ) } export default Ellipsis ================================================ FILE: packages/studio/src/uiComponents/icons/EllipsisFill.tsx ================================================ import * as React from 'react' function EllipsisFill(props: React.SVGProps) { return ( ) } export default EllipsisFill ================================================ FILE: packages/studio/src/uiComponents/icons/GlobeSimple.tsx ================================================ import * as React from 'react' function GlobeSimple(props: React.SVGProps) { return ( ) } export default GlobeSimple ================================================ FILE: packages/studio/src/uiComponents/icons/Outline.tsx ================================================ import * as React from 'react' function Outline(props: React.SVGProps) { return ( ) } export default Outline ================================================ FILE: packages/studio/src/uiComponents/icons/Package.tsx ================================================ import * as React from 'react' function Package(props: React.SVGProps) { return ( ) } export default Package ================================================ FILE: packages/studio/src/uiComponents/icons/Resize.tsx ================================================ import * as React from 'react' function Resize(props: React.SVGProps) { return ( ) } export default Resize ================================================ FILE: packages/studio/src/uiComponents/icons/Trash.tsx ================================================ import * as React from 'react' function Trash(props: React.SVGProps) { return ( ) } export default Trash ================================================ FILE: packages/studio/src/uiComponents/icons/index.ts ================================================ export {default as Outline} from './Outline' export {default as ArrowClockwise} from './ArrowClockwise' export {default as ArrowsOutCardinal} from './ArrowsOutCardinal' export {default as Camera} from './Camera' export {default as ChevronDown} from './ChevronDown' export {default as ChevronRight} from './ChevronRight' export {default as ChevronLeft} from './ChevronLeft' export {default as Cube} from './Cube' export {default as CubeFull} from './CubeFull' export {default as CubeHalf} from './CubeHalf' export {default as CubeRendered} from './CubeRendered' export {default as Details} from './Details' export {default as Ellipsis} from './Ellipsis' export {default as GlobeSimple} from './GlobeSimple' export {default as Resize} from './Resize' export {default as Package} from './Package' export {default as Bell} from './Bell' export {default as Trash} from './Trash' export {default as AddImage} from './AddImage' export {default as EllipsisFill} from './EllipsisFill' ================================================ FILE: packages/studio/src/uiComponents/isSafari.ts ================================================ export const isSafari = typeof window !== 'undefined' && /^((?!chrome|android).)*safari/i.test(navigator.userAgent) ================================================ FILE: packages/studio/src/uiComponents/onPointerOutside.ts ================================================ /** * Calls the callback when the mouse pointer moves outside the * bounds of the node. */ export default function onPointerOutside( node: Element, threshold: number, onPointerOutside: (e: MouseEvent) => void, ) { const containerRect = node.getBoundingClientRect() const onMouseMove = (e: MouseEvent) => { if ( e.clientX < containerRect.left - threshold || e.clientX > containerRect.left + containerRect.width + threshold || e.clientY < containerRect.top - threshold || e.clientY > containerRect.top + containerRect.height + threshold ) { onPointerOutside(e) } } window.addEventListener('mousemove', onMouseMove) return () => { window.removeEventListener('mousemove', onMouseMove) } } ================================================ FILE: packages/studio/src/uiComponents/selects/BasicSelect.tsx ================================================ import type {ElementType} from 'react' import React from 'react' import styled from 'styled-components' const Container = styled.div` display: flex; flex-grow: 1; ` const Label = styled.div` flex-grow: 0; color: hsl(0, 0%, 80%); ` const SelectedValueLabel = styled.div` flex-grow: 1; padding-left: 8px; ` type Option = { label: string value: string } const BasicSelect: React.FC<{ label: string | ElementType options: Array