Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attachment management #55

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions common/input/file/FileInputField.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React, { useRef } from "react"
import styled from "styled-components"
import { getHumanReadableSize } from "../../../modules/message/preview/attachment/helpers/getHumanReadableSize"
import type { AttachmentLike } from "../../../modules/message/state/models/AttachmentModel"
import { SCREEN_SMALL } from "../../layout/breakpoints"
import { FlexContainer } from "../../layout/FlexContainer"
import { PrimaryButton } from "../button/PrimaryButton"
import { InputError } from "../error/InputError"
import { getLengthConstraintColor } from "../getLengthConstraintColor"
import { Input } from "../layout/Input"
import { InputConstraint } from "../layout/InputConstraint"
import { InputContainer } from "../layout/InputContainer"
Expand All @@ -26,11 +29,11 @@ const ClipboardButton = styled(PasteFileButton)`

export type FileInputProps = {
id: string
value: readonly File[]
value: AttachmentLike[]
onChange: (value: File[]) => void
label: string
disabled?: boolean
maxSize?: number
maxSize?: number,
}

export function FileInputField(props: FileInputProps) {
Expand All @@ -40,7 +43,7 @@ export function FileInputField(props: FileInputProps) {
onChange: handleChange,
label,
disabled = false,
maxSize,
maxSize = 0,
} = props

const inputRef = useRef<HTMLInputElement>(null)
Expand All @@ -52,13 +55,19 @@ export function FileInputField(props: FileInputProps) {
handleChange([])
}

const currentSize = value.reduce((acc, v) => acc + v.size, 0)
const maxReadableSize = getHumanReadableSize(maxSize)
const currentReadableSize = getHumanReadableSize(currentSize)

return (
<InputContainer>
<InputLabel>
<label htmlFor={id}>{label}</label>
{maxSize && (
<InputConstraint>
{getHumanReadableSize(maxSize)} max.
<InputConstraint
state={getLengthConstraintColor(currentSize, maxSize)}
>
{currentReadableSize}/{maxReadableSize}
</InputConstraint>
)}
</InputLabel>
Expand All @@ -75,7 +84,7 @@ export function FileInputField(props: FileInputProps) {
}}
/>
<FakeInput
value={value.map(file => file.name).join(", ")}
value={value.map(attachment => attachment.filename).join(", ")}
readOnly
disabled={disabled}
tabIndex={-1}
Expand All @@ -89,6 +98,7 @@ export function FileInputField(props: FileInputProps) {
Clear
</PrimaryButton>
</FlexContainer>
{currentSize > maxSize && <InputError error={`Exceeds maximum size of ${getHumanReadableSize(maxSize)}`} />}
</InputContainer>
)
}
20 changes: 19 additions & 1 deletion modules/editor/EditorManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { Instance, SnapshotOrInstance, types } from "mobx-state-tree"
import { delay } from "../../common/state/delay"
import type { AttachmentData } from "../message/state/data/AttachmentData"
import { AttachmentModel } from "../message/state/models/AttachmentModel"
import { MessageModel } from "../message/state/models/MessageModel"
import { WebhookModel } from "../webhook/WebhookModel"

Expand Down Expand Up @@ -59,6 +61,22 @@ export const EditorManager = types

/* eslint-enable no-await-in-loop */

message.set(
"reference",
`https://discord.com/channels/${self.targets[0].guildId}/${data.channel_id}/${data.id}`,
)
message.set("attachments", data.attachments.map(
(attachment: AttachmentData) =>
AttachmentModel.create({
id: attachment.id,
contentType: attachment.content_type,
filename: attachment.filename,
local: false,
url: attachment.url,
size: attachment.size
})
))

console.log("Target executed", data)
}
}
Expand Down Expand Up @@ -91,4 +109,4 @@ export const EditorManager = types
}))

// eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/consistent-type-definitions
export interface EditorManagerLike extends Instance<typeof EditorManager> {}
export interface EditorManagerLike extends Instance<typeof EditorManager> { }
114 changes: 68 additions & 46 deletions modules/editor/message/PrimaryContentEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { useObserver } from "mobx-react-lite"
import { applyPatch } from "mobx-state-tree"
import React from "react"
import { InputError } from "../../../common/input/error/InputError"
import { FileInputField } from "../../../common/input/file/FileInputField"
import { InputLabel } from "../../../common/input/layout/InputLabel"
import { InputField } from "../../../common/input/text/InputField"
import { Section } from "../../../common/layout/Section"
import { Stack } from "../../../common/layout/Stack"
import type { MessageItemFormState } from "../../message/state/editorForm"
import { isLocal, isNotLocal } from "../../message/state/models/AttachmentModel"
import type { MessageLike } from "../../message/state/models/MessageModel"

