@blocklet/pages-kit-block-studio 0.5.41 → 0.5.42

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.
@@ -42,9 +42,9 @@ const jsx_runtime_1 = require("react/jsx-runtime");
42
42
  // NOTICE: This file is very important, please do not change it!
43
43
  // import BlockStudio from '@blocklet/pages-kit-block-studio/frontend';
44
44
  // export default BlockStudio;
45
+ // add tailwindcss css to the head
45
46
  const Session_1 = require("@arcblock/did-connect/lib/Session");
46
47
  const Config_1 = require("@arcblock/ux/lib/Config");
47
- const Empty_1 = __importDefault(require("@arcblock/ux/lib/Empty"));
48
48
  const context_1 = require("@arcblock/ux/lib/Locale/context");
49
49
  const Theme_1 = require("@arcblock/ux/lib/Theme");
50
50
  const Toast_1 = __importStar(require("@arcblock/ux/lib/Toast"));
@@ -60,16 +60,21 @@ const property_1 = require("@blocklet/pages-kit/utils/property");
60
60
  const studio_ui_1 = require("@blocklet/studio-ui");
61
61
  const ui_react_1 = require("@blocklet/ui-react");
62
62
  const Add_1 = __importDefault(require("@mui/icons-material/Add"));
63
+ const Delete_1 = __importDefault(require("@mui/icons-material/Delete"));
64
+ const DragIndicator_1 = __importDefault(require("@mui/icons-material/DragIndicator"));
63
65
  const LaptopMac_1 = __importDefault(require("@mui/icons-material/LaptopMac"));
64
66
  const PhoneAndroid_1 = __importDefault(require("@mui/icons-material/PhoneAndroid"));
67
+ const Settings_1 = __importDefault(require("@mui/icons-material/Settings"));
68
+ const SortByAlpha_1 = __importDefault(require("@mui/icons-material/SortByAlpha"));
65
69
  const material_1 = require("@mui/material");
66
70
  const ahooks_1 = require("ahooks");
67
71
  const ahooks_2 = require("ahooks");
68
72
  const cloneDeep_1 = __importDefault(require("lodash/cloneDeep"));
69
73
  const get_1 = __importDefault(require("lodash/get"));
70
74
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
71
- const react_1 = require("react");
75
+ const react_1 = __importStar(require("react"));
72
76
  const react_dnd_1 = require("react-dnd");
77
+ const react_dnd_2 = require("react-dnd");
73
78
  const react_dnd_html5_backend_1 = require("react-dnd-html5-backend");
74
79
  const react_router_dom_1 = require("react-router-dom");
75
80
  const split_pane_react_1 = __importStar(require("split-pane-react"));
