@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.
@@ -4,9 +4,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  // NOTICE: This file is very important, please do not change it!
5
5
  // import BlockStudio from '@blocklet/pages-kit-block-studio/frontend';
6
6
  // export default BlockStudio;
7
+ // add tailwindcss css to the head
7
8
  import { createAuthServiceSessionContext } from '@arcblock/did-connect/lib/Session';
8
9
  import { useConfig } from '@arcblock/ux/lib/Config';
9
- import Empty from '@arcblock/ux/lib/Empty';
10
10
  import { LocaleProvider, useLocaleContext } from '@arcblock/ux/lib/Locale/context';
11
11
  import { ThemeProvider } from '@arcblock/ux/lib/Theme';
12
12
  import Toast, { ToastProvider } from '@arcblock/ux/lib/Toast';
@@ -24,16 +24,21 @@ import { parsePropertyValue } from '@blocklet/pages-kit/utils/property';
24
24
  import { Dashboard } from '@blocklet/studio-ui';
25
25
  import { BlockletStudio } from '@blocklet/ui-react';
26
26
  import AddIcon from '@mui/icons-material/Add';
27
+ import DeleteIcon from '@mui/icons-material/Delete';
28
+ import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
27
29
  import LaptopMacIcon from '@mui/icons-material/LaptopMac';
28
30
  import PhoneAndroidIcon from '@mui/icons-material/PhoneAndroid';
29
- import { Alert, Box, Button, CircularProgress, Dialog, DialogContent, DialogTitle, List, ListItem, ListItemButton, Skeleton, Stack, StyledEngineProvider, TextField, ToggleButton, ToggleButtonGroup, Tooltip, Typography, backdropClasses, circularProgressClasses, styled, } from '@mui/material';
31
+ import SettingsIcon from '@mui/icons-material/Settings';
32
+ import SortByAlphaIcon from '@mui/icons-material/SortByAlpha';
33
+ import { Alert, Box, Button, CircularProgress, Dialog, DialogContent, DialogTitle, IconButton, List, ListItem, ListItemButton, Skeleton, Stack, StyledEngineProvider, TextField, ToggleButton, ToggleButtonGroup, Tooltip, Typography, backdropClasses, circularProgressClasses, styled, } from '@mui/material';
30
34
  import { useDebounceFn, useLocalStorageState, useReactive } from 'ahooks';
31
35
  import { useUpdateEffect } from 'ahooks';
32
36
  import cloneDeep from 'lodash/cloneDeep';
33
37
  import get from 'lodash/get';
34
38
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
35
- import { Suspense, useCallback, useContext, useEffect, useMemo, useState } from 'react';
39
+ import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
36
40
  import { DndProvider } from 'react-dnd';
41
+ import { useDrag, useDrop } from 'react-dnd';
37
42
  import { HTML5Backend } from 'react-dnd-html5-backend';
38
43
  import { useLocation, useNavigate } from 'react-router-dom';
39
44
  import SplitPane, { Pane } from 'split-pane-react';
