代码整理

This commit is contained in:
xu_yanfeng
2025-02-03 13:22:09 +08:00
parent ac6339ee8c
commit 2aca3a9867
126 changed files with 79 additions and 12932 deletions

View File

@@ -0,0 +1,79 @@
import { debugLog, Page, PluginEvent } from "../../core/types";
import { FrameDetails } from "../../views/devtools/data";
import { Terminal } from "../terminal";
import { TabInfo } from "./tabInfo";
export class Content {
public frameID: number = 0;
/**
* port的名字标识
*/
public name: string = Page.None;
/**
* tab.id作为唯一标识
*/
public tabID: number | null = null;
public title: string = "";
public url: string = "";
protected port: chrome.runtime.Port | null = null;
public tab: chrome.tabs.Tab | null = null;
public terminal: Terminal = null;
/**
* 是否正在使用
*/
public using: boolean = false;
private tabInfo: TabInfo | null = null;
constructor(tab: chrome.tabs.Tab, port: chrome.runtime.Port, tabInfo: TabInfo) {
this.tabInfo = tabInfo;
this.port = port;
this.tab = tab;
this.name = port.name;
this.tabID = tab.id;
this.url = port.sender.url;
this.title = tab.title;
this.terminal = new Terminal(`Port-${this.name}`);
port.onMessage.addListener((data: any, port: chrome.runtime.Port) => {
const event = PluginEvent.create(data);
debugLog && console.log(...this.terminal.chunkMessage(event.toChunk()));
if (event.valid && this.onMessage) {
this.onMessage(event);
} else {
debugLog && console.log(...this.terminal.log(JSON.stringify(data)));
}
});
port.onDisconnect.addListener((port: chrome.runtime.Port) => {
debugLog && console.log(...this.terminal.disconnect(""));
this.onDisconnect(port);
});
this.frameID = port.sender.frameId || 0;
}
getFrameDetais(): FrameDetails {
return {
tabID: this.tabID,
url: this.url,
frameID: this.frameID,
};
}
private onDisconnect(disPort: chrome.runtime.Port) {
this.tabInfo.removePort(this);
}
public onMessage(data: PluginEvent) {
// content的数据一般都是要同步到devtools
if (data.isTargetDevtools()) {
if (this.tabInfo.devtool) {
this.tabInfo.devtool.send(data);
} else {
debugger;
}
} else {
debugger;
}
}
send(data: PluginEvent) {
if (this.port) {
this.port.postMessage(data);
}
}
}

View File

@@ -0,0 +1,63 @@
import { debugLog, Msg, Page, PluginEvent, RequestUseFrameData, ResponseSupportData } from "../../core/types";
import { Terminal } from "../terminal";
import { TabInfo } from "./tabInfo";
export class Devtools {
/**
* port的名字标识
*/
public name: string = Page.None;
/**
* tab.id作为唯一标识
*/
public tabID: number | null = null;
public title: string = "";
public url: string = "";
protected port: chrome.runtime.Port | null = null;
public tab: chrome.tabs.Tab | null = null;
public terminal: Terminal = null;
public tabInfo: TabInfo | null = null;
constructor(port: chrome.runtime.Port, tabInfo: TabInfo) {
this.tabInfo = tabInfo;
this.port = port;
this.name = port.name;
this.url = port.sender.url;
this.terminal = new Terminal(`Port-${this.name}`);
port.onMessage.addListener((data: any, port: chrome.runtime.Port) => {
const event = PluginEvent.create(data);
debugLog && console.log(...this.terminal.chunkMessage(event.toChunk()));
if (event.valid && this.onMessage) {
this.onMessage(event);
} else {
debugLog && console.log(...this.terminal.log(JSON.stringify(data)));
}
});
port.onDisconnect.addListener((port: chrome.runtime.Port) => {
debugLog && console.log(...this.terminal.disconnect(""));
if (this.onDisconnect) {
this.onDisconnect(port);
}
});
}
public onDisconnect(port: chrome.runtime.Port) {
this.tabInfo.removeDevtools(this);
}
public onMessage(data: PluginEvent) {
if (data.msg === Msg.RequestUseFrame) {
// 因为devtool是定时器驱动这里改变了content后续就会将数据派发到对应的content中去
this.tabInfo.useFrame((data.data as RequestUseFrameData).id);
} else {
// 从devtools过来的消息统一派发到目标content中
if (data.check(Page.Devtools, Page.Background)) {
data.reset(Page.Background, Page.Content);
this.tabInfo.sendMsgToContent(data);
}
}
}
send(data: PluginEvent) {
if (this.port) {
this.port.postMessage(data);
}
}
}

View File

@@ -0,0 +1,57 @@
import { debugLog, Page, PluginEvent } from "../../core/types";
import { getDevToolsInspectorId } from "../../core/util";
import { Terminal } from "../terminal";
import { tabMgr } from "./tabMgr";
const terminal = new Terminal(Page.Background);
debugLog && console.log(...terminal.init());
chrome.runtime.onConnect.addListener((port: chrome.runtime.Port) => {
if (port.name === Page.Content) {
const tab: chrome.tabs.Tab | undefined = port.sender?.tab;
const tabID = tab.id;
if (tabID === undefined || tabID <= 0) {
return;
}
tabMgr.addTab(tab, port);
} else if (port.name.startsWith(Page.Devtools)) {
const id = getDevToolsInspectorId(port.name);
const tab = tabMgr.findTab(id);
if (tab) {
tab.addDevtools(port);
} else {
debugger;
}
}
});
chrome.runtime.onMessage.addListener((request: PluginEvent, sender: any, sendResponse: any) => {
const event = PluginEvent.create(request);
const tabID = sender.tab.id;
const tabInfo = tabMgr.findTab(tabID);
if (tabInfo) {
if (event.check(Page.Content, Page.Background)) {
// 监听来自content.js发来的事件将消息转发到devtools
event.reset(Page.Background, Page.Devtools);
console.log(`%c[Message]url:${sender.url}]\n${JSON.stringify(request)}`, "color:green");
if (tabInfo.devtool) {
tabInfo.devtool.send(request);
}
}
}
});
chrome.tabs.onActivated.addListener(({ tabId, windowId }) => {});
chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
//
});
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
// 页面发生刷新,通知重新生成数据
if (changeInfo.status === "complete") {
const { id } = tab;
// -1为自己
if (id >= 0) {
const tabInfo = tabMgr.findTab(id);
if (tabInfo) {
tabInfo.useFrame(0);
}
}
}
});

View File

@@ -0,0 +1,80 @@
import { Msg, Page, PluginEvent, ResponseUpdateFramesData, ResponseUseFrameData } from "../../core/types";
import { FrameDetails } from "../../views/devtools/data";
import { Content } from "./content";
import { Devtools } from "./devtools";
export class TabInfo {
/**
* 标签的ID
*/
public tabID: number;
constructor(tabID: number) {
this.tabID = tabID;
}
/**
* 因为iframe的原因可能对应多个主iframe的id是0
*/
public contentArray: Array<Content> = [];
addContent(tab: chrome.tabs.Tab, port: chrome.runtime.Port) {
// 新的content连上来需要更新devtools
let portContent: Content = new Content(tab, port, this);
this.contentArray.push(portContent);
this.updateFrames();
}
public removePort(item: Content) {
let index = this.contentArray.findIndex((el) => el === item);
if (index > -1) {
this.contentArray.splice(index, 1);
this.updateFrames();
// 使用第一个frame
if (this.contentArray.length) {
const id = this.contentArray[0].frameID;
this.useFrame(id);
}
}
}
public removeDevtools(item: Devtools) {
this.devtool = null;
}
useFrame(id: number) {
this.contentArray.map((content) => {
content.using = content.frameID === id;
});
this.sendMsgToDevtool(Msg.ResponseUseFrame, { id } as ResponseUseFrameData);
}
/**
* 通知devtools更新
*/
private updateFrames() {
const data: FrameDetails[] = [];
this.contentArray.forEach((item) => {
const frame = (item as Content).getFrameDetais();
data.push(frame);
});
this.sendMsgToDevtool(Msg.ResponseUpdateFrames, data as ResponseUpdateFramesData);
}
private sendMsgToDevtool(msg: Msg, data: any) {
if (this.devtool) {
const event = new PluginEvent(Page.Background, Page.Devtools, msg, data);
this.devtool.send(event);
}
}
public sendMsgToContent(data: PluginEvent) {
const content = this.contentArray.find((el) => el.using);
if (content) {
content.send(data);
} else {
// 当页面没有完成刷新状态时conent并没有using就会触发此处逻辑
// 在页面完成刷新后会主动设置为using
}
}
public devtool: Devtools | null = null;
addDevtools(port: chrome.runtime.Port) {
if (this.devtool === null) {
this.devtool = new Devtools(port, this);
} else {
debugger;
}
}
}

View File

@@ -0,0 +1,21 @@
import { TabInfo } from "./tabInfo";
export class TabMgr {
/**
* chrome打开的所有标签页面
*/
public tabArray: TabInfo[] = [];
public findTab(id: number): TabInfo | null {
return this.tabArray.find((el) => el.tabID === id) || null;
}
addTab(tab: chrome.tabs.Tab, port: chrome.runtime.Port) {
let tabInfo = this.findTab(tab.id);
if (!tabInfo) {
tabInfo = new TabInfo(tab.id);
this.tabArray.push(tabInfo);
}
tabInfo.addContent(tab, port);
}
}
export const tabMgr = new TabMgr();

