add ap parse code

Signed-off-by: yanpeng <yanpeng51@h-partners.com>
Change-Id: I9c0c2b23e0bd926efc3350d117d351a7c9732fd7
Signed-off-by: yanpeng <yanpeng51@h-partners.com>
This commit is contained in:
yanpeng 2024-06-13 10:13:19 +08:00
parent 8c30d95c97
commit 6f453479ba
37 changed files with 3103 additions and 0 deletions

View File

@ -74,6 +74,10 @@ Note:If the text contains special characters, please escape them according to th
<filteritem type="filepath" name="docs/figures/zh-cn_image_ark-ts-arch.png" desc="自造的二进制图片"/>
<filteritem type="filepath" name="tools/circuit_viewer/figures/.*.png" desc="自造的二进制图片"/>
<filteritem type="filepath" name="tools/circuit_viewer/dist/favicon.ico" desc="ico文件"/>
<filteritem type="filepath" name="tools/ap_file_viewer/src/img/.*.png" desc="自制ap图片"/>
<filteritem type="filepath" name="tools/ap_file_viewer/test/.*.ap" desc="ap测试文件"/>
<filteritem type="filepath" name="tools/ap_file_viewer/test/figures/.*.png" desc="自制测试readme图片"/>
<filteritem type="filepath" name="tools/ap_file_viewer/figures/.*.png" desc="自制readme图片"/>
</filefilter>
<filefilter name="copyrightPolicyFilter" desc="copyright文件头校验策略的过滤条件" >
</filefilter>

5
tools/ap_file_viewer/.gitignore vendored Executable file
View File

@ -0,0 +1,5 @@
node_modules
coverage/
dist
package-lock.json
.idea

View File

@ -0,0 +1,69 @@
# Ap文件可视化工具
#### 简介
本文主要介绍Ap文件可视化工具该工具根据ark_js_vm生成的ap文件实现可视化分析该工具通过web访问。
#### 目录
```
arkcompiler/ets_runtime/tools/ap_file_viewer
├── build.py 构建脚本
├── native ap文件解析为json源码
├── package.json 项目配置文件
├── README.md 工具使用指导文档
├── server 工具web服务代码
├── src 工具前端源码
└── webpack.config.js webpack打包配置文件
```
#### 使用对象
分析ap文件的开发者
#### 使用场景
需对ap文件分析窗口中展示可视化展示ap文件内容
#### 工具使用
具体的工具使用步骤,可以左键单击以下链接了解:
[工具使用说明](./docs/INSTRUCTION_zh.md)
### 工具输出
在window或linux环境下根据导入ap件如下图所示
![](./figures/img.png)
## 工具开发说明
### 对象
工具的开发者
### 开发场景
若当前工具的功能已经不能满足开发者的全部需求,则开发者可以基于已有的源码对工具进行二次开发,来增强工具的能力,编译打包生成自定义的工具。
### 开发步骤
[工具开发说明](./docs/DEVELOP_zh.md)
## 版本说明
暂无
## FAQ
## 参与贡献
暂无
## 相关仓
暂无

91
tools/ap_file_viewer/build.py Executable file
View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2024 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
import subprocess
import argparse
import shutil
def get_script_directory():
return os.path.dirname(os.path.abspath(__file__))
def copy_files_to_dist(target_dir):
file_list = [
"out/sdk/clang_x64/thirdparty/icu/libhmicui18n.so",
"out/sdk/clang_x64/thirdparty/icu/libhmicuuc.so",
"out/sdk/clang_x64/arkcompiler/ets_runtime/libprofDumpJson.so",
"out/sdk/mingw_x86_64/thirdparty/icu/libhmicui18n.dll",
"out/sdk/mingw_x86_64/thirdparty/icu/libhmicuuc.dll",
"out/sdk/mingw_x86_64/arkcompiler/ets_runtime/libprofDumpJson.dll",
"out/sdk/mingw_x86_64/hiviewdfx/hilog/libhilog.dll",
"out/sdk/mingw_x86_64/thirdparty/bounds_checking_function/libsec_shared.dll",
"out/sdk/mingw_x86_64/thirdparty/zlib/libshared_libz.dll"
]
current_path = get_script_directory()
dist_dir = os.path.join(current_path, "dist")
for relative_file_path in file_list:
absolute_file_path = os.path.join(target_dir, relative_file_path)
if os.path.exists(absolute_file_path):
print(f'Copied {absolute_file_path} to {dist_dir}')
shutil.copy(absolute_file_path, dist_dir)
else:
print(f"File not found: {absolute_file_path}")
def run_command(command):
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in process.stdout:
print(line.decode('utf-8').strip())
process.wait()
if process.returncode != 0:
raise Exception(f"Command failed with return code {process.returncode}")
def main(target_dir):
original_dir = get_script_directory()
try:
command = ["npm", "install"]
print(f"Running command: {command}")
run_command(command)
os.chdir(target_dir)
print(f"Changed directory to: {os.getcwd()}")
command = ["/bin/bash", "build.sh", "--product-name", "ohos-sdk", "--build-target",
"ap_viewer_all_host_tools_packages"]
print(f"Running command: {command}")
run_command(command)
os.chdir(original_dir)
print(f"Returned to original directory: {os.getcwd()}")
npm_command = ["npm", "run", "build"]
print(f"Running command: {npm_command}")
run_command(npm_command)
copy_files_to_dist(target_dir)
except Exception as e:
print(f"An error occurred: {e}")
finally:
os.chdir(original_dir)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Build script with optional target directory.")
parser.add_argument("--target-dir", metavar="dir", type=str,
help="The target directory to cd into before running the build script.")
args = parser.parse_args()
target_directory = args.target_dir if args.target_dir else "../../../../"
main(target_directory)

View File

@ -0,0 +1,42 @@
# AP可视化工具开发说明
若当前工具功能不满足开发者需求,开发者需增强工具能力,则可基于已有源码进行工具二次开发,编译打包生成自定义的工具。
## 使用场景
1. 编译打包发布工具
2. 开发者需增强工具能力,进行二次开发
## 编译步骤:
#### 编译:
在ap_file_viewer 目录下执行
```
python build.py
```
生成dist目录,dist目录中内容如下
![](../figures/dist.png)
#### 运行
在dist目录内运行python apDumpServer.py启动web服务如下图所示
![](../figures/start_web.png)
打开chrome浏览器访问http://{ip}:9001/ap/,如下图所示:
![](../figures/main.png)
#### 开发
```
项目整体为BS架构
native为 C++ 代码提供将ap文件转换为json的接口。
server为 python 代码, 提供http接口主要有文件上传接口和静态文件访问接口
src 是前端js代码 提供json数据渲染
```

View File

@ -0,0 +1,21 @@
# AP可视化工具使用说明
## 简介
Ap文件可视化工具根据ark_js_vm生成的ap文件实现可视化分析。
### 使用说明
运行ark_js_vm采集PGO信息生成的PGO信息落盘在ap文件。
根据开发说明打包工具,并运行起来:
[开发说明](./DEVELOP_zh.md)
将ap文件拖入浏览器窗口区域等待解析完成界面功能如下图所示
![](../figures/img.png)
## 注意
暂无

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,19 @@
{
"name": "ApDump",
"version": "1.0.0",
"description": "Ap Parse",
"main": "main.js",
"scripts": {
"build": "npx webpack"
},
"repository": {
"type": "git",
"url": ""
},
"author": "",
"license": "Apache License",
"devDependencies": {
"webpack": "^5.64.4",
"webpack-cli": "^4.9.1"
}
}

View File

