Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
32 changes: 32 additions & 0 deletions models/File.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Base } from '@idea2app/data-server';
import { toggle } from 'mobx-restful';

import { TableModel } from './Base';
import userStore from './User';

interface SignedLink {
putLink: string;
getLink: string;
}

export class FileModel extends TableModel<Base> {
baseURI = 'file';
client = userStore.client;

@toggle('uploading')
async upload(file: File | Blob) {
const name = file instanceof File ? file.name : crypto.randomUUID();

const { body } = await this.client.post<SignedLink>(`file/signed-link/${name}`);

await fetch(body!.putLink, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type },
});

return body!.getLink;
}
}

export default new FileModel();
66 changes: 63 additions & 3 deletions pages/dashboard/project/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { ConsultMessage, User, UserRole } from '@idea2app/data-server';
import { Avatar, Button, Container, Paper, TextField, Typography } from '@mui/material';
import { Avatar, Button, Container, IconButton, Paper, TextField, Tooltip, Typography } from '@mui/material';
import { marked } from 'marked';
import { observer } from 'mobx-react';
import { ObservedComponent, reaction } from 'mobx-react-helper';
import { compose, JWTProps, jwtVerifier, RouteProps, router } from 'next-ssr-middleware';
import { FormEvent, KeyboardEventHandler } from 'react';
import { ChangeEvent, ClipboardEvent, createRef, DragEvent, FormEvent, KeyboardEventHandler } from 'react';
import { formToJSON, scrollTo, sleep } from 'web-utility';

import { SymbolIcon } from '../../../components/Icon';
import { PageHead } from '../../../components/PageHead';
import { EvaluationDisplay } from '../../../components/Project/EvaluationDisplay';
import { ScrollList } from '../../../components/ScrollList';
import { SessionBox } from '../../../components/User/SessionBox';
import fileStore from '../../../models/File';
import { ConsultMessageModel, ProjectModel } from '../../../models/ProjectEvaluation';
import { i18n, I18nContext } from '../../../models/Translation';

Expand All @@ -31,6 +33,8 @@ export default class ProjectEvaluationPage extends ObservedComponent<

messageStore = new ConsultMessageModel(this.projectId);

fileInputRef = createRef<HTMLInputElement>();

get menu() {
const { t } = this.observedContext;

Expand Down Expand Up @@ -76,6 +80,43 @@ export default class ProjectEvaluationPage extends ObservedComponent<
);
};

handleFiles = async (files: File[]) => {
for (const file of files) {
const url = await fileStore.upload(file);
const content = file.type.startsWith('image/')
? `![${file.name}](${url})`
: `[${file.name}](${url})`;

await this.messageStore.updateOne({ content });
}
};

handleFileInputChange = async (event: ChangeEvent<HTMLInputElement>) => {
const files = [...(event.target.files || [])];

event.target.value = '';

if (files.length > 0) await this.handleFiles(files);
};

handlePasteDrop = async (event: ClipboardEvent | DragEvent) => {
const items =
event.type === 'paste'
? [...(event as ClipboardEvent).clipboardData.items]
: [...(event as DragEvent).dataTransfer.items];

const files = items
.filter(item => item.kind === 'file')
.map(item => item.getAsFile())
.filter((file): file is File => file !== null);

if (files.length > 0) {
event.preventDefault();

await this.handleFiles(files);
}
};
Comment thread
TechQuery marked this conversation as resolved.
Outdated

renderChatMessage = (
{ id, content, evaluation, prototypes, createdAt, createdBy }: ConsultMessage,
index = 0,
Expand Down Expand Up @@ -175,6 +216,22 @@ export default class ProjectEvaluationPage extends ObservedComponent<
className="sticky bottom-0 mx-1 mt-auto mb-1 flex items-end gap-2 p-1.5 sm:mx-0 sm:mb-0 sm:p-2"
onSubmit={this.handleMessageSubmit}
>
<input
ref={this.fileInputRef}
type="file"
multiple
className="hidden"
onChange={this.handleFileInputChange}
/>
<Tooltip title={t('attach_files')}>
<IconButton
size="small"
disabled={fileStore.uploading > 0 || messageStore.uploading > 0}
onClick={() => this.fileInputRef.current?.click()}
>
<SymbolIcon name="attach_file" />
</IconButton>
</Tooltip>
Comment thread
TechQuery marked this conversation as resolved.
Outdated
<TextField
name="content"
placeholder={t('type_your_message')}
Expand All @@ -185,12 +242,15 @@ export default class ProjectEvaluationPage extends ObservedComponent<
size="small"
required
onKeyUp={this.handleQuickSubmit}
onPaste={this.handlePasteDrop}
onDragOver={e => e.preventDefault()}
onDrop={this.handlePasteDrop}
/>
<Button
type="submit"
variant="contained"
className="min-w-full px-2 whitespace-nowrap sm:min-w-0"
disabled={messageStore.uploading > 0}
disabled={fileStore.uploading > 0 || messageStore.uploading > 0}
>
{t('send')}
</Button>
Expand Down
1 change: 1 addition & 0 deletions translation/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export default {
type_your_message: 'Type your message...',
send_message: 'Send Message',
send: 'Send',
attach_files: 'Attach Files',

// Prototype Generator
generate_prototype: 'Generate Prototype',
Expand Down
1 change: 1 addition & 0 deletions translation/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export default {
type_your_message: '输入您的消息……',
send_message: '发送消息',
send: '发送',
attach_files: '上传文件',

// Prototype Generator
generate_prototype: '生成原型',
Expand Down
1 change: 1 addition & 0 deletions translation/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export default {
type_your_message: '輸入您的訊息……',
send_message: '發送訊息',
send: '發送',
attach_files: '上傳檔案',

// Prototype Generator
generate_prototype: '生成原型',
Expand Down
Loading