22
src/scripts/const.ts Normal file
View File

@@ -0,0 +1,22 @@
import { GA_EventName } from "../ga/type";
export enum DocumentEvent {
/**
* 从inject到content的事件
*/
Inject2Content = "inject2content",
/**
* 从content到inject的事件
*/
Content2Inject = "content2inject",
EngineVersion = "engineVersion",
GoogleAnalytics = "googleAnalytics",
LoadInjectCss = "load-inject-css",
InspectorClear = "inspector_clear",
GameInspectorBegan = "GameInspectorBegan",
GameInspectorEnd = "GameInspectorEnd",
}
export interface GoogleAnalyticsData {
event: GA_EventName;
params: string;
}

View File

@@ -0,0 +1,85 @@
// content.js 和原始界面共享DOM具有操作dom的能力
// 但是不共享js,要想访问页面js,只能通过注入的方式
import { ChromeConst } from "cc-plugin/src/chrome/const";
import { debugLog, Msg, Page, PluginEvent } from "../../core/types";
import { ga } from "../../ga";
import { GA_EventName } from "../../ga/type";
import { DocumentEvent, GoogleAnalyticsData } from "../const";
import { Terminal } from "../terminal";
const terminal = new Terminal(Page.Content);
debugLog && console.log(...terminal.init());
// #region 注入脚本
export function injectScript(url: string) {
if (chrome && chrome.runtime && chrome.runtime.getURL) {
let content = chrome.runtime.getURL(url);
const script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", content);
script.onload = function () {
// 加载注入脚本界面的css
let css = chrome.runtime.getURL(ChromeConst.css.inject_view);
const event = new CustomEvent(DocumentEvent.LoadInjectCss, { detail: [css] });
document.dispatchEvent(event);
document.head.removeChild(script);
};
document.head.appendChild(script);
debugLog && console.log(...terminal.green(`inject script success: ${content}`));
} else {
debugLog && console.log(...terminal.red("inject script failed"));
}
}
document.addEventListener(DocumentEvent.EngineVersion, async (event: CustomEvent) => {
const version: string = event.detail;
if (version) {
ga.fireEventWithParam(GA_EventName.EngineVersion, version);
}
});
document.addEventListener(DocumentEvent.GoogleAnalytics, (event: CustomEvent) => {
const data: GoogleAnalyticsData = event.detail;
if (data && data.event) {
if (data.params) {
ga.fireEventWithParam(data.event, data.params);
} else {
ga.fireEvent(data.event);
}
}
});
// #region 和Inject通讯
document.addEventListener(DocumentEvent.Inject2Content, (event: CustomEvent) => {
let data: PluginEvent = PluginEvent.create(event.detail);
if (data.valid && data.check(Page.Inject, Page.Content)) {
debugLog && console.log(...terminal.chunkMessage(data.toChunk()));
data.reset(Page.Content, Page.Devtools);
if (connect) {
// 接受来自inject.js的消息数据,然后中转到background.js
connect.postMessage(data);
} else {
debugLog && console.log(...terminal.log(`connect is null`));
console.log("connect is null");
}
} else {
throw new Error(`invalid data: ${event.detail}`);
}
});
// #region 和background通讯
let connect: chrome.runtime.Port = chrome.runtime.connect({ name: Page.Content });
connect.onDisconnect.addListener(() => {
debugLog && console.log(...terminal.disconnect(""));
connect = null;
});
connect.onMessage.addListener((data: PluginEvent, sender: chrome.runtime.Port) => {
const event = PluginEvent.create(data);
if (event.valid && event.check(Page.Background, Page.Content)) {
debugLog && console.log(...terminal.chunkMessage(event.toChunk()));
event.reset(Page.Content, Page.Inject);
const e = new CustomEvent(DocumentEvent.Content2Inject, { detail: event });
debugLog && console.log(...terminal.chunkSend(event.toChunk()));
document.dispatchEvent(e);
} else {
throw new Error(`invalid data: ${data}`);
}
});
injectScript(ChromeConst.script.inject);

View File

@@ -0,0 +1,80 @@
<template>
<div v-show="ads.length" class="ad">
<div class="body" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave">
<div class="list ccui-scrollbar">
<Banner v-for="(item, index) in ads" :data="item" :key="index"></Banner>
</div>
</div>
</div>
</template>
<script lang="ts">
import ccui from "@xuyanfeng/cc-ui";
import { defineComponent, onMounted, onUnmounted, ref, toRaw } from "vue";
import { GA_EventName } from "../../ga/type";
import Banner from "./banner.vue";
import { emitter, Msg } from "./const";
import { AdItem, getAdData } from "./loader";
import { ga } from "./util";
const { CCButton } = ccui.components;
export default defineComponent({
name: "ad",
components: { CCButton, Banner },
setup(props, { emit }) {
onMounted(async () => {
const data = await getAdData();
if (!data) {
console.log(`get ad failed`);
return;
}
if (!data.valid) {
console.log(`set ad forbidden`);
return;
}
if (!data.data.length) {
console.log(`not find any ad`);
return;
}
ads.value = data.data;
console.log("get ads ", toRaw(ads.value));
ga(GA_EventName.ShowAd);
});
onUnmounted(() => {});
function testBanner() {
const data = new AdItem();
data.name = "ad test 11111111111 11111111111 44444444444444 5555555555555 111111111111111111 2222222222222222 33333333333333 444444444444444";
data.store = "http://www.baidu.com";
emitter.emit(Msg.ChangeAd, data);
}
let ads = ref<AdItem[]>([]);
return {
ads,
onMouseEnter() {},
onMouseLeave() {},
};
},
});
</script>
<style lang="less" scoped>
@color-bg: #8d8d8da6;
@color-hover: #f9c04e;
@color-active: #ffaa00;
.ad {
display: flex;
flex-direction: column;
.body {
display: flex;
overflow: hidden;
.list {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
overflow: auto;
}
}
}
</style>

View File

