Skip to content

Commit

Permalink
Dashboard directory interactivity (#6279)
Browse files Browse the repository at this point in the history
* turn object into var

* add todo

* fix style

* fixes

* remove forgot password + reset password

* remove signout

* remove setusername

* remove login

* remove registration

* fix comments

* re-enable flag

* rename div

* add comment

* fix lints

* remove tailwind conf

* Revert "remove registration"

This reverts commit 02439c9.

* Revert "remove login"

This reverts commit 8e6f9c1.

* Revert "remove setusername"

This reverts commit 84721bc.

* Revert "remove signout"

This reverts commit 08a96d3.

* Revert "remove forgot password + reset password"

This reverts commit e52f51a.

* remove opener

* move opener

* tmp

* prettier

* expand docs

* tmp

* replace react-scripts with craco

* add tailwindcss

* switch to brands

* tmp

* tmp

* tmp

* 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

* Fix bundling for dashboard

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

* Move CSS to Tailwind config

* Run prettier

* Update endpoints

* Fix esbuild binary package names

* Remove redundant "npx" prefix from build scripts

* Remove unused dependency

* Begin adding interactivity

* workaround for mac freeze

* Fix modal bugs

* Begin implementing forms, split forms and modals into new files

* Get form UI working

* add missing sections

* Minor fixes, save current directory to localStorage

* Fixes for drop-to-upload

Note: currently it is opening in a new tab instead of actually uploading

* Address review issue

* Fix prettier config; run prettier

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

* (WIP)

* Fix service worker for client-side routing

* Add close button to asset creation forms; fix saving directory to localStorage

* Remove workaround for backend bug when listing directories

* Fix drop-to-upload

* Fix sizing

* Fix spacing, add fixed paths

* WIP: fix toast notification styles, begin adding context menu

* WIP: Add context menu, minor fixes

* Fix authentication on desktop IDE

* Allow unused locals and parameters in tsconfig.json

* Run prettier

* Fix TypeScript errors

* Fix modals; minor refactor

* Implement context menus for labels; fixes

* Add modal provider and switch all modals to provider; fixes

* Fix modals and user icon size

* Fixes

* Remove obsolete files from incorrect merge

* Address review issues

* Fix type error

* Stop removing `#root`

* Fixes for cloud

* Implement search on frontend side

* Fix race condition related to `directoryId`

* Hide directories, files and secrets tables on desktop IDE

* Fix lint errors

* Properly update visible projects when a project is created

* Pass directory id to create project

* Hide column display switcher; remove placeholder column data

---------

Co-authored-by: Nikita Pekin <nikita@frecency.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Paweł Buchowski <pawel.buchowski@enso.org>
  • Loading branch information
4 people authored and Akirathan committed Apr 26, 2023
1 parent f970d7b commit d672d50
Show file tree
Hide file tree
Showing 32 changed files with 1,637 additions and 353 deletions.
1 change: 1 addition & 0 deletions app/ide-desktop/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ export default [
},
],
'@typescript-eslint/no-confusing-void-expression': 'error',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-extraneous-class': 'error',
'@typescript-eslint/no-invalid-void-type': ['error', { allowAsThisParameter: true }],
// React 17 and later supports async functions as event handlers, so we need to disable this
Expand Down
3 changes: 0 additions & 3 deletions app/ide-desktop/lib/client/src/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,6 @@
import * as fs from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
import opener from 'opener'

import * as electron from 'electron'

import * as electron from 'electron'
import opener from 'opener'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as router from 'react-router-dom'
import * as app from '../../components/app'
import * as auth from '../providers/auth'
import * as svg from '../../components/svg'

import Input from './input'
import SvgIcon from './svgIcon'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as fontawesomeIcons from '@fortawesome/free-brands-svg-icons'
import * as app from '../../components/app'
import * as auth from '../providers/auth'
import * as svg from '../../components/svg'

import FontAwesomeIcon from './fontAwesomeIcon'
import Input from './input'
import SvgIcon from './svgIcon'
Expand All @@ -15,9 +16,6 @@ import SvgIcon from './svgIcon'
// === Constants ===
// =================

const BUTTON_CLASS_NAME =
'relative mt-6 border rounded-md py-2 text-sm text-gray-800 bg-gray-100 hover:bg-gray-200'