@@ -57,6 +62,136 @@ function useSessionContext() {
57
62
  const LEFT_DRAWER_WIDTH = 300;
58
63
  const RIGHT_DRAWER_WIDTH = 300;
59
64
  const defaultLocale = 'en';
65
+ // Drag and drop types
66
+ const ItemTypes = {
67
+ ROUTE_ITEM: 'route-item',
68
+ };
69
+ const DraggableRouteItem = ({ route, staticDataInRoute, currentPageId, onNavigate, onDelete, index, moveItem, showDragHandle, routes, }) => {
70
+ const ref = React.useRef(null);
71
+ const [isHovered, setIsHovered] = React.useState(false);
72
+ const [{ handlerId }, drop] = useDrop({
73
+ accept: ItemTypes.ROUTE_ITEM,
74
+ collect(monitor) {
75
+ return {
76
+ handlerId: monitor.getHandlerId(),
77
+ };
78
+ },
79
+ hover(item, monitor) {
80
+ if (!ref.current || !showDragHandle) {
81
+ return;
82
+ }
83
+ // Get the real dragIndex from routes array using route as identifier
84
+ // This prevents issues when dragging too fast and item.index becomes stale
85
+ const dragIndex = routes.findIndex((r) => r === item.route);
86
+ const hoverIndex = index;
87
+ // Don't replace items with themselves
88
+ if (dragIndex === hoverIndex || dragIndex === -1) {
89
+ return;
90
+ }
91
+ // Determine rectangle on screen
92
+ const hoverBoundingRect = ref.current?.getBoundingClientRect();
93
+ // Get vertical middle
94
+ const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
95
+ // Determine mouse position
96
+ const clientOffset = monitor.getClientOffset();
97
+ // Get pixels to the top
98
+ const hoverClientY = (clientOffset?.y ?? 0) - hoverBoundingRect.top;
99
+ // Only perform the move when the mouse has crossed half of the items height
100
+ // When dragging downwards, only move when the cursor is below 50%
101
+ // When dragging upwards, only move when the cursor is above 50%
102
+ // Dragging downwards
103
+ if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
104
+ return;
105
+ }
106
+ // Dragging upwards
107
+ if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
108
+ return;
109
+ }
110
+ // Time to actually perform the action
111
+ moveItem(dragIndex, hoverIndex);
112
+ // Note: we're mutating the monitor item here!
113
+ // Generally it's better to avoid mutations,
114
+ // but it's good here for the sake of performance
115
+ // to avoid expensive index searches.
116
+ item.index = hoverIndex;
117
+ },
118
+ });
119
+ const [{ isDragging }, drag, preview] = useDrag({
120
+ type: ItemTypes.ROUTE_ITEM,
121
+ item: () => {
122
+ return { route, index };
123
+ },
124
+ collect: (monitor) => ({
125
+ isDragging: monitor.isDragging(),
126
+ }),
127
+ canDrag: showDragHandle,
128
+ });
129
+ const opacity = isDragging ? 0.5 : 1;
130
+ // Separate drag handle from the clickable area
131
+ const dragHandleRef = React.useRef(null);
132
+ // Only attach drag to the handle when in custom sort mode
133
+ React.useEffect(() => {
134
+ if (showDragHandle && dragHandleRef.current) {
135
+ drag(dragHandleRef.current);
136
+ }
137
+ }, [drag, showDragHandle]);
138
+ drop(ref);
139
+ return (_jsx(ListItem, { ref: ref, disablePadding: true, className: isDragging ? 'drag-shaking' : '', sx: {
140
+ opacity,
141
+ transition: isDragging ? 'opacity 0.2s ease' : 'opacity 0.2s ease, transform 0.2s ease',
142
+ zIndex: isDragging ? 1000 : 'auto',
143
+ }, "data-handler-id": handlerId, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: _jsxs(ListItemButton, { selected: currentPageId === route, onClick: () => onNavigate(route), sx: {
144
+ borderRadius: 1,
145
+ width: '100%',
146
+ textOverflow: 'ellipsis',
147
+ whiteSpace: 'nowrap',
148
+ overflowX: 'hidden',
149
+ transition: 'all 0.3s ease',
150
+ '&.Mui-selected': {
151
+ backgroundColor: 'primary.main',
152
+ color: 'white',
153
+ '&:hover': {
154
+ backgroundColor: 'primary.main',
155
+ },
156
+ },
157
+ fontSize: '14px',
158
+ display: 'flex',
159
+ alignItems: 'center',
160
+ gap: 1,
161
+ border: (theme) => (isDragging ? `1px solid ${theme.palette.primary.main}` : '1px solid transparent'),
162
+ backgroundColor: isDragging ? 'grey.100' : 'inherit',
163
+ pl: 1,
164
+ pr: 1,
165
+ }, children: [showDragHandle && (_jsx(Box, { ref: dragHandleRef, sx: {
166
+ display: 'flex',
167
+ alignItems: 'center',
168
+ justifyContent: 'center',
169
+ cursor: 'grab',
170
+ borderRadius: 1,
171
+ '&:active': {
172
+ cursor: 'grabbing',
173
+ },
174
+ }, children: _jsx(DragIndicatorIcon, { sx: {
175
+ fontSize: '14px',
176
+ } }) })), _jsx(Tooltip, { title: staticDataInRoute.blockName || route, children: _jsx(Box, { sx: {
177
+ flex: 1,
178
+ overflow: 'hidden',
179
+ textOverflow: 'ellipsis',
180
+ whiteSpace: 'nowrap',
181
+ }, children: staticDataInRoute.blockName || route }) }), _jsx(IconButton, { size: "small", onClick: (e) => {
182
+ e.stopPropagation();
183
+ onDelete(route, staticDataInRoute.blockName || route);
184
+ }, sx: {
185
+ ml: 1,
186
+ opacity: isHovered ? 0.7 : 0,
187
+ visibility: isHovered ? 'visible' : 'hidden',
188
+ transition: 'opacity 0.2s ease',
189
+ '&:hover': {
190
+ bgcolor: 'error.main',
191
+ color: 'error.contrastText',
192
+ },
193
+ }, children: _jsx(DeleteIcon, { sx: { fontSize: '16px' } }) })] }) }));
194
+ };
60
195
  const ComparisonPreviewDialog = ({ open, title, leftTitle, leftContent, rightTitle, rightContent, description, loading, onConfirm, onClose, }) => {
61
196
  const { t } = useLocaleContext();
62
197
  const handleConfirm = async () => {
@@ -110,6 +245,12 @@ function Layout({ loadState, loadedData }) {
110
245
  loading: false,
111
246
  onConfirm: async () => { },
112
247
  },
248
+ deleteConfirmDialog: {
249
+ open: false,
250
+ blockName: '',
251
+ route: '',
252
+ loading: false,
253
+ },
113
254
  init: false,
114
255
  allComponents: [],
115
256
  propertiesValue: {},
@@ -123,6 +264,10 @@ function Layout({ loadState, loadedData }) {
123
264
  const [hSizes, setHSizes] = useLocalStorageState('BlockStudioHorizontalSizes', {
124
265
  defaultValue: [LEFT_DRAWER_WIDTH, 'auto', RIGHT_DRAWER_WIDTH],
125
266
  });
267
+ // Custom sorting order storage
268
+ const [customSortOrder, setCustomSortOrder] = useLocalStorageState('BlockStudioCustomSortOrder', {
269
+ defaultValue: [],
270
+ });
126
271
  const { t, locale } = useLocaleContext();
127
272
  const { session } = useSessionContext();
128
273
  const location = useLocation();
@@ -146,18 +291,37 @@ function Layout({ loadState, loadedData }) {
146
291
  iframe.contentWindow.postMessage(safeData, '*');
147
292
  }
148
293
  }, [mode]);
149
- const { routes, currentPage } = useMemo(() => {
150
- const routes = Object.keys(staticData)
151
- .sort((a, b) => a.localeCompare(b))
152
- .filter((route) => staticData[route]?.main?.isDeleted !== true);
294
+ const { routes, currentPage, hasCustomSort } = useMemo(() => {
295
+ const allRoutes = Object.keys(staticData).filter((route) => staticData[route]?.main?.isDeleted !== true);
296
+ let sortedRoutes;
297
+ let hasCustomSort = false;
298
+ if (customSortOrder && customSortOrder.length > 0) {
299
+ // Use custom sort order if available
300
+ hasCustomSort = true;
301
+ const sortOrderMap = new Map(customSortOrder.map((route, index) => [route, index]));
302
+ sortedRoutes = allRoutes.sort((a, b) => {
303
+ const indexA = sortOrderMap.get(a) ?? Number.MAX_SAFE_INTEGER;
304
+ const indexB = sortOrderMap.get(b) ?? Number.MAX_SAFE_INTEGER;
305
+ if (indexA === indexB) {
306
+ return a.localeCompare(b); // Fallback to alphabetical for new items
307
+ }
308
+ return indexA - indexB;
309
+ });
310
+ }
311
+ else {
312
+ // Use alphabetical sort as default
313
+ hasCustomSort = false;
314
+ sortedRoutes = allRoutes.sort((a, b) => a.localeCompare(b));
315
+ }
153
316
  return {
154
- routes,
317
+ routes: sortedRoutes,
318
+ hasCustomSort,
155
319
  currentPage: {
156
320
  ...staticData[location.pathname]?.main,
157
321
  pageId: location.pathname,
158
322
  },
159
323
  };
160
- }, [staticData, location.pathname]);
324
+ }, [staticData, location.pathname, customSortOrder]);
161
325
  const notSelectedBlock = useMemo(() => {
162
326
  return !currentPage?.blockName || !currentPage?.code;
163
327
  }, [currentPage]);
@@ -226,6 +390,9 @@ function Layout({ loadState, loadedData }) {
226
390
  }
227
391
  catch (error) {
228
392
  console.error('Failed to write metadata:', error);
393
+ Toast.error(t('themeTranslations.saveMetadataFailed', {
394
+ error: error.message,
395
+ }));
229
396
  }
230
397
  }
