@malloy-publisher/sdk 0.0.198-dev → 0.0.198-dev1

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.
Files changed (45) hide show
  1. package/dist/{ServerProvider-DDScRRDc.es.js → ServerProvider-BuM1usxf.es.js} +181 -177
  2. package/dist/{ServerProvider-IhQ4aYBm.cjs.js → ServerProvider-C_Mnvmgc.cjs.js} +1 -1
  3. package/dist/client/api.d.ts +61 -4
  4. package/dist/client/index.cjs.js +1 -1
  5. package/dist/client/index.es.js +1 -1
  6. package/dist/components/Package/ContentTypeIcon.d.ts +16 -0
  7. package/dist/components/Package/index.d.ts +0 -1
  8. package/dist/components/styles.d.ts +16 -0
  9. package/dist/{core-w79IMXAG.es.js → core-DfcpQGVP.es.js} +1 -1
  10. package/dist/{core-7-3Jcsb0.cjs.js → core-yDgxkpo0.cjs.js} +1 -1
  11. package/dist/index-CMA8U4-B.cjs.js +228 -0
  12. package/dist/{index-CN0_kZSF.es.js → index-Y4ooZDYA.es.js} +17654 -20603
  13. package/dist/index.cjs.js +1 -1
  14. package/dist/index.es.js +33 -34
  15. package/package.json +5 -5
  16. package/src/components/Environment/AddPackageDialog.tsx +116 -79
  17. package/src/components/Environment/DeletePackageDialog.tsx +3 -2
  18. package/src/components/Environment/Environment.tsx +44 -23
  19. package/src/components/Environment/Packages.tsx +164 -156
  20. package/src/components/Home/DeleteEnvironmentDialog.tsx +3 -2
  21. package/src/components/Home/Home.tsx +272 -389
  22. package/src/components/Model/Model.tsx +2 -2
  23. package/src/components/Model/ModelCell.tsx +1 -1
  24. package/src/components/Model/ModelExplorerDialog.tsx +1 -1
  25. package/src/components/Model/SourcesExplorer.tsx +4 -4
  26. package/src/components/Notebook/Notebook.tsx +4 -9
  27. package/src/components/Notebook/NotebookCell.tsx +10 -7
  28. package/src/components/Package/ContentTypeIcon.tsx +79 -0
  29. package/src/components/Package/Package.tsx +387 -55
  30. package/src/components/Package/index.ts +0 -1
  31. package/src/components/QueryResult/QueryResult.tsx +1 -1
  32. package/src/components/RenderedResult/RenderedResult.tsx +9 -8
  33. package/src/components/ResultsDialog.tsx +1 -1
  34. package/src/components/styles.ts +28 -15
  35. package/dist/components/Package/Config.d.ts +0 -5
  36. package/dist/components/Package/Databases.d.ts +0 -5
  37. package/dist/components/Package/FileTreeView.d.ts +0 -9
  38. package/dist/components/Package/Models.d.ts +0 -6
  39. package/dist/components/Package/Notebooks.d.ts +0 -6
  40. package/dist/index-Xo_ADux9.cjs.js +0 -233
  41. package/src/components/Package/Config.tsx +0 -97
  42. package/src/components/Package/Databases.tsx +0 -228
  43. package/src/components/Package/FileTreeView.tsx +0 -241
  44. package/src/components/Package/Models.tsx +0 -68
  45. package/src/components/Package/Notebooks.tsx +0 -77
@@ -1,23 +1,17 @@
1
1
  import { MoreVert } from "@mui/icons-material";
