Skip to content

Commit

Permalink
[botcom] Shared file fixes (tldraw#4761)
Browse files Browse the repository at this point in the history
making it so that

- you can't rename other people's files
- you can't delete other people's files
- shared files appear immediately in your nav menu
- you can't change the share settings of other people's files

### Change type


- [x] `other`
  • Loading branch information
ds300 authored Oct 23, 2024
1 parent 1f97365 commit 6644f01
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 23 deletions.
15 changes: 13 additions & 2 deletions apps/dotcom/client/src/tla/app/TldrawApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,19 @@ export class TldrawApp {
return file.ownerId === this.getCurrentUserId()
}

async deleteFile(_fileId: TldrawAppFileId) {
this.store.remove([_fileId])
async deleteOrForgetFile(fileId: TldrawAppFileId) {
if (this.isFileOwner(fileId)) {
this.store.remove([fileId])
} else {
const myId = this.getCurrentUserId()
const fileVisits = this.getAll('file-visit')
.filter((r) => r.fileId === fileId && r.ownerId === myId)
.map((r) => r.id)
const fileEdits = this.getAll('file-edit')
.filter((r) => r.ownerId === myId && r.fileId === fileId)
.map((r) => r.id)
this.store.remove([...fileVisits, ...fileEdits])
}
}

async createFilesFromTldrFiles(_snapshots: TLStoreSnapshot[]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { TldrawAppFileRecordType } from '@tldraw/dotcom-shared'
import classNames from 'classnames'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useParams } from 'react-router-dom'
import {
DefaultPageMenu,
TldrawUiButton,
Expand All @@ -11,6 +13,7 @@ import {
} from 'tldraw'
import { useApp } from '../../hooks/useAppState'
import { useCurrentFileId } from '../../hooks/useCurrentFileId'
import { useIsFileOwner } from '../../hooks/useIsFileOwner'
import { useRaw } from '../../hooks/useRaw'
import { TlaFileMenu } from '../TlaFileMenu/TlaFileMenu'
import { TlaFileShareMenu } from '../TlaFileShareMenu/TlaFileShareMenu'
Expand All @@ -36,6 +39,7 @@ export function TlaEditorTopLeftPanel({ isAnonUser }: { isAnonUser: boolean }) {
export function TlaEditorTopLeftPanelAnonymous() {
const raw = useRaw()
const editor = useEditor()
const isTempFile = !useParams().fileSlug
const fileName = useValue('fileName', () => editor.getDocumentSettings().name || 'New board', [])
const handleFileNameChange = useCallback(
(name: string) => editor.updateDocumentSettings({ name }),
Expand All @@ -49,7 +53,10 @@ export function TlaEditorTopLeftPanelAnonymous() {
<TldrawUiIcon icon="share-1" />
</TldrawUiButton>
</TlaFileShareMenu>
<TlaFileNameEditor fileName={fileName} onChange={handleFileNameChange} />
<TlaFileNameEditor
fileName={fileName}
onChange={isTempFile ? handleFileNameChange : undefined}
/>
<span className={styles.topPanelSeparator}>{raw('/')}</span>
<DefaultPageMenu />
</>
Expand Down Expand Up @@ -86,14 +93,17 @@ export function TlaEditorTopLeftPanelSignedIn() {
const handleRenameAction = () => setIsRenaming(true)
const handleRenameEnd = () => setIsRenaming(false)

const fileSlug = useParams().fileSlug ?? '_not_a_file_' // fall back to a string that will not match any file
const isOwner = useIsFileOwner(TldrawAppFileRecordType.createId(fileSlug))

return (
<>
<TlaSidebarToggle />
<TlaSidebarToggleMobile />
<TlaFileNameEditor
isRenaming={isRenaming}
fileName={fileName ?? 'FIXME'}
onChange={handleFileNameChange}
onChange={isOwner ? handleFileNameChange : undefined}
onEnd={handleRenameEnd}
/>
<span className={styles.topPanelSeparator}>{raw('/')}</span>
Expand All @@ -114,22 +124,25 @@ function TlaFileNameEditor({
isRenaming,
}: {
fileName: string
onChange(name: string): void
onChange?(name: string): void
onEnd?(): void
isRenaming?: boolean
}) {
const [isEditing, setIsEditing] = useState(false)

const handleEditingStart = useCallback(() => {
if (!onChange) return
setIsEditing(true)
}, [])
}, [onChange])

const handleEditingEnd = useCallback(() => {
if (!onChange) return
setIsEditing(false)
}, [])
}, [onChange])

const handleEditingComplete = useCallback(
(name: string) => {
if (!onChange) return
setIsEditing(false)
onChange(name)
onEnd?.()
Expand All @@ -145,15 +158,18 @@ function TlaFileNameEditor({
}, [isRenaming, isEditing])

return (
<div className={styles.inputWrapper}>
<div className={classNames(styles.inputWrapper, onChange && styles.inputWrapperEditable)}>
{isEditing ? (
<TlaFileNameEditorInput
fileName={fileName}
onComplete={handleEditingComplete}
onBlur={handleEditingEnd}
/>
) : (
<button className={styles.nameWidthSetter} onClick={handleEditingStart}>
<button
className={styles.nameWidthSetter}
onClick={onChange ? handleEditingStart : undefined}
>
{fileName.replace(/ /g, '\u00a0')}
</button>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ button.nameWidthSetter {
padding: 0px 12px;
pointer-events: all;
height: 100%;
cursor: pointer;
color: var(--color-text-1);
}

Expand All @@ -85,7 +84,7 @@ button.nameWidthSetter {
color: var(--color-text-1);
}

.inputWrapper::after {
.inputWrapperEditable::after {
display: block;
content: '';
position: absolute;
Expand All @@ -94,8 +93,12 @@ button.nameWidthSetter {
border-radius: var(--radius-2);
}

.inputWrapperEditable button {
cursor: pointer;
}

@media (hover: hover) {
.inputWrapper:hover::after {
.inputWrapperEditable:hover::after {
background-color: var(--color-muted-2);
}
}
Expand Down
13 changes: 11 additions & 2 deletions apps/dotcom/client/src/tla/components/TlaFileMenu/TlaFileMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
useToasts,
} from 'tldraw'
import { useApp } from '../../hooks/useAppState'
import { useIsFileOwner } from '../../hooks/useIsFileOwner'
import { copyTextToClipboard } from '../../utils/copy'
import { getCurrentEditor } from '../../utils/getCurrentEditor'
import { getFilePath, getShareableFileUrl } from '../../utils/urls'
Expand Down Expand Up @@ -77,19 +78,27 @@ export function TlaFileMenu({
})
}, [fileId, addDialog])

const isOwner = useIsFileOwner(fileId)

return (
<TldrawUiDropdownMenuRoot id={`file-menu-${fileId}-${source}`}>
<TldrawUiMenuContextProvider type="menu" sourceId="dialog">
<TldrawUiDropdownMenuTrigger>{children}</TldrawUiDropdownMenuTrigger>
<TldrawUiDropdownMenuContent side="bottom" align="start" alignOffset={0} sideOffset={0}>
<TldrawUiMenuGroup id="file-actions">
<TldrawUiMenuItem label="Copy link" id="copy-link" onSelect={handleCopyLinkClick} />
<TldrawUiMenuItem label="Rename" id="copy-link" onSelect={onRenameAction} />
{isOwner && (
<TldrawUiMenuItem label="Rename" id="copy-link" onSelect={onRenameAction} />
)}
<TldrawUiMenuItem label="Duplicate" id="copy-link" onSelect={handleDuplicateClick} />
{/* <TldrawUiMenuItem label="Star" id="copy-link" onSelect={handleStarLinkClick} /> */}
</TldrawUiMenuGroup>
<TldrawUiMenuGroup id="file-delete">
<TldrawUiMenuItem label="Delete" id="delete" onSelect={handleDeleteClick} />
<TldrawUiMenuItem
label={isOwner ? 'Delete' : 'Forget'}
id="delete"
onSelect={handleDeleteClick}
/>
</TldrawUiMenuGroup>
</TldrawUiDropdownMenuContent>
</TldrawUiMenuContextProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TldrawAppFile, TldrawAppFileId } from '@tldraw/dotcom-shared'
import { useCallback } from 'react'
import { useToasts, useValue } from 'tldraw'
import { useApp } from '../../../hooks/useAppState'
import { useIsFileOwner } from '../../../hooks/useIsFileOwner'
import { useRaw } from '../../../hooks/useRaw'
import { useTldrawUser } from '../../../hooks/useUser'
import { copyTextToClipboard } from '../../../utils/copy'
Expand Down Expand Up @@ -29,13 +30,17 @@ export function TlaShareTab({ fileId }: { fileId: TldrawAppFileId }) {
[app, fileId]
)

const isOwner = useIsFileOwner(fileId)

return (
<>
<TlaMenuSection>
<TlaMenuControlGroup>
<TlaSharedToggle isShared={isShared} fileId={fileId} />
<TlaSelectSharedLinkType isShared={isShared} fileId={fileId} />
</TlaMenuControlGroup>
{isOwner && (
<TlaMenuControlGroup>
<TlaSharedToggle isShared={isShared} fileId={fileId} />
<TlaSelectSharedLinkType isShared={isShared} fileId={fileId} />
</TlaMenuControlGroup>
)}
{isShared && <TlaCopyLinkButton isShared={isShared} fileId={fileId} />}
{isShared && <QrCode url={getShareableFileUrl(fileId)} />}
</TlaMenuSection>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TldrawUiDialogTitle,
} from 'tldraw'
import { useApp } from '../../hooks/useAppState'
import { useIsFileOwner } from '../../hooks/useIsFileOwner'
import { useRaw } from '../../hooks/useRaw'

export function TlaDeleteFileDialog({
Expand All @@ -25,8 +26,10 @@ export function TlaDeleteFileDialog({
const location = useLocation()
const navigate = useNavigate()

const isOwner = useIsFileOwner(fileId)

const handleDelete = useCallback(() => {
app.deleteFile(fileId)
app.deleteOrForgetFile(fileId)
if (location.pathname.endsWith(TldrawAppFileRecordType.parseId(fileId))) {
navigate('/q')
}
Expand All @@ -36,18 +39,18 @@ export function TlaDeleteFileDialog({
return (
<>
<TldrawUiDialogHeader>
<TldrawUiDialogTitle>{raw('Delete file')}</TldrawUiDialogTitle>
<TldrawUiDialogTitle>{raw(isOwner ? 'Delete file' : 'Forget file')}</TldrawUiDialogTitle>
<TldrawUiDialogCloseButton />
</TldrawUiDialogHeader>
<TldrawUiDialogBody style={{ maxWidth: 350 }}>
<>{raw('Are you sure you want to delete this file?')}</>
<>{raw(`Are you sure you want to ${isOwner ? 'delete' : 'forget'} this file?`)}</>
</TldrawUiDialogBody>
<TldrawUiDialogFooter className="tlui-dialog__footer__actions">
<TldrawUiButton type="normal" onClick={onClose}>
<TldrawUiButtonLabel>{raw('Cancel')}</TldrawUiButtonLabel>
</TldrawUiButton>
<TldrawUiButton type="danger" onClick={handleDelete}>
<TldrawUiButtonLabel>{raw('Delete')}</TldrawUiButtonLabel>
<TldrawUiButtonLabel>{raw(isOwner ? 'Delete' : 'Forget')}</TldrawUiButtonLabel>
</TldrawUiButton>
</TldrawUiDialogFooter>
</>
Expand Down
14 changes: 14 additions & 0 deletions apps/dotcom/client/src/tla/hooks/useIsFileOwner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { TldrawAppFileId } from '@tldraw/dotcom-shared'
import { useValue } from 'tldraw'
import { useMaybeApp } from './useAppState'

export function useIsFileOwner(fileId: TldrawAppFileId): boolean {
const app = useMaybeApp()
return useValue(
'isOwner',
() => {
return app?.isFileOwner(fileId) ?? false
},
[app, fileId]
)
}
1 change: 0 additions & 1 deletion apps/dotcom/client/src/tla/utils/local-session-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ try {
...(prevStored as TldrawAppSessionState),
// forced initial values
createdAt: Date.now(),
isSidebarOpen: false,
isSidebarOpenMobile: false,
auth: undefined,
}
Expand Down

0 comments on commit 6644f01

Please sign in to comment.