const LOGIN_QUERY_PARAMS = {
email: 'email',
} as const
Expand Down Expand Up @@ -51,7 +49,7 @@ function Login() {
event.preventDefault()
await signInWithGoogle()
}}
className={BUTTON_CLASS_NAME}
className="relative mt-6 border rounded-md py-2 text-sm text-gray-800 bg-gray-100 hover:bg-gray-200"
>
<FontAwesomeIcon icon={fontawesomeIcons.faGithub} />
<span>Login with Google</span>
Expand All @@ -61,7 +59,7 @@ function Login() {
event.preventDefault()
await signInWithGitHub()
}}
className={BUTTON_CLASS_NAME}
className="relative mt-6 border rounded-md py-2 text-sm text-gray-800 bg-gray-100 hover:bg-gray-200"
>
<FontAwesomeIcon icon={fontawesomeIcons.faGithub} />
<span>Login with Github</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,31 +48,12 @@ interface SessionProviderProps {
export function SessionProvider(props: SessionProviderProps) {
const { mainPageUrl, children, userSession, registerAuthEventListener } = props

const [refresh, doRefresh] = hooks.useRefresh()

/** Flag used to avoid rendering child components until we've fetched the user's session at least
* once. Avoids flash of the login screen when the user is already logged in. */
const [initialized, setInitialized] = react.useState(false)

/** Produces a new object every time.
* This is not equal to any other empty object because objects are compared by reference.
* Because it is not equal to the old value, React re-renders the component. */
function newRefresh() {
return {}
}

/** State that, when set, forces a refresh of the user session. This is useful when a
* user has just logged in (so their cached credentials are out of date). Should be used via the
* `refreshSession` function. */
const [refresh, setRefresh] = react.useState(newRefresh())

/** Forces a refresh of the user session.
*
* Should be called after any operation that **will** (not **might**) change the user's session.
* For example, this should be called after signing out. Calling this will result in a re-render
* of the whole page, which is why it should only be done when necessary. */
const refreshSession = () => {
setRefresh(newRefresh())
}

/** Register an async effect that will fetch the user's session whenever the `refresh` state is
* incremented. This is useful when a user has just logged in (as their cached credentials are
* out of date, so this will update them). */
Expand All @@ -83,7 +64,7 @@ export function SessionProvider(props: SessionProviderProps) {
setInitialized(true)
return innerSession
},
[refresh, userSession]
[userSession, refresh]
)

/** Register an effect that will listen for authentication events. When the event occurs, we
Expand All @@ -97,7 +78,7 @@ export function SessionProvider(props: SessionProviderProps) {
switch (event) {
case listen.AuthEvent.signIn:
case listen.AuthEvent.signOut: {
refreshSession()
doRefresh()
break
}
case listen.AuthEvent.customOAuthState:
Expand All @@ -110,7 +91,7 @@ export function SessionProvider(props: SessionProviderProps) {
* See:
* https://github.com/aws-amplify/amplify-js/issues/3391#issuecomment-756473970 */
window.history.replaceState({}, '', mainPageUrl)
refreshSession()
doRefresh()
break
}
default: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export const SECRET_ICON = (
<path
d="M10.3 13a4 4 0 1 1 0-2h10a1 1 0 0 1 1 1v3a1 1 0 0 1-2 0v-2h-2v2a1 1 0 0 1-2 0v-2ZM3.5 12a1 1 0 1 1 2 0a1 1 0 1 1-2 0"
fill="currentColor"
fill-rule="evenodd"
fillRule="evenodd"
/>
</svg>
)
Expand Down Expand Up @@ -164,6 +164,17 @@ export const ARROW_UP_ICON = (
</svg>
)

/** `+`-shaped icon representing creation of an item. */
export const ADD_ICON = (
<svg width={18} height={18} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx={12} cy={12} r={12} fill="currentColor" fillOpacity={0.1} />
<g opacity={0.66}>
<rect x={11} y={6} width={2} height={12} fill="currentColor" />
<rect x={6} y={11} width={12} height={2} fill="currentColor" />
</g>
</svg>
)

