diff --git a/.gitignore b/.gitignore index 386ac2b..fd0c1e0 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ data.json # Exclude macOS Finder (System Explorer) View States .DS_Store +package-lock.json diff --git a/src/ClassShape.ts b/src/ClassShape.ts index a73a457..651895d 100644 --- a/src/ClassShape.ts +++ b/src/ClassShape.ts @@ -30,110 +30,124 @@ Graph.registerPortLayout( /** * 類聲明 圖形定義 */ -Graph.registerNode( - 'clazz-shape', - { - // 最上層 class 名稱 - inherit: 'rect', - markup: [ - { - tagName: 'rect', - selector: 'body', - }, - { - tagName: 'text', - selector: 'label', - }, - ], - attrs: { - rect: { - strokeWidth: 1, - stroke: '#e5885c', - fill: '#fd9a6c', - }, - label: { - fontWeight: 'bold', - fill: '#ffffff', - fontSize: 14, - textAnchor: 'middle', - textVerticalAnchor: 'middle' - }, +let clazzShapeDef = { + // 最上層 class 名稱 + inherit: 'rect', + markup: [ + { + tagName: 'rect', + selector: 'body', }, - // 該 class 所屬的方法列表 - ports: { - groups: { - list: { - position: 'classFuncPosition', - markup: [ - { - tagName: 'rect', - selector: 'func', - }, - { - tagName: 'text', - selector: 'funcName', - }, - { - tagName: 'circle', - selector: 'funcInCircle' - }, - { - tagName: 'circle', - selector: 'funcOutCircle' - }, - { - tagName: 'circle', - selector: 'toggle' - }, - { - tagName: 'text', - selector: 'toggleText' - } - ], - attrs: { - func: { - strokeWidth: 1, - stroke: '#e5885c', - fill: '#FFFFFF', - }, - funcName: { - fontSize: 13, - textAnchor: 'middle', - textVerticalAnchor: 'middle' - }, - funcInCircle: { - r: 5, - stroke: '#efab7c', - strokeWidth: 1, - fill: '#fff' - }, - funcOutCircle: { - r: 5, - stroke: '#efab7c', - strokeWidth: 1, - fill: '#fff' - }, - toggle: { - r: 7, - stroke: '#b2bec3', - strokeWidth: 2, - fill: '#fff', - cursor: 'pointer', - }, - toggleText: { - fontSize: 12, - fontWeight: 800, - fill: '#b2bec3', - textAnchor: 'middle', - textVerticalAnchor: 'middle', - 'pointer-events': 'none', // 避免按鈕不靈敏 - cursor: 'pointer', - } + { + tagName: 'text', + selector: 'label', + }, + ], + attrs: { + rect: { + strokeWidth: 1, + stroke: '#e5885c', + fill: '#fd9a6c', + }, + label: { + fontWeight: 'bold', + fill: '#ffffff', + fontSize: 14, + textAnchor: 'middle', + textVerticalAnchor: 'middle' + }, + }, + // 該 class 所屬的方法列表 + ports: { + groups: { + list: { + position: 'classFuncPosition', + markup: [ + { + tagName: 'rect', + selector: 'func', }, + { + tagName: 'text', + selector: 'funcName', + }, + { + tagName: 'circle', + selector: 'funcInCircle' + }, + // { + // tagName: 'circle', + // selector: 'funcOutCircle' + // }, + { + tagName: 'circle', + selector: 'toggle' + }, + { + tagName: 'text', + selector: 'toggleText' + } + ], + attrs: { + + func: { + strokeWidth: 1, + stroke: '#e5885c', + fill: '#FFFFFF', + }, + funcName: { + fontSize: 13, + textAnchor: 'middle', + textVerticalAnchor: 'middle' + }, + funcInCircle: { + r: 5, + stroke: '#efab7c', + strokeWidth: 1, + fill: '#fff' + }, + // funcOutCircle: { + // r: 5, + // stroke: '#efab7c', + // strokeWidth: 1, + // fill: '#fff' + // }, + toggle: { + r: 7, + stroke: '#b2bec3', + strokeWidth: 2, + fill: '#fff', + cursor: 'pointer', + }, + toggleText: { + fontSize: 12, + fontWeight: 800, + fill: '#b2bec3', + textAnchor: 'middle', + textVerticalAnchor: 'middle', + 'pointer-events': 'none', // 避免按鈕不靈敏 + cursor: 'pointer', + } }, }, }, }, +}; +clazzShapeDef.ports.groups.list.markup.push( { + tagName: 'circle', + selector: 'funcOutCircle' +}); +clazzShapeDef.ports.groups.list.attrs.funcOutCircle = { + r: 5, + stroke: '#efab7c', + strokeWidth: 1, + fill: '#fff' +} + + +Graph.registerNode( + 'clazz-shape', + clazzShapeDef, true, ); @@ -170,7 +184,6 @@ class ClassShape { }; classShape.ports = []; - // 定義下方函數名稱方塊 funcNames.forEach((funcName: string) => { diff --git a/src/Graph.ts b/src/Graph.ts index 9c2a7db..0d7df5d 100644 --- a/src/Graph.ts +++ b/src/Graph.ts @@ -25,14 +25,18 @@ export const initGraph = function (codeBlocks: CodeBlockContent[]) { }, }); - // 將 代碼塊字串 集合 解析成 CodeData 對象集合,並將相同 className 的對象進行合併 + // 將 代碼塊字串 集合 解析成 CodeData 對象集合 let codeDatas: CodeData[] = []; codeBlocks.forEach((codeBlock: CodeBlockContent) => { const codeData: CodeData = myUtils.parseCodeData(codeBlock); codeDatas.push(codeData); }); + + // 將相同 className 的對象進行合併 codeDatas = myUtils.mergeCodeDatas(codeDatas); + // 設置 CodeDataFunc 中的 calleds 屬性 + myUtils.setCalleds(codeDatas); // 將 CodeData 對象列表 繪製成 類圖形、代碼塊圖形 for (const codeData of codeDatas) { diff --git a/src/Utils.ts b/src/Utils.ts index 3e452cc..534d819 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,5 +1,5 @@ import hljs from "highlight.js"; -import {CodeBlockContent} from "./CodeBlockShape"; +import { CodeBlockContent } from "./CodeBlockShape"; type CodeData = { className: string, // 類名稱 @@ -10,7 +10,8 @@ type CodeDataFunc = { language: string, // 程式語言 name: string, // 函數名稱 code: string, // 代碼塊字串 - calls: CodeDataFuncCall[] // 調用的函數集合 + calls: CodeDataFuncCall[], // 調用的函數集合 + calleds: CodeDataFuncCall[], // 被調用的函數集合 }; type CodeDataFuncCall = { @@ -21,6 +22,9 @@ type CodeDataFuncCall = { export const CLASS_SHAPE_ID_TAG = "class-"; export const CODE_BLOCK_ID_TAG = 'code-block-'; +// Key: 函數的唯一標示符,Value: 函數對象 +const funcNameToFunc = new Map(); + class Utils { /** @@ -44,7 +48,7 @@ class Utils { // 創建一個新容器,將 HTML 代碼加入到容器中 const node = document.createElement('div'); - node.innerHTML = '
' + hljs.highlight(codeText, {language: 'java'}).value + '
'; + node.innerHTML = '
' + hljs.highlight(codeText, { language: 'java' }).value + '
'; document.body.appendChild(node); // 計算 容器高度 @@ -94,8 +98,15 @@ class Utils { // 從 註釋塊 中獲取 類名、方法名、調用的函數集合 let className = ''; - let functionName = ''; const calls: CodeDataFuncCall[] = []; + const calleds: CodeDataFuncCall[] = []; + let codeDataFunc = { + language: lang, + name: '', + code: content, + calls: calls, + calleds: calleds + }; commentLines.forEach((comment) => { @@ -106,16 +117,20 @@ class Utils { // 獲取 方法名 if (comment.indexOf(functionTag) != -1) { - functionName = comment.substring(comment.indexOf(functionTag) + functionTag.length).trim(); + codeDataFunc.name = comment.substring(comment.indexOf(functionTag) + functionTag.length).trim(); + + // 保存 到 共用 Map 中 + funcNameToFunc.set(this.getClassShapeId(className, codeDataFunc.name, 'function'), codeDataFunc); } // 獲取 調用的函數 if (comment.indexOf(callTag) != -1) { const callStr = comment.substring(comment.indexOf(callTag) + callTag.length).trim(); - calls.push({ + const call = { className: callStr.substring(0, callStr.indexOf('@')).trim(), functionName: callStr.substring(callStr.indexOf('@') + 1).trim() - }); + } + calls.push(call); } }); @@ -123,12 +138,7 @@ class Utils { // 封裝結果 const result: CodeData = { className: className, - funcs: [{ - language: lang, - name: functionName, - code: content, - calls: calls - }] + funcs: [codeDataFunc] }; return result; @@ -190,9 +200,33 @@ class Utils { return result; } + + /** + * 設置所有 CodeData 中的 CodeDataFunc 的 calleds 屬性 + * @param codeDatas 要進行處理的 CodeData 集合 + */ + public setCalleds(codeDatas: CodeData[]) { + + // 遍歷 呼叫函數 的 對象屬性 + for (const codeData of codeDatas) { + for (const codeDataFunc of codeData.funcs) { + for (const call of codeDataFunc.calls) { + + // 取得 被調用的 函數對象 + let calledCodeDataFunc: CodeDataFunc | undefined = funcNameToFunc.get(this.getClassShapeId(call.className, call.functionName, 'function')); + + // 將 呼叫函數對象的屬性 加入到 被調用的函數對象 的 被調用函數集合 中 + calledCodeDataFunc?.calleds.push({ + className: codeDataFunc.name, + functionName: codeData.className + }); + } + } + } + } } -export {Utils}; -export type {CodeData, CodeDataFunc}; +export { Utils }; +export type { CodeData, CodeDataFunc };