Dashboard fixes (#9467)

- Close https://github.com/enso-org/cloud-v2/issues/1037
- Fix downloading files. The issue was that the S3 domain was not whitelisted.
- Fix #9345
- Fix https://github.com/enso-org/cloud-v2/issues/1012
- Make "Update" button in assets panel (right panel) disabled until its value is updated.

# Important Notes
None
This commit is contained in:
somebody1234 2024-03-19 23:31:33 +10:00 committed by GitHub
parent 4379df7c2d
commit 3505893390
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 44 additions and 30 deletions

View File

@ -15,6 +15,7 @@ const TRUSTED_HOSTS = [
'production-enso-domain.auth.eu-west-1.amazoncognito.com',
'production-enso-organizations-files.s3.amazonaws.com',
'pb-enso-domain.auth.eu-west-1.amazoncognito.com',
's3.eu-west-1.amazonaws.com',
// This (`localhost`) is required to access Project Manager HTTP endpoints.
// This should be changed appropriately if the Project Manager's port number becomes dynamic.
'127.0.0.1:30535',

View File

@ -66,6 +66,7 @@ import Subscribe from '#/pages/subscribe/Subscribe'
import type Backend from '#/services/Backend'
import LocalBackend from '#/services/LocalBackend'
import * as eventModule from '#/utilities/event'
import LocalStorage from '#/utilities/LocalStorage'
import * as object from '#/utilities/object'
@ -267,12 +268,7 @@ function AppRouter(props: AppProps) {
isClick = true
}
const onMouseUp = (event: MouseEvent) => {
if (
isClick &&
!(event.target instanceof HTMLInputElement) &&
!(event.target instanceof HTMLTextAreaElement) &&
!(event.target instanceof HTMLElement && event.target.isContentEditable)
) {
if (isClick && !eventModule.isElementTextInput(event.target)) {
const selection = document.getSelection()
const app = document.getElementById('app')
const appContainsSelection =

View File

@ -158,7 +158,8 @@ export default function JSONSchemaInput(props: JSONSchemaInputProps) {
readOnly={readOnly}
checked={typeof value === 'boolean' && value}
onChange={event => {
setValue(event.currentTarget.checked)
const newValue: boolean = event.currentTarget.checked
setValue(newValue)
}}
/>
)
@ -240,7 +241,7 @@ export default function JSONSchemaInput(props: JSONSchemaInputProps) {
// This is SAFE; but there is no way to tell TypeScript that an object
// has an index signature.
// eslint-disable-next-line no-restricted-syntax
(oldValue as Record<string, unknown>)[key] === newValue
(oldValue as Readonly<Record<string, unknown>>)[key] === newValue
? oldValue
: { ...oldValue, [key]: newValue }
)
@ -300,7 +301,6 @@ export default function JSONSchemaInput(props: JSONSchemaInputProps) {
setSelectedChildIndex(index)
const newConstantValue = jsonSchema.constantValue(defs, childSchema, true)
setValue(newConstantValue[0] ?? null)
setSelectedChildIndex(index)
}}
/>
)

View File

@ -7,6 +7,7 @@ import * as animationHooks from '#/hooks/animationHooks'
import * as modalProvider from '#/providers/ModalProvider'
import * as eventModule from '#/utilities/event'
import type * as geometry from '#/utilities/geometry'
// ======================
@ -63,9 +64,7 @@ export default function SelectionBrush(props: SelectionBrushProps) {
const onMouseDown = (event: MouseEvent) => {
if (
modalRef.current == null &&
!(event.target instanceof HTMLInputElement) &&
!(event.target instanceof HTMLTextAreaElement) &&
(!(event.target instanceof HTMLElement) || !event.target.isContentEditable) &&
!eventModule.isElementTextInput(event.target) &&
!(event.target instanceof HTMLButtonElement) &&
!(event.target instanceof HTMLAnchorElement)
) {

View File

@ -51,7 +51,7 @@ export default function AssetProperties(props: AssetPropertiesProps) {
const [description, setDescription] = React.useState('')
const [dataLinkValue, setDataLinkValue] = React.useState<NonNullable<unknown> | null>(null)
const [editedDataLinkValue, setEditedDataLinkValue] = React.useState<NonNullable<unknown> | null>(
null
dataLinkValue
)
const [isDataLinkFetched, setIsDataLinkFetched] = React.useState(false)
const isDataLinkSubmittable = React.useMemo(
@ -75,6 +75,7 @@ export default function AssetProperties(props: AssetPropertiesProps) {
self?.permission === permissions.PermissionAction.admin ||
self?.permission === permissions.PermissionAction.edit
const isDataLink = item.item.type === backendModule.AssetType.dataLink
const isDataLinkDisabled = dataLinkValue === editedDataLinkValue || !isDataLinkSubmittable
React.useEffect(() => {
setDescription(item.item.description ?? '')
@ -85,7 +86,7 @@ export default function AssetProperties(props: AssetPropertiesProps) {
if (item.item.type === backendModule.AssetType.dataLink) {
const value = await backend.getConnector(item.item.id, item.item.title)
setDataLinkValue(value)
setEditedDataLinkValue(structuredClone(value))
setEditedDataLinkValue(value)
setIsDataLinkFetched(true)
}
})()
@ -231,8 +232,11 @@ export default function AssetProperties(props: AssetPropertiesProps) {
<div className="flex gap-buttons">
<button
type="button"
disabled={dataLinkValue === editedDataLinkValue || !isDataLinkSubmittable}
className="button bg-invite text-white selectable enabled:active"
disabled={isDataLinkDisabled}
{...(isDataLinkDisabled
? { title: 'Edit the Data Link before updating it.' }
: {})}
className="button bg-invite text-white enabled:active"
onClick={() => {
void (async () => {
if (item.item.type === backendModule.AssetType.dataLink) {
@ -258,9 +262,10 @@ export default function AssetProperties(props: AssetPropertiesProps) {
</button>
<button
type="button"
className="button bg-selected-frame"
disabled={isDataLinkDisabled}
className="button bg-selected-frame enabled:active"
onClick={() => {
setEditedDataLinkValue(structuredClone(dataLinkValue))
setEditedDataLinkValue(dataLinkValue)
}}
>
Cancel

View File

@ -170,9 +170,7 @@ export default function AssetSearchBar(props: AssetSearchBarProps) {
}
// Allow `alt` key to be pressed in case it is being used to enter special characters.
if (
!(event.target instanceof HTMLInputElement) &&
!(event.target instanceof HTMLTextAreaElement) &&
(!(event.target instanceof HTMLElement) || !event.target.isContentEditable) &&
!eventModule.isElementTextInput(event.target) &&
(!(event.target instanceof Node) || rootRef.current?.contains(event.target) !== true) &&
eventModule.isTextInputEvent(event) &&
event.key !== ' ' &&

View File

@ -122,6 +122,12 @@ export default function AccountSettingsTab() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-non-null-assertion
accessToken != null ? JSON.parse(atob(accessToken.split('.')[1]!)).username : null
const canChangePassword = username != null ? !/^Github_|^Google_/.test(username) : false
const canSubmitPassword =
currentPassword !== '' &&
newPassword !== '' &&
confirmNewPassword !== '' &&
newPassword === confirmNewPassword &&
validation.PASSWORD_REGEX.test(newPassword)
const doUpdateName = async (newName: string) => {
const oldName = user?.name ?? ''
@ -231,14 +237,8 @@ export default function AccountSettingsTab() {
</div>
<div className="flex h-row items-center gap-buttons">
<button
disabled={
currentPassword === '' ||
newPassword === '' ||
confirmNewPassword === '' ||
newPassword !== confirmNewPassword ||
!validation.PASSWORD_REGEX.test(newPassword)
}
type="submit"
disabled={!canSubmitPassword}
className={`settings-value rounded-full bg-invite font-medium text-white selectable enabled:active`}
onClick={() => {
setPasswordFormKey(uniqueString.uniqueString())
@ -252,7 +252,8 @@ export default function AccountSettingsTab() {
</button>
<button
type="button"
className="settings-value rounded-full bg-selected-frame font-medium"
disabled={!canSubmitPassword}
className="settings-value rounded-full bg-selected-frame font-medium selectable enabled:active"
onClick={() => {
setPasswordFormKey(uniqueString.uniqueString())
setCurrentPassword('')

View File

@ -50,3 +50,13 @@ export function isTextInputEvent(event: KeyboardEvent | React.KeyboardEvent) {
// Allow `alt` key to be pressed in case it is being used to enter special characters.
return !event.ctrlKey && !event.shiftKey && !event.metaKey && isTextInputKey(event)
}
/** Whether the element accepts text input. */
export function isElementTextInput(element: EventTarget | null) {
return (
element != null &&
(element instanceof HTMLInputElement ||
element instanceof HTMLTextAreaElement ||
(element instanceof HTMLElement && element.isContentEditable))
)
}

View File

@ -2,6 +2,7 @@
* shortcuts. */
import * as detect from 'enso-common/src/detect'
import * as eventModule from '#/utilities/event'
import * as newtype from '#/utilities/newtype'
import * as object from '#/utilities/object'
import * as string from '#/utilities/string'
@ -552,7 +553,10 @@ export function defineBindingNamespace<T extends Record<keyof T, KeybindValue>>(
: buttonToPointerButtonFlags(event.button)
]?.[eventModifierFlags]
let handle = handlers[DEFAULT_HANDLER]
if (matchingBindings != null) {
const isTextInputFocused = eventModule.isElementTextInput(document.activeElement)
const isTextInputEvent = 'key' in event && eventModule.isTextInputEvent(event)
const shouldIgnoreEvent = isTextInputFocused && isTextInputEvent
if (matchingBindings != null && !shouldIgnoreEvent) {
for (const bindingNameRaw in handlers) {
// This is SAFE, because `handlers` is an object with identical keys to `T`,
// which `BindingName` is also derived from.