Skip to content

Commit

Permalink
Cloud/desktop mode switcher (#6308)
Browse files Browse the repository at this point in the history
* fixmes

* fixmes

* fixmes

* fixmes

* fixmes

* fixes for e-hern's comments

* use abortcontroller

* add docs

* fixes

* revert craco, fix windows build

* remove from gitignore

* remove unnecessary check

* tmp

* augment window

* tmptmp

* split errors back up

* tmp

* tmp

* prettier

* fix

* Fix lints

* Prepare for addition for `as T` lint

* Add lint for early returns

* Address review issues

* Fix lints

* remove withrouter

* fix file length

* fixes

* fixes

* remove dashboard

* fix

* use switch

* prettier

* fixes

* prettier

* fixes

* run prettier

* run prettier

* run prettier

* fix main page url

* allow node.js debugging

* fix lints

* change not equal

* prettier

* Remove references to withRouter; fix lints

* Run prettier

* Add cloud endpoints

* Add JSON-RPC endpoints

* Add dashboard skeleton

* Add components and edit dashboard

* Run prettier

* (WIP) Add cloud endpoints

* Add rpc endpoints

* Address review issues

* Formatting and minor fixes for `newtype.ts`

* Address review issues

* Rename `Brand` to `NewtypeVariant`

* Rename `Brand` to `NewtypeVariant`

* Fix formatting in `newtype.ts`

* Switch dashboard to esbuild

* Minor fixes; move Tailwind generation into esbuild-config

* Fix watching `content/` and `client/`

* Bump esbuild binary versions; minor dependency list fixes

* Add dashboard skeleton

* Run prettier

* Fixes; rename "npm run dev" to "npm run watch-dashboard"

* Avoid writing esbuild outputs to disk for `dashboard/`

* Convert watch-dashboard to be fully in-memory; rebuild css files on change

* Remove obsolete FIXME

* Remove unused constants

* Run prettier

* add missing styles

* Fixes

* Fix the fixes

* Run prettier

* Fixes; use nesting plugin to wrap tailwind preflight

* Remove testing flag from client/watch

* Minor fixes

* Run prettier

* Export newtypes

* Make css rebuild when tailwind config changes

* Fix endpoints

* Finish copying changes over

* Remove duplicate type definitions

* Feat: top-bar styles and changePassword feature

* Fix: remove eslint disable comments

* Fix bundling for dashboard

* Fix dashboard/bundle.ts erroring when build directory does not exist

* Move CSS to Tailwind config

* Run prettier

* Copy changes from old branch

* Update endpoints

* Fix esbuild binary package names

* Remove redundant "npx" prefix from build scripts

* Remove unused dependency

* workaround for mac freeze

* Fix bug

* add missing sections

* Fix: bug

* Address review issue

* Fix prettier config; run prettier

* Fix live-reload of `npm run watch-dashboard`

* Fix service worker for client-side routing

* Remove workaround for backend bug when listing directories

* Fix sizing

* Fix spacing, add fixed paths

* Address review issues; minor fixes

* Fix authentication on desktop IDE

* Run prettier

* Allow unused locals and parameters in tsconfig.json

* Run prettier

* Fix TypeScript errors

* Run prettier

* Fix eslint errors, restructure code

* Minor fixes and other changes

* Revert incorrect change to `cognito/changePassword`

* Remove unused file

* Run prettier

* Merge with top bar; implement switching between IDE and dashboard

* Animate project switcher (WIP)

* Fix IDE and project switcher animation

* Fix eslint errors

* Change `#dashboard` to `.dashboard` for Tailwind

* Split `-authentication` option into `-cloud.authentication` and `-cloud.dashboard`

* Address review issues

* Add description for cloud option group

* Extract custom CSS values into Tailwind config; use ModalProvider

* Hide topbar in IDE view

* Add project manager backend service

* Begin fixing IDE open

* (WIP)

* Clean up unused code

* Minor fixes

* Fix

* Fix local backend's usage of project manager (WIP)

* WIP

* Minor fixes

* Fix scrollbar showing because of margins

* Expose `runProject` instead of `main`

* Keep persistent websocket to Project Manager

* Fix spinner state bug

* Fix race condition when switching between backends

* Fix loading local projects

* Make switching local projects work

* Fix opening cloud projects

* Add shortcut to switch back to dashboard

* Fix templates for desktop; other fixes

* Fix status check polling in `ProjectActionButton` not being stopped

* Run prettier

* Retry reconnecting to Project Manager

* fix back to dashboard shortcut on macOS

* Fixes

* More fixes

* Rename `backendModule` to `cloudService`

---------

Co-authored-by: Nikita Pekin <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Paweł Buchowski <[email protected]>
Co-authored-by: Nctdtman <[email protected]>
  • Loading branch information
5 people authored Apr 26, 2023
1 parent dcfbf84 commit 400cdbe
Show file tree
Hide file tree
Showing 30 changed files with 1,210 additions and 724 deletions.
4 changes: 4 additions & 0 deletions app/ide-desktop/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ const RESTRICTED_SYNTAXES = [
'TSAsExpression:has(TSUnknownKeyword, TSNeverKeyword, TSAnyKeyword) > TSAsExpression',
message: 'Use type assertions to specific types instead of `unknown`, `any` or `never`',
},
{
selector: 'IfStatement > ExpressionStatement',
message: 'Wrap `if` branches in `{}`',
},
]