export type PrimaryContentEditorProps = {
Expand All @@ -18,50 +21,69 @@ export function PrimaryContentEditor(props: PrimaryContentEditorProps) {

const isEditing = Boolean(message.reference)

return useObserver(() => (
<Stack gap={12}>
<InputField
id={`_${message.id}_content`}
label="Content"
maxLength={2000}
rows={4}
error={form.field("content").error}
{...form.field("content").inputProps}
/>
<Section name="Profile">
<Stack gap={12}>
<InputField
id={`_${message.id}_username`}
label="Username"
maxLength={80}
error={form.field("username").error}
{...form.field("username").inputProps}
disabled={isEditing}
/>
<InputField
id={`_${message.id}_avatar`}
label="Avatar URL"
error={form.field("avatar").error}
{...form.field("avatar").inputProps}
disabled={isEditing}
/>
<InputError
variant="warning"
error={
isEditing
? "You cannot edit the username and avatar for previously sent messages"
: undefined
}
/>
</Stack>
</Section>
<FileInputField
id={`_${message.id}_files`}
label="Files"
maxSize={8 * 1024 ** 2}
value={message.files}
onChange={files => message.set("files", files)}
/>
</Stack>
))
return useObserver(() => {
const attachments = message.attachments.filter(isNotLocal)
const localFiles = message.attachments.filter(isLocal)
return (
<Stack gap={12}>
<InputField
id={`_${message.id}_content`}
label="Content"
maxLength={2000}
rows={4}
error={form.field("content").error}
{...form.field("content").inputProps}
/>
<Section name="Profile">
<Stack gap={12}>
<InputField
id={`_${message.id}_username`}
label="Username"
maxLength={80}
error={form.field("username").error}
{...form.field("username").inputProps}
disabled={isEditing}
/>
<InputField
id={`_${message.id}_avatar`}
label="Avatar URL"
error={form.field("avatar").error}
{...form.field("avatar").inputProps}
disabled={isEditing}
/>
<InputError
variant="warning"
error={
isEditing
? "You cannot edit the username and avatar for previously sent messages"
: undefined
}
/>
</Stack>
</Section>
{attachments.length > 0 && (
<Stack gap={12}>
<InputLabel>Attachments</InputLabel>
{attachments.map((a, i) => (
<button onClick={() => {
applyPatch(form.state.value, [
{
op: "remove",
path: `${form.path}/attachments/${i}`,
}
])
}} key={a.id}>Remove {a.filename}</button>
))}
</Stack>
)}
<FileInputField
id={`_${message.id}_files`}
label="Files"
maxSize={8 * 1024 ** 2}
value={localFiles}
onChange={files => message.setFiles(files)}
/>
</Stack>
)
})
}
8 changes: 4 additions & 4 deletions modules/message/preview/MessagePreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const Container = styled.div`
`};
`

const Content = styled(Markdown)<{ direction: "neutral" | "ltr" | "rtl" }>`
const Content = styled(Markdown) <{ direction: "neutral" | "ltr" | "rtl" }>`
${({ theme, direction }) =>
theme.appearance.display === "cozy" &&
direction === "rtl" &&
Expand Down Expand Up @@ -110,10 +110,10 @@ export function MessagePreview(props: MessagePreviewProps) {
{message.hasExtras && (
<ExtrasContainer>
{[
...message.files.map(file => (
...message.attachments.map(attachment => (
<Attachment
key={`Attachment ${JSON.stringify(file.name)}`}
file={file}
key={`Attachment ${attachment.id} ${attachment.filename}`}
file={attachment}
/>
)),
...message.embeds.map(embed => (
Expand Down
7 changes: 4 additions & 3 deletions modules/message/preview/attachment/Attachment.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import React from "react"
import type { AttachmentLike } from "../../state/models/AttachmentModel"
import { AudioAttachment } from "./components/AudioAttachment"
import { DefaultAttachment } from "./components/DefaultAttachment"
import { ImageAttachment } from "./components/ImageAttachment"
import { getAttachmentType } from "./helpers/getAttachmentType"

export type AttachmentProps = {
file: File
file: AttachmentLike
}

export function Attachment(props: AttachmentProps) {
const { file } = props
const { name, type: mime } = file
const { filename, contentType: mime } = file

const type = getAttachmentType(name, mime)
const type = getAttachmentType(filename, mime)

switch (type) {
case "image": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react"
import styled from "styled-components"
import type { AttachmentLike } from "../../../state/models/AttachmentModel"
import { getAttachmentIcon } from "../helpers/getAttachmentIcon"
import { getHumanReadableSize } from "../helpers/getHumanReadableSize"
import { download } from "../icons/download"
Expand Down Expand Up @@ -83,18 +84,18 @@ const AudioDownloadButton = styled(AttachmentDownloadButton)`
`

export type AudioAttachmentProps = {
file: File
file: AttachmentLike
}

export function AudioAttachment(props: AudioAttachmentProps) {
const { name, size } = props.file
const { filename, size } = props.file

return (
<AudioContainer>
<AudioMetadata>
<AudioIconContainer>{getAttachmentIcon("audio")}</AudioIconContainer>
<AudioAttachmentInfo>
<AudioFileName>{name}</AudioFileName>
<AudioFileName>{filename}</AudioFileName>
<AudioFileSize>{getHumanReadableSize(size)}</AudioFileSize>
</AudioAttachmentInfo>
<AudioDownloadButton>{download}</AudioDownloadButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from "react"
import type { AttachmentLike } from "../../../state/models/AttachmentModel"
import type { AttachmentType } from "../AttachmentType"
import { getAttachmentIcon } from "../helpers/getAttachmentIcon"
import { getHumanReadableSize } from "../helpers/getHumanReadableSize"
Expand All @@ -14,13 +15,13 @@ import {
} from "./styles"

export type DefaultAttachmentProps = {
file: File
file: AttachmentLike
type: AttachmentType
}

export function DefaultAttachment(props: DefaultAttachmentProps) {
const { file, type } = props
const { name, size } = file
const { filename, size } = file

return (
<AttachmentContainer>
Expand All @@ -29,7 +30,7 @@ export function DefaultAttachment(props: DefaultAttachmentProps) {
</AttachmentIconContainer>
<AttachmentInfo>
<AttachmentFileName>
<AttachmentFileNameInner>{name}</AttachmentFileNameInner>
<AttachmentFileNameInner>{filename}</AttachmentFileNameInner>
</AttachmentFileName>
<AttachmentFileSize>{getHumanReadableSize(size)}</AttachmentFileSize>
</AttachmentInfo>
Expand Down
Loading