@@ -0,0 +1,390 @@
<template>
<div class="ad" ref="rootEl" v-show="!picking" @contextmenu.prevent="onContextMenuRoot" @mouseleave="onMouseLeaveRoot" @mouseenter="onMouseEnterCocosLogo">
<div class="title">
<div class="btns" v-show="showBtns">
<div v-for="(item, index) in listArray" :key="index" class="list" @click="item.click($event, item)" :title="item.txt" v-show="item.visible">
<i class="iconfont icon" :class="item.icon" @contextmenu.prevent.stop="item.contextmenu"></i>
</div>
</div>
<i class="iconfont icon_cocos cocos" @mousedown="onMouseDown" @click="onCocosLogoClick"></i>
</div>
<!-- <Memory></Memory> -->
<CCDialog></CCDialog>
<CCMenu></CCMenu>
</div>
</template>
<script lang="ts">
import ccui from "@xuyanfeng/cc-ui";
import { IUiMenuItem } from "@xuyanfeng/cc-ui/types/cc-menu/const";
import { storeToRefs } from "pinia";
import { defineComponent, onMounted, ref, toRaw } from "vue";
import { GA_EventName } from "../../ga/type";
import { DocumentEvent } from "../const";
import { inspectTarget } from "../inject/inspect-list";
import Ad from "./ad.vue";
import Banner from "./banner.vue";
import Memory from "./memory.vue";
import { appStore } from "./store";
import { ga } from "./util";
declare const cc: any;
const { CCDialog, CCMenu } = ccui.components;
interface ListItem {
icon: string;
txt: string;
visible: boolean;
/**
* 点击回调
*/
click: (event: MouseEvent, item: ListItem) => void;
contextmenu: (event: MouseEvent) => void;
}
export default defineComponent({
name: "ad",
components: { CCDialog, Banner, Memory, CCMenu },
setup() {
function randomSupport(): { icon: string; title: string } {
const arr = [
{ icon: "icon_shop_cart", title: "冲冲冲" },
{ icon: "icon_good", title: "赞一个" },
{ icon: "icon_coffe", title: "请我喝杯咖啡" },
];
const idx = Math.floor(Math.random() * arr.length);
return arr[idx];
}
const store = appStore();
store.init();
const rnd = randomSupport();
const { config } = storeToRefs(appStore());
const listArray = ref<ListItem[]>([
{
icon: `${rnd.icon} ani_shop_cart`,
txt: rnd.title,
contextmenu: () => {},
visible: true,
click: () => {
ccui.dialog.showDialog({
title: "Recommended Plugins",
comp: Ad,
width: 310,
height: 500,
closeCB: () => {
ga(GA_EventName.CloseAd);
},
});
},
},
{
icon: "icon_do_play",
click: (event: MouseEvent, item: ListItem) => {
ga(GA_EventName.GamePlayer);
if (typeof cc !== "undefined") {
cc.game.resume();
}
},
visible: true,
txt: "game play",
contextmenu: () => {},
},
{
icon: "icon_do_pause",
visible: true,
txt: "game pause",
click: () => {
ga(GA_EventName.GamePause);
if (typeof cc !== "undefined") {
cc.game.pause();
}
},
contextmenu: () => {},
},
{
icon: "icon_do_step",
visible: true,
txt: "game step",
click: () => {
ga(GA_EventName.GameStep);
if (typeof cc !== "undefined") {
cc.game.step();
}
},
contextmenu: () => {},
},
{
icon: "icon_target",
txt: "Inspect Game",
visible: true,
click: () => {
ga(GA_EventName.GameInspector);
if (config.value.autoHide) {
showBtns.value = false;
}
picking.value = true;
if (typeof cc === "undefined") {
testInspector();
} else {
const event = new CustomEvent(DocumentEvent.GameInspectorBegan);
document.dispatchEvent(event);
}
},
contextmenu: (event: MouseEvent) => {
const arr = [
{ name: "Inspect Label", type: typeof cc !== "undefined" ? cc.Label : "cc.Label" }, //
{ name: "Inspect Sprite", type: typeof cc !== "undefined" ? cc.Sprite : "cc.Sprite" },
{ name: "Inspect Button", type: typeof cc !== "undefined" ? cc.Button : "cc.Button" },
{ name: "Inspect RichText", type: typeof cc !== "undefined" ? cc.RichText : "cc.RichText" },
];
const compMenu: IUiMenuItem[] = arr.map((item) => {
return {
name: item.name,
enabled: inspectTarget.enabled,
selected: inspectTarget.isContainInspectType(item.type),
callback: (menu: IUiMenuItem) => {
ga(GA_EventName.MouseMenu, menu.name);
if (menu.selected) {
inspectTarget.removeInspectType(item.type);
} else {
inspectTarget.addInspectType(item.type);
}
},
};
});
ccui.menu.showMenuByMouseEvent(event, [
{
name: "Clear",
callback: (menu: IUiMenuItem) => {
const event = new CustomEvent(DocumentEvent.InspectorClear);
document.dispatchEvent(event);
ga(GA_EventName.MouseMenu, menu.name);
},
},
{
name: "Pick Top",
selected: config.value.pickTop,
callback: (menu: IUiMenuItem) => {
config.value.pickTop = !config.value.pickTop;
appStore().save();
ga(GA_EventName.MouseMenu, menu.name);
},
},
{ type: ccui.menu.MenuType.Separator },
{
name: "Filter Enabled",
selected: inspectTarget.enabled,
callback: (menu: IUiMenuItem) => {
ga(GA_EventName.MouseMenu, menu.name);
inspectTarget.enabled = !inspectTarget.enabled;
},
},
...compMenu,
]);
},
},
]);
document.addEventListener(DocumentEvent.GameInspectorEnd, () => {
picking.value = false;
});
function testInspector() {
const cursor = document.body.style.cursor;
document.body.style.cursor = "zoom-in";
function test(event: MouseEvent) {
document.removeEventListener("mousedown", test, true);
document.body.style.cursor = cursor;
picking.value = false;
}
document.addEventListener("mousedown", test, true);
}
function recoverAssistantTop() {
const top = toRaw(config.value.pos);
updateAssistantTop(top);
}
function updateAssistantTop(top: number) {
const root = toRaw(rootEl.value) as HTMLDivElement;
if (!root) {
return;
}
if (top < 0) {
top = 0;
}
const maxTop = document.body.clientHeight - root.clientHeight;
if (top > maxTop) {
top = maxTop;
}
root.style.top = `${top}px`;
config.value.pos = top;
appStore().save();
}
onMounted(async () => {
recoverAssistantTop();
window.addEventListener("resize", () => {
const root = toRaw(rootEl.value) as HTMLDivElement;
if (!root) {
return;
}
updateAssistantTop(root.offsetTop);
});
return;
});
const picking = ref(false);
const rootEl = ref<HTMLDivElement>(null);
const showBtns = ref(true);
if (config.value.autoHide) {
showBtns.value = false;
}
let autoHideTimer = null;
let isDraging = false;
return {
showBtns,
listArray,
rootEl,
picking,
onMouseEnterCocosLogo() {
clearTimeout(autoHideTimer);
showBtns.value = true;
},
onCocosLogoClick() {
showBtns.value = !showBtns.value;
},
onContextMenuRoot(event: MouseEvent) {
const arr: IUiMenuItem[] = [
{
name: "auto hide",
selected: config.value.autoHide,
callback: (item) => {
config.value.autoHide = !config.value.autoHide;
appStore().save();
ga(GA_EventName.MouseMenu, item.name);
if (!config.value.autoHide) {
clearTimeout(autoHideTimer);
showBtns.value = true;
}
},
},
];
ccui.menu.showMenuByMouseEvent(event, arr);
},
onMouseLeaveRoot(event: MouseEvent) {
if (isDraging) {
return;
}
if (!config.value.autoHide) {
return;
}
autoHideTimer = setTimeout(() => {
showBtns.value = false;
}, 500);
},
onMouseDown(event: MouseEvent) {
const root = toRaw(rootEl.value) as HTMLDivElement;
if (!root) {
return;
}
const startY = event.pageY;
const startTop = root.offsetTop;
function onMouseMove(e: MouseEvent) {
isDraging = true;
const dy = e.pageY - startY;
const top = startTop + dy;
updateAssistantTop(top);
}
function onMouseUp(e: MouseEvent) {
isDraging = false;
document.removeEventListener("mousemove", onMouseMove, true);
document.removeEventListener("mouseup", onMouseUp, true);
}
document.addEventListener("mousemove", onMouseMove, true);
document.addEventListener("mouseup", onMouseUp, true);
},
};
},
});
</script>
<style scoped lang="less">
@x: 1px;
@r: 8deg;
@keyframes color-change {
0% {
color: #f00;
transform: rotate(0) translateX(0px);
}
20% {
transform: rotate(-@r) translateX(-@x);
}
40% {
transform: rotate(@r) translateX(@x);
}
50% {
color: #0f0;
}
60% {
transform: rotate(-@r) translateX(-@x);
}
80% {
transform: rotate(@r) translateX(@x);
}
100% {
color: #f00;
transform: rotate(0) translateX(0px);
}
}
.ad {
position: fixed;
box-shadow: 0px 0px 6px 1px rgb(255, 255, 255);
//z-index: 99999;
top: 0px;
right: 0px;
display: flex;
flex-direction: column;
background-color: white;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
// overflow: hidden;
.title {
display: flex;
flex-direction: row;
align-items: center;
font-size: 14px;
user-select: none;
background-color: rgb(0, 0, 0);
border: 1px solid black;
color: white;
padding: 2px 4px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
.btns {
color: white;
display: flex;
flex-direction: row;
.list {
display: flex;
flex-direction: row;
align-items: center;
color: white;
user-select: none;
&:hover {
color: rgb(101, 163, 249);
}
&:active {
color: rgb(255, 187, 0);
}
.icon {
font-size: 20px;
}
.ani_shop_cart {
animation: color-change 2s infinite;
}
}
}
.cocos {
cursor: move;
font-size: 20px;
color: rgb(85, 192, 224);
}
}
}
</style>

View File