@ -0,0 +1,272 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2024 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import cgi
import ctypes
import http
import json
import os
import re
import signal
import sys
import threading
import time
import webbrowser
from datetime import datetime
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
from urllib.parse import urlparse
VERSION = 'v1.0.0'
LIB_NAME = 'libprofDumpJson'
current = os.path.dirname(os.path.abspath(__file__))
libs_directory = current
dependencies = ['hmicuuc', 'hmicui18n']
keep_running = True
httpd = None
def is_windows():
return os.name == 'nt'
def is_linux():
return os.name == 'posix'
def load_dependencies(load_dependencies_lib, lib_directory):
for dep in load_dependencies_lib:
dep_path = f"{lib_directory}/lib{dep}.so"
try:
ctypes.CDLL(dep_path)
except OSError as e:
raise OSError(f"Failed to load dependency {dep_path}: {e}")
def load_library_os_compatible(load_lib_name, lib_directory=None, lib_dependencies=None):
if lib_directory is None:
lib_directory = Path.cwd()
if is_windows():
lib_extension = '.dll'
load_lib_path = f"{lib_directory}/{load_lib_name}{lib_extension}"
load_lib = ctypes.CDLL(str(load_lib_path))
else:
lib_extension = '.so'
load_lib_path = f"{lib_directory}/{load_lib_name}{lib_extension}"
if lib_dependencies:
load_dependencies(lib_dependencies, lib_directory)
try:
load_lib = ctypes.CDLL(str(load_lib_path))
except OSError as e:
raise OSError(f"Failed to load {load_lib_path}: {e}")
if load_lib is None:
raise ImportError(f"Could not load library {load_lib_name}.")
return load_lib
lib = load_library_os_compatible(LIB_NAME, libs_directory, dependencies)
ConvertApToJson = lib.ConvertApToJson
ConvertApToJson.argtypes = [ctypes.c_char_p, ctypes.c_size_t]
ConvertApToJson.restype = ctypes.c_size_t
GetConvertResult = lib.GetConvertResult
GetConvertResult.argtypes = [ctypes.c_char_p, ctypes.c_size_t]
GetConvertResult.restype = ctypes.c_bool
def get_content_type(file_extension):
if file_extension == '.js':
return 'application/javascript'
if file_extension == '.wasm':
return 'application/wasm'
if file_extension == '.json':
return 'application/json'
if file_extension == '.html':
return 'text/html'
if file_extension == '.svg':
return 'image/svg+xml'
return 'text/plain'
def parse_ap_file(file_path):
encoded_path = file_path.encode('utf-8')
path_length = len(encoded_path)
written_size = ConvertApToJson(encoded_path, path_length)
if written_size > 0:
buffer = ctypes.create_string_buffer(int(written_size))
GetConvertResult(buffer, written_size)
json_result = buffer[:written_size].decode('utf-8')
return json_result
else:
return None
def is_subpath(parent_path, child_path):
try:
relative_path = os.path.relpath(child_path, parent_path)
return relative_path != os.pardir
except ValueError:
return False
def open_web(url):
webbrowser.open(url)
class SafeFileHandler:
def __init__(self):
self.lock = threading.Lock()
def parse_ap_file_safely(self, file_path):
with self.lock:
return parse_ap_file(file_path)
safe_handler = SafeFileHandler()
class ThreadedHTTPServer(ThreadingHTTPServer):
daemon_threads = True
class ApRequestHandler(SimpleHTTPRequestHandler):
global VERSION
def log_message(self, formate, *arg):
return
def simple_secure_filename(self, filename):
ascii_filename = filename.encode('ascii', errors='ignore').decode('ascii')
safe_chars = re.compile(r'[^\w\.\- ]')
safe_filename = safe_chars.sub('_', ascii_filename)
safe_filename = safe_filename.lstrip('.')
if not safe_filename:
return "index.html"
return safe_filename
def do_GET(self):
parse_result = urlparse(self.path)
if parse_result.path.startswith('/ap'):
self.application_handler(parse_result)
else:
self.send_error(http.HTTPStatus.NOT_FOUND, 'Not found')
def upload_handler(self):
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={
'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': self.headers['Content-Type'],
}
)
if 'file' in form:
file_item = form['file']
filename = self.simple_secure_filename(file_item.filename)
save_path = os.path.join(current, 'uploads', datetime.now().strftime('%Y%m%d%H%M%S%f'))
file_path = os.path.join(save_path, filename)
if not os.path.exists(save_path):
os.makedirs(save_path, mode=0o755)
fd = os.open(file_path, os.O_WRONLY | os.O_CREAT, 0o644)
with os.fdopen(fd, 'wb') as f:
f.write(file_item.file.read())
ap_res = safe_handler.parse_ap_file_safely(file_path)
os.remove(file_path)
dir_path = os.path.dirname(file_path)
if not os.listdir(dir_path):
os.rmdir(dir_path)
self.send_response(http.HTTPStatus.OK)
self.send_header('Content-Type', 'application/json')
self.end_headers()
if ap_res is None:
response = {"success": False, "code": -1, "message": "parse ap failed", "data": ""}
else:
response = {"success": True, "code": 0, "message": "success", "data": ap_res}
self.wfile.write(bytes(json.dumps(response), "utf-8"))
else:
self.send_error(http.HTTPStatus.BAD_REQUEST, 'Bad request')
def do_POST(self):
parse_result = urlparse(self.path)
if parse_result.path.startswith('/ap/upload'):
self.upload_handler()
else:
self.send_error(http.HTTPStatus.NOT_FOUND, 'Not found')
def application_handler(self, parse_result):
file_path = parse_result.path[3:]
file_extension = os.path.splitext(file_path)[1]
safe_path = os.path.normpath(file_path).lstrip('/')
if is_windows():
safe_path = os.path.normpath(file_path).lstrip("\\")
full_path = os.path.join(current, safe_path)
if file_path == '' or file_path == '/' or file_path is None or safe_path.strip() == ".":
full_path = os.path.join(current, "index.html")
file_extension = '.html'
elif not is_subpath(current, full_path):
self.send_error(http.HTTPStatus.NOT_FOUND, 'Not found')
return
try:
with open(full_path, 'rb') as file:
content = file.read()
self.send_response(http.HTTPStatus.OK)
self.send_header('Content-type', get_content_type(file_extension))
self.send_header("Cross-Origin-Opener-Policy", "unsafe-none")
self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Credentials", "true")
self.send_header("Access-Control-Allow-Headers", "x-requested-with, authorization, blade-auth")
self.send_header("Access-Control-Allow-Methods", "*")
self.send_header("Access-Control-Max-Age", "3600")
self.send_header("data-version", VERSION)
self.end_headers()
self.wfile.write(content)
except FileNotFoundError:
self.send_error(http.HTTPStatus.NOT_FOUND, 'File not found')
except Exception as e:
self.log_message("ERROR", f"Error handling GET request: {str(e)}")
self.send_error(http.HTTPStatus.INTERNAL_SERVER_ERROR, 'Internal Server Error')
def sigint_handler(sig, frame):
print("\nServer stopped by user")
global keep_running
keep_running = False
httpd.shutdown()
sys.exit(0)
def main(port):
global httpd, keep_running
server_address = ('', port)
httpd = ThreadedHTTPServer(server_address, ApRequestHandler)
print(f'Starting http server on port {port}...')
thread = threading.Thread(target=httpd.serve_forever, daemon=True)
thread.start()
if is_windows():
open_web(f'http:127.0.0.1:{args.port}/ap/')
while keep_running:
time.sleep(1)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Run a ap dump HTTP server.")
parser.add_argument("-p", "--port", type=int, default=9001, help="Specify the server port.")
args = parser.parse_args()
signal.signal(signal.SIGINT, sigint_handler)
main(args.port)

View File

@ -0,0 +1,32 @@
{
"filterKeys": [
"moduleName",
"funcName",
"type",
"isRoot",
"apKind",
"apAbcId",
"apId",
"offset"
],
"summaryTreeLevel": [
"ModuleName",
"->FunctionName",
"->Offset",
"->Type"
],
"apTableTr": [
{
"is_root_column_td": "isRoot"
},
{
"kind_column_td": "apKind"
},
{
"abc_column_td": "apAbcId"
},
{
"typeid_column_td": "apId"
}
]
}

View File

@ -0,0 +1,258 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '../base-ui/menu/MainMenu.js';
import '../base-ui/icon/LitIcon.js';
import '../base-ui/progress-bar/ProgressBar.js';
import './component/search/Search.js';
import './component/ApWelcome.js';
import {applicationHtml,} from './ApApplicationPublicFunc.js';
import './component/sheet/TabPaneApSummary.js';
import {TabPaneApSummary} from './component/sheet/TabPaneApSummary.js';
export class ApApplication extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'}).innerHTML = this.initHtml();
this.initElements();
}
set search(search) {
if (search) {
this.setAttribute('search', '');
} else {
this.removeAttribute('search');
}
}
get search() {
return this.hasAttribute('search');
}
initHtml() {
return applicationHtml;
}
initElements() {
this.rootEL = this.shadowRoot.querySelector('.root');
this.headerDiv = this.shadowRoot.querySelector('.search-vessel');
this.apWelcome = this.shadowRoot.querySelector('#ap-welcome');
this.apTreeTable = this.shadowRoot.querySelector('#tabpane-summary');
this.mainMenu = this.shadowRoot?.querySelector('#main-menu');
this.menu = this.mainMenu.shadowRoot?.querySelector('.menu-button');
this.progressEL = this.shadowRoot?.querySelector('.progress');
this.litSearch = this.shadowRoot?.querySelector('#lit-search');
this.initElementsAttr();
this.initSearchEvents();
this.resetMenus();
this.initGlobalEvents();
this.initSearchChangeEvents();
}
initElementsAttr() {
this.mainMenu.setAttribute('main_menu', '1');
this.childComponent = [
this.apTreeTable,
this.apWelcome,
];
}
openTraceFile(file) {
if (file === null) {
return;
}
this.litSearch.style.marginLeft = '0px';
this.openFileInit();
this.parseApFile(file);
}
initGlobalDropEvents() {
let body = document.querySelector('body');
body.addEventListener('drop', (event) => {
event.preventDefault();
event.stopPropagation();
if (this.rootEL.classList.contains('filedrag')) {
this.rootEL.classList.remove('filedrag');
}
if (event && event.dataTransfer && event.dataTransfer.items &&
event.dataTransfer.items.length > 0) {
let item = event.dataTransfer.items[0];
const entry = item.webkitGetAsEntry();
if (entry?.isFile) {
this.openTraceFile(item.getAsFile());
} else if (entry?.isDirectory) {
this.litSearch.setPercent('This File is not supported!', -1);
this.progressEL.loading = false;
this.freshMenuDisable(false);
}
}
}, false);
}
initGlobalEvents() {
let body = document.querySelector('body');
body.addEventListener('dragover', (event) => {
event.preventDefault();
event.stopPropagation();
if (event && event.dataTransfer && event.dataTransfer.items.length > 0 &&
event.dataTransfer.items[0].kind === 'file') {
event.dataTransfer.dropEffect = 'copy';
if (!this.rootEL.classList.contains('filedrag')) {
this.rootEL.classList.add('filedrag');
}
}
}, false);
body.addEventListener('dragleave', (event) => {
event.stopPropagation();
event.preventDefault();
if (this.rootEL.classList.contains('filedrag')) {
this.rootEL.classList.remove('filedrag');
}
}, false);
this.initGlobalDropEvents();
}
parseApFile(file) {
let fileName = file.name;
let showFileName = fileName.lastIndexOf('.') === -1 ? fileName : fileName.substring(0, fileName.lastIndexOf('.'));
this.litSearch.setPercent('', 1);
let fileSizeInKB = (file.size / 1024).toFixed(1);
document.title = `${showFileName} (${fileSizeInKB}KB)`;
if (!fileName.endsWith('.ap')) {
this.litSearch.setPercent('This File is not supported!', -1);
this.progressEL.loading = false;
this.freshMenuDisable(false);
} else {
try {
this.uploadFile(file).then((value) => {
this.litSearch.setPercent('', 101);
this.litSearch.style.pointerEvents = 'auto';
this.progressEL.loading = false;
this.freshMenuDisable(false);
this.apTreeTable.data = TabPaneApSummary.TransformJson(value);
});
} catch (httpError) {
this.litSearch.setPercent('Parse Filed Please Wait', -1);
this.progressEL.loading = false;
this.freshMenuDisable(false);
}
}
}
async uploadFile(file) {
const formData = new FormData();
formData.append('file', file, file.name);
const response = await fetch(`${window.location.origin}/ap/upload`, {
method: 'POST',
body: formData,
});
if (!response.ok) {
throw new Error(`file Upload Failedstatus${response.status}`);
}
return await response.text();
}
initSearchEvents() {
this.litSearch.addEventListener('focus', () => {
});
this.litSearch.addEventListener('ap-change', () => {
});
this.initSearchChangeEvents();
}
resetMenus() {
this.mainMenu.menus = [
{
collapsed: false,
title: 'Navigation',
second: false,
icon: 'caret-down',
describe: 'Import ap file',
children: [
{
title: 'Import ap file',
icon: 'folder',
fileChoose: true,
fileHandler: (ev) => {
this.openTraceFile(ev.detail);
},
}
],
}
];
}
openFileInit() {
this.litSearch.clear();
this.headerDiv.style.pointerEvents = 'none';
this.resetMenus();
this.freshMenuDisable(true);
this.showContent(this.apTreeTable);
this.progressEL.loading = true;
}
initSearchChangeEvents() {
let timer;
this.litSearch.valueChangeHandler = (value) => {
this.litSearch.list = [];
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
this.litSearch.isClearValue = false;
if (value.length > 0) {
this.apTreeTable.filterData(value.toString().toLowerCase());
} else {
this.litSearch.list = [];
this.apTreeTable.filterData('');
}
this.apTreeTable.expansionAll();
}, 500);
};
}
showContent(showNode) {
if (showNode === this.apTreeTable) {
this.menu.style.pointerEvents = 'auto';
this.search = true;
this.litSearch.style.display = 'block';
} else {
this.menu.style.pointerEvents = 'none';
this.search = this.litSearch.isLoading;
if (!this.search) {
this.litSearch.style.display = 'none';
}
}
this.childComponent.forEach((node) => {
if (node === showNode) {
showNode.style.visibility = 'visible';
} else {
node.style.visibility = 'hidden';
}
});
}
freshMenuDisable(disable) {
let menus;
this.mainMenu.menus[0].children[0].disabled = disable;
if (this.mainMenu.menus.length > 2) {
(menus = this.mainMenu.menus) === null || menus === void 0 ? void 0 : menus[1].children.map((it) => (it.disabled = disable));
}
}
}
if (!customElements.get('ap-application')) {
customElements.define('ap-application', ApApplication);
}

