feat: desktop version

This commit is contained in:
Morten Olsen
2023-06-20 12:17:40 +02:00
parent fec30cc430
commit 6477b56ade
17 changed files with 2337 additions and 65 deletions

View File

@@ -0,0 +1,44 @@
import { Button, CodeEditor, Form } from '@refocus/ui';
import { useCallback, useState } from 'react';
import { Props } from './schema';
type EditorProps = {
value?: Props;
save: (data: Props) => void;
};
const Edit: React.FC<EditorProps> = ({ value, save }) => {
const [code, setCode] = useState(value?.code || '');
const [language, setLanguage] = useState(value?.language || '');
const [name, setName] = useState(value?.name || '');
const handleSave = useCallback(() => {
save({
name,
code,
language,
});
}, [code, save, language, name]);
return (
<Form>
<Form.Field label="Name">
<Form.Input value={name} onChange={(e) => setName(e.target.value)} />
</Form.Field>
<Form.Field label="Language">
<Form.Input
value={language}
onChange={(e) => setLanguage(e.target.value)}
/>
</Form.Field>
<Form.Field label="Code">
<CodeEditor language={language} value={code} setValue={setCode} />
</Form.Field>
<Form.Buttons>
<Button onClick={handleSave} title="Save" />
</Form.Buttons>
</Form>
);
};
export { Edit };

View File

@@ -0,0 +1,17 @@
import { Widget } from '@refocus/sdk';
import { IoLogoMarkdown } from 'react-icons/io';
import { schema } from './schema';
import { Edit } from './edit';
import { View } from './view';
const widget: Widget<typeof schema> = {
name: 'Code',
description: 'Write a code file',
icon: <IoLogoMarkdown />,
id: 'text.code',
schema,
component: View,
edit: Edit,
};
export default widget;

View File

@@ -0,0 +1,12 @@
import { Type, Static } from '@sinclair/typebox';
const schema = Type.Object({
name: Type.String(),
code: Type.String(),
language: Type.String(),
});
type Props = Static<typeof schema>;
export type { Props };
export { schema };

View File

@@ -0,0 +1,33 @@
import { useName } from '@refocus/sdk';
import { Props } from './schema';
import { useEffect } from 'react';
import { CodeEditor, View } from '@refocus/ui';
import { styled } from 'styled-components';
const FullHeight = styled(View)`
height: 100%;
`;
const StyledCodeEditor = styled(CodeEditor)`
height: 100%;
`;
const WidgetView: React.FC<Props> = ({ code, language, name }) => {
const [, setName] = useName();
useEffect(() => {
setName(name);
}, [name, setName]);
return (
<FullHeight $fc>
<StyledCodeEditor
readOnly
language={language}
value={code}
setValue={() => {}}
/>
</FullHeight>
);
};
export { WidgetView as View };

View File

@@ -3,12 +3,14 @@ import { github } from './github';
import { linear } from './linear';
import { slack } from './slack';
import markdown from './markdown';
import code from './code';
const widgets = [
...linear,
...github,
...slack,
markdown,
code,
] satisfies Widget<any>[];
export { widgets };

View File

@@ -25,7 +25,7 @@ const renderElement = (item: Renderable) => {
</a>
);
case 'user':
return <User id={item.user_id} />;
return <User key={item.user_id} id={item.user_id} />;
case 'emoji':
return unicodeToEmoji(item.unicode);
case 'rich_text_list':

View File

@@ -5,7 +5,6 @@ import { render } from '../../block/render';
import { useMemo } from 'react';
import { WidgetProvider, WidgetView } from '@refocus/sdk';
import { styled } from 'styled-components';
import { User } from '../../block/elements/user';
import { UserAvatar } from '../../block/elements/user-avatar';
type Message = Exclude<
@@ -57,7 +56,7 @@ const Message: React.FC<Message> = ({
{reaction.name}
<View $fr>
{reaction.users?.map((user) => (
<UserAvatar id={user} />
<UserAvatar key={user} id={user} />
))}
</View>
</Typography>

View File

@@ -9,7 +9,7 @@ import styled from 'styled-components';
import { Props } from './schema';
import { ConversationsHistoryResponse } from '@slack/web-api';
import { useState } from 'react';
import { Chat, Slack, Typography, View } from '@refocus/ui';
import { Chat, Loader, Slack, Typography, View } from '@refocus/ui';
import { User } from '../block/elements/user';
import { Message } from './message/view';
@@ -42,21 +42,23 @@ const WidgetView = withSlack<Props>(({ conversationId, ts }) => {
const [, setName] = useName();
const addNotification = useAddWidgetNotification();
const [message, setMessage] = useState('');
const { fetch, data } = useSlackQuery(async (client, props: Props) => {
if (props.ts) {
const response = await client.send('conversations.replies', {
channel: props.conversationId,
ts: props.ts,
});
return response.messages! as MessageType[];
} else {
const response = await client.send('conversations.history', {
channel: props.conversationId,
limit: 5,
});
return response.messages! as MessageType[];
}
});
const { fetch, data, loading } = useSlackQuery(
async (client, props: Props) => {
if (props.ts) {
const response = await client.send('conversations.replies', {
channel: props.conversationId,
ts: props.ts,
});
return response.messages!.reverse() as MessageType[];
} else {
const response = await client.send('conversations.history', {
channel: props.conversationId,
limit: 5,
});
return response.messages! as MessageType[];
}
},
);
const info = useSlackQuery(async (client, props: Props) => {
const response = await client.send('conversations.info', {
channel: props.conversationId,
@@ -65,16 +67,7 @@ const WidgetView = withSlack<Props>(({ conversationId, ts }) => {
return response.channel!;
});
const { fetch: post } = useSlackQuery(
async (client, props: PostMessageOptions) => {
client.send('chat.postMessage', {
text: props.message,
channel: conversationId,
});
},
);
useAutoUpdate(
const update = useAutoUpdate(
{
action: async () => {
await info.fetch({ conversationId, ts });
@@ -103,10 +96,22 @@ const WidgetView = withSlack<Props>(({ conversationId, ts }) => {
[conversationId, ts],
);
const { fetch: post } = useSlackQuery(
async (client, props: PostMessageOptions) => {
await client.send('chat.postMessage', {
text: props.message,
channel: conversationId,
});
setMessage('');
await update();
},
);
return (
<Wrapper $p="sm" $fc $gap="sm">
{loading && <Loader />}
<MessageList $gap="md" $fc>
{data?.map((message) => {
{data?.map((message, index) => {
if ('subtype' in message && message.subtype === 'channel_join') {
return (
<Typography key={message.ts}>
@@ -116,7 +121,7 @@ const WidgetView = withSlack<Props>(({ conversationId, ts }) => {
}
return (
<Message
key={message.ts}
key={message.ts || index}
{...message}
conversationId={conversationId}
/>