2
- import AnalyticsRoundedIcon from "@mui/icons-material/AnalyticsRounded";
3
- import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded";
4
- import AutoAwesomeRoundedIcon from "@mui/icons-material/AutoAwesomeRounded";
5
- import CodeRoundedIcon from "@mui/icons-material/CodeRounded";
6
- import ExploreRoundedIcon from "@mui/icons-material/ExploreRounded";
7
- import PsychologyRoundedIcon from "@mui/icons-material/PsychologyRounded";
8
- import StorageRoundedIcon from "@mui/icons-material/StorageRounded";
2
+ import FolderOutlinedIcon from "@mui/icons-material/FolderOutlined";
9
3
  import {
10
4
  Box,
11
5
  Button,
12
6
  Card,
13
7
  CardContent,
14
- Chip,
15
8
  Container,
16
9
  Divider,
17
10
  Grid,
18
11
  IconButton,
19
12
  Menu,
20
13
  Stack,
14
+ Tooltip,
21
15
  Typography,
22
16
  } from "@mui/material";
23
17
  import { useState } from "react";
@@ -35,6 +29,24 @@ interface HomeProps {
35
29
  onClickEnvironment?: (to: string, event?: React.MouseEvent) => void;
36
30
  }
37
31
 
32
+ const FEATURES: Array<{ title: string; body: string; href: string }> = [
33
+ {
34
+ title: "Ad-hoc analysis",
35
+ body: "Browse semantic sources, build queries, and run nested logic in Explorer — no code.",
36
+ href: "https://github.com/malloydata/publisher/blob/main/README.md#ad-hoc-data-analysis",
37
+ },
38
+ {
39
+ title: "Notebook dashboards",
40
+ body: "Code-first dashboards using Malloy notebooks. Versioned alongside your models.",
41
+ href: "https://github.com/malloydata/publisher/blob/main/README.md#notebook-based-dashboards",
42
+ },
43
+ {
44
+ title: "AI data agents",
45
+ body: "Expose models via MCP so agents can discover sources and ask well-formed questions.",
46
+ href: "https://github.com/malloydata/publisher/blob/main/README.md#mcp-based-ai-data-agents",
47
+ },
48
+ ];
49
+
38
50
  export default function Home({ onClickEnvironment }: HomeProps) {
39
51
  const { apiClients, mutable } = useServer();
40
52
 
@@ -47,422 +59,293 @@ export default function Home({ onClickEnvironment }: HomeProps) {
47
59
  return <ApiErrorDisplay error={error} context="Environments List" />;
48
60
  }
49
61
 
50
- if (isSuccess) {
51
- return (
52
- <Container maxWidth="lg" sx={{ py: 4 }}>
53
- {/* Hero Section */}
54
- <Box sx={{ textAlign: "center", mb: 6 }}>
62
+ if (!isSuccess) {
63
+ return <Loading text="Loading environments..." />;
64
+ }
65
+
66
+ const environments = data.data ?? [];
67
+
68
+ return (
69
+ <Container maxWidth="md" sx={{ py: 6 }}>
70
+ <Box sx={{ mb: 5 }}>
71
+ <Typography
72
+ variant="h3"
73
+ component="h1"
74
+ sx={{ fontWeight: 500, letterSpacing: "-0.025em", mb: 1 }}
75
+ >
76
+ Publisher
77
+ </Typography>
78
+ <Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
79
+ The open-source semantic model server for the Malloy data
80
+ language.
81
+ </Typography>
82
+ <Typography
83
+ variant="body2"
84
+ color="text.secondary"
85
+ sx={{ maxWidth: 720, lineHeight: 1.6 }}
86
+ >
87
+ Define semantic models once — and use them everywhere. Publisher
88
+ serves Malloy models through clean APIs, enabling consistent,
89
+ interpretable, and AI-ready data access for tools, applications,
90
+ and agents.
91
+ </Typography>
92
+ </Box>
93
+
94
+ <Grid container spacing={4} sx={{ mb: 5 }}>
95
+ {FEATURES.map((feature) => (
96
+ <Grid size={{ xs: 12, md: 4 }} key={feature.title}>
97
+ <Stack spacing={1}>
98
+ <Typography
99
+ variant="body2"
100
+ component="a"
101
+ href={feature.href}
102
+ target="_blank"
103
+ rel="noopener noreferrer"
104
+ sx={{
105
+ fontWeight: 500,
106
+ color: "text.primary",
107
+ textDecoration: "none",
108
+ "&:hover": { textDecoration: "underline" },
109
+ }}
110
+ >
111
+ {feature.title}
112
+ </Typography>
113
+ <Typography
114
+ variant="body2"
115
+ color="text.secondary"
116
+ sx={{ lineHeight: 1.6 }}
117
+ >
118
+ {feature.body}
119
+ </Typography>
120
+ </Stack>
121
+ </Grid>
122
+ ))}
123
+ </Grid>
124
+
125
+ <Divider sx={{ my: 4 }} />
126
+
127
+ {environments.length > 0 ? (
128
+ <Box sx={{ mb: 4 }}>
55
129
  <Stack
56
130
  direction="row"
57
- justifyContent="center"
58
- alignItems="center"
59
- spacing={1}
60
- sx={{ mb: 2 }}
131
+ justifyContent="space-between"
132
+ alignItems="flex-start"
133
+ sx={{ mb: 3 }}
61
134
  >
62
- <AutoAwesomeRoundedIcon
63
- sx={{ fontSize: 32, color: "primary.main" }}
64
- />
65
- <Typography variant="h3" component="h1" fontWeight={700}>
66
- Publisher
67
- </Typography>
135
+ <Box>
136
+ <Typography
137
+ variant="h5"
138
+ sx={{
139
+ fontWeight: 500,
140
+ letterSpacing: "-0.025em",
141
+ mb: 0.5,
142
+ }}
143
+ >
144
+ Environments
145
+ </Typography>
146
+ <Typography variant="body2" color="text.secondary">
147
+ Published environments available on this server
148
+ </Typography>
149
+ </Box>
150
+ {mutable && <AddEnvironmentDialog />}
68
151
  </Stack>
152
+ <Grid container spacing={2}>
153
+ {environments.map((environment) => (
154
+ <Grid
155
+ size={{ xs: 12, sm: 6, md: 4 }}
156
+ key={environment.name}
157
+ >
158
+ <EnvironmentCard
159
+ environment={environment}
160
+ onClickEnvironment={onClickEnvironment}
161
+ />
162
+ </Grid>
163
+ ))}
164
+ </Grid>
165
+ </Box>
166
+ ) : (
167
+ <Box sx={{ mb: 4 }}>
69
168
  <Typography
70
169
  variant="h5"
71
- color="text.secondary"
72
- sx={{ mb: 3, maxWidth: 600, mx: "auto" }}
170
+ sx={{ fontWeight: 500, letterSpacing: "-0.025em", mb: 1 }}
73
171
  >
74
- The open-source semantic model server for the Malloy data
75
- language
172
+ Get started
76
173
  </Typography>
77
174
  <Typography
78
- variant="body1"
175
+ variant="body2"
79
176
  color="text.secondary"
80
- sx={{ maxWidth: 800, mx: "auto" }}
177
+ sx={{ mb: 3, maxWidth: 600 }}
81
178
  >
82
- Define semantic models once and use them everywhere.
83
- Publisher serves Malloy models through clean APIs, enabling
84
- consistent, interpretable, and AI-ready data access for tools,
85
- applications, and agents.
179
+ Create your first Malloy environment to start exploring
180
+ semantic models and building data experiences.
86
181
  </Typography>
87
- </Box>
88
-
89
- {/* Feature Cards */}
90
- <Grid container spacing={3} sx={{ mb: 6 }}>
91
- <Grid size={{ xs: 12, md: 4 }}>
92
- <Card
93
- variant="outlined"
94
- onClick={() => {
95
- window.open(
96
- "https://github.com/malloydata/publisher/blob/main/README.md#ad-hoc-data-analysis",
97
- "_blank",
98
- );
99
- }}
100
- sx={{
101
- height: "100%",
102
- cursor: "pointer",
103
- transition: "all 0.2s ease",
104
- "&:hover": {
105
- transform: "translateY(-2px)",
106
- boxShadow: 2,
107
- },
108
- }}
109
- >
110
- <CardContent sx={{ p: 3 }}>
111
- <Stack
112
- direction="row"
113
- alignItems="center"
114
- spacing={1}
115
- sx={{ mb: 2 }}
116
- >
117
- <AnalyticsRoundedIcon
118
- sx={{ color: "info.main", fontSize: 28 }}
119
- />
120
- <Typography variant="h6" fontWeight={600}>
121
- Ad Hoc Analysis
122
- </Typography>
123
- </Stack>
124
- <Typography
125
- variant="body2"
126
- color="text.secondary"
127
- sx={{ mb: 2 }}
128
- >
129
- Use Explorer, a visual query builder that allows
130
- analysts to browse semantic sources, build queries,
131
- and run nested logic — all without writing code.
132
- </Typography>
133
- <Chip
134
- label="No-code"
135
- size="small"
136
- color="primary"
137
- variant="outlined"
138
- />
139
- </CardContent>
140
- </Card>
141
- </Grid>
142
-
143
- <Grid size={{ xs: 12, md: 4 }}>
144
- <Card
145
- variant="outlined"
146
- onClick={() => {
147
- window.open(
148
- "https://github.com/malloydata/publisher/blob/main/README.md#notebook-based-dashboards",
149
- "_blank",
150
- );
151
- }}
152
- sx={{
153
- height: "100%",
154
- cursor: "pointer",
155
- transition: "all 0.2s ease",
156
- "&:hover": {
157
- transform: "translateY(-2px)",
158
- boxShadow: 2,
159
- },
160
- }}
161
- >
162
- <CardContent sx={{ p: 3 }}>
163
- <Stack
164
- direction="row"
165
- alignItems="center"
166
- spacing={1}
167
- sx={{ mb: 2 }}
168
- >
169
- <CodeRoundedIcon
170
- sx={{ color: "warning.main", fontSize: 28 }}
171
- />
172
- <Typography variant="h6" fontWeight={600}>
173
- Notebook Dashboards
174
- </Typography>
175
- </Stack>
176
- <Typography
177
- variant="body2"
178
- color="text.secondary"
179
- sx={{ mb: 2 }}
180
- >
181
- Create shareable, code-first dashboards using Malloy
182
- notebooks. Include text, charts, and reusable views —
183
- all versioned alongside your models.
184
- </Typography>
185
- <Chip
186
- label="Versioned"
187
- size="small"
188
- color="warning"
189
- variant="outlined"
190
- />
191
- </CardContent>
192
- </Card>
193
- </Grid>
194
-
195
- <Grid size={{ xs: 12, md: 4 }}>
196
- <Card
197
- variant="outlined"
198
- onClick={() => {
199
- window.open(
200
- "https://github.com/malloydata/publisher/blob/main/README.md#mcp-based-ai-data-agents",
201
- "_blank",
202
- );
203
- }}
204
- sx={{
205
- height: "100%",
206
- cursor: "pointer",
207
- transition: "all 0.2s ease",
208
- "&:hover": {
209
- transform: "translateY(-2px)",
210
- boxShadow: 2,
211
- },
212
- }}
182
+ {mutable ? (
183
+ <AddEnvironmentDialog />
184
+ ) : (
185
+ <Button
186
+ variant="contained"
187
+ color="primary"
188
+ href="https://github.com/malloydata/publisher/blob/main/README.md#server-configuration"
189
+ target="_blank"
190
+ rel="noopener noreferrer"
213
191
  >
214
- <CardContent sx={{ p: 3 }}>
215
- <Stack
216
- direction="row"
217
- alignItems="center"
218
- spacing={1}
219
- sx={{ mb: 2 }}
220
- >
221
- <PsychologyRoundedIcon
222
- sx={{ color: "success.main", fontSize: 28 }}
223
- />
224
- <Typography variant="h6" fontWeight={600}>
225
- AI Data Agents
226
- </Typography>
227
- </Stack>
228
- <Typography
229
- variant="body2"
230
- color="text.secondary"
231
- sx={{ mb: 2 }}
232
- >
233
- Expose your semantic models via the Model Context
234
- Protocol (MCP), enabling AI agents to discover
235
- sources and ask well-formed questions.
236
- </Typography>
237
- <Chip
238
- label="AI-Ready"
239
- size="small"
240
- color="success"
241
- variant="outlined"
242
- />
243
- </CardContent>
244
- </Card>
245
- </Grid>
246
- </Grid>
247
-
248
- <Divider sx={{ my: 4 }} />
192
+ Learn how to create models
193
+ </Button>
194
+ )}
195
+ </Box>
196
+ )}
249
197
 
250
- {/* Environment Selection Section */}
251
- {data.data.length > 0 ? (
252
- <>
253
- <Box sx={{ textAlign: "center", mb: 4 }}>
254
- <Stack
255
- direction="row"
256
- justifyContent="center"
257
- alignItems="center"
258
- spacing={1}
259
- sx={{ mb: 2 }}
260
- >
261
- <StorageRoundedIcon
262
- sx={{ color: "primary.main", fontSize: 24 }}
263
- />
264
- <Typography variant="h4" fontWeight={600}>
265
- Select an Environment
266
- </Typography>
267
- </Stack>
268
- <Typography variant="body1" color="text.secondary">
269
- Choose an environment to explore its semantic models and
270
- start analyzing your data
271
- </Typography>
272
- {mutable && <AddEnvironmentDialog />}
273
- </Box>
274
- <Grid container spacing={3} justifyContent="center">
275
- {data.data.map((environment) => (
276
- <Grid
277
- size={{ xs: 12, sm: 6, md: 4 }}
278
- key={environment.name}
279
- >
280
- <EnvironmentCard
281
- environment={environment}
282
- onClickEnvironment={onClickEnvironment}
283
- />
284
- </Grid>
285
- ))}
286
- </Grid>
287
- </>
288
- ) : (
289
- <Box sx={{ textAlign: "center", mb: 4 }}>
290
- <Stack
291
- direction="row"
292
- justifyContent="center"
293
- alignItems="center"
294
- spacing={1}
295
- sx={{ mb: 2 }}
296
- >
297
- <StorageRoundedIcon
298
- sx={{ color: "primary.main", fontSize: 24 }}
299
- />
300
- <Typography variant="h4" fontWeight={600}>
301
- Get Started
302
- </Typography>
303
- </Stack>
304
- <Typography
305
- variant="body1"
306
- color="text.secondary"
307
- sx={{ mb: 3 }}
308
- >
309
- No environments found. Create your first Malloy environment
310
- to start exploring semantic models and building data
311
- experiences.
312
- </Typography>
313
- {mutable ? (
314
- <AddEnvironmentDialog />
315
- ) : (
316
- <Button
317
- variant="contained"
318
- size="large"
319
- color="primary"
320
- startIcon={<AutoAwesomeRoundedIcon />}
321
- href="https://github.com/malloydata/publisher/blob/main/README.md#server-configuration"
322
- target="_blank"
323
- rel="noopener noreferrer"
324
- >
325
- Learn How to Create Models
326
- </Button>
327
- )}
328
- </Box>
329
- )}
198
+ <Divider sx={{ my: 4 }} />
330
199
 
331
- {/* Footer Section */}
200
+ <Typography variant="body2" color="text.secondary">
201
+ Publisher is built on fully open infrastructure and designed for the
202
+ AI era. Join the{" "}
332
203
  <Box
204
+ component="a"
205
+ href="https://join.slack.com/t/malloy-community/shared_invite/zt-1kgfwgi5g-CrsdaRqs81QY67QW0~t_uw"
206
+ target="_blank"
207
+ rel="noopener noreferrer"
333
208
  sx={{
334
- textAlign: "center",
335
- mt: 6,
336
- pt: 4,
337
- borderTop: 1,
338
- borderColor: "divider",
209
+ color: "text.primary",
210
+ textDecoration: "underline",
339
211
  }}
340
212
  >
341
- <Typography variant="body2" color="text.secondary">
342
- Publisher is built on fully open infrastructure and designed
343
- for the AI era. Join the{" "}
344
- <a
345
- href="https://join.slack.com/t/malloy-community/shared_invite/zt-1kgfwgi5g-CrsdaRqs81QY67QW0~t_uw"
346
- target="_blank"
347
- rel="noopener noreferrer"
348
- style={{
349
- color: "primary.main",
350
- textDecoration: "underline",
351
- }}
352
- >
353
- Malloy Slack community
354
- </a>{" "}
355
- to ask questions, share ideas, and contribute to the future of
356
- data modeling.
357
- </Typography>
358
- </Box>
359
- </Container>
360
- );
361
- } else {
362
- return <Loading text="Loading environments..." />;
363
- }
213
+ Malloy Slack community
214
+ </Box>{" "}
215
+ to ask questions, share ideas, and contribute.
216
+ </Typography>
217
+ </Container>
218
+ );
364
219
  }
