create-fluxstack 1.20.0 → 1.21.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/LLMD/resources/live-components.md +103 -57
- package/LLMD/resources/live-rooms.md +187 -88
- package/README.md +27 -25
- package/app/client/.live-stubs/LiveCounter.js +4 -4
- package/app/client/src/App.tsx +11 -12
- package/app/client/src/components/AppLayout.tsx +290 -252
- package/app/client/src/components/BackButton.tsx +16 -13
- package/app/client/src/components/DemoPage.tsx +135 -22
- package/app/client/src/index.css +21 -11
- package/app/client/src/live/AuthDemo.tsx +270 -333
- package/app/client/src/live/CounterDemo.tsx +151 -206
- package/app/client/src/live/FormDemo.tsx +140 -119
- package/app/client/src/live/PingPongDemo.tsx +180 -202
- package/app/client/src/live/RoomChatDemo.tsx +397 -374
- package/app/client/src/pages/HomePage.tsx +170 -104
- package/app/server/live/LiveCounter.ts +71 -68
- package/app/server/live/LiveSharedCounter.ts +18 -12
- package/app/server/live/auto-generated-components.ts +1 -3
- package/app/server/live/rooms/CounterRoom.ts +15 -10
- package/core/client/index.ts +0 -3
- package/core/client/state/createStore.ts +88 -88
- package/core/client/state/index.ts +5 -5
- package/core/server/live/auto-generated-components.ts +1 -3
- package/core/utils/version.ts +1 -1
- package/package.json +1 -1
- package/tsconfig.json +7 -6
- package/app/client/src/components/LiveUploadWidget.tsx +0 -200
- package/app/client/src/live/UploadDemo.tsx +0 -21
- package/app/server/live/LiveUpload.ts +0 -96
- package/core/client/hooks/useLiveUpload.ts +0 -70
|
@@ -1,104 +1,170 @@
|
|
|
1
|
-
import FluxStack from '@client/src/assets/fluxstack.svg'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
1
|
+
import FluxStack from '@client/src/assets/fluxstack.svg'
|
|
2
|
+
import { Link } from 'react-router'
|
|
3
|
+
import {
|
|
4
|
+
FaArrowRight,
|
|
5
|
+
FaBolt,
|
|
6
|
+
FaCodeBranch,
|
|
7
|
+
FaLayerGroup,
|
|
8
|
+
FaServer,
|
|
9
|
+
FaShieldAlt,
|
|
10
|
+
FaTerminal,
|
|
11
|
+
} from 'react-icons/fa'
|
|
12
|
+
|
|
13
|
+
export function HomePage({ apiStatus }: { apiStatus: 'checking' | 'online' | 'offline' }) {
|
|
14
|
+
const statusCopy = {
|
|
15
|
+
checking: 'Checking API',
|
|
16
|
+
online: 'API Online',
|
|
17
|
+
offline: 'API Offline',
|
|
18
|
+
}[apiStatus]
|
|
19
|
+
|
|
20
|
+
const statusClass = {
|
|
21
|
+
checking: 'border-amber-400/25 bg-amber-400/10 text-amber-200',
|
|
22
|
+
online: 'border-emerald-400/25 bg-emerald-400/10 text-emerald-200',
|
|
23
|
+
offline: 'border-red-400/25 bg-red-400/10 text-red-200',
|
|
24
|
+
}[apiStatus]
|
|
25
|
+
|
|
26
|
+
const statusDotClass = {
|
|
27
|
+
checking: 'bg-amber-300',
|
|
28
|
+
online: 'bg-emerald-300',
|
|
29
|
+
offline: 'bg-red-300',
|
|
30
|
+
}[apiStatus]
|
|
31
|
+
|
|
32
|
+
const features = [
|
|
33
|
+
{
|
|
34
|
+
icon: FaBolt,
|
|
35
|
+
title: 'Runtime rapido',
|
|
36
|
+
copy: 'Bun no centro do fluxo de dev e build, com feedback curto para projetos full stack.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
icon: FaShieldAlt,
|
|
40
|
+
title: 'Type-safe',
|
|
41
|
+
copy: 'Contratos compartilhados entre Elysia, Eden Treaty e React sem duplicar modelos.',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
icon: FaLayerGroup,
|
|
45
|
+
title: 'Live Components',
|
|
46
|
+
copy: 'Estado no servidor com UI reativa, salas em tempo real e componentes declarativos.',
|
|
47
|
+
},
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
const stackItems = ['Bun', 'Elysia', 'React', 'TypeScript']
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className="relative min-h-[calc(100vh-72px)] overflow-hidden px-4 py-10 sm:px-6 lg:px-8">
|
|
54
|
+
<div className="absolute inset-0 app-grid-bg opacity-70" />
|
|
55
|
+
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white/25 to-transparent" />
|
|
56
|
+
|
|
57
|
+
<div className="relative z-10 mx-auto flex w-full max-w-7xl flex-col gap-10 lg:min-h-[calc(100vh-144px)] lg:justify-center">
|
|
58
|
+
<section className="grid items-center gap-8 lg:grid-cols-[1.04fr_0.96fr]">
|
|
59
|
+
<div className="max-w-3xl">
|
|
60
|
+
<div className="mb-6 inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs text-gray-300 shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]">
|
|
61
|
+
<span className={`h-1.5 w-1.5 rounded-full ${statusDotClass}`} />
|
|
62
|
+
<span className={`rounded-full border px-2 py-0.5 ${statusClass}`}>{statusCopy}</span>
|
|
63
|
+
<span className="hidden sm:inline">Full-stack starter for production apps</span>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div className="mb-5 flex items-center gap-4">
|
|
67
|
+
<div className="relative flex h-16 w-16 items-center justify-center rounded-lg border border-white/10 bg-white/[0.04] shadow-2xl shadow-black/30">
|
|
68
|
+
<img src={FluxStack} alt="FluxStack" className="h-11 w-11 glow-theme" />
|
|
69
|
+
</div>
|
|
70
|
+
<div className="hidden h-px flex-1 bg-gradient-to-r from-white/20 to-transparent sm:block" />
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<h1 className="max-w-4xl text-left text-5xl font-semibold tracking-tight text-white sm:text-6xl lg:text-7xl">
|
|
74
|
+
FluxStack
|
|
75
|
+
<span className="block bg-theme-gradient bg-clip-text text-transparent">
|
|
76
|
+
TypeScript full stack.
|
|
77
|
+
</span>
|
|
78
|
+
</h1>
|
|
79
|
+
|
|
80
|
+
<p className="mt-5 max-w-2xl text-left text-base leading-8 text-gray-400 sm:text-lg">
|
|
81
|
+
Um framework direto ao ponto para construir APIs Elysia, interfaces React,
|
|
82
|
+
componentes em tempo real e contratos type-safe em uma unica base.
|
|
83
|
+
</p>
|
|
84
|
+
|
|
85
|
+
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
|
|
86
|
+
<Link
|
|
87
|
+
to="/counter"
|
|
88
|
+
className="inline-flex h-11 items-center justify-center gap-2 rounded-lg bg-white px-5 text-sm font-semibold text-black transition hover:bg-gray-200"
|
|
89
|
+
>
|
|
90
|
+
Ver demos
|
|
91
|
+
<FaArrowRight className="h-3.5 w-3.5" />
|
|
92
|
+
</Link>
|
|
93
|
+
<a
|
|
94
|
+
href="/swagger"
|
|
95
|
+
target="_blank"
|
|
96
|
+
rel="noopener noreferrer"
|
|
97
|
+
className="inline-flex h-11 items-center justify-center gap-2 rounded-lg border border-white/10 bg-white/[0.03] px-5 text-sm font-semibold text-white transition hover:border-white/20 hover:bg-white/[0.06]"
|
|
98
|
+
>
|
|
99
|
+
<FaServer className="h-3.5 w-3.5 text-theme" />
|
|
100
|
+
API docs
|
|
101
|
+
</a>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div className="mt-8 flex flex-wrap gap-2">
|
|
105
|
+
{stackItems.map(item => (
|
|
106
|
+
<span key={item} className="rounded-full border border-white/10 bg-black/20 px-3 py-1 text-xs font-medium text-gray-300">
|
|
107
|
+
{item}
|
|
108
|
+
</span>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div className="relative">
|
|
114
|
+
<div className="rounded-lg border border-white/10 bg-[#050508]/80 shadow-2xl shadow-black/40 backdrop-blur">
|
|
115
|
+
<div className="flex items-center justify-between border-b border-white/10 px-4 py-3">
|
|
116
|
+
<div className="flex items-center gap-2">
|
|
117
|
+
<span className="h-2.5 w-2.5 rounded-full bg-red-400/80" />
|
|
118
|
+
<span className="h-2.5 w-2.5 rounded-full bg-amber-300/80" />
|
|
119
|
+
<span className="h-2.5 w-2.5 rounded-full bg-emerald-300/80" />
|
|
120
|
+
</div>
|
|
121
|
+
<span className="font-mono text-xs text-gray-500">fluxstack.config.ts</span>
|
|
122
|
+
</div>
|
|
123
|
+
<div className="space-y-5 p-5 sm:p-6">
|
|
124
|
+
<div className="rounded-lg border border-white/10 bg-white/[0.03] p-4">
|
|
125
|
+
<div className="mb-3 flex items-center gap-2 text-sm font-semibold text-white">
|
|
126
|
+
<FaTerminal className="text-theme" />
|
|
127
|
+
Ship from one command
|
|
128
|
+
</div>
|
|
129
|
+
<pre className="overflow-x-auto rounded-md bg-black/50 p-4 text-left text-xs leading-6 text-gray-300">
|
|
130
|
+
<code>{`bunx create-fluxstack my-app
|
|
131
|
+
cd my-app
|
|
132
|
+
bun dev`}</code>
|
|
133
|
+
</pre>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
137
|
+
<div className="rounded-lg border border-white/10 bg-white/[0.03] p-4">
|
|
138
|
+
<FaCodeBranch className="mb-4 text-theme-secondary" />
|
|
139
|
+
<p className="text-sm font-semibold text-white">Typed routes</p>
|
|
140
|
+
<p className="mt-1 text-xs leading-5 text-gray-500">Client e server compartilham o contrato.</p>
|
|
141
|
+
</div>
|
|
142
|
+
<div className="rounded-lg border border-white/10 bg-white/[0.03] p-4">
|
|
143
|
+
<FaLayerGroup className="mb-4 text-theme" />
|
|
144
|
+
<p className="text-sm font-semibold text-white">Live UI</p>
|
|
145
|
+
<p className="mt-1 text-xs leading-5 text-gray-500">WebSocket, rooms e estado remoto.</p>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</section>
|
|
152
|
+
|
|
153
|
+
<section className="grid gap-3 sm:grid-cols-3">
|
|
154
|
+
{features.map(({ icon: Icon, title, copy }) => (
|
|
155
|
+
<article
|
|
156
|
+
key={title}
|
|
157
|
+
className="group rounded-lg border border-white/10 bg-white/[0.025] p-5 text-left transition hover:border-white/20 hover:bg-white/[0.045]"
|
|
158
|
+
>
|
|
159
|
+
<div className="mb-5 flex h-9 w-9 items-center justify-center rounded-lg border border-white/10 bg-black/30 text-theme transition group-hover:border-theme-active">
|
|
160
|
+
<Icon className="h-4 w-4" />
|
|
161
|
+
</div>
|
|
162
|
+
<h2 className="text-sm font-semibold text-white">{title}</h2>
|
|
163
|
+
<p className="mt-2 text-sm leading-6 text-gray-500">{copy}</p>
|
|
164
|
+
</article>
|
|
165
|
+
))}
|
|
166
|
+
</section>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
@@ -1,68 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
static
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
1
|
+
import { LiveComponent, type FluxStackWebSocket } from '@core/types/types'
|
|
2
|
+
import { CounterRoom } from './rooms/CounterRoom'
|
|
3
|
+
|
|
4
|
+
import type { CounterDemo as _Client } from '@client/src/live/CounterDemo'
|
|
5
|
+
|
|
6
|
+
export class LiveCounter extends LiveComponent<typeof LiveCounter.defaultState> {
|
|
7
|
+
static componentName = 'LiveCounter'
|
|
8
|
+
static publicActions = ['increment', 'decrement', 'reset'] as const
|
|
9
|
+
static defaultState = {
|
|
10
|
+
count: 0,
|
|
11
|
+
lastUpdatedBy: null as string | null,
|
|
12
|
+
connectedUsers: 0,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private roomId: string
|
|
16
|
+
private unsubscribeCounter: (() => void) | null = null
|
|
17
|
+
private unsubscribePresence: (() => void) | null = null
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
initialState: Partial<typeof LiveCounter.defaultState> = {},
|
|
21
|
+
ws: FluxStackWebSocket,
|
|
22
|
+
options?: { room?: string; userId?: string }
|
|
23
|
+
) {
|
|
24
|
+
super(initialState, ws, options)
|
|
25
|
+
|
|
26
|
+
this.roomId = options?.room ?? 'default'
|
|
27
|
+
const room = this.$room(CounterRoom, this.roomId)
|
|
28
|
+
room.join()
|
|
29
|
+
|
|
30
|
+
this.setState({
|
|
31
|
+
count: room.state.count,
|
|
32
|
+
lastUpdatedBy: room.state.lastUpdatedBy,
|
|
33
|
+
connectedUsers: room.state.onlineCount,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
this.unsubscribeCounter = room.on('counter:updated', (data) => {
|
|
37
|
+
this.setState({
|
|
38
|
+
count: data.count,
|
|
39
|
+
lastUpdatedBy: data.updatedBy,
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
this.unsubscribePresence = room.on('presence:changed', (data) => {
|
|
44
|
+
this.setState({ connectedUsers: data.onlineCount })
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async increment() {
|
|
49
|
+
const room = this.$room(CounterRoom, this.roomId)
|
|
50
|
+
const count = room.increment(this.userId || 'anonymous')
|
|
51
|
+
return { success: true, count }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async decrement() {
|
|
55
|
+
const room = this.$room(CounterRoom, this.roomId)
|
|
56
|
+
const count = room.decrement(this.userId || 'anonymous')
|
|
57
|
+
return { success: true, count }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async reset() {
|
|
61
|
+
const room = this.$room(CounterRoom, this.roomId)
|
|
62
|
+
const count = room.reset(this.userId || 'anonymous')
|
|
63
|
+
return { success: true, count }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
destroy() {
|
|
67
|
+
this.unsubscribeCounter?.()
|
|
68
|
+
this.unsubscribePresence?.()
|
|
69
|
+
super.destroy()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -19,7 +19,8 @@ export class LiveSharedCounter extends LiveComponent<typeof LiveSharedCounter.de
|
|
|
19
19
|
onlineCount: 0
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
private counterUnsub: (() => void) | null = null
|
|
22
|
+
private counterUnsub: (() => void) | null = null
|
|
23
|
+
private presenceUnsub: (() => void) | null = null
|
|
23
24
|
|
|
24
25
|
constructor(
|
|
25
26
|
initialState: Partial<typeof LiveSharedCounter.defaultState> = {},
|
|
@@ -40,12 +41,16 @@ export class LiveSharedCounter extends LiveComponent<typeof LiveSharedCounter.de
|
|
|
40
41
|
})
|
|
41
42
|
|
|
42
43
|
// Listen for updates from other users
|
|
43
|
-
this.counterUnsub = room.on('counter:updated', (data) => {
|
|
44
|
-
this.setState({
|
|
45
|
-
count: data.count,
|
|
46
|
-
lastUpdatedBy: data.updatedBy
|
|
47
|
-
})
|
|
48
|
-
})
|
|
44
|
+
this.counterUnsub = room.on('counter:updated', (data) => {
|
|
45
|
+
this.setState({
|
|
46
|
+
count: data.count,
|
|
47
|
+
lastUpdatedBy: data.updatedBy
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
this.presenceUnsub = room.on('presence:changed', (data) => {
|
|
52
|
+
this.setState({ onlineCount: data.onlineCount })
|
|
53
|
+
})
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
async increment() {
|
|
@@ -66,8 +71,9 @@ export class LiveSharedCounter extends LiveComponent<typeof LiveSharedCounter.de
|
|
|
66
71
|
return { success: true, count }
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
destroy() {
|
|
70
|
-
this.counterUnsub?.()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
+
destroy() {
|
|
75
|
+
this.counterUnsub?.()
|
|
76
|
+
this.presenceUnsub?.()
|
|
77
|
+
super.destroy()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Auto-generated Live Components Registration
|
|
2
2
|
// Generated by @fluxstack/live — DO NOT EDIT MANUALLY
|
|
3
|
-
// Generated at: 2026-04-
|
|
3
|
+
// Generated at: 2026-04-15T23:06:28.958Z
|
|
4
4
|
|
|
5
5
|
import { LiveAdminPanel } from "./LiveAdminPanel"
|
|
6
6
|
import { LiveCounter } from "./LiveCounter"
|
|
@@ -10,7 +10,6 @@ import { LivePingPong } from "./LivePingPong"
|
|
|
10
10
|
import { LiveProtectedChat } from "./LiveProtectedChat"
|
|
11
11
|
import { LiveRoomChat } from "./LiveRoomChat"
|
|
12
12
|
import { LiveSharedCounter } from "./LiveSharedCounter"
|
|
13
|
-
import { LiveUpload } from "./LiveUpload"
|
|
14
13
|
|
|
15
14
|
// Component classes array for LiveServer({ components }) option
|
|
16
15
|
export const liveComponentClasses = [
|
|
@@ -22,5 +21,4 @@ export const liveComponentClasses = [
|
|
|
22
21
|
LiveProtectedChat,
|
|
23
22
|
LiveRoomChat,
|
|
24
23
|
LiveSharedCounter,
|
|
25
|
-
LiveUpload,
|
|
26
24
|
]
|
|
@@ -12,22 +12,27 @@ interface CounterState {
|
|
|
12
12
|
onlineCount: number
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
interface CounterEvents {
|
|
16
|
-
'counter:updated': { count: number; updatedBy: string }
|
|
17
|
-
}
|
|
15
|
+
interface CounterEvents {
|
|
16
|
+
'counter:updated': { count: number; updatedBy: string }
|
|
17
|
+
'presence:changed': { onlineCount: number }
|
|
18
|
+
}
|
|
18
19
|
|
|
19
20
|
export class CounterRoom extends LiveRoom<CounterState, {}, CounterEvents> {
|
|
20
21
|
static roomName = 'counter'
|
|
21
22
|
static defaultState: CounterState = { count: 0, lastUpdatedBy: null, onlineCount: 0 }
|
|
22
23
|
static defaultMeta = {}
|
|
23
24
|
|
|
24
|
-
onJoin(_ctx: RoomJoinContext) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
onJoin(_ctx: RoomJoinContext) {
|
|
26
|
+
const onlineCount = this.state.onlineCount + 1
|
|
27
|
+
this.setState({ onlineCount })
|
|
28
|
+
this.emit('presence:changed', { onlineCount })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
onLeave(_ctx: RoomLeaveContext) {
|
|
32
|
+
const onlineCount = Math.max(0, this.state.onlineCount - 1)
|
|
33
|
+
this.setState({ onlineCount })
|
|
34
|
+
this.emit('presence:changed', { onlineCount })
|
|
35
|
+
}
|
|
31
36
|
|
|
32
37
|
increment(username: string) {
|
|
33
38
|
const count = this.state.count + 1
|