@@ -93,6 +98,136 @@ function useSessionContext() {
93
98
  const LEFT_DRAWER_WIDTH = 300;
94
99
  const RIGHT_DRAWER_WIDTH = 300;
95
100
  const defaultLocale = 'en';
101
+ // Drag and drop types
102
+ const ItemTypes = {
103
+ ROUTE_ITEM: 'route-item',
104
+ };
105
+ const DraggableRouteItem = ({ route, staticDataInRoute, currentPageId, onNavigate, onDelete, index, moveItem, showDragHandle, routes, }) => {
106
+ const ref = react_1.default.useRef(null);
107
+ const [isHovered, setIsHovered] = react_1.default.useState(false);
108
+ const [{ handlerId }, drop] = (0, react_dnd_2.useDrop)({
109
+ accept: ItemTypes.ROUTE_ITEM,
110
+ collect(monitor) {
111
+ return {
112
+ handlerId: monitor.getHandlerId(),
113
+ };
114
+ },
115
+ hover(item, monitor) {
116
+ if (!ref.current || !showDragHandle) {
117
+ return;
118
+ }
119
+ // Get the real dragIndex from routes array using route as identifier
120
+ // This prevents issues when dragging too fast and item.index becomes stale
121
+ const dragIndex = routes.findIndex((r) => r === item.route);
122
+ const hoverIndex = index;
123
+ // Don't replace items with themselves
124
+ if (dragIndex === hoverIndex || dragIndex === -1) {
125
+ return;
126
+ }
127
+ // Determine rectangle on screen
128
+ const hoverBoundingRect = ref.current?.getBoundingClientRect();
129
+ // Get vertical middle
130
+ const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
131
+ // Determine mouse position
132
+ const clientOffset = monitor.getClientOffset();
133
+ // Get pixels to the top
134
+ const hoverClientY = (clientOffset?.y ?? 0) - hoverBoundingRect.top;
135
+ // Only perform the move when the mouse has crossed half of the items height
136
+ // When dragging downwards, only move when the cursor is below 50%
137
+ // When dragging upwards, only move when the cursor is above 50%
138
+ // Dragging downwards
139
+ if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
140
+ return;
141
+ }
142
+ // Dragging upwards
143
+ if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
144
+ return;
145
+ }
146
+ // Time to actually perform the action
147
+ moveItem(dragIndex, hoverIndex);
148
+ // Note: we're mutating the monitor item here!
149
+ // Generally it's better to avoid mutations,
150
+ // but it's good here for the sake of performance
151
+ // to avoid expensive index searches.
152
+ item.index = hoverIndex;
153
+ },
154
+ });
155
+ const [{ isDragging }, drag, preview] = (0, react_dnd_2.useDrag)({
156
+ type: ItemTypes.ROUTE_ITEM,
157
+ item: () => {
158
+ return { route, index };
159
+ },
160
+ collect: (monitor) => ({
161
+ isDragging: monitor.isDragging(),
162
+ }),
163
+ canDrag: showDragHandle,
164
+ });
165
+ const opacity = isDragging ? 0.5 : 1;
166
+ // Separate drag handle from the clickable area
167
+ const dragHandleRef = react_1.default.useRef(null);
168
+ // Only attach drag to the handle when in custom sort mode
169
+ react_1.default.useEffect(() => {
170
+ if (showDragHandle && dragHandleRef.current) {
171
+ drag(dragHandleRef.current);
172
+ }
173
+ }, [drag, showDragHandle]);
174
+ drop(ref);
175
+ return ((0, jsx_runtime_1.jsx)(material_1.ListItem, { ref: ref, disablePadding: true, className: isDragging ? 'drag-shaking' : '', sx: {
176
+ opacity,
177
+ transition: isDragging ? 'opacity 0.2s ease' : 'opacity 0.2s ease, transform 0.2s ease',
178
+ zIndex: isDragging ? 1000 : 'auto',
179
+ }, "data-handler-id": handlerId, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: (0, jsx_runtime_1.jsxs)(material_1.ListItemButton, { selected: currentPageId === route, onClick: () => onNavigate(route), sx: {
180
+ borderRadius: 1,
181
+ width: '100%',
182
+ textOverflow: 'ellipsis',
183
+ whiteSpace: 'nowrap',
184
+ overflowX: 'hidden',
185
+ transition: 'all 0.3s ease',
186
+ '&.Mui-selected': {
187
+ backgroundColor: 'primary.main',
188
+ color: 'white',
189
+ '&:hover': {
190
+ backgroundColor: 'primary.main',
191
+ },
192
+ },
193
+ fontSize: '14px',
194
+ display: 'flex',
195
+ alignItems: 'center',
196
+ gap: 1,
197
+ border: (theme) => (isDragging ? `1px solid ${theme.palette.primary.main}` : '1px solid transparent'),
198
+ backgroundColor: isDragging ? 'grey.100' : 'inherit',
199
+ pl: 1,
200
+ pr: 1,
201
+ }, children: [showDragHandle && ((0, jsx_runtime_1.jsx)(material_1.Box, { ref: dragHandleRef, sx: {
202
+ display: 'flex',
203
+ alignItems: 'center',
204
+ justifyContent: 'center',
205
+ cursor: 'grab',
206
+ borderRadius: 1,
207
+ '&:active': {
208
+ cursor: 'grabbing',
209
+ },
210
+ }, children: (0, jsx_runtime_1.jsx)(DragIndicator_1.default, { sx: {
211
+ fontSize: '14px',
212
+ } }) })), (0, jsx_runtime_1.jsx)(material_1.Tooltip, { title: staticDataInRoute.blockName || route, children: (0, jsx_runtime_1.jsx)(material_1.Box, { sx: {
213
+ flex: 1,
214
+ overflow: 'hidden',
215
+ textOverflow: 'ellipsis',
216
+ whiteSpace: 'nowrap',
217
+ }, children: staticDataInRoute.blockName || route }) }), (0, jsx_runtime_1.jsx)(material_1.IconButton, { size: "small", onClick: (e) => {
218
+ e.stopPropagation();
219
+ onDelete(route, staticDataInRoute.blockName || route);
220
+ }, sx: {
221
+ ml: 1,
222
+ opacity: isHovered ? 0.7 : 0,
223
+ visibility: isHovered ? 'visible' : 'hidden',
224
+ transition: 'opacity 0.2s ease',
225
+ '&:hover': {
226
+ bgcolor: 'error.main',
227
+ color: 'error.contrastText',
228
+ },
229
+ }, children: (0, jsx_runtime_1.jsx)(Delete_1.default, { sx: { fontSize: '16px' } }) })] }) }));
230
+ };
96
231
  const ComparisonPreviewDialog = ({ open, title, leftTitle, leftContent, rightTitle, rightContent, description, loading, onConfirm, onClose, }) => {
97
232
  const { t } = (0, context_1.useLocaleContext)();
98
233
  const handleConfirm = async () => {
@@ -146,6 +281,12 @@ function Layout({ loadState, loadedData }) {
146
281
  loading: false,
147
282
  onConfirm: async () => { },
148
283
  },
284
+ deleteConfirmDialog: {
285
+ open: false,
286
+ blockName: '',
287
+ route: '',
288
+ loading: false,
289
+ },
149
290
  init: false,
150
291
  allComponents: [],
151
292
  propertiesValue: {},
@@ -159,6 +300,10 @@ function Layout({ loadState, loadedData }) {
159
300
  const [hSizes, setHSizes] = (0, ahooks_1.useLocalStorageState)('BlockStudioHorizontalSizes', {
160
301
  defaultValue: [LEFT_DRAWER_WIDTH, 'auto', RIGHT_DRAWER_WIDTH],
161
302
  });
303
+ // Custom sorting order storage
304
+ const [customSortOrder, setCustomSortOrder] = (0, ahooks_1.useLocalStorageState)('BlockStudioCustomSortOrder', {
305
+ defaultValue: [],
306
+ });
162
307
  const { t, locale } = (0, context_1.useLocaleContext)();
163
308
  const { session } = useSessionContext();
164
309
  const location = (0, react_router_dom_1.useLocation)();
@@ -182,18 +327,37 @@ function Layout({ loadState, loadedData }) {
182
327
  iframe.contentWindow.postMessage(safeData, '*');
183
328
  }
184
329
  }, [mode]);
185
- const { routes, currentPage } = (0, react_1.useMemo)(() => {
186
- const routes = Object.keys(staticData)
187
- .sort((a, b) => a.localeCompare(b))
188
- .filter((route) => staticData[route]?.main?.isDeleted !== true);
330
+ const { routes, currentPage, hasCustomSort } = (0, react_1.useMemo)(() => {
331
+ const allRoutes = Object.keys(staticData).filter((route) => staticData[route]?.main?.isDeleted !== true);
332
+ let sortedRoutes;
333
+ let hasCustomSort = false;
334
+ if (customSortOrder && customSortOrder.length > 0) {
335
+ // Use custom sort order if available
336
+ hasCustomSort = true;
337
+ const sortOrderMap = new Map(customSortOrder.map((route, index) => [route, index]));
338
+ sortedRoutes = allRoutes.sort((a, b) => {
339
+ const indexA = sortOrderMap.get(a) ?? Number.MAX_SAFE_INTEGER;
340
+ const indexB = sortOrderMap.get(b) ?? Number.MAX_SAFE_INTEGER;
341
+ if (indexA === indexB) {
342
+ return a.localeCompare(b); // Fallback to alphabetical for new items
343
+ }
344
+ return indexA - indexB;
345
+ });
346
+ }
347
+ else {
348
+ // Use alphabetical sort as default
349
+ hasCustomSort = false;
350
+ sortedRoutes = allRoutes.sort((a, b) => a.localeCompare(b));
351
+ }
189
352
  return {
190
- routes,
353
+ routes: sortedRoutes,
354
+ hasCustomSort,
191
355
  currentPage: {
192
356
  ...staticData[location.pathname]?.main,
193
357
  pageId: location.pathname,
194
358
  },
195
359
  };
196
- }, [staticData, location.pathname]);
360
+ }, [staticData, location.pathname, customSortOrder]);
197
361
  const notSelectedBlock = (0, react_1.useMemo)(() => {
198
362
  return !currentPage?.blockName || !currentPage?.code;
199
363
  }, [currentPage]);
@@ -262,6 +426,9 @@ function Layout({ loadState, loadedData }) {
262
426
  }
263
427
  catch (error) {
264
428
  console.error('Failed to write metadata:', error);
429
+ Toast_1.default.error(t('themeTranslations.saveMetadataFailed', {
430
+ error: error.message,
431
+ }));
265
432
  }
266
433
  }