231
398
  }, { wait: 500 });
@@ -316,6 +483,16 @@ function Layout({ loadState, loadedData }) {
316
483
  const DraggingSplitPlaceholder = useMemo(() => {
317
484
  return (_jsx(Box, { p: 1.5, width: "100%", height: "100%", children: _jsx(Skeleton, { variant: "rectangular", height: "100%", sx: { borderRadius: 1 } }) }));
318
485
  }, []);
486
+ // Move item function for drag and drop
487
+ const moveItem = useCallback((dragIndex, hoverIndex) => {
488
+ if (dragIndex === hoverIndex)
489
+ return;
490
+ const newRoutes = [...routes];
491
+ const [draggedItem] = newRoutes.splice(dragIndex, 1);
492
+ newRoutes.splice(hoverIndex, 0, draggedItem);
493
+ // Save the new order to localStorage (automatically enables custom sort)
494
+ setCustomSortOrder(newRoutes);
495
+ }, [routes, setCustomSortOrder]);
319
496
  // 修改 iframe URL 构建逻辑,添加初始状态参数
320
497
  const iframeUrl = useMemo(() => {
321
498
  const url = new URL(window.location.href);
@@ -350,16 +527,67 @@ function Layout({ loadState, loadedData }) {
350
527
  window.addEventListener('message', handleMessage);
351
528
  return () => window.removeEventListener('message', handleMessage);
352
529
  }, [changeMode, mode]);
530
+ // Add delete block function
531
+ const handleDeleteBlock = async (route, blockName) => {
532
+ state.deleteConfirmDialog = {
533
+ open: true,
534
+ blockName,
535
+ route,
536
+ loading: false,
537
+ };
538
+ };
539
+ const confirmDeleteBlock = async () => {
540
+ state.deleteConfirmDialog.loading = true;
541
+ try {
542
+ // Call delete API
543
+ const response = await api.delete(`/api/blocks/delete`, {
544
+ data: { route: state.deleteConfirmDialog.route },
545
+ });
546
+ if (response.data.success) {
547
+ Toast.success(t('themeTranslations.deleteSuccess'));
548
+ // Navigate away if currently viewing the deleted component
549
+ if (currentPage.pageId === state.deleteConfirmDialog.route) {
550
+ const remainingComponents = routes.filter((r) => r !== state.deleteConfirmDialog.route);
551
+ navigate('/');
552
+ }
553
+ }
554
+ else {
555
+ throw new Error(response.data.error || t('themeTranslations.deleteFailed'));
556
+ }
557
+ }
558
+ catch (error) {
559
+ console.error('Failed to delete block:', error);
560
+ Toast.error(t('themeTranslations.deleteFailed'));
561
+ }
562
+ finally {
563
+ state.deleteConfirmDialog.loading = false;
564
+ state.deleteConfirmDialog.open = false;
565
+ }
566
+ };
353
567
  const leftPanelContent = useMemo(() => {
354
568
  if (state.draggingSplitPane) {
355
569
  return DraggingSplitPlaceholder;
356
570
  }
357
- return (_jsxs(Stack, { height: "100%", children: [_jsxs(Stack, { gap: 1, direction: "row", alignItems: "center", sx: { py: 2, pr: 1, pl: 0.5, flex: 1 }, children: [_jsx(TextField, { placeholder: t('themeTranslations.search'), sx: { minWidth: 60, flex: 1 }, onChange: (e) => {
571
+ return (_jsxs(Stack, { height: "100%", children: [_jsxs(Stack, { gap: 1, direction: "row", alignItems: "center", sx: { pt: 2, pr: 1, pl: 0.5, pb: 1 }, children: [_jsx(TextField, { placeholder: t('themeTranslations.search'), sx: { minWidth: 60, flex: 1 }, onChange: (e) => {
358
572
  state.searchValue = e.target.value;
359
- } }), _jsx(Button, { variant: "contained", sx: { minWidth: 40 }, onClick: () => {
573
+ } }), hasCustomSort && (_jsx(Tooltip, { title: t('themeTranslations.resetToAlphabeticalSort'), children: _jsx(IconButton, { size: "small", onClick: () => {
574
+ setCustomSortOrder([]);
575
+ }, sx: {
576
+ transition: 'all 0.2s ease',
577
+ '&:hover': {
578
+ backgroundColor: 'action.hover',
579
+ transform: 'scale(1.1)',
580
+ },
581
+ }, children: _jsx(SortByAlphaIcon, { fontSize: "small" }) }) })), _jsx(Button, { variant: "contained", sx: { minWidth: 40 }, onClick: () => {
360
582
  state.createBlockOpen = true;
361
- }, children: _jsx(AddIcon, { fontSize: "small" }) })] }), routes?.length > 0 ? (_jsx(List, { sx: { pr: 1, overflowY: 'auto', height: 'calc(100% - 60px)' }, children: routes
362
- .map((route) => {
583
+ }, children: _jsx(AddIcon, { fontSize: "small" }) })] }), routes?.length > 0 ? (_jsx(List, { sx: {
584
+ pr: 1,
585
+ overflowY: 'auto',
586
+ height: hasCustomSort ? 'calc(100% - 80px)' : 'calc(100% - 60px)',
587
+ // Prevent text selection during drag
588
+ userSelect: 'none',
589
+ }, children: routes
590
+ .map((route, index) => {
363
591
  const routeName = route;
364
592
  const staticDataInRoute = staticData[route]?.main;
365
593
  if (state.searchValue && !routeName?.toLowerCase().includes(state.searchValue?.toLowerCase())) {
@@ -368,35 +596,35 @@ function Layout({ loadState, loadedData }) {
368
596
  if (!state.allComponents?.find(({ blockName }) => `/${blockName}` === routeName)) {
369
597
  return null;
370
598
  }
371
- return (_jsx(ListItem, { disablePadding: true, children: _jsx(ListItemButton, { selected: currentPage.pageId === route, onClick: () => {
372
- if (route !== currentPage.pageId) {
373
- navigate(route);
374
- state.iframeLoaded = false;
375
- }
376
- }, sx: {
377
- borderRadius: 1,
378
- mb: 1,
379
- width: '100%',
380
- textOverflow: 'ellipsis',
381
- whiteSpace: 'nowrap',
382
- overflowX: 'hidden',
383
- transition: 'all 0.3s ease',
384
- '&.Mui-selected': {
385
- backgroundColor: 'primary.main',
386
- color: 'white',
387
- '&:hover': {
388
- backgroundColor: 'primary.main',
389
- },
390
- },
391
- fontSize: '14px',
392
- }, children: _jsx(Tooltip, { title: staticDataInRoute.blockName || routeName, children: _jsx("div", { style: {
393
- width: '100%',
394
- overflow: 'hidden',
395
- textOverflow: 'ellipsis',
396
- }, children: staticDataInRoute.blockName || routeName }) }) }) }, route));
599
+ return (_jsx(DraggableRouteItem, { route: route, staticDataInRoute: staticDataInRoute, currentPageId: currentPage.pageId, onNavigate: (route) => {
600
+ if (route !== currentPage.pageId) {
601
+ navigate(route);
602
+ state.iframeLoaded = false;
603
+ }
604
+ }, onDelete: handleDeleteBlock, index: index, moveItem: moveItem, showDragHandle: true, routes: routes }, route));
397
605
  })
398
- .filter(Boolean) })) : (_jsx(Box, { display: "flex", justifyContent: "center", alignItems: "center", height: "100%", children: _jsx(Empty, { children: _jsx(Typography, { variant: "body2", color: "text.secondary", children: t('themeTranslations.noRoutesFound') }) }) }))] }));
399
- }, [routes, staticData, state.allComponents, state.searchValue, state.draggingSplitPane, locale, mode]);
606
+ .filter(Boolean) })) : (_jsx(Box, { display: "flex", justifyContent: "center", alignItems: "center", height: "100%", sx: { p: 3 }, children: _jsxs(Stack, { alignItems: "center", spacing: 3, sx: { textAlign: 'center' }, children: [_jsx(Box, { component: "img", src: "https://api.iconify.design/material-symbols:folder-open-outline.svg", sx: {
607
+ width: 48,
608
+ height: 48,
609
+ opacity: 0.5,
610
+ filter: 'grayscale(100%)',
611
+ }, alt: "No components" }), _jsx(Typography, { variant: "h6", color: "text.primary", sx: { fontWeight: 600 }, children: t('themeTranslations.noComponentsFound') }), _jsx(Typography, { variant: "body2", color: "text.secondary", sx: { maxWidth: 200 }, children: t('themeTranslations.createFirstComponent') }), _jsx(Button, { variant: "contained", startIcon: _jsx(AddIcon, {}), onClick: () => {
612
+ state.createBlockOpen = true;
613
+ }, sx: { mt: 2 }, children: t('themeTranslations.createComponent') })] }) }))] }));
614
+ }, [
615
+ routes,
616
+ staticData,
617
+ state.allComponents,
618
+ state.searchValue,
619
+ state.draggingSplitPane,
620
+ hasCustomSort,
621
+ moveItem,
622
+ currentPage.pageId,
623
+ navigate,
624
+ locale,
625
+ mode,
626
+ handleDeleteBlock,
627
+ ]);
400
628
  // 修改 middlePanelContent - iframe 内部监听消息
401
629
  const middlePanelContent = useMemo(() => {
402
630
  // 如果在iframe内部,添加消息接收逻辑
@@ -411,11 +639,11 @@ function Layout({ loadState, loadedData }) {
411
639
  const url = new URL(window.location.href);
412
640
  const initialComponentId = url.searchParams.get('componentId') || state.metadata.id;
413
641
  const initialLocale = url.searchParams.get('locale') || locale || 'en';
414
- return (_jsx(Box, { className: "custom-component-root", sx: { height: '100vh', width: '100vw', overflow: 'auto' }, children: _jsx(ThemeProvider, { theme: theme, children: _jsx(Suspense, { fallback: _jsx(CircularProgress, {}), children: _jsx(CustomComponentRenderer, { locale: initialLocale, componentId: initialComponentId || state.metadata.id, dev: {
415
- mode: 'draft',
416
- components: mergedAllBlocks,
417
- defaultLocale,
418
- }, properties: mergedPropertiesValues }, `custom-${renderComponentTrigger}`) }) }) }));
642
+ return (_jsx(Box, { className: "custom-component-root", sx: { height: '100vh', width: '100vw', overflow: 'auto' }, children: _jsx(ThemeProvider, { theme: theme, children: _jsx(CustomComponentRenderer, { locale: initialLocale, componentId: initialComponentId || state.metadata.id, dev: {
643
+ mode: 'draft',
644
+ components: mergedAllBlocks,
645
+ defaultLocale,
646
+ }, properties: mergedPropertiesValues }, `custom-${renderComponentTrigger}`) }) }));
419
647
  }
420
648
  // 没有匹配到路由,使用欢迎页面
421
649
  if (notSelectedBlock) {
@@ -582,6 +810,7 @@ function Layout({ loadState, loadedData }) {
582
810
  JSON.stringify(state.metadata || {}),
583
811
  JSON.stringify(mergedAllBlocks || []),
584
812
  JSON.stringify(mergedPropertiesValues || {}),
813
+ handleDeleteBlock,
585
814
  ]);
586
815
  const rightPanelContent = useMemo(() => {
587
816
  if (state.draggingSplitPane) {
@@ -596,7 +825,7 @@ function Layout({ loadState, loadedData }) {
596
825
  // add disabled backdrop if not selected block
597
826
  '& .MuiListItem-root': {
598
827
  pointerEvents: notSelectedBlock ? 'none' : 'auto',
599
- opacity: notSelectedBlock ? 0.5 : 1,
828
+ opacity: notSelectedBlock ? 0.4 : 1,
600
829
  userSelect: notSelectedBlock ? 'none' : 'auto',
601
830
  filter: notSelectedBlock ? 'blur(5px)' : 'none',
602
831
  },
@@ -609,7 +838,13 @@ function Layout({ loadState, loadedData }) {
609
838
  display: 'flex',
610
839
  justifyContent: 'center',
611
840
  alignItems: 'center',
612
- }, children: _jsx(Empty, { children: _jsx(Typography, { variant: "body2", color: "text.secondary", children: t('themeTranslations.selectBlockAndContinue') }) }) })), _jsx(ListItem, { children: _jsx(Box, { sx: { width: '100%' }, children: _jsx(BasicInfo, { config: state.metadata }) }) }), _jsx(ListItem, { children: _jsxs(Box, { sx: { width: '100%' }, children: [_jsx(PropertiesConfig, { config: state.metadata, currentLocale: locale, defaultLocale: defaultLocale, allComponents: mergedAllBlocks, onUpdateConfig: (updater) => {
841
+ p: 3,
842
+ }, children: _jsxs(Stack, { alignItems: "center", spacing: 2, sx: {
843
+ textAlign: 'center',
844
+ bgcolor: 'background.default',
845
+ borderRadius: 2,
846
+ p: 3,
847
+ }, children: [_jsx(SettingsIcon, { sx: { fontSize: 48, color: 'primary.main' } }), _jsxs(Stack, { spacing: 1, children: [_jsx(Typography, { variant: "h6", color: "primary.main", sx: { fontWeight: 600 }, children: t('themeTranslations.componentPropertiesTitle') }), _jsx(Typography, { variant: "body2", color: "text.secondary", children: t('themeTranslations.selectComponentToConfigureProperties') })] })] }) })), _jsx(ListItem, { children: _jsx(Box, { sx: { width: '100%' }, children: _jsx(BasicInfo, { config: state.metadata }) }) }), _jsx(ListItem, { children: _jsxs(Box, { sx: { width: '100%' }, children: [_jsx(PropertiesConfig, { config: state.metadata, currentLocale: locale, defaultLocale: defaultLocale, allComponents: mergedAllBlocks, onUpdateConfig: (updater) => {
613
848
  updater(state.metadata);
614
849
  }, useI18nEditor: false }), _jsxs(Stack, { direction: "column", spacing: 1, sx: { mt: 1 }, children: [_jsx(Button, { variant: "contained", size: "small", color: "primary", onClick: async () => {
615
850
  try {
@@ -743,6 +978,7 @@ function Layout({ loadState, loadedData }) {
743
978
  getStaticData,
744
979
  state.draggingSplitPane,
745
980
  mode,
981
+ handleDeleteBlock,
746
982
  ]);
747
983
  if (isInsideIframe) {
748
984
  return middlePanelContent;
@@ -808,7 +1044,11 @@ function Layout({ loadState, loadedData }) {
808
1044
  }
809
1045
  }, children: t('themeTranslations.create') })] }) })] }), _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: () => {
810
1046
  state.previewDialog.open = false;
811
- } })] }) }));
1047
+ } }), _jsxs(Dialog, { open: state.deleteConfirmDialog.open, onClose: () => (state.deleteConfirmDialog.open = false), maxWidth: "sm", fullWidth: true, children: [_jsx(DialogTitle, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: t('themeTranslations.deleteComponent') }), _jsxs(DialogContent, { children: [_jsx(Alert, { severity: "warning", sx: { mb: 2 }, children: t('themeTranslations.deleteWarning') }), _jsx(Typography, { children: t('themeTranslations.deleteConfirmMessage', {
1048
+ componentName: state.deleteConfirmDialog.blockName,
1049
+ }) })] }), _jsxs(Box, { sx: { display: 'flex', justifyContent: 'flex-end', p: 2, gap: 1 }, children: [_jsx(Button, { variant: "outlined", onClick: () => (state.deleteConfirmDialog.open = false), disabled: state.deleteConfirmDialog.loading, children: t('themeTranslations.cancel') }), _jsx(Button, { variant: "contained", color: "error", onClick: confirmDeleteBlock, disabled: state.deleteConfirmDialog.loading, startIcon: state.deleteConfirmDialog.loading ? _jsx(CircularProgress, { size: 20 }) : _jsx(DeleteIcon, {}), children: state.deleteConfirmDialog.loading
1050
+ ? t('themeTranslations.deleting')
1051
+ : t('themeTranslations.deleteConfirm') })] })] })] }) }));
812
1052
  }