View File

@ -0,0 +1,185 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const applicationHtml = `
<style>
:host{
}
.root{
display: grid;
grid-template-rows: min-content 1fr;
grid-template-columns: min-content 1fr;
grid-template-areas: 'm s'
'm b';
height: 100vh;
width: 100vw;
}
.filedrag::after {
content: 'Drop the trace file to open it';
position: fixed;
z-index: 2001;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 5px dashed var(--dark-color1,#404854);
text-align: center;
font-size: 3rem;
line-height: 100vh;
background: rgba(255, 255, 255, 0.5);
}
.menu{
grid-area: m;
box-shadow: 4px 0px 20px rgba(0,0,0,0.05);
z-index: 2000;
}
.search-vessel{
z-index: 999;
position: relative;
cursor: default;
}
.progress{
bottom: 0;
position: absolute;
height: 1px;
left: 0;
right: 0;
}
:host(:not([search])) .search-vessel {
display: none;
}
:host(:not([search])) .search-vessel .search {
background-color: var(--dark-background5,#F6F6F6);
}
.search{
grid-area: s;
background-color: var(--dark-background,#FFFFFF);
height: 48px;
display: flex;
justify-content: center;
align-items: center;
}
.search .search-bg{
background-color: var(--dark-background5,#fff);
border-radius: 40px;
padding: 3px 20px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--dark-border,#c5c5c5);
}
lit-search input{
outline: none;
border: 0px;
background-color: transparent;
font-size: inherit;
color: var(--dark-color,#666666);
width: 30vw;
height: auto;
vertical-align:middle;
line-height:inherit;
height:inherit;
padding: 6px 6px 6px 6px};
max-height: inherit;
box-sizing: border-box;
}
::placeholder { /* CSS 3 標準 */
color: #b5b7ba;
font-size: 1em;
}
lit-search input::placeholder {
color: #b5b7ba;
font-size: 1em;
}
.content{
grid-area: b;
background-color: #ffffff;
height: 100%;
overflow: auto;
position:relative;
}
.sheet{
}
.sidebar-button{
position: absolute;
top: 0;
left: 0;
background-color: var(--dark-background1,#FFFFFF);
height: 100%;
border-radius: 0 5px 5px 0;
width: 48px;
display: flex;
align-content: center;
justify-content: center;
cursor: pointer;
}
:host{
font-size: inherit;
display: inline-block;
transition: .3s;
}
:host([spin]){
animation: rotate 1.75s linear infinite;
}
@keyframes rotate {
to{
transform: rotate(360deg);
}
}
.icon{
display: block;
width: 1em;
height: 1em;
margin: auto;
fill: currentColor;
overflow: hidden;
font-size: 20px;
color: var(--dark-color1,#47A7E0);
}
.content-center-option {
justify-content: center;
width: -webkit-fill-available;
margin-right: 5.2em;
align-items: center;
width: auto;
}
</style>
<div class="root" style="position: relative;">
<lit-main-menu id="main-menu" class="menu" data=''></lit-main-menu>
<div class="search-vessel">
<div class="search" style="position: relative;">
<div class="sidebar-button" style="width: 0">
<svg class="icon" id="icon" aria-hidden="true" viewBox="0 0 1024 1024">
<use id="use" xlink:href="./base-ui/icon.svg#icon-menu"></use>
</svg>
</div>
<lit-search id="lit-search"></lit-search>
</div>
<lit-progress-bar class="progress"></lit-progress-bar>
</div>
<div id="app-content" class="content">
<ap-welcome style="visibility:visible;top:0px;left:0px;position:absolute;z-index: 100" id="ap-welcome">
</ap-welcome>
<div style="height:100%;">
<tab-ap-summary id="tabpane-summary" mode="hidden" tree ondragstart="return false">
</tab-ap-summary>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class ApWelcome extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = this.initHtml();
}
initHtml() {
return `
<style>
:host {
width: 100%;
height: 100%;
display: grid;
justify-content: center;
align-content: center;
background: #F6F6F6;
}
.lit-icon{
content: url('img/pic.png');
}
</style>
<div>
<img class="lit-icon" >
</div>
`;
}
}
if (!customElements.get('ap-welcome')) {
customElements.define('ap-welcome', ApWelcome);
}

View File

@ -0,0 +1,163 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const SearchHtml = `
<style>
:host{
}
.root{
background-color: var(--dark-background5,#fff);
border-radius: 40px;
padding: 3px 20px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--dark-border,#c5c5c5);
width: 35vw;
overflow: hidden;
}
.root input{
outline: none;
width: max-content;
border: 0px;
background-color: transparent;
font-size: inherit;
color: var(--dark-color,#666666);
flex: 1;
height: auto;
vertical-align:middle;
line-height:inherit;
height:inherit;
padding: 6px 6px 6px 6px;
max-height: inherit;
box-sizing: border-box;
}
::placeholder {
color: #b5b7ba;
font-size: 1em;
}
.write::placeholder {
color: #b5b7ba;
font-size: 1em;
}
.readonly::placeholder {
color: #4f7ab3;
font-size: 1em;
}
.text-Roll::placeholder {
font-weight: 700;
color: #DB5860;
font-size: 1em;
}
:host([show-search-info]) .search-info{
display: inline-flex;
higth:100%!important;
justify-content: center;
align-items: center;
}
:host(:not([show-search-info])) .search-info{
display: none;
}
:host(:not([distributed])) #trace_select{
display: none;
}
:host([distributed]) #trace_select{
display: block;
}
.search-info span{
color:#ABABAB;
}
.search-info lit-icon{
font-weight: bold;
}
:host([textRoll]) input {
position: relative;
animation: textRoll 5s ease-in-out 0s backwards;
white-space: nowrap;
overflow: hidden;
display: block;
text-overflow: ellipsis;
}
@keyframes textRoll {
0% {
left: 0;
}
100% {
left: 100%;
}
}
.search-history {
position: relative;
}
.search-history-list {
list-style-type: none;
margin: 0;
padding: 0;
position: absolute;
width: 37vw;
top: 100%;
background-color: #FFFFFF;
border: 1px solid #ddd;
max-height: 200px;
overflow-y: auto;
display: none;
border-radius: 0 0 20px 20px;
}
.search-history-list:hover{
cursor: pointer;
}
.search-history-list-item {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
width: 100%;
}
.search-list:hover {
background-color: #e9e9e9;
}
.search-list {
display: flex;
justify-content: space-between;
padding-right: 20px;
padding-left: 45px;
}
#trace_selector{
position: unset;
width: 100px;
margin-left: -10px;
border:none;
}
input[name="retarge_index"]{
width:100px!important;
}
</style>
<div class="root" style="display: none">
<div id="trace_select" style="border-right: 1px solid var(--dark-border,#c5c5c5)">
<lit-select id="trace_selector" default-value="1" placement="bottom"></lit-select>
</div>
<lit-icon id="search-icon" name="search" size="22" color="#aaaaaa"></lit-icon>
<input name="search" class="readonly" placeholder="Search" readonly/>
<div class="search-info">
<input name="retarge_index" placeholder="Go" oninput="value=value.replace(/^(0+)|[^0-9]/g,'')"/>
</div>
</div>
<div id="prompt" style="display: none"></div>
<div class="search-history">
<ul class="search-history-list"></ul>
</div>
`;

View File

@ -0,0 +1,230 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {SearchHtml} from './Search.html.js';
const LOCAL_STORAGE_SEARCH_KEY = 'search_key';
export class LitSearch extends HTMLElement {
constructor() {
super();
this._index = 0;
this._list = [];
this._value = false;
this.historyMaxCount = 100;
this.searchList = [];
this.searchELList = [];
this.attachShadow({mode: 'open'}).innerHTML = this.initHtml();
this.initElements();
}
get list() {
return this._list;
}
set list(value) {
this._list = value;
}
get index() {
return this._index;
}
set index(value) {
this._index = value;
}
get searchValue() {
return this.search?.value ?? '';
}
get isLoading() {
return this.hasAttribute('isLoading');
}
set isLoading(va) {
if (va) {
this.setAttribute('isLoading', '');
} else {
this.removeAttribute('isLoading');
window.localStorage.setItem(LOCAL_STORAGE_SEARCH_KEY, '');
}
}
set isClearValue(value) {
this._value = value;
}
get isClearValue() {
return this._value;
}
valueChangeHandler = () => {
};
setPercent(name = '', value) {
let searchHide = this.shadowRoot.querySelector('.root');
let searchIcon = this.shadowRoot.querySelector('#search-icon');
if (this.hasAttribute('textRoll')) {
this.removeAttribute('textRoll');
}
this.isLoading = false;
if (value > 0 && value <= 100) {
searchHide.style.display = 'flex';
searchHide.style.backgroundColor = '#e3e3e3';
searchIcon === null || searchIcon === void 0 ? void 0 : searchIcon.setAttribute('name', 'cloud-sync');
this.search.setAttribute('placeholder', `${name}${value}%`);
this.search.setAttribute('readonly', '');
this.search.className = 'readonly';
this.isLoading = true;
} else if (value > 100) {
searchHide.style.display = 'flex';
searchHide.style.backgroundColor = '#fff';
searchIcon?.setAttribute('name', 'search');
this.search?.setAttribute('placeholder', 'search');
this.search?.removeAttribute('readonly');
this.search.className = 'write';
} else if (value === -1) {
searchHide.style.display = 'flex';
searchHide.style.backgroundColor = '#e3e3e3';
searchIcon === null || searchIcon === void 0 ? void 0 : searchIcon.setAttribute('name', 'cloud-sync');
this.search.setAttribute('placeholder', `${name}`);
this.search.setAttribute('readonly', '');
this.search.className = 'readonly';
} else {
searchHide.style.display = 'none';
}
}
clear() {
this.search = this.shadowRoot.querySelector('input');
this.search.value = '';
this.list = [];
}
blur() {
this.search?.blur();
}
updateSearchList(searchStr) {
if (searchStr === null || searchStr.length === 0 || searchStr.trim().length === 0) {
return;
}
let searchInfo = this.searchList.find((searchInfo) => searchInfo.searchContent === searchStr);
if (searchInfo !== undefined) {
let index = this.searchList.indexOf(searchInfo);
this.searchList.splice(index, 1);
this.searchList.unshift({searchContent: searchStr, useCount: 1});
} else {
this.searchList.unshift({searchContent: searchStr, useCount: 1});
}
}
getSearchHistory() {
let searchString = window.localStorage.getItem(LOCAL_STORAGE_SEARCH_KEY);
if (searchString) {
let searHistory = JSON.parse(searchString);
if (Array.isArray(searHistory)) {
this.searchList = searHistory;
return searHistory;
}
}
return [];
}
searchFocusListener() {
}
searchBlurListener() {
this.dispatchEvent(new CustomEvent('blur', {
detail: {
value: this.search.value,
},
}));
}
searchKeyupListener(e) {
if (e.code === 'Enter' || e.code === 'NumpadEnter') {
this.updateSearchList(this.search.value);
} else {
this.updateSearchHistoryList(this.search.value);
this.valueChangeHandler?.call(this, this.trimSideSpace(this.search.value));
}
e.stopPropagation();
}
trimSideSpace(str) {
return str.replace(/(^\s*)|(\s*$)/g, '');
}
initElements() {
this.search = this.shadowRoot.querySelector('input');
this.searchHistoryListEL = this.shadowRoot.querySelector('.search-history-list');
this.retargetIndex = this.shadowRoot.querySelector('input[name="retarge_index"]');
this.search.addEventListener('focus', () => {
});
this.search.addEventListener('blur', () => {
this.searchBlurListener();
});
this.search.addEventListener('change', () => {
this.index = -1;
this.retargetIndex.value = '';
});
this.search.addEventListener('keyup', (e) => {
this.retargetIndex.value = '';
this.index = -1;
this.searchKeyupListener(e);
});
(this.shadowRoot?.querySelector('input[name="retarget_index"]'))?.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.stopPropagation();
}
});
}
initHtml() {
return SearchHtml;
}
hideSearchHistoryList() {
this.searchHistoryListEL.style.display = 'none';
if (this.searchList.length > this.historyMaxCount) {
this.searchList = this.searchList.slice(0, this.historyMaxCount);
}
if (this.searchList.length === 0) {
return;
}
let historyStr = JSON.stringify(this.searchList);
window.localStorage.setItem(LOCAL_STORAGE_SEARCH_KEY, historyStr);
this.searchList = [];
this.searchELList = [];
}
updateSearchHistoryList(searchValue) {
const keyword = searchValue.toLowerCase();
this.searchELList.forEach((item) => {
if (item.textContent.toLowerCase().includes(keyword)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
}
}
if (!customElements.get('lit-search')) {
customElements.define('lit-search', LitSearch);
}

View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const TabPaneApSummaryHtml = `<style>
:host{
padding: 10px 10px;
display: flex;
flex-direction: column;
}
.tab-summary-head {
display: grid;
grid-template-columns: 40% 15% 15% 15% 15%;
height: 30px;
line-height: 30px;
align-items: center;
background-color: white;
}
.tree-row-tr {
display: flex;
height: 30px;
line-height: 30px;
align-items: center;
background-color: white;
width: 100%;
}
.tree-row-tr:hover {
background-color: #DEEDFF;
}
.tree-row-tr:nth-last-child(1):hover {
background-color: white;
}
.head-label, .head-count {
white-space: nowrap;
overflow: hidden;
font-weight: bold;
}
.row-name-td {
white-space: nowrap;
overflow-y: hidden;
display: inline-block;
margin-right: 15px;
height: 30px;
}
tr {
height: 30px;
}
.row-name-td::-webkit-scrollbar {
display: none;
}
.ap-tree-table {
display: grid;
overflow: hidden;
grid-template-rows: repeat(auto-fit, 30px);
position: sticky;
top: 0;
}
.ap-tree-table:hover{
overflow-x: auto;
}
</style>
<div class="tab-summary-head">
<div style="justify-content: flex-start; display: flex">
<div class="expansion-div" style="display: grid;">
<lit-icon class="expansion-up-icon" name="up"></lit-icon>
<lit-icon class="expansion-down-icon" name="down"></lit-icon>
</div>
<label class="head-label" style="cursor: pointer;">ModuleName</label>
<label class="head-label" style="cursor: pointer;">->FunctionName</label>
<label class="head-label" style="cursor: pointer;">->Offset</label>
<label class="head-label" style="cursor: pointer;">->Type</label>
</div>
<label class="head-count">isRoot</label>
<label class="head-count">Kind</label>
<label class="head-count">abcId</label>
<label class="head-count">Id</label>
</div>
<div id="tab-summary" style="overflow: auto;display: grid; grid-template-columns: 40% 15% 15% 15% 15%;"></div>
`;

View File

@ -0,0 +1,418 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {TabPaneApSummaryHtml} from './TabPaneApSummary.html.js';
export class TabPaneApSummary extends HTMLElement {
static jsonData = null;
constructor() {
super();
this.apDataSource = [];
this.apData = [];
this.apTreeNodes = [];
this.expandedNodeList = new Set();
this.selectTreeDepth = 0;
fetch('/ap/config.json')
.then(response => response.json())
.then(data => {
TabPaneApSummary.jsonData = data;
this.expansionClickEvent = () => {
let apItem;
this.expandedNodeList.clear();
if (((apItem = this.expansionUpIcon) === null || apItem === void 0 ? void 0 : apItem.name) === 'down') {
this.selectTreeDepth = 0;
this.expansionUpIcon.name = 'up';
this.expansionDownIcon.name = 'down';
} else {
this.selectTreeDepth = TabPaneApSummary.jsonData.summaryTreeLevel.length;
this.expansionUpIcon.name = 'down';
this.expansionDownIcon.name = 'up';
}
this.refreshSelectDepth(this.apTreeNodes);
this.refreshRowNodeTable(true);
};
this.attachShadow({mode: 'open'}).innerHTML = this.initHtml();
this.initElements();
});
}
set data(apDatas) {
this.apDataSource = [];
this.expandedNodeList.clear();
this.expansionUpIcon.name = 'up';
this.expansionDownIcon.name = 'down';
this.apSummaryTable.innerHTML = '';
this.apDataSource = apDatas;
this.apData = this.apDataSource;
if (this.apData.length !== 0) {
this.refreshRowNodeTable();
}
}
filterData(filter) {
const keysOfApBean = TabPaneApSummary.jsonData.filterKeys;
if (filter === '') {
this.apData = this.apDataSource;
} else {
this.apData = this.apDataSource.filter((item) => {
return keysOfApBean.some(field => {
const value = item[field];
return value !== undefined && value !== null && value.toString().toLowerCase().includes(filter);
});
});
}
this.expandedNodeList.clear();
this.expansionUpIcon.name = 'up';
this.expansionDownIcon.name = 'down';
this.apSummaryTable.innerHTML = '';
this.refreshRowNodeTable();
}
initElements() {
this.apSummaryTable = this.shadowRoot?.querySelector('#tab-summary');
this.expansionDiv = this.shadowRoot?.querySelector('.expansion-div');
this.expansionUpIcon = this.shadowRoot?.querySelector('.expansion-up-icon');
this.expansionDownIcon = this.shadowRoot?.querySelector('.expansion-down-icon');
let summaryTreeLevel = TabPaneApSummary.jsonData.summaryTreeLevel;
this.shadowRoot?.querySelectorAll('.head-label').forEach((summaryTreeHead) => {
summaryTreeHead.addEventListener('click', () => {
this.selectTreeDepth = summaryTreeLevel.indexOf(summaryTreeHead.textContent);
this.expandedNodeList.clear();
this.refreshSelectDepth(this.apTreeNodes);
this.refreshRowNodeTable(true);
});
});
this.apSummaryTable.onscroll = () => {
let apTreeTableEl = this.shadowRoot?.querySelector('.ap-tree-table');
if (apTreeTableEl) {
apTreeTableEl.scrollTop = (this.apSummaryTable?.scrollTop) || 0;
}
};
}
expansionAll() {
this.selectTreeDepth = TabPaneApSummary.jsonData.summaryTreeLevel.length - 1;
this.expandedNodeList.clear();
this.refreshSelectDepth(this.apTreeNodes);
this.refreshRowNodeTable(true);
}
initHtml() {
return TabPaneApSummaryHtml;
}
connectedCallback() {
new ResizeObserver(() => {
this.parentElement.style.overflow = 'hidden';
this.refreshRowNodeTable();
}).observe(this.parentElement);
this.expansionDiv?.addEventListener('click', this.expansionClickEvent);
}
disconnectedCallback() {
this.expansionDiv?.removeEventListener('click', this.expansionClickEvent);
}
refreshSelectDepth(apTreeNodes) {
apTreeNodes.forEach((item) => {
if (item.depth < this.selectTreeDepth) {
this.expandedNodeList.add(item.id);
if (item.children.length > 0) {
this.refreshSelectDepth(item.children);
}
}
});
}
createTr(rowNode, className, proper) {
let elementTr = document.createElement('tr');
let elementTd = document.createElement('td');
elementTr.title = rowNode[proper];
elementTd.textContent = rowNode[proper];
elementTd.className = className;
elementTr.appendChild(elementTd);
return elementTr;
}
createRowNodeTableEL(rowNodeList, tableTreeEl, tableRightDivs) {
let unitPadding = 20;
let leftPadding = 5;
rowNodeList.forEach((rowNode) => {
let tableTreeRowEl = document.createElement('tr');
tableTreeRowEl.className = 'tree-row-tr';
tableTreeRowEl.title = `${rowNode.apName}`;
let leftSpacingEl = document.createElement('td');
leftSpacingEl.style.paddingLeft = `${rowNode.depth * unitPadding + leftPadding}px`;
tableTreeRowEl.appendChild(leftSpacingEl);
this.addToggleIconEl(rowNode, tableTreeRowEl);
let rowNodeTextEL = document.createElement('td');
rowNodeTextEL.textContent = `${rowNode.apName}`;
rowNodeTextEL.className = 'row-name-td';
tableTreeRowEl.appendChild(rowNodeTextEL);
tableTreeEl.appendChild(tableTreeRowEl);
let apTrs = TabPaneApSummary.jsonData.apTableTr;
for (let index = 0; index < apTrs.length; index++) {
let item = apTrs[index];
Object.keys(item).forEach((key) => {
tableRightDivs[index].appendChild(this.createTr(rowNode, key, item[key]));
});
}
if (rowNode.children && this.expandedNodeList.has(rowNode.id)) {
this.createRowNodeTableEL(rowNode.children, tableTreeEl, tableRightDivs);
}
});
}
addToggleIconEl(rowNode, tableRowEl) {
let toggleIconEl = document.createElement('td');
let expandIcon = document.createElement('lit-icon');
expandIcon.classList.add('tree-icon');
if (rowNode.children && rowNode.children.length > 0) {
toggleIconEl.appendChild(expandIcon);
expandIcon.name = this.expandedNodeList.has(rowNode.id) ? 'minus-square' : 'plus-square';
toggleIconEl.classList.add('expand-icon');
toggleIconEl.addEventListener('click', () => {
let scrollTop = this.apSummaryTable?.scrollTop ?? 0;
this.changeNode(rowNode.id);
this.apSummaryTable.scrollTop = scrollTop;
let apTreeTableEl = this.shadowRoot?.querySelector('.ap-tree-table');
if (apTreeTableEl) {
apTreeTableEl.scrollTop = scrollTop;
}
});
}
tableRowEl.appendChild(toggleIconEl);
}
changeNode(currentNode) {
if (this.expandedNodeList.has(currentNode)) {
this.expandedNodeList.delete(currentNode);
} else {
this.expandedNodeList.add(currentNode);
}
this.refreshRowNodeTable();
}
refreshRowNodeTable(useCacheRefresh = false) {
if (this.apSummaryTable === undefined) {
return;
}
this.apSummaryTable.innerHTML = '';
if (this.apSummaryTable && this.parentElement) {
this.apSummaryTable.style.height = `${this.parentElement.clientHeight - 30}px`;
}
if (!useCacheRefresh) {
this.apTreeNodes = this.buildTreeTblNodes(this.apData);
}
let tableFragmentEl = document.createDocumentFragment();
let tableTreeEl = document.createElement('div');
tableTreeEl.className = 'ap-tree-table';
let apTrs = TabPaneApSummary.jsonData.apTableTr;
let tableRightDivs = [];
for (let index = 0; index < apTrs.length; index++) {
tableRightDivs.push(document.createElement('div'));
}
if (this.parentElement) {
tableTreeEl.style.height = `${this.parentElement.clientHeight - 40}px`;
}
this.createRowNodeTableEL(this.apTreeNodes, tableTreeEl, tableRightDivs);
let emptyTr = document.createElement('tr');
emptyTr.className = 'tree-row-tr';
tableTreeEl === null || tableTreeEl === void 0 ? void 0 : tableTreeEl.appendChild(emptyTr);
tableFragmentEl.appendChild(tableTreeEl);
for (let index = 0; index < tableRightDivs.length; index++) {
let emptyItemTr = document.createElement('tr');
emptyItemTr.className = 'tree-row-tr';
tableRightDivs[index].appendChild(emptyItemTr);
tableFragmentEl.appendChild(tableRightDivs[index]);
}
this.apSummaryTable.appendChild(tableFragmentEl);
}
buildTreeTblNodes(apTreeNodes) {
let id = 0;
let root = {
id: id,
depth: 0,
children: [],
apName: 'All',
type: '',
isRoot: '',
apKind: '',
apAbcId: '',
apId: ''
};
apTreeNodes.forEach((item) => {
id++;
let moduleNode = root.children.find((node) => node.apName === item.moduleName);
let result = this.buildItem(moduleNode, id, item, root);
id = result.id;
let offsetNode = result.offsetNode;
if (offsetNode.depth === 2) {
let typeNode;
id++;
typeNode = {
id: id, depth: 3, children: [], apName: item.type, type: '',
isRoot: `${item.isRoot}`,
apKind: item.apKind === '-' ? '-' : TabPaneApSummary.KindMap[item.apKind === undefined ?
'UnknowId' : item.apKind],
apAbcId: `${item.apAbcId}`,
apId: `${item.apId}`
};
offsetNode.children.push(typeNode);
}
});
return root.children.sort((leftData, rightData) => {
return leftData.apName.localeCompare(rightData.apName);
});
}
buildItem(moduleNode, id, item, root) {
if (!moduleNode) {
id++;
moduleNode = {
id: id, depth: 0,
children: [],
apName: item.moduleName,
type: '',
isRoot: '-',
apKind: '-',
apAbcId: '-',
apId: '-'
};
root.children.push(moduleNode);
}
let funcNode = moduleNode.children.find((node) => node.apName === item.funcName);
if (!funcNode) {
id++;
funcNode = {
id: id, depth: 1,
children: [],
apName: item.funcName,
type: '',
isRoot: '-',
apKind: '-',
apAbcId: '-',
apId: '-'
};
moduleNode.children.push(funcNode);
}
if (item.offset === '-') {
return {id: id, offsetNode: funcNode};
}
let offsetNode = funcNode.children.find((node) => node.apName === item.offset);
if (!offsetNode) {
id++;
offsetNode = {
id: id, depth: 2,
children: [],
apName: item.offset,
type: '',
isRoot: '-',
apKind: '-',
apAbcId: '-',
apId: '-'
};
funcNode.children.push(offsetNode);
}
return {id, offsetNode};
}
static MakeApObj(moduleName, functionName, item) {
if (item === undefined) {
item = new TypeItem();
}
let abcId = this.abcFilePoolMap.get(String(item.abcId));
if (abcId === undefined) {
abcId = '-';
}
let transformedItem = {
moduleName: moduleName,
funcName: functionName,
offset: (item === null || item === void 0 ? void 0 : item.typeOffset) === undefined ? '-' : item.typeOffset,
type: (item === null || item === void 0 ? void 0 : item.typeName) === undefined ? '-' : item.typeName,
apKind: (item === null || item === void 0 ? void 0 : item.kind) === undefined ? '-' : item.kind,
apAbcId: (item === null || item === void 0 ? void 0 : item.abcId) === undefined ? '-' : abcId,
apId: (item === null || item === void 0 ? void 0 : item.id) === undefined ? '-' : item.id,
isRoot: (item === null || item === void 0 ? void 0 : item.isRoot) === undefined ? '-' : item.isRoot
};
return transformedItem;
}
static TransformJson(value) {
let apRes = JSON.parse(value);
let apData = JSON.parse(apRes.data);
let jsonData = apData.recordDetail;
let abcFilePool = apData.abcFilePool;
this.abcFilePoolMap = new Map(abcFilePool.map(item => {
let parts = item.abcFile.split(/\/|\\/);
let fileName = parts.length > 1 ? parts[parts.length - 1] : item.abcFile;
return [item.abcId, fileName];
}));
let result = [];
for (let moduleItem of jsonData) {
for (let functionItem of moduleItem.function) {
if (functionItem.type.length === 0) {
result.push(TabPaneApSummary.MakeApObj(moduleItem.moduleName, functionItem.functionName));
}
for (let typeItem of functionItem.type) {
if (Array.isArray(typeItem)) {
for (let typesItem of typeItem) {
if (Array.isArray(typesItem)) {
for (let item of typesItem) {
result.push(TabPaneApSummary.MakeApObj(moduleItem.moduleName, functionItem.functionName, item));
}
} else {
result.push(TabPaneApSummary.MakeApObj(moduleItem.moduleName, functionItem.functionName, typesItem));
}
}
} else {
result.push(TabPaneApSummary.MakeApObj(moduleItem.moduleName, functionItem.functionName, typeItem));
}
}
}
}
return result;
}
}
TabPaneApSummary.KindMap = {
'0': 'ClassId',
'1': 'LiteralId',
'2': 'BuiltinsId',
'3': 'LegacyKind',
'4': 'MethodId',
'5': 'BuiltinFunctionId',
'6': 'RecordClassId',
'7': 'PrototypeId',
'8': 'ConstructorId',
'9': 'MegaStateKinds',
'10': 'TotalKinds',
'11': 'UnknowId',
'12': 'GlobalsId'
};
TabPaneApSummary.abcFilePoolMap = new Map();
if (!customElements.get('tab-ap-summary')) {
customElements.define('tab-ap-summary', TabPaneApSummary);
}
export class TypeItem {
constructor() {
this.typeName = '-';
this.typeOffset = '-';
this.isRoot = '-';
this.kind = '-';
this.abcId = '-';
this.id = '-';
}
}

View File

@ -0,0 +1,90 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true"
xmlns="http://www.w3.org/2000/svg">
<symbol id="icon-minus-square" viewBox="0 0 16 16">
<g id="play_expand" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="rectangle" stroke="currentColor" opacity="0.899323091" x="2" y="2" width="12"
height="12"></rect>
<rect id="rectangle" fill="currentColor" opacity="0.802454776" x="4.00073886"
y="7.50196767" width="8" height="1"></rect>
</g>
</symbol>
<symbol id="icon-plus-square" viewBox="0 0 16 16">
<g id="play_retract" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="rectangle" stroke="currentColor" opacity="0.899323091" x="2" y="2" width="12"
height="12"></rect>
<path
d="M8.50073886,4.00196767 L8.50073886,7.50196767 L12.0007389,7.50196767 L12.0007389,8.50196767 L8.50073886,8.50196767 L8.50073886,12.0019677 L7.50073886,12.0019677 L7.49973886,8.50196767 L4.00073886,8.50196767 L4.00073886,7.50196767 L7.50073886,7.50196767 L7.50073886,4.00196767 L8.50073886,4.00196767 Z"
id="shapecombine" fill="currentColor" opacity="0.899323091"></path>
</g>
</symbol>
<symbol id="icon-folder" viewBox="0 0 16 16">
<g id="toolWindowProject" stroke="none" stroke-width="1" fill="currentColor"
fill-rule="evenodd" opacity="0.600880941">
<g transform="translate(2.498604, 3.499928)" fill="currentColor" id="path">
<polygon points="5.75640322 2 11 2 11 9 0 9 0 2 0 0 4.60006714 0"></polygon>
</g>
</g>
</symbol>
<symbol id="icon-select" viewBox="0 0 16 16">
<g id="export" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="1">
<g transform="translate(2.000000, 2.000000)" fill="#7FA1E7">
<path d="M2,2 L2,10 L10,10 L10,12 L0,12 L0,2 L2,2 Z" id="shapecombine"></path>
<g id="marshalling"
transform="translate(7.897997, 4.105003) rotate(180.000000) translate(-7.897997, -4.105003) translate(3.795994, -0.000000)">
<path
d="M6.78979195,1.95399252e-13 L8.20400551,1.41421356 L3.704,5.914 L6,8.21000551 L0,8.21000551 L0,2.21000551 L2.289,4.499 L6.78979195,1.95399252e-13 Z"
id="shapecombine"></path>
</g>
</g>
</g>
</symbol>
<symbol id="icon-down" viewBox="0 0 16 16">
<g id="ic_down" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="1">
<g id="play_back" transform="translate(3.087500, 4.912500)" fill="currentColor">
<polygon id="path"
transform="translate(5.000000, 3.087500) rotate(-270.000000) translate(-5.000000, -3.087500) "
points="1.9125 -0.7375 5.7375 3.0875 1.9125 6.9125 3.0875 8.0875 8.0875 3.0875 3.0875 -1.9125"></polygon>
</g>
</g>
</symbol>
<symbol id="icon-up" viewBox="0 0 16 16">
<g id="ic_up" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="1">
<g id="play_back" transform="translate(3.087500, 4.912500)" fill="currentColor">
<polygon id="path"
transform="translate(5.000000, 3.087500) rotate(-90.000000) translate(-5.000000, -3.087500) "
points="1.9125 -0.7375 5.7375 3.0875 1.9125 6.9125 3.0875 8.0875 8.0875 3.0875 3.0875 -1.9125"></polygon>
</g>
</g>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24">
<g id="seach" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<circle id="oval" stroke="#afafaf" stroke-width="1.2" cx="10.7192096" cy="10.5553598"
r="5"></circle>
<line x1="15.4514426" y1="15.7875928" x2="18.9869765" y2="19.3231267" id="line-9"
stroke="#afafaf" stroke-width="1.2" stroke-linecap="round"></line>
</g>
</symbol>
<symbol id="icon-menu" viewBox="0 0 16 16">
<g id="ic_list" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="marshalling-2">
<rect id="rectangle" x="0" y="0" width="16" height="16"></rect>
<rect id="rectangle" fill="currentColor" opacity="0.600880941" x="1" y="2"
width="14" height="2"></rect>
<rect id="rectangle" fill="currentColor" opacity="0.600880941" x="1" y="7"
width="14" height="2"></rect>
<rect id="rectangle-2" fill="currentColor" opacity="0.600880941" x="1" y="12"
width="14" height="2"></rect>
</g>
</g>
</symbol>
<symbol id="icon-close" viewBox="0 0 24 24">
<g id="close" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="rectangle" fill="#000000"
transform="translate(12.002785, 12.003027) rotate(-315.000000) translate(-12.002785, -12.003027) "
x="4.50278502" y="11.4030272" width="15" height="1.2" rx="0.6"></rect>
<rect id="rectangle" fill="#000000"
transform="translate(12.001725, 12.001590) rotate(-315.000000) translate(-12.001725, -12.001590) "
x="11.4017247" y="4.50158952" width="1.2" height="15" rx="0.6"></rect>
</g>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -0,0 +1,106 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class LitIcon extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = this.initHtml();
this.initElements();
}
static get observedAttributes() {
return ['name', 'size', 'color', 'path'];
}
get name() {
return this.getAttribute('name') || '';
}
set name(value) {
this.setAttribute('name', value);
}
get size() {
return parseInt(this.getAttribute('size') || '0', 10);
}
set size(value) {
this.setAttribute('size', `${value}`);
}
set color(value) {
this.setAttribute('color', value);
}
set path(value) {
this._path = value;
this.setAttribute('path', value);
}
initHtml() {
return `
<style>
:host{
display: inline-block;
font-size: inherit;
}
.icon{
width: 1em;
height: 1em;
display: block;
fill: currentColor;
overflow: hidden;
margin: auto;
}
@keyframes rotate {
to{
transform: rotate(360deg);
}
}
:host([spin]){
animation: rotate 1.75s linear infinite;
}
</style>
<svg class="icon" id="icon" aria-hidden="true" viewBox="0 0 ${this.view || 1024} ${this.view || 1024}">
${this._path ? '<path id="path"></path>' : '<use id="use"></use>'}
</svg>
`;
}
initElements() {
if (this.shadowRoot) {
this.icon = this.shadowRoot.getElementById('icon');
this.use = this.shadowRoot.querySelector('use');
this.apPath = this.shadowRoot.querySelector('path');
}
}
attributeChangedCallback(name, oldValue, value) {
switch (name) {
case 'name':
if (this.use) {
this.use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `./base-ui/icon.svg#icon-${value}`);
}
break;
case 'path':
if (this.apPath) {
this.apPath.setAttribute('apPath', value);
}
break;
case 'color':
if (this.icon) {
this.icon.style.color = value;
}
break;
case 'size':
if (this.icon) {
this.icon.style.fontSize = `${value}px`;
}
break;
}
}
}
if (!customElements.get('lit-icon')) {
customElements.define('lit-icon', LitIcon);
}