267
434
  }, { wait: 500 });
@@ -352,6 +519,16 @@ function Layout({ loadState, loadedData }) {
352
519
  const DraggingSplitPlaceholder = (0, react_1.useMemo)(() => {
353
520
  return ((0, jsx_runtime_1.jsx)(material_1.Box, { p: 1.5, width: "100%", height: "100%", children: (0, jsx_runtime_1.jsx)(material_1.Skeleton, { variant: "rectangular", height: "100%", sx: { borderRadius: 1 } }) }));
354
521
  }, []);
522
+ // Move item function for drag and drop
523
+ const moveItem = (0, react_1.useCallback)((dragIndex, hoverIndex) => {
524
+ if (dragIndex === hoverIndex)
525
+ return;
526
+ const newRoutes = [...routes];
527
+ const [draggedItem] = newRoutes.splice(dragIndex, 1);
528
+ newRoutes.splice(hoverIndex, 0, draggedItem);
529
+ // Save the new order to localStorage (automatically enables custom sort)
530
+ setCustomSortOrder(newRoutes);
531
+ }, [routes, setCustomSortOrder]);
355
532
  // 修改 iframe URL 构建逻辑,添加初始状态参数
356
533
  const iframeUrl = (0, react_1.useMemo)(() => {
357
534
  const url = new URL(window.location.href);
@@ -386,16 +563,67 @@ function Layout({ loadState, loadedData }) {
386
563
  window.addEventListener('message', handleMessage);
387
564
  return () => window.removeEventListener('message', handleMessage);
388
565
  }, [changeMode, mode]);
566
+ // Add delete block function
567
+ const handleDeleteBlock = async (route, blockName) => {
568
+ state.deleteConfirmDialog = {
569
+ open: true,
570
+ blockName,
571
+ route,
572
+ loading: false,
573
+ };
574
+ };
575
+ const confirmDeleteBlock = async () => {
576
+ state.deleteConfirmDialog.loading = true;
577
+ try {
578
+ // Call delete API
579
+ const response = await api.delete(`/api/blocks/delete`, {
580
+ data: { route: state.deleteConfirmDialog.route },
581
+ });
582
+ if (response.data.success) {
583
+ Toast_1.default.success(t('themeTranslations.deleteSuccess'));
584
+ // Navigate away if currently viewing the deleted component
585
+ if (currentPage.pageId === state.deleteConfirmDialog.route) {
586
+ const remainingComponents = routes.filter((r) => r !== state.deleteConfirmDialog.route);
587
+ navigate('/');
588
+ }
589
+ }
590
+ else {
591
+ throw new Error(response.data.error || t('themeTranslations.deleteFailed'));
592
+ }
593
+ }
594
+ catch (error) {
595
+ console.error('Failed to delete block:', error);
596
+ Toast_1.default.error(t('themeTranslations.deleteFailed'));
597
+ }
598
+ finally {
599
+ state.deleteConfirmDialog.loading = false;
600
+ state.deleteConfirmDialog.open = false;
601
+ }
602
+ };
389
603
  const leftPanelContent = (0, react_1.useMemo)(() => {
390
604
  if (state.draggingSplitPane) {
391
605
  return DraggingSplitPlaceholder;
392
606
  }
393
- return ((0, jsx_runtime_1.jsxs)(material_1.Stack, { height: "100%", children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, { gap: 1, direction: "row", alignItems: "center", sx: { py: 2, pr: 1, pl: 0.5, flex: 1 }, children: [(0, jsx_runtime_1.jsx)(material_1.TextField, { placeholder: t('themeTranslations.search'), sx: { minWidth: 60, flex: 1 }, onChange: (e) => {
607
+ return ((0, jsx_runtime_1.jsxs)(material_1.Stack, { height: "100%", children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, { gap: 1, direction: "row", alignItems: "center", sx: { pt: 2, pr: 1, pl: 0.5, pb: 1 }, children: [(0, jsx_runtime_1.jsx)(material_1.TextField, { placeholder: t('themeTranslations.search'), sx: { minWidth: 60, flex: 1 }, onChange: (e) => {
394
608
  state.searchValue = e.target.value;
395
- } }), (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "contained", sx: { minWidth: 40 }, onClick: () => {
609
+ } }), hasCustomSort && ((0, jsx_runtime_1.jsx)(material_1.Tooltip, { title: t('themeTranslations.resetToAlphabeticalSort'), children: (0, jsx_runtime_1.jsx)(material_1.IconButton, { size: "small", onClick: () => {
610
+ setCustomSortOrder([]);
611
+ }, sx: {
612
+ transition: 'all 0.2s ease',
613
+ '&:hover': {
614
+ backgroundColor: 'action.hover',
615
+ transform: 'scale(1.1)',
616
+ },
617
+ }, children: (0, jsx_runtime_1.jsx)(SortByAlpha_1.default, { fontSize: "small" }) }) })), (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "contained", sx: { minWidth: 40 }, onClick: () => {
396
618
  state.createBlockOpen = true;
397
- }, children: (0, jsx_runtime_1.jsx)(Add_1.default, { fontSize: "small" }) })] }), routes?.length > 0 ? ((0, jsx_runtime_1.jsx)(material_1.List, { sx: { pr: 1, overflowY: 'auto', height: 'calc(100% - 60px)' }, children: routes
398
- .map((route) => {
619
+ }, children: (0, jsx_runtime_1.jsx)(Add_1.default, { fontSize: "small" }) })] }), routes?.length > 0 ? ((0, jsx_runtime_1.jsx)(material_1.List, { sx: {
620
+ pr: 1,
621
+ overflowY: 'auto',
622
+ height: hasCustomSort ? 'calc(100% - 80px)' : 'calc(100% - 60px)',
623
+ // Prevent text selection during drag
624
+ userSelect: 'none',
625
+ }, children: routes
626
+ .map((route, index) => {
399
627
  const routeName = route;
400
628
  const staticDataInRoute = staticData[route]?.main;
401
629
  if (state.searchValue && !routeName?.toLowerCase().includes(state.searchValue?.toLowerCase())) {
@@ -404,35 +632,35 @@ function Layout({ loadState, loadedData }) {
404
632
  if (!state.allComponents?.find(({ blockName }) => `/${blockName}` === routeName)) {
405
633
  return null;
406
634
  }
407
- return ((0, jsx_runtime_1.jsx)(material_1.ListItem, { disablePadding: true, children: (0, jsx_runtime_1.jsx)(material_1.ListItemButton, { selected: currentPage.pageId === route, onClick: () => {
408
- if (route !== currentPage.pageId) {
409
- navigate(route);
410
- state.iframeLoaded = false;
411
- }
412
- }, sx: {
413
- borderRadius: 1,
414
- mb: 1,
415
- width: '100%',
416
- textOverflow: 'ellipsis',
417
- whiteSpace: 'nowrap',
418
- overflowX: 'hidden',
419
- transition: 'all 0.3s ease',
420
- '&.Mui-selected': {
421
- backgroundColor: 'primary.main',
422
- color: 'white',
423
- '&:hover': {
424
- backgroundColor: 'primary.main',
425
- },
426
- },
427
- fontSize: '14px',
428
- }, children: (0, jsx_runtime_1.jsx)(material_1.Tooltip, { title: staticDataInRoute.blockName || routeName, children: (0, jsx_runtime_1.jsx)("div", { style: {
429
- width: '100%',
430
- overflow: 'hidden',
431
- textOverflow: 'ellipsis',
432
- }, children: staticDataInRoute.blockName || routeName }) }) }) }, route));
635
+ return ((0, jsx_runtime_1.jsx)(DraggableRouteItem, { route: route, staticDataInRoute: staticDataInRoute, currentPageId: currentPage.pageId, onNavigate: (route) => {
636
+ if (route !== currentPage.pageId) {
637
+ navigate(route);
638
+ state.iframeLoaded = false;
639
+ }
640
+ }, onDelete: handleDeleteBlock, index: index, moveItem: moveItem, showDragHandle: true, routes: routes }, route));
433
641
  })
434
- .filter(Boolean) })) : ((0, jsx_runtime_1.jsx)(material_1.Box, { display: "flex", justifyContent: "center", alignItems: "center", height: "100%", children: (0, jsx_runtime_1.jsx)(Empty_1.default, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", color: "text.secondary", children: t('themeTranslations.noRoutesFound') }) }) }))] }));
435
- }, [routes, staticData, state.allComponents, state.searchValue, state.draggingSplitPane, locale, mode]);
642
+ .filter(Boolean) })) : ((0, jsx_runtime_1.jsx)(material_1.Box, { display: "flex", justifyContent: "center", alignItems: "center", height: "100%", sx: { p: 3 }, children: (0, jsx_runtime_1.jsxs)(material_1.Stack, { alignItems: "center", spacing: 3, sx: { textAlign: 'center' }, children: [(0, jsx_runtime_1.jsx)(material_1.Box, { component: "img", src: "https://api.iconify.design/material-symbols:folder-open-outline.svg", sx: {
643
+ width: 48,
644
+ height: 48,
645
+ opacity: 0.5,
646
+ filter: 'grayscale(100%)',
647
+ }, alt: "No components" }), (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "h6", color: "text.primary", sx: { fontWeight: 600 }, children: t('themeTranslations.noComponentsFound') }), (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", color: "text.secondary", sx: { maxWidth: 200 }, children: t('themeTranslations.createFirstComponent') }), (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "contained", startIcon: (0, jsx_runtime_1.jsx)(Add_1.default, {}), onClick: () => {
648
+ state.createBlockOpen = true;
649
+ }, sx: { mt: 2 }, children: t('themeTranslations.createComponent') })] }) }))] }));
650
+ }, [
651
+ routes,
652
+ staticData,
653
+ state.allComponents,
654
+ state.searchValue,
655
+ state.draggingSplitPane,
656
+ hasCustomSort,
657
+ moveItem,
658
+ currentPage.pageId,
659
+ navigate,
660
+ locale,
661
+ mode,
662
+ handleDeleteBlock,
663
+ ]);
436
664
  // 修改 middlePanelContent - iframe 内部监听消息
437
665
  const middlePanelContent = (0, react_1.useMemo)(() => {
438
666
  // 如果在iframe内部,添加消息接收逻辑
@@ -447,11 +675,11 @@ function Layout({ loadState, loadedData }) {
447
675
  const url = new URL(window.location.href);
448
676
  const initialComponentId = url.searchParams.get('componentId') || state.metadata.id;
449
677
  const initialLocale = url.searchParams.get('locale') || locale || 'en';
450
- return ((0, jsx_runtime_1.jsx)(material_1.Box, { className: "custom-component-root", sx: { height: '100vh', width: '100vw', overflow: 'auto' }, children: (0, jsx_runtime_1.jsx)(Theme_1.ThemeProvider, { theme: theme, children: (0, jsx_runtime_1.jsx)(react_1.Suspense, { fallback: (0, jsx_runtime_1.jsx)(material_1.CircularProgress, {}), children: (0, jsx_runtime_1.jsx)(components_2.CustomComponentRenderer, { locale: initialLocale, componentId: initialComponentId || state.metadata.id, dev: {
451
- mode: 'draft',
452
- components: mergedAllBlocks,
453
- defaultLocale,
454
- }, properties: mergedPropertiesValues }, `custom-${renderComponentTrigger}`) }) }) }));
678
+ return ((0, jsx_runtime_1.jsx)(material_1.Box, { className: "custom-component-root", sx: { height: '100vh', width: '100vw', overflow: 'auto' }, children: (0, jsx_runtime_1.jsx)(Theme_1.ThemeProvider, { theme: theme, children: (0, jsx_runtime_1.jsx)(components_2.CustomComponentRenderer, { locale: initialLocale, componentId: initialComponentId || state.metadata.id, dev: {
679
+ mode: 'draft',
680
+ components: mergedAllBlocks,
681
+ defaultLocale,
682
+ }, properties: mergedPropertiesValues }, `custom-${renderComponentTrigger}`) }) }));
455
683
  }
456
684
  // 没有匹配到路由,使用欢迎页面
457
685
  if (notSelectedBlock) {
@@ -618,6 +846,7 @@ function Layout({ loadState, loadedData }) {
618
846
  JSON.stringify(state.metadata || {}),
619
847
  JSON.stringify(mergedAllBlocks || []),
620
848
  JSON.stringify(mergedPropertiesValues || {}),
849
+ handleDeleteBlock,
621
850
  ]);
622
851
  const rightPanelContent = (0, react_1.useMemo)(() => {
623
852
  if (state.draggingSplitPane) {
@@ -632,7 +861,7 @@ function Layout({ loadState, loadedData }) {
632
861
  // add disabled backdrop if not selected block
633
862
  '& .MuiListItem-root': {
634
863
  pointerEvents: notSelectedBlock ? 'none' : 'auto',
635
- opacity: notSelectedBlock ? 0.5 : 1,
864
+ opacity: notSelectedBlock ? 0.4 : 1,
636
865
  userSelect: notSelectedBlock ? 'none' : 'auto',
637
866
  filter: notSelectedBlock ? 'blur(5px)' : 'none',
638
867
  },
@@ -645,7 +874,13 @@ function Layout({ loadState, loadedData }) {
645
874
  display: 'flex',
646
875
  justifyContent: 'center',
647
876
  alignItems: 'center',
648
- }, children: (0, jsx_runtime_1.jsx)(Empty_1.default, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", color: "text.secondary", children: t('themeTranslations.selectBlockAndContinue') }) }) })), (0, jsx_runtime_1.jsx)(material_1.ListItem, { children: (0, jsx_runtime_1.jsx)(material_1.Box, { sx: { width: '100%' }, children: (0, jsx_runtime_1.jsx)(components_1.BasicInfo, { config: state.metadata }) }) }), (0, jsx_runtime_1.jsx)(material_1.ListItem, { children: (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { width: '100%' }, children: [(0, jsx_runtime_1.jsx)(components_1.PropertiesConfig, { config: state.metadata, currentLocale: locale, defaultLocale: defaultLocale, allComponents: mergedAllBlocks, onUpdateConfig: (updater) => {
877
+ p: 3,
878
+ }, children: (0, jsx_runtime_1.jsxs)(material_1.Stack, { alignItems: "center", spacing: 2, sx: {
879
+ textAlign: 'center',
880
+ bgcolor: 'background.default',
881
+ borderRadius: 2,
882
+ p: 3,
883
+ }, children: [(0, jsx_runtime_1.jsx)(Settings_1.default, { sx: { fontSize: 48, color: 'primary.main' } }), (0, jsx_runtime_1.jsxs)(material_1.Stack, { spacing: 1, children: [(0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "h6", color: "primary.main", sx: { fontWeight: 600 }, children: t('themeTranslations.componentPropertiesTitle') }), (0, jsx_runtime_1.jsx)(material_1.Typography, { variant: "body2", color: "text.secondary", children: t('themeTranslations.selectComponentToConfigureProperties') })] })] }) })), (0, jsx_runtime_1.jsx)(material_1.ListItem, { children: (0, jsx_runtime_1.jsx)(material_1.Box, { sx: { width: '100%' }, children: (0, jsx_runtime_1.jsx)(components_1.BasicInfo, { config: state.metadata }) }) }), (0, jsx_runtime_1.jsx)(material_1.ListItem, { children: (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { width: '100%' }, children: [(0, jsx_runtime_1.jsx)(components_1.PropertiesConfig, { config: state.metadata, currentLocale: locale, defaultLocale: defaultLocale, allComponents: mergedAllBlocks, onUpdateConfig: (updater) => {
649
884
  updater(state.metadata);
650
885
  }, useI18nEditor: false }), (0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "column", spacing: 1, sx: { mt: 1 }, children: [(0, jsx_runtime_1.jsx)(material_1.Button, { variant: "contained", size: "small", color: "primary", onClick: async () => {
651
886
  try {
@@ -779,6 +1014,7 @@ function Layout({ loadState, loadedData }) {
779
1014
  getStaticData,
780
1015
  state.draggingSplitPane,
781
1016
  mode,
1017
+ handleDeleteBlock,
782
1018
  ]);
783
1019
  if (isInsideIframe) {
784
1020
  return middlePanelContent;
@@ -844,7 +1080,11 @@ function Layout({ loadState, loadedData }) {
844
1080
  }
845
1081
  }, children: t('themeTranslations.create') })] }) })] }), (0, jsx_runtime_1.jsx)(ComparisonPreviewDialog, { open: state.previewDialog.open, title: state.previewDialog.title, leftTitle: state.previewDialog.leftTitle, leftContent: state.previewDialog.leftContent, rightTitle: state.previewDialog.rightTitle, rightContent: state.previewDialog.rightContent, description: state.previewDialog.description, loading: state.previewDialog.loading, onConfirm: state.previewDialog.onConfirm, onClose: () => {
846
1082
  state.previewDialog.open = false;
847
- } })] }) }));
1083
+ } }), (0, jsx_runtime_1.jsxs)(material_1.Dialog, { open: state.deleteConfirmDialog.open, onClose: () => (state.deleteConfirmDialog.open = false), maxWidth: "sm", fullWidth: true, children: [(0, jsx_runtime_1.jsx)(material_1.DialogTitle, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: t('themeTranslations.deleteComponent') }), (0, jsx_runtime_1.jsxs)(material_1.DialogContent, { children: [(0, jsx_runtime_1.jsx)(material_1.Alert, { severity: "warning", sx: { mb: 2 }, children: t('themeTranslations.deleteWarning') }), (0, jsx_runtime_1.jsx)(material_1.Typography, { children: t('themeTranslations.deleteConfirmMessage', {
1084
+ componentName: state.deleteConfirmDialog.blockName,
1085
+ }) })] }), (0, jsx_runtime_1.jsxs)(material_1.Box, { sx: { display: 'flex', justifyContent: 'flex-end', p: 2, gap: 1 }, children: [(0, jsx_runtime_1.jsx)(material_1.Button, { variant: "outlined", onClick: () => (state.deleteConfirmDialog.open = false), disabled: state.deleteConfirmDialog.loading, children: t('themeTranslations.cancel') }), (0, jsx_runtime_1.jsx)(material_1.Button, { variant: "contained", color: "error", onClick: confirmDeleteBlock, disabled: state.deleteConfirmDialog.loading, startIcon: state.deleteConfirmDialog.loading ? (0, jsx_runtime_1.jsx)(material_1.CircularProgress, { size: 20 }) : (0, jsx_runtime_1.jsx)(Delete_1.default, {}), children: state.deleteConfirmDialog.loading
1086
+ ? t('themeTranslations.deleting')
1087
+ : t('themeTranslations.deleteConfirm') })] })] })] }) }));
848
1088
  }
