Skip to content

Commit

Permalink
Merge branch 'feat/vue-devtool'
Browse files Browse the repository at this point in the history
avil13 committed Apr 27, 2024
2 parents 483aea7 + 18b9594 commit 8d38f71
Showing 6 changed files with 271 additions and 66 deletions.
8 changes: 4 additions & 4 deletions packages/act-master/package.json
Original file line number Diff line number Diff line change
@@ -55,10 +55,10 @@
"url": "https://github.com/avil13/vue-act-master.git"
},
"devDependencies": {
"typescript": "^5.4.4",
"vite": "^5.2.8",
"vitest": "^1.4.0",
"vue": "^3.4.21"
"typescript": "^5.4.5",
"vite": "^5.2.10",
"vitest": "^1.5.2",
"vue": "^3.4.25"
},
"dependencies": {
"@vue/devtools-api": "^6.6.1"
9 changes: 5 additions & 4 deletions packages/act-master/src/vue/index.ts
Original file line number Diff line number Diff line change
@@ -33,8 +33,6 @@ declare module '@vue/runtime-core' {
export class VueActMaster {
static actMaster: ActMaster | null = null;

static devtools = false;

static setActMaster(actMaster: ActMaster) {
VueActMaster.actMaster = actMaster;
}
@@ -58,8 +56,11 @@ export class VueActMaster {
}

// devtool
if (VueActMaster.devtools && VueActMaster.actMaster) {
addDevtools(app, VueActMaster.actMaster);
if (VueActMaster.actMaster) {
//@ts-ignore
if (process.env.NODE_ENV === 'development' || __VUE_PROD_DEVTOOLS__) {
addDevtools(app, VueActMaster.actMaster);
}
}
}

Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { expect, it } from 'vitest';

import { ActTest } from '../../..';
import { getActInspectorState } from '../devtools';

it('getActInspectorState', () => {
it.skip('getActInspectorState', () => {
const NAME_1 = 'NAME_1';
const NAME_2 = 'NAME_2';
const NAME_3 = 'NAME_3';
124 changes: 67 additions & 57 deletions packages/act-master/src/vue/plugins/devtools.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
StateBase,
setupDevtoolsPlugin,
type App,
type CustomInspectorNode,
@@ -11,13 +12,25 @@ import {
type ActMasterAction,
} from '../..';

import {
resetMonkeyWatcherState,
hasMonkeyState,
watchOnEventsByMonkey,
getActEventsByMonkeyWatcher,
isShowActionByConfig,
toggleSettingsShowCall,
useSettings,
sortActInspectorTree,
} from './lib/monkey-watch';
import { debounce, getArguments, logSettings } from './lib/utils';

// #region [ colors ]
// const PINK_500 = 0xec4899;
// const BLUE_600 = 0x2563eb;
const BLUE_600 = 0x2563eb;
const LIME_500 = 0x84cc16;
// const CYAN_400 = 0x22d3ee;
const CYAN_400 = 0x22d3ee;
const ORANGE_400 = 0xfb923c;
// const GRAY_100 = 0xf4f4f5
const GRAY_100 = 0xf4f4f5;
const DARK = 0x666666;
// #endregion

@@ -39,44 +52,38 @@ export function addDevtools(app: App, actMaster: ActMaster) {
(api) => {
// Use the API here

// #region [ timeline ]
// api.addTimelineLayer({
// id: ACTIONS_LAYER_ID,
// label: `ActMaster 🥷`,
// color: CYAN_400,
// });

// // gracefully handle errors
// const now = typeof api.now === 'function' ? api.now.bind(api) : Date.now;

// actMaster.addActionWatch((name, status) => {
// // TODO: groupID
// const title = (status === 'START' ? '🛫 ' : '🛬 ') + name;
// const logType = status === 'ERROR' ? 'error' : 'default';

// api.addTimelineEvent({
// layerId: ACTIONS_LAYER_ID,
// event: {
// title,
// time: now(),
// data: {
// info: `${name}: ${status}`,
// },
// logType,
// },
// });
// });

// #endregion

// #region [ inspector ]
const currentSettings = useSettings(api.getSettings());

logSettings('CALL_FILTER', currentSettings.isShowOnlyCalls);

api.addInspector({
id: INSPECTOR_ID,
label: 'ActMaster 🥷',
icon: 'gavel',
treeFilterPlaceholder: 'Filter acts...',
treeFilterPlaceholder: 'Filter acts... Change the filter type ⇒',
stateFilterPlaceholder: ' ',
actions: [
{
icon: 'playlist_add_check',
action: () => {
debounce(200, () => {
toggleSettingsShowCall();
api.setSettings(currentSettings);

logSettings('CALL_FILTER', currentSettings.isShowOnlyCalls);
});
},
tooltip: 'Toggle show only Acts that have been called',
},
{
icon: 'delete',
action: () => {
resetMonkeyWatcherState();
},
tooltip: 'Clears the call-state',
},
],
});

api.on.getInspectorTree((payload) => {
@@ -116,6 +123,10 @@ function getActInspectorTree(
let maxLen = 0;

actions.forEach((_, name) => {
if (!isShowActionByConfig(name)) {
return;
}

if (name.length > maxLen) {
maxLen = name.length;
}
@@ -139,6 +150,14 @@ function getActInspectorTree(
});
}

if (hasMonkeyState(name)) {
tags.push({
label: `call`,
textColor: CYAN_400,
backgroundColor: DARK,
});
}

list.push({
id: name,
label: name,
@@ -149,12 +168,10 @@ function getActInspectorTree(

maxLen += 2;

return list
.sort((a, b) => (a.id > b.id ? 1 : -1))
.map((item) => ({
...item,
label: item.label.padEnd(maxLen, ' '),
}));
return sortActInspectorTree(list).map((item) => ({
...item,
label: item.label.padEnd(maxLen, ' '),
}));
}

export function getActInspectorState(
@@ -170,26 +187,17 @@ export function getActInspectorState(

const subs = subsMap.get(actName) || [];

let errorHandlerName =
act?.errorHandlerEventName || config?.errorHandlerEventName || '❌';
watchOnEventsByMonkey(actMaster);

let errorHandlerName = act?.$onError || config?.errorHandlerEventName || '❌';
if (errorHandlerName === actName) {
errorHandlerName = '';
}

const fnBody = act?.exec.toString();
const argRegExp = /[^\(]*\(([^\)]*)\)/;
const match = fnBody?.match(argRegExp);
let args: string[] = [];

if (match && match[1]) {
args = match[1]
.split(',')
.map((arg) => arg.trim())
.filter((arg) => arg.length > 0);
}
const args: string[] = getArguments(act?.exec);

return {
[actName]: [
[`Act name: "${actName}"`]: [
{
key: 'subscribers',
value: subs.length,
@@ -201,21 +209,23 @@ export function getActInspectorState(
},
{
key: 'watch',
value: (act?.watch || []).join(', '),
value: (act?.$watch || []).join(', '),
},
{
key: 'isSingleExec',
value: act?.isSingleExec ? 'true' : 'false',
value: act?.$isSingleton || false,
},
{
key: 'validateInput',
value: act?.validateInput ? 'true' : 'false',
value: act?.$validate || false,
},
{
key: 'arguments',
value: `(${args.join(', ')})`,
},
],

'Data passed:': getActEventsByMonkeyWatcher(actName, act),
};
}

128 changes: 128 additions & 0 deletions packages/act-master/src/vue/plugins/lib/monkey-watch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { CustomInspectorNode, StateBase } from '@vue/devtools-api';
import { getArguments, getCurrentTime } from './utils';
import { type ActMaster, type ActMasterAction } from '../../..';

const eventMonkeyState = new Map<string, StateBase[]>();
const eventMonkeyStateSort = new Set<string>();
const config = {
isShowOnlyCalls: false,
};

export function watchOnEventsByMonkey(actMaster: ActMaster) {
// @ts-ignore
if (actMaster._old_emit) {
return;
}

//@ts-ignore
actMaster._old_emit = actMaster.emit.bind(actMaster);

//@ts-ignore
actMaster.emit = (...args: any[]) => {
const [name, ...props] = args;
const time = getCurrentTime();

addStateByMonkey(name, {
//@ts-ignore
_isArgument: true, // path to define argument
key: `Arguments ${time}`,
value: props,
});

//@ts-ignore
return actMaster._old_emit(...args).then((result) => {
addStateByMonkey(name, {
key: `Result ${time}`,
value: result,
});

return result;
});
};
}

export function addStateByMonkey(name: string, state: StateBase): void {
let items: StateBase[] = eventMonkeyState.get(name) || [];

if (items.length > 20) {
items = items.slice(-10);
}

items.push(state);
eventMonkeyState.set(name, items);
if (eventMonkeyStateSort.has(name)) {
eventMonkeyStateSort.delete(name);
}
eventMonkeyStateSort.add(name);
}

export function getActEventsByMonkeyWatcher(
actName: string,
act: ActMasterAction | null
): StateBase[] {
const list = eventMonkeyState.get(actName) || [];

const argumentNames = getArguments(act?.exec);

return list.map((stateBase) => {
//@ts-ignore
if (!stateBase._isArgument) {
return stateBase;
}

let value: Record<string, any> = {};

stateBase.value.forEach((v: any, i: number) => {
value[argumentNames[i]] = v;
});

return {
...stateBase,
value,
};
});
}

export function hasMonkeyState(name: string): boolean {
return eventMonkeyState.has(name);
}

export function resetMonkeyWatcherState() {
eventMonkeyState.clear();
eventMonkeyStateSort.clear();
}

//
export function isShowActionByConfig(name: string): boolean {
if (config.isShowOnlyCalls) {
return hasMonkeyState(name);
}
return true;
}

export function toggleSettingsShowCall() {
return (config.isShowOnlyCalls = !config.isShowOnlyCalls);
}

export function useSettings(conf: typeof config | any) {
Object.assign(config, conf || {});
return config;
}

export function sortActInspectorTree(list: CustomInspectorNode[]) {
if (config.isShowOnlyCalls) {
const orderNames = [...eventMonkeyStateSort];
const orderIndexes = new Map<string, number>(
orderNames.map((item, index) => [item, index])
);

return list.sort((a, b) => {
const indexOfA = orderIndexes.get(a.label) || -1;
const indexOfB = orderNames.indexOf(b.label) || -1;

return indexOfA - indexOfB;
});
}

return list.sort((a, b) => (a.id > b.id ? 1 : -1));
}
65 changes: 65 additions & 0 deletions packages/act-master/src/vue/plugins/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
export function getArguments(fn?: Function): string[] {
const argRegExp = /[^\(]*\(([^\)]*)\)/;
const match = fn?.toString().match(argRegExp);

if (match && match[1]) {
return match[1]
.split(',')
.map((arg) => arg.trim())
.filter((arg) => arg.length > 0);
}
return [];
}

export function getCurrentTime() {
const now = new Date();
return `${now.getHours().toString().padStart(2, '0')}:${now
.getMinutes()
.toString()
.padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}.${now
.getMilliseconds()
.toString()
.padStart(3, '0')}`;
}

// #region [ debounce ]
let debounceTimer: Record<any, any> = {};

/**
*
* @param {*} wait
* @param {*} func
* @param {*} args
* @example
* debounce(200, (v) => {
* this.$store.commit('api/search', v);
* }, val);
*/
export function debounce(wait: number, func: Function, args?: any) {
debounceTimer = debounceTimer || {};

if (debounceTimer[func.toString()]) {
clearTimeout(debounceTimer[func.toString()]);
}

debounceTimer[func.toString()] = setTimeout(() => {
func(args);
debounceTimer[func.toString()] = null;
}, wait);
}

// #endregion


export function logSettings(type: 'CALL_FILTER', value: any) {
if (type === 'CALL_FILTER') {
const title = value ? 'Acts filtered by call' : 'Show all Acts';
console.log(
`%c ActMaster 🥷 %c "${title}"\n%c Don\'t forget to update the list %c ⟳ `,
'background:#44bd90; color:#fff; border-radius: 3px; padding: 3px;',
value ? 'background:transparent; color:#e56e17;' : 'background:transparent; color:#44bd90;',
'background:transparent; color:#44bd90;',
'background:#ffc759; color:#fff; border-radius: 3px; padding: 3px;'
);
}
}

0 comments on commit 8d38f71

Please sign in to comment.