813
1053
  // Add SplitPane styling
814
1054
  const StyledSplitPane = styled(SplitPane) `
@@ -828,6 +1068,29 @@ const DragHandle = styled('div') `
828
1068
  }
829
1069
  `;
830
1070
  const StyledDashboard = styled(Dashboard) `
1071
+ @keyframes dragShake {
1072
+ 0%,
1073
+ 100% {
1074
+ transform: translateY(0px) rotate(0deg);
1075
+ }
1076
+ 25% {
1077
+ transform: translateY(-2px) rotate(-2deg);
1078
+ }
1079
+ 50% {
1080
+ transform: translateY(2px) rotate(0deg);
1081
+ }
1082
+ 75% {
1083
+ transform: translateY(-2px) rotate(2deg);
1084
+ }
1085
+ 100% {
1086
+ transform: translateY(0px) rotate(0deg);
1087
+ }
1088
+ }
1089
+
1090
+ .drag-shaking {
1091
+ animation: dragShake 0.3s ease-in-out infinite;
1092
+ }
1093
+
831
1094
  .dashboard-content {
832
1095
  display: flex;
833
1096
  flex-direction: row;
@@ -873,12 +1136,12 @@ function CreateResource({ open, onClose }) {
873
1136
  return null;
874
1137
  }
875
1138
  const tenantScope = 'pages-kit-block-studio';
876
- return (_jsx(Suspense, { children: _jsx(BlockletStudio, { mode: "dialog", tenantScope: tenantScope, title: "Pages Kit Blocks", description: "", note: "", introduction: "", logo: "", componentDid: PAGES_KIT_BLOCK_STUDIO_BLOCKLET_DID,
877
- // 透传到 get blocklet resource 的参数
878
- resourcesParams: {}, dependentComponentsMode: "readonly", open: true, setOpen: () => onClose(), onConnected: () => { }, onUploaded: () => { }, onReleased: () => { },
879
- // onOpened={() => onOpened?.()}
880
- // 默认选中的资源
881
- resources: {} }) }));
1139
+ return (_jsx(BlockletStudio, { mode: "dialog", tenantScope: tenantScope, title: "Pages Kit Blocks", description: "", note: "", introduction: "", logo: "", componentDid: PAGES_KIT_BLOCK_STUDIO_BLOCKLET_DID,
1140
+ // 透传到 get blocklet resource 的参数
1141
+ resourcesParams: {}, dependentComponentsMode: "readonly", open: true, setOpen: () => onClose(), onConnected: () => { }, onUploaded: () => { }, onReleased: () => { },
1142
+ // onOpened={() => onOpened?.()}
1143
+ // 默认选中的资源
1144
+ resources: {} }));
882
1145
  }
