@dxos/plugin-thread 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PLUGIN.mdl +122 -1
- package/dist/lib/ChannelBackend.mjs +46 -3
- package/dist/lib/ChannelBackend.mjs.map +1 -1
- package/dist/lib/ThreadCapabilities.mjs.map +1 -1
- package/dist/lib/ThreadOperation.mjs +101 -4
- package/dist/lib/ThreadOperation.mjs.map +1 -1
- package/dist/lib/ThreadOperationHandlerSet.mjs +19 -0
- package/dist/lib/ThreadOperationHandlerSet.mjs.map +1 -0
- package/dist/lib/capabilities.browser.mjs +36 -0
- package/dist/lib/capabilities.browser.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +8 -5
- package/dist/lib/capabilities.mjs.map +1 -1
- package/dist/lib/capabilities.node.mjs +6 -1
- package/dist/lib/capabilities.node.mjs.map +1 -1
- package/dist/lib/capabilities.tauri.mjs +36 -0
- package/dist/lib/capabilities.tauri.mjs.map +1 -0
- package/dist/lib/capabilities.workerd.mjs +6 -1
- package/dist/lib/capabilities.workerd.mjs.map +1 -1
- package/dist/lib/chunk-ChannelArticle.mjs +16 -19
- package/dist/lib/chunk-ChannelArticle.mjs.map +1 -1
- package/dist/lib/chunk-ChannelCreatePanel.mjs +5 -4
- package/dist/lib/chunk-ChannelCreatePanel.mjs.map +1 -1
- package/dist/lib/chunk-PLUGIN.mjs +6 -0
- package/dist/lib/chunk-PLUGIN.mjs.map +1 -0
- package/dist/lib/chunk-ThreadArticle.mjs +13 -8
- package/dist/lib/chunk-ThreadArticle.mjs.map +1 -1
- package/dist/lib/chunk-app-graph-builder.mjs +7 -4
- package/dist/lib/chunk-app-graph-builder.mjs.map +1 -1
- package/dist/lib/chunk-append-channel-message.mjs +3 -14
- package/dist/lib/chunk-append-channel-message.mjs.map +1 -1
- package/dist/lib/chunk-channel-backend-feed.mjs +8 -18
- package/dist/lib/chunk-channel-backend-feed.mjs.map +1 -1
- package/dist/lib/chunk-connect-channel.mjs +14 -0
- package/dist/lib/chunk-connect-channel.mjs.map +1 -0
- package/dist/lib/chunk-create-channel.mjs +1 -1
- package/dist/lib/chunk-create-object.mjs +1 -1
- package/dist/lib/chunk-disconnect-channel.mjs +14 -0
- package/dist/lib/chunk-disconnect-channel.mjs.map +1 -0
- package/dist/lib/chunk-get-channel-status.mjs +14 -0
- package/dist/lib/chunk-get-channel-status.mjs.map +1 -0
- package/dist/lib/chunk-open-direct.mjs +15 -0
- package/dist/lib/chunk-open-direct.mjs.map +1 -0
- package/dist/lib/chunk-operation-handler.mjs +2 -2
- package/dist/lib/chunk-operation-handler.mjs.map +1 -1
- package/dist/lib/chunk-react-surface.mjs +2 -2
- package/dist/lib/chunk-react-surface.mjs.map +1 -1
- package/dist/lib/chunk-send-to-channel.mjs +28 -0
- package/dist/lib/chunk-send-to-channel.mjs.map +1 -0
- package/dist/lib/components.mjs +9 -7
- package/dist/lib/components.mjs.map +1 -1
- package/dist/lib/containers.mjs.map +1 -1
- package/dist/lib/hooks.mjs +4 -3
- package/dist/lib/hooks.mjs.map +1 -1
- package/dist/lib/index.mjs +2 -4
- package/dist/lib/meta.mjs +1 -0
- package/dist/lib/meta.mjs.map +1 -1
- package/dist/lib/operations.mjs +2 -9
- package/dist/lib/translations.mjs +1 -1
- package/dist/lib/translations.mjs.map +1 -1
- package/dist/types/src/capabilities/channel-backend-feed.d.ts.map +1 -1
- package/dist/types/src/capabilities/gen/browser.d.ts +10 -0
- package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
- package/dist/types/src/capabilities/gen/node.d.ts.map +1 -1
- package/dist/types/src/capabilities/gen/tauri.d.ts +10 -0
- package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
- package/dist/types/src/capabilities/gen/workerd.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/components/MessageThread/MessageThread.d.ts +2 -2
- package/dist/types/src/components/MessageThread/MessageThread.d.ts.map +1 -1
- package/dist/types/src/components/MessageThread/index.d.ts +1 -1
- package/dist/types/src/components/MessageThread/index.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/ChannelArticle/ChannelArticle.d.ts +1 -1
- package/dist/types/src/containers/ChannelArticle/ChannelArticle.d.ts.map +1 -1
- package/dist/types/src/containers/ChannelArticle/ChannelArticle.stories.d.ts +1 -1
- package/dist/types/src/containers/ChannelArticle/ChannelArticle.stories.d.ts.map +1 -1
- package/dist/types/src/containers/ChannelArticle/index.d.ts +1 -1
- package/dist/types/src/containers/ChannelArticle/index.d.ts.map +1 -1
- package/dist/types/src/containers/ChannelCreatePanel/ChannelCreatePanel.d.ts +1 -1
- package/dist/types/src/containers/ChannelCreatePanel/ChannelCreatePanel.d.ts.map +1 -1
- package/dist/types/src/containers/ChannelCreatePanel/index.d.ts +1 -1
- package/dist/types/src/containers/ChannelCreatePanel/index.d.ts.map +1 -1
- package/dist/types/src/containers/ThreadArticle/ThreadArticle.d.ts +2 -2
- package/dist/types/src/containers/ThreadArticle/ThreadArticle.d.ts.map +1 -1
- package/dist/types/src/containers/ThreadArticle/index.d.ts +1 -1
- package/dist/types/src/containers/ThreadArticle/index.d.ts.map +1 -1
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -2
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useStatus.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -4
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/operations/ThreadOperationHandlerSet.d.ts +3 -0
- package/dist/types/src/operations/ThreadOperationHandlerSet.d.ts.map +1 -0
- package/dist/types/src/operations/append-channel-message.d.ts.map +1 -1
- package/dist/types/src/operations/channel-operations.test.d.ts +2 -0
- package/dist/types/src/operations/channel-operations.test.d.ts.map +1 -0
- package/dist/types/src/operations/connect-channel.d.ts +5 -0
- package/dist/types/src/operations/connect-channel.d.ts.map +1 -0
- package/dist/types/src/operations/disconnect-channel.d.ts +5 -0
- package/dist/types/src/operations/disconnect-channel.d.ts.map +1 -0
- package/dist/types/src/operations/get-channel-status.d.ts +5 -0
- package/dist/types/src/operations/get-channel-status.d.ts.map +1 -0
- package/dist/types/src/operations/index.d.ts +1 -2
- package/dist/types/src/operations/index.d.ts.map +1 -1
- package/dist/types/src/operations/open-direct.d.ts +5 -0
- package/dist/types/src/operations/open-direct.d.ts.map +1 -0
- package/dist/types/src/operations/send-to-channel.d.ts +5 -0
- package/dist/types/src/operations/send-to-channel.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/ChannelBackend.d.ts +79 -1
- package/dist/types/src/types/ChannelBackend.d.ts.map +1 -1
- package/dist/types/src/types/ThreadCapabilities.d.ts +30 -4
- package/dist/types/src/types/ThreadCapabilities.d.ts.map +1 -1
- package/dist/types/src/types/ThreadOperation.d.ts +78 -2
- package/dist/types/src/types/ThreadOperation.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +4 -4
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +3 -0
- package/package.json +53 -40
- package/src/capabilities/app-graph-builder.ts +3 -2
- package/src/capabilities/channel-backend-feed.ts +5 -10
- package/src/capabilities/gen/browser.ts +57 -0
- package/src/capabilities/gen/node.ts +5 -5
- package/src/capabilities/gen/tauri.ts +57 -0
- package/src/capabilities/gen/workerd.ts +5 -5
- package/src/capabilities/index.ts +8 -8
- package/src/capabilities/operation-handler.ts +1 -1
- package/src/capabilities/react-surface.ts +2 -2
- package/src/components/MessageThread/MessageThread.tsx +11 -14
- package/src/components/MessageThread/index.ts +1 -1
- package/src/components/index.ts +1 -1
- package/src/containers/ChannelArticle/ChannelArticle.stories.tsx +4 -4
- package/src/containers/ChannelArticle/ChannelArticle.tsx +17 -20
- package/src/containers/ChannelArticle/index.ts +1 -1
- package/src/containers/ChannelCreatePanel/ChannelCreatePanel.tsx +10 -5
- package/src/containers/ChannelCreatePanel/index.ts +1 -1
- package/src/containers/ThreadArticle/ThreadArticle.stories.tsx +7 -7
- package/src/containers/ThreadArticle/ThreadArticle.tsx +21 -12
- package/src/containers/ThreadArticle/index.ts +1 -1
- package/src/containers/index.ts +3 -3
- package/src/hooks/index.ts +2 -2
- package/src/hooks/useMessages.ts +2 -2
- package/src/hooks/useStatus.ts +4 -1
- package/src/index.ts +2 -4
- package/src/meta.ts +1 -1
- package/src/operations/ThreadOperationHandlerSet.ts +20 -0
- package/src/operations/append-channel-message.ts +2 -6
- package/src/operations/channel-operations.test.ts +185 -0
- package/src/operations/connect-channel.ts +23 -0
- package/src/operations/disconnect-channel.ts +23 -0
- package/src/operations/get-channel-status.ts +23 -0
- package/src/operations/index.ts +1 -9
- package/src/operations/open-direct.ts +32 -0
- package/src/operations/send-to-channel.ts +33 -0
- package/src/plugin.test.ts +3 -3
- package/src/translations.ts +2 -2
- package/src/types/ChannelBackend.test.ts +2 -2
- package/src/types/ChannelBackend.ts +66 -2
- package/src/types/ThreadCapabilities.ts +41 -4
- package/src/types/ThreadOperation.ts +114 -5
- package/src/types/index.ts +4 -4
- package/dist/lib/chunk-paths.mjs +0 -8
- package/dist/lib/chunk-paths.mjs.map +0 -1
- package/dist/lib/operations.mjs.map +0 -1
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { useAtomValue } from '@effect/atom-react/Hooks';
|
|
6
|
-
import * as Atom from 'effect/
|
|
6
|
+
import * as Atom from 'effect/reactivity/Atom';
|
|
7
7
|
import React, { useCallback } from 'react';
|
|
8
8
|
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import * as Hooks from '@dxos/app-framework/Hooks';
|
|
10
|
+
import * as Surface from '@dxos/app-framework/Surface';
|
|
11
|
+
import * as AppSurface from '@dxos/app-toolkit/AppSurface';
|
|
11
12
|
import { Obj } from '@dxos/echo';
|
|
12
13
|
import { useIdentity, useMembers } from '@dxos/halo-react';
|
|
13
14
|
import { log } from '@dxos/log';
|
|
14
15
|
import * as CallsCapabilities from '@dxos/plugin-calls/CallsCapabilities';
|
|
15
16
|
import { getSpace } from '@dxos/react-client/echo';
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
17
|
+
import { ActionToolbar, MenuBuilder, useMenuBuilder } from '@dxos/react-ui-menu';
|
|
18
|
+
import * as Panel from '@dxos/react-ui/Panel';
|
|
18
19
|
import { type Channel } from '@dxos/types';
|
|
19
20
|
|
|
20
21
|
import { MessageThread } from '#components';
|
|
@@ -50,15 +51,15 @@ export const ChannelArticle = ({ role, subject: channel, attendableId, chatOnly
|
|
|
50
51
|
const members = useMembers(space?.id);
|
|
51
52
|
const id = channel ? Obj.getURI(channel) : undefined;
|
|
52
53
|
const activity = useStatus(space, id);
|
|
53
|
-
const { invokePromise } = useOperationInvoker();
|
|
54
|
+
const { invokePromise } = Hooks.useOperationInvoker();
|
|
54
55
|
|
|
55
|
-
const providers = useCapabilities(ThreadCapabilities.ChannelBackend);
|
|
56
|
+
const providers = Hooks.useCapabilities(ThreadCapabilities.ChannelBackend);
|
|
56
57
|
const provider = channel ? ChannelBackend.resolveProvider(providers, channel.backend.kind) : undefined;
|
|
57
58
|
const messages = useMessages(channel);
|
|
58
59
|
const readOnly = channel ? (provider?.readOnly?.(channel) ?? Obj.getMeta(channel).keys.length > 0) : false;
|
|
59
60
|
|
|
60
|
-
const callProvider = useCapabilities(CallsCapabilities.CallTransportProvider)[0];
|
|
61
|
-
const callManager = useCapabilities(CallsCapabilities.Manager)[0];
|
|
61
|
+
const callProvider = Hooks.useCapabilities(CallsCapabilities.CallTransportProvider)[0];
|
|
62
|
+
const callManager = Hooks.useCapabilities(CallsCapabilities.Manager)[0];
|
|
62
63
|
const joined = useAtomValue(callManager?.joinedAtom ?? NOT_JOINED);
|
|
63
64
|
const currentRoomId = useAtomValue(callManager?.roomIdAtom ?? NO_ROOM);
|
|
64
65
|
// `chatOnly` (the in-call chat companion) keeps showing messages so the call lives only in the primary.
|
|
@@ -114,20 +115,16 @@ export const ChannelArticle = ({ role, subject: channel, attendableId, chatOnly
|
|
|
114
115
|
return (
|
|
115
116
|
<Panel.Root role={role}>
|
|
116
117
|
{canStartCall && (
|
|
117
|
-
<
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
<Menu.Items />
|
|
121
|
-
</Menu.Toolbar>
|
|
122
|
-
</Panel.Toolbar>
|
|
123
|
-
</Menu.Root>
|
|
118
|
+
<Panel.Header>
|
|
119
|
+
<ActionToolbar {...menuActions} attendableId={attendableId} />
|
|
120
|
+
</Panel.Header>
|
|
124
121
|
)}
|
|
125
122
|
{showCall ? (
|
|
126
|
-
<Panel.
|
|
123
|
+
<Panel.Body>
|
|
127
124
|
<Surface.Surface type={AppSurface.Article} data={{ subject: { roomId: id }, attendableId }} limit={1} />
|
|
128
|
-
</Panel.
|
|
125
|
+
</Panel.Body>
|
|
129
126
|
) : (
|
|
130
|
-
<Panel.
|
|
127
|
+
<Panel.Body asChild>
|
|
131
128
|
<MessageThread
|
|
132
129
|
id={id}
|
|
133
130
|
classNames='dx-document'
|
|
@@ -138,7 +135,7 @@ export const ChannelArticle = ({ role, subject: channel, attendableId, chatOnly
|
|
|
138
135
|
onSend={handleSend}
|
|
139
136
|
readOnly={readOnly}
|
|
140
137
|
/>
|
|
141
|
-
</Panel.
|
|
138
|
+
</Panel.Body>
|
|
142
139
|
)}
|
|
143
140
|
</Panel.Root>
|
|
144
141
|
);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { useCallback, useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import * as Hooks from '@dxos/app-framework/Hooks';
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
9
|
import type * as SpaceCapabilities from '@dxos/plugin-space/SpaceCapabilities';
|
|
10
10
|
import { Form } from '@dxos/react-ui-form';
|
|
@@ -18,8 +18,12 @@ import { ChannelBackend, ThreadCapabilities } from '#types';
|
|
|
18
18
|
* selected provider's create fields (just a name field when there is a single
|
|
19
19
|
* field-less provider).
|
|
20
20
|
*/
|
|
21
|
-
export const ChannelCreatePanel = ({
|
|
22
|
-
|
|
21
|
+
export const ChannelCreatePanel = ({
|
|
22
|
+
target,
|
|
23
|
+
onCreateObject,
|
|
24
|
+
onCancel,
|
|
25
|
+
}: SpaceCapabilities.CreateObjectCustomPanelProps) => {
|
|
26
|
+
const providers = Hooks.useCapabilities(ThreadCapabilities.ChannelBackend);
|
|
23
27
|
const schema = useMemo(() => ChannelBackend.buildChannelFormSchema(providers), [providers]);
|
|
24
28
|
|
|
25
29
|
const handleSave = useCallback(
|
|
@@ -38,12 +42,13 @@ export const ChannelCreatePanel = ({ target, onCreateObject }: SpaceCapabilities
|
|
|
38
42
|
schema={schema}
|
|
39
43
|
defaultValues={{}}
|
|
40
44
|
onSave={handleSave}
|
|
45
|
+
onCancel={onCancel}
|
|
41
46
|
testId='create-channel-form'
|
|
42
47
|
>
|
|
43
48
|
<Form.Viewport>
|
|
44
49
|
<Form.Content>
|
|
45
|
-
<Form.
|
|
46
|
-
<Form.
|
|
50
|
+
<Form.Fields />
|
|
51
|
+
<Form.Actions />
|
|
47
52
|
</Form.Content>
|
|
48
53
|
</Form.Viewport>
|
|
49
54
|
</Form.Root>
|
|
@@ -7,24 +7,24 @@ import React, { useState } from 'react';
|
|
|
7
7
|
|
|
8
8
|
import * as Capabilities from '@dxos/app-framework/Capabilities';
|
|
9
9
|
import * as Capability from '@dxos/app-framework/Capability';
|
|
10
|
+
import * as Surface from '@dxos/app-framework/Surface';
|
|
10
11
|
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import { corePlugins } from '@dxos/plugin-testing';
|
|
12
|
+
import * as AppSurface from '@dxos/app-toolkit/AppSurface';
|
|
13
|
+
import * as CorePlugins from '@dxos/plugin-testing/CorePlugins';
|
|
14
14
|
import { random } from '@dxos/random';
|
|
15
15
|
import { useClient } from '@dxos/react-client';
|
|
16
16
|
import { type Space } from '@dxos/react-client/echo';
|
|
17
17
|
import { useIdentity } from '@dxos/react-client/halo';
|
|
18
18
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
19
|
-
import { useAsyncEffect } from '@dxos/react-ui';
|
|
20
19
|
import { withMosaic } from '@dxos/react-ui-mosaic/testing';
|
|
21
20
|
import { Thread as ThreadComponent } from '@dxos/react-ui-thread';
|
|
21
|
+
import * as Hooks from '@dxos/react-ui/Hooks';
|
|
22
22
|
import { Loading, withLayout } from '@dxos/react-ui/testing';
|
|
23
23
|
import { Message, Thread } from '@dxos/types';
|
|
24
24
|
|
|
25
25
|
import { translations } from '#translations';
|
|
26
26
|
|
|
27
|
-
import { ThreadArticle } from './ThreadArticle';
|
|
27
|
+
import { ThreadArticle } from './ThreadArticle.tsx';
|
|
28
28
|
|
|
29
29
|
random.seed(1);
|
|
30
30
|
|
|
@@ -34,7 +34,7 @@ const DefaultStory = () => {
|
|
|
34
34
|
const [space, setSpace] = useState<Space>();
|
|
35
35
|
const [thread, setThread] = useState<Thread.Thread | null>();
|
|
36
36
|
|
|
37
|
-
useAsyncEffect(async () => {
|
|
37
|
+
Hooks.useAsyncEffect(async () => {
|
|
38
38
|
if (identity) {
|
|
39
39
|
const space = await client.spaces.create();
|
|
40
40
|
const thread = space.db.add(Thread.make({ status: 'active' }));
|
|
@@ -60,7 +60,7 @@ const meta = {
|
|
|
60
60
|
// TODO(wittjosiah): This shouldn't depend on app framework (use withClientProvider instead).
|
|
61
61
|
// Currently this is required because MessageThread renders an object tile via a Surface.
|
|
62
62
|
withPluginManager({
|
|
63
|
-
plugins:
|
|
63
|
+
plugins: CorePlugins.make(),
|
|
64
64
|
capabilities: [
|
|
65
65
|
Capability.contribute(
|
|
66
66
|
Capabilities.ReactSurface,
|
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { useAtomValue } from '@effect/atom-react/Hooks';
|
|
6
|
+
import * as Atom from 'effect/reactivity/Atom';
|
|
5
7
|
import React, { useMemo } from 'react';
|
|
6
8
|
|
|
7
9
|
import { Obj, Ref } from '@dxos/echo';
|
|
8
10
|
import { useIdentity, useMembers } from '@dxos/halo-react';
|
|
9
11
|
import { getSpace } from '@dxos/react-client/echo';
|
|
10
|
-
import { Panel, type ThemedClassName } from '@dxos/react-ui';
|
|
11
|
-
import { composable, composableProps } from '@dxos/react-ui';
|
|
12
12
|
import { type ThreadContentProps } from '@dxos/react-ui-thread';
|
|
13
|
+
import * as Panel from '@dxos/react-ui/Panel';
|
|
14
|
+
import * as Util from '@dxos/react-ui/Util';
|
|
13
15
|
import { Message, type Thread } from '@dxos/types';
|
|
14
|
-
import { isNonNullable } from '@dxos/util';
|
|
15
16
|
|
|
16
17
|
import { MessageThread } from '#components';
|
|
17
18
|
import { useStatus } from '#hooks';
|
|
18
19
|
|
|
19
|
-
export type ThreadArticleProps = ThemedClassName<
|
|
20
|
+
export type ThreadArticleProps = Util.ThemedClassName<
|
|
20
21
|
{
|
|
21
22
|
thread: Thread.Thread;
|
|
22
23
|
context?: Obj.Unknown;
|
|
@@ -28,7 +29,7 @@ export type ThreadArticleProps = ThemedClassName<
|
|
|
28
29
|
* Renders an AutoMerge {@link Thread} as a chat: appends new messages by pushing
|
|
29
30
|
* onto `thread.messages`. Used for comment threads and the meeting in-call chat.
|
|
30
31
|
*/
|
|
31
|
-
export const ThreadArticle = composable<HTMLDivElement, ThreadArticleProps>(
|
|
32
|
+
export const ThreadArticle = Util.composable<HTMLDivElement, ThreadArticleProps>(
|
|
32
33
|
({ thread, context, autoFocus, current, ...props }, forwardedRef) => {
|
|
33
34
|
// Members and presence are space-scoped; a thread outside a space has nothing to resolve against.
|
|
34
35
|
const space = getSpace(thread);
|
|
@@ -37,9 +38,17 @@ export const ThreadArticle = composable<HTMLDivElement, ThreadArticleProps>(
|
|
|
37
38
|
const members = useMembers(space?.id);
|
|
38
39
|
const activity = useStatus(space, id);
|
|
39
40
|
|
|
40
|
-
const messages =
|
|
41
|
-
(
|
|
42
|
-
|
|
41
|
+
const messages = useAtomValue(
|
|
42
|
+
useMemo(
|
|
43
|
+
() =>
|
|
44
|
+
Atom.make((get) =>
|
|
45
|
+
(get(Obj.atomProperty(thread, 'messages')) ?? []).flatMap((message) => {
|
|
46
|
+
const value = get(message.atom);
|
|
47
|
+
return value ? [value] : [];
|
|
48
|
+
}),
|
|
49
|
+
),
|
|
50
|
+
[thread],
|
|
51
|
+
),
|
|
43
52
|
);
|
|
44
53
|
|
|
45
54
|
const handleSend = (text: string) => {
|
|
@@ -64,10 +73,10 @@ export const ThreadArticle = composable<HTMLDivElement, ThreadArticleProps>(
|
|
|
64
73
|
|
|
65
74
|
return (
|
|
66
75
|
<Panel.Root>
|
|
67
|
-
<Panel.
|
|
68
|
-
<Panel.
|
|
76
|
+
<Panel.Header></Panel.Header>
|
|
77
|
+
<Panel.Body asChild>
|
|
69
78
|
<MessageThread
|
|
70
|
-
{...composableProps(props)}
|
|
79
|
+
{...Util.composableProps(props)}
|
|
71
80
|
id={id}
|
|
72
81
|
identity={identity}
|
|
73
82
|
members={members}
|
|
@@ -78,7 +87,7 @@ export const ThreadArticle = composable<HTMLDivElement, ThreadArticleProps>(
|
|
|
78
87
|
current={current}
|
|
79
88
|
ref={forwardedRef}
|
|
80
89
|
/>
|
|
81
|
-
</Panel.
|
|
90
|
+
</Panel.Body>
|
|
82
91
|
</Panel.Root>
|
|
83
92
|
);
|
|
84
93
|
},
|
package/src/containers/index.ts
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import { type ComponentType, lazy } from 'react';
|
|
6
6
|
|
|
7
|
-
export const ChannelCreatePanel: ComponentType<any> = lazy(() => import('./ChannelCreatePanel'));
|
|
8
|
-
export const ChannelArticle: ComponentType<any> = lazy(() => import('./ChannelArticle'));
|
|
9
|
-
export const ThreadArticle: ComponentType<any> = lazy(() => import('./ThreadArticle'));
|
|
7
|
+
export const ChannelCreatePanel: ComponentType<any> = lazy(() => import('./ChannelCreatePanel/index.ts'));
|
|
8
|
+
export const ChannelArticle: ComponentType<any> = lazy(() => import('./ChannelArticle/index.ts'));
|
|
9
|
+
export const ThreadArticle: ComponentType<any> = lazy(() => import('./ThreadArticle/index.ts'));
|
package/src/hooks/index.ts
CHANGED
package/src/hooks/useMessages.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import * as Hooks from '@dxos/app-framework/Hooks';
|
|
8
8
|
import { type Channel, type Message } from '@dxos/types';
|
|
9
9
|
|
|
10
10
|
import { ChannelBackend, ThreadCapabilities } from '#types';
|
|
@@ -13,7 +13,7 @@ const EMPTY: readonly Message.Message[] = [];
|
|
|
13
13
|
|
|
14
14
|
/** Reactive message list for a channel, resolved through its backend provider. */
|
|
15
15
|
export const useMessages = (channel: Channel.Channel | undefined): readonly Message.Message[] => {
|
|
16
|
-
const providers = useCapabilities(ThreadCapabilities.ChannelBackend);
|
|
16
|
+
const providers = Hooks.useCapabilities(ThreadCapabilities.ChannelBackend);
|
|
17
17
|
const provider = useMemo(
|
|
18
18
|
() => (channel ? ChannelBackend.resolveProvider(providers, channel.backend.kind) : undefined),
|
|
19
19
|
[providers, channel],
|
package/src/hooks/useStatus.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useEffect, useState } from 'react';
|
|
6
6
|
|
|
7
|
+
import { unpackJson } from '@dxos/protocols/buf';
|
|
7
8
|
import { type Space } from '@dxos/react-client/echo';
|
|
8
9
|
|
|
9
10
|
// TODO(burdon): Generalize and factor out.
|
|
@@ -15,7 +16,9 @@ export const useStatus = (space?: Space, channel?: string) => {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const unsubscribe = space.listen(`status/${channel}`, (status) => {
|
|
18
|
-
|
|
19
|
+
// The status payload travels as a `google.protobuf.Struct` packed in an `Any`, so it arrives
|
|
20
|
+
// as opaque JSON that has to be narrowed rather than destructured as typed.
|
|
21
|
+
const { event } = unpackJson(status.payload) ?? {};
|
|
19
22
|
setProcessing(event === 'processing');
|
|
20
23
|
});
|
|
21
24
|
|
package/src/index.ts
CHANGED
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
// TODO(wittjosiah): Hooks should not be exported from the plugin package at all.
|
|
6
6
|
// Either refactor callers to not need them or factor them out to a shared package.
|
|
7
7
|
|
|
8
|
-
export * as ThreadPlugin from './ThreadPlugin';
|
|
9
|
-
export * from '#
|
|
10
|
-
export * from '#meta';
|
|
11
|
-
export * from './paths';
|
|
8
|
+
export * as ThreadPlugin from './ThreadPlugin.ts';
|
|
9
|
+
export * from '#operations';
|
|
12
10
|
export * from '#types';
|
package/src/meta.ts
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
// @import-as-namespace
|
|
6
|
+
|
|
7
|
+
import * as Operation from '@dxos/compute/Operation';
|
|
8
|
+
import * as OperationHandlerSet from '@dxos/compute/OperationHandlerSet';
|
|
9
|
+
|
|
10
|
+
import { ThreadOperation } from '#types';
|
|
11
|
+
|
|
12
|
+
export const handlers = OperationHandlerSet.lazy([
|
|
13
|
+
ThreadOperation.AppendChannelMessage.pipe(Operation.lazyHandler(() => import('./append-channel-message.ts'))),
|
|
14
|
+
ThreadOperation.CreateChannel.pipe(Operation.lazyHandler(() => import('./create-channel.ts'))),
|
|
15
|
+
ThreadOperation.SendToChannel.pipe(Operation.lazyHandler(() => import('./send-to-channel.ts'))),
|
|
16
|
+
ThreadOperation.OpenDirect.pipe(Operation.lazyHandler(() => import('./open-direct.ts'))),
|
|
17
|
+
ThreadOperation.ConnectChannel.pipe(Operation.lazyHandler(() => import('./connect-channel.ts'))),
|
|
18
|
+
ThreadOperation.DisconnectChannel.pipe(Operation.lazyHandler(() => import('./disconnect-channel.ts'))),
|
|
19
|
+
ThreadOperation.GetChannelStatus.pipe(Operation.lazyHandler(() => import('./get-channel-status.ts'))),
|
|
20
|
+
]);
|
|
@@ -4,20 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
import * as Effect from 'effect/Effect';
|
|
6
6
|
|
|
7
|
-
import * as Capability from '@dxos/app-framework/Capability';
|
|
8
7
|
import * as Operation from '@dxos/compute/Operation';
|
|
9
|
-
import { invariant } from '@dxos/invariant';
|
|
10
8
|
import { Message } from '@dxos/types';
|
|
11
9
|
|
|
12
|
-
import { ChannelBackend,
|
|
10
|
+
import { ChannelBackend, ThreadOperation } from '#types';
|
|
13
11
|
|
|
14
12
|
const handler: Operation.WithHandler<typeof ThreadOperation.AppendChannelMessage> =
|
|
15
13
|
ThreadOperation.AppendChannelMessage.pipe(
|
|
16
14
|
Operation.withHandler(
|
|
17
15
|
Effect.fnUntraced(function* ({ channel, sender, text }) {
|
|
18
|
-
const
|
|
19
|
-
const provider = ChannelBackend.resolveProvider(providers, channel.backend.kind);
|
|
20
|
-
invariant(provider, `No channel backend for kind: ${channel.backend.kind}`);
|
|
16
|
+
const provider = yield* ChannelBackend.getProvider(channel);
|
|
21
17
|
|
|
22
18
|
const message = Message.make({
|
|
23
19
|
sender,
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from '@effect/vitest';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import * as Exit from 'effect/Exit';
|
|
8
|
+
import * as Registry from 'effect/reactivity/AtomRegistry';
|
|
9
|
+
import * as Schema from 'effect/Schema';
|
|
10
|
+
|
|
11
|
+
import * as Capability from '@dxos/app-framework/Capability';
|
|
12
|
+
import * as CapabilityManager from '@dxos/app-framework/CapabilityManager';
|
|
13
|
+
import { Database, Feed, Filter, Ref } from '@dxos/echo';
|
|
14
|
+
import { TestDatabaseLayer } from '@dxos/echo-client/testing';
|
|
15
|
+
import { BaseError } from '@dxos/errors';
|
|
16
|
+
import { Channel, Message, Person } from '@dxos/types';
|
|
17
|
+
|
|
18
|
+
import { ChannelBackend, ThreadCapabilities, type ThreadOperation } from '#types';
|
|
19
|
+
|
|
20
|
+
import { feedChannelBackend } from '../capabilities/channel-backend-feed.ts';
|
|
21
|
+
import connectChannel from './connect-channel.ts';
|
|
22
|
+
import disconnectChannel from './disconnect-channel.ts';
|
|
23
|
+
import getChannelStatus from './get-channel-status.ts';
|
|
24
|
+
import openDirect from './open-direct.ts';
|
|
25
|
+
import sendToChannel from './send-to-channel.ts';
|
|
26
|
+
|
|
27
|
+
const FAKE_KIND = 'org.dxos.channel.backend.fake';
|
|
28
|
+
|
|
29
|
+
class RefusedError extends BaseError.extend('RefusedError', 'The backend refused the post.') {}
|
|
30
|
+
|
|
31
|
+
/** A backend with every optional member, recording what it was asked to do. */
|
|
32
|
+
const makeFakeBackend = () => {
|
|
33
|
+
const sent: { thread?: string; text: string }[] = [];
|
|
34
|
+
let running = false;
|
|
35
|
+
const status = (): ThreadOperation.ConnectionStatus => ({ running, state: running ? 'ready' : 'idle' });
|
|
36
|
+
const record = (message: Message.Message, thread?: string) =>
|
|
37
|
+
Effect.sync(() => {
|
|
38
|
+
const text = Message.extractText(message);
|
|
39
|
+
if (text === 'refuse') {
|
|
40
|
+
return Effect.fail(new RefusedError());
|
|
41
|
+
}
|
|
42
|
+
sent.push({ thread, text });
|
|
43
|
+
return Effect.succeed({ messageIds: [`m${sent.length}`], properties: { fake: { thread } } });
|
|
44
|
+
}).pipe(Effect.flatten);
|
|
45
|
+
|
|
46
|
+
const provider: ThreadCapabilities.ChannelBackendProvider = {
|
|
47
|
+
kind: FAKE_KIND,
|
|
48
|
+
label: 'Fake',
|
|
49
|
+
createFields: Schema.Struct({}),
|
|
50
|
+
makeConfig: () => Feed.make(),
|
|
51
|
+
subscribe: () => () => {},
|
|
52
|
+
send: (_channel, message) => record(message),
|
|
53
|
+
openDirect: (_channel, person) =>
|
|
54
|
+
Effect.succeed(person.identities?.find((identity) => identity.label === 'fake')?.value),
|
|
55
|
+
threads: { send: (_channel, thread, message) => record(message, thread) },
|
|
56
|
+
connection: {
|
|
57
|
+
start: () => Effect.sync(() => ((running = true), status())),
|
|
58
|
+
stop: () => Effect.sync(() => ((running = false), status())),
|
|
59
|
+
status: () => Effect.sync(status),
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
return { provider, sent };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Capability manager holding the given channel backends, as plugin-thread and backend plugins contribute them. */
|
|
66
|
+
const capabilities = (...providers: ThreadCapabilities.ChannelBackendProvider[]) => {
|
|
67
|
+
const manager = CapabilityManager.make({ registry: Registry.make() });
|
|
68
|
+
for (const provider of providers) {
|
|
69
|
+
manager.contribute({
|
|
70
|
+
interface: ThreadCapabilities.ChannelBackend,
|
|
71
|
+
implementation: provider,
|
|
72
|
+
module: provider.kind,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return manager;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** The printed cause of a failed exit, or undefined when it succeeded. */
|
|
79
|
+
const failureOf = <A, E>(exit: Exit.Exit<A, E>): string | undefined =>
|
|
80
|
+
Exit.isFailure(exit) ? String(exit.cause) : undefined;
|
|
81
|
+
|
|
82
|
+
const testLayer = () => TestDatabaseLayer({ types: [Channel.Channel, Feed.Feed, Message.Message, Person.Person] });
|
|
83
|
+
|
|
84
|
+
describe('channel operations', () => {
|
|
85
|
+
it.effect('sendToChannel appends to a feed-backed channel', () =>
|
|
86
|
+
Effect.gen(function* () {
|
|
87
|
+
const channel = yield* Database.add(Channel.make({ name: 'general' }));
|
|
88
|
+
yield* Database.flush();
|
|
89
|
+
|
|
90
|
+
const result = yield* sendToChannel
|
|
91
|
+
.handler({ channel: Ref.make(channel), text: 'hello' })
|
|
92
|
+
.pipe(Effect.provideService(Capability.Service, capabilities(feedChannelBackend)));
|
|
93
|
+
expect(result).toEqual({ delivered: true });
|
|
94
|
+
|
|
95
|
+
const feed = Channel.getFeed(channel);
|
|
96
|
+
expect(feed).toBeDefined();
|
|
97
|
+
if (feed) {
|
|
98
|
+
const messages = yield* Feed.query(feed, Filter.type(Message.Message)).run;
|
|
99
|
+
expect(messages.map(Message.extractText)).toEqual(['hello']);
|
|
100
|
+
}
|
|
101
|
+
}).pipe(Effect.provide(testLayer())),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
it.effect('sendToChannel with a thread fails on a backend without threads', () =>
|
|
105
|
+
Effect.gen(function* () {
|
|
106
|
+
const channel = yield* Database.add(Channel.make({ name: 'general' }));
|
|
107
|
+
const exit = yield* sendToChannel
|
|
108
|
+
.handler({ channel: Ref.make(channel), thread: 't1', text: 'hello' })
|
|
109
|
+
.pipe(Effect.provideService(Capability.Service, capabilities(feedChannelBackend)), Effect.exit);
|
|
110
|
+
expect(Exit.isFailure(exit)).toBe(true);
|
|
111
|
+
if (Exit.isFailure(exit)) {
|
|
112
|
+
expect(String(exit.cause)).toContain('does not support threads');
|
|
113
|
+
}
|
|
114
|
+
}).pipe(Effect.provide(testLayer())),
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
it.effect('fails with a typed error when no backend handles the channel', () =>
|
|
118
|
+
Effect.gen(function* () {
|
|
119
|
+
const channel = yield* Database.add(Channel.make({ backend: { kind: FAKE_KIND, config: Feed.make() } }));
|
|
120
|
+
const exit = yield* getChannelStatus
|
|
121
|
+
.handler({ channel: Ref.make(channel) })
|
|
122
|
+
.pipe(Effect.provideService(Capability.Service, capabilities(feedChannelBackend)), Effect.exit);
|
|
123
|
+
expect(Exit.isFailure(exit)).toBe(true);
|
|
124
|
+
if (Exit.isFailure(exit)) {
|
|
125
|
+
expect(String(exit.cause)).toContain(ChannelBackend.ChannelBackendNotFoundError.name);
|
|
126
|
+
}
|
|
127
|
+
}).pipe(Effect.provide(testLayer())),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
it.effect('dispatches threads, direct conversations and the connection to the backend', () =>
|
|
131
|
+
Effect.gen(function* () {
|
|
132
|
+
const { provider, sent } = makeFakeBackend();
|
|
133
|
+
const channel = yield* Database.add(Channel.make({ backend: { kind: FAKE_KIND, config: Feed.make() } }));
|
|
134
|
+
const dima = yield* Database.add(Person.make({ fullName: 'Dima', identities: [{ label: 'fake', value: 'd1' }] }));
|
|
135
|
+
const nobody = yield* Database.add(Person.make({ fullName: 'Nobody' }));
|
|
136
|
+
const withBackend = Effect.provideService(Capability.Service, capabilities(feedChannelBackend, provider));
|
|
137
|
+
|
|
138
|
+
const direct = yield* openDirect
|
|
139
|
+
.handler({ channel: Ref.make(channel), person: Ref.make(dima) })
|
|
140
|
+
.pipe(withBackend);
|
|
141
|
+
expect(direct).toEqual({ thread: 'd1' });
|
|
142
|
+
const unreachable = yield* openDirect
|
|
143
|
+
.handler({ channel: Ref.make(channel), person: Ref.make(nobody) })
|
|
144
|
+
.pipe(withBackend);
|
|
145
|
+
expect(unreachable.thread).toBeUndefined();
|
|
146
|
+
expect(unreachable.reason).toContain('Fake');
|
|
147
|
+
|
|
148
|
+
const posted = yield* sendToChannel
|
|
149
|
+
.handler({ channel: Ref.make(channel), thread: 'd1', text: 'hi' })
|
|
150
|
+
.pipe(withBackend);
|
|
151
|
+
expect(posted).toEqual({ delivered: true, messageIds: ['m1'], properties: { fake: { thread: 'd1' } } });
|
|
152
|
+
const refused = yield* sendToChannel.handler({ channel: Ref.make(channel), text: 'refuse' }).pipe(withBackend);
|
|
153
|
+
expect(refused).toEqual({ delivered: false, reason: 'The backend refused the post.' });
|
|
154
|
+
expect(sent).toEqual([{ thread: 'd1', text: 'hi' }]);
|
|
155
|
+
|
|
156
|
+
expect((yield* getChannelStatus.handler({ channel: Ref.make(channel) }).pipe(withBackend)).status.running).toBe(
|
|
157
|
+
false,
|
|
158
|
+
);
|
|
159
|
+
expect((yield* connectChannel.handler({ channel: Ref.make(channel) }).pipe(withBackend)).status).toEqual({
|
|
160
|
+
running: true,
|
|
161
|
+
state: 'ready',
|
|
162
|
+
});
|
|
163
|
+
expect((yield* disconnectChannel.handler({ channel: Ref.make(channel) }).pipe(withBackend)).status.running).toBe(
|
|
164
|
+
false,
|
|
165
|
+
);
|
|
166
|
+
}).pipe(Effect.provide(testLayer())),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
it.effect('openDirect and the connection fail with a typed error on a backend without them', () =>
|
|
170
|
+
Effect.gen(function* () {
|
|
171
|
+
const channel = yield* Database.add(Channel.make({ name: 'general' }));
|
|
172
|
+
const person = yield* Database.add(Person.make({ fullName: 'Dima' }));
|
|
173
|
+
const withFeed = Effect.provideService(Capability.Service, capabilities(feedChannelBackend));
|
|
174
|
+
|
|
175
|
+
const direct = yield* openDirect
|
|
176
|
+
.handler({ channel: Ref.make(channel), person: Ref.make(person) })
|
|
177
|
+
.pipe(withFeed, Effect.exit);
|
|
178
|
+
const connect = yield* connectChannel.handler({ channel: Ref.make(channel) }).pipe(withFeed, Effect.exit);
|
|
179
|
+
expect([failureOf(direct), failureOf(connect)]).toEqual([
|
|
180
|
+
expect.stringContaining(ChannelBackend.ChannelBackendUnsupportedError.name),
|
|
181
|
+
expect.stringContaining(ChannelBackend.ChannelBackendUnsupportedError.name),
|
|
182
|
+
]);
|
|
183
|
+
}).pipe(Effect.provide(testLayer())),
|
|
184
|
+
);
|
|
185
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import * as Operation from '@dxos/compute/Operation';
|
|
8
|
+
import { Database } from '@dxos/echo';
|
|
9
|
+
|
|
10
|
+
import { ChannelBackend, ThreadOperation } from '#types';
|
|
11
|
+
|
|
12
|
+
const handler: Operation.WithHandler<typeof ThreadOperation.ConnectChannel> = ThreadOperation.ConnectChannel.pipe(
|
|
13
|
+
Operation.withHandler(
|
|
14
|
+
Effect.fnUntraced(function* ({ channel: channelRef }) {
|
|
15
|
+
const channel = yield* Database.load(channelRef);
|
|
16
|
+
const provider = yield* ChannelBackend.getProvider(channel);
|
|
17
|
+
const connection = yield* ChannelBackend.requireMember(provider, 'connection');
|
|
18
|
+
return { status: yield* connection.start(channel) };
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export default handler;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import * as Operation from '@dxos/compute/Operation';
|
|
8
|
+
import { Database } from '@dxos/echo';
|
|
9
|
+
|
|
10
|
+
import { ChannelBackend, ThreadOperation } from '#types';
|
|
11
|
+
|
|
12
|
+
const handler: Operation.WithHandler<typeof ThreadOperation.DisconnectChannel> = ThreadOperation.DisconnectChannel.pipe(
|
|
13
|
+
Operation.withHandler(
|
|
14
|
+
Effect.fnUntraced(function* ({ channel: channelRef }) {
|
|
15
|
+
const channel = yield* Database.load(channelRef);
|
|
16
|
+
const provider = yield* ChannelBackend.getProvider(channel);
|
|
17
|
+
const connection = yield* ChannelBackend.requireMember(provider, 'connection');
|
|
18
|
+
return { status: yield* connection.stop(channel) };
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export default handler;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import * as Operation from '@dxos/compute/Operation';
|
|
8
|
+
import { Database } from '@dxos/echo';
|
|
9
|
+
|
|
10
|
+
import { ChannelBackend, ThreadOperation } from '#types';
|
|
11
|
+
|
|
12
|
+
const handler: Operation.WithHandler<typeof ThreadOperation.GetChannelStatus> = ThreadOperation.GetChannelStatus.pipe(
|
|
13
|
+
Operation.withHandler(
|
|
14
|
+
Effect.fnUntraced(function* ({ channel: channelRef }) {
|
|
15
|
+
const channel = yield* Database.load(channelRef);
|
|
16
|
+
const provider = yield* ChannelBackend.getProvider(channel);
|
|
17
|
+
const connection = yield* ChannelBackend.requireMember(provider, 'connection');
|
|
18
|
+
return { status: yield* connection.status(channel) };
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export default handler;
|