View File

@ -0,0 +1,171 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './MainMenuItem.js';
import './MainMenuGroup.js';
import { MainMenuGroup } from './MainMenuGroup.js';
import { MainMenuItem } from './MainMenuItem.js';
const initHtmlStyle = `
<style>
:host{
width: 248px;
height: 100vh;
display: flex;
flex-direction: column;
background-color: #fff;
}
.menu-body ::-webkit-scrollbar-thumb
{
background-color: var(--dark-background,#FFFFFF);
border-radius:10px;
}
.menu-body ::-webkit-scrollbar-track
{
border-radius:10px;
background-color:#F5F5F5;
}
.header{
display: grid;
width: 100%;
height: 56px;
font-size: 1.4rem;
padding-left: 20px;
gap: 0 20px;
box-sizing: border-box;
grid-template-columns: min-content 1fr min-content;
grid-template-rows: auto;
color: #47A7E0;
background-color: var(--dark-background1);
border-bottom: 1px solid var(--dark-background1,#EFEFEF);
}
.bottom{
width: 100%;
display: flex;
justify-content: space-between;
}
.header *{
user-select: none;
align-self: center;
}
*{
box-sizing: border-box;
}
</style>
`;
export class MainMenu extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = this.initHtml();
this.initElements();
}
get menus() {
return this._menus;
}
set menus(value) {
this._menus = value;
this.shadowRoot?.querySelectorAll('lit-main-menu-group').forEach((menuItem) => menuItem.remove());
let menuBody = this.shadowRoot?.querySelector('.menu-body');
if (this.getAttribute('main_menu') === '1' && window.localStorage.getItem('Theme') === 'dark') {
this.style.backgroundColor = '#262f3c';
}
else {
this.style.backgroundColor = '#fff';
}
value === null || value === void 0 ? void 0 : value.forEach((it) => {
let group = new MainMenuGroup();
group.setAttribute('title', it.title || '');
if (it.describe !== '') {
group.setAttribute('describe', it.describe || '');
}
else {
group.removeAttribute('describe');
}
group.setAttribute('icon', it.icon || '');
if (it.collapsed) {
group.setAttribute('collapsed', '');
}
else {
group.removeAttribute('collapsed');
}
let groupName = group.shadowRoot.querySelector('.group-name');
let groupDescribe = group.shadowRoot.querySelector('.group-describe');
menuBody === null || menuBody === void 0 ? void 0 : menuBody.appendChild(group);
it.children?.forEach((item) => {
if (item.fileModel !== undefined && item.fileModel === 'db') {
return;
}
this.notChildren(item, group, groupName, groupDescribe);
});
});
}
notChildren(item, group, groupName, groupDescribe) {
let th = new MainMenuItem();
th.setAttribute('icon', item.icon || '');
th.setAttribute('title', item.title || '');
if (this.getAttribute('main_menu') === '1' && window.localStorage.getItem('Theme') === 'dark') {
groupName.style.color = 'white';
groupDescribe.style.color = 'white';
th.style.color = 'white';
}
else {
groupName.style.color = 'black';
groupDescribe.style.color = 'black';
th.style.color = 'black';
}
if (item.fileChoose) {
th.setAttribute('file', '');
th.addEventListener('file-change', (event) => {
if (item.fileHandler && !th.disabled) {
item.fileHandler(event);
}
});
}
else {
th.removeAttribute('file');
th.addEventListener('click', (event) => {
if (item.clickHandler && !th.disabled) {
item.clickHandler(item);
}
});
}
if (item.multi) {
th.multi = true;
}
if (item.disabled !== undefined) {
th.disabled = item.disabled;
}
group === null || group === void 0 ? void 0 : group.appendChild(th);
}
initElements() {
}
initHtml() {
return `
${initHtmlStyle}
<div class="header" name="header">
<img src="img/ap_logo.png"/>
<div class="menu-button">
<lit-icon name="menu" size="20" color="var(blue,#4D4D4D)"></lit-icon>
</div>
</div>
<div class="menu-body" style="overflow: auto;overflow-x:hidden;height: 100%">
<slot id="st" ></slot>
</div>
`;
}
}
if (!customElements.get('lit-main-menu')) {
customElements.define('lit-main-menu', MainMenu);
}