@@ -0,0 +1,122 @@
<template>
<div v-if="data" class="banner" :class="ani" @click="onClick" :title="data.tip" :style="getStyle()">
<div class="text">
<span v-if="data.name">
{{ data.name }}
</span>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, PropType, ref, toRaw } from "vue";
import { GA_EventName } from "../../ga/type";
import { emitter, Msg } from "./const";
import { AdItem } from "./loader";
import { ga } from "./util";
export default defineComponent({
name: "banner",
props: {
data: {
type: Object as PropType<AdItem>,
default: () => new AdItem(),
},
},
setup(props, { emit }) {
function chageAd(v: AdItem) {
console.log("show ad: ", JSON.stringify(v));
}
onMounted(() => {
emitter.on(Msg.ChangeAd, chageAd);
});
onUnmounted(() => {
emitter.off(Msg.ChangeAd, chageAd);
});
const ani = ref("");
return {
ani,
getStyle() {
const img = props.data.img;
if (img) {
return `background-image: url(${img})`;
} else {
return "";
}
},
onClick() {
const url = toRaw(props.data.store);
if (url) {
window.open(url);
ga(GA_EventName.ClickPluginLink, url);
}
},
};
},
});
</script>
<style scoped lang="less">
@keyframes flip-out {
0% {
transform: rotateX(0);
}
100% {
transform: rotateX(90deg);
}
}
@keyframes flip-in {
0% {
transform: rotateX(90deg);
}
100% {
transform: rotateX(0);
}
}
.banner-out {
animation: flip-out 0.4s cubic-bezier(0.455, 0.03, 0.515, 0.955) both;
}
.banner-in {
animation: flip-in 0.4s cubic-bezier(0.455, 0.03, 0.515, 0.955) both;
}
.banner {
border: 2px solid #d2d2d2;
background-color: #ffffff;
background-position: center center;
background-size: cover;
overflow: hidden;
min-width: 300px;
max-width: 300px;
min-height: 50px;
max-height: 50px;
margin: 0;
cursor: pointer;
display: flex;
text-align: center;
align-items: flex-end;
&:hover {
border: 2px solid #949494;
background-color: #d1d1d1;
}
.text {
user-select: none;
flex: 1;
padding-bottom: 2px;
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
span {
color: #000000c4;
background-color: #afafaf6b;
padding: 1px 4px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
</style>

View File

@@ -0,0 +1,5 @@
import { TinyEmitter } from "tiny-emitter";
export const Msg = {
ChangeAd: "ChangeAd",
};
export const emitter = new TinyEmitter();

View File

@@ -0,0 +1,157 @@
export interface MirrorInfo {
/**
* 请求的url
*/
name: string;
/**
* 上次请求成功的时间
*/
time: number;
}
class Config {
private key = "cc-inspector-ad-config";
private data: MirrorInfo[] = [];
constructor() {
const cfg = localStorage.getItem(this.key);
if (cfg) {
try {
const ret = JSON.parse(cfg) as MirrorInfo[];
if (ret) {
ret.forEach((el) => {
this.data.push({ name: el.name, time: el.time });
});
}
} catch {}
}
}
save(name: string, time: number) {
const ret = this.data.find((el) => el.name === name);
if (ret) {
ret.time = time;
} else {
this.data.push({ name: name, time: time } as MirrorInfo);
}
localStorage.setItem(this.key, JSON.stringify(this.data));
}
getTime(url: string) {
const ret = this.data.find((el) => el.name === url);
if (ret) {
return ret.time;
}
return 0;
}
}
export class GithubMirror {
owner: string = "tidys";
repo: string = "cc-inspector-ad";
branch: string = "main";
/**
* 上次请求成功的时间
*/
time: number = 0;
/**
* 镜像的名字
*/
name: string = "";
private calcUrl: Function;
constructor(name: string, cb) {
this.name = name;
this.time = cfg.getTime(name);
this.calcUrl = cb;
}
public getUrl(file: string) {
if (!file) {
return "";
}
if (this.calcUrl) {
return this.calcUrl(this.owner, this.repo, this.branch, file);
} else {
return "";
}
}
public async getData(file: string) {
const url = this.getUrl(file);
if (url) {
const data = await this.reqFecth(url);
return data;
}
return null;
}
private reqFecth(url: string): Promise<Object | null> {
return new Promise((resolve, reject) => {
console.log(`req ad: ${url}`);
fetch(url)
.then((res) => {
return res.json();
})
.then((data) => {
resolve(data);
})
.catch((e) => {
resolve(null);
});
});
}
}
const cfg = new Config();
export class GithubMirrorMgr {
mirrors: GithubMirror[] = [];
constructor() {
// 使用国内gitub镜像来达到下载远程配置文件的目的
this.mirrors.push(
new GithubMirror("github", (owner: string, repo: string, branch: string, file: string) => {
return `https://raw.githubusercontent.com/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
this.mirrors.push(
new GithubMirror("bgithub", (owner: string, repo: string, branch: string, file: string) => {
return `https://raw.bgithub.xyz/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
this.mirrors.push(
new GithubMirror("kkgithub", (owner: string, repo: string, branch: string, file: string) => {
return `https://raw.kkgithub.com/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
this.mirrors.push(
new GithubMirror("xiaohei", (owner: string, repo: string, branch: string, file: string) => {
return `https://raw-githubusercontent.xiaohei.me/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
this.mirrors.push(
new GithubMirror("gh-proxy", (owner: string, repo: string, branch: string, file: string) => {
return `https://gh-proxy.com/raw.githubusercontent.com/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
this.mirrors.push(
new GithubMirror("ghproxy", (owner: string, repo: string, branch: string, file: string) => {
return `https://ghproxy.net/https://raw.githubusercontent.com/${owner}/${repo}/refs/heads/${branch}/${file}`;
})
);
}
async getData(file: string): Promise<Object | null> {
this.mirrors.sort((a, b) => b.time - a.time);
for (let i = 0; i < this.mirrors.length; i++) {
const mirror = this.mirrors[i];
const data = await mirror.getData(file);
if (data) {
const time = new Date().getTime();
mirror.time = time;
cfg.save(mirror.name, time);
return data;
}
}
return null;
}
getFileUrl(file: string): string {
if (!file) {
return "";
}
this.mirrors.sort((a, b) => b.time - a.time);
const url = this.mirrors[0].getUrl(file);
return url;
}
}
export const githubMirrorMgr = new GithubMirrorMgr();

View File

@@ -0,0 +1,116 @@
import CCPlugin from "../../../cc-plugin.config";
import { githubMirrorMgr } from "./github";
export class AdItem {
/**
* 广告的名字
*/
name: string = "";
/**
* 鼠标悬浮提示
*/
tip: string = "";
/**
* 插件的试用地址
*/
try: string = "";
/**
* 广告的store购买链接
*/
store: string = "";
/**
* 广告的展示时间单位s
*/
duration: number = 0;
/**
* 广告的有效性
*/
valid: boolean = true;
/**
* 背景图
*/
img: string = "";
parse(data: AdItem) {
this.name = data.name;
this.store = data.store || "";
this.parseStore();
this.try = data.try || "";
this.tip = data.tip || "";
this.duration = data.duration || 0;
this.valid = !!data.valid;
const img = data.img || "";
this.img = githubMirrorMgr.getFileUrl(img);
return this;
}
parseStore() {
const flag = "${git}";
if (this.store.startsWith(flag)) {
const file = this.store.split(flag)[1];
this.store = githubMirrorMgr.getFileUrl(file);
}
}
}
export class AdData {
desc: string = "";
/**
* 是否启用广告
*/
valid: boolean = false;
/**
* 多少分钟不再展示单位分钟默认10分钟
*/
showDuration: number = 10;
/**
* 底部广告多少秒滚动一次
*/
scrollDuration: number = 3;
/**
* 将位置随机打乱,保证用户每次看到的插件数量不一样,提高转换率
*/
randomIndex: boolean = false;
/**
* 展示的广告数量,-1为所有
*/
showCount: number = -1;
data: Array<AdItem> = [];
parse(data: AdData) {
this.desc = data.desc;
this.valid = !!data.valid;
this.showDuration = data.showDuration || 10;
this.scrollDuration = data.scrollDuration || 3;
this.randomIndex = !!data.randomIndex;
this.showCount = data.showCount || -1;
if (data.data) {
if (this.randomIndex) {
data.data.sort(() => Math.random() - 0.5);
}
data.data.forEach((el) => {
if (this.showCount !== -1 && this.data.length >= this.showCount) {
return;
}
const item = new AdItem().parse(el);
if (!item.duration) {
console.warn(`add failed, ad.duration is ${item.duration}, ${JSON.stringify(item)}`);
return;
}
if (!item.valid) {
console.warn(`add failed, ad is invalid, ${JSON.stringify(item)}`);
return;
}
this.data.push(item);
});
}
}
}
export async function getAdData(): Promise<AdData | null> {
const data = await githubMirrorMgr.getData(`ad-${CCPlugin.manifest.version}.json`);
if (data) {
const ad = new AdData();
ad.parse(data as AdData);
return ad;
}
return null;
}

View File

@@ -0,0 +1,58 @@
export class Memory {
public time: number = 0;
public jsHeapSizeLimit: number = 0;
public totalJSHeapSize: number = 0;
public usedJSHeapSize: number = 0;
constructor() {
this.update();
}
update() {
const memory = window.performance["memory"];
if (memory) {
this.time = Date.now();
this.jsHeapSizeLimit = memory.jsHeapSizeLimit;
this.totalJSHeapSize = memory.totalJSHeapSize;
this.usedJSHeapSize = memory.usedJSHeapSize;
}
}
}
export class MemoryDraw {
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
private width: number = 0;
private height: number = 0;
private memoryHistory: Memory[] = [];
init(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.width = canvas.width;
this.height = canvas.height;
this.clearBg();
this.initTimer();
}
private initTimer() {
setInterval(() => {
const m = new Memory();
this.memoryHistory.push(m);
this.update();
}, 300);
}
private update() {
this.clearBg();
for (let i = 0; i < this.memoryHistory.length; i++) {
const m = this.memoryHistory[i];
this.drawLine(i);
}
}
private clearBg() {
this.ctx.clearRect(0, 0, this.width, this.height);
this.ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
this.ctx.fillRect(0, 0, this.width, this.height);
}
private lineWidth = 10;
private drawLine(i: number) {
this.ctx.fillStyle = "rgba(255, 37, 37, 0.5)";
this.ctx.fillRect(0, 0, i * this.lineWidth, this.height / 2);
}
}

View File

@@ -0,0 +1,62 @@
<template>
<div class="memory">
<canvas class="canvas" ref="elCanvas"></canvas>
<div class="info">
<div class="txt">{{ transformSize(memory.usedJSHeapSize) }}</div>
<div class="txt">{{ transformSize(memory.totalJSHeapSize) }}</div>
<div class="txt">{{ transformSize(memory.jsHeapSizeLimit) }}</div>
</div>
</div>
</template>
<script lang="ts">
import ccui from "@xuyanfeng/cc-ui";
import { defineComponent, onMounted, ref, toRaw } from "vue";
import { Memory, MemoryDraw } from "./memory-draw";
import { transformSize } from "./util";
const { CCButton } = ccui.components;
export default defineComponent({
name: "memory",
components: { CCButton },
setup(props, { emit }) {
const memoryDraw = new MemoryDraw();
const memory = ref<Memory>(new Memory());
setInterval(() => {
memory.value.update();
}, 300);
onMounted(() => {
const el = toRaw(elCanvas.value);
if (el) {
// memoryDraw.init(el as HTMLCanvasElement);
}
});
const elCanvas = ref<HTMLCanvasElement>(null);
return { memory, transformSize, elCanvas };
},
});
</script>
<style lang="less" scoped>
.memory {
display: flex;
flex-direction: column;
.canvas {
display: flex;
height: 50px;
}
.info {
display: flex;
flex-direction: row;
.txt {
user-select: none;
margin: 0 3px;
}
:first-child {
margin-left: 0;
}
:last-child {
margin-right: 0;
}
}
}
</style>

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1737262301480" class="icon" viewBox="0 0 1028 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2139" width="16.0625" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M597.54570376 512l219.97466684-219.97466683c24.44162965-24.44162965 24.44162965-61.10407412 0-85.54570377-24.44162965-24.44162965-61.10407412-24.44162965-85.54570377 0L512 426.45429624 292.02533317 206.4796294C267.58370352 182.03799974 230.92125905 182.03799974 206.4796294 206.4796294c-24.44162965 24.44162965-24.44162965 61.10407412 0 85.54570377L426.45429624 512l-219.97466684 219.97466683c-24.44162965 24.44162965-24.44162965 61.10407412 0 85.54570377 12.22081482 12.22081482 28.51523459 16.29441978 44.80965436 16.29441976s32.58883953-4.07360495 44.80965435-16.29441976L512 597.54570376l219.97466683 219.97466684c12.22081482 12.22081482 28.51523459 16.29441978 44.80965436 16.29441977s32.58883953-4.07360495 44.80965436-16.29441977c24.44162965-24.44162965 24.44162965-61.10407412 0-85.54570377L597.54570376 512z" fill="#cdcdcd" p-id="2140"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1737262395969" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2346" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M704 868c-8.3 0-16.5-2.8-23.1-8.3l-384-320c-8.2-6.8-13-17-13-27.7s4.7-20.8 13-27.7l384-320c10.7-8.9 25.7-10.9 38.3-4.9C731.9 165.3 740 178 740 192v640c0 14-8.1 26.7-20.7 32.6-4.9 2.3-10.1 3.4-15.3 3.4z" fill="#dbdbdb" p-id="2347"></path></svg>

After

Width:  |  Height:  |  Size: 574 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1737262412557" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2507" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M304.7 864.6C292.1 858.7 284 846 284 832V192c0-14 8.1-26.7 20.7-32.6 12.6-5.9 27.6-4 38.3 4.9l384 320c8.2 6.8 13 17 13 27.7s-4.7 20.8-13 27.7l-384 320c-6.6 5.5-14.8 8.3-23.1 8.3-5.1 0-10.3-1.1-15.2-3.4z" fill="#dbdbdb" p-id="2508"></path></svg>

After

Width:  |  Height:  |  Size: 575 B

View File

@@ -0,0 +1,36 @@
import { ref, toRaw } from "vue";
import { defineStore } from "pinia";
import profile from "cc-plugin/src/ccp/profile";
import pluginConfig from "../../../cc-plugin.config";
export class ConfigData {
/**
* 用户拖动的位置
*/
pos: number = 0;
/**
* 是否自动隐藏
*/
autoHide: boolean = true;
/**
* 是否只拾取顶部元素
*/
pickTop: boolean = true;
}
export const appStore = defineStore("app", () => {
const config = ref<ConfigData>(new ConfigData());
return {
config,
init() {
profile.init(new ConfigData(), pluginConfig);
const data = profile.load(`${pluginConfig.manifest.name}-assistant.json`) as ConfigData;
config.value.autoHide = data.autoHide;
config.value.pos = data.pos;
config.value.pickTop = data.pickTop;
},
save() {
const cfg = toRaw(config.value);
profile.save(cfg);
},
};
});

View File

@@ -0,0 +1,29 @@
import { GA_EventName } from "../../ga/type";
import { DocumentEvent, GoogleAnalyticsData } from "../const";
export function ga(event: GA_EventName, params: string = "") {
const detail = { event, params } as GoogleAnalyticsData;
const e = new CustomEvent(DocumentEvent.GoogleAnalytics, { detail });
document.dispatchEvent(e);
}
export function transformSize(size: number) {
if (!size) return "0B";
size = parseInt(size.toString());
if (size < 1024) {
return size + "B";
}
size = size / 1024;
if (size < 1024) {
return size.toFixed(2) + "KB";
}
size = size / 1024;
if (size < 1024) {
return size.toFixed(2) + "MB";
}
size = size / 1024;
if (size < 1024) {
return size.toFixed(2) + "GB";
}
return size;
}

View File

@@ -0,0 +1,19 @@
/**
* 这个是web测试inject_view的入口实际chrome插件不应该走这个界面
*/
import ccui from "@xuyanfeng/cc-ui";
import "@xuyanfeng/cc-ui/dist/ccui.css";
import "@xuyanfeng/cc-ui/iconfont/iconfont.css";
import CCP from "cc-plugin/src/ccp/entry-render";
import { createPinia } from "pinia";
import { createApp } from "vue";
import pluginConfig from "../../../cc-plugin.config";
import App from "./app.vue";
export default CCP.init(pluginConfig, {
ready: function (rootElement: any, args: any) {
const app = createApp(App);
app.use(createPinia());
app.use(ccui);
app.mount(rootElement);
},
});

View File

@@ -0,0 +1,239 @@
declare const cc: any;
export function getEnumListConfig() {
const enumConfig: Array<{
type: any;
list: Array<{
key: string;
values: () => Array<{ name: string; value: number }>;
}>;
}> = [
{
type: cc.Widget,
list: [
{
key: "alignMode",
values: () => {
return cc.Widget.AlignMode.__enums__;
},
},
],
},
{
type: cc.Button,
list: [
{
key: "transition",
values: () => {
return cc.Button.Transition.__enums__;
},
},
],
},
{
type: cc.Sprite,
list: [
{
key: "sizeMode",
values: () => {
return cc.Sprite.SizeMode.__enums__;
},
},
{
key: "type",
values: () => {
return cc.Sprite.Type.__enums__;
},
},
],
},
{
type: cc.Mask,
list: [
{
key: "type",
values() {
return cc.Mask.Type.__enums__;
},
},
],
},
{
type: cc.Label,
list: [
{
key: "cacheMode",
values() {
return cc.Label.CacheMode.__enums__;
},
},
{
key: "overflow",
values() {
return cc.Label.Overflow.__enums__;
},
},
{
key: "verticalAlign",
values() {
return cc.Label.VerticalAlign.__enums__;
},
},
{
key: "horizontalAlign",
values() {
return cc.Label.HorizontalAlign.__enums__;
},
},
],
},
{
type: cc.Slider,
list: [
{
key: "direction",
values() {
return cc.Slider.Direction.__enums__;
},
},
],
},
{
type: cc.PageView,
list: [
{
key: "direction",
values() {
return cc.PageView.Direction.__enums__;
},
},
{
key: "sizeMode",
values() {
return cc.PageView.SizeMode.__enums__;
},
},
],
},
{
type: cc.PageViewIndicator,
list: [
{
key: "direction",
values() {
return cc.PageViewIndicator.Direction.__enums__;
},
},
],
},
{
type: cc.RichText,
list: [
{
key: "cacheMode",
values() {
return cc.Label.CacheMode.__enums__;
},
},
{
key: "horizontalAlign",
values() {
return cc.RichText.HorizontalAlign.__enums__;
},
},
{
key: "verticalAlign",
values() {
return cc.RichText.VerticalAlign.__enums__;
},
},
],
},
{
type: cc.ProgressBar,
list: [
{
key: "mode",
values() {
return cc.ProgressBar.Mode.__enums__;
},
},
],
},
{
type: cc.Scrollbar,
list: [
{
key: "direction",
values() {
return cc.Scrollbar.Direction.__enums__;
},
},
],
},
{
type: cc.EditBox,
list: [
{
key: "inputMode",
values() {
return cc.EditBox.InputMode.__enums__;
},
},
{
key: "inputFlag",
values() {
return cc.EditBox.InputFlag.__enums__;
},
},
],
},
{
type: cc.Layout,
list: [
{
key: "resizeMode",
values() {
return cc.Layout.ResizeMode.__enums__;
},
},
{
key: "type",
values() {
return cc.Layout.Type.__enums__;
},
},
{
key: "startAxis",
values() {
return cc.Layout.AxisDirection.__enums__;
},
},
{
key: "horizontalDirection",
values() {
return cc.Layout.HorizontalDirection.__enums__;
},
},
{
key: "verticalDirection",
values() {
return cc.Layout.VerticalDirection.__enums__;
},
},
],
},
{
type: cc.VideoPlayer,
list: [
{
key: "resourceType",
values() {
return cc.VideoPlayer.ResourceType.__enums__;
},
},
],
},
];
return enumConfig;
}

View File

@@ -0,0 +1,32 @@
import { debugLog, Msg, Page, PluginEvent } from "../../core/types";
import { GA_EventName } from "../../ga/type";
import { DocumentEvent, GoogleAnalyticsData } from "../const";
import { Terminal } from "../terminal";
export class InjectEvent {
protected terminal = new Terminal("Inject ");
constructor() {
document.addEventListener(DocumentEvent.Content2Inject, (event: CustomEvent) => {
const pluginEvent: PluginEvent = PluginEvent.create(event.detail);
debugLog && console.log(...this.terminal.chunkMessage(pluginEvent.toChunk()));
this.onMessage(pluginEvent);
});
}
onMessage(data: PluginEvent) {}
sendMsgToContent(msg: Msg, data: any) {
const detail = new PluginEvent(Page.Inject, Page.Content, msg, data);
debugLog && console.log(...this.terminal.chunkSend(detail.toChunk()));
const event = new CustomEvent(DocumentEvent.Inject2Content, { detail });
document.dispatchEvent(event);
}
sendEngineVersion(version: string) {
const detail = version;
const event = new CustomEvent(DocumentEvent.EngineVersion, { detail });
document.dispatchEvent(event);
}
sendAppVersion(version: string) {
const detail = { event: GA_EventName.AppVersion, params: version } as GoogleAnalyticsData;
const event = new CustomEvent(DocumentEvent.GoogleAnalytics, { detail });
document.dispatchEvent(event);
}
}

View File

@@ -0,0 +1,97 @@
declare const cc: any;
export interface DrawOptions {
fill: boolean;
fillColor: string;
stroke: boolean;
strokeColor: string;
}
export class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
export class RectPoints {
points: Point[] = [];
get len() {
return this.points.length;
}
add(point: Point) {
this.points.push(point);
}
at(index: number) {
return this.points[index];
}
test(width: number, height: number) {
this.points.push(new Point(0, 0));
this.points.push(new Point(width, 0));
this.points.push(new Point(width, height));
this.points.push(new Point(0, height));
}
}
export class HintAdapter {
protected draw = null;
constructor() {}
resetIndex() {
throw new Error("not implemented");
}
getRectPoints(node: any): RectPoints | null {
throw new Error("not implemented");
}
clear() {
if (this.draw) {
this.draw.clear();
}
}
convertMousePos(event: MouseEvent, canvas: HTMLCanvasElement): { x: number; y: number } {
throw new Error("not implemented");
}
hitTest(node: any, x: number, y: number): boolean {
throw new Error("not implemented");
}
initDrawNode() {
if (this.draw && !this.draw.isValid) {
this.draw = null;
}
if (this.draw) {
return;
}
const scene = cc.director.getScene();
if (!scene) {
return;
}
let node = new cc.Node("draw-node");
this.addDraw(scene, node);
this.draw = node.addComponent(cc.Graphics || cc.GraphicsComponent);
}
public isDrawValid() {
return this.draw && this.draw.isValid;
}
protected addDraw(scene: any, node: any) {
throw new Error("not implemented");
}
public drawRect(points: RectPoints, opts: DrawOptions) {
this.draw.lineWidth = 2;
for (let i = 0; i < points.len; i++) {
const p = points.at(i);
if (i === 0) {
this.draw.moveTo(p.x, p.y);
} else {
this.draw.lineTo(p.x, p.y);
}
}
if (points.len) {
this.draw.close();
}
if (opts.stroke) {
this.draw.strokeColor = new cc.Color().fromHEX(opts.strokeColor);
this.draw.stroke();
}
if (opts.fill) {
this.draw.fillColor = new cc.Color().fromHEX(opts.fillColor);
this.draw.fill();
}
}
}

View File

@@ -0,0 +1,136 @@
import { HintAdapter, Point, RectPoints } from "./adapter";
declare const cc: any;
export class HintV2 extends HintAdapter {
constructor() {
super();
}
protected addDraw(scene: any, node: any): void {
scene.addChild(node);
}
private canvasBoundingRect = {
left: 0,
top: 0,
width: 0,
height: 0,
adjustedTop: 0,
adjustedLeft: 0,
};
private _updateCanvasBoundingRect() {
// @ts-ignore
const element: any = cc.game.canvas;
var docElem = document.documentElement;
var leftOffset = window.pageXOffset - docElem.clientLeft;
var topOffset = window.pageYOffset - docElem.clientTop;
if (element.getBoundingClientRect) {
var box = element.getBoundingClientRect();
this.canvasBoundingRect.left = box.left + leftOffset;
this.canvasBoundingRect.top = box.top + topOffset;
this.canvasBoundingRect.width = box.width;
this.canvasBoundingRect.height = box.height;
} else if (element instanceof HTMLCanvasElement) {
this.canvasBoundingRect.left = leftOffset;
this.canvasBoundingRect.top = topOffset;
this.canvasBoundingRect.width = element.width;
this.canvasBoundingRect.height = element.height;
} else {
this.canvasBoundingRect.left = leftOffset;
this.canvasBoundingRect.top = topOffset;
this.canvasBoundingRect.width = parseInt(element.style.width);
this.canvasBoundingRect.height = parseInt(element.style.height);
}
}
getPointByEvent(event: MouseEvent) {
this._updateCanvasBoundingRect();
if (event.pageX != null)
//not avalable in <= IE8
return { x: event.pageX, y: event.pageY };
this.canvasBoundingRect.left -= document.body.scrollLeft;
this.canvasBoundingRect.top -= document.body.scrollTop;
return { x: event.clientX, y: event.clientY };
}
convertMousePos(event: MouseEvent, canvas: HTMLCanvasElement): { x: number; y: number } {
let location = this.getPointByEvent(event);
// let p = cc.view._convertMouseToLocationInView(location, this.canvasBoundingRect);
let p = cc.view.convertToLocationInView(location.x, location.y, this.canvasBoundingRect);
let scaleX = cc.view._scaleX;
let scaleY = cc.view._scaleY;
let viewport = cc.view._viewportRect;
let x = (p.x - viewport.x) / scaleX;
let y = (p.y - viewport.y) / scaleY;
// let position = cc.v2(event.offsetX, event.offsetY);
// let size = cc.view.getDesignResolutionSize();
// let rect = { left: 0, top: 0, width: size.width, height: size.height };
// cc.view._convertMouseToLocationInView(position, rect);
// let wordPos = cc.v2();
// cc.Camera.main.getScreenToWorldPoint(position, wordPos);
return { x, y };
}
hitTest(node: any, x: number, y: number): boolean {
// let rect = item.getBoundingBox();
// let p = item.parent.convertToNodeSpaceAR(wordPos);
// if (rect.contains(p)) {
// return item;
// }
const mask = this._searchComponentsInParent(node, cc.Mask);
const b = node._hitTest(cc.v2(x, y), { mask });
return b;
}
private _searchComponentsInParent(node: any, comp: any) {
if (comp) {
let index = 0;
let list = null;
for (var curr = node; curr && cc.Node.isNode(curr); curr = curr._parent, ++index) {
if (curr.getComponent(comp)) {
let next = {
index: index,
node: curr,
};
if (list) {
list.push(next);
} else {
list = [next];
}
}
}
return list;
}
return null;
}
resetIndex(): void {
const node = this.draw.node;
node.zIndex = node.parent.children.length;
}
getRectPoints(node: any): RectPoints | null {
const points: RectPoints = new RectPoints();
const { anchorX, anchorY, width, height } = node;
const x = -anchorX * width;
const y = -anchorY * height;
const matrixm = node._worldMatrix.m;
const m00 = matrixm[0],
m01 = matrixm[1],
m04 = matrixm[4],
m05 = matrixm[5],
m12_tx = matrixm[12],
m13_ty = matrixm[13];
[
new Point(x, y), //
new Point(x + width, y),
new Point(x + width, y + height),
new Point(x, y + height),
].forEach(({ x, y }: Point) => {
const worldX = x * m00 + y * m04 + m12_tx; // x
const worldY = x * m01 + y * m05 + m13_ty; // y
const pos = this.draw.node.convertToNodeSpaceAR(cc.v2(worldX, worldY));
points.add(new Point(pos.x, pos.y));
});
return points;
}
}

View File

@@ -0,0 +1,149 @@
import { HintAdapter, Point, RectPoints } from "./adapter";
declare const cc: any;
export class HintV3 extends HintAdapter {
resetIndex(): void {
const node = this.draw.node;
if (node.parent) {
const len = node.parent.children.length;
node.setSiblingIndex(len);
}
}
private get transformComponent() {
return cc.UITransformComponent || cc.UITransform;
}
hitTest(node: any, x: number, y: number): boolean {
let hitTest = null;
// hitTest = node._uiProps?.uiTransformComp?.hitTest;
const tr = node.getComponent(this.transformComponent);
if (tr) {
if (tr.hitTest) {
hitTest = tr.hitTest.bind(tr);
} else if (tr.isHit) {
// TODO: 3.3.1使用的是这个接口hitTest有问题有人反馈再说修复老版本暂不花费太多精力
hitTest = tr.isHit.bind(tr);
}
}
if (hitTest) {
let b = hitTest({ x, y }, 0);
return b;
} else {
return false;
}
}
convertMousePos(event: MouseEvent, canvas: HTMLCanvasElement): { x: number; y: number } {
const rect = canvas.getBoundingClientRect();
let x = event.clientX - rect.x;
let y = rect.y + rect.height - event.clientY;
x *= window.devicePixelRatio;
y *= window.devicePixelRatio;
return { x, y };
}
/**
* 这种方式能够获取到优先绘制的canvas也能保证线框在顶部但是方案不完美会收到node.layer的影响
*/
private getCanvas(scene: any) {
const canvasArray: Array<{ canvas: any; index: number }> = [];
scene.walk((item: any) => {
if (cc.Canvas) {
const comp = item.getComponent(cc.Canvas);
if (comp) {
const idx = comp.cameraComponent?.priority || 0;
canvasArray.push({ canvas: comp, index: idx });
}
}
});
canvasArray.sort((a, b) => a.index - b.index);
return canvasArray[0].canvas.node;
}
protected addDraw(scene: any, node: any): void {
const canvas = this.getCanvas(scene);
if (canvas) {
if (canvas.layer) {
node.layer = canvas.layer;
}
const tr = node.getComponent(this.transformComponent) || node.addComponent(this.transformComponent);
if (tr) {
tr.setContentSize(0, 0);
}
// FIXME: 多canvas的情况下如果hover和select的节点不在一个canvas下绘制线框有问题暂时先不支持多canvas的情况
canvas.addChild(node);
}
}
getRectPoints(node: any): RectPoints | null {
if (!node.worldPosition) {
return null;
}
if (!this.transformComponent) {
return null;
}
const tr = node.getComponent(this.transformComponent);
if (!tr) {
return null;
}
const { anchorPoint, width, height } = tr;
const points: RectPoints = new RectPoints();
const x = -anchorPoint.x * width;
const y = -anchorPoint.y * height;
const m = node.worldMatrix;
[
new Point(x, y), //
new Point(x + width, y),
new Point(x + width, y + height),
new Point(x, y + height),
].forEach(({ x, y }: Point) => {
let rhw = m.m03 * x + m.m07 * y + m.m15;
rhw = rhw ? 1 / rhw : 1;
let worldX = (m.m00 * x + m.m04 * y + m.m12) * rhw;
let worldY = (m.m01 * x + m.m05 * y + m.m13) * rhw;
let worldZ = (m.m02 * x + m.m06 * y + m.m14) * rhw;
const pos = this.draw.getComponent(this.transformComponent).convertToNodeSpaceAR(cc.v3(worldX, worldY, worldZ));
points.add(new Point(pos.x, pos.y));
});
return points;
}
/**
* 尝试实现wireframe模式
*/
private getTrangles(node: any) {
const comps = node._components;
if (comps && Array.isArray(comps)) {
const m = node.worldMatrix;
comps.forEach((comp) => {
const { renderData } = comp;
if (!renderData) {
return;
}
const { data, _vc: VertexCount, _ic: IndexCount, chunk } = renderData;
if (!data) {
return;
}
// 获取indexBuffer的索引顺序
const ib = chunk.meshBuffer.iData;
let indexOffset = chunk.meshBuffer.indexOffset;
const vidOrigin = chunk.vertexOffset;
const arr = [];
for (let i = 0; i < IndexCount; i++) {
const index = ib[indexOffset + i] - vidOrigin;
arr.push(index);
}
for (let j = 0; j < data.length; j++) {
const item = data[j];
const { x, y, z, u, v, color } = item;
let rhw = m.m03 * x + m.m07 * y + m.m15;
rhw = rhw ? 1 / rhw : 1;
let worldX = (m.m00 * x + m.m04 * y + m.m12) * rhw;
let worldY = (m.m01 * x + m.m05 * y + m.m13) * rhw;
let worldZ = (m.m02 * x + m.m06 * y + m.m14) * rhw;
// const pos = this.draw.getComponent(transform).convertToNodeSpaceAR(cc.v3(worldX, worldY, worldZ));
// points.points.push(new Point(pos.x, pos.y));
}
});
}
}
}

View File

@@ -0,0 +1,217 @@
import ccui from "@xuyanfeng/cc-ui";
import { IUiMenuItem } from "@xuyanfeng/cc-ui/types/cc-menu/const";
import { throttle } from "lodash";
import { toRaw } from "vue";
import { Msg } from "../../../core/types";
import { DocumentEvent } from "../../const";
import { appStore } from "../../inject-view/store";
import { Inspector } from "../inspector";
import { DrawOptions, HintAdapter, RectPoints } from "./adapter";
import { HintV2 } from "./hint-v2";
import { HintV3 } from "./hint-v3";
declare const cc: any;
/**
* 只负责管理hint的流程
*/
export class Hint {
private engineVersion: string = "";
private hintAdapter: HintAdapter = null;
public setEngineVersion(version: string) {
this.engineVersion = version;
if (version.startsWith("2.")) {
this.hintAdapter = new HintV2();
} else if (version.startsWith("3.")) {
this.hintAdapter = new HintV3();
}
}
private inspector: Inspector = null;
constructor(inspector: Inspector) {
this.inspector = inspector;
document.addEventListener(DocumentEvent.InspectorClear, () => {
this.cleanHover();
this.cleanSelected();
});
document.addEventListener(DocumentEvent.GameInspectorBegan, (event: CustomEvent) => {
const el = this.getTargetElement();
if (!el) {
return;
}
const cursor = el.style.cursor;
el.style.cursor = "zoom-in";
const mousedown = (event: MouseEvent) => {
event.stopPropagation();
event.preventDefault();
el.removeEventListener("mousedown", mousedown, true);
el.removeEventListener("mousemove", mousemove, true);
el.style.cursor = cursor;
const e = new CustomEvent(DocumentEvent.GameInspectorEnd);
document.dispatchEvent(e);
if (event.button === 0) {
// 左键拾取
this.updateHintDown(event, el);
} else {
this.updateHitMoveThrottle.cancel();
// 其他按键取消
this.cleanHover();
}
};
const mousemove = (event: MouseEvent) => {
this.updateHitMoveThrottle(event, el);
};
el.addEventListener("mousemove", mousemove, true);
el.addEventListener("mousedown", mousedown, true);
});
}
private updateHitMoveThrottle = throttle(this.updateHintMove, 300);
private updateHintMove(event: MouseEvent, canvas: HTMLCanvasElement) {
const nodes = this.getMouseNodes(event, canvas);
if (nodes.length) {
const node = nodes[0];
this.setHover(node);
}
}
private getMouseNodes(event: MouseEvent, canvas: HTMLCanvasElement): Array<any> {
this.inspector.updateTreeInfo(false);
const { x, y } = this.hintAdapter.convertMousePos(event, canvas);
const nodes = [];
this.inspector.forEachNode((node) => {
const b = this.hintAdapter.hitTest(node, x, y);
if (b && node.active && node.activeInHierarchy) {
nodes.push(node);
}
});
nodes.reverse();
return nodes;
}
private updateHintDown(event: MouseEvent, canvas: HTMLCanvasElement) {
this.cleanHover();
this.cleanSelected();
const nodes = this.getMouseNodes(event, canvas);
const pickTop = toRaw(appStore().config.pickTop);
if (nodes.length === 1 || (pickTop && nodes.length)) {
const item = nodes[0];
this.cleanHover();
this.setSelected(item);
this.sendInspectNodeMsg(item);
} else {
const menu = nodes.map((item) => {
const path = this.getPath(item);
return {
name: path,
callback: () => {
this.cleanHover();
this.setSelected(item);
this.sendInspectNodeMsg(item);
},
enter: () => {
this.setHover(item);
},
leave: () => {
this.cleanHover();
},
} as IUiMenuItem;
});
ccui.menu.showMenuByMouseEvent(event, menu, 0.8);
}
}
private sendInspectNodeMsg(node: any) {
if (node.uuid) {
this.inspector.sendMsgToContent(Msg.InspectNode, node.uuid);
}
}
private getPath(node: any) {
let path = [];
let parent = node;
while (parent) {
path.push(parent.name);
parent = parent.parent;
}
path = path.reverse();
return path.join("/");
}
private getTargetElement(): HTMLCanvasElement | null {
// @ts-ignore
if (typeof cc !== "undefined" && cc.game && cc.game.canvas) {
// @ts-ignore
return cc.game.canvas;
} else {
null;
}
}
public cleanHover() {
this.hoverNodes = [];
this.hintAdapter && this.hintAdapter.clear();
}
public cleanSelected() {
this.selectedNodes = [];
this.hintAdapter && this.hintAdapter.clear();
}
private hoverNodes = [];
private selectedNodes = [];
public update() {
if (!this.hintAdapter) {
return;
}
this.hintAdapter.initDrawNode();
if (!this.hintAdapter.isDrawValid()) {
return;
}
this.hintAdapter.clear();
this.hintAdapter.resetIndex();
// this.testRect();
this.hoverNodes.forEach((node) => {
if (node.isValid) {
this.hintNode(node, {
fill: true,
fillColor: "#00ff0055",
stroke: true,
strokeColor: "#ffffff44",
});
}
});
this.selectedNodes.forEach((node) => {
if (node.isValid) {
this.hintNode(node, {
fill: false,
fillColor: "#ff0000",
stroke: true,
strokeColor: "#ff0000",
});
}
});
}
private testRect() {
const points = new RectPoints();
points.test(100, 100);
this.hintAdapter.drawRect(points, {
fill: true,
fillColor: "#00ff0099",
stroke: true,
strokeColor: "#ff000099",
});
}
public setHover(node: any) {
if (node.isValid) {
this.hoverNodes = [node];
}
}
public setSelected(node: any) {
if (node.isValid) {
this.selectedNodes = [node];
}
}
private hintNode(node: any, opts: DrawOptions) {
const points = this.hintAdapter.getRectPoints(node);
if (!points) {
return;
}
this.hintAdapter.drawRect(points, opts);
}
}

View File

@@ -0,0 +1,4 @@
import { Inspector } from "./inspector";
const inspector = new Inspector();
inspector.init();
window["CCInspector"] = inspector;

View File

@@ -0,0 +1,56 @@
import ccui from "@xuyanfeng/cc-ui";
import "@xuyanfeng/cc-ui/dist/ccui.css";
import "@xuyanfeng/cc-ui/iconfont/iconfont.css";
import { createPinia } from "pinia";
import { createApp } from "vue";
import { DocumentEvent } from "../const";
import App from "../inject-view/app.vue";
export class InjectView {
private _inited = false;
private css: string[] = [];
private hasLoadCss = false;
constructor() {
document.addEventListener(DocumentEvent.LoadInjectCss, (event: CustomEvent) => {
const cssArray: string[] = event.detail;
this.css = cssArray;
this.loadCss();
});
}
public init() {
if (this._inited) {
return;
}
this._inited = true;
this.createUI();
}
private loadCss() {
if (this.hasLoadCss) {
return;
}
if (this.css.length === 0) {
return;
}
this.hasLoadCss = true;
this.css.forEach((css) => {
const link = document.createElement("link");
link.href = css;
link.rel = "stylesheet";
document.head.appendChild(link);
});
}
private createUI() {
const el = document.createElement("div");
el.setAttribute("app", "");
el.style.zIndex = "9000";
document.body.appendChild(el);
// load css
this.loadCss();
// vue
const app = createApp(App);
app.use(createPinia());
// ccui.uiElement.setDoc(document);
app.use(ccui);
app.mount(el);
}
}
export const injectView = new InjectView();

View File

@@ -0,0 +1,38 @@
export class InspectTarget {
private list = [];
/**
* 是否启用过滤
*/
enabled: boolean = false;
addInspectType(item: any) {
if (!this.list.find((el) => item === el)) {
this.list.push(item);
}
console.log(this.list);
}
removeInspectType(item: any) {
this.list.splice(this.list.indexOf(item), 1);
console.log(this.list);
}
cleanInspectType() {
this.list.length = 0;
}
isContainInspectType(type: any) {
return !!this.list.find((el) => type === el);
}
checkNodeComponentsIsInList(node: any) {
const comps = node._components;
for (let i = 0; i < comps.length; i++) {
const comp = comps[i];
if (this.list.find((el) => comp instanceof el)) {
return true;
}
}
return false;
}
}
export const inspectTarget = new InspectTarget();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
import { isVersion3 } from "./util";
interface ConfigItem {
path: string[],
func: Function;
}
const config: ConfigItem[] = [
{
path: ["position", "x"],
func: (target: any, value: any) => {
let pos = target.getPosition();
pos.x = value;
target.setPosition(pos);
}
},
{
path: ["position", "y"],
func: (target: any, value: any) => {
let pos = target.getPosition();
pos.y = value;
target.setPosition(pos);
}
},
{
path: ["position", "z"],
func: (target: any, value: any) => {
let pos = target.getPosition();
pos.z = value;
target.setPosition(pos);
}
},
{
path: ["scale", "x"],
func: ((target: any, value: any) => {
let scale = target.getScale();
scale.x = value;
target.setScale(scale);
})
},
{
path: ["scale", "y"],
func: ((target: any, value: any) => {
let scale = target.getScale();
scale.y = value;
target.setScale(scale);
})
},
{
path: ["scale", "z"],
func: ((target: any, value: any) => {
let scale = target.getScale();
scale.z = value;
target.setScale(scale);
})
}
]
// 3.x不允许直接设置xyz需要走setPosition
export function trySetValueWithConfig(pathArray: string[], targetObject: any, targetValue: any) {
if (isVersion3()) {
let fullPath: string = pathArray.toString()
let item = config.find(el => {
return fullPath.endsWith(el.path.toString())
});
if (item) {
// 将多余的path去掉
let leftPathArray = [];
let max = pathArray.length - item.path.length;
for (let i = 0; i < max; i++) {
leftPathArray.push(pathArray[i])
}
let pathObjectValue = getValue(targetObject, leftPathArray);
if (pathObjectValue) {
try {
item.func(pathObjectValue, targetValue);
return true;
} catch (e) {
console.error(e);
return false
}
}
}
}
return false;
}
export function getValue(target: any, path: string[]) {
for (let i = 0; i < path.length; i++) {
let key = path[i];
if (target[key] !== undefined || target.hasOwnProperty(key)) {
target = target[key]
} else {
return null;
}
}
return target;
}

View File

@@ -0,0 +1,43 @@
import { ArrayData, ImageData, Info, ObjectData, Vec2Data, Vec3Data } from "../../views/devtools/data";
export interface BuildObjectOptions {
path: string[];
value: Object;
data: ObjectData;
}
export interface BuildArrayOptions {
path: string[];
value: Object;
data: ArrayData;
keys: number[];
}
export interface BuildVecOptions {
path: string[];
keys: Array<{
key: string;
/**
* 分量使用的步进值,优先使用,主要是为了实现不同分量不同的步进
*/
step?: (key: string) => number;
/**
* 分量是否可以调整
*/
disabled?: (key: string, item: Info) => boolean;
}>;
/**
* 所有的vec统一使用的步进值
*/
step?: number;
ctor: Function;
value: Object;
data: Vec3Data | Vec2Data;
}
export interface BuildImageOptions {
path: string[];
ctor: Function;
value: Object;
data: ImageData;
}

View File

@@ -0,0 +1,23 @@
declare const cc: any;
export function isVersion3() {
if (typeof cc.ENGINE_VERSION === "string") {
const version: string = cc.ENGINE_VERSION;
return version.startsWith("3.")
}
return false;
}
export function isHasProperty(base: Object, key: string): boolean {
let ret = Object.getOwnPropertyDescriptor(base, key)
if (ret) {
return true;
} else {
let proto = Object.getPrototypeOf(base);
if (proto) {
return isHasProperty(proto, key)
} else {
return false;
}
}
}

163
src/scripts/terminal.ts Normal file
View File

@@ -0,0 +1,163 @@
export class Chunk {
/**
* 显示的值
*/
value: string = "";
/**
* 是否换行
*/
private newline: boolean = false;
/**
* 显示的样式
*/
style: string[] = [];
constructor(v: string, newline: boolean = false) {
this.value = v;
this.newline = newline;
}
color(c: string) {
this.style.push(`color:${c}`);
return this;
}
background(c: string) {
this.style.push(`background:${c}`);
return this;
}
padding(c: string) {
this.style.push(`padding:${c}`);
return this;
}
fontwight(c: string) {
this.style.push(`font-weight:${c}`);
return this;
}
bold() {
return this.fontwight("bold");
}
margin(c: string) {
this.style.push(`margin:${c}`);
return this;
}
marginLeft(c: string) {
this.style.push(`margin-left:${c}`);
return this;
}
marginRight(c: string) {
this.style.push(`margin-right:${c}`);
return this;
}
toValue() {
return `%c${this.value}${this.newline ? "\n" : ""}`;
}
toStyle() {
return this.style.join(";");
}
}
export class Terminal {
/**
* 标签
*/
tag = "terminal";
/**
* 子标签
*/
subTag = "";
/**
* 标签的颜色
*/
tagColor = "blue";
/**
* 标签的背景色
*/
tagBackground = "yellow";
/**
* 日志文本的颜色
*/
txtColor = "black";
private chunks: Chunk[] = [];
constructor(tag: string) {
this.tag = tag;
}
init(): string[] {
this.txtColor = "black";
this.subTag = "init";
return this.log();
}
public log(message: string = "", newline: boolean = false): string[] {
const txt = new Chunk(message).color(this.txtColor).background("#e6e6e6").marginLeft("5px");
return this.doChunk(newline, [txt]);
}
public chunkMessage(chunk: Chunk[]) {
this.subTag = "message";
return this.doChunk(false, chunk);
}
public chunkSend(chunk: Chunk[]) {
this.subTag = "send ";
return this.doChunk(false, chunk);
}
private doChunk(newline: boolean = false, chunks: Chunk[]) {
this.chunks = [];
const tag = new Chunk(this.tag).color(this.tagColor).background(this.tagBackground).padding("0 4px");
this.chunks.push(tag);
const subTag = new Chunk(this.subTag, newline).color(this.tagBackground).background(this.tagColor).padding("0 3px");
this.chunks.push(subTag);
chunks.forEach((c) => {
this.chunks.push(c);
});
let head = "*";
for (let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i];
head += chunk.toValue();
}
const ret = [head];
this.chunks.forEach((chunk) => {
ret.push(chunk.toStyle());
});
this.reset();
return ret;
}
private reset() {
this.subTag = "";
}
public blue(message: string): string[] {
this.txtColor = "blue";
this.subTag = "";
return this.log(message);
}
public green(message: string): string[] {
this.txtColor = "green";
this.subTag = "";
return this.log(message);
}
public red(message: string): string[] {
this.txtColor = "red";
this.subTag = "";
return this.log(message);
}
send(msg: string) {
this.txtColor = "black";
this.subTag = "send";
return this.log(`${msg}`);
}
message(msg: string): string[] {
this.txtColor = "black";
this.subTag = "message";
return this.log(`${msg}`);
}
connect(msg: string): string[] {
this.txtColor = "black";
this.subTag = "connect";
return this.log(`${msg}`);
}
disconnect(msg: string): string[] {
this.txtColor = "black";
this.subTag = "disconnect";
return this.log(`${msg}`);
}
}