220
+
365
221
  function EnvironmentCard({
366
222
  environment,
367
223
  onClickEnvironment,
368
224
  }: {
369
225
  environment: Environment;
370
- onClickEnvironment: (to: string, event?: React.MouseEvent) => void;
226
+ onClickEnvironment?: (to: string, event?: React.MouseEvent) => void;
371
227
  }) {
372
228
  const { mutable } = useServer();
373
229
  const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null);
374
- const isMenuOpen = Boolean(menuAnchorEl);
375
- const openMenu = (event: React.MouseEvent<HTMLElement>) => {
230
+ const menuOpen = Boolean(menuAnchorEl);
231
+
232
+ const description = getEnvironmentDescription(environment.readme);
233
+
234
+ const handleClick = (event: React.MouseEvent) => {
235
+ if (environment.name && onClickEnvironment) {
236
+ onClickEnvironment(`/${environment.name}/`, event);
237
+ }
238
+ };
239
+
240
+ const handleMenuClick = (event: React.MouseEvent<HTMLElement>) => {
241
+ event.stopPropagation();
376
242
  setMenuAnchorEl(event.currentTarget);
377
243
  };
378
- const closeMenu = () => {
244
+
245
+ const handleMenuClose = () => {
379
246
  setMenuAnchorEl(null);
380
247
  };
248
+
381
249
  return (
382
- <div>
383
- <Card
384
- variant="outlined"
385
- sx={{
386
- height: "100%",
387
- transition: "all 0.2s ease",
388
- "&:hover": {
389
- transform: "translateY(-2px)",
390
- boxShadow: 2,
391
- borderColor: "primary.main",
392
- },
393
- }}
394
- >
395
- {mutable && (
396
- <>
397
- <IconButton
398
- aria-label={`Environment actions for ${environment.name}`}
399
- aria-controls={isMenuOpen ? "environment-menu" : undefined}
400
- aria-haspopup="true"
401
- aria-expanded={isMenuOpen ? "true" : undefined}
402
- onClick={openMenu}
403
- sx={{ position: "absolute", top: 8, right: 8 }}
404
- >
405
- <MoreVert fontSize="small" />
406
- </IconButton>
407
- <Menu
408
- id="environment-menu"
409
- aria-haspopup="true"
410
- aria-expanded={isMenuOpen ? "true" : undefined}
411
- open={isMenuOpen}
412
- anchorEl={menuAnchorEl}
413
- onClose={closeMenu}
414
- disableRestoreFocus
415
- anchorOrigin={{
416
- vertical: "top",
417
- horizontal: "left",
418
- }}
419
- transformOrigin={{
420
- vertical: "top",
421
- horizontal: "right",
422
- }}
423
- >
424
- <EditEnvironmentDialog
425
- environment={environment}
426
- onCloseDialog={closeMenu}
427
- />
428
- <DeleteEnvironmentDialog
429
- environment={environment}
430
- onCloseDialog={closeMenu}
431
- />
432
- </Menu>
433
- </>
434
- )}
435
- <CardContent sx={{ p: 3, textAlign: "center" }}>
436
- <ExploreRoundedIcon
250
+ <Card
251
+ variant="outlined"
252
+ onClick={handleClick}
253
+ sx={{
254
+ height: "100%",
255
+ cursor: "pointer",
256
+ borderRadius: 3,
257
+ borderColor: "divider",
258
+ boxShadow: "none",
259
+ transition: "all 0.2s ease-in-out",
260
+ "&:hover": { boxShadow: 2, borderColor: "primary.main" },
261
+ }}
262
+ >
263
+ <CardContent sx={{ p: 2.5, "&:last-child": { pb: 2.5 } }}>
264
+ <Box
265
+ sx={{
266
+ display: "flex",
267
+ alignItems: "flex-start",
268
+ gap: 1.5,
269
+ }}
270
+ >
271
+ <Box
437
272
  sx={{
438
- fontSize: 48,
439
- color: "primary.main",
440
- mb: 2,
273
+ width: 36,
274
+ height: 36,
275
+ borderRadius: 1.5,
276
+ bgcolor: "grey.100",
277
+ display: "flex",
278
+ alignItems: "center",
279
+ justifyContent: "center",
280
+ flexShrink: 0,
281
+ color: "text.primary",
441
282
  }}
442
- />
443
- <Typography variant="h6" fontWeight={600} gutterBottom>
444
- {environment.name}
445
- </Typography>
446
- <Typography
447
- variant="body2"
448
- color="text.secondary"
449
- sx={{ mb: 2, minHeight: "60px" }}
450
- >
451
- {getEnvironmentDescription(environment.readme)}
452
- </Typography>
453
- <Button
454
- variant="contained"
455
- color="secondary"
456
- endIcon={<ArrowForwardRoundedIcon />}
457
- fullWidth
458
- onClick={(event) =>
459
- onClickEnvironment(`/${environment.name}/`, event)
460
- }
461
283
  >
462
- Open Environment
463
- </Button>
464
- </CardContent>
465
- </Card>
466
- </div>
284
+ <FolderOutlinedIcon sx={{ fontSize: 20 }} />
285
+ </Box>
286
+ <Box sx={{ flex: 1, minWidth: 0 }}>
287
+ <Typography
288
+ variant="subtitle1"
289
+ component="h6"
290
+ noWrap
291
+ sx={{ fontWeight: 600, mb: 0.5 }}
292
+ >
293
+ {environment.name}
294
+ </Typography>
295
+ <Tooltip title={description} followCursor enterDelay={1000}>
296
+ <Typography
297
+ variant="body2"
298
+ color="text.secondary"
299
+ sx={{
300
+ overflow: "hidden",
301
+ textOverflow: "ellipsis",
302
+ display: "-webkit-box",
303
+ WebkitLineClamp: 2,
304
+ WebkitBoxOrient: "vertical",
305
+ lineHeight: 1.5,
306
+ }}
307
+ >
308
+ {description}
309
+ </Typography>
310
+ </Tooltip>
311
+ </Box>
312
+ {mutable && (
313
+ <>
314
+ <IconButton
315
+ size="small"
316
+ onClick={handleMenuClick}
317
+ aria-label={`Environment actions for ${environment.name}`}
318
+ sx={{ flexShrink: 0, mt: -0.5, mr: -0.5 }}
319
+ >
320
+ <MoreVert fontSize="small" />
321
+ </IconButton>
322
+ <Menu
323
+ anchorEl={menuAnchorEl}
324
+ open={menuOpen}
325
+ onClose={handleMenuClose}
326
+ onClick={(e) => e.stopPropagation()}
327
+ anchorOrigin={{
328
+ vertical: "bottom",
329
+ horizontal: "right",
330
+ }}
331
+ transformOrigin={{
332
+ vertical: "top",
333
+ horizontal: "right",
334
+ }}
335
+ >
336
+ <EditEnvironmentDialog
337
+ environment={environment}
338
+ onCloseDialog={handleMenuClose}
339
+ />
340
+ <DeleteEnvironmentDialog
341
+ environment={environment}
342
+ onCloseDialog={handleMenuClose}
343
+ />
344
+ </Menu>
345
+ </>
346
+ )}
347
+ </Box>
348
+ </CardContent>
349
+ </Card>
467
350
  );
468
351
  }