View File

@ -0,0 +1,183 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const initHtmlStyle = `
<style>
:host(:not([collapsed])){
width: 248px;
display: flex;
background-color: var(--dark-background);
cursor: pointer;
flex-direction: column;
}
:host{
user-select: none;
transition: background-color .3s;
}
:host(:not([collapsed])),:host(:not([second])) ::slotted(lit-main-menu-item){
display: flex;
}
host(:not([collapsed])) :host([second]) ::slotted(lit-main-menu-group){
display:flex;
}
:host([second]) .group-name{
padding-left:40px;
}
:host(:not([collapsed])) .group-describe{
height: 0;
visibility: hidden;
padding:0;
}
:host([collapsed]):hover){
background-color: #FFFFFF;
}
:host([collapsed]){
width: 248px;
display: flex;
flex-direction: column;
cursor: pointer;
background-color: var(--dark-background);
}
:host([collapsed]) .group-describe{
height: auto;
visibility: visible;
}
:host([radius]) {
border-radius: 16px 0px 0px 16px ;
}
:host([collapsed]) ::slotted(lit-main-menu-item){
display: none;
}
:host([collapsed]) ::slotted(lit-main-menu-group){
display:none;
}
:host(:not([describe])) .group-describe{
display:none;
}
:host([describe]) .group-describe{
padding: 4px 24px 0 24px;
color: #999 !important;
font-size: 12px;
}
:host([describe]) .group-name{
margin-top: 10px;
}
.group-name{
display:flex;
font-size: 14px;
font-family: Helvetica;
color: #000;
padding: 15px 24px 5px 10px;
line-height: 16px;
font-weight: 400;
text-align: left;
}
:host([collapsed]) .icon{
transform: rotateZ(-90deg);
}
</style>
`;
export class MainMenuGroup extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = this.initHtml();
this.initElements();
}
static get observedAttributes() {
return ['title', 'describe', 'collapsed', 'nocollapse', 'radius', 'second', 'icon'];
}
get second() {
return this.hasAttribute('second');
}
set second(value) {
if (value) {
this.setAttribute('second', '');
}
else {
this.removeAttribute('second');
}
}
get collapsed() {
return this.hasAttribute('collapsed');
}
set collapsed(value) {
if (value) {
this.setAttribute('collapsed', '');
}
else {
this.removeAttribute('collapsed');
}
}
get nocollapsed() {
return this.hasAttribute('nocollapsed');
}
set nocollapsed(value) {
if (value) {
this.setAttribute('nocollapsed', '');
}
else {
this.removeAttribute('nocollapsed');
}
}
get radius() {
return this.hasAttribute('radius');
}
initElements() {
this.groupNameEl = this.shadowRoot?.querySelector('.group-title');
this.groupDescEl = this.shadowRoot?.querySelector('.group-describe');
this.iconEl = this.shadowRoot?.querySelector('.icon');
this.group = this.shadowRoot?.querySelector('#group');
this.group.addEventListener('click', (event) => {
if (this.nocollapsed) {
return;
}
this.collapsed = !this.collapsed;
});
}
initHtml() {
return `
${initHtmlStyle}
<div id="group">
<div class="group-name">
<lit-icon class="icon" name="user" size="20"></lit-icon>
<span class="group-title"></span>
</div>
<div class="group-describe"></div>
</div>
<slot></slot>
`;
}
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case 'title':
if (this.groupNameEl) {
this.groupNameEl.textContent = newValue;
}
break;
case 'describe':
if (this.groupDescEl) {
this.groupDescEl.textContent = newValue;
}
break;
case 'icon':
if (this.iconEl) {
this.iconEl.setAttribute('name', newValue);
}
break;
}
}
}
if (!customElements.get('lit-main-menu-group')) {
customElements.define('lit-main-menu-group', MainMenuGroup);
}