/** An icon representing creation of an item. */
export const CIRCLED_PLUS_ICON = (
<svg
Expand Down Expand Up @@ -200,7 +211,7 @@ export const MAGNIFYING_GLASS_ICON = (
d="M11.4142 10L15.6569 14.2426L14.2426 15.6569L10 11.4142L11.4142 10Z"
fill="currentColor"
/>
<circle cx={7} cy={7} r={5} stroke="currentColor" stroke-width={2} />
<circle cx={7} cy={7} r={5} stroke="currentColor" strokeWidth={2} />
</g>
</svg>
)
Expand All @@ -213,6 +224,17 @@ export const SPEECH_BUBBLE_ICON = (
</svg>
)

/** `x`-shaped icon representing the closing of a window. */
export const CLOSE_ICON = (
<svg width={18} height={18} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx={12} cy={12} r={12} fill="currentColor" fillOpacity={0.1} />
<g opacity={0.66} transform="rotate(45 12 12)">
<rect x={11} y={6} width={2} height={12} fill="currentColor" />
<rect x={6} y={11} width={12} height={2} fill="currentColor" />
</g>
</svg>
)

// ===========
// === Svg ===
// ===========
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/** @file Modal for confirming delete of any type of asset. */
import toast from 'react-hot-toast'

import * as modalProvider from '../../providers/modal'
import * as svg from '../../components/svg'

import Modal from './modal'

// =================
// === Component ===
// =================

export interface ConfirmDeleteModalProps {
assetType: string
name: string
doDelete: () => Promise<void>
onSuccess: () => void
}

function ConfirmDeleteModal(props: ConfirmDeleteModalProps) {
const { assetType, name, doDelete, onSuccess } = props
const { unsetModal } = modalProvider.useSetModal()
return (
<Modal className="bg-opacity-90">
<form
className="relative bg-white shadow-soft rounded-lg w-96 p-2"
onClick={event => {
event.stopPropagation()
}}
>
<button type="button" className="absolute right-0 top-0 m-2" onClick={unsetModal}>
{svg.CLOSE_ICON}
</button>
Are you sure you want to delete the {assetType} '{name}'?
<div className="m-1">
<div
className="hover:cursor-pointer inline-block text-white bg-red-500 rounded-full px-4 py-1 m-1"
onClick={async () => {
unsetModal()
await toast.promise(doDelete(), {
loading: `Deleting ${assetType}...`,
success: `Deleted ${assetType}.`,
error: `Could not delete ${assetType}.`,
})
onSuccess()
}}
>
Delete
</div>
<div
className="hover:cursor-pointer inline-block bg-gray-200 rounded-full px-4 py-1 m-1"
onClick={unsetModal}
>
Cancel
</div>
</div>
</form>
</Modal>
)
}

export default ConfirmDeleteModal
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/** @file A context menu. */

import * as react from 'react'

// =================
// === Component ===
// =================

export interface ContextMenuProps {
// `left: number` and `top: number` may be more correct,
// however passing an event eliminates the chance
// of passing the wrong coordinates from the event.
event: react.MouseEvent
}

function ContextMenu(props: react.PropsWithChildren<ContextMenuProps>) {
const { children, event } = props
return (
<div
style={{ left: event.pageX, top: event.pageY }}
className="absolute bg-white rounded-lg shadow-soft flex flex-col flex-nowrap"
>
{children}
</div>
)
}

export default ContextMenu
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/** @file An entry in a context menu. */

import * as react from 'react'

export interface ContextMenuEntryProps {
disabled?: boolean
onClick: (event: react.MouseEvent<HTMLButtonElement>) => void
}

// This component MUST NOT use `useState` because it is not rendered directly.
function ContextMenuEntry(props: react.PropsWithChildren<ContextMenuEntryProps>) {
const { children, disabled, onClick } = props
return (
<button
disabled={disabled}
className={`${
disabled ? 'opacity-50' : ''
} p-1 hover:bg-gray-200 first:rounded-t-lg last:rounded-b-lg text-left`}
onClick={event => {
event.stopPropagation()
onClick(event)
}}
>
{children}
</button>
)
}

export default ContextMenuEntry
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/** @file Base form to create an asset.
* This should never be used directly, but instead should be wrapped in a component
* that creates a specific asset type. */

import * as react from 'react'

import * as modalProvider from '../../providers/modal'
import * as svg from '../../components/svg'

import Modal from './modal'

/** The props that should also be in the wrapper component. */
export interface CreateFormPassthroughProps {
left: number
top: number
}

/** `CreateFormPassthroughProps`, plus props that should be defined in the wrapper component. */
export interface CreateFormProps extends CreateFormPassthroughProps, react.PropsWithChildren {
title: string
onSubmit: (event: react.FormEvent) => Promise<void>
}

function CreateForm(props: CreateFormProps) {
const { title, left, top, children, onSubmit: wrapperOnSubmit } = props
const { unsetModal } = modalProvider.useSetModal()

async function onSubmit(event: react.FormEvent) {
event.preventDefault()
await wrapperOnSubmit(event)
}

return (
<Modal className="bg-opacity-25">
<form
style={{ left, top }}
className="absolute bg-white shadow-soft rounded-lg w-60"
onSubmit={onSubmit}
onClick={event => {
event.stopPropagation()
}}
>
<button type="button" className="absolute right-0 m-2" onClick={unsetModal}>
{svg.CLOSE_ICON}
</button>
<h2 className="inline-block font-semibold m-2">{title}</h2>
{children}
<input
type="submit"
className="hover:cursor-pointer inline-block text-white bg-blue-600 rounded-full px-4 py-1 m-2"
value="Create"
/>
</form>
</Modal>
)
}

export default CreateForm
Loading

0 comments on commit d672d50

Please sign in to comment.