Commit 02fc83cf02f48874629e60ca9f82ec891cdb58ca

Authored by ww
1 parent f00aade9

feat: 实现规则链导入逻辑

@@ -4,12 +4,23 @@ import { defHttp } from '/@/utils/http/axios'; @@ -4,12 +4,23 @@ import { defHttp } from '/@/utils/http/axios';
4 import { RuleChainDetail, RuleChainType } from '/@/views/rule/designer/types/ruleNode'; 4 import { RuleChainDetail, RuleChainType } from '/@/views/rule/designer/types/ruleNode';
5 5
6 enum Api { 6 enum Api {
  7 + SAVE_RULE_CHAINS = '/ruleChain',
7 GET_RULE_CHAINS_DETAIL = '/ruleChain', 8 GET_RULE_CHAINS_DETAIL = '/ruleChain',
8 SAVE = '/ruleChain/metadata', 9 SAVE = '/ruleChain/metadata',
9 GET_RULE_CHAINES = '/ruleChains', 10 GET_RULE_CHAINES = '/ruleChains',
10 GET_RULE_NODE_EVENTS = '/events/RULE_NODE', 11 GET_RULE_NODE_EVENTS = '/events/RULE_NODE',
11 } 12 }
12 13
  14 +export const saveRuleChainDetail = (params: Partial<RuleChainDetail>) => {
  15 + return defHttp.post<RuleChainDetail>(
  16 + {
  17 + url: Api.SAVE_RULE_CHAINS,
  18 + data: params,
  19 + },
  20 + { joinPrefix: false }
  21 + );
  22 +};
  23 +