849
1089
  // Add SplitPane styling
850
1090
  const StyledSplitPane = (0, material_1.styled)(split_pane_react_1.default) `
@@ -864,6 +1104,29 @@ const DragHandle = (0, material_1.styled)('div') `
864
1104
  }
865
1105
  `;
866
1106
  const StyledDashboard = (0, material_1.styled)(studio_ui_1.Dashboard) `
1107
+ @keyframes dragShake {
1108
+ 0%,
1109
+ 100% {
1110
+ transform: translateY(0px) rotate(0deg);
1111
+ }
1112
+ 25% {
1113
+ transform: translateY(-2px) rotate(-2deg);
1114
+ }
1115
+ 50% {
1116
+ transform: translateY(2px) rotate(0deg);
1117
+ }
1118
+ 75% {
1119
+ transform: translateY(-2px) rotate(2deg);
1120
+ }
1121
+ 100% {
1122
+ transform: translateY(0px) rotate(0deg);
1123
+ }
1124
+ }
1125
+
1126
+ .drag-shaking {
1127
+ animation: dragShake 0.3s ease-in-out infinite;
1128
+ }
1129
+
867
1130
  .dashboard-content {
868
1131
  display: flex;
869
1132
  flex-direction: row;
@@ -909,12 +1172,12 @@ function CreateResource({ open, onClose }) {
909
1172
  return null;
910
1173
  }
911
1174
  const tenantScope = 'pages-kit-block-studio';
912
- return ((0, jsx_runtime_1.jsx)(react_1.Suspense, { children: (0, jsx_runtime_1.jsx)(ui_react_1.BlockletStudio, { mode: "dialog", tenantScope: tenantScope, title: "Pages Kit Blocks", description: "", note: "", introduction: "", logo: "", componentDid: PAGES_KIT_BLOCK_STUDIO_BLOCKLET_DID,
913
- // 透传到 get blocklet resource 的参数
914
- resourcesParams: {}, dependentComponentsMode: "readonly", open: true, setOpen: () => onClose(), onConnected: () => { }, onUploaded: () => { }, onReleased: () => { },
915
- // onOpened={() => onOpened?.()}
916
- // 默认选中的资源
917
- resources: {} }) }));
1175
+ return ((0, jsx_runtime_1.jsx)(ui_react_1.BlockletStudio, { mode: "dialog", tenantScope: tenantScope, title: "Pages Kit Blocks", description: "", note: "", introduction: "", logo: "", componentDid: PAGES_KIT_BLOCK_STUDIO_BLOCKLET_DID,
1176
+ // 透传到 get blocklet resource 的参数
1177
+ resourcesParams: {}, dependentComponentsMode: "readonly", open: true, setOpen: () => onClose(), onConnected: () => { }, onUploaded: () => { }, onReleased: () => { },
1178
+ // onOpened={() => onOpened?.()}
1179
+ // 默认选中的资源
1180
+ resources: {} }));
918
1181
  }
