calabasas 0.15.0 → 0.16.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/dist/BotList-gmtf52xh.js +165 -0
- package/dist/dashboard-y75f0avj.js +235 -0
- package/dist/index.js +893 -9
- package/package.json +1 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import {
|
|
2
|
+
build_default,
|
|
3
|
+
relativeTime
|
|
4
|
+
} from "./index-8gymgyxd.js";
|
|
5
|
+
import"./index-tre7d3f1.js";
|
|
6
|
+
import {
|
|
7
|
+
__toESM
|
|
8
|
+
} from "./index-sdksp5px.js";
|
|
9
|
+
import {
|
|
10
|
+
Box_default,
|
|
11
|
+
Text
|
|
12
|
+
} from "./index-4rn9k8et.js";
|
|
13
|
+
import {
|
|
14
|
+
cliApi,
|
|
15
|
+
require_jsx_dev_runtime,
|
|
16
|
+
useQuery
|
|
17
|
+
} from "./convex-1z1jsz1n.js";
|
|
18
|
+
import"./index-vmy4gfe1.js";
|
|
19
|
+
|
|
20
|
+
// src/components/StatusBadge.tsx
|
|
21
|
+
var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
|
|
22
|
+
var STATUS_CONFIG = {
|
|
23
|
+
connected: { color: "green", dot: "●" },
|
|
24
|
+
connecting: { color: "yellow", dot: "●" },
|
|
25
|
+
error: { color: "red", dot: "●" },
|
|
26
|
+
disconnected: { color: "gray", dot: "●" }
|
|
27
|
+
};
|
|
28
|
+
function StatusBadge({ status, width = 0 }) {
|
|
29
|
+
const config = STATUS_CONFIG[status];
|
|
30
|
+
const label = `${config.dot} ${status}`;
|
|
31
|
+
const padded = width > 0 ? label.padEnd(width) : label;
|
|
32
|
+
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
35
|
+
color: config.color,
|
|
36
|
+
children: config.dot
|
|
37
|
+
}, undefined, false, undefined, this),
|
|
38
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
39
|
+
children: padded.slice(1)
|
|
40
|
+
}, undefined, false, undefined, this)
|
|
41
|
+
]
|
|
42
|
+
}, undefined, true, undefined, this);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/components/BotList.tsx
|
|
46
|
+
var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
|
|
47
|
+
function BotList({
|
|
48
|
+
apiKey,
|
|
49
|
+
selectedIndex,
|
|
50
|
+
onSelect
|
|
51
|
+
}) {
|
|
52
|
+
const bots = useQuery(cliApi.listBots, { apiKey });
|
|
53
|
+
if (bots === undefined) {
|
|
54
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
57
|
+
color: "cyan",
|
|
58
|
+
children: /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(build_default, {
|
|
59
|
+
type: "dots"
|
|
60
|
+
}, undefined, false, undefined, this)
|
|
61
|
+
}, undefined, false, undefined, this),
|
|
62
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
63
|
+
children: " Loading bots..."
|
|
64
|
+
}, undefined, false, undefined, this)
|
|
65
|
+
]
|
|
66
|
+
}, undefined, true, undefined, this);
|
|
67
|
+
}
|
|
68
|
+
if (bots.length === 0) {
|
|
69
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
70
|
+
children: /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
71
|
+
dimColor: true,
|
|
72
|
+
children: "No bots found. Run `calabasas bot add` to create one."
|
|
73
|
+
}, undefined, false, undefined, this)
|
|
74
|
+
}, undefined, false, undefined, this);
|
|
75
|
+
}
|
|
76
|
+
const COL = { name: 30, status: 16, appId: 22, env: 8, last: 16 };
|
|
77
|
+
function truncate(str, max) {
|
|
78
|
+
if (str.length <= max)
|
|
79
|
+
return str.padEnd(max);
|
|
80
|
+
return str.slice(0, max - 1) + "…";
|
|
81
|
+
}
|
|
82
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
83
|
+
flexDirection: "column",
|
|
84
|
+
children: [
|
|
85
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
86
|
+
marginBottom: 1,
|
|
87
|
+
children: /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
88
|
+
bold: true,
|
|
89
|
+
children: [
|
|
90
|
+
" ",
|
|
91
|
+
"Name".padEnd(COL.name),
|
|
92
|
+
"Status".padEnd(COL.status),
|
|
93
|
+
"Discord App ID".padEnd(COL.appId),
|
|
94
|
+
"Env".padEnd(COL.env),
|
|
95
|
+
"Last Connected"
|
|
96
|
+
]
|
|
97
|
+
}, undefined, true, undefined, this)
|
|
98
|
+
}, undefined, false, undefined, this),
|
|
99
|
+
bots.map((bot, i) => {
|
|
100
|
+
const isSelected = selectedIndex === i;
|
|
101
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
102
|
+
flexDirection: "column",
|
|
103
|
+
children: [
|
|
104
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
105
|
+
children: [
|
|
106
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
107
|
+
color: isSelected ? "cyan" : undefined,
|
|
108
|
+
children: isSelected ? "→ " : " "
|
|
109
|
+
}, undefined, false, undefined, this),
|
|
110
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
111
|
+
bold: isSelected,
|
|
112
|
+
children: truncate(bot.name, COL.name)
|
|
113
|
+
}, undefined, false, undefined, this),
|
|
114
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(StatusBadge, {
|
|
115
|
+
status: bot.status,
|
|
116
|
+
width: COL.status
|
|
117
|
+
}, undefined, false, undefined, this),
|
|
118
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
119
|
+
dimColor: true,
|
|
120
|
+
children: bot.discordAppId.padEnd(COL.appId)
|
|
121
|
+
}, undefined, false, undefined, this),
|
|
122
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
123
|
+
dimColor: true,
|
|
124
|
+
children: bot.environment.padEnd(COL.env)
|
|
125
|
+
}, undefined, false, undefined, this),
|
|
126
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
127
|
+
dimColor: true,
|
|
128
|
+
children: relativeTime(bot.lastConnectedAt)
|
|
129
|
+
}, undefined, false, undefined, this)
|
|
130
|
+
]
|
|
131
|
+
}, undefined, true, undefined, this),
|
|
132
|
+
bot.errorMessage && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
133
|
+
marginLeft: 4,
|
|
134
|
+
children: /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
135
|
+
color: "red",
|
|
136
|
+
dimColor: true,
|
|
137
|
+
children: [
|
|
138
|
+
"└",
|
|
139
|
+
" ",
|
|
140
|
+
bot.errorMessage
|
|
141
|
+
]
|
|
142
|
+
}, undefined, true, undefined, this)
|
|
143
|
+
}, undefined, false, undefined, this),
|
|
144
|
+
!bot.errorMessage && bot.disconnectReason && /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
145
|
+
marginLeft: 4,
|
|
146
|
+
children: /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
147
|
+
dimColor: true,
|
|
148
|
+
children: [
|
|
149
|
+
"└",
|
|
150
|
+
" ",
|
|
151
|
+
bot.disconnectReason
|
|
152
|
+
]
|
|
153
|
+
}, undefined, true, undefined, this)
|
|
154
|
+
}, undefined, false, undefined, this)
|
|
155
|
+
]
|
|
156
|
+
}, bot._id, true, undefined, this);
|
|
157
|
+
})
|
|
158
|
+
]
|
|
159
|
+
}, undefined, true, undefined, this);
|
|
160
|
+
}
|
|
161
|
+
export {
|
|
162
|
+
BotList
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LogViewer
|
|
3
|
+
} from "./index-b0cp0nch.js";
|
|
4
|
+
import {
|
|
5
|
+
getConvexUrl,
|
|
6
|
+
resolveEnv,
|
|
7
|
+
resolvePlatformApiKey
|
|
8
|
+
} from "./index-a8vtmtf9.js";
|
|
9
|
+
import {
|
|
10
|
+
build_default,
|
|
11
|
+
formatLatency,
|
|
12
|
+
formatNumber
|
|
13
|
+
} from "./index-8gymgyxd.js";
|
|
14
|
+
import"./index-tre7d3f1.js";
|
|
15
|
+
import {
|
|
16
|
+
__toESM
|
|
17
|
+
} from "./index-sdksp5px.js";
|
|
18
|
+
import {
|
|
19
|
+
BotList
|
|
20
|
+
} from "./BotList-gmtf52xh.js";
|
|
21
|
+
import {
|
|
22
|
+
Box_default,
|
|
23
|
+
Text,
|
|
24
|
+
render_default,
|
|
25
|
+
use_app_default,
|
|
26
|
+
use_input_default
|
|
27
|
+
} from "./index-4rn9k8et.js";
|
|
28
|
+
import {
|
|
29
|
+
ConvexProvider,
|
|
30
|
+
cliApi,
|
|
31
|
+
createConvexClient,
|
|
32
|
+
require_jsx_dev_runtime,
|
|
33
|
+
useQuery
|
|
34
|
+
} from "./convex-1z1jsz1n.js";
|
|
35
|
+
import {
|
|
36
|
+
require_react
|
|
37
|
+
} from "./index-vmy4gfe1.js";
|
|
38
|
+
|
|
39
|
+
// src/components/Dashboard.tsx
|
|
40
|
+
var import_react2 = __toESM(require_react(), 1);
|
|
41
|
+
|
|
42
|
+
// src/components/Header.tsx
|
|
43
|
+
var jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
|
|
44
|
+
function Header({
|
|
45
|
+
botCount,
|
|
46
|
+
env
|
|
47
|
+
}) {
|
|
48
|
+
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Box_default, {
|
|
49
|
+
marginBottom: 1,
|
|
50
|
+
children: [
|
|
51
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
52
|
+
bold: true,
|
|
53
|
+
color: "magenta",
|
|
54
|
+
children: "calabasas"
|
|
55
|
+
}, undefined, false, undefined, this),
|
|
56
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
57
|
+
dimColor: true,
|
|
58
|
+
children: " v0.1.12"
|
|
59
|
+
}, undefined, false, undefined, this),
|
|
60
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
61
|
+
dimColor: true,
|
|
62
|
+
children: " · "
|
|
63
|
+
}, undefined, false, undefined, this),
|
|
64
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
65
|
+
children: [
|
|
66
|
+
botCount,
|
|
67
|
+
" bot",
|
|
68
|
+
botCount !== 1 ? "s" : ""
|
|
69
|
+
]
|
|
70
|
+
}, undefined, true, undefined, this),
|
|
71
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
72
|
+
dimColor: true,
|
|
73
|
+
children: " · "
|
|
74
|
+
}, undefined, false, undefined, this),
|
|
75
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
76
|
+
color: env === "dev" ? "yellow" : "green",
|
|
77
|
+
children: env
|
|
78
|
+
}, undefined, false, undefined, this),
|
|
79
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
80
|
+
dimColor: true,
|
|
81
|
+
children: " · Press "
|
|
82
|
+
}, undefined, false, undefined, this),
|
|
83
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
84
|
+
bold: true,
|
|
85
|
+
children: "q"
|
|
86
|
+
}, undefined, false, undefined, this),
|
|
87
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
88
|
+
dimColor: true,
|
|
89
|
+
children: " to quit"
|
|
90
|
+
}, undefined, false, undefined, this)
|
|
91
|
+
]
|
|
92
|
+
}, undefined, true, undefined, this);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/components/StatsPanel.tsx
|
|
96
|
+
var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
|
|
97
|
+
var ONE_HOUR = 60 * 60 * 1000;
|
|
98
|
+
function StatsPanel({
|
|
99
|
+
apiKey,
|
|
100
|
+
botId
|
|
101
|
+
}) {
|
|
102
|
+
const stats = useQuery(cliApi.botStats, {
|
|
103
|
+
apiKey,
|
|
104
|
+
botId,
|
|
105
|
+
since: Date.now() - ONE_HOUR
|
|
106
|
+
});
|
|
107
|
+
if (stats === undefined) {
|
|
108
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
109
|
+
children: [
|
|
110
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
111
|
+
color: "cyan",
|
|
112
|
+
children: /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(build_default, {
|
|
113
|
+
type: "dots"
|
|
114
|
+
}, undefined, false, undefined, this)
|
|
115
|
+
}, undefined, false, undefined, this),
|
|
116
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
117
|
+
children: " Loading stats..."
|
|
118
|
+
}, undefined, false, undefined, this)
|
|
119
|
+
]
|
|
120
|
+
}, undefined, true, undefined, this);
|
|
121
|
+
}
|
|
122
|
+
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
123
|
+
gap: 2,
|
|
124
|
+
marginBottom: 1,
|
|
125
|
+
children: [
|
|
126
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
127
|
+
dimColor: true,
|
|
128
|
+
children: "Events (1h):"
|
|
129
|
+
}, undefined, false, undefined, this),
|
|
130
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
131
|
+
bold: true,
|
|
132
|
+
children: formatNumber(stats.total)
|
|
133
|
+
}, undefined, false, undefined, this),
|
|
134
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
135
|
+
color: "green",
|
|
136
|
+
children: [
|
|
137
|
+
formatNumber(stats.success),
|
|
138
|
+
" ok"
|
|
139
|
+
]
|
|
140
|
+
}, undefined, true, undefined, this),
|
|
141
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
142
|
+
color: "red",
|
|
143
|
+
children: [
|
|
144
|
+
formatNumber(stats.failed),
|
|
145
|
+
" failed"
|
|
146
|
+
]
|
|
147
|
+
}, undefined, true, undefined, this),
|
|
148
|
+
/* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Text, {
|
|
149
|
+
dimColor: true,
|
|
150
|
+
children: [
|
|
151
|
+
"avg ",
|
|
152
|
+
formatLatency(stats.avgLatencyMs)
|
|
153
|
+
]
|
|
154
|
+
}, undefined, true, undefined, this)
|
|
155
|
+
]
|
|
156
|
+
}, undefined, true, undefined, this);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/components/Dashboard.tsx
|
|
160
|
+
var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
|
|
161
|
+
function Dashboard({
|
|
162
|
+
apiKey,
|
|
163
|
+
env
|
|
164
|
+
}) {
|
|
165
|
+
const { exit } = use_app_default();
|
|
166
|
+
const [selectedIndex, setSelectedIndex] = import_react2.useState(0);
|
|
167
|
+
const bots = useQuery(cliApi.listBots, { apiKey });
|
|
168
|
+
const botCount = bots?.length ?? 0;
|
|
169
|
+
const selectedBot = bots?.[selectedIndex];
|
|
170
|
+
use_input_default(import_react2.useCallback((input, key) => {
|
|
171
|
+
if (input === "q") {
|
|
172
|
+
exit();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if ((input === "j" || key.downArrow) && botCount > 0) {
|
|
176
|
+
setSelectedIndex((i) => Math.min(i, botCount - 1) === botCount - 1 ? 0 : i + 1);
|
|
177
|
+
}
|
|
178
|
+
if ((input === "k" || key.upArrow) && botCount > 0) {
|
|
179
|
+
setSelectedIndex((i) => i === 0 ? botCount - 1 : i - 1);
|
|
180
|
+
}
|
|
181
|
+
}, [botCount, exit]));
|
|
182
|
+
return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
183
|
+
flexDirection: "column",
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Header, {
|
|
186
|
+
botCount,
|
|
187
|
+
env
|
|
188
|
+
}, undefined, false, undefined, this),
|
|
189
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(BotList, {
|
|
190
|
+
apiKey,
|
|
191
|
+
selectedIndex
|
|
192
|
+
}, undefined, false, undefined, this),
|
|
193
|
+
selectedBot && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
194
|
+
flexDirection: "column",
|
|
195
|
+
marginTop: 1,
|
|
196
|
+
children: [
|
|
197
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(StatsPanel, {
|
|
198
|
+
apiKey,
|
|
199
|
+
botId: selectedBot._id
|
|
200
|
+
}, undefined, false, undefined, this),
|
|
201
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(LogViewer, {
|
|
202
|
+
apiKey,
|
|
203
|
+
botId: selectedBot._id
|
|
204
|
+
}, undefined, false, undefined, this)
|
|
205
|
+
]
|
|
206
|
+
}, undefined, true, undefined, this)
|
|
207
|
+
]
|
|
208
|
+
}, undefined, true, undefined, this);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/commands/dashboard.tsx
|
|
212
|
+
var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
|
|
213
|
+
async function dashboard(options) {
|
|
214
|
+
const env = resolveEnv(options);
|
|
215
|
+
const resolved = resolvePlatformApiKey({}, env);
|
|
216
|
+
if (!resolved) {
|
|
217
|
+
console.log("Not logged in. Run `calabasas login` first.");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const apiKey = resolved.key;
|
|
221
|
+
const convexUrl = getConvexUrl(env);
|
|
222
|
+
const client = createConvexClient(convexUrl);
|
|
223
|
+
const { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime4.jsxDEV(ConvexProvider, {
|
|
224
|
+
client,
|
|
225
|
+
children: /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Dashboard, {
|
|
226
|
+
apiKey,
|
|
227
|
+
env
|
|
228
|
+
}, undefined, false, undefined, this)
|
|
229
|
+
}, undefined, false, undefined, this));
|
|
230
|
+
await waitUntilExit();
|
|
231
|
+
await client.close();
|
|
232
|
+
}
|
|
233
|
+
export {
|
|
234
|
+
dashboard
|
|
235
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -2204,7 +2204,7 @@ export default defineCalabasas({
|
|
|
2204
2204
|
members: false, // Sync members (calabasasMembers table) - requires privileged intent
|
|
2205
2205
|
},
|
|
2206
2206
|
|
|
2207
|
-
// Custom event handlers - events forwarded to your discord:receive mutation
|
|
2207
|
+
// Custom event handlers - events forwarded to your calabasas/discord:receive mutation
|
|
2208
2208
|
events: {
|
|
2209
2209
|
// Enable events you want to receive (all fields included)
|
|
2210
2210
|
// messageCreate: true,
|
|
@@ -2573,13 +2573,13 @@ async function generate(options) {
|
|
|
2573
2573
|
export { ${syncExports.join(", ")} } from "./calabasas/_generated/sync";
|
|
2574
2574
|
|
|
2575
2575
|
3. Add CALABASAS_SECRET to your Convex environment variables
|
|
2576
|
-
4. Create your event handler in convex/discord.ts
|
|
2576
|
+
4. Create your event handler in convex/calabasas/discord.ts
|
|
2577
2577
|
5. Run \`calabasas push\` to sync config with Calabasas`, "Next steps");
|
|
2578
2578
|
} else {
|
|
2579
2579
|
p5.note(`1. Add CALABASAS_SECRET to your Convex environment variables
|
|
2580
|
-
2. Create your handler in convex/discord.ts:
|
|
2580
|
+
2. Create your handler in convex/calabasas/discord.ts:
|
|
2581
2581
|
|
|
2582
|
-
import { handleDiscordEvent } from "./
|
|
2582
|
+
import { handleDiscordEvent } from "./_generated/discord";
|
|
2583
2583
|
|
|
2584
2584
|
export const receive = handleDiscordEvent({
|
|
2585
2585
|
messageCreate: async (ctx, event) => {
|
|
@@ -4729,6 +4729,878 @@ export function useOnlineCount(guildDiscordId: string): number | undefined {
|
|
|
4729
4729
|
});`
|
|
4730
4730
|
};
|
|
4731
4731
|
|
|
4732
|
+
// src/lib/registry/components/emoji-picker.ts
|
|
4733
|
+
var emojiPicker = {
|
|
4734
|
+
name: "emoji-picker",
|
|
4735
|
+
kind: "component",
|
|
4736
|
+
description: "Grid-based emoji picker with custom Discord emojis, Unicode emojis, search, and animated GIF support",
|
|
4737
|
+
requiredSyncTypes: ["emojis"],
|
|
4738
|
+
requiredShadcnComponents: ["popover", "button"],
|
|
4739
|
+
generateReactComponent: () => `"use client";
|
|
4740
|
+
|
|
4741
|
+
import { useState, useRef, useEffect, useCallback, useMemo } from "react";
|
|
4742
|
+
import { useQuery } from "convex/react";
|
|
4743
|
+
import { api } from "@/convex/_generated/api";
|
|
4744
|
+
import {
|
|
4745
|
+
Popover,
|
|
4746
|
+
PopoverContent,
|
|
4747
|
+
PopoverTrigger,
|
|
4748
|
+
} from "@/components/ui/popover";
|
|
4749
|
+
import { Button } from "@/components/ui/button";
|
|
4750
|
+
import { cn } from "@/lib/utils";
|
|
4751
|
+
|
|
4752
|
+
type EmojiData = {
|
|
4753
|
+
id: string;
|
|
4754
|
+
name: string;
|
|
4755
|
+
animated?: boolean;
|
|
4756
|
+
source: "guild" | "application" | "default";
|
|
4757
|
+
};
|
|
4758
|
+
|
|
4759
|
+
type EmojiPickerProps = {
|
|
4760
|
+
guildDiscordId?: string;
|
|
4761
|
+
source?: "all" | "guild" | "application" | "default";
|
|
4762
|
+
mode?: "single" | "multi";
|
|
4763
|
+
maxCount?: number;
|
|
4764
|
+
value?: string[];
|
|
4765
|
+
onSelect?: (emoji: EmojiData) => void;
|
|
4766
|
+
onChange?: (emojis: EmojiData[]) => void;
|
|
4767
|
+
columns?: number;
|
|
4768
|
+
pageSize?: number;
|
|
4769
|
+
placeholder?: string;
|
|
4770
|
+
className?: string;
|
|
4771
|
+
};
|
|
4772
|
+
|
|
4773
|
+
/** Curated default Unicode emojis organized by Discord-style categories */
|
|
4774
|
+
const DEFAULT_EMOJIS: { category: string; emojis: { codepoint: string; name: string }[] }[] = [
|
|
4775
|
+
{
|
|
4776
|
+
category: "Smileys & Emotion",
|
|
4777
|
+
emojis: [
|
|
4778
|
+
{ codepoint: "\uD83D\uDE00", name: "grinning face" },
|
|
4779
|
+
{ codepoint: "\uD83D\uDE03", name: "grinning face with big eyes" },
|
|
4780
|
+
{ codepoint: "\uD83D\uDE04", name: "grinning face with smiling eyes" },
|
|
4781
|
+
{ codepoint: "\uD83D\uDE01", name: "beaming face with smiling eyes" },
|
|
4782
|
+
{ codepoint: "\uD83D\uDE06", name: "grinning squinting face" },
|
|
4783
|
+
{ codepoint: "\uD83D\uDE05", name: "grinning face with sweat" },
|
|
4784
|
+
{ codepoint: "\uD83E\uDD23", name: "rolling on the floor laughing" },
|
|
4785
|
+
{ codepoint: "\uD83D\uDE02", name: "face with tears of joy" },
|
|
4786
|
+
{ codepoint: "\uD83D\uDE42", name: "slightly smiling face" },
|
|
4787
|
+
{ codepoint: "\uD83D\uDE09", name: "winking face" },
|
|
4788
|
+
{ codepoint: "\uD83D\uDE0A", name: "smiling face with smiling eyes" },
|
|
4789
|
+
{ codepoint: "\uD83D\uDE07", name: "smiling face with halo" },
|
|
4790
|
+
{ codepoint: "\uD83E\uDD70", name: "smiling face with hearts" },
|
|
4791
|
+
{ codepoint: "\uD83D\uDE0D", name: "smiling face with heart eyes" },
|
|
4792
|
+
{ codepoint: "\uD83E\uDD29", name: "star struck" },
|
|
4793
|
+
{ codepoint: "\uD83D\uDE18", name: "face blowing a kiss" },
|
|
4794
|
+
{ codepoint: "\uD83D\uDE0B", name: "face savoring food" },
|
|
4795
|
+
{ codepoint: "\uD83D\uDE1B", name: "face with tongue" },
|
|
4796
|
+
{ codepoint: "\uD83D\uDE1C", name: "winking face with tongue" },
|
|
4797
|
+
{ codepoint: "\uD83E\uDD2A", name: "zany face" },
|
|
4798
|
+
{ codepoint: "\uD83D\uDE1D", name: "squinting face with tongue" },
|
|
4799
|
+
{ codepoint: "\uD83E\uDD11", name: "money mouth face" },
|
|
4800
|
+
{ codepoint: "\uD83E\uDD17", name: "hugging face" },
|
|
4801
|
+
{ codepoint: "\uD83E\uDD2D", name: "face with hand over mouth" },
|
|
4802
|
+
{ codepoint: "\uD83E\uDD2B", name: "shushing face" },
|
|
4803
|
+
{ codepoint: "\uD83E\uDD14", name: "thinking face" },
|
|
4804
|
+
{ codepoint: "\uD83E\uDD10", name: "zipper mouth face" },
|
|
4805
|
+
{ codepoint: "\uD83E\uDD28", name: "face with raised eyebrow" },
|
|
4806
|
+
{ codepoint: "\uD83D\uDE10", name: "neutral face" },
|
|
4807
|
+
{ codepoint: "\uD83D\uDE11", name: "expressionless face" },
|
|
4808
|
+
{ codepoint: "\uD83D\uDE36", name: "face without mouth" },
|
|
4809
|
+
{ codepoint: "\uD83D\uDE0F", name: "smirking face" },
|
|
4810
|
+
{ codepoint: "\uD83D\uDE12", name: "unamused face" },
|
|
4811
|
+
{ codepoint: "\uD83D\uDE44", name: "face with rolling eyes" },
|
|
4812
|
+
{ codepoint: "\uD83D\uDE2C", name: "grimacing face" },
|
|
4813
|
+
{ codepoint: "\uD83E\uDD25", name: "lying face" },
|
|
4814
|
+
{ codepoint: "\uD83D\uDE0C", name: "relieved face" },
|
|
4815
|
+
{ codepoint: "\uD83D\uDE14", name: "pensive face" },
|
|
4816
|
+
{ codepoint: "\uD83D\uDE2A", name: "sleepy face" },
|
|
4817
|
+
{ codepoint: "\uD83E\uDD24", name: "drooling face" },
|
|
4818
|
+
{ codepoint: "\uD83D\uDE34", name: "sleeping face" },
|
|
4819
|
+
{ codepoint: "\uD83D\uDE37", name: "face with medical mask" },
|
|
4820
|
+
{ codepoint: "\uD83E\uDD12", name: "face with thermometer" },
|
|
4821
|
+
{ codepoint: "\uD83E\uDD15", name: "face with head bandage" },
|
|
4822
|
+
{ codepoint: "\uD83E\uDD22", name: "nauseated face" },
|
|
4823
|
+
{ codepoint: "\uD83E\uDD2E", name: "face vomiting" },
|
|
4824
|
+
{ codepoint: "\uD83E\uDD75", name: "hot face" },
|
|
4825
|
+
{ codepoint: "\uD83E\uDD76", name: "cold face" },
|
|
4826
|
+
{ codepoint: "\uD83E\uDD74", name: "woozy face" },
|
|
4827
|
+
{ codepoint: "\uD83D\uDE35", name: "face with crossed out eyes" },
|
|
4828
|
+
{ codepoint: "\uD83E\uDD2F", name: "exploding head" },
|
|
4829
|
+
{ codepoint: "\uD83D\uDE0E", name: "smiling face with sunglasses" },
|
|
4830
|
+
{ codepoint: "\uD83E\uDD73", name: "partying face" },
|
|
4831
|
+
{ codepoint: "\uD83D\uDE1F", name: "worried face" },
|
|
4832
|
+
{ codepoint: "\uD83D\uDE15", name: "confused face" },
|
|
4833
|
+
{ codepoint: "\uD83D\uDE22", name: "crying face" },
|
|
4834
|
+
{ codepoint: "\uD83D\uDE2D", name: "loudly crying face" },
|
|
4835
|
+
{ codepoint: "\uD83D\uDE24", name: "face with steam from nose" },
|
|
4836
|
+
{ codepoint: "\uD83D\uDE20", name: "angry face" },
|
|
4837
|
+
{ codepoint: "\uD83D\uDE21", name: "pouting face" },
|
|
4838
|
+
{ codepoint: "\uD83E\uDD2C", name: "face with symbols on mouth" },
|
|
4839
|
+
{ codepoint: "\uD83D\uDC7F", name: "angry face with horns" },
|
|
4840
|
+
{ codepoint: "\uD83D\uDC80", name: "skull" },
|
|
4841
|
+
{ codepoint: "\uD83D\uDCA9", name: "pile of poo" },
|
|
4842
|
+
{ codepoint: "\uD83E\uDD21", name: "clown face" },
|
|
4843
|
+
{ codepoint: "\uD83D\uDC7B", name: "ghost" },
|
|
4844
|
+
{ codepoint: "\uD83D\uDC7D", name: "alien" },
|
|
4845
|
+
{ codepoint: "\uD83E\uDD16", name: "robot" },
|
|
4846
|
+
{ codepoint: "\uD83D\uDE3A", name: "grinning cat" },
|
|
4847
|
+
{ codepoint: "\uD83D\uDE3B", name: "smiling cat with heart eyes" },
|
|
4848
|
+
{ codepoint: "\uD83D\uDC8B", name: "kiss mark" },
|
|
4849
|
+
{ codepoint: "\uD83D\uDCAF", name: "hundred points" },
|
|
4850
|
+
{ codepoint: "\uD83D\uDCA2", name: "anger symbol" },
|
|
4851
|
+
{ codepoint: "\uD83D\uDCA5", name: "collision" },
|
|
4852
|
+
{ codepoint: "\uD83D\uDCAB", name: "dizzy" },
|
|
4853
|
+
{ codepoint: "\uD83D\uDCA6", name: "sweat droplets" },
|
|
4854
|
+
{ codepoint: "❤️", name: "red heart" },
|
|
4855
|
+
{ codepoint: "\uD83E\uDDE1", name: "orange heart" },
|
|
4856
|
+
{ codepoint: "\uD83D\uDC9B", name: "yellow heart" },
|
|
4857
|
+
{ codepoint: "\uD83D\uDC9A", name: "green heart" },
|
|
4858
|
+
{ codepoint: "\uD83D\uDC99", name: "blue heart" },
|
|
4859
|
+
{ codepoint: "\uD83D\uDC9C", name: "purple heart" },
|
|
4860
|
+
{ codepoint: "\uD83D\uDDA4", name: "black heart" },
|
|
4861
|
+
{ codepoint: "\uD83E\uDD0D", name: "white heart" },
|
|
4862
|
+
{ codepoint: "\uD83D\uDC94", name: "broken heart" },
|
|
4863
|
+
],
|
|
4864
|
+
},
|
|
4865
|
+
{
|
|
4866
|
+
category: "People & Body",
|
|
4867
|
+
emojis: [
|
|
4868
|
+
{ codepoint: "\uD83D\uDC4B", name: "waving hand" },
|
|
4869
|
+
{ codepoint: "\uD83E\uDD1A", name: "raised back of hand" },
|
|
4870
|
+
{ codepoint: "✋", name: "raised hand" },
|
|
4871
|
+
{ codepoint: "\uD83D\uDD96", name: "vulcan salute" },
|
|
4872
|
+
{ codepoint: "\uD83D\uDC4C", name: "ok hand" },
|
|
4873
|
+
{ codepoint: "\uD83E\uDD0C", name: "pinched fingers" },
|
|
4874
|
+
{ codepoint: "✌️", name: "victory hand" },
|
|
4875
|
+
{ codepoint: "\uD83E\uDD1E", name: "crossed fingers" },
|
|
4876
|
+
{ codepoint: "\uD83E\uDD1F", name: "love you gesture" },
|
|
4877
|
+
{ codepoint: "\uD83E\uDD18", name: "sign of the horns" },
|
|
4878
|
+
{ codepoint: "\uD83E\uDD19", name: "call me hand" },
|
|
4879
|
+
{ codepoint: "\uD83D\uDC48", name: "backhand index pointing left" },
|
|
4880
|
+
{ codepoint: "\uD83D\uDC49", name: "backhand index pointing right" },
|
|
4881
|
+
{ codepoint: "\uD83D\uDC46", name: "backhand index pointing up" },
|
|
4882
|
+
{ codepoint: "\uD83D\uDC47", name: "backhand index pointing down" },
|
|
4883
|
+
{ codepoint: "☝️", name: "index pointing up" },
|
|
4884
|
+
{ codepoint: "\uD83D\uDC4D", name: "thumbs up" },
|
|
4885
|
+
{ codepoint: "\uD83D\uDC4E", name: "thumbs down" },
|
|
4886
|
+
{ codepoint: "✊", name: "raised fist" },
|
|
4887
|
+
{ codepoint: "\uD83D\uDC4A", name: "oncoming fist" },
|
|
4888
|
+
{ codepoint: "\uD83E\uDD1B", name: "left facing fist" },
|
|
4889
|
+
{ codepoint: "\uD83E\uDD1C", name: "right facing fist" },
|
|
4890
|
+
{ codepoint: "\uD83D\uDC4F", name: "clapping hands" },
|
|
4891
|
+
{ codepoint: "\uD83D\uDE4C", name: "raising hands" },
|
|
4892
|
+
{ codepoint: "\uD83D\uDC50", name: "open hands" },
|
|
4893
|
+
{ codepoint: "\uD83E\uDD32", name: "palms up together" },
|
|
4894
|
+
{ codepoint: "\uD83E\uDD1D", name: "handshake" },
|
|
4895
|
+
{ codepoint: "\uD83D\uDE4F", name: "folded hands" },
|
|
4896
|
+
{ codepoint: "\uD83D\uDCAA", name: "flexed biceps" },
|
|
4897
|
+
{ codepoint: "\uD83E\uDDBE", name: "mechanical arm" },
|
|
4898
|
+
{ codepoint: "\uD83D\uDC40", name: "eyes" },
|
|
4899
|
+
{ codepoint: "\uD83D\uDC41️", name: "eye" },
|
|
4900
|
+
{ codepoint: "\uD83D\uDC45", name: "tongue" },
|
|
4901
|
+
{ codepoint: "\uD83D\uDC44", name: "mouth" },
|
|
4902
|
+
{ codepoint: "\uD83E\uDDE0", name: "brain" },
|
|
4903
|
+
{ codepoint: "\uD83D\uDC76", name: "baby" },
|
|
4904
|
+
{ codepoint: "\uD83E\uDDD1", name: "person" },
|
|
4905
|
+
{ codepoint: "\uD83D\uDC66", name: "boy" },
|
|
4906
|
+
{ codepoint: "\uD83D\uDC67", name: "girl" },
|
|
4907
|
+
],
|
|
4908
|
+
},
|
|
4909
|
+
{
|
|
4910
|
+
category: "Animals & Nature",
|
|
4911
|
+
emojis: [
|
|
4912
|
+
{ codepoint: "\uD83D\uDC36", name: "dog face" },
|
|
4913
|
+
{ codepoint: "\uD83D\uDC31", name: "cat face" },
|
|
4914
|
+
{ codepoint: "\uD83D\uDC2D", name: "mouse face" },
|
|
4915
|
+
{ codepoint: "\uD83D\uDC39", name: "hamster" },
|
|
4916
|
+
{ codepoint: "\uD83D\uDC30", name: "rabbit face" },
|
|
4917
|
+
{ codepoint: "\uD83E\uDD8A", name: "fox" },
|
|
4918
|
+
{ codepoint: "\uD83D\uDC3B", name: "bear" },
|
|
4919
|
+
{ codepoint: "\uD83D\uDC3C", name: "panda" },
|
|
4920
|
+
{ codepoint: "\uD83D\uDC28", name: "koala" },
|
|
4921
|
+
{ codepoint: "\uD83D\uDC2F", name: "tiger face" },
|
|
4922
|
+
{ codepoint: "\uD83E\uDD81", name: "lion" },
|
|
4923
|
+
{ codepoint: "\uD83D\uDC2E", name: "cow face" },
|
|
4924
|
+
{ codepoint: "\uD83D\uDC37", name: "pig face" },
|
|
4925
|
+
{ codepoint: "\uD83D\uDC38", name: "frog" },
|
|
4926
|
+
{ codepoint: "\uD83D\uDC35", name: "monkey face" },
|
|
4927
|
+
{ codepoint: "\uD83D\uDE48", name: "see no evil monkey" },
|
|
4928
|
+
{ codepoint: "\uD83D\uDE49", name: "hear no evil monkey" },
|
|
4929
|
+
{ codepoint: "\uD83D\uDE4A", name: "speak no evil monkey" },
|
|
4930
|
+
{ codepoint: "\uD83D\uDC14", name: "chicken" },
|
|
4931
|
+
{ codepoint: "\uD83D\uDC27", name: "penguin" },
|
|
4932
|
+
{ codepoint: "\uD83D\uDC26", name: "bird" },
|
|
4933
|
+
{ codepoint: "\uD83E\uDD85", name: "eagle" },
|
|
4934
|
+
{ codepoint: "\uD83E\uDD86", name: "duck" },
|
|
4935
|
+
{ codepoint: "\uD83E\uDD89", name: "owl" },
|
|
4936
|
+
{ codepoint: "\uD83D\uDC3A", name: "wolf" },
|
|
4937
|
+
{ codepoint: "\uD83D\uDC17", name: "boar" },
|
|
4938
|
+
{ codepoint: "\uD83D\uDC34", name: "horse face" },
|
|
4939
|
+
{ codepoint: "\uD83E\uDD84", name: "unicorn" },
|
|
4940
|
+
{ codepoint: "\uD83D\uDC1D", name: "honeybee" },
|
|
4941
|
+
{ codepoint: "\uD83D\uDC1B", name: "bug" },
|
|
4942
|
+
{ codepoint: "\uD83E\uDD8B", name: "butterfly" },
|
|
4943
|
+
{ codepoint: "\uD83D\uDC0C", name: "snail" },
|
|
4944
|
+
{ codepoint: "\uD83D\uDC19", name: "octopus" },
|
|
4945
|
+
{ codepoint: "\uD83D\uDC20", name: "tropical fish" },
|
|
4946
|
+
{ codepoint: "\uD83D\uDC2C", name: "dolphin" },
|
|
4947
|
+
{ codepoint: "\uD83D\uDC33", name: "spouting whale" },
|
|
4948
|
+
{ codepoint: "\uD83E\uDD88", name: "shark" },
|
|
4949
|
+
{ codepoint: "\uD83D\uDC0A", name: "crocodile" },
|
|
4950
|
+
{ codepoint: "\uD83D\uDC09", name: "dragon" },
|
|
4951
|
+
{ codepoint: "\uD83C\uDF38", name: "cherry blossom" },
|
|
4952
|
+
{ codepoint: "\uD83C\uDF39", name: "rose" },
|
|
4953
|
+
{ codepoint: "\uD83C\uDF3A", name: "hibiscus" },
|
|
4954
|
+
{ codepoint: "\uD83C\uDF3B", name: "sunflower" },
|
|
4955
|
+
{ codepoint: "\uD83C\uDF32", name: "evergreen tree" },
|
|
4956
|
+
{ codepoint: "\uD83C\uDF35", name: "cactus" },
|
|
4957
|
+
{ codepoint: "\uD83C\uDF40", name: "four leaf clover" },
|
|
4958
|
+
{ codepoint: "\uD83C\uDF41", name: "maple leaf" },
|
|
4959
|
+
{ codepoint: "\uD83C\uDF42", name: "fallen leaf" },
|
|
4960
|
+
{ codepoint: "\uD83C\uDF43", name: "leaf fluttering in wind" },
|
|
4961
|
+
],
|
|
4962
|
+
},
|
|
4963
|
+
{
|
|
4964
|
+
category: "Food & Drink",
|
|
4965
|
+
emojis: [
|
|
4966
|
+
{ codepoint: "\uD83C\uDF4E", name: "red apple" },
|
|
4967
|
+
{ codepoint: "\uD83C\uDF4A", name: "tangerine" },
|
|
4968
|
+
{ codepoint: "\uD83C\uDF4B", name: "lemon" },
|
|
4969
|
+
{ codepoint: "\uD83C\uDF4C", name: "banana" },
|
|
4970
|
+
{ codepoint: "\uD83C\uDF49", name: "watermelon" },
|
|
4971
|
+
{ codepoint: "\uD83C\uDF47", name: "grapes" },
|
|
4972
|
+
{ codepoint: "\uD83C\uDF53", name: "strawberry" },
|
|
4973
|
+
{ codepoint: "\uD83E\uDED0", name: "blueberries" },
|
|
4974
|
+
{ codepoint: "\uD83C\uDF51", name: "peach" },
|
|
4975
|
+
{ codepoint: "\uD83C\uDF52", name: "cherries" },
|
|
4976
|
+
{ codepoint: "\uD83E\uDD51", name: "avocado" },
|
|
4977
|
+
{ codepoint: "\uD83C\uDF55", name: "pizza" },
|
|
4978
|
+
{ codepoint: "\uD83C\uDF54", name: "hamburger" },
|
|
4979
|
+
{ codepoint: "\uD83C\uDF5F", name: "french fries" },
|
|
4980
|
+
{ codepoint: "\uD83C\uDF2D", name: "hot dog" },
|
|
4981
|
+
{ codepoint: "\uD83C\uDF7F", name: "popcorn" },
|
|
4982
|
+
{ codepoint: "\uD83C\uDF69", name: "doughnut" },
|
|
4983
|
+
{ codepoint: "\uD83C\uDF6A", name: "cookie" },
|
|
4984
|
+
{ codepoint: "\uD83C\uDF82", name: "birthday cake" },
|
|
4985
|
+
{ codepoint: "\uD83C\uDF70", name: "shortcake" },
|
|
4986
|
+
{ codepoint: "\uD83E\uDDC1", name: "cupcake" },
|
|
4987
|
+
{ codepoint: "\uD83C\uDF6B", name: "chocolate bar" },
|
|
4988
|
+
{ codepoint: "\uD83C\uDF6C", name: "candy" },
|
|
4989
|
+
{ codepoint: "☕", name: "hot beverage" },
|
|
4990
|
+
{ codepoint: "\uD83C\uDF75", name: "teacup without handle" },
|
|
4991
|
+
{ codepoint: "\uD83C\uDF7A", name: "beer mug" },
|
|
4992
|
+
{ codepoint: "\uD83C\uDF7B", name: "clinking beer mugs" },
|
|
4993
|
+
{ codepoint: "\uD83E\uDD42", name: "clinking glasses" },
|
|
4994
|
+
{ codepoint: "\uD83C\uDF77", name: "wine glass" },
|
|
4995
|
+
{ codepoint: "\uD83E\uDDC3", name: "beverage box" },
|
|
4996
|
+
],
|
|
4997
|
+
},
|
|
4998
|
+
{
|
|
4999
|
+
category: "Travel & Places",
|
|
5000
|
+
emojis: [
|
|
5001
|
+
{ codepoint: "\uD83D\uDE97", name: "automobile" },
|
|
5002
|
+
{ codepoint: "\uD83D\uDE95", name: "taxi" },
|
|
5003
|
+
{ codepoint: "\uD83D\uDE80", name: "rocket" },
|
|
5004
|
+
{ codepoint: "✈️", name: "airplane" },
|
|
5005
|
+
{ codepoint: "\uD83D\uDE81", name: "helicopter" },
|
|
5006
|
+
{ codepoint: "\uD83D\uDE82", name: "locomotive" },
|
|
5007
|
+
{ codepoint: "\uD83D\uDEA2", name: "ship" },
|
|
5008
|
+
{ codepoint: "\uD83C\uDFE0", name: "house" },
|
|
5009
|
+
{ codepoint: "\uD83C\uDFE2", name: "office building" },
|
|
5010
|
+
{ codepoint: "\uD83C\uDFF0", name: "castle" },
|
|
5011
|
+
{ codepoint: "\uD83D\uDDFC", name: "Tokyo tower" },
|
|
5012
|
+
{ codepoint: "\uD83D\uDDFD", name: "Statue of Liberty" },
|
|
5013
|
+
{ codepoint: "⛪", name: "church" },
|
|
5014
|
+
{ codepoint: "\uD83C\uDF0D", name: "globe showing Europe Africa" },
|
|
5015
|
+
{ codepoint: "\uD83C\uDF0E", name: "globe showing Americas" },
|
|
5016
|
+
{ codepoint: "\uD83C\uDF0F", name: "globe showing Asia Australia" },
|
|
5017
|
+
{ codepoint: "\uD83C\uDF0B", name: "volcano" },
|
|
5018
|
+
{ codepoint: "\uD83C\uDFD4️", name: "snow capped mountain" },
|
|
5019
|
+
{ codepoint: "⛰️", name: "mountain" },
|
|
5020
|
+
{ codepoint: "\uD83C\uDFD6️", name: "beach with umbrella" },
|
|
5021
|
+
{ codepoint: "\uD83C\uDF05", name: "sunrise" },
|
|
5022
|
+
{ codepoint: "\uD83C\uDF04", name: "sunrise over mountains" },
|
|
5023
|
+
{ codepoint: "\uD83C\uDF20", name: "shooting star" },
|
|
5024
|
+
{ codepoint: "\uD83C\uDF87", name: "sparkler" },
|
|
5025
|
+
{ codepoint: "\uD83C\uDF86", name: "fireworks" },
|
|
5026
|
+
{ codepoint: "\uD83C\uDF08", name: "rainbow" },
|
|
5027
|
+
{ codepoint: "⛈️", name: "cloud with lightning and rain" },
|
|
5028
|
+
{ codepoint: "❄️", name: "snowflake" },
|
|
5029
|
+
{ codepoint: "☀️", name: "sun" },
|
|
5030
|
+
{ codepoint: "\uD83C\uDF19", name: "crescent moon" },
|
|
5031
|
+
{ codepoint: "⭐", name: "star" },
|
|
5032
|
+
{ codepoint: "\uD83C\uDF1F", name: "glowing star" },
|
|
5033
|
+
{ codepoint: "\uD83D\uDCA7", name: "droplet" },
|
|
5034
|
+
{ codepoint: "\uD83D\uDD25", name: "fire" },
|
|
5035
|
+
],
|
|
5036
|
+
},
|
|
5037
|
+
{
|
|
5038
|
+
category: "Activities",
|
|
5039
|
+
emojis: [
|
|
5040
|
+
{ codepoint: "⚽", name: "soccer ball" },
|
|
5041
|
+
{ codepoint: "\uD83C\uDFC0", name: "basketball" },
|
|
5042
|
+
{ codepoint: "\uD83C\uDFC8", name: "american football" },
|
|
5043
|
+
{ codepoint: "⚾", name: "baseball" },
|
|
5044
|
+
{ codepoint: "\uD83C\uDFBE", name: "tennis" },
|
|
5045
|
+
{ codepoint: "\uD83C\uDFD0", name: "volleyball" },
|
|
5046
|
+
{ codepoint: "\uD83C\uDFB1", name: "pool 8 ball" },
|
|
5047
|
+
{ codepoint: "\uD83C\uDFD3", name: "ping pong" },
|
|
5048
|
+
{ codepoint: "\uD83C\uDFAE", name: "video game" },
|
|
5049
|
+
{ codepoint: "\uD83D\uDD79️", name: "joystick" },
|
|
5050
|
+
{ codepoint: "\uD83C\uDFB2", name: "game die" },
|
|
5051
|
+
{ codepoint: "\uD83E\uDDE9", name: "puzzle piece" },
|
|
5052
|
+
{ codepoint: "\uD83C\uDFAF", name: "bullseye" },
|
|
5053
|
+
{ codepoint: "\uD83C\uDFB3", name: "bowling" },
|
|
5054
|
+
{ codepoint: "\uD83C\uDFAA", name: "circus tent" },
|
|
5055
|
+
{ codepoint: "\uD83C\uDFAD", name: "performing arts" },
|
|
5056
|
+
{ codepoint: "\uD83C\uDFA8", name: "artist palette" },
|
|
5057
|
+
{ codepoint: "\uD83C\uDFAC", name: "clapper board" },
|
|
5058
|
+
{ codepoint: "\uD83C\uDFA4", name: "microphone" },
|
|
5059
|
+
{ codepoint: "\uD83C\uDFA7", name: "headphone" },
|
|
5060
|
+
{ codepoint: "\uD83C\uDFB5", name: "musical note" },
|
|
5061
|
+
{ codepoint: "\uD83C\uDFB6", name: "musical notes" },
|
|
5062
|
+
{ codepoint: "\uD83C\uDFB9", name: "musical keyboard" },
|
|
5063
|
+
{ codepoint: "\uD83C\uDFB8", name: "guitar" },
|
|
5064
|
+
{ codepoint: "\uD83E\uDD41", name: "drum" },
|
|
5065
|
+
{ codepoint: "\uD83C\uDFAA", name: "circus tent" },
|
|
5066
|
+
{ codepoint: "\uD83C\uDFC6", name: "trophy" },
|
|
5067
|
+
{ codepoint: "\uD83E\uDD47", name: "1st place medal" },
|
|
5068
|
+
{ codepoint: "\uD83E\uDD48", name: "2nd place medal" },
|
|
5069
|
+
{ codepoint: "\uD83E\uDD49", name: "3rd place medal" },
|
|
5070
|
+
{ codepoint: "\uD83C\uDFC5", name: "sports medal" },
|
|
5071
|
+
{ codepoint: "\uD83C\uDF96️", name: "military medal" },
|
|
5072
|
+
{ codepoint: "\uD83C\uDF97️", name: "reminder ribbon" },
|
|
5073
|
+
{ codepoint: "\uD83C\uDF81", name: "wrapped gift" },
|
|
5074
|
+
{ codepoint: "\uD83C\uDF88", name: "balloon" },
|
|
5075
|
+
{ codepoint: "\uD83C\uDF89", name: "party popper" },
|
|
5076
|
+
{ codepoint: "\uD83C\uDF8A", name: "confetti ball" },
|
|
5077
|
+
],
|
|
5078
|
+
},
|
|
5079
|
+
{
|
|
5080
|
+
category: "Objects",
|
|
5081
|
+
emojis: [
|
|
5082
|
+
{ codepoint: "⌚", name: "watch" },
|
|
5083
|
+
{ codepoint: "\uD83D\uDCF1", name: "mobile phone" },
|
|
5084
|
+
{ codepoint: "\uD83D\uDCBB", name: "laptop" },
|
|
5085
|
+
{ codepoint: "⌨️", name: "keyboard" },
|
|
5086
|
+
{ codepoint: "\uD83D\uDDA5️", name: "desktop computer" },
|
|
5087
|
+
{ codepoint: "\uD83D\uDDA8️", name: "printer" },
|
|
5088
|
+
{ codepoint: "\uD83D\uDDB1️", name: "computer mouse" },
|
|
5089
|
+
{ codepoint: "\uD83D\uDCBE", name: "floppy disk" },
|
|
5090
|
+
{ codepoint: "\uD83D\uDCBF", name: "optical disk" },
|
|
5091
|
+
{ codepoint: "\uD83D\uDCF7", name: "camera" },
|
|
5092
|
+
{ codepoint: "\uD83D\uDCF9", name: "video camera" },
|
|
5093
|
+
{ codepoint: "\uD83C\uDFA5", name: "movie camera" },
|
|
5094
|
+
{ codepoint: "\uD83D\uDCFA", name: "television" },
|
|
5095
|
+
{ codepoint: "\uD83D\uDCFB", name: "radio" },
|
|
5096
|
+
{ codepoint: "\uD83D\uDD14", name: "bell" },
|
|
5097
|
+
{ codepoint: "\uD83D\uDCE3", name: "megaphone" },
|
|
5098
|
+
{ codepoint: "\uD83D\uDCA1", name: "light bulb" },
|
|
5099
|
+
{ codepoint: "\uD83D\uDD26", name: "flashlight" },
|
|
5100
|
+
{ codepoint: "\uD83D\uDCDA", name: "books" },
|
|
5101
|
+
{ codepoint: "\uD83D\uDCD6", name: "open book" },
|
|
5102
|
+
{ codepoint: "\uD83D\uDCDD", name: "memo" },
|
|
5103
|
+
{ codepoint: "✏️", name: "pencil" },
|
|
5104
|
+
{ codepoint: "\uD83D\uDCCE", name: "paperclip" },
|
|
5105
|
+
{ codepoint: "\uD83D\uDCCC", name: "pushpin" },
|
|
5106
|
+
{ codepoint: "\uD83D\uDD11", name: "key" },
|
|
5107
|
+
{ codepoint: "\uD83D\uDD12", name: "locked" },
|
|
5108
|
+
{ codepoint: "\uD83D\uDD13", name: "unlocked" },
|
|
5109
|
+
{ codepoint: "\uD83D\uDD27", name: "wrench" },
|
|
5110
|
+
{ codepoint: "\uD83D\uDD28", name: "hammer" },
|
|
5111
|
+
{ codepoint: "⚙️", name: "gear" },
|
|
5112
|
+
{ codepoint: "\uD83E\uDDF2", name: "magnet" },
|
|
5113
|
+
{ codepoint: "\uD83D\uDC8E", name: "gem stone" },
|
|
5114
|
+
{ codepoint: "\uD83E\uDDEA", name: "test tube" },
|
|
5115
|
+
{ codepoint: "\uD83D\uDC8A", name: "pill" },
|
|
5116
|
+
{ codepoint: "\uD83E\uDE79", name: "adhesive bandage" },
|
|
5117
|
+
],
|
|
5118
|
+
},
|
|
5119
|
+
{
|
|
5120
|
+
category: "Symbols",
|
|
5121
|
+
emojis: [
|
|
5122
|
+
{ codepoint: "❤️", name: "red heart" },
|
|
5123
|
+
{ codepoint: "✅", name: "check mark button" },
|
|
5124
|
+
{ codepoint: "❌", name: "cross mark" },
|
|
5125
|
+
{ codepoint: "⭕", name: "hollow red circle" },
|
|
5126
|
+
{ codepoint: "❗", name: "red exclamation mark" },
|
|
5127
|
+
{ codepoint: "❓", name: "red question mark" },
|
|
5128
|
+
{ codepoint: "⚠️", name: "warning" },
|
|
5129
|
+
{ codepoint: "\uD83D\uDD34", name: "red circle" },
|
|
5130
|
+
{ codepoint: "\uD83D\uDFE0", name: "orange circle" },
|
|
5131
|
+
{ codepoint: "\uD83D\uDFE1", name: "yellow circle" },
|
|
5132
|
+
{ codepoint: "\uD83D\uDFE2", name: "green circle" },
|
|
5133
|
+
{ codepoint: "\uD83D\uDD35", name: "blue circle" },
|
|
5134
|
+
{ codepoint: "\uD83D\uDFE3", name: "purple circle" },
|
|
5135
|
+
{ codepoint: "⚫", name: "black circle" },
|
|
5136
|
+
{ codepoint: "⚪", name: "white circle" },
|
|
5137
|
+
{ codepoint: "\uD83D\uDD36", name: "large orange diamond" },
|
|
5138
|
+
{ codepoint: "\uD83D\uDD37", name: "large blue diamond" },
|
|
5139
|
+
{ codepoint: "➕", name: "plus" },
|
|
5140
|
+
{ codepoint: "➖", name: "minus" },
|
|
5141
|
+
{ codepoint: "➗", name: "divide" },
|
|
5142
|
+
{ codepoint: "✖️", name: "multiply" },
|
|
5143
|
+
{ codepoint: "♻️", name: "recycling symbol" },
|
|
5144
|
+
{ codepoint: "\uD83D\uDCB2", name: "heavy dollar sign" },
|
|
5145
|
+
{ codepoint: "©️", name: "copyright" },
|
|
5146
|
+
{ codepoint: "®️", name: "registered" },
|
|
5147
|
+
{ codepoint: "™️", name: "trade mark" },
|
|
5148
|
+
{ codepoint: "\uD83C\uDFF3️", name: "white flag" },
|
|
5149
|
+
{ codepoint: "\uD83C\uDFF4", name: "black flag" },
|
|
5150
|
+
{ codepoint: "\uD83D\uDEA9", name: "triangular flag" },
|
|
5151
|
+
{ codepoint: "\uD83C\uDF8C", name: "crossed flags" },
|
|
5152
|
+
],
|
|
5153
|
+
},
|
|
5154
|
+
];
|
|
5155
|
+
|
|
5156
|
+
/** Renders a custom Discord emoji with animated hover behavior */
|
|
5157
|
+
function CustomEmoji({
|
|
5158
|
+
discordId,
|
|
5159
|
+
name,
|
|
5160
|
+
animated,
|
|
5161
|
+
selected,
|
|
5162
|
+
size = 32,
|
|
5163
|
+
}: {
|
|
5164
|
+
discordId: string;
|
|
5165
|
+
name: string;
|
|
5166
|
+
animated?: boolean;
|
|
5167
|
+
selected?: boolean;
|
|
5168
|
+
size?: number;
|
|
5169
|
+
}) {
|
|
5170
|
+
const [hovered, setHovered] = useState(false);
|
|
5171
|
+
const ext = animated && (hovered || selected) ? "gif" : "png";
|
|
5172
|
+
const url = \`https://cdn.discordapp.com/emojis/\${discordId}.\${ext}?size=\${size}&quality=lossless\`;
|
|
5173
|
+
|
|
5174
|
+
return (
|
|
5175
|
+
<img
|
|
5176
|
+
src={url}
|
|
5177
|
+
alt={name}
|
|
5178
|
+
title={name}
|
|
5179
|
+
width={size}
|
|
5180
|
+
height={size}
|
|
5181
|
+
loading="lazy"
|
|
5182
|
+
className="object-contain"
|
|
5183
|
+
onMouseEnter={() => setHovered(true)}
|
|
5184
|
+
onMouseLeave={() => setHovered(false)}
|
|
5185
|
+
/>
|
|
5186
|
+
);
|
|
5187
|
+
}
|
|
5188
|
+
|
|
5189
|
+
export function EmojiPicker({
|
|
5190
|
+
guildDiscordId,
|
|
5191
|
+
source = "all",
|
|
5192
|
+
mode = "single",
|
|
5193
|
+
maxCount,
|
|
5194
|
+
value,
|
|
5195
|
+
onSelect,
|
|
5196
|
+
onChange,
|
|
5197
|
+
columns = 8,
|
|
5198
|
+
pageSize = 64,
|
|
5199
|
+
placeholder = "Pick emoji...",
|
|
5200
|
+
className,
|
|
5201
|
+
}: EmojiPickerProps) {
|
|
5202
|
+
const [open, setOpen] = useState(false);
|
|
5203
|
+
const [search, setSearch] = useState("");
|
|
5204
|
+
const [selected, setSelected] = useState<EmojiData[]>([]);
|
|
5205
|
+
const [visibleCount, setVisibleCount] = useState(pageSize);
|
|
5206
|
+
const sentinelRef = useRef<HTMLDivElement>(null);
|
|
5207
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
5208
|
+
const searchRef = useRef<HTMLInputElement>(null);
|
|
5209
|
+
|
|
5210
|
+
const currentSelected = value
|
|
5211
|
+
? selected.filter((e) => value.includes(e.id))
|
|
5212
|
+
: selected;
|
|
5213
|
+
|
|
5214
|
+
// Determine which data sources to fetch
|
|
5215
|
+
const needsGuild = source === "all" || source === "guild";
|
|
5216
|
+
const needsApp = source === "all" || source === "application";
|
|
5217
|
+
const needsDefault = source === "all" || source === "default";
|
|
5218
|
+
|
|
5219
|
+
const guildEmojis = useQuery(
|
|
5220
|
+
api.calabasas.queries.listGuildEmojis,
|
|
5221
|
+
needsGuild && guildDiscordId ? { guildDiscordId } : "skip"
|
|
5222
|
+
);
|
|
5223
|
+
const appEmojis = useQuery(
|
|
5224
|
+
api.calabasas.queries.listAppEmojis,
|
|
5225
|
+
needsApp ? {} : "skip"
|
|
5226
|
+
);
|
|
5227
|
+
|
|
5228
|
+
// Build flat emoji list for search and infinite scroll
|
|
5229
|
+
const allEmojis = useMemo(() => {
|
|
5230
|
+
const sections: { header: string; items: EmojiData[] }[] = [];
|
|
5231
|
+
|
|
5232
|
+
if (needsGuild && guildEmojis && guildEmojis.length > 0) {
|
|
5233
|
+
sections.push({
|
|
5234
|
+
header: "Server Emojis",
|
|
5235
|
+
items: guildEmojis.map((e) => ({
|
|
5236
|
+
id: e.discordId,
|
|
5237
|
+
name: e.name,
|
|
5238
|
+
animated: e.animated,
|
|
5239
|
+
source: "guild" as const,
|
|
5240
|
+
})),
|
|
5241
|
+
});
|
|
5242
|
+
}
|
|
5243
|
+
|
|
5244
|
+
if (needsApp && appEmojis && appEmojis.length > 0) {
|
|
5245
|
+
sections.push({
|
|
5246
|
+
header: "App Emojis",
|
|
5247
|
+
items: appEmojis.map((e) => ({
|
|
5248
|
+
id: e.discordId,
|
|
5249
|
+
name: e.name,
|
|
5250
|
+
animated: e.animated,
|
|
5251
|
+
source: "application" as const,
|
|
5252
|
+
})),
|
|
5253
|
+
});
|
|
5254
|
+
}
|
|
5255
|
+
|
|
5256
|
+
if (needsDefault) {
|
|
5257
|
+
for (const cat of DEFAULT_EMOJIS) {
|
|
5258
|
+
sections.push({
|
|
5259
|
+
header: cat.category,
|
|
5260
|
+
items: cat.emojis.map((e) => ({
|
|
5261
|
+
id: e.codepoint,
|
|
5262
|
+
name: e.name,
|
|
5263
|
+
source: "default" as const,
|
|
5264
|
+
})),
|
|
5265
|
+
});
|
|
5266
|
+
}
|
|
5267
|
+
}
|
|
5268
|
+
|
|
5269
|
+
return sections;
|
|
5270
|
+
}, [guildEmojis, appEmojis, needsGuild, needsApp, needsDefault]);
|
|
5271
|
+
|
|
5272
|
+
// Filter by search
|
|
5273
|
+
const filtered = useMemo(() => {
|
|
5274
|
+
if (!search) return allEmojis;
|
|
5275
|
+
const q = search.toLowerCase();
|
|
5276
|
+
const flat = allEmojis.flatMap((s) => s.items).filter((e) =>
|
|
5277
|
+
e.name.toLowerCase().includes(q)
|
|
5278
|
+
);
|
|
5279
|
+
return flat.length > 0 ? [{ header: "", items: flat }] : [];
|
|
5280
|
+
}, [allEmojis, search]);
|
|
5281
|
+
|
|
5282
|
+
// Flatten for counting
|
|
5283
|
+
const totalCount = useMemo(
|
|
5284
|
+
() => filtered.reduce((acc, s) => acc + s.items.length, 0),
|
|
5285
|
+
[filtered]
|
|
5286
|
+
);
|
|
5287
|
+
|
|
5288
|
+
// Intersection observer for infinite scroll
|
|
5289
|
+
useEffect(() => {
|
|
5290
|
+
const sentinel = sentinelRef.current;
|
|
5291
|
+
if (!sentinel) return;
|
|
5292
|
+
|
|
5293
|
+
const observer = new IntersectionObserver(
|
|
5294
|
+
([entry]) => {
|
|
5295
|
+
if (entry.isIntersecting && visibleCount < totalCount) {
|
|
5296
|
+
setVisibleCount((prev) => prev + pageSize);
|
|
5297
|
+
}
|
|
5298
|
+
},
|
|
5299
|
+
{ root: scrollRef.current, threshold: 0 }
|
|
5300
|
+
);
|
|
5301
|
+
|
|
5302
|
+
observer.observe(sentinel);
|
|
5303
|
+
return () => observer.disconnect();
|
|
5304
|
+
}, [visibleCount, totalCount, pageSize]);
|
|
5305
|
+
|
|
5306
|
+
// Reset visible count on search change
|
|
5307
|
+
useEffect(() => {
|
|
5308
|
+
setVisibleCount(pageSize);
|
|
5309
|
+
}, [search, pageSize]);
|
|
5310
|
+
|
|
5311
|
+
// Auto-focus search on open
|
|
5312
|
+
useEffect(() => {
|
|
5313
|
+
if (open) {
|
|
5314
|
+
setTimeout(() => searchRef.current?.focus(), 0);
|
|
5315
|
+
} else {
|
|
5316
|
+
setSearch("");
|
|
5317
|
+
}
|
|
5318
|
+
}, [open]);
|
|
5319
|
+
|
|
5320
|
+
const isSelected = useCallback(
|
|
5321
|
+
(id: string) => {
|
|
5322
|
+
if (value) return value.includes(id);
|
|
5323
|
+
return currentSelected.some((e) => e.id === id);
|
|
5324
|
+
},
|
|
5325
|
+
[value, currentSelected]
|
|
5326
|
+
);
|
|
5327
|
+
|
|
5328
|
+
const handleSelect = useCallback(
|
|
5329
|
+
(emoji: EmojiData) => {
|
|
5330
|
+
if (mode === "single") {
|
|
5331
|
+
setSelected([emoji]);
|
|
5332
|
+
onSelect?.(emoji);
|
|
5333
|
+
onChange?.([emoji]);
|
|
5334
|
+
setOpen(false);
|
|
5335
|
+
return;
|
|
5336
|
+
}
|
|
5337
|
+
|
|
5338
|
+
// Multi mode
|
|
5339
|
+
const alreadySelected = isSelected(emoji.id);
|
|
5340
|
+
let next: EmojiData[];
|
|
5341
|
+
|
|
5342
|
+
if (alreadySelected) {
|
|
5343
|
+
next = currentSelected.filter((e) => e.id !== emoji.id);
|
|
5344
|
+
} else {
|
|
5345
|
+
if (maxCount && currentSelected.length >= maxCount) return;
|
|
5346
|
+
next = [...currentSelected, emoji];
|
|
5347
|
+
}
|
|
5348
|
+
|
|
5349
|
+
setSelected(next);
|
|
5350
|
+
onSelect?.(emoji);
|
|
5351
|
+
onChange?.(next);
|
|
5352
|
+
},
|
|
5353
|
+
[mode, maxCount, currentSelected, isSelected, onSelect, onChange]
|
|
5354
|
+
);
|
|
5355
|
+
|
|
5356
|
+
const handleClear = useCallback(() => {
|
|
5357
|
+
setSelected([]);
|
|
5358
|
+
onChange?.([]);
|
|
5359
|
+
}, [onChange]);
|
|
5360
|
+
|
|
5361
|
+
const atMax = mode === "multi" && maxCount !== undefined && currentSelected.length >= maxCount;
|
|
5362
|
+
|
|
5363
|
+
// Render grid items with virtual slicing
|
|
5364
|
+
let rendered = 0;
|
|
5365
|
+
const gridStyle = { gridTemplateColumns: \`repeat(\${columns}, 1fr)\` };
|
|
5366
|
+
|
|
5367
|
+
// Trigger display
|
|
5368
|
+
const renderTrigger = () => {
|
|
5369
|
+
if (currentSelected.length === 0) return placeholder;
|
|
5370
|
+
|
|
5371
|
+
if (mode === "single" && currentSelected.length === 1) {
|
|
5372
|
+
const emoji = currentSelected[0];
|
|
5373
|
+
if (emoji.source === "default") {
|
|
5374
|
+
return <span className="text-xl">{emoji.id}</span>;
|
|
5375
|
+
}
|
|
5376
|
+
return (
|
|
5377
|
+
<CustomEmoji
|
|
5378
|
+
discordId={emoji.id}
|
|
5379
|
+
name={emoji.name}
|
|
5380
|
+
animated={emoji.animated}
|
|
5381
|
+
selected
|
|
5382
|
+
size={20}
|
|
5383
|
+
/>
|
|
5384
|
+
);
|
|
5385
|
+
}
|
|
5386
|
+
|
|
5387
|
+
// Multi mode
|
|
5388
|
+
const shown = currentSelected.slice(0, 5);
|
|
5389
|
+
const extra = currentSelected.length - shown.length;
|
|
5390
|
+
return (
|
|
5391
|
+
<span className="flex items-center gap-1">
|
|
5392
|
+
{shown.map((emoji) =>
|
|
5393
|
+
emoji.source === "default" ? (
|
|
5394
|
+
<span key={emoji.id} className="text-base">{emoji.id}</span>
|
|
5395
|
+
) : (
|
|
5396
|
+
<CustomEmoji
|
|
5397
|
+
key={emoji.id}
|
|
5398
|
+
discordId={emoji.id}
|
|
5399
|
+
name={emoji.name}
|
|
5400
|
+
animated={emoji.animated}
|
|
5401
|
+
selected
|
|
5402
|
+
size={18}
|
|
5403
|
+
/>
|
|
5404
|
+
)
|
|
5405
|
+
)}
|
|
5406
|
+
{extra > 0 && (
|
|
5407
|
+
<span className="text-xs text-muted-foreground ml-1">
|
|
5408
|
+
+{extra}
|
|
5409
|
+
</span>
|
|
5410
|
+
)}
|
|
5411
|
+
</span>
|
|
5412
|
+
);
|
|
5413
|
+
};
|
|
5414
|
+
|
|
5415
|
+
return (
|
|
5416
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
5417
|
+
<PopoverTrigger asChild>
|
|
5418
|
+
<Button
|
|
5419
|
+
variant="outline"
|
|
5420
|
+
role="combobox"
|
|
5421
|
+
aria-expanded={open}
|
|
5422
|
+
className={cn("w-[280px] justify-between", className)}
|
|
5423
|
+
>
|
|
5424
|
+
{renderTrigger()}
|
|
5425
|
+
<span className="ml-2 text-lg opacity-50">\uD83D\uDE00</span>
|
|
5426
|
+
</Button>
|
|
5427
|
+
</PopoverTrigger>
|
|
5428
|
+
<PopoverContent className="w-[352px] p-0" align="start">
|
|
5429
|
+
{/* Search */}
|
|
5430
|
+
<div className="flex items-center gap-2 border-b px-3 py-2">
|
|
5431
|
+
<svg
|
|
5432
|
+
className="h-4 w-4 shrink-0 opacity-50"
|
|
5433
|
+
fill="none"
|
|
5434
|
+
stroke="currentColor"
|
|
5435
|
+
viewBox="0 0 24 24"
|
|
5436
|
+
>
|
|
5437
|
+
<path
|
|
5438
|
+
strokeLinecap="round"
|
|
5439
|
+
strokeLinejoin="round"
|
|
5440
|
+
strokeWidth={2}
|
|
5441
|
+
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
|
5442
|
+
/>
|
|
5443
|
+
</svg>
|
|
5444
|
+
<input
|
|
5445
|
+
ref={searchRef}
|
|
5446
|
+
type="text"
|
|
5447
|
+
placeholder="Search emojis..."
|
|
5448
|
+
value={search}
|
|
5449
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
5450
|
+
className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
5451
|
+
/>
|
|
5452
|
+
{search && (
|
|
5453
|
+
<button
|
|
5454
|
+
onClick={() => setSearch("")}
|
|
5455
|
+
className="text-muted-foreground hover:text-foreground"
|
|
5456
|
+
>
|
|
5457
|
+
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
5458
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
5459
|
+
</svg>
|
|
5460
|
+
</button>
|
|
5461
|
+
)}
|
|
5462
|
+
</div>
|
|
5463
|
+
|
|
5464
|
+
{/* Scrollable emoji grid */}
|
|
5465
|
+
<div
|
|
5466
|
+
ref={scrollRef}
|
|
5467
|
+
className="max-h-[400px] overflow-y-auto p-2"
|
|
5468
|
+
>
|
|
5469
|
+
{filtered.length === 0 && (
|
|
5470
|
+
<div className="py-8 text-center text-sm text-muted-foreground">
|
|
5471
|
+
No emojis found
|
|
5472
|
+
</div>
|
|
5473
|
+
)}
|
|
5474
|
+
|
|
5475
|
+
{filtered.map((section) => {
|
|
5476
|
+
if (rendered >= visibleCount) return null;
|
|
5477
|
+
|
|
5478
|
+
const remaining = visibleCount - rendered;
|
|
5479
|
+
const items = section.items.slice(0, remaining);
|
|
5480
|
+
rendered += items.length;
|
|
5481
|
+
|
|
5482
|
+
return (
|
|
5483
|
+
<div key={section.header || "search-results"}>
|
|
5484
|
+
{section.header && (
|
|
5485
|
+
<div className="sticky top-0 z-10 bg-popover px-1 py-1.5 text-xs font-semibold text-muted-foreground">
|
|
5486
|
+
{section.header}
|
|
5487
|
+
</div>
|
|
5488
|
+
)}
|
|
5489
|
+
<div className="grid gap-0.5" style={gridStyle}>
|
|
5490
|
+
{items.map((emoji) => {
|
|
5491
|
+
const sel = isSelected(emoji.id);
|
|
5492
|
+
const disabled = !sel && atMax;
|
|
5493
|
+
|
|
5494
|
+
return (
|
|
5495
|
+
<button
|
|
5496
|
+
key={\`\${emoji.source}-\${emoji.id}\`}
|
|
5497
|
+
onClick={() => !disabled && handleSelect(emoji)}
|
|
5498
|
+
title={emoji.name}
|
|
5499
|
+
className={cn(
|
|
5500
|
+
"flex items-center justify-center rounded-md p-1 h-9 w-full transition-colors",
|
|
5501
|
+
sel
|
|
5502
|
+
? "ring-2 ring-primary bg-accent/50"
|
|
5503
|
+
: "hover:bg-accent",
|
|
5504
|
+
disabled && "opacity-50 cursor-not-allowed"
|
|
5505
|
+
)}
|
|
5506
|
+
>
|
|
5507
|
+
{emoji.source === "default" ? (
|
|
5508
|
+
<span className="text-xl leading-none">{emoji.id}</span>
|
|
5509
|
+
) : (
|
|
5510
|
+
<CustomEmoji
|
|
5511
|
+
discordId={emoji.id}
|
|
5512
|
+
name={emoji.name}
|
|
5513
|
+
animated={emoji.animated}
|
|
5514
|
+
selected={sel}
|
|
5515
|
+
size={28}
|
|
5516
|
+
/>
|
|
5517
|
+
)}
|
|
5518
|
+
</button>
|
|
5519
|
+
);
|
|
5520
|
+
})}
|
|
5521
|
+
</div>
|
|
5522
|
+
</div>
|
|
5523
|
+
);
|
|
5524
|
+
})}
|
|
5525
|
+
|
|
5526
|
+
{/* Sentinel for infinite scroll */}
|
|
5527
|
+
<div ref={sentinelRef} className="h-1" />
|
|
5528
|
+
</div>
|
|
5529
|
+
|
|
5530
|
+
{/* Multi-select footer */}
|
|
5531
|
+
{mode === "multi" && (
|
|
5532
|
+
<div className="flex items-center justify-between border-t px-3 py-2 text-xs text-muted-foreground">
|
|
5533
|
+
<span>
|
|
5534
|
+
Selected: {currentSelected.length}
|
|
5535
|
+
{maxCount !== undefined && \` / \${maxCount}\`}
|
|
5536
|
+
</span>
|
|
5537
|
+
{currentSelected.length > 0 && (
|
|
5538
|
+
<button
|
|
5539
|
+
onClick={handleClear}
|
|
5540
|
+
className="text-xs text-muted-foreground hover:text-foreground underline"
|
|
5541
|
+
>
|
|
5542
|
+
Clear
|
|
5543
|
+
</button>
|
|
5544
|
+
)}
|
|
5545
|
+
</div>
|
|
5546
|
+
)}
|
|
5547
|
+
</PopoverContent>
|
|
5548
|
+
</Popover>
|
|
5549
|
+
);
|
|
5550
|
+
}
|
|
5551
|
+
`,
|
|
5552
|
+
generateConvexQueries: () => `export const listGuildEmojis = query({
|
|
5553
|
+
args: { guildDiscordId: v.string() },
|
|
5554
|
+
returns: v.array(
|
|
5555
|
+
v.object({
|
|
5556
|
+
discordId: v.string(),
|
|
5557
|
+
name: v.string(),
|
|
5558
|
+
animated: v.boolean(),
|
|
5559
|
+
available: v.boolean(),
|
|
5560
|
+
})
|
|
5561
|
+
),
|
|
5562
|
+
handler: async (ctx, { guildDiscordId }) => {
|
|
5563
|
+
const emojis = await ctx.db
|
|
5564
|
+
.query("calabasasEmojis")
|
|
5565
|
+
.withIndex("by_guild", (q) => q.eq("guildDiscordId", guildDiscordId))
|
|
5566
|
+
.collect();
|
|
5567
|
+
return emojis
|
|
5568
|
+
.filter((e) => e.available)
|
|
5569
|
+
.map((e) => ({
|
|
5570
|
+
discordId: e.discordId,
|
|
5571
|
+
name: e.name,
|
|
5572
|
+
animated: e.animated,
|
|
5573
|
+
available: e.available,
|
|
5574
|
+
}));
|
|
5575
|
+
},
|
|
5576
|
+
});
|
|
5577
|
+
|
|
5578
|
+
export const listAppEmojis = query({
|
|
5579
|
+
args: {},
|
|
5580
|
+
returns: v.array(
|
|
5581
|
+
v.object({
|
|
5582
|
+
discordId: v.string(),
|
|
5583
|
+
name: v.string(),
|
|
5584
|
+
animated: v.boolean(),
|
|
5585
|
+
available: v.boolean(),
|
|
5586
|
+
})
|
|
5587
|
+
),
|
|
5588
|
+
handler: async (ctx) => {
|
|
5589
|
+
const emojis = await ctx.db
|
|
5590
|
+
.query("calabasasAppEmojis")
|
|
5591
|
+
.collect();
|
|
5592
|
+
return emojis
|
|
5593
|
+
.filter((e) => e.available)
|
|
5594
|
+
.map((e) => ({
|
|
5595
|
+
discordId: e.discordId,
|
|
5596
|
+
name: e.name,
|
|
5597
|
+
animated: e.animated,
|
|
5598
|
+
available: e.available,
|
|
5599
|
+
}));
|
|
5600
|
+
},
|
|
5601
|
+
});`
|
|
5602
|
+
};
|
|
5603
|
+
|
|
4732
5604
|
// src/lib/registry/index.ts
|
|
4733
5605
|
var REGISTRY = [
|
|
4734
5606
|
channelSelect,
|
|
@@ -4741,7 +5613,8 @@ var REGISTRY = [
|
|
|
4741
5613
|
memberCard,
|
|
4742
5614
|
channelTree,
|
|
4743
5615
|
memberRoster,
|
|
4744
|
-
useOnlineCount
|
|
5616
|
+
useOnlineCount,
|
|
5617
|
+
emojiPicker
|
|
4745
5618
|
];
|
|
4746
5619
|
function getComponent(name) {
|
|
4747
5620
|
return REGISTRY.find((c) => c.name === name);
|
|
@@ -4764,6 +5637,8 @@ function parseEnabledSyncTypes(configPath) {
|
|
|
4764
5637
|
enabled.add("members");
|
|
4765
5638
|
if (/presence:\s*true/.test(syncContent))
|
|
4766
5639
|
enabled.add("presence");
|
|
5640
|
+
if (/emojis:\s*true/.test(syncContent))
|
|
5641
|
+
enabled.add("emojis");
|
|
4767
5642
|
}
|
|
4768
5643
|
return enabled;
|
|
4769
5644
|
}
|
|
@@ -4925,7 +5800,15 @@ const count = ${camel}("123456789");`;
|
|
|
4925
5800
|
"member-card"
|
|
4926
5801
|
]);
|
|
4927
5802
|
const isDisplay = displayComponents.has(firstInstalled.name);
|
|
4928
|
-
|
|
5803
|
+
const isEmojiPicker = firstInstalled.name === "emoji-picker";
|
|
5804
|
+
if (isEmojiPicker) {
|
|
5805
|
+
usage = `import { ${pascal} } from "@/components/calabasas/${firstInstalled.name}";
|
|
5806
|
+
|
|
5807
|
+
<${pascal}
|
|
5808
|
+
guildDiscordId="123456789"
|
|
5809
|
+
onSelect={(emoji) => console.log(emoji)}
|
|
5810
|
+
/>`;
|
|
5811
|
+
} else if (isDisplay) {
|
|
4929
5812
|
const propsMap = {
|
|
4930
5813
|
"role-badge": ` guildDiscordId="123456789"
|
|
4931
5814
|
roleIds={["role_id_1", "role_id_2"]}`,
|
|
@@ -5329,7 +6212,7 @@ async function botList(options) {
|
|
|
5329
6212
|
const React = reactModule.default ?? reactModule;
|
|
5330
6213
|
const { render } = await import("./index-4rn9k8et.js");
|
|
5331
6214
|
const { createConvexClient, ConvexProvider } = await import("./convex-1z1jsz1n.js");
|
|
5332
|
-
const { BotList } = await import("./BotList-
|
|
6215
|
+
const { BotList } = await import("./BotList-gmtf52xh.js");
|
|
5333
6216
|
const convexUrl = getConvexUrl(env);
|
|
5334
6217
|
const client = createConvexClient(convexUrl);
|
|
5335
6218
|
const { waitUntilExit } = render(React.createElement(ConvexProvider, {
|
|
@@ -5359,7 +6242,8 @@ Your Discord bots:
|
|
|
5359
6242
|
console.log(`${statusDot} ${pc.bold(bot.name)}`);
|
|
5360
6243
|
console.log(` ${pc.dim("ID:")} ${bot._id}`);
|
|
5361
6244
|
console.log(` ${pc.dim("Discord App:")} ${bot.discordAppId}`);
|
|
5362
|
-
|
|
6245
|
+
const statusText = (bot.status === "disconnected" || bot.status === "error") && bot.disconnectReason ? `${statusColor(bot.status)} ${pc.dim("—")} ${pc.dim(bot.disconnectReason)}` : statusColor(bot.status);
|
|
6246
|
+
console.log(` ${pc.dim("Status:")} ${statusText}`);
|
|
5363
6247
|
console.log(` ${pc.dim("Env:")} ${bot.environment}`);
|
|
5364
6248
|
console.log("");
|
|
5365
6249
|
}
|
|
@@ -6149,7 +7033,7 @@ Run ${pc3.cyan("`calabasas init`")} first.`);
|
|
|
6149
7033
|
|
|
6150
7034
|
// src/index.ts
|
|
6151
7035
|
var dashboard = async (...args) => {
|
|
6152
|
-
const mod = await import("./dashboard-
|
|
7036
|
+
const mod = await import("./dashboard-y75f0avj.js");
|
|
6153
7037
|
return mod.dashboard(args[0]);
|
|
6154
7038
|
};
|
|
6155
7039
|
var logs = async (...args) => {
|