Skip to content

Commit

Permalink
feat(channels): Add channels package and app examples (#57)
Browse files Browse the repository at this point in the history
* feat(channels): add channels package

* feat(apps): add example channels pages

* fix(channels): flush buffer on connect
  • Loading branch information
rdunk authored Sep 20, 2023
1 parent 2d18114 commit 0145396
Show file tree
Hide file tree
Showing 20 changed files with 922 additions and 5 deletions.
1 change: 1 addition & 0 deletions apps/nextjs/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const nextConfig = {
'@sanity/composer',
'@sanity/overlays',
'@sanity/preview-kit',
'channels',
'apps-common',
],

Expand Down
1 change: 1 addition & 0 deletions apps/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@vercel/stega": "0.1.0",
"apps-common": "workspace:*",
"autoprefixer": "10.4.15",
"channels": "workspace:*",
"eslint": "8.49.0",
"eslint-config-next": "13.4.19",
"next": "13.4.19",
Expand Down
120 changes: 120 additions & 0 deletions apps/nextjs/src/app/channels/child/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use client'

import Link from 'next/link'
import {
studioTheme,
ThemeProvider,
Box,
Button,
Card,
Code,
Flex,
Heading,
Text,
} from '@sanity/ui'
import { type ChannelReturns, type Connection, createChannel } from 'channels'
import { useEffect, useState } from 'react'

function ChannelDisplay({ clientId }: { clientId: string }) {
const [log, setLog] = useState<any[]>([])
const [connections, setConnections] = useState<Connection[]>([])
const [channel, setChannel] = useState<ChannelReturns>()

useEffect(() => {
const channel = createChannel({
id: clientId,
connections: [
{
target: parent,
id: 'parent',
},
],
handle(type, data) {
setLog((l) => [{ ...data, type }, ...l])
},
onConnect: (added) => setConnections((cs) => [...cs, added]),
onDisconnect: (removed) =>
setConnections((cs) =>
cs.filter((c) => c.id === removed.id && c.target === removed.target),
),
})

setChannel(channel)
return () => {
setConnections([])
setLog([])
channel.disconnect()
}
}, [clientId])

const sendMessage = () => {
channel?.send('child/event', { from: clientId })
}

return !channel ? (
<Card flex={1} padding={4}>
<Text>Loading...</Text>
</Card>
) : (
<Card flex={1} overflow={'auto'}>
<Card borderBottom>
<Flex justify={'space-between'} align={'center'} padding={3}>
<Heading as="h1" size={1}>
Channel ({channel?.inFrame ? 'in iFrame' : 'outside iFrame'})
</Heading>

{channel.inFrame ? (
<Flex gap={2} align={'center'}>
<Text size={0}>{connections.length} Connections</Text>
<Button
fontSize={1}
mode="ghost"
padding={3}
text="Clear"
onClick={() => setLog([])}
disabled={!log.length}
/>
<Button
fontSize={1}
mode="default"
padding={3}
text="Send"
tone="positive"
onClick={sendMessage}
/>
</Flex>
) : (
<Link href="/channels/">
<Button
fontSize={1}
mode="ghost"
padding={3}
text="Go to Parent"
/>
</Link>
)}
</Flex>
</Card>
<Box padding={3}>
<Card padding={4} tone="primary" radius={3} overflow={'hidden'}>
<Code language="json" size={1}>
{JSON.stringify(log, null, 2)}
</Code>
</Card>
</Box>
</Card>
)
}

export default function ChildPage() {
return (
<ThemeProvider theme={studioTheme} tone="transparent">
<Flex direction={'column'} className="h-screen">
<ChannelDisplay clientId={'overlays'} />
<Card flex={1} borderTop>
<ChannelDisplay clientId={'store'} />
</Card>
</Flex>
</ThemeProvider>
)
}
123 changes: 123 additions & 0 deletions apps/nextjs/src/app/channels/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
'use client'

import { studioTheme, ThemeProvider } from '@sanity/ui'
import { Button, Box, Card, Code, Flex, Heading, Text } from '@sanity/ui'
import { type Connection, type ChannelReturns, createChannel } from 'channels'
import { forwardRef, useEffect, useRef, useState } from 'react'

const IFrame = forwardRef<HTMLIFrameElement, { src: string }>(function IFrame(
{ src },
ref,
) {
return (
<Card flex={1} borderRight>
<iframe className="h-full w-full bg-white" src={src} ref={ref} />
</Card>
)
})

export default function ParentPage() {
const [log, setLog] = useState<any[]>([])
const [connections, setConnections] = useState<Connection[]>([])
const frameOne = useRef<HTMLIFrameElement>(null)
const frameTwo = useRef<HTMLIFrameElement>(null)
const [channel, setChannel] = useState<ChannelReturns>()

useEffect(() => {
const targetOne = frameOne.current?.contentWindow
const targetTwo = frameTwo.current?.contentWindow
if (!targetOne || !targetTwo) return
const channel = createChannel({
connections: [
{
target: targetOne,
id: 'overlays',
},
{
target: targetOne,
id: 'store',
},
{
target: targetTwo,
id: 'overlays',
},
{
target: targetTwo,
id: 'store',
},
],
id: 'parent',
handle(type, data) {
setLog((l) => [{ ...data, type }, ...l])
},
onConnect: (added) => {
setConnections((cs) => [...cs, added])
},
onDisconnect: (removed) => {
setConnections((cs) =>
cs.filter((c) => c.id === removed.id && c.target === removed.target),
)
},
})
setChannel(channel)

return () => {
channel.disconnect()
setLog([])
}
}, [])

const sendMessage = async () => {
console.log('Send!')
const value = await channel?.send('parent/event', {
foo: 'bar',
})
console.log('Done!', value)
}

return (
<ThemeProvider theme={studioTheme} tone="transparent">
<Flex justify={'space-evenly'} className={'h-screen'}>
<Flex flex={1} justify={'space-evenly'} direction={'column'}>
<IFrame ref={frameOne} src="/channels/child" />
<IFrame ref={frameTwo} src="/channels/child" />
</Flex>
<Card flex={1} overflow="auto">
<Card borderBottom>
<Flex justify={'space-between'} align={'center'} padding={3}>
<Heading as="h1" size={1}>
Composer
</Heading>
<Flex gap={2} align={'center'}>
<Text size={0}>{connections.length} Connections</Text>
<Button
fontSize={1}
mode="ghost"
padding={3}
text="Clear"
onClick={() => setLog([])}
disabled={!log.length}
/>
<Button
fontSize={1}
mode="default"
padding={3}
text="Send"
tone="primary"
onClick={sendMessage}
/>
</Flex>
</Flex>
</Card>
<Box padding={3}>
<Card padding={4} tone="positive" radius={3} overflow={'hidden'}>
<Code language="json" size={1}>
{JSON.stringify(log, null, 2)}
</Code>
</Card>
</Box>
</Card>
</Flex>
</ThemeProvider>
)
}
4 changes: 3 additions & 1 deletion apps/nextjs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@
"@sanity/composer": ["../../packages/composer/src"],
"@sanity/overlays": ["../../packages/overlays/src"],
"@sanity/preview-kit": ["../../packages/preview-kit/src"],
"apps-common": ["../apps-common/src"]
"apps-common": ["../apps-common/src"],
"channels": ["../../packages/channels/src"]
}
},
"include": [
"../../packages/composer/src",
"../../packages/overlays/src",
"../../packages/preview-kit/src",
"../../packages/channels/src",
"../apps-common/src",
"next-env.d.ts",
"**/*.ts",
Expand Down
1 change: 1 addition & 0 deletions apps/nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@sanity/composer": "workspace:*",
"@sanity/overlays": "workspace:*",
"apps-common": "workspace:*",
"channels": "workspace:*",
"rxjs": "^7.8.1",
"sanity": "3.16.5-pink-lizard.35"
},
Expand Down
60 changes: 60 additions & 0 deletions apps/nuxt/pages/channels/child.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<div class="h-full w-full bg-white p-8">
<div v-if="channel" class="flex items-center justify-between">
<h1 class="text-lg font-bold">Channel</h1>
<button
v-if="channel.inFrame"
class="rounded bg-purple-500 p-2 leading-none text-white"
type="button"
@click.prevent="sendMessage"
>
Send
</button>
<RouterLink
v-else
class="rounded bg-purple-500 p-2 leading-none text-white"
to="/channels/parent"
>
Go to Parent
</RouterLink>
</div>
<div v-else>Loading...</div>
<div v-if="channel" class="mt-4 rounded-lg bg-green-200 p-4">
<pre class="text-xs">{{ log }}</pre>
</div>
</div>
</template>

<script lang="ts" setup>
import { type ChannelReturns, createChannel } from 'channels'
const log = ref<any[]>([])
const channel = ref<ChannelReturns | undefined>()
onMounted(() => {
channel.value = createChannel({
id: 'child',
connections: [
{
target: parent,
id: 'parent',
},
],
handle(type, data) {
log.value.unshift({ ...data, type })
},
})
})
onUnmounted(() => {
channel.value?.disconnect()
})
const sendMessage = () => {
channel.value?.send('child/event', {
datetime: new Date().toISOString(),
})
}
</script>

<style lang="postcss"></style>
Loading

1 comment on commit 0145396

@vercel
Copy link

@vercel vercel bot commented on 0145396 Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

visual-editing-svelte – ./apps/svelte

visual-editing-svelte-git-main.sanity.build
visual-editing-svelte.sanity.build

Please sign in to comment.