919
1182
  // 添加 themeTranslations 到 translations 对象
920
1183
  // 这里我们在运行时扩展 translations 对象,而不是修改源文件
@@ -965,6 +1228,21 @@ if (!locales_1.translations.en.themeTranslations) {
965
1228
  step1: 'Browse and select components from the left menu, or create your own',
966
1229
  step2: 'Preview your component in the center area with responsive design views',
967
1230
  step3: 'Customize properties and configurations in the right panel to bring your ideas to life',
1231
+ // New translations for sorting and improved empty states
1232
+ resetToAlphabeticalSort: 'Reset to Alphabetical Sort',
1233
+ noComponentsFound: 'No Components Found',
1234
+ createFirstComponent: 'Create your first component to get started',
1235
+ createComponent: 'Create Component',
1236
+ componentPropertiesTitle: 'Configure Component',
1237
+ selectComponentToConfigureProperties: 'Select a component from the left panel to configure its properties',
1238
+ saveMetadataFailed: 'Failed to write @metadata.json: {error}',
1239
+ deleteComponent: 'Delete Component',
1240
+ deleteWarning: 'This action cannot be undone. All files in the component directory will be permanently deleted.',
1241
+ deleteConfirmMessage: "Are you sure you want to delete the component '{componentName}'?",
1242
+ deleteConfirm: 'Delete',
1243
+ deleting: 'Deleting...',
1244
+ deleteSuccess: 'Component deleted successfully',
1245
+ deleteFailed: 'Failed to delete component',
968
1246
  };