883
1146
  // 添加 themeTranslations 到 translations 对象
884
1147
  // 这里我们在运行时扩展 translations 对象,而不是修改源文件
@@ -929,6 +1192,21 @@ if (!translations.en.themeTranslations) {
929
1192
  step1: 'Browse and select components from the left menu, or create your own',
930
1193
  step2: 'Preview your component in the center area with responsive design views',
931
1194
  step3: 'Customize properties and configurations in the right panel to bring your ideas to life',
1195
+ // New translations for sorting and improved empty states
1196
+ resetToAlphabeticalSort: 'Reset to Alphabetical Sort',
1197
+ noComponentsFound: 'No Components Found',
1198
+ createFirstComponent: 'Create your first component to get started',
1199
+ createComponent: 'Create Component',
1200
+ componentPropertiesTitle: 'Configure Component',
1201
+ selectComponentToConfigureProperties: 'Select a component from the left panel to configure its properties',
1202
+ saveMetadataFailed: 'Failed to write @metadata.json: {error}',
1203
+ deleteComponent: 'Delete Component',
1204
+ deleteWarning: 'This action cannot be undone. All files in the component directory will be permanently deleted.',
1205
+ deleteConfirmMessage: "Are you sure you want to delete the component '{componentName}'?",
1206
+ deleteConfirm: 'Delete',
1207
+ deleting: 'Deleting...',
1208
+ deleteSuccess: 'Component deleted successfully',
1209
+ deleteFailed: 'Failed to delete component',
932
1210
  };
