Tldraw/packages/ui/src/lib/components/EditLinkDialog.tsx

143 wiersze
4.2 KiB
TypeScript
Czysty Zwykły widok Historia

2023-04-25 11:01:25 +00:00
import { useApp } from '@tldraw/editor'
import { useCallback, useRef, useState } from 'react'
import { track } from 'signia-react'
import { DialogProps } from '../hooks/useDialogsProvider'
import { useTranslation } from '../hooks/useTranslation/useTranslation'
import { Button } from './primitives/Button'
import * as Dialog from './primitives/Dialog'
import { Input } from './primitives/Input'
const validUrlRegex = new RegExp(
/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i
)
// A url can either be invalid, or valid with a protocol, or valid without a protocol.
// For example, "aol.com" would be valid with a protocol ()
function valiateUrl(url: string) {
if (validUrlRegex.test(url)) return true
if (validUrlRegex.test('https://' + url)) return 'needs protocol'
return false
}
export const EditLinkDialog = track(function EditLink({ onClose }: DialogProps) {
const app = useApp()
const msg = useTranslation()
const selectedShape = app.onlySelectedShape
[feature] ui events (#1326) This PR updates the editor events: - adds types to the events emitted by the app (by `app.emit`) - removes a few events emitted by the app (e.g. `move-to-page`, `change-camera`) - adds `onEvent` prop to the <TldrawUi> / <Tldraw> components - call the `onEvent` when actions occur or tools are selected - does some superficial cleanup on editor app APIs ### Release Note - Fix layout bug in error dialog - (ui) Add `TLEventMap` for types emitted from editor app - (editor) Update `crash` event emitted from editor app to include error - (editor) Update `change-history` event emitted from editor app - (editor) Remove `change-camera` event from editor app - (editor) Remove `move-to-page` event from editor app - (ui) Add `onEvent` prop and events to <Tldraw> / <TldrawUi> - (editor) Replace `app.openMenus` plain Set with computed value - (editor) Add `addOpenMenu` method - (editor) Add `removeOpenMenu` method - (editor) Add `setFocusMode` method - (editor) Add `setToolLocked` method - (editor) Add `setSnapMode` method - (editor) Add `isSnapMode` method - (editor) Update `setGridMode` method return type to editor app - (editor) Update `setReadOnly` method return type to editor app - (editor) Update `setPenMode` method return type to editor app - (editor) Update `selectNone` method return type to editor app - (editor) Rename `backToContent` to `zoomToContent` - (editor) Remove `TLReorderOperation` type --------- Co-authored-by: Orange Mug <orangemug@users.noreply.github.com>
2023-05-11 22:14:58 +00:00
const [validState, setValid] = useState(valiateUrl(selectedShape?.props.url))
2023-04-25 11:01:25 +00:00
const rInitialValue = useRef(selectedShape?.props.url)
const rValue = useRef(selectedShape?.props.url)
const [urlValue, setUrlValue] = useState<string>(
validState
? validState === 'needs protocol'
? 'https://' + selectedShape?.props.url
: selectedShape?.props.url
: 'https://'
)
const handleChange = useCallback((rawValue: string) => {
// Just auto-correct double https:// from a bad paste.
const value = rawValue.replace(/https?:\/\/(https?:\/\/)/, (_match, arg1) => {
return arg1
})
setUrlValue(value)
const validStateUrl = valiateUrl(value.trim())
[feature] ui events (#1326) This PR updates the editor events: - adds types to the events emitted by the app (by `app.emit`) - removes a few events emitted by the app (e.g. `move-to-page`, `change-camera`) - adds `onEvent` prop to the <TldrawUi> / <Tldraw> components - call the `onEvent` when actions occur or tools are selected - does some superficial cleanup on editor app APIs ### Release Note - Fix layout bug in error dialog - (ui) Add `TLEventMap` for types emitted from editor app - (editor) Update `crash` event emitted from editor app to include error - (editor) Update `change-history` event emitted from editor app - (editor) Remove `change-camera` event from editor app - (editor) Remove `move-to-page` event from editor app - (ui) Add `onEvent` prop and events to <Tldraw> / <TldrawUi> - (editor) Replace `app.openMenus` plain Set with computed value - (editor) Add `addOpenMenu` method - (editor) Add `removeOpenMenu` method - (editor) Add `setFocusMode` method - (editor) Add `setToolLocked` method - (editor) Add `setSnapMode` method - (editor) Add `isSnapMode` method - (editor) Update `setGridMode` method return type to editor app - (editor) Update `setReadOnly` method return type to editor app - (editor) Update `setPenMode` method return type to editor app - (editor) Update `selectNone` method return type to editor app - (editor) Rename `backToContent` to `zoomToContent` - (editor) Remove `TLReorderOperation` type --------- Co-authored-by: Orange Mug <orangemug@users.noreply.github.com>
2023-05-11 22:14:58 +00:00
setValid((s) => (s === validStateUrl ? s : validStateUrl))
2023-04-25 11:01:25 +00:00
if (validStateUrl) {
rValue.current = value
}
}, [])
const handleClear = useCallback(() => {
app.setProp('url', '', false)
onClose()
}, [app, onClose])
const handleComplete = useCallback(
(value: string) => {
value = value.trim()
const validState = valiateUrl(value)
const shape = app.selectedShapes[0]
if (shape) {
const current = shape.props.url
const next = validState
? validState === 'needs protocol'
? 'https://' + value
: value
: shape.type === 'bookmark'
? rInitialValue.current
: ''
if (current !== undefined && current !== next) {
app.setProp('url', next, false)
}
}
onClose()
},
[app, onClose]
)
const handleCancel = useCallback(() => {
onClose()
}, [onClose])
if (!selectedShape) {
// dismiss modal
onClose()
return null
}
// Are we going from a valid state to an invalid state?
const isRemoving = rInitialValue.current && !validState
return (
<>
<Dialog.Header>
<Dialog.Title>{msg('edit-link-dialog.title')}</Dialog.Title>
<Dialog.CloseButton />
</Dialog.Header>
<Dialog.Body>
<div className="tlui-edit-link-dialog">
<Input
className="tlui-edit-link-dialog__input"
label="edit-link-dialog.url"
autofocus
value={urlValue}
onValueChange={handleChange}
onComplete={handleComplete}
onCancel={handleCancel}
/>
<div>
{validState ? msg('edit-link-dialog.detail') : msg('edit-link-dialog.invalid-url')}
</div>
</div>
</Dialog.Body>
<Dialog.Footer className="tlui-dialog__footer__actions">
<Button onClick={handleCancel} onTouchEnd={handleCancel}>
{msg('edit-link-dialog.cancel')}
</Button>
{isRemoving ? (
<Button type={'danger'} onTouchEnd={handleClear} onClick={handleClear}>
{msg('edit-link-dialog.clear')}
</Button>
) : (
<Button
type="primary"
disabled={!validState}
onTouchEnd={() => handleComplete(rValue.current)}
onClick={() => handleComplete(rValue.current)}
>
{msg('edit-link-dialog.save')}
</Button>
)}
</Dialog.Footer>
</>
)
})