969
1247
  }
970
1248
  if (!locales_1.translations.zh.themeTranslations) {
@@ -1014,5 +1292,20 @@ if (!locales_1.translations.zh.themeTranslations) {
1014
1292
  step1: '从左侧菜单浏览并选择组件,或创建您自己的组件',
1015
1293
  step2: '在中央区域预览您的组件,支持响应式设计视图',
1016
1294
  step3: '在右侧面板自定义属性和配置,让您的创意变为现实',
1295
+ // New translations for sorting and improved empty states
1296
+ resetToAlphabeticalSort: '重置为字母排序',
1297
+ noComponentsFound: '未找到组件',
1298
+ createFirstComponent: '创建您的第一个组件来开始',
1299
+ createComponent: '创建组件',
1300
+ componentPropertiesTitle: '配置组件',
1301
+ selectComponentToConfigureProperties: '从左侧面板选择一个组件来配置其属性',
1302
+ saveMetadataFailed: '修改 @metadata.json 失败: {error}',
1303
+ deleteComponent: '删除组件',
1304
+ deleteWarning: '此操作无法撤销。组件目录中的所有文件将被永久删除。',
1305
+ deleteConfirmMessage: "确定要删除组件 '{componentName}' 吗?",
1306
+ deleteConfirm: '删除',
1307
+ deleting: '删除中...',
1308
+ deleteSuccess: '组件删除成功',
1309
+ deleteFailed: '删除组件失败',
1017
1310
  };
1018
1311
  }