13 export const getRuleChainDetail = (id: string) => { 24 export const getRuleChainDetail = (id: string) => {
14 return defHttp.get<RuleChainDetail>( 25 return defHttp.get<RuleChainDetail>(
15 { 26 {
@@ -41,8 +41,12 @@ export const MENU_LIST = 'MENU_LIST'; @@ -41,8 +41,12 @@ export const MENU_LIST = 'MENU_LIST';
41 41
42 export const RULE_NODE_LOCAL_CACHE_KEY = 'RULE__NODE__KEY__'; 42 export const RULE_NODE_LOCAL_CACHE_KEY = 'RULE__NODE__KEY__';
43 43
  44 +export const RULE_CHAIN_IMPORT_LOCAL_CACHE_KEY = 'RULE__CHAIN__IMPORT__KEY__';
  45 +
44 export const RULE_NODE_KEY = 'RULE_NODE'; 46 export const RULE_NODE_KEY = 'RULE_NODE';
45 47
  48 +export const RULE_CHAIN_KEY = 'RULE_CHAIN';
  49 +
46 export enum CacheTypeEnum { 50 export enum CacheTypeEnum {
47 SESSION, 51 SESSION,
48 LOCAL, 52 LOCAL,
@@ -18,4 +18,6 @@ export const PageEnum = { @@ -18,4 +18,6 @@ export const PageEnum = {
18 SHARE_PAGE: '/share/:viewType/:id/:publicId', 18 SHARE_PAGE: '/share/:viewType/:id/:publicId',
19 19
20 RULE_CHAIN_DETAIL: '/rule/chain/:id', 20 RULE_CHAIN_DETAIL: '/rule/chain/:id',
  21 +
  22 + RULE_CHAIN_DETAIL_IMPORT: '/rule/chain/import',
21 }; 23 };
@@ -8,6 +8,7 @@ import { buildUUID } from '/@/utils/uuid'; @@ -8,6 +8,7 @@ import { buildUUID } from '/@/utils/uuid';
8 import { isNullOrUnDef } from '/@/utils/is'; 8 import { isNullOrUnDef } from '/@/utils/is';
9 import { useAddNodes } from './useAddNodes'; 9 import { useAddNodes } from './useAddNodes';
10 import { useAddEdges } from './useAddEdges'; 10 import { useAddEdges } from './useAddEdges';
  11 +import { RuleChainEntityType } from '../enum/entity';
11 12
12 export function useBasicDataTransform() { 13 export function useBasicDataTransform() {
13 const nodeConfigMap = new Map<string, NodeData>(); 14 const nodeConfigMap = new Map<string, NodeData>();
@@ -67,8 +68,8 @@ export function useBasicDataTransform() { @@ -67,8 +68,8 @@ export function useBasicDataTransform() {
67 68
68 const sourceNode = indexMap.get(Number(fromIndex)); 69 const sourceNode = indexMap.get(Number(fromIndex));
69 const targetNode = indexMap.get(Number(toIndex)); 70 const targetNode = indexMap.get(Number(toIndex));
70 - const source = sourceNode!.id!.id;  
71 - const target = targetNode!.id!.id; 71 + const source = sourceNode?.id?.id || buildUUID();
  72 + const target = targetNode?.id?.id || buildUUID();
72 const sourceHandle = `${source}${SOURCE_HANDLE}`; 73 const sourceHandle = `${source}${SOURCE_HANDLE}`;
73 const targetHandle = `${target}${TARGET_HANDLE}`; 74 const targetHandle = `${target}${TARGET_HANDLE}`;
74 75
@@ -88,7 +89,7 @@ export function useBasicDataTransform() { @@ -88,7 +89,7 @@ export function useBasicDataTransform() {
88 if (!isNullOrUnDef(firstNodeIndex)) { 89 if (!isNullOrUnDef(firstNodeIndex)) {
89 const targetNode = indexMap.get(firstNodeIndex); 90 const targetNode = indexMap.get(firstNodeIndex);
90 const source = inputNodeId; 91 const source = inputNodeId;
91 - const target = targetNode!.id!.id; 92 + const target = targetNode?.id?.id || buildUUID();
92 const sourceHandle = `${source}$${SOURCE_HANDLE}`; 93 const sourceHandle = `${source}$${SOURCE_HANDLE}`;
93 const targetHandle = `${target}${TARGET_HANDLE}`; 94 const targetHandle = `${target}${TARGET_HANDLE}`;
94 edges.push( 95 edges.push(
@@ -112,7 +113,7 @@ export function useBasicDataTransform() { @@ -112,7 +113,7 @@ export function useBasicDataTransform() {
112 const { layoutX, layoutY, description } = additionalInfo || {}; 113 const { layoutX, layoutY, description } = additionalInfo || {};
113 const { getAddNodesParams } = useAddNodes(); 114 const { getAddNodesParams } = useAddNodes();
114 115
115 - return getAddNodesParams( 116 + const value = getAddNodesParams(
116 { x: layoutX!, y: layoutY! }, 117 { x: layoutX!, y: layoutY! },
117 { 118 {
118 ...config, 119 ...config,
@@ -128,6 +129,10 @@ export function useBasicDataTransform() { @@ -128,6 +129,10 @@ export function useBasicDataTransform() {
128 id: id?.id || buildUUID(), 129 id: id?.id || buildUUID(),
129 } 130 }
130 ); 131 );
  132 +
  133 + if (!id?.id) node.id = { id: value.id, entityType: RuleChainEntityType.RULE_NODE };
  134 +
  135 + return value;
131 } 136 }
132 137
133 function deconstructionNode(nodes: RuleChainType['nodes']) { 138 function deconstructionNode(nodes: RuleChainType['nodes']) {
1 import { GraphEdge, GraphNode, VueFlowStore, pointToRendererPoint } from '@vue-flow/core'; 1 import { GraphEdge, GraphNode, VueFlowStore, pointToRendererPoint } from '@vue-flow/core';
2 -import { getRuleNodeCache, setRuleNodeCache } from './useRuleCopyPaste'; 2 +import { getRuleNodeCache, setRuleNodeCache } from './useRuleChainCache';
3 import { RuleContextMenuEnum } from './useRuleChainContextMenu'; 3 import { RuleContextMenuEnum } from './useRuleChainContextMenu';
4 import { useAddNodes } from './useAddNodes'; 4 import { useAddNodes } from './useAddNodes';
5 import { buildUUID } from '/@/utils/uuid'; 5 import { buildUUID } from '/@/utils/uuid';
src/views/rule/designer/hook/useRuleChainCache.ts renamed from src/views/rule/designer/hook/useRuleCopyPaste.ts
1 import { Edge, Node } from '@vue-flow/core'; 1 import { Edge, Node } from '@vue-flow/core';
2 -import { RULE_NODE_KEY, RULE_NODE_LOCAL_CACHE_KEY } from '/@/enums/cacheEnum'; 2 +import {
  3 + RULE_NODE_KEY,
  4 + RULE_CHAIN_KEY,
  5 + RULE_NODE_LOCAL_CACHE_KEY,
  6 + RULE_CHAIN_IMPORT_LOCAL_CACHE_KEY,
  7 +} from '/@/enums/cacheEnum';
3 import { createLocalStorage } from '/@/utils/cache'; 8 import { createLocalStorage } from '/@/utils/cache';
  9 +import { RuleChainDetail, RuleChainType } from '../types/ruleNode';
4 10
5 const ruleNodeStorage = createLocalStorage({ prefixKey: RULE_NODE_LOCAL_CACHE_KEY }); 11 const ruleNodeStorage = createLocalStorage({ prefixKey: RULE_NODE_LOCAL_CACHE_KEY });
  12 +const ruleChainStorage = createLocalStorage({ prefixKey: RULE_CHAIN_IMPORT_LOCAL_CACHE_KEY });
6 13
7 interface RuleNodeCacheType { 14 interface RuleNodeCacheType {
8 nodes?: Node[]; 15 nodes?: Node[];
@@ -11,6 +18,11 @@ interface RuleNodeCacheType { @@ -11,6 +18,11 @@ interface RuleNodeCacheType {
11 originY?: number; 18 originY?: number;
12 } 19 }
13 20
  21 +export interface RuleChainCacheType {
  22 + ruleChain: Partial<RuleChainDetail>;
  23 + metadata: RuleChainType;
  24 +}
  25 +
14 export const setRuleNodeCache = ({ 26 export const setRuleNodeCache = ({
15 nodes = [], 27 nodes = [],
16 edges = [], 28 edges = [],
@@ -29,6 +41,14 @@ export const getRuleNodeCache = (): RuleNodeCacheType => ruleNodeStorage.get(RUL @@ -29,6 +41,14 @@ export const getRuleNodeCache = (): RuleNodeCacheType => ruleNodeStorage.get(RUL
29 41
30 export const checkHasCacheRuleNode = () => !!getRuleNodeCache(); 42 export const checkHasCacheRuleNode = () => !!getRuleNodeCache();
31 43
  44 +export const getRuleChainImportCache = (): RuleChainCacheType =>
  45 + ruleChainStorage.get(RULE_CHAIN_KEY) || {};
  46 +
  47 +export const setRuleChainImportCache = (value: RuleChainCacheType) =>
  48 + ruleChainStorage.set(RULE_CHAIN_KEY, value);
  49 +
  50 +export const clearRuleChainImportCache = () => ruleChainStorage.remove(RULE_CHAIN_KEY);
  51 +
32 function initRuleNodeStorage() { 52 function initRuleNodeStorage() {
33 const value = ruleNodeStorage.get(RULE_NODE_KEY); 53 const value = ruleNodeStorage.get(RULE_NODE_KEY);
34 value && ruleNodeStorage.set(RULE_NODE_KEY, value); 54 value && ruleNodeStorage.set(RULE_NODE_KEY, value);
@@ -2,7 +2,7 @@ import { NodeMouseEvent } from '@vue-flow/core'; @@ -2,7 +2,7 @@ import { NodeMouseEvent } from '@vue-flow/core';
2 import { useContextMenu } from '../src/components/RuleChainContextMenu'; 2 import { useContextMenu } from '../src/components/RuleChainContextMenu';
3 import { RuleChainContextMenuItemType } from '../src/components/RuleChainContextMenu/index.type'; 3 import { RuleChainContextMenuItemType } from '../src/components/RuleChainContextMenu/index.type';
4 import { ElementsTypeEnum } from '../enum'; 4 import { ElementsTypeEnum } from '../enum';
5 -import { checkHasCacheRuleNode } from './useRuleCopyPaste'; 5 +import { checkHasCacheRuleNode } from './useRuleChainCache';
6 6
7 export enum RuleContextMenuEnum { 7 export enum RuleContextMenuEnum {
8 DETAIL = 'DETAIL', 8 DETAIL = 'DETAIL',
@@ -17,6 +17,8 @@ export enum RuleContextMenuEnum { @@ -17,6 +17,8 @@ export enum RuleContextMenuEnum {
17 UNDO_CHANGE = 'UNDO_CHANGE', 17 UNDO_CHANGE = 'UNDO_CHANGE',
18 18
19 SELECT_ALL = 'SELECT_ALL', 19 SELECT_ALL = 'SELECT_ALL',
  20 +
  21 + CREATE_RULE_CHAIN = 'CREATE_RULE_CHAIN',
20 } 22 }
21 23
22 export enum RuleContextMenuNameEnum { 24 export enum RuleContextMenuNameEnum {
@@ -32,6 +34,8 @@ export enum RuleContextMenuNameEnum { @@ -32,6 +34,8 @@ export enum RuleContextMenuNameEnum {
32 UNDO_CHANGE = '撤销更改', 34 UNDO_CHANGE = '撤销更改',
33 35
34 SELECT_ALL = '选择全部', 36 SELECT_ALL = '选择全部',
  37 +
  38 + CREATE_RULE_CHAIN = '创建规则链',
35 } 39 }
36 40
37 export enum RuleChainContextMenuIconEnum { 41 export enum RuleChainContextMenuIconEnum {
@@ -48,6 +52,8 @@ export enum RuleChainContextMenuIconEnum { @@ -48,6 +52,8 @@ export enum RuleChainContextMenuIconEnum {
48 52
49 SELECT_ALL = 'material-symbols:select-all', 53 SELECT_ALL = 'material-symbols:select-all',
50 54
  55 + CREATE_RULE_CHAIN = 'material-symbols:settings-ethernet',
  56 +
51 // LINK = 'material-symbols:trending-flat', 57 // LINK = 'material-symbols:trending-flat',
52 } 58 }
53 59
@@ -95,6 +101,7 @@ export function useCreateRuleChainContextMenu() { @@ -95,6 +101,7 @@ export function useCreateRuleChainContextMenu() {
95 const createElementsSelectedContextMenu = ( 101 const createElementsSelectedContextMenu = (
96 params: NodeMouseEvent, 102 params: NodeMouseEvent,
97 changeMarker: boolean, 103 changeMarker: boolean,
  104 + hasCreateChainMenuItem = false,
98 elementsType: ElementsTypeEnum.NODE = ElementsTypeEnum.NODE 105 elementsType: ElementsTypeEnum.NODE = ElementsTypeEnum.NODE
99 ): Promise<RuleContextMenuEnum | ''> => { 106 ): Promise<RuleContextMenuEnum | ''> => {
100 return new Promise(async (resolve) => { 107 return new Promise(async (resolve) => {
@@ -106,6 +113,9 @@ export function useCreateRuleChainContextMenu() { @@ -106,6 +113,9 @@ export function useCreateRuleChainContextMenu() {
106 getMenuItem(RuleContextMenuEnum.PASTE, resolve, !checkHasCacheRuleNode()), 113 getMenuItem(RuleContextMenuEnum.PASTE, resolve, !checkHasCacheRuleNode()),
107 getDivider(), 114 getDivider(),
108 getMenuItem(RuleContextMenuEnum.UNSELECTED, resolve), 115 getMenuItem(RuleContextMenuEnum.UNSELECTED, resolve),
  116 + ...(hasCreateChainMenuItem
  117 + ? [getMenuItem(RuleContextMenuEnum.CREATE_RULE_CHAIN, resolve)]
  118 + : []),
109 getMenuItem(RuleContextMenuEnum.DELETE_SELECT, resolve), 119 getMenuItem(RuleContextMenuEnum.DELETE_SELECT, resolve),
110 getDivider(), 120 getDivider(),
111 getMenuItem(RuleContextMenuEnum.APPLY_CHANGE, resolve, !changeMarker), 121 getMenuItem(RuleContextMenuEnum.APPLY_CHANGE, resolve, !changeMarker),
@@ -166,7 +166,8 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { @@ -166,7 +166,8 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) {
166 const menuType = params.node.selected 166 const menuType = params.node.selected
167 ? await createElementsSelectedContextMenu( 167 ? await createElementsSelectedContextMenu(
168 params, 168 params,
169 - unref(useSaveAndRedoActionType.changeMarker) 169 + unref(useSaveAndRedoActionType.changeMarker),
  170 + validateSelectionElementsCanCreateRuleChain()
170 ) 171 )
171 : await createNodeContextMenu(params); 172 : await createNodeContextMenu(params);
172 173
@@ -214,7 +215,8 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { @@ -214,7 +215,8 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) {
214 const menuType = unref(flowActionType.getSelectedElements).length 215 const menuType = unref(flowActionType.getSelectedElements).length
215 ? await createElementsSelectedContextMenu( 216 ? await createElementsSelectedContextMenu(
216 getCreatePanelContextMenuParams(params), 217 getCreatePanelContextMenuParams(params),
217 - unref(useSaveAndRedoActionType.changeMarker) 218 + unref(useSaveAndRedoActionType.changeMarker),
  219 + validateSelectionElementsCanCreateRuleChain()
218 ) 220 )
219 : await createPanelContextMenu( 221 : await createPanelContextMenu(
220 getCreatePanelContextMenuParams(params), 222 getCreatePanelContextMenuParams(params),
@@ -380,5 +382,17 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) { @@ -380,5 +382,17 @@ export function useRuleFlow(options: UseRuleFlowOptionsType) {
380 } as NodeMouseEvent; 382 } as NodeMouseEvent;
381 } 383 }
382 384
  385 + function validateSelectionElementsCanCreateRuleChain() {
  386 + const nodes = unref(flowActionType.getSelectedNodes);
  387 + const edges = unref(flowActionType.getSelectedEdges);
  388 +
  389 + for (const node of nodes) {
  390 + const index = edges.findIndex((edge) => edge.target === node.id || edge.source === node.id);
  391 + if (!~index) return false;
  392 + }
  393 +
  394 + return true;
  395 + }
  396 +
383 return { flowActionType }; 397 return { flowActionType };
384 } 398 }
@@ -3,12 +3,19 @@ import { ComputedRef, computed, ref, unref } from 'vue'; @@ -3,12 +3,19 @@ import { ComputedRef, computed, ref, unref } from 'vue';
3 import { BasicNodeBindData, EdgeData, NodeData } from '../types/node'; 3 import { BasicNodeBindData, EdgeData, NodeData } from '../types/node';
4 import { EntryCategoryComponentEnum } from '../enum/category'; 4 import { EntryCategoryComponentEnum } from '../enum/category';
5 import { useBasicDataTransform } from './useBasicDataTransform'; 5 import { useBasicDataTransform } from './useBasicDataTransform';
6 -import { getRuleChainData, getRuleChainDetail, saveRuleChainData } from '/@/api/ruleDesigner'; 6 +import {
  7 + getRuleChainData,
  8 + getRuleChainDetail,
  9 + saveRuleChainData,
  10 + saveRuleChainDetail,
  11 +} from '/@/api/ruleDesigner';
7 import { ConnectionItemType, RuleChainDetail, RuleChainType } from '../types/ruleNode'; 12 import { ConnectionItemType, RuleChainDetail, RuleChainType } from '../types/ruleNode';
8 import { useInputNode } from './useInputNode'; 13 import { useInputNode } from './useInputNode';
9 import { buildUUID } from '/@/utils/uuid'; 14 import { buildUUID } from '/@/utils/uuid';
10 -import { useRoute } from 'vue-router'; 15 +import { useRoute, useRouter } from 'vue-router';
11 import { RuleChainEntityType } from '../enum/entity'; 16 import { RuleChainEntityType } from '../enum/entity';
  17 +import { PageEnum } from '/@/enums/pageEnum';
  18 +import { clearRuleChainImportCache, getRuleChainImportCache } from './useRuleChainCache';
12 19
13 const ignoreNodeKeys: string[] = [EntryCategoryComponentEnum.INPUT]; 20 const ignoreNodeKeys: string[] = [EntryCategoryComponentEnum.INPUT];
14 21
@@ -21,10 +28,14 @@ export function useSaveAndRedo() { @@ -21,10 +28,14 @@ export function useSaveAndRedo() {
21 28
22 const ROUTE = useRoute(); 29 const ROUTE = useRoute();
23 30
  31 + const ROUTER = useRouter();
  32 +
24 const debugMarker = ref(false); 33 const debugMarker = ref(false);
25 34
26 const getRuleChainId = computed(() => (ROUTE.params as Record<'id', string>).id); 35 const getRuleChainId = computed(() => (ROUTE.params as Record<'id', string>).id);
27 36
  37 + const getIsImportFlag = computed(() => ROUTE.fullPath === PageEnum.RULE_CHAIN_DETAIL_IMPORT);
  38 +
28 const ruleChainDetail = ref<RuleChainDetail>(); 39 const ruleChainDetail = ref<RuleChainDetail>();
29 40
30 const { mergeData, deconstructionData } = useBasicDataTransform(); 41 const { mergeData, deconstructionData } = useBasicDataTransform();
@@ -131,7 +142,9 @@ export function useSaveAndRedo() { @@ -131,7 +142,9 @@ export function useSaveAndRedo() {
131 }; 142 };
132 143
133 async function getCurrentRuleChainDetail() { 144 async function getCurrentRuleChainDetail() {
134 - ruleChainDetail.value = await getRuleChainDetail(unref(getRuleChainId)); 145 + ruleChainDetail.value = unref(getIsImportFlag)
  146 + ? (getImportChainDetail() as RuleChainDetail)
  147 + : await getRuleChainDetail(unref(getRuleChainId));
135 } 148 }
136 149
137 async function handleSaveRuleChain( 150 async function handleSaveRuleChain(
@@ -142,19 +155,35 @@ export function useSaveAndRedo() { @@ -142,19 +155,35 @@ export function useSaveAndRedo() {
142 try { 155 try {
143 loading.value = true; 156 loading.value = true;
144 157
  158 + let ruleChainId = unref(getRuleChainId);
  159 + if (unref(getIsImportFlag)) {
  160 + const detail = await saveRuleChainDetail(unref(ruleChainDetail)!);
  161 + ruleChainDetail.value = detail;
  162 + ruleChainId = detail.id.id;
  163 + }
  164 +
145 const data = await saveRuleChainData({ 165 const data = await saveRuleChainData({
146 connections, 166 connections,
147 nodes, 167 nodes,
148 firstNodeIndex, 168 firstNodeIndex,
149 ruleChainId: { 169 ruleChainId: {
150 entityType: RuleChainEntityType.RULE_CHAIN, 170 entityType: RuleChainEntityType.RULE_CHAIN,
151 - id: unref(getRuleChainId), 171 + id: ruleChainId,
152 }, 172 },
153 }); 173 });
154 174
155 parseRuleChain(data); 175 parseRuleChain(data);
156 176
157 resetChange(); 177 resetChange();
  178 +
  179 + if (unref(getIsImportFlag)) {
  180 + clearRuleChainImportCache();
  181 +
  182 + ROUTER.replace({
  183 + path: `/rule/chain/${ruleChainId}`,
  184 + replace: true,
  185 + });
  186 + }
158 } finally { 187 } finally {
159 loading.value = false; 188 loading.value = false;
160 } 189 }
@@ -164,13 +193,16 @@ export function useSaveAndRedo() { @@ -164,13 +193,16 @@ export function useSaveAndRedo() {
164 try { 193 try {
165 loading.value = true; 194 loading.value = true;
166 195
167 - const data = await getRuleChainData(unref(getRuleChainId)); 196 + const data = unref(getIsImportFlag)
  197 + ? await getImportMetadata()
  198 + : await getRuleChainData(unref(getRuleChainId));
  199 + if (!data) return;
168 200
169 const elements = parseRuleChain(data); 201 const elements = parseRuleChain(data);
170 202
171 flowActionType.setElements(elements); 203 flowActionType.setElements(elements);
172 204
173 - resetChange(); 205 + unref(getIsImportFlag) ? triggerChange() : resetChange();
174 } finally { 206 } finally {
175 loading.value = false; 207 loading.value = false;
176 } 208 }
@@ -201,6 +233,12 @@ export function useSaveAndRedo() { @@ -201,6 +233,12 @@ export function useSaveAndRedo() {
201 triggerChange(); 233 triggerChange();
202 }; 234 };
203 235
  236 + const getImportMetadata = () => {
  237 + return getRuleChainImportCache().metadata;
  238 + };
  239 +
  240 + const getImportChainDetail = () => getRuleChainImportCache().ruleChain;
  241 +
204 return { 242 return {
205 loading, 243 loading,
206 debugMarker, 244 debugMarker,