/* eslint-disable @typescript-eslint/naming-convention */
Expand Down
9 changes: 2 additions & 7 deletions app/ide-desktop/lib/content/esbuild-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ export function bundlerOptions(args: Arguments) {
entryPoints: [
pathModule.resolve(THIS_PATH, 'src', 'index.ts'),
pathModule.resolve(THIS_PATH, 'src', 'index.html'),
pathModule.resolve(THIS_PATH, 'src', 'run.js'),
pathModule.resolve(THIS_PATH, 'src', 'style.css'),
pathModule.resolve(THIS_PATH, 'src', 'docsStyle.css'),
...wasmArtifacts.split(pathModule.delimiter),
Expand All @@ -108,13 +107,9 @@ export function bundlerOptions(args: Arguments) {
outbase: 'src',
plugins: [
{
// This is a workaround that is needed
// because esbuild panics when using `loader: { '.js': 'copy' }`.
// See https://github.com/evanw/esbuild/issues/3041.
// Setting `loader: 'copy'` prevents this file from being converted to ESM
// because of the `"type": "module"` in the `package.json`.
// This file MUST be in CommonJS format because it is loaded using `Function()`
// in `ensogl/pack/js/src/runner/index.ts`
// in `ensogl/pack/js/src/runner/index.ts`.
// All other files are ESM because of `"type": "module"` in `package.json`.
name: 'pkg-js-is-cjs',
setup: build => {
build.onLoad({ filter: /[/\\]pkg.js$/ }, async ({ path }) => ({
Expand Down
1 change: 0 additions & 1 deletion app/ide-desktop/lib/content/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
<link rel="stylesheet" href="/docsStyle.css" />
<link rel="stylesheet" href="/tailwind.css" />
<script type="module" src="/index.js" defer></script>
<script type="module" src="/run.js" defer></script>
</head>
<body>
<div id="enso-dashboard" class="enso-dashboard"></div>
Expand Down
208 changes: 118 additions & 90 deletions app/ide-desktop/lib/content/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import * as authentication from 'enso-authentication'
import * as contentConfig from 'enso-content-config'

import * as app from '../../../../../target/ensogl-pack/linked-dist/index'
import * as projectManager from './project_manager'
import GLOBAL_CONFIG from '../../../../gui/config.yaml' assert { type: 'yaml' }

const logger = app.log.logger

Expand All @@ -25,6 +23,8 @@ const ESBUILD_EVENT_NAME = 'change'
const SECOND = 1000
/** Time in seconds after which a `fetchTimeout` ends. */
const FETCH_TIMEOUT = 300
/** The `id` attribute of the element that the IDE will be rendered into. */
const IDE_ELEMENT_ID = 'root'

// ===================
// === Live reload ===
Expand Down Expand Up @@ -119,104 +119,132 @@ function displayDeprecatedVersionDialog() {
}

// ========================
// === Main Entry Point ===
// === Main entry point ===
// ========================

interface StringConfig {
[key: string]: StringConfig | string
}

class Main {
async main(inputConfig: StringConfig) {
const config = Object.assign(
{
loader: {
wasmUrl: 'pkg-opt.wasm',
jsUrl: 'pkg.js',
assetsUrl: 'dynamic-assets',
},
},
inputConfig
)

const appInstance = new app.App({
config,
configOptions: contentConfig.OPTIONS,
packageInfo: {
version: BUILD_INFO.version,
engineVersion: BUILD_INFO.engineVersion,
// Hack to mutate `configOptions.OPTIONS`
let currentAppInstance: app.App | null = new app.App({
config: {
loader: {
wasmUrl: 'pkg-opt.wasm',
jsUrl: 'pkg.js',
assetsUrl: 'dynamic-assets',
},
},
configOptions: contentConfig.OPTIONS,
packageInfo: {
version: BUILD_INFO.version,
engineVersion: BUILD_INFO.engineVersion,
},
})

function tryStopProject() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
currentAppInstance?.wasm?.drop?.()
}

async function runProject(inputConfig?: StringConfig) {
tryStopProject()
const rootElement = document.getElementById(IDE_ELEMENT_ID)
if (!rootElement) {
logger.error(`The root element (the element with ID '${IDE_ELEMENT_ID}') was not found.`)
} else {
while (rootElement.firstChild) {
rootElement.removeChild(rootElement.firstChild)
}
}

const config = Object.assign(
{
loader: {
wasmUrl: 'pkg-opt.wasm',
jsUrl: 'pkg.js',
assetsUrl: 'dynamic-assets',
},
})
},
inputConfig
)

if (appInstance.initialized) {
if (contentConfig.OPTIONS.options.dataCollection.value) {
// TODO: Add remote-logging here.
}
if (!(await checkMinSupportedVersion(contentConfig.OPTIONS))) {
displayDeprecatedVersionDialog()
} else {
if (
(contentConfig.OPTIONS.options.authentication.value ||
contentConfig.OPTIONS.groups.featurePreview.options.newDashboard.value) &&
contentConfig.OPTIONS.groups.startup.options.entry.value ===
contentConfig.OPTIONS.groups.startup.options.entry.default
) {
const hideAuth = () => {
const auth = document.getElementById('dashboard')
const ide = document.getElementById('root')
if (auth) auth.style.display = 'none'
if (ide) ide.style.display = ''
}
/** This package is an Electron desktop app (i.e., not in the Cloud), so
* we're running on the desktop. */
/** TODO [NP]: https://github.com/enso-org/cloud-v2/issues/345
* `content` and `dashboard` packages **MUST BE MERGED INTO ONE**. The IDE
* should only have one entry point. Right now, we have two. One for the cloud
* and one for the desktop. Once these are merged, we can't hardcode the
* platform here, and need to detect it from the environment. */
const platform = authentication.Platform.desktop
/** FIXME [PB]: https://github.com/enso-org/cloud-v2/issues/366
* React hooks rerender themselves multiple times. It is resulting in multiple
* Enso main scene being initialized. As a temporary workaround we check whether
* appInstance was already ran. Target solution should move running appInstance
* where it will be called only once. */
let appInstanceRan = false
const onAuthenticated = () => {
if (
!contentConfig.OPTIONS.groups.featurePreview.options.newDashboard.value
) {
hideAuth()
if (!appInstanceRan) {
appInstanceRan = true
void appInstance.run()
}
}
}
authentication.run({
logger,
platform,
projectManager: projectManager.ProjectManager.default(),
showDashboard:
contentConfig.OPTIONS.groups.featurePreview.options.newDashboard.value,
onAuthenticated,
})
} else {
void appInstance.run()
}
const email = contentConfig.OPTIONS.groups.authentication.options.email.value
// The default value is `""`, so a truthiness check is most appropriate here.
if (email) {
logger.log(`User identified as '${email}'.`)
}
}
currentAppInstance = new app.App({
config,
configOptions: contentConfig.OPTIONS,
packageInfo: {
version: BUILD_INFO.version,
engineVersion: BUILD_INFO.engineVersion,
},
})
console.log('bruh', currentAppInstance)

if (!currentAppInstance.initialized) {
console.error('Failed to initialize the application.')
} else {
if (contentConfig.OPTIONS.options.dataCollection.value) {
// TODO: Add remote-logging here.
}
if (!(await checkMinSupportedVersion(contentConfig.OPTIONS))) {
displayDeprecatedVersionDialog()
} else {
console.error('Failed to initialize the application.')
const email = contentConfig.OPTIONS.groups.authentication.options.email.value
// The default value is `""`, so a truthiness check is most appropriate here.
if (email) {
logger.log(`User identified as '${email}'.`)
}
void currentAppInstance.run()
}
}
}

const API = new Main()

// @ts-expect-error `globalConfig.windowAppScopeName` is not known at typecheck time.
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
window[GLOBAL_CONFIG.windowAppScopeName] = API
if (
(contentConfig.OPTIONS.options.authentication.value ||
contentConfig.OPTIONS.groups.featurePreview.options.newDashboard.value) &&
contentConfig.OPTIONS.groups.startup.options.entry.value ===
contentConfig.OPTIONS.groups.startup.options.entry.default
) {
window.tryStopProject = tryStopProject
window.runProject = runProject
const hideAuth = () => {
const auth = document.getElementById('dashboard')
const ide = document.getElementById('root')
if (auth) {
auth.style.display = 'none'
}
if (ide) {
ide.hidden = false
}
}
/** This package is an Electron desktop app (i.e., not in the Cloud), so
* we're running on the desktop. */
/** TODO [NP]: https://github.com/enso-org/cloud-v2/issues/345
* `content` and `dashboard` packages **MUST BE MERGED INTO ONE**. The IDE
* should only have one entry point. Right now, we have two. One for the cloud
* and one for the desktop. Once these are merged, we can't hardcode the
* platform here, and need to detect it from the environment. */
const platform = authentication.Platform.desktop
/** FIXME [PB]: https://github.com/enso-org/cloud-v2/issues/366
* React hooks rerender themselves multiple times. It is resulting in multiple
* Enso main scene being initialized. As a temporary workaround we check whether
* appInstance was already ran. Target solution should move running appInstance
* where it will be called only once. */
let appInstanceRan = false
const onAuthenticated = () => {
if (!contentConfig.OPTIONS.groups.featurePreview.options.newDashboard.value) {
hideAuth()
if (!appInstanceRan) {
appInstanceRan = true
void runProject()
}
}
}
authentication.run({
logger,
platform,
showDashboard: contentConfig.OPTIONS.groups.featurePreview.options.newDashboard.value,
onAuthenticated,
})
} else {
void runProject()
}
39 changes: 0 additions & 39 deletions app/ide-desktop/lib/content/src/newtype.ts

This file was deleted.

Loading

0 comments on commit 400cdbe

Please sign in to comment.