View File

@ -0,0 +1,214 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const initHtmlStyle = `
<style>
:host{
user-select: none;
display: flex;
font-family: Helvetica;
font-size: 14px;
color: #000;
text-align: left;
line-height: 20px;
font-weight: 400
background-color: #FFFFFF;
transition: background-color .3s;
}
:host(:not([disabled]):hover){
display: flex;
background-color: #6C9BFA;
color: #FFFFFF;
cursor: pointer;
}
:host([disabled]:hover){
display: flex;
cursor:not-allowed;
}
:host([disabled]) .root{
cursor:not-allowed;
display: flex;
align-items: center;
padding: 10px 24px;
width: 100%;
}
:host(:not([disabled])) .root{
cursor:pointer;
display: flex;
align-items: center;
padding: 10px 24px;
width: 100%;
}
.name{
padding-left: 10px;
cursor: pointer;
overflow-wrap: anywhere;
}
.icon{
pointer-events: none;
}
:host(:not([file])) .name{
pointer-events: none;
}
:host(:not([file])) .root{
pointer-events: none;
}
:host([file]) .name{
pointer-events: none;
}
:host([file]) .icon{
pointer-events: none;
}
:host([back]) {
background-color: #6C9BFA;
}
</style>
`;
export class MainMenuItem extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'}).innerHTML = this.initHtml();
this.initElements();
}
static get observedAttributes() {
return ['title', 'icon', 'file', 'multi', 'disabled'];
}
get title() {
return this.getAttribute('title') || '';
}
set title(val) {
this.setAttribute('title', val);
}
get multi() {
return this.hasAttribute('multi');
}
set multi(val) {
if (val) {
this.setAttribute('multi', '');
} else {
this.removeAttribute('multi');
}
}
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(val) {
if (val) {
this.setAttribute('disabled', val.toString());
this.fileEL?.setAttribute('disabled', val.toString());
} else {
this.removeAttribute('disabled');
this.fileEL?.removeAttribute('disabled');
}
}
get back() {
return this.hasAttribute('back');
}
set back(isShowBack) {
if (isShowBack) {
this.setAttribute('back', '');
} else {
this.removeAttribute('back');
}
}
initElements() {
this.rootEL = this.shadowRoot?.querySelector('.root');
this.titleEl = this.shadowRoot?.querySelector('.name');
this.iconEl = this.shadowRoot?.querySelector('.icon');
this.fileEL = this.shadowRoot?.querySelector('.file');
}
isFile() {
if (this.hasAttribute('file')) {
if (this.fileEL) {
return true;
}
}
return false;
}
connectedCallback() {
if (this.hasAttribute('file')) {
this.setupFileChangeEvents();
this.setupClickEvents();
}
}
setupFileChangeEvents() {
if (this.fileEL) {
this.fileEL.addEventListener('change', event => {
const files = this.fileEL.files;
if (files && files.length > 0) {
this.dispatchEvent(new CustomEvent('file-change', { detail: files[0] }));
this.resetFileInput();
}
});
}
}
resetFileInput() {
if (this.fileEL) {
this.fileEL.value = '';
}
}
setupClickEvents() {
this.addEventListener('click', mouseEvent => {
mouseEvent.stopPropagation();
});
}
initHtml() {
return `
${initHtmlStyle}
<input id="file" class="file" type="file" style="display:none;pointer-events: none" />
<label class="root" for="file">
<lit-icon class="icon" name="user" size="20"></lit-icon>
<label class="name"></label>
</label>
`;
}
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case 'title':
if (this.titleEl) {
this.titleEl.textContent = newValue;
}
break;
case 'icon':
if (this.iconEl) {
this.iconEl.setAttribute('name', newValue);
}
break;
}
}
}
if (!customElements.get('lit-main-menu-item')) {
customElements.define('lit-main-menu-item', MainMenuItem);
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class ProgressBar extends HTMLElement {
static get observedAttributes() {
return ['loading'];
}
constructor() {
super();
this.attachShadow({mode: 'open'}).innerHTML = this.initHtml();
}
get loading() {
return this.hasAttribute('loading');
}
set loading(value) {
if (value) {
this.setAttribute('loading', '');
} else {
this.removeAttribute('loading');
}
}
initHtml() {
return `
<style>
:host{
width: 100%;
height: 1px;
display: flex;
position: absolute;
overflow: hidden;
}
.root{
width: 100%;
height: 100%;
position:relative;
}
:host([loading]) .track1{
position: absolute;
width: 30%;
height: 100%;
background-image:
linear-gradient(to right, transparent, #535da6, #535da6, #535da6, #535da6, #535da6, transparent);
left: -30%;
animation: anim 1.7s linear 0s infinite;
}
:host([loading]) .track2{
position: absolute;
width: 30%;
height: 100%;
background-image:
linear-gradient(to right,transparent, #535da6, #535da6, #535da6, #535da6, #535da6, transparent);
left: -30%;
animation: anim 1.7s ease-in-out 0.7s infinite;
}
@keyframes anim {
0% {
left:-30%;
}
100% {
left:100%;
}
}
</style>
<div class="root">
<div class="track1"></div>
<div class="track2"></div>
</div>
`;
}
}
if (!customElements.get('lit-progress-bar')) {
customElements.define('lit-progress-bar', ProgressBar);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="cache-control" content="no-cache"/>
<meta http-equiv="Pragma" content="no-cache"/>
<title>Ap Profdump</title>
<style>
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<script src="./index.js" type="module"></script>
</body>
</html>

View File

@ -0,0 +1,17 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import './ap/ApApplication.js';
document.body.innerHTML = '<ap-application></ap-application>';

View File

@ -0,0 +1,52 @@
# AP分析工具单元测试
## 概述
为了确保开发过程中代码逻辑紧密贴合设计要求实施单元测试是一种关键实践。本文主要介绍AP分析工具单元测试使用指导。
## 目录
ap_code
├── ... # 其他文件
└──test
├── figures # 图片文件
├── test.txt # txt格式文件
├── test.ap # ap后缀文件
├── test_ap.py # 单元测试代码
├── requirements.txt # python依赖库
└── README.md # 指导文档
## 软件环境准备
依赖版本python3.11
1.右键windows开始菜单单击运行输入cmd单击确定。
2.在命令行中进入到ap_code\test目录下安装依赖库命令如下
pip install -r requirements.txt
3.查看谷歌浏览器版本此处得到浏览器版本为126.0.6478.61查询网址为chrome://version/,在浏览器中访问,如下图所示:
![img.png](figures/img.png)
4.由于浏览器版本为126.0.6478.61查看驱动版本列表中是否有126.0.6478.61版本若有可下载此版本驱动若没有可下载126.0.6478版本。进入版本系统选择列表后选择win32win系统不区分win32或win64下载压缩包下载地址如下
[驱动下载地址](https://googlechromelabs.github.io/chrome-for-testing/)
5.将解压缩获得的chromedriver.exe放到Python的Scripts目录下。
## 使用说明
在命令行中进入到ap_code\test目录下
1.执行所有用例,命令为:
pytest -s -W ignore test_ap.py
2.执行单个用例如test_import_file命令为
pytest -s -W ignore test_ap.py::test_import_file
## 相关仓
暂无

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,2 @@
pytest==7.4.0
selenium==4.10.0

BIN
tools/ap_file_viewer/test/test.ap Executable file

Binary file not shown.

View File

@ -0,0 +1 @@
Test text.

View File

@ -0,0 +1,112 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright (c) 2024 Huawei Device Co., Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import pytest
import os
import subprocess
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()
root = None
file_input = None
script_directory = ''
def setup():
global root
os.chdir("..")
subprocess.Popen(["python", 'dist/apDumpServer.py'])
print("setup : Selenium opens the test page")
time.sleep(2)
driver.get("http://127.0.0.1:9001/ap/")
application = driver.find_element(By.TAG_NAME, "ap-application")
root = application.shadow_root.find_element(By.CSS_SELECTOR, ".root")
def test_import_file():
global root, file_input, script_directory
main_menu = root.find_element(By.TAG_NAME, "lit-main-menu")
menu_body = main_menu.shadow_root.find_element(By.CSS_SELECTOR, ".menu-body")
menu_group = menu_body.find_element(By.TAG_NAME, "lit-main-menu-group")
menu_item = menu_group.find_element(By.TAG_NAME, "lit-main-menu-item")
file_input = menu_item.shadow_root.find_element(By.CSS_SELECTOR, ".file")
current_script_path = os.path.abspath(__file__)
script_directory = os.path.dirname(current_script_path)
path = os.path.join(script_directory, 'test.ap')
file_path = os.path.normpath(path).replace("\\", "/")
file_input.send_keys(file_path)
time.sleep(2)
def test_node_click():
global root
app_content = root.find_element(By.CSS_SELECTOR, ".content")
summary_div = app_content.find_element(By.TAG_NAME, "div")
tab_summary = summary_div.find_element(By.TAG_NAME, "tab-ap-summary")
summary_head = tab_summary.shadow_root.find_element(By.CSS_SELECTOR, ".tab-summary-head")
expansion_div = summary_head.find_element(By.TAG_NAME, "div").find_element(By.TAG_NAME, "div")
expansion_div.click()
time.sleep(2)
expansion_div.click()
time.sleep(2)
labels = summary_head.find_elements(By.TAG_NAME, "label")
for label in labels:
label.click()
time.sleep(2)
def test_search():
global root
search_vessel = root.find_element(By.CSS_SELECTOR, ".search-vessel")
search_div = search_vessel.find_element(By.CSS_SELECTOR, ".search")
lit_search = search_div.find_element(By.CSS_SELECTOR, "lit-search")
search_input = lit_search.shadow_root.find_element(By.CSS_SELECTOR, ".root").find_element(By.TAG_NAME, "input")
search_input.send_keys("d")
time.sleep(2)
search_input.send_keys("w")
time.sleep(2)
current_text = search_input.get_attribute("value")
for _ in range(len(current_text)):
search_input.send_keys(Keys.BACK_SPACE)
time.sleep(2)
def test_unsupported_file():
global script_directory
driver.refresh()
application_node = driver.find_element(By.TAG_NAME, "ap-application")
root_node = application_node.shadow_root.find_element(By.CSS_SELECTOR, ".root")
main_menu = root_node.find_element(By.TAG_NAME, "lit-main-menu")
menu_body = main_menu.shadow_root.find_element(By.CSS_SELECTOR, ".menu-body")
menu_group = menu_body.find_element(By.TAG_NAME, "lit-main-menu-group")
menu_item = menu_group.find_element(By.TAG_NAME, "lit-main-menu-item")
file_input2 = menu_item.shadow_root.find_element(By.CSS_SELECTOR, ".file")
path = os.path.join(script_directory, 'test.txt')
file_path = os.path.normpath(path).replace("\\", "/")
file_input2.send_keys(file_path)
time.sleep(5)
def teardown():
print("teardown : Close selenium")
driver.close()

View File

@ -0,0 +1,99 @@
/*
* Copyright (C) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const path = require('path');
const childProcess = require('child_process');
const fs = require('fs');
const staticPath = ['/src/img', '/server'];
const staticFiles = [
'/src/index.html',
'/src/base-ui/icon.svg'
];
function cpDir(sourcePath, targetPath) {
fs.readdir(sourcePath, async (err, files) => {
if (err) {
console.error('unable read dir', err);
return;
}
for (const file of files) {
const source = `${sourcePath}/${file}`;
const target = `${targetPath}/${file}`;
await cpFile(source, target);
}
});
}
async function cpFile(source, target) {
if (fs.lstatSync(source).isFile()) {
const dirPath = path.dirname(target);
if (!fs.existsSync(dirPath)) {
await fs.promises.mkdir(dirPath, {recursive: true});
}
await fs.promises.copyFile(source, target);
}
}
function clearDirectory(directoryPath) {
let files = [];
if (fs.existsSync(directoryPath)) {
files = fs.readdirSync(directoryPath);
files.forEach((file, index) => {
let curPath = directoryPath + '/' + file;
if (fs.statSync(curPath).isDirectory()) {
clearDirectory(curPath);
} else {
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(directoryPath);
}
}
module.exports = (env, argv) => {
const outPath = path.normalize(path.join(__dirname, '/', 'dist'));
clearDirectory(outPath);
staticPath.forEach((value) => {
let pa = path.join(__dirname, value);
let distPath;
if (value.startsWith('/src')) {
distPath = path.join(outPath, value.substring(4, value.length + 1));
} else if (value.startsWith('/server')) {
distPath = path.join(outPath, value.substring(7, value.length + 1));
}
cpDir(pa, distPath);
});
staticFiles.forEach((value) => {
let filePath = path.join(__dirname, value);
let distFile;
if (value.startsWith('/src')) {
distFile = path.join(outPath, value.substring(4, value.length + 1));
} else if (value.startsWith('/server')) {
distFile = path.join(outPath, value.substring(7, value.length + 1));
}
cpFile(filePath, distFile);
});
return {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
}
};
};