add ap parse code
Signed-off-by: yanpeng <yanpeng51@h-partners.com> Change-Id: I9c0c2b23e0bd926efc3350d117d351a7c9732fd7 Signed-off-by: yanpeng <yanpeng51@h-partners.com>
4
OAT.xml
@ -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
@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
coverage/
|
||||
dist
|
||||
package-lock.json
|
||||
.idea
|
69
tools/ap_file_viewer/README_zh.md
Executable 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
@ -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)
|
42
tools/ap_file_viewer/docs/DEVELOP_zh.md
Executable 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数据渲染
|
||||
```
|
21
tools/ap_file_viewer/docs/INSTRUCTION_zh.md
Executable file
@ -0,0 +1,21 @@
|
||||
# AP可视化工具使用说明
|
||||
|
||||
## 简介
|
||||
|
||||
Ap文件可视化工具根据ark_js_vm生成的ap文件实现可视化分析。
|
||||
|
||||
### 使用说明
|
||||
|
||||
运行ark_js_vm,采集PGO信息,生成的PGO信息落盘在ap文件。
|
||||
|
||||
根据开发说明打包工具,并运行起来:
|
||||
|
||||
[开发说明](./DEVELOP_zh.md)
|
||||
|
||||
将ap文件拖入浏览器窗口区域,等待解析完成,界面功能如下图所示:
|
||||
|
||||
![](../figures/img.png)
|
||||
|
||||
## 注意
|
||||
|
||||
暂无
|
BIN
tools/ap_file_viewer/figures/dist.png
Executable file
After Width: | Height: | Size: 7.5 KiB |
BIN
tools/ap_file_viewer/figures/img.png
Executable file
After Width: | Height: | Size: 52 KiB |
BIN
tools/ap_file_viewer/figures/main.png
Executable file
After Width: | Height: | Size: 32 KiB |
BIN
tools/ap_file_viewer/figures/start_web.png
Executable file
After Width: | Height: | Size: 1.1 KiB |
19
tools/ap_file_viewer/package.json
Executable 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"
|
||||
}
|
||||
}
|
272
tools/ap_file_viewer/server/apdump_server.py
Executable 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)
|
32
tools/ap_file_viewer/server/config.json
Executable 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"
|
||||
}
|
||||
]
|
||||
}
|
258
tools/ap_file_viewer/src/ap/ApApplication.js
Executable 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 Failed,status:${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);
|
||||
}
|
185
tools/ap_file_viewer/src/ap/ApApplicationPublicFunc.js
Executable 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>
|
||||
`;
|
43
tools/ap_file_viewer/src/ap/component/ApWelcome.js
Executable 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);
|
||||
}
|
163
tools/ap_file_viewer/src/ap/component/search/Search.html.js
Executable 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>
|
||||
`;
|
230
tools/ap_file_viewer/src/ap/component/search/Search.js
Executable 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);
|
||||
}
|
89
tools/ap_file_viewer/src/ap/component/sheet/TabPaneApSummary.html.js
Executable 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>
|
||||
`;
|
418
tools/ap_file_viewer/src/ap/component/sheet/TabPaneApSummary.js
Executable 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 = '-';
|
||||
}
|
||||
}
|
90
tools/ap_file_viewer/src/base-ui/icon.svg
Executable 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 |
106
tools/ap_file_viewer/src/base-ui/icon/LitIcon.js
Executable 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);
|
||||
}
|
171
tools/ap_file_viewer/src/base-ui/menu/MainMenu.js
Executable 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);
|
||||
}
|
183
tools/ap_file_viewer/src/base-ui/menu/MainMenuGroup.js
Executable 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);
|
||||
}
|
214
tools/ap_file_viewer/src/base-ui/menu/MainMenuItem.js
Executable 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);
|
||||
}
|
91
tools/ap_file_viewer/src/base-ui/progress-bar/ProgressBar.js
Executable 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);
|
||||
}
|
BIN
tools/ap_file_viewer/src/img/ap_logo.png
Executable file
After Width: | Height: | Size: 3.2 KiB |
BIN
tools/ap_file_viewer/src/img/pic.png
Executable file
After Width: | Height: | Size: 16 KiB |
24
tools/ap_file_viewer/src/index.html
Executable 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>
|
17
tools/ap_file_viewer/src/index.js
Executable 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>';
|
52
tools/ap_file_viewer/test/README.md
Executable 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版本。进入版本系统选择列表后,选择win32(win系统不区分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
|
||||
|
||||
## 相关仓
|
||||
|
||||
暂无
|
BIN
tools/ap_file_viewer/test/figures/img.png
Executable file
After Width: | Height: | Size: 21 KiB |
2
tools/ap_file_viewer/test/requirements.txt
Executable file
@ -0,0 +1,2 @@
|
||||
pytest==7.4.0
|
||||
selenium==4.10.0
|
BIN
tools/ap_file_viewer/test/test.ap
Executable file
1
tools/ap_file_viewer/test/test.txt
Executable file
@ -0,0 +1 @@
|
||||
Test text.
|
112
tools/ap_file_viewer/test/test_ap.py
Executable 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()
|
99
tools/ap_file_viewer/webpack.config.js
Executable 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',
|
||||
}
|
||||
};
|
||||
};
|