933
1211
  }
934
1212
  if (!translations.zh.themeTranslations) {
@@ -978,5 +1256,20 @@ if (!translations.zh.themeTranslations) {
978
1256
  step1: '从左侧菜单浏览并选择组件,或创建您自己的组件',
979
1257
  step2: '在中央区域预览您的组件,支持响应式设计视图',
980
1258
  step3: '在右侧面板自定义属性和配置,让您的创意变为现实',
1259
+ // New translations for sorting and improved empty states
1260
+ resetToAlphabeticalSort: '重置为字母排序',
1261
+ noComponentsFound: '未找到组件',
1262
+ createFirstComponent: '创建您的第一个组件来开始',
1263
+ createComponent: '创建组件',
1264
+ componentPropertiesTitle: '配置组件',
1265
+ selectComponentToConfigureProperties: '从左侧面板选择一个组件来配置其属性',
1266
+ saveMetadataFailed: '修改 @metadata.json 失败: {error}',
1267
+ deleteComponent: '删除组件',
1268
+ deleteWarning: '此操作无法撤销。组件目录中的所有文件将被永久删除。',
1269
+ deleteConfirmMessage: "确定要删除组件 '{componentName}' 吗?",
1270
+ deleteConfirm: '删除',
1271
+ deleting: '删除中...',
1272
+ deleteSuccess: '组件删除成功',
1273
+ deleteFailed: '删除组件失败',
981
1274
  };
982
1275
  }