config_tools: refactor configurator for web page cache issue

refactor configurator for web page cache issue

Tracked-On: #7356
Signed-off-by: Weiyi Feng <weiyix.feng@intel.com>
This commit is contained in:
Weiyi Feng 2022-04-24 16:59:56 +08:00 committed by acrnsi-robot
parent 8e61d417db
commit 696ba31be8
199 changed files with 3838 additions and 7045 deletions

4
.gitignore vendored
View File

@ -9,4 +9,6 @@ build
.idea
venv
*.egg-info
/misc/config_tools/dist
/misc/config_tools/dist
/misc/config_tools/schema/sliced.xsd
/misc/config_tools/schema/allchecks.xsd

View File

@ -0,0 +1,4 @@
graft data
graft schema
graft launch_config
include configurator/src/assets/schema/scenario.json

View File

@ -0,0 +1,9 @@
@echo off
python scenario_config/schema_slicer.py
python scenario_config/jsonschema/converter.py
xmllint --xinclude schema/datachecks.xsd > schema/allchecks.xsd
python -m build
rem pip install .\dist\acrn_config_tools-3.0-py3-none-any.whl --force-reinstall
del .\configurator\thirdLib\acrn_config_tools-3.0-py3-none-any.whl
python .\configurator\thirdLib\manager.py install
echo build and install success

View File

@ -21,7 +21,3 @@ build
*.njsproj
*.sln
*.sw?
# TS temp file
tauri-plugin.js.map
tauri-plugin.js

View File

@ -1,6 +1,6 @@
# ACRN Configurator
This version based on tauri, WIP.
This version is based on Tauri, WIP.
## Features
@ -18,6 +18,10 @@ This version based on tauri, WIP.
Please follow [this guide](https://tauri.studio/docs/getting-started/prerequisites)
to install system dependencies **(including yarn)**.
In Windows, [chocolatey](https://chocolatey.org/) is a Windows package manager,
you can use `choco install xsltproc` to install `xsltproc` package,
which provide `xmllint` command.
### 2. Clone Project And Install Project Dependencies.
#### Linux
@ -36,7 +40,7 @@ Similar to Linux.
On macOS, you may need to install git and python3 via `brew`.
In Windows environment maybe you need to install git and python3 via chocolatey or manually,
In the Windows environment maybe you need to install git and python3 via chocolatey or manually,
and replace the command line `python3` with `py -3`.
### 3. How To Build
@ -51,12 +55,18 @@ make configurator
#### Windows/macOS
Run follow command in the 'acrn-hypervisor' directory.
Run following command in the 'acrn-hypervisor' directory.
```shell
python3 misc/config_tools/scenario_config/schema_slicer.py
python3 misc/config_tools/scenario_config/xs2js.py
cd misc/config_tools/configurator
cd misc/config_tools
python scenario_config/schema_slicer.py
python scenario_config/xs2js.py
xmllint --xinclude schema/datachecks.xsd > schema/allchecks.xsd
python -m build
cd configurator
python thirdLib/manager.py install
yarn build
```
@ -75,5 +85,4 @@ acrn-configurator
You can find msi(Windows)/dmg(macOS) folder under the
`misc/config_tools/configurator/src-tauri/target/release/bundle`
directory, the installer in the folder.

View File

@ -2,23 +2,23 @@
<html lang="en">
<head>
<meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg"/>
<link rel="icon" href="/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Vite App</title>
<script src="https://cdn.jsdelivr.net/pyodide/v0.19.1/full/pyodide.js"></script>
<title>ACRN Configurator</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript">
async function main() {
window.pyodide = await loadPyodide({
indexURL: "https://cdn.jsdelivr.net/pyodide/v0.19.1/full/"
})
pyodide.loadPackage(['micropip', 'lxml', 'beautifulsoup4'])
}
main();
</script>
<script type="module" src="/src/main.jsx"></script>
<!-- must using style, otherwise style will lost -->
<body style="margin: 0;padding: 0;height: calc(100vh - 1px);width: calc(100vw - 1px);border: 1px #787878 solid;">
<div id="app">
<div data-tauri-drag-region="true" style="width: 100%;height: 100%;position:fixed;overflow:hidden;display: flex;justify-content: center;align-items: center;background: #007B81">
<div style="display: block;color: white;text-align: center;user-select: none">
<text style="font-size:36px;display: flex;padding: 16px">
<img data-tauri-drag-region="true" src="src/assets/images/ACRN_Logo.svg" alt="logo" style="height: 49px;padding-right: 1.5rem"/>
Loading...
</text>
<text style="font-size:24px;display: block">Please wait for 5 seconds~</text>
</div>
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@ -15,33 +15,29 @@
"tauri": "tauri"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.3.0",
"@fortawesome/free-regular-svg-icons": "^6.0.0",
"@fortawesome/free-solid-svg-icons": "^6.0.0",
"@fortawesome/react-fontawesome": "^0.1.17",
"@popperjs/core": "^2.11.2",
"@rjsf/core": "^4.0.1",
"@lljj/vue3-form-naive": "^1.12.2",
"@popperjs/core": "^2.11.5",
"@rollup/plugin-replace": "^4.0.0",
"@tauri-apps/api": "^1.0.0-rc.1",
"@tauri-apps/api": "^1.0.0-rc.3",
"@vicons/carbon": "^0.12.0",
"@vicons/fa": "^0.12.0",
"@vicons/utils": "^0.1.4",
"ajv-i18n": "^4.2.0",
"bootstrap": "^5.1.3",
"bootstrap-vue-3": "^0.1.10",
"js-base64": "^3.7.2",
"lodash": "^4.17.21",
"naive-ui": "^2.28.1",
"node-fetch": "2",
"query-string": "^7.1.1",
"react": "^17.0.2",
"react-bootstrap": "^2.1.2",
"react-dom": "^17.0.2",
"react-icons": "^4.3.1",
"react-router": "^6.2.1",
"react-router-dom": "^6.2.1",
"vconsole": "^3.14.3"
"sass": "^1.50.0",
"vconsole": "^3.14.6",
"vfonts": "^0.0.3",
"vue": "^3.2.25",
"vue-router": "4"
},
"devDependencies": {
"@tauri-apps/cli": "^1.0.0-rc.5",
"@types/lodash": "^4.14.179",
"@types/node": "^17.0.21",
"@vitejs/plugin-react": "^1.0.7",
"sass": "^1.49.9",
"vite": "^2.8.0"
"@tauri-apps/cli": "^1.0.0-rc.8",
"@vitejs/plugin-vue": "^2.3.1",
"vite": "^2.9.2"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,27 @@
# ACRN Configurator WASM Python Module
Every file must set `__package__ = 'configurator.pyodide'` before import,
set this magic var can resolve python relative import error when we direct run it.
## Function define
Every python script need a test function and a main function.
### test
run script will call this function,
so please set script default params in this function
### main
in js side will use this function.
like:
```javascript
// after pyodide install all dependices
var launch_cfg_gen = pyodide.pyimport("configurator.pyodide.launch_cfg_gen").main;
var board_xml = this.readFile('xxxx/board.xml');
var scenario_xml = this.readFile('xxx/scenario.xml');
var launch_scripts = launch_cfg_gen(board_xml, scenario_xml);
console.log(launch_scripts)
```

View File

@ -0,0 +1 @@
from .pyodide import *

View File

@ -0,0 +1,51 @@
#!/usr/bin/env python3
__package__ = 'configurator.pyodide'
import os
from tempfile import TemporaryDirectory
from pathlib import Path
from launch_config.launch_cfg_gen import main as launch_cfg_gen_main
from .pyodide import convert_result, nuc11_board, nuc11_scenario, write_temp_file
def generate_launch_script(board, scenario, user_vm_id=0):
"""
:param board: board xml text
:param scenario: scenario xml text
:param user_vm_id: the vm which you want to generate launch script, will generate all launch script if it set to zero
"""
launch_scripts = {}
with TemporaryDirectory() as tmpdir:
# Write file to dir
write_temp_file(tmpdir, {
'board.xml': board,
'scenario.xml': scenario
})
# define path
board_file_path = Path(tmpdir) / 'board.xml'
scenario_file_path = Path(tmpdir) / 'scenario.xml'
launch_script_output_dir = Path(tmpdir) / 'output'
# generate launch script
launch_cfg_gen_main(board_file_path, scenario_file_path, user_vm_id, launch_script_output_dir)
# get output and convert it to {filename: content}
for filename in os.listdir(launch_script_output_dir):
abs_name = launch_script_output_dir / str(filename)
launch_scripts[filename] = open(abs_name, encoding='utf-8').read()
return convert_result(launch_scripts)
main = generate_launch_script
def test():
main(nuc11_board, nuc11_scenario)
if __name__ == '__main__':
test()

View File

@ -0,0 +1,98 @@
#!/usr/bin/env python3
__package__ = 'configurator.pyodide'
import json
from copy import deepcopy
import elementpath
import lxml.etree as etree
from bs4 import BeautifulSoup
from . import convert_result, nuc11_board, scenario_json_schema
def get_dynamic_scenario(board):
"""
:type board: str
:param board: board xml text
"""
board_xml = etree.fromstring(board)
def get_enum(source, options):
elements = [str(x) for x in elementpath.select(source, options) if x]
elements = list(set(elements))
if not elements:
elements = ['']
return elements
def dynamic_enum(**enum_setting):
# value from env
function, source = [
{"get_enum": get_enum, "board_xml": board_xml}[enum_setting[key]]
for key in ['function', 'source']
]
# value from given
selector, sorted_func = [enum_setting[key] for key in ['selector', 'sorted']]
# get enum data
enum = function(source, selector)
if sorted_func:
enum = sorted(enum, key=eval(sorted_func))
return enum
def dynamic_enum_apply(obj):
# get json schema enum obj
if 'enum' in obj and isinstance(obj['enum'], dict):
enum_setting = obj['enum']
# check enum obj type
if enum_setting['type'] == 'dynamicEnum':
del enum_setting['type']
# replace json schema obj enum field data
obj['enum'] = dynamic_enum(**enum_setting)
return obj
data = json.loads(scenario_json_schema, object_hook=dynamic_enum_apply)
form_schemas = {}
tab_types = ['HV', 'PreLaunchedVM', 'ServiceVM', 'PostLaunchedVM']
form_types = ['BasicConfigType', 'AdvancedConfigType']
for tab_type in tab_types:
form_schemas[tab_type] = {}
for form_type in form_types:
form_schema = deepcopy(data)
current_form_type_schema_obj = form_schema['definitions'][f'{tab_type}{form_type}']
for key in ['type', 'required', 'properties']:
form_schema[key] = current_form_type_schema_obj[key]
form_schemas[tab_type][form_type] = form_schema
return form_schemas
def get_board_info(board):
soup = BeautifulSoup(board, 'xml')
result = {
'name': soup.select_one('acrn-config').attrs['board'] + '.board.xml',
'content': board,
'BIOS_INFO': soup.select_one('BIOS_INFO').text,
'BASE_BOARD_INFO': soup.select_one('BASE_BOARD_INFO').text
}
return result
def load_board(board):
result = {
'scenarioJSONSchema': get_dynamic_scenario(board),
'boardInfo': get_board_info(board)
}
return convert_result(result)
def test():
load_board(nuc11_board)
main = load_board
if __name__ == '__main__':
test()

View File

@ -0,0 +1,78 @@
#!/usr/bin/env python3
__package__ = 'configurator.pyodide'
import json
import xmltodict
from . import convert_result, nuc11_scenario, scenario_json_schema
def get_array_and_int_keys():
array_keys = []
int_keys = ['@id']
def object_mapper(obj):
if not isinstance(obj, dict):
return obj
for key, value in obj.items():
if not isinstance(value, dict):
continue
if value.get('type', '') == 'array':
array_keys.append(key)
elif value.get('type', '') == 'integer':
int_keys.append(key)
return obj
json.loads(scenario_json_schema, object_hook=object_mapper)
array_keys = list(set(array_keys))
int_keys = list(set(int_keys))
return array_keys, int_keys
def load_scenario_xml(scenario):
"""
convert scenario xml to json data followed scenario json schema
:type scenario: str
:param scenario: scenario xml text
:return:
"""
arr_keys, int_keys = get_array_and_int_keys()
scenario_xml = xmltodict.parse(scenario)
scenario_xml = json.dumps(scenario_xml)
def correct_struct(obj):
if not isinstance(obj, dict):
return obj
keys_need_remove = []
for key, value in obj.items():
if value is None:
keys_need_remove.append(key)
continue
if key in int_keys:
if isinstance(obj[key], list):
obj[key] = [int(x) for x in obj[key]]
elif not isinstance(value, int):
obj[key] = int(value)
if key in arr_keys and not isinstance(value, list):
obj[key] = [value]
for key in keys_need_remove:
del obj[key]
return obj
scenario_dict = json.loads(scenario_xml, object_hook=correct_struct)
return scenario_dict
def main(scenario):
result = load_scenario_xml(scenario)
return convert_result(result)
def test():
main(nuc11_scenario)
if __name__ == '__main__':
test()

View File

@ -0,0 +1,60 @@
#!/usr/bin/env python3
__package__ = 'configurator.pyodide'
import json
import sys
from pathlib import Path
class LazyPath:
def __init__(self, my):
self.my = my
def __truediv__(self, other):
return str(self.my / other)
def file_text(path):
return open(path, encoding='utf-8').read()
# path define
config_tools_dir = Path(__file__).absolute().parent.parent.parent
configurator_dir = config_tools_dir / 'configurator'
schema_dir = config_tools_dir / 'schema'
scenario_xml_schema_path = schema_dir / 'sliced.xsd'
datachecks_xml_schema_path = schema_dir / 'allchecks.xsd'
nuc11_folder = LazyPath(config_tools_dir / 'data' / 'nuc11tnbi5')
# file define
nuc11_board = file_text(nuc11_folder / 'nuc11tnbi5.xml')
nuc11_scenario = file_text(nuc11_folder / 'shared_launch_6user_vm.xml')
scenario_json_schema = file_text(configurator_dir / 'src' / 'assets' / 'schema' / 'scenario.json')
debug = sys.platform != 'emscripten'
def convert_result(result):
if debug:
print(json.dumps(result, indent=' '))
return json.dumps(result)
def write_temp_file(tmpdir, file_dict: dict):
temp_path = Path(tmpdir)
for filename, content in file_dict.items():
with open(temp_path / filename, 'w', encoding='utf-8') as f:
f.write(content)
def main():
pass
def test():
pass
if __name__ == '__main__':
test()

View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
__package__ = 'configurator.pyodide'
from .loadBoard import test as load_board_test
from .loadScenario import test as load_scenario_test
from .generateLaunchScript import test as generate_launch_script_test
from .validateScenario import test as validate_scenario_test
def main():
load_board_test()
load_scenario_test()
generate_launch_script_test()
validate_scenario_test()
def test():
main()
if __name__ == '__main__':
test()

View File

@ -0,0 +1,54 @@
#!/usr/bin/env python3
__package__ = 'configurator.pyodide'
from pathlib import Path
from tempfile import TemporaryDirectory
from scenario_config.default_populator import DefaultValuePopulatingStage
from scenario_config.pipeline import PipelineObject, PipelineEngine
from scenario_config.validator import ValidatorConstructionByFileStage, SemanticValidationStage
from scenario_config.xml_loader import XMLLoadStage
from .pyodide import (
convert_result, write_temp_file,
nuc11_board, nuc11_scenario, scenario_xml_schema_path, datachecks_xml_schema_path
)
def main(board, scenario):
pipeline = PipelineEngine(["board_path", "scenario_path", "schema_path", "datachecks_path"])
pipeline.add_stages([
ValidatorConstructionByFileStage(),
XMLLoadStage("schema"),
XMLLoadStage("board"),
XMLLoadStage("scenario"),
DefaultValuePopulatingStage(),
SemanticValidationStage(),
])
with TemporaryDirectory() as tmpdir:
write_temp_file(tmpdir, {
'board.xml': board,
'scenario.xml': scenario
})
board_file_path = Path(tmpdir) / 'board.xml'
scenario_file_path = Path(tmpdir) / 'scenario.xml'
obj = PipelineObject(
board_path=board_file_path,
scenario_path=scenario_file_path,
schema_path=scenario_xml_schema_path,
datachecks_path=datachecks_xml_schema_path
)
pipeline.run(obj)
validate_result = obj.get("semantic_errors")
return convert_result(validate_result)
def test():
main(nuc11_board, nuc11_scenario)
if __name__ == '__main__':
test()

View File

@ -1,7 +1,3 @@
lxml
xmltodict
xmlschema
defusedxml
build
tqdm
requests
bs4
sphinx

View File

@ -2,13 +2,3 @@
# will have compiled files and executables
/target/
WixTools
# These are backup files generated by rustfmt
**/*.rs.bk
config.json
bundle.json
# ts temp file
types/*.js
types/*.map

File diff suppressed because it is too large Load Diff

View File

@ -12,17 +12,16 @@ rust-version = "1.57"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.0.0-rc.3", features = [] }
tauri-build = { version = "1.0.0-rc.5", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { git = "https://github.com/Weiyi-Feng/tauri.git", features = ["api-all", "cli"] }
log = "0.4.14"
env_logger = "0.9.0"
glob = "0.3.0"
dirs = "4.0.0"
itertools = "0.10.3"
tauri = { version = "1.0.0-rc.6", features = ["api-all"] }
log = "0.4"
glob = "0.3"
dirs = "4.0"
itertools = "0.10"
[features]
# by default Tauri runs in production mode

View File

@ -1,5 +1,4 @@
use std::borrow::Borrow;
use std::fs;
use std::ops::Add;
use std::path::{Path, PathBuf};
@ -7,6 +6,12 @@ use serde::{Deserialize, Serialize};
use glob::{glob_with, MatchOptions};
use itertools::Itertools;
use std::fs::{self, File};
use std::io;
use std::io::prelude::*;
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
#[repr(u16)]
#[non_exhaustive]
@ -178,8 +183,8 @@ impl Configurator {
}
}
pub fn add_history(&mut self, history_type: HistoryType, path: &Path) {
let path_string: String = path.to_string_lossy().parse().unwrap();
pub fn add_history(&mut self, history_type: HistoryType, history_path: &Path) {
let path_string: String = history_path.to_string_lossy().parse().unwrap();
match history_type {
HistoryType::WorkingFolder => {
self.config_data.history.working_folder.insert(0, path_string);
@ -266,8 +271,8 @@ pub fn get_history(history_type: HistoryType) -> Result<String, ()> {
}
#[tauri::command]
pub fn add_history(history_type: HistoryType, path: String) -> Result<(), &'static str> {
let path_buf = Path::new(&path);
pub fn add_history(history_type: HistoryType, history_path: String) -> Result<(), &'static str> {
let path_buf = Path::new(&history_path);
if !(path_buf.is_dir() || path_buf.is_file()) {
return Err("Not a validate dir or file path.");
}
@ -313,4 +318,69 @@ pub fn get_home() -> Result<String, ()> {
Ok(path.to_str().unwrap().to_string())
}
}
}
}
#[derive(Serialize)]
pub struct DirEntry {
path: String,
children: Option<Vec<DirEntry>>,
}
#[tauri::command]
pub fn acrn_read(file_path: &str) -> Result<String, String> {
let mut file = File::open(file_path).map_err(|e| e.to_string())?;
let mut contents = String::new();
file
.read_to_string(&mut contents)
.map_err(|e| e.to_string())?;
Ok(contents)
}
#[tauri::command]
pub fn acrn_write(file_path: &str, contents: &str) -> Result<(), String> {
let mut file = File::create(file_path).map_err(|e| e.to_string())?;
file
.write_all(contents.as_bytes())
.map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
pub fn acrn_is_file(path: &str) -> bool {
fs::metadata(path)
.map(|metadata| metadata.is_file())
.unwrap_or(false)
}
#[tauri::command]
pub fn acrn_create_dir(path: &str) -> Result<(), String> {
fs::create_dir(path).map_err(|e| e.to_string())
}
fn read_dir<P: AsRef<Path>>(
path: P,
recursive: bool,
) -> io::Result<Vec<DirEntry>> {
let path = path.as_ref();
let mut entries = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path().to_str().unwrap().to_string();
let children = if recursive && entry.file_type()?.is_dir() {
Some(read_dir(&path, true)?)
} else {
None
};
entries.push(DirEntry { path, children });
}
Ok(entries)
}
#[tauri::command]
pub fn acrn_read_dir(
path: &str,
recursive: bool,
) -> Result<Vec<DirEntry>, String> {
read_dir(path, recursive).map_err(|e| e.to_string())
}

View File

@ -1,102 +0,0 @@
use serde::Serialize;
use std::fs::{self, File};
use std::io;
use std::io::prelude::*;
use std::path::Path;
#[tauri::command]
pub fn fs_read_text_file(path: &str) -> Result<String, String> {
let mut file = File::open(path).map_err(|e| e.to_string())?;
let mut contents = String::new();
file
.read_to_string(&mut contents)
.map_err(|e| e.to_string())?;
Ok(contents)
}
#[tauri::command]
pub fn fs_write_text_file(path: &str, contents: &str) -> Result<(), String> {
let mut file = File::create(path).map_err(|e| e.to_string())?;
file
.write_all(contents.as_bytes())
.map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
pub fn fs_read_binary_file(path: &str) -> Result<Vec<u8>, String> {
let mut file = File::open(path).map_err(|e| e.to_string())?;
let mut contents = Vec::new();
file.read_to_end(&mut contents).map_err(|e| e.to_string())?;
Ok(contents)
}
#[tauri::command]
pub fn fs_write_binary_file(path: &str, contents: &[u8]) -> Result<(), String> {
let mut file = File::create(path).map_err(|e| e.to_string())?;
file.write_all(contents).map_err(|e| e.to_string())?;
Ok(())
}
#[derive(Serialize)]
pub struct DirEntry {
path: String,
children: Option<Vec<DirEntry>>,
}
fn read_dir<P: AsRef<Path>>(
path: P,
recursive: bool,
) -> io::Result<Vec<DirEntry>> {
let path = path.as_ref();
let mut entries = Vec::new();
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path().to_str().unwrap().to_string();
let children = if recursive && entry.file_type()?.is_dir() {
Some(read_dir(&path, true)?)
} else {
None
};
entries.push(DirEntry { path, children });
}
Ok(entries)
}
#[tauri::command]
pub fn fs_read_dir(
path: &str,
recursive: bool,
) -> Result<Vec<DirEntry>, String> {
read_dir(path, recursive).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn fs_rename(from: &str, to: &str) -> Result<(), String> {
fs::rename(from, to).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn fs_delete_file(path: &str) -> Result<(), String> {
fs::remove_file(path).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn fs_delete_dir(path: &str) -> Result<(), String> {
fs::remove_dir_all(path).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn fs_create_dir(path: &str) -> Result<(), String> {
fs::create_dir(path).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn fs_is_file(path: &str) -> bool {
fs::metadata(path).map(|m| m.is_file()).unwrap_or(false)
}
#[tauri::command]
pub fn fs_is_dir(path: &str) -> bool {
fs::metadata(path).map(|m| m.is_dir()).unwrap_or(false)
}

View File

@ -3,78 +3,24 @@ all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
mod filesystem;
mod configurator;
use log::*;
use tauri::{api::cli::get_matches, utils::config::CliConfig, PackageInfo, RunEvent};
fn main() {
env_logger::init();
// Init context
let context = tauri::generate_context!();
// Build app instance and run
let app = tauri::Builder::default()
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
filesystem::fs_rename,
filesystem::fs_read_dir,
filesystem::fs_read_text_file,
filesystem::fs_read_binary_file,
filesystem::fs_write_text_file,
filesystem::fs_write_binary_file,
filesystem::fs_read_dir,
filesystem::fs_delete_file,
filesystem::fs_delete_dir,
filesystem::fs_create_dir,
filesystem::fs_is_file,
filesystem::fs_is_dir,
configurator::get_history,
configurator::add_history,
configurator::set_working_folder,
configurator::write_board,
configurator::force_reset,
configurator::get_home
])
.setup(|app| {
// Handle cli cmdline
let app_config = app.config();
let cli_config = app_config.tauri.cli.as_ref().unwrap();
let package_info = app.package_info();
handle_cli_arg_data(&cli_config, &package_info);
Ok(())
})
.build(context)
configurator::get_home,
configurator::acrn_read,
configurator::acrn_write,
configurator::acrn_is_file,
configurator::acrn_read_dir,
configurator::acrn_create_dir,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
app.run(|_app, event| {
if let RunEvent::Exit = event {
info!("Received Exit event");
}
});
}
fn handle_cli_arg_data(cli_config: &CliConfig, pkg_info: &PackageInfo) {
match get_matches(cli_config, pkg_info) {
Ok(matches) => {
let mut exit_flag = false;
if let Some(arg_data) = matches.args.get("help") {
println!("{}", arg_data.value.as_str().unwrap_or("No help available"));
exit_flag = true;
}
if let Some(arg_data) = matches.args.get("version") {
println!("{}", arg_data.value.as_str().unwrap_or("No version data available"));
exit_flag = true
}
if exit_flag {
std::process::exit(1);
}
}
Err(e) => {
error!("{}", e.to_string());
std::process::exit(1);
}
}
}

View File

@ -0,0 +1 @@
{"package":{"productName":"acrn-configurator","version":"../package.json"},"build":{"distDir":"../build","devPath":"http://localhost:3000","beforeDevCommand":"","beforeBuildCommand":""},"tauri":{"bundle":{"active":true,"targets":"all","identifier":"com.projectacrn.configurator","icon":["icons/32x32.png","icons/128x128.png","icons/128x128@2x.png","icons/icon.icns","icons/icon.ico"],"resources":[],"externalBin":[],"copyright":"","category":"DeveloperTool","shortDescription":"","longDescription":"","deb":{"depends":[],"useBootstrapper":false},"macOS":{"frameworks":[],"useBootstrapper":false,"exceptionDomain":"","signingIdentity":null,"providerShortName":null,"entitlements":null},"windows":{"certificateThumbprint":null,"digestAlgorithm":"sha256","timestampUrl":""}},"updater":{"active":false},"allowlist":{"all":true},"windows":[{"title":"ACRN Configurator","width":1366,"height":768,"resizable":true,"fullscreen":false,"decorations":false}],"security":{"csp":null}}}

View File

@ -1,5 +1,4 @@
{
"$schema": "types/schema.json",
"package": {
"productName": "acrn-configurator",
"version": "../package.json"
@ -11,18 +10,10 @@
"beforeBuildCommand": ""
},
"tauri": {
"cli": {
"args": [
{
"name": "debug",
"takesValue": false
}
]
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.tauri.dev",
"identifier": "com.projectacrn.configurator",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
@ -42,7 +33,6 @@
},
"macOS": {
"frameworks": [],
"minimumSystemVersion": "",
"useBootstrapper": false,
"exceptionDomain": "",
"signingIdentity": null,

View File

@ -1,37 +0,0 @@
import React, {createContext} from 'react'
import {Helper, TauriLocalFSBackend} from "./lib/helper";
import {Configurator} from "./lib/acrn";
// 1. Use React createContext API to create ACRN Context
export const ACRNContext = createContext({
helper: () => {
},
configurator: () => {
}
})
// 2. Create Context Provider
export class ACRNProvider extends React.Component {
constructor(props) {
super(props);
let fsBackend = new TauriLocalFSBackend()
let helper = new Helper(fsBackend, fsBackend)
let configurator = new Configurator(helper)
this.state = {
helper: helper,
configurator: configurator
}
}
render() {
console.log(this.state)
return (
<ACRNContext.Provider value={this.state}>
{this.props.children}
</ACRNContext.Provider>
)
}
}
// 3. export Consumer
export const ACRNConsumer = ACRNContext.Consumer

View File

@ -1,97 +0,0 @@
a {
/* Browse for folder… */
font-family: Roboto, serif;
font-style: normal;
font-weight: bold;
font-size: 16px;
line-height: 19px;
color: #2A958D;
text-decoration: auto;
}
@media (min-width: 576px) {
.border-end-sm {
border-right: 1px solid #dee2e6 !important;
}
}
.btn-lg {
min-height: 50px;
border-radius: 30px;
font-family: Roboto, serif;
font-style: normal;
font-weight: bold;
line-height: 23px;
text-align: center;
text-transform: capitalize;
color: #FFFFFF;
}
.btn-primary {
background-color: #007B81;
border-color: #007B81;
}
.btn-primary:hover {
background-color: #004B50;
border-color: #004B50;
}
select {
height: 49px;
background-image: url("assets/images/dropdown_arrow.svg") !important;
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 16px 12px;
border: 1px solid #ced4da;
border-radius: 0.25rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
appearance: none;
}
select:focus {
border: 1px solid #69BFAD;
box-sizing: border-box;
box-shadow: 0 0 6px rgba(105, 191, 173, 0.37);
border-radius: 5px;
}
.banner-text {
max-width: 594px;
font-family: Roboto, serif;
font-style: normal;
font-weight: normal;
font-size: 28px;
line-height: 37px;
letter-spacing: -0.175px;
color: #FFFFFF;
}
.bg-gray {
background: #F5F5F5;
}
.text-right {
text-align: right;
}
table {
width: 100%;
}
.descInfoBtn {
color: gray;
}
.descInfoBtn:hover {
color: lightgray;
}
.rst-paragraph:last-child{
margin-bottom: 0;
}

View File

@ -1,25 +0,0 @@
import {ACRNProvider} from "./ACRNContext";
import {HashRouter, Routes, Route} from "react-router-dom";
import './App.css'
import Welcome from "./pages/Welcome/Welcome";
import Error from "./pages/Error/Error";
import Config from "./pages/Config/Config";
import "bootstrap/dist/js/bootstrap"
function App() {
return (
<ACRNProvider>
<HashRouter>
<Routes>
<Route path="/" element={<Welcome/>}/>
<Route path="/config" element={<Config/>}/>
<Route path="*" element={<Error/>}/>
</Routes>
</HashRouter>
</ACRNProvider>
)
}
export default App

View File

@ -0,0 +1,24 @@
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import ControlBar from "./components/common/ControlBar.vue";
import Footer from "./components/common/Footer.vue";
</script>
<template>
<ControlBar>
<div class="acrnBody">
<router-view></router-view>
<Footer/>
</div>
</ControlBar>
</template>
<style lang="scss">
@import "./assets/css/index.scss";
.acrnBody {
height: calc(100vh - 84px);
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,183 @@
// global
$primary: #007B81;
// grid padding
$grid-gutter-width: 0;
// a
$link-decoration: auto;
$link-shade-percentage: 39%;
// text box fontsize
$input-font-size-lg: 1rem;
@import "bootstrap/scss/bootstrap";
@import 'bootstrap-vue-3/dist/bootstrap-vue-3.css';
.accordion-button {
box-shadow: none;
display: flex;
align-items: baseline;
&:not(.collapsed) {
background-color: #fff;
box-shadow: none;
&::before {
background-image: url("/src/assets/images/dropdown_arrow.svg");
color: #69BFAD;
transform: rotate(0deg);
margin-top: 9px;
}
&::after {
display: none;
}
}
// Accordion icon
&::before {
flex-shrink: 0;
width: $accordion-icon-width;
height: $accordion-icon-width;
content: "";
background-image: url("/src/assets/images/dropdown_arrow.svg");
background-repeat: no-repeat;
background-size: $accordion-icon-width;
margin: 8px 0 0 5px;
transform: rotate(-90deg);
@include transition($accordion-icon-transition);
}
&::after {
display: none;
}
&:focus {
box-shadow: none;
}
}
.text-pre-line {
white-space: pre-line;
}
.btn.btn-outline-primary {
color: #007B81;
border-color: #007B81;
background-color: white;
}
.btn-outline-primary:hover {
color: #004B50;
border-color: #004B50;
background-color: white;
}
.btn-close-white {
opacity: 1;
filter: none;
background-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11.5195 8.99331L17.4644 3.04835C18.1607 2.3521 18.1607 1.21845 17.4644 0.522192C16.7682 -0.174064 15.6345 -0.174064 14.9383 0.522192L8.99331 6.46715L3.04835 0.531118C2.3521 -0.165138 1.21845 -0.165138 0.522192 0.531118C-0.174064 1.22737 -0.174064 2.36102 0.522192 3.05728L6.46715 9.00223L0.522192 14.9472C-0.174064 15.6434 -0.174064 16.7771 0.522192 17.4733C0.87032 17.8215 1.32556 18 1.78081 18C2.23605 18 2.6913 17.8215 3.03942 17.4733L8.98438 11.5284L14.9293 17.4733C15.2775 17.8215 15.7327 18 16.1879 18C16.6432 18 17.0984 17.8215 17.4466 17.4733C18.1428 16.7771 18.1428 15.6434 17.4466 14.9472L11.5195 8.99331Z' fill='white'/%3E%3C/svg%3E%0A");
}
@import "vfonts/Roboto.css";
a {
/* Browse for folder… */
font-family: Roboto;
font-style: normal;
font-weight: bold;
font-size: 16px;
line-height: 19px;
color: #2A958D;
text-decoration: auto;
}
@media (min-width: 576px) {
.border-end-sm {
border-right: 1px solid #dee2e6 !important;
}
}
.btn-lg {
min-height: 50px;
border-radius: 30px;
font-family: Roboto;
font-style: normal;
font-weight: bold;
line-height: 23px;
text-align: center;
text-transform: capitalize;
color: #FFFFFF;
}
.btn-primary {
background-color: #007B81;
border-color: #007B81;
}
.btn-primary:hover {
background-color: #004B50;
border-color: #004B50;
}
select {
height: 49px;
background-image: url("/src/assets/images/dropdown_arrow.svg") !important;
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 16px 12px;
border: 1px solid #ced4da;
border-radius: 0.25rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
appearance: none;
}
select:focus {
border: 1px solid #69BFAD;
box-sizing: border-box;
box-shadow: 0 0 6px rgba(105, 191, 173, 0.37);
border-radius: 5px;
}
.banner-text {
max-width: 594px;
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 28px;
line-height: 37px;
letter-spacing: -0.175px;
color: #FFFFFF;
}
.bg-gray {
background: #F5F5F5;
}
.text-right {
text-align: right;
}
table {
width: 100%;
}
.descInfoBtn {
color: gray;
}
.descInfoBtn:hover {
color: lightgray;
}
.rst-paragraph:last-child {
margin-bottom: 0;
}

View File

@ -1,9 +0,0 @@
@font-face {
font-family: 'Roboto';
src: url('Roboto-Regular.woff2') format('woff2'),
url('Roboto-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,78 +0,0 @@
"""
public var board_xml
public function get_enum(source, selector)
enum = {
'type': 'dynamicEnum',
'function': 'get_enum',
'source': 'board_xml',
'selector': element['xs:annotation']['@acrn:options'],
'sorted': element['xs:annotation'].get('@acrn:options-sorted-by', None)
}
params defined before this document string in js side like
```js
let params = JSON.stringify({board_xml: boardXMLText, scenario_json: JSON.stringify(scenario_json)})
params = Base64.encode(params);
return runPyCode(`
params="${params}"
${dynamicScenario}
`)
```
"""
import json
from base64 import b64decode
import elementpath
import lxml.etree as etree
# local test var set
if 'params' not in globals():
# params = b64encode(open(r"board.xml",'rb').read())
raise NotImplementedError
# Main flow
# noinspection PyUnresolvedReferences,PyUnboundLocalVariable
params: str = b64decode(params).decode('utf-8')
params: dict = json.loads(params)
board_xml = etree.XML(params["board_xml"])
scenario_json = params['scenario_json']
def get_enum(source, options):
elements = [str(x) for x in elementpath.select(source, options) if x]
elements = list(set(elements))
if not elements:
elements = ['']
return elements
def dynamic_enum(**kwargs):
# value from env
function, source = [globals()[kwargs[key]] for key in ['function', 'source']]
# value from given
selector, sorted_func = [kwargs[key] for key in ['selector', 'sorted']]
# get enum data
enum = function(source, selector)
if sorted_func:
enum = sorted(enum, key=eval(sorted_func))
return enum
def dynamic_enum_apply(obj):
# get json schema enum obj
if 'enum' in obj and isinstance(obj['enum'], dict):
enum_setting = obj['enum']
# check enum obj type
if enum_setting['type'] == 'dynamicEnum':
del enum_setting['type']
# replace json schema obj enum field data
obj['enum'] = dynamic_enum(**enum_setting)
return obj
data = json.loads(scenario_json, object_hook=dynamic_enum_apply)
json.dumps(data)

View File

@ -1,6 +0,0 @@
.banner {
min-height: 50px;
background-image: url("images/top_pattern.png"), linear-gradient(90.29deg, #242357 0.08%, #6ABFAE 85.19%, #69BFAD 99.72%);
background-repeat: no-repeat;
background-position: 50%;
}

View File

@ -1,9 +0,0 @@
import React from "react";
export class Banner extends React.Component {
render() {
return (
<div className="banner">{this.props.children}</div>
);
}
}

View File

@ -1,5 +0,0 @@
import './Banner.css'
import {Banner} from "./Banner";
export default Banner;

View File

@ -1,45 +0,0 @@
import {Button, Modal} from "react-bootstrap";
import {useState} from "react";
export default function Confirm(props) {
const [show, setShow] = useState(false);
const handleClose = (choice) => {
setShow(false);
props.callback(choice)
}
const handleShow = (e) => {
setShow(true);
e.preventDefault()
}
return (
<Modal show={show} onHide={() => handleClose('cancel')} size="lg">
<Modal.Header closeButton closeVariant="white" className="bg-primary text-white">
<Modal.Title>{props.title}</Modal.Title>
</Modal.Header>
<Modal.Body>
<div className="p-4">
<b>{props.content}</b>
</div>
</Modal.Body>
<Modal.Footer className="px-5 py-4 my-2">
<Button className="me-sm-2" variant="outline-primary" onClick={() => handleShow('cancel')}
size="lg" style={{width: '137px'}}>
Cancel
</Button>
<Button className="me-sm-2" variant="outline-primary" onClick={() => handleShow('no')} size="lg"
style={{width: '137px'}}>
{props.no ? props.no : "No"}
</Button>
<Button variant="primary" onClick={() => {
props.callback('yes')
}} size="lg" style={{width: '137px'}}>
{props.yes ? props.yes : "Yes"}
</Button>
</Modal.Footer>
</Modal>
);
}

View File

@ -1,3 +0,0 @@
import Confirm from "./Confirm";
export default Confirm

View File

@ -1,25 +0,0 @@
import {Component} from "react";
import {getVersion} from "@tauri-apps/api/app";
export default class Footer extends Component {
constructor(props) {
super(props);
this.state = {
version: "0.1.0"
}
}
componentDidMount = () => {
getVersion().then((version) => {
this.setState({version: version})
})
}
render = () => {
return <div className="pt-3">
<p className="text-center text-secondary">© Copyright Project ACRN, a Series of LF Projects, LLC.
- Version {this.state.version}</p>
</div>
}
}

View File

@ -1,3 +0,0 @@
import Footer from "./footer";
export default Footer

View File

@ -0,0 +1,21 @@
<script setup>
import { ref } from 'vue'
defineProps({
msg: String
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<button type="button" @click="count++">count is: {{ count }}</button>
</template>
<style scoped>
a {
color: #42b983;
}
</style>

View File

@ -1,44 +0,0 @@
body {
-webkit-app-region: no-drag
}
.bg-navbar {
height: 80px;
background: #007B81;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
-webkit-user-select: none;
-webkit-app-region: drag;
}
.logo-text {
cursor: default;
font-family: Roboto, serif;
font-style: normal;
font-weight: bold;
font-size: 26px;
line-height: 30px;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #9ADFD1;
}
.controlButtons {
margin-right: 30px;
width: 130px;
filter: drop-shadow(3px 3px 2px rgb(0 0 0 / 0.4));
}
.wmb {
cursor: pointer;
-webkit-app-region: no-drag;
}
.wmb:hover {
opacity: 0.7;
}
.btn-close {
-webkit-app-region: no-drag;
}

View File

@ -1,39 +0,0 @@
import React from "react";
import {Container} from "react-bootstrap";
import {Navbar as BootstrapNavbar} from "react-bootstrap";
import logo from "./images/ACRN_Logo.svg";
import {faWindowMaximize} from "@fortawesome/free-regular-svg-icons";
import {faClose, faMinus} from "@fortawesome/free-solid-svg-icons"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {windowHelper} from "../../lib/platform/tauri/tauri";
export class Navbar extends React.Component {
render() {
return (
<BootstrapNavbar bg="navbar" variant="dark" height="80">
<Container fluid data-tauri-drag-region>
<BootstrapNavbar.Brand>
<img
alt="ACRN" src={logo} height="38" data-tauri-drag-region="true"
className="d-inline-block align-self-center mx-3"
/>
<div className="d-inline align-bottom logo-text" data-tauri-drag-region="true">
Configurator
</div>
</BootstrapNavbar.Brand>
<div className="controlButtons d-flex justify-content-between align-items-center"
data-tauri-drag-region={true}>
<FontAwesomeIcon className="wmb" icon={faMinus} color="white" size="lg"
onClick={windowHelper.minimal}/>
<FontAwesomeIcon className="wmb" icon={faWindowMaximize} color="white" size="lg"
onClick={windowHelper.maxmal}/>
<FontAwesomeIcon className="wmb" icon={faClose} color="white" size="lg"
onClick={windowHelper.close}/>
</div>
</Container>
</BootstrapNavbar>
);
}
}

View File

@ -1,5 +0,0 @@
import './Navbar.css'
import {Navbar} from "./Navbar";
export default Navbar;

View File

@ -0,0 +1,21 @@
<template>
<div class="banner">
<slot></slot>
</div>
</template>
<script>
export default {
name: "Banner"
}
</script>
<style scoped>
.banner {
min-height: 50px;
background-image: url("/src/assets/images/top_pattern.png"), linear-gradient(90.29deg, #242357 0.08%, #6ABFAE 85.19%, #69BFAD 99.72%);
background-repeat: no-repeat;
background-position: 50%;
}
</style>

View File

@ -0,0 +1,96 @@
<template>
<nav class="navbar navbar-expand navbar-dark bg-navbar">
<div data-tauri-drag-region="true" class="container-fluid">
<span class="navbar-brand">
<img alt="ACRN"
src="/src/assets/images/ACRN_Logo.svg"
height="38"
data-tauri-drag-region="true"
class="d-inline-block align-self-center mx-3">
<text class="d-inline align-bottom logo-text" data-tauri-drag-region="true">Configurator</text>
</span>
<div class="controlButtons d-flex justify-content-between align-items-center" data-tauri-drag-region="true">
<Icon @click="minus" size="20px" color="white">
<Minus/>
</Icon>
<Icon @click="maximize" size="20px" color="white">
<WindowMaximizeRegular/>
</Icon>
<Icon @click="close" size="20px" color="white">
<Times/>
</Icon>
</div>
</div>
</nav>
<slot></slot>
</template>
<script>
import {Icon} from '@vicons/utils'
import {Minus, WindowMaximizeRegular, Times} from '@vicons/fa'
import {appWindow} from "@tauri-apps/api/window";
export default {
name: "ControlBar",
components: {Icon, Minus, WindowMaximizeRegular, Times},
methods: {
minus() {
appWindow.minimize()
},
maximize() {
appWindow.isMaximized().then((isMaximize) => {
if (isMaximize) {
appWindow.unmaximize()
} else {
appWindow.maximize()
}
})
},
close: () => {
console.log("close")
alert("close")
}
}
}
</script>
<style scoped>
.navbar-expand {
height: 80px;
}
.bg-navbar {
height: 80px;
background: #007B81;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
-webkit-user-select: none;
-webkit-app-region: drag;
}
.logo-text {
cursor: default;
font-family: Roboto;
font-style: normal;
font-weight: bold;
font-size: 26px;
line-height: 30px;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #9ADFD1;
}
.controlButtons {
margin-right: 30px;
width: 130px;
filter: drop-shadow(3px 3px 2px rgb(0 0 0 / 0.4));
}
.xicon:hover {
opacity: 0.7;
}
</style>

View File

@ -0,0 +1,39 @@
<template>
<div class="pt-3">
<p class="text-center text-secondary">
© Copyright Project ACRN, a Series of LF Projects, LLC.
<text v-if="version" @click="home">- Version {{ version }}</text>
</p>
</div>
</template>
<script>
import {getVersion} from "@tauri-apps/api/app";
export default {
name: "Footer",
data: () => {
return {
version: ''
}
},
mounted() {
this.getAppVersion()
},
methods: {
home() {
this.$router.push('/')
},
getAppVersion() {
getVersion().then((version) => {
this.version = version
})
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,15 +0,0 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,36 +0,0 @@
// global
$primary: #007B81;
// grid padding
$grid-gutter-width: 0;
// a
$link-decoration: auto;
$link-shade-percentage: 39%;
// text box fontsize
$input-font-size-lg: 1rem;
@import "../node_modules/bootstrap/scss/bootstrap";
.text-pre-line {
white-space: pre-line;
}
.btn.btn-outline-primary {
color: #007B81;
border-color: #007B81;
background-color: white;
}
.btn-outline-primary:hover {
color: #004B50;
border-color: #004B50;
background-color: white;
}
.btn-close-white {
opacity: 1;
filter: none;
background-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11.5195 8.99331L17.4644 3.04835C18.1607 2.3521 18.1607 1.21845 17.4644 0.522192C16.7682 -0.174064 15.6345 -0.174064 14.9383 0.522192L8.99331 6.46715L3.04835 0.531118C2.3521 -0.165138 1.21845 -0.165138 0.522192 0.531118C-0.174064 1.22737 -0.174064 2.36102 0.522192 3.05728L6.46715 9.00223L0.522192 14.9472C-0.174064 15.6434 -0.174064 16.7771 0.522192 17.4733C0.87032 17.8215 1.32556 18 1.78081 18C2.23605 18 2.6913 17.8215 3.03942 17.4733L8.98438 11.5284L14.9293 17.4733C15.2775 17.8215 15.7327 18 16.1879 18C16.6432 18 17.0984 17.8215 17.4466 17.4733C18.1428 16.7771 18.1428 15.6434 17.4466 14.9472L11.5195 8.99331Z' fill='white'/%3E%3C/svg%3E%0A");
}

View File

@ -1,2 +0,0 @@
pyodide
/pyodide.tar.bz2

View File

@ -1,503 +0,0 @@
import _ from "lodash";
import React, {Component} from "react";
import {path} from "@tauri-apps/api";
import scenario from '../assets/schema/scenario.json'
import {createSearchParams} from "react-router-dom";
import queryString from 'query-string'
import {invoke} from "@tauri-apps/api/tauri";
import {getNewSchema} from "./runpy";
function ThrowError(errMsg) {
alert(errMsg)
throw new Error(errMsg)
}
function NameTrans(name) {
return {
"recentlyWorkingFolders": "WorkingFolder",
"board": "Board",
"scenario": "Scenario",
}[name.replace("History", "")]
}
class EventBase extends Component {
constructor(props) {
super(props)
this.funRegister = {}
this.funRegisterID = 0
this.eventName = {
scenarioDataUpdate: 'scenarioDataUpdate'
}
}
register = (eventName, fun) => {
if (this.funRegister.hasOwnProperty(eventName)) {
this.funRegisterID++
this.funRegister[eventName][this.funRegisterID] = fun
return this.funRegisterID
}
}
unregister = (eventName, funRegisterID) => {
if (this.funRegister.hasOwnProperty(eventName)) {
if (this.funRegister[eventName].hasOwnProperty(funRegisterID)) {
delete this.funRegister[eventName][funRegisterID]
}
}
}
}
export class XMLLayer extends EventBase {
constructor(helper) {
super();
this.helper = helper
this.funRegister = {
onScenarioLoad: []
}
}
#parseXML = (XMLText) => {
return (new DOMParser()).parseFromString(XMLText, "text/xml")
}
#validateBoardXMLText = boardXMLText => {
// call inside
try {
let boardXML = this.#parseXML(boardXMLText, "text/xml");
return !!boardXML.querySelector("BIOS_INFO")
} catch (e) {
return false
}
}
#validateScenarioXMLText = (scenarioXMLText) => {
// call inside
try {
let scenarioXML = this.#parseXML(scenarioXMLText);
return !!scenarioXML.querySelector("acrn-config")
} catch (e) {
return false
}
}
loadBoard = async (boardXMLPath) => {
// call by program
let boardXMLText = await this.helper.read(await this.helper.resolveHome(boardXMLPath))
if (this.#validateBoardXMLText(boardXMLText)) {
let PCIDevices = this.getPCIDevice(boardXMLText)
return {boardXMLText, PCIDevices}
} else {
alert('Board XML Error!')
return false
}
}
loadScenario = async (scenarioXMLPath) => {
// call by program
// load scenario data from xml file
let scenarioXMLText = await this.helper.read(await this.helper.resolveHome(scenarioXMLPath))
if (!this.#validateScenarioXMLText(scenarioXMLText)) {
ThrowError('Scenario XML Error!')
}
return this.helper.convertXMLTextToObj(scenarioXMLText)['acrn-config']
}
getPCIDevice = (boardXMLText) => {
let pci_devices = this.#parseXML(boardXMLText).querySelector("PCI_DEVICE").textContent
// Remove Region line
pci_devices = pci_devices.replace(/Region.+\s+/g, '\n')
// Remove Space
pci_devices = pci_devices.replace(/[\n\t]+/g, '\n')
// Split by \n
pci_devices = pci_devices.split('\n')
return _.uniq(pci_devices)
}
saveBoard = (boardFileWritePath, boardData) => {
this.helper.save(boardFileWritePath, boardData)
}
saveScenario = (scenarioWritePath, scenarioData) => {
// call by program
console.log(scenarioData)
const scenarioXML = this.helper.convertObjToXML({'acrn-config': scenarioData})
console.log(scenarioXML)
// debugger
this.helper.save(scenarioWritePath, scenarioXML)
}
}
export class ProgramLayer extends EventBase {
constructor(helper, instanceOfXMLLayer) {
super()
this.helper = helper
this.vmID = 0
this.scenarioData = {}
this.initScenario()
this.xmlLayer = instanceOfXMLLayer
this.funRegister = {
scenarioDataUpdate: []
}
}
initScenario = () => {
this.vmID = 0
this.scenarioData = {
hv: {},
vm: {
PRE_LAUNCHED_VM: [],
SERVICE_VM: [],
POST_LAUNCHED_VM: [],
}
}
}
onScenarioDataUpdateEvent() {
this.funRegister.scenarioDataUpdate.map((f) => f())
}
newScenario = (preLaunchedVM, serviceVM, postLaunchedVM) => {
// call by view
this.initScenario()
for (let i = 0; i < preLaunchedVM; i++) {
this.addVM('PRE_LAUNCHED_VM')
}
for (let i = 0; i < serviceVM; i++) {
this.addVM('SERVICE_VM')
}
for (let i = 0; i < postLaunchedVM; i++) {
this.addVM('POST_LAUNCHED_VM')
}
this.onScenarioDataUpdateEvent()
}
addVM = (VMType, vmData = {}) => {
// call by inside and view
// if provide VMType and vmData at same time
// function will set VMType to vmData
let vm = vmData
let usedVMID = false;
// check VMType
let vmType = VMType;
if (!_.isString(vmType) || _.isEmpty(vmType)) {
// vmType is empty or not correct
if (vm.hasOwnProperty('load_order')) {
vmType = vm.load_order;
} else {
ThrowError('No VMType Set')
}
} else {
if (vm.hasOwnProperty('load_order') && vm.load_order !== vmType) {
console.warn("vmType and vmData provide load_order at same time, set vmData's load_order to vmType now")
}
vm.load_order = vmType
}
// check and validate vm name
if (!vm.hasOwnProperty('name') || !_.isString(vm.name) || _.isEmpty(vm.name)) {
vm.name = 'VM' + this.vmID
usedVMID = true
}
vm['@id'] = this.vmID
this.vmID++
// add to config
this.scenarioData.vm[vmType].push(vm)
this.onScenarioDataUpdateEvent()
}
isServiceVM = (vmID) => {
for (var idx in this.scenarioData.vm.SERVICE_VM) {
if (this.scenarioData.vm.SERVICE_VM[idx]['@id'] === vmID) {
return true
}
}
return false
}
deleteVM = (vmID) => {
// call by view
for (let vmType in this.scenarioData.vm) {
this.scenarioData.vm[vmType].map((vmConfig, vmIndex) => {
if (vmConfig['@id'] === vmID) {
this.scenarioData.vm[vmType].splice(vmIndex, 1)
}
})
}
this.onScenarioDataUpdateEvent()
}
loadBoard = async (WorkingFolder, boardXMLPath) => {
// call by view
let boardData = await this.xmlLayer.loadBoard(boardXMLPath)
if (boardData === false) {
return false
}
let {boardXMLText, PCIDevices} = boardData
// get new board file name
let newBoardFileName = await path.basename(boardXMLPath)
let cut = 0
if (_.endsWith(newBoardFileName.toLowerCase(), '.xml')) {
cut = '.xml'.length
}
if (_.endsWith(newBoardFileName.toLowerCase(), '.board.xml')) {
cut = '.board.xml'.length
}
newBoardFileName = newBoardFileName.slice(0, newBoardFileName.length - cut)
newBoardFileName = newBoardFileName + '.board.xml'
// new board file save path
const boardFileWritePath = await path.join(await this.helper.resolveHome(WorkingFolder), newBoardFileName)
// remove current working folder old Board File first
await this.removeOldBoardFile(WorkingFolder)
// save board file to working director
this.xmlLayer.saveBoard(boardFileWritePath, boardXMLText)
// get shownName
let shownName = await path.join(WorkingFolder, newBoardFileName)
console.log({shownName, boardXMLText, PCIDevices})
return {shownName, boardXMLText, PCIDevices}
}
loadScenario = async (scenarioXMLPath) => {
// call by view
let scenarioConfig = await this.xmlLayer.loadScenario(scenarioXMLPath)
this.initScenario()
this.scenarioData.hv = scenarioConfig.hv;
if (scenarioConfig.hasOwnProperty('vm')) {
if (!_.isArray(scenarioConfig.vm)) {
if (_.isObject(scenarioConfig.vm)) {
scenarioConfig.vm = [scenarioConfig.vm]
} else {
console.log(scenarioConfig.vm);
debugger;
this.onScenarioDataUpdateEvent()
return
}
}
scenarioConfig.vm.map((vmConfig, index) => {
let vmType = vmConfig.load_order
if (!this.scenarioData.vm.hasOwnProperty(vmType)) {
try {
ThrowError('VM @id=' + index + ' VMType Does Not Exist')
} catch (e) {
}
} else {
//fix pci_devs is object issue
if (
vmConfig.hasOwnProperty("pci_devs") &&
vmConfig.pci_devs != null &&
vmConfig.pci_devs.hasOwnProperty("pci_dev") &&
vmConfig.pci_devs != null &&
_.isString(vmConfig.pci_devs.pci_dev)
) {
vmConfig.pci_devs.pci_dev = [vmConfig.pci_devs.pci_dev]
}
this.addVM(vmType, vmConfig)
}
})
}
this.onScenarioDataUpdateEvent()
}
getOriginScenarioData = () => {
// call by inside
let originScenario = _.cloneDeep(this.scenarioData)
originScenario.vm = originScenario.vm.PRE_LAUNCHED_VM.concat(
originScenario.vm.SERVICE_VM,
originScenario.vm.POST_LAUNCHED_VM
)
return originScenario
}
async removeOldBoardFile(WorkingFolder) {
let files = await this.helper.list(await this.helper.resolveHome(WorkingFolder))
files.map((filename) => {
if (_.endsWith(filename, '.board.xml')) {
this.helper.remove(filename)
}
})
}
saveScenario = async (WorkingFolder) => {
// call by view
let originScenarioData = this.getOriginScenarioData()
let filename = 'scenario.xml'
let scenarioWritePath = await path.join(await this.helper.resolveHome(WorkingFolder), filename)
this.xmlLayer.saveScenario(scenarioWritePath, originScenarioData)
// noinspection UnnecessaryLocalVariableJS
let shownPath = await path.join(WorkingFolder, filename)
return shownPath
}
}
export class Configurator extends EventBase {
// get data from Program
// convert it to view data
constructor(helper) {
super()
this.WorkingFolder = this.#getURLParam('WorkingFolder')
this.helper = helper
this.XMLLayer = new XMLLayer(this.helper)
this.programLayer = new ProgramLayer(this.helper, this.XMLLayer)
this.vmSchemas = this.Schemas()
this.hvSchema = this.vmSchemas.HV
delete this.vmSchemas.HV
this.updateSchema()
}
#getURLParam(key) {
let hash = location.hash
let params = hash.substring(hash.indexOf('?'))
params = queryString.parse(params)
return params[key]
}
#buildPageParams(url, queryParams = {}) {
let data = {pathname: url}
if (queryParams) {
data.search = createSearchParams(queryParams).toString()
}
return data;
}
settingWorkingFolder = (WorkingFolder) => {
this.WorkingFolder = WorkingFolder
return this.#buildPageParams('./config', {WorkingFolder})
}
updateSchema = () => {
let listingFunctions = [this.ivshmemEnum]
listingFunctions.forEach((func) => {
func()
this.programLayer.register("scenarioDataUpdate", func)
})
}
ivshmemEnum = () => {
let odata = this.programLayer.getOriginScenarioData()
let vmNames = odata.vm.map((vmData) => {
return vmData.name
})
if (vmNames.length === 0) {
vmNames = ['']
}
this.hvSchema.basic.definitions.VMNameType.enum = vmNames
}
loadBoard = async (boardXMLPath, callback) => {
let boardData = await this.programLayer.loadBoard(this.WorkingFolder, boardXMLPath)
if (boardData === false) {
await this.removeHistory('board', boardXMLPath)
return
}
let {shownName, boardXMLText, PCIDevices} = boardData
scenario.definitions.PCIDevsConfiguration.properties.pci_dev.items.enum = PCIDevices
Object.keys(this.vmSchemas).map((VMTypeKey) => {
Object.keys(this.vmSchemas[VMTypeKey]).map((configLevelKey) => {
this.vmSchemas[VMTypeKey][configLevelKey].definitions.PCIDevsConfiguration.properties.pci_dev.items.enum = PCIDevices
})
})
try {
let new_scenario = getNewSchema(boardXMLText)
scenario.definitions = new_scenario.definitions;
Object.keys(this.vmSchemas).map((VMTypeKey) => {
Object.keys(this.vmSchemas[VMTypeKey]).map((configLevelKey) => {
this.vmSchemas[VMTypeKey][configLevelKey].definitions = new_scenario.definitions
})
})
Object.keys(this.hvSchema).map((configLevelKey) => {
this.hvSchema[configLevelKey].definitions = new_scenario.definitions;
})
} catch (e) {
}
callback(shownName, boardXMLText)
}
async getHistory(key) {
let p = await invoke("get_history", {historyType: NameTrans(key)})
console.log("p", p);
return JSON.parse(p)
}
async addHistory(key, historyPath) {
await invoke("add_history", {historyType: NameTrans(key), path: historyPath})
return Promise.resolve()
}
async setHistory(key, history) {
return Promise.resolve()
}
async removeHistory(key, historyPath) {
let history = await this.getHistory(key)
let index = history.indexOf(historyPath);
if (index > -1) {
history.splice(index, 1);
}
return this.setHistory(key, history)
}
saveScenario = async () => {
let shownPath = await this.programLayer.saveScenario(this.WorkingFolder)
return this.addHistory('scenario', shownPath)
}
static #getSchema(prefix) {
let bo = scenario.definitions[prefix + "BasicConfigType"]
let basic = _.cloneDeep(scenario);
basic.type = bo.type;
basic.required = bo.required;
basic.properties = bo.properties;
let ao = scenario.definitions[prefix + "AdvancedConfigType"]
let advanced = _.cloneDeep(scenario);
advanced.type = ao.type;
advanced.required = ao.required;
advanced.properties = ao.properties;
return {basic, advanced}
}
Schemas() {
let prefixData = {
HV: 'HV',
PRE_LAUNCHED_VM: 'PreLaunchedVM',
SERVICE_VM: 'ServiceVM',
POST_LAUNCHED_VM: 'PostLaunchedVM'
}
for (let key in prefixData) {
prefixData[key] = Configurator.#getSchema(prefixData[key])
}
return prefixData
}
log() {
this.helper.log(...arguments)
}
}

View File

@ -0,0 +1,144 @@
import {dialog, invoke} from "@tauri-apps/api";
import JSON2XML from "./json2xml"
import scenarioSchema from "../assets/schema/scenario.json";
import {OpenDialogOptions} from "@tauri-apps/api/dialog";
enum HistoryType {
WorkingFolder,
Board,
Scenario
}
export type HistoryTypeString = keyof typeof HistoryType;
class PythonObject {
api(scriptName, ...params) {
// @ts-ignore
let pythonFunction = window.pyodide.pyimport(`configurator.pyodide.${scriptName}`);
let result = pythonFunction.main(...params);
return JSON.parse(result);
}
loadBoard(boardXMLText) {
return this.api('loadBoard', boardXMLText)
}
loadScenario(scenarioXMLText) {
return this.api('loadScenario', scenarioXMLText)
}
validateScenario(boardXMLText, scenarioXMLText) {
return this.api('validateScenario', boardXMLText, scenarioXMLText)
}
generateLaunchScript(boardXMLText, scenarioXMLText) {
return this.api('generateLaunchScript', boardXMLText, scenarioXMLText)
}
}
class Configurator {
public pythonObject: PythonObject;
constructor() {
this.pythonObject = new PythonObject()
}
getHistory(historyType: HistoryTypeString): Promise<String[] | []> {
return invoke("get_history", {historyType})
.then((historyJsonText) => {
if (typeof historyJsonText === "string") {
return JSON.parse(historyJsonText);
}
return [];
})
}
addHistory(historyType: HistoryTypeString, historyPath: String) {
return invoke("add_history", {historyType, historyPath})
}
openDialog(options: OpenDialogOptions) {
return dialog.open(options)
}
readFile(filePath: String): Promise<String> {
return invoke("acrn_read", {filePath})
}
writeFile(filePath: String, contents: String) {
return invoke("acrn_write", {filePath, contents})
}
isFile(filePath: String): Promise<boolean> {
return invoke("acrn_is_file", {path: filePath})
}
readDir(path: String, recursive: Boolean) {
return invoke('acrn_read_dir', {path, recursive})
}
creatDir(path: String) {
return invoke('acrn_create_dir', {path})
}
runPython(code: String, isJSON = false): String | Object {
// @ts-ignore
let result = window.pydoide.runPython(code);
if (isJSON) {
result = JSON.parse(result)
}
return result
}
loadBoard(path: String) {
return this.readFile(path)
.then((fileContent) => {
let params = JSON.stringify({
boardXML: fileContent,
scenarioSchema: scenarioSchema
})
return this.pythonObject.loadBoard(fileContent)
})
}
loadScenario(path: String): Object {
return this.readFile(path).then((fileContent) => {
return this.pythonObject.loadScenario(fileContent)
})
}
newVM(vmid, load_order) {
return {
'@id': vmid,
load_order: load_order,
name: `VM${vmid}`
}
}
createNewScenario(pre, service, post) {
let newScenario = {
hv: {},
vm: []
}
let vmid = 0
let vmNums = {'PRE_LAUNCHED_VM': pre, 'SERVICE_VM': service, 'POST_LAUNCHED_VM': post}
for (let key in vmNums) {
for (let i = 0; i < vmNums[key]; i++) {
newScenario.vm.push(this.newVM(vmid, key))
vmid++;
}
}
return newScenario;
}
convertScenarioToXML(scenarioData: Object) {
let json2xml = new JSON2XML();
let xml_data = json2xml.convert(scenarioData);
return xml_data
}
}
let configurator = new Configurator()
export default configurator

View File

@ -1,2 +0,0 @@
*.js
*.js.map

View File

@ -1,13 +0,0 @@
import React from "react";
import {AddButtonProps} from "@rjsf/core";
import Button from "react-bootstrap/Button";
import {BsPlus} from "react-icons/bs";
const AddButton: React.FC<AddButtonProps> = props => (
<Button {...props} style={{width: "100%"}} className={`ml-1 ${props.className}`}>
<BsPlus/>
</Button>
);
export default AddButton;

View File

@ -1,2 +0,0 @@
export {default} from "./AddButton";
export * from "./AddButton";

View File

@ -1,210 +0,0 @@
import React from "react";
import {utils} from "@rjsf/core";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import Container from "react-bootstrap/Container";
import {ArrayFieldTemplateProps, IdSchema} from "@rjsf/core";
import AddButton from "../AddButton/AddButton";
import IconButton from "../IconButton/IconButton";
const {isMultiSelect, getDefaultRegistry} = utils;
const ArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
const {schema, registry = getDefaultRegistry()} = props;
if (isMultiSelect(schema, registry.rootSchema)) {
return <DefaultFixedArrayFieldTemplate {...props} />;
} else {
return <DefaultNormalArrayFieldTemplate {...props} />;
}
};
type ArrayFieldTitleProps = {
TitleField: any;
idSchema: IdSchema;
title: string;
required: boolean;
};
const ArrayFieldTitle = ({
TitleField,
idSchema,
title,
required,
}: ArrayFieldTitleProps) => {
if (!title) {
return null;
}
const id = `${idSchema.$id}__title`;
return <TitleField id={id} title={title} required={required}/>;
};
type ArrayFieldDescriptionProps = {
DescriptionField: any;
idSchema: IdSchema;
description: string;
};
const ArrayFieldDescription = ({
DescriptionField,
idSchema,
description,
}: ArrayFieldDescriptionProps) => {
if (!description) {
return null;
}
const id = `${idSchema.$id}__description`;
return <DescriptionField id={id} description={description}/>;
};
// Used in the two templates
const DefaultArrayItem = (props: any) => {
const btnStyle = {
flex: 1,
paddingLeft: 6,
paddingRight: 6,
fontWeight: "bold",
};
return (
<div key={props.key}>
<Row className="mb-2 d-flex align-items-baseline">
<Col xs="9" lg="9">{props.children}</Col>
<Col xs="3" lg="3" className="px-3">
{props.hasToolbar && (
<div className="d-flex flex-row">
{(props.hasMoveUp || props.hasMoveDown) && (
<div className="m-0 p-0">
<IconButton
icon="arrow-up"
className="array-item-move-up"
tabIndex={-1}
style={btnStyle as any}
disabled={
props.disabled || props.readonly || !props.hasMoveUp
}
onClick={props.onReorderClick(props.index, props.index - 1)}
/>
</div>
)}
{(props.hasMoveUp || props.hasMoveDown) && (
<div className="m-0 p-0">
<IconButton
icon="arrow-down"
tabIndex={-1}
style={btnStyle as any}
disabled={
props.disabled || props.readonly || !props.hasMoveDown
}
onClick={props.onReorderClick(props.index, props.index + 1)}
/>
</div>
)}
{props.hasRemove && (
<div className="m-0 p-0">
<IconButton
icon="remove"
tabIndex={-1}
style={btnStyle as any}
disabled={props.disabled || props.readonly}
onClick={props.onDropIndexClick(props.index)}
/>
</div>
)}
</div>
)}
</Col>
</Row>
</div>
);
};
const DefaultFixedArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
return (
<fieldset className={props.className}>
<ArrayFieldTitle
key={`array-field-title-${props.idSchema.$id}`}
TitleField={props.TitleField}
idSchema={props.idSchema}
title={props.uiSchema["ui:title"] || props.title}
required={props.required}
/>
{(props.uiSchema["ui:description"] || props.schema.description) && (
<div
className="field-description"
key={`field-description-${props.idSchema.$id}`}>
{props.uiSchema["ui:description"] || props.schema.description}
</div>
)}
<div
className="row array-item-list"
key={`array-item-list-${props.idSchema.$id}`}>
{props.items && props.items.map(DefaultArrayItem)}
</div>
{props.canAdd && (
<AddButton
className="array-item-add"
onClick={props.onAddClick}
disabled={props.disabled || props.readonly}
/>
)}
</fieldset>
);
};
const DefaultNormalArrayFieldTemplate = (props: ArrayFieldTemplateProps) => {
return (
<div>
<Row className="p-0 m-0">
<Col className="p-0 m-0">
<ArrayFieldTitle
key={`array-field-title-${props.idSchema.$id}`}
TitleField={props.TitleField}
idSchema={props.idSchema}
title={props.uiSchema["ui:title"] || props.title}
required={props.required}
/>
{(props.uiSchema["ui:description"] || props.schema.description) && (
<ArrayFieldDescription
key={`array-field-description-${props.idSchema.$id}`}
DescriptionField={props.DescriptionField}
idSchema={props.idSchema}
description={
props.uiSchema["ui:description"] || props.schema.description
}
/>
)}
<Container fluid key={`array-item-list-${props.idSchema.$id}`} className="p-0 m-0">
{props.items && props.items.map(p => DefaultArrayItem(p))}
{props.canAdd && (
<Container className="">
<Row className="mt-2">
<Col xs={9}/>
<Col xs={3} className="py-4 col-lg-3 col-3"> <AddButton
className="array-item-add"
onClick={props.onAddClick}
disabled={props.disabled || props.readonly}
/></Col>
</Row>
</Container>
)}
</Container></Col>
</Row>
</div>
);
};
export default ArrayFieldTemplate;

View File

@ -1,2 +0,0 @@
export {default} from "./ArrayFieldTemplate";
export * from "./ArrayFieldTemplate";

View File

@ -1,50 +0,0 @@
import React from "react";
import {WidgetProps} from "@rjsf/core";
import Form from "react-bootstrap/Form";
const CheckboxWidget = (props: WidgetProps) => {
const {
id,
value,
required,
disabled,
readonly,
label,
schema,
autofocus,
onChange,
onBlur,
onFocus,
} = props;
const _onChange = ({
target: {checked},
}: React.FocusEvent<HTMLInputElement>) => onChange(checked);
const _onBlur = ({
target: {checked},
}: React.FocusEvent<HTMLInputElement>) => onBlur(id, checked);
const _onFocus = ({
target: {checked},
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, checked);
const desc = label || schema.description;
return (
<Form.Group className={`checkbox ${disabled || readonly ? "disabled" : ""}`}>
<Form.Check
id={id}
label={desc}
checked={typeof value === "undefined" ? false : value}
required={required}
disabled={disabled || readonly}
autoFocus={autofocus}
onChange={_onChange}
type="checkbox"
onBlur={_onBlur}
onFocus={_onFocus}
/>
</Form.Group>
);
};
export default CheckboxWidget;

View File

@ -1,2 +0,0 @@
export {default} from "./CheckboxWidget";
export * from "./CheckboxWidget";

View File

@ -1,103 +0,0 @@
import React from "react";
import Form from "react-bootstrap/Form";
import {WidgetProps} from "@rjsf/core";
const selectValue = (value: any, selected: any, all: any) => {
const at = all.indexOf(value);
const updated = selected.slice(0, at).concat(value, selected.slice(at));
// As inserting values at predefined index positions doesn't work with empty
// arrays, we need to reorder the updated selection to match the initial order
return updated.sort((a: any, b: any) => all.indexOf(a) > all.indexOf(b));
};
const deselectValue = (value: any, selected: any) => {
return selected.filter((v: any) => v !== value);
};
const CheckboxesWidget = ({
schema,
label,
id,
disabled,
options,
value,
autofocus,
readonly,
required,
onChange,
onBlur,
onFocus,
}: WidgetProps) => {
const {enumOptions, enumDisabled, inline} = options;
const _onChange = (option: any) => ({
target: {checked},
}: React.ChangeEvent<HTMLInputElement>) => {
const all = (enumOptions as any).map(({value}: any) => value);
if (checked) {
onChange(selectValue(option.value, value, all));
} else {
onChange(deselectValue(option.value, value));
}
};
const _onBlur = ({target: {value}}: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value);
const _onFocus = ({
target: {value},
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);
return (
<>
<Form.Label htmlFor={id}>{label || schema.title}</Form.Label>
<Form.Group>
{(enumOptions as any).map((option: any, index: number) => {
const checked = value.indexOf(option.value) !== -1;
const itemDisabled =
enumDisabled && (enumDisabled as any).indexOf(option.value) != -1;
return inline ? (
<Form key={index}>
<Form.Check
required={required}
inline
className="bg-transparent border-0"
custom
checked={checked}
type={"checkbox"}
id={`${id}_${index}`}
label={option.label}
autoFocus={autofocus && index === 0}
onChange={_onChange(option)}
onBlur={_onBlur}
onFocus={_onFocus}
disabled={disabled || itemDisabled || readonly}
/>
</Form>
) : (
<Form key={index}>
<Form.Check
custom
required={required}
checked={checked}
className="bg-transparent border-0"
type={"checkbox"}
id={`${id}_${index}`}
label={option.label}
autoFocus={autofocus && index === 0}
onChange={_onChange(option)}
onBlur={_onBlur}
onFocus={_onFocus}
disabled={disabled || itemDisabled || readonly}
/>
</Form>
);
})}
</Form.Group>
</>
);
};
export default CheckboxesWidget;

View File

@ -1,2 +0,0 @@
export {default} from "./CheckboxesWidget";
export * from "./CheckboxesWidget";

View File

@ -1,10 +0,0 @@
import React from "react";
import {WidgetProps} from "@rjsf/core";
const ColorWidget = (props: WidgetProps) => {
const {registry} = props;
const {TextWidget} = registry.widgets;
return <TextWidget {...props} type="color"/>;
};
export default ColorWidget;

View File

@ -1,2 +0,0 @@
export {default} from "./ColorWidget";
export * from "./ColorWidget";

View File

@ -1,24 +0,0 @@
import React from "react";
import {utils, WidgetProps} from "@rjsf/core";
const {localToUTC, utcToLocal} = utils;
const DateTimeWidget = (props: WidgetProps) => {
const {registry} = props;
const {TextWidget} = registry.widgets;
const value = utcToLocal(props.value);
const onChange = (value: any) => {
props.onChange(localToUTC(value));
};
return (
<TextWidget
{...props}
type="datetime-local"
value={value}
onChange={onChange}
/>
);
};
export default DateTimeWidget;

View File

@ -1,2 +0,0 @@
export {default} from "./DateTimeWidget";
export * from "./DateTimeWidget";

View File

@ -1,15 +0,0 @@
import React from "react";
import {WidgetProps} from "@rjsf/core";
const DateWidget = (props: WidgetProps) => {
const {registry} = props;
const {TextWidget} = registry.widgets;
return (
<TextWidget
{...props}
type="date"
/>
);
};
export default DateWidget;

View File

@ -1,2 +0,0 @@
export {default} from "./DateWidget";
export * from "./DateWidget";

View File

@ -1,18 +0,0 @@
import React from "react";
import {FieldProps} from "@rjsf/core";
export interface DescriptionFieldProps extends Partial<FieldProps> {
description?: string;
}
const DescriptionField = ({description}: Partial<FieldProps>) => {
if (description) {
return <div>
<div className="mb-3" dangerouslySetInnerHTML={{__html: description}}/>
</div>;
}
return null;
};
export default DescriptionField;

View File

@ -1,2 +0,0 @@
export {default} from "./DescriptionField";
export * from "./DescriptionField";

View File

@ -1,10 +0,0 @@
import React from "react";
import {WidgetProps} from "@rjsf/core";
const EmailWidget = (props: WidgetProps) => {
const {registry} = props;
const {TextWidget} = registry.widgets;
return <TextWidget {...props} type="email"/>;
};
export default EmailWidget;

View File

@ -1,2 +0,0 @@
export {default} from "./EmailWidget";
export * from "./EmailWidget";

View File

@ -1,25 +0,0 @@
import React from "react";
import Card from "react-bootstrap/Card";
import ListGroup from "react-bootstrap/ListGroup";
import {ErrorListProps} from "@rjsf/core";
const ErrorList = ({errors}: ErrorListProps) => (
<Card border="danger" className="mb-4">
<Card.Header className="alert-danger">Errors</Card.Header>
<Card.Body className="p-0">
<ListGroup>
{errors.map((error, i: number) => {
return (
<ListGroup.Item key={i} className="border-0">
<span>{error.stack}</span>
</ListGroup.Item>
);
})}
</ListGroup>
</Card.Body>
</Card>
);
export default ErrorList;

View File

@ -1,2 +0,0 @@
export {default} from "./ErrorList";
export * from "./ErrorList";

View File

@ -1,171 +0,0 @@
import React from "react";
import {FieldTemplateProps} from "@rjsf/core";
import Form from "react-bootstrap/Form";
import ListGroup from "react-bootstrap/ListGroup";
import WrapIfAdditional from "./WrapIfAdditional";
import {OverlayTrigger, Popover} from "react-bootstrap";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faCircleInfo, faCircleExclamation} from "@fortawesome/free-solid-svg-icons";
import _ from "lodash";
const FieldTemplate = (
{
id,
children,
displayLabel,
rawErrors = [],
rawHelp,
rawDescription,
classNames,
disabled,
label,
onDropPropertyClick,
onKeyChange,
readonly,
required,
schema,
uiSchema
}: FieldTemplateProps) => {
let descLabel = (uiSchema.hasOwnProperty("ui:descLabel") && uiSchema["ui:descLabel"] === true)
let descWithChildren
let showLabel = _.endsWith(id, 'IVSHMEM_VM_0_VBDF') || _.endsWith(id, 'IVSHMEM_VM_0_VM_NAME') || (
id.indexOf('vuart_connection') > 0 && (_.endsWith(id, 'vm_name') || _.endsWith(id, 'io_port'))
)
let dlva = uiSchema.hasOwnProperty("ui:descLabelAli") && uiSchema["ui:descLabelAli"] === 'V'
if (displayLabel && rawDescription) {
let desc
const icon = rawErrors.length > 0 ? faCircleExclamation : faCircleInfo;
if (descLabel) {
if (dlva) {
desc = <OverlayTrigger
trigger={["hover", "focus"]}
key="top"
placement="top"
overlay={
<Popover id={`popover-positioned-top`}>
<Popover.Body>
<Form.Text className={rawErrors.length > 0 ? "text-danger" : "text-muted"}
dangerouslySetInnerHTML={{__html: rawDescription}}/>
</Popover.Body>
</Popover>
}>
<div className="mx-2 py-2 row">
<Form.Label
className={(showLabel ? 'col-12 ps-4' : 'd-none') + " col-form-label " + (rawErrors.length > 0 ? "text-danger" : "")}>
{uiSchema["ui:title"] || schema.title || label}
{(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null}
</Form.Label>
</div>
</OverlayTrigger>
descWithChildren = <div className="row">
{desc}
<div className="col-12">
{children}
</div>
</div>
} else {
desc = <OverlayTrigger
trigger={["hover", "focus"]}
key="top"
placement="top"
overlay={
<Popover id={`popover-positioned-top`}>
<Popover.Body>
<Form.Text className={rawErrors.length > 0 ? "text-danger" : "text-muted"}
dangerouslySetInnerHTML={{__html: rawDescription}}/>
</Popover.Body>
</Popover>
}>
<div className="col-4"
style={{marginTop: (uiSchema.hasOwnProperty("ui:descLabelMT") && uiSchema["ui:descLabelMT"] ? '54px' : 'auto')}}>
<Form.Label
className={(showLabel ? 'col-12 ps-4' : 'd-none') + " col-form-label " + (rawErrors.length > 0 ? "text-danger" : "")}>
{uiSchema["ui:title"] || schema.title || label}
{(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null}
</Form.Label>
</div>
</OverlayTrigger>
descWithChildren = <div className="row">
{desc}
<div className="col-8">
{children}
</div>
</div>
}
} else {
desc = <OverlayTrigger
trigger={["hover", "focus"]}
key="top"
placement="top"
overlay={
<Popover id={`popover-positioned-top`}>
<Popover.Body>
<Form.Text className={rawErrors.length > 0 ? "text-danger" : "text-muted"}
dangerouslySetInnerHTML={{__html: rawDescription}}/>
</Popover.Body>
</Popover>
}>
<div className="mx-2 py-2 descInfoBtn">
<FontAwesomeIcon
icon={icon}
color={rawErrors.length > 0 ? "red" : ""}
/>
</div>
</OverlayTrigger>
descWithChildren = <div className="d-flex">
{desc}
<div className="w-100">
{children}
</div>
</div>
}
} else {
descWithChildren = children
}
return (
<WrapIfAdditional
classNames={classNames}
disabled={disabled}
id={id}
label={label}
onDropPropertyClick={onDropPropertyClick}
onKeyChange={onKeyChange}
readonly={readonly}
required={required}
schema={schema}
>
<Form.Group style={uiSchema.hasOwnProperty('ui:style') ? uiSchema['ui:style'] : {}}>
{descWithChildren}
{rawErrors.length > 0 && (
<ListGroup as="ul" className={descLabel ? (dlva ? 'ps-4' : ' col-8 offset-4') : ''}>
{rawErrors.map((error: string) => {
return (
<ListGroup.Item as="li" key={error} className="border-0 m-0 p-0">
<small className="m-0 text-danger">
{error}
</small>
</ListGroup.Item>
);
})}
</ListGroup>
)}
{rawHelp && (
<Form.Text
className={rawErrors.length > 0 ? "text-danger" : "text-muted"}
id={id}>
{rawHelp}
</Form.Text>
)}
</Form.Group>
</WrapIfAdditional>
);
};
export default FieldTemplate;

View File

@ -1,83 +0,0 @@
import React from "react";
import {utils} from "@rjsf/core";
import {JSONSchema7} from "json-schema";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import Form from "react-bootstrap/Form";
import IconButton from "../IconButton/IconButton";
const {ADDITIONAL_PROPERTY_FLAG} = utils;
type WrapIfAdditionalProps = {
children: React.ReactElement;
classNames: string;
disabled: boolean;
id: string;
label: string;
onDropPropertyClick: (index: string) => (event?: any) => void;
onKeyChange: (index: string) => (event?: any) => void;
readonly: boolean;
required: boolean;
schema: JSONSchema7;
};
const WrapIfAdditional = ({
children,
disabled,
id,
label,
onDropPropertyClick,
onKeyChange,
readonly,
required,
schema,
}: WrapIfAdditionalProps) => {
const keyLabel = `${label} Key`; // i18n ?
const additional = schema.hasOwnProperty(ADDITIONAL_PROPERTY_FLAG);
if (!additional) {
return children;
}
const handleBlur = ({target}: React.FocusEvent<HTMLInputElement>) =>
onKeyChange(target.value);
return (
<Row key={`${id}-key`}>
<Col xs={5}>
<Form.Group>
<Form.Label>{keyLabel}</Form.Label>
<Form.Control
required={required}
defaultValue={label}
disabled={disabled || readonly}
id={`${id}-key`}
name={`${id}-key`}
onBlur={!readonly ? handleBlur : undefined}
type="text"
/>
</Form.Group>
</Col>
<Col xs={5}>
{children}
</Col>
<Col xs={2} className="py-4">
<IconButton
// @ts-ignore
block={"true"}
className="w-100"
variant="danger"
icon="remove"
tabIndex={-1}
disabled={disabled || readonly}
onClick={onDropPropertyClick(label)}
/>
</Col>
</Row>
);
};
export default WrapIfAdditional;

View File

@ -1,2 +0,0 @@
export {default} from "./FieldTemplate";
export * from "./FieldTemplate";

View File

@ -1,7 +0,0 @@
import DescriptionField from "../DescriptionField/DescriptionField";
import TitleField from "../TitleField/TitleField";
export default {
DescriptionField,
TitleField,
};

View File

@ -1,2 +0,0 @@
export {default} from "./Fields";
export * from "./Fields";

View File

@ -1,10 +0,0 @@
import React from "react";
import {WidgetProps} from "@rjsf/core";
const FileWidget = (props: WidgetProps) => {
const {registry} = props;
const {TextWidget} = registry.widgets;
return <TextWidget {...props} type="file"/>;
};
export default FileWidget;

View File

@ -1,2 +0,0 @@
export {default} from "./FileWidget";
export * from "./FileWidget";

View File

@ -1,10 +0,0 @@
import {withTheme, FormProps} from "@rjsf/core";
import Theme from "../Theme";
import {StatelessComponent} from "react";
const Form:
| React.ComponentClass<FormProps<any>>
| StatelessComponent<FormProps<any>> = withTheme(Theme);
export default Form;

View File

@ -1,2 +0,0 @@
export {default} from "./Form";
export * from "./Form";

View File

@ -1,33 +0,0 @@
import React from "react";
import Button, {ButtonProps} from "react-bootstrap/Button";
import {IoIosRemove} from "react-icons/io";
import {GrAdd} from "react-icons/gr";
import {AiOutlineArrowUp, AiOutlineArrowDown} from "react-icons/ai";
const mappings: any = {
remove: <IoIosRemove/>,
plus: <GrAdd/>,
"arrow-up": <AiOutlineArrowUp/>,
"arrow-down": <AiOutlineArrowDown/>,
};
type IconButtonProps = ButtonProps & {
icon: string;
variant?: ButtonProps["variant"];
className?: string;
tabIndex?: number;
style?: any;
disabled?: any;
onClick?: any;
};
const IconButton = (props: IconButtonProps) => {
const {icon, className, ...otherProps} = props;
return (
<Button {...otherProps} variant={props.variant || "light"} size="sm">
{mappings[icon]}
</Button>
);
};
export default IconButton;

View File

@ -1,2 +0,0 @@
export {default} from "./IconButton";
export * from "./IconButton";

View File

@ -1,81 +0,0 @@
import React from "react";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import Container from "react-bootstrap/Container";
import {ObjectFieldTemplateProps} from "@rjsf/core";
import {utils} from "@rjsf/core";
import AddButton from "../AddButton/AddButton";
const {canExpand} = utils;
const ObjectFieldTemplate = (
{
DescriptionField,
description,
TitleField,
title,
properties,
required,
uiSchema,
idSchema,
schema,
formData,
onAddClick,
disabled,
readonly,
}: ObjectFieldTemplateProps) => {
let content = properties.map((element: any, index: number) => {
return (
<Col
key={index}
style={{marginBottom: "10px"}}
className={element.hidden ? "d-none" : undefined}
xs={(uiSchema[element.name] && uiSchema[element.name]['ui:grid']) ?
uiSchema[element.name]['ui:grid'] :
12
}>
{element.content}
</Col>
);
})
let expand = canExpand(schema, uiSchema, formData) ? (
<Row>
<Col xs={{offset: 9, span: 3}} className="py-4">
<AddButton
onClick={onAddClick(schema)}
disabled={disabled || readonly}
className="object-property-expand"
/>
</Col>
</Row>
) : null
let container = <Container fluid className="p-0">
<Row>{content}</Row>
{expand}
</Container>
return (
<>
{(uiSchema["ui:title"] || title) && (
<TitleField
id={`${idSchema.$id}-title`}
title={uiSchema["ui:title"] || title}
required={required}
/>
)}
{description && (
<DescriptionField
id={`${idSchema.$id}-description`}
description={description}
/>
)}
{container}
</>
);
};
export default ObjectFieldTemplate;

View File

@ -1,2 +0,0 @@
export {default} from "./ObjectFieldTemplate";
export * from "./ObjectFieldTemplate";

View File

@ -1,55 +0,0 @@
import React from "react";
import Form from "react-bootstrap/Form";
import {WidgetProps} from "@rjsf/core";
const PasswordWidget = ({
id,
required,
readonly,
disabled,
value,
label,
onFocus,
onBlur,
onChange,
options,
autofocus,
schema,
rawErrors = [],
}: WidgetProps) => {
const _onChange = ({
target: {value},
}: React.ChangeEvent<HTMLInputElement>) =>
onChange(value === "" ? options.emptyValue : value);
const _onBlur = ({target: {value}}: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value);
const _onFocus = ({
target: {value},
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);
return (
<Form.Group className="mb-0">
<Form.Label className={rawErrors.length > 0 ? "text-danger" : ""}>
{label || schema.title}
{(label || schema.title) && required ? "*" : null}
</Form.Label>
<Form.Control
id={id}
autoFocus={autofocus}
className={rawErrors.length > 0 ? "is-invalid" : ""}
required={required}
disabled={disabled}
readOnly={readonly}
type="password"
value={value ? value : ""}
onFocus={_onFocus}
onBlur={_onBlur}
onChange={_onChange}
/>
</Form.Group>
);
};
export default PasswordWidget;

View File

@ -1,2 +0,0 @@
export {default} from "./PasswordWidget";
export * from "./PasswordWidget";

View File

@ -1,70 +0,0 @@
import React from "react";
import Form from "react-bootstrap/Form";
import {WidgetProps} from "@rjsf/core";
const RadioWidget = (
{
id,
schema,
options,
value,
required,
disabled,
readonly,
label,
onChange,
onBlur,
onFocus,
uiSchema,
}: WidgetProps) => {
const {enumOptions, enumDisabled} = options;
const _onChange = ({target: {value},}: React.ChangeEvent<HTMLInputElement>) =>
onChange(schema.type == "boolean" ? value !== "false" : value);
const _onBlur = ({target: {value}}: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value);
const _onFocus = ({target: {value},}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);
const inline = Boolean(options && options.inline);
return (
<Form.Group className="mb-0 row">
<Form.Label className="col-sm-4 col-form-label">
{uiSchema["ui:title"] || schema.title || label}
{(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null}
</Form.Label>
<div className="col-sm-8">
{(enumOptions as any).map((option: any, i: number) => {
const itemDisabled =
Array.isArray(enumDisabled) &&
enumDisabled.indexOf(option.value) !== -1;
const checked = option.value == value;
// @ts-ignore
const radio = (
<Form.Check
inline={inline}
label={option.label}
id={option.label}
key={i}
name={id}
type="radio"
disabled={disabled || itemDisabled || readonly}
checked={checked}
required={required}
value={option.value}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
/>
);
return radio;
})}
</div>
</Form.Group>
);
};
export default RadioWidget;

View File

@ -1,2 +0,0 @@
export {default} from "./RadioWidget";
export * from "./RadioWidget";

View File

@ -1,57 +0,0 @@
import React from "react";
import Form from "react-bootstrap/Form";
import {utils} from "@rjsf/core";
import {WidgetProps} from "@rjsf/core";
const {rangeSpec} = utils;
const RangeWidget = ({
value,
readonly,
disabled,
onBlur,
onFocus,
options,
schema,
onChange,
required,
label,
id,
uiSchema,
}: WidgetProps) => {
let sliderProps = {value, label, id, ...rangeSpec(schema)};
const _onChange = ({
target: {value},
}: React.ChangeEvent<HTMLInputElement>) =>
onChange(value === "" ? options.emptyValue : value);
const _onBlur = ({target: {value}}: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value);
const _onFocus = ({
target: {value},
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);
return (
<Form.Group className="mb-0">
<Form.Label>
{uiSchema["ui:title"] || schema.title || label}
{(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null}
</Form.Label>
<Form.Control
type="range"
required={required}
disabled={disabled}
readOnly={readonly}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
{...sliderProps}
/>
<span className="range-view">{value}</span>
</Form.Group>
);
};
export default RangeWidget;

View File

@ -1,2 +0,0 @@
export {default} from "./RangeWidget";
export * from "./RangeWidget";

View File

@ -1,132 +0,0 @@
import React from "react";
import Form from "react-bootstrap/Form";
import {WidgetProps} from "@rjsf/core";
import {utils} from "@rjsf/core";
const {asNumber, guessType} = utils;
const nums = new Set(["number", "integer"]);
/**
* This is a silly limitation in the DOM where option change event values are
* always retrieved as strings.
*/
const processValue = (schema: any, value: any) => {
// "enum" is a reserved word, so only "type" and "items" can be destructured
const {type, items} = schema;
if (value === "") {
return undefined;
} else if (type === "array" && items && nums.has(items.type)) {
return value.map(asNumber);
} else if (type === "boolean") {
return value === "true";
} else if (type === "number") {
return asNumber(value);
}
// If type is undefined, but an enum is present, try and infer the type from
// the enum values
if (schema.enum) {
if (schema.enum.every((x: any) => guessType(x) === "number")) {
return asNumber(value);
} else if (schema.enum.every((x: any) => guessType(x) === "boolean")) {
return value === "true";
}
}
return value;
};
const SelectWidget = (
{
schema,
id,
options,
label,
required,
disabled,
readonly,
value,
multiple,
autofocus,
onChange,
onBlur,
onFocus,
placeholder,
rawErrors = [],
}: WidgetProps) => {
const {enumOptions, enumDisabled} = options;
const emptyValue = multiple ? [] : "";
function getValue(
event: React.FocusEvent | React.ChangeEvent | any,
multiple: Boolean,
) {
if (multiple) {
return [].slice
.call(event.target.options as any)
.filter((o: any) => o.selected)
.map((o: any) => o.value);
} else {
return event.target.value;
}
}
return (
<Form.Group className="row">
<Form.Label className={"col-sm-4 col-form-label " + (rawErrors.length > 0 ? "text-danger" : "")}>
{label || schema.title}
{(label || schema.title) && required ? "*" : null}
</Form.Label>
<div className="col-sm-8">
<Form.Control
as="select"
id={id}
value={typeof value === "undefined" ? emptyValue : value}
required={required}
multiple={multiple}
disabled={disabled}
readOnly={readonly}
autoFocus={autofocus}
className={"form-select " + (rawErrors.length > 0 ? "is-invalid" : "")}
onBlur={
onBlur &&
((event: React.FocusEvent) => {
const newValue = getValue(event, multiple);
onBlur(id, processValue(schema, newValue));
})
}
onFocus={
onFocus &&
((event: React.FocusEvent) => {
const newValue = getValue(event, multiple);
onFocus(id, processValue(schema, newValue));
})
}
onChange={(event: React.ChangeEvent) => {
const newValue = getValue(event, multiple);
onChange(processValue(schema, newValue));
}}>
{!multiple && schema.default === undefined && (
<option value="">{placeholder}</option>
)}
{(enumOptions as any).map(({value, label}: any, i: number) => {
const disabled: any =
Array.isArray(enumDisabled) &&
(enumDisabled as any).indexOf(value) != -1;
return (
<option key={i} id={label} value={value} disabled={disabled}>
{label}
</option>
);
})}
</Form.Control>
</div>
</Form.Group>
);
};
export default SelectWidget;

View File

@ -1,2 +0,0 @@
export {default} from "./SelectWidget";
export * from "./SelectWidget";

View File

@ -1,74 +0,0 @@
import React from "react";
import Form from "react-bootstrap/Form";
import {WidgetProps} from "@rjsf/core";
const TextWidget = (
{
id,
placeholder,
required,
readonly,
disabled,
type,
label,
value,
onChange,
onBlur,
onFocus,
autofocus,
options,
schema,
rawErrors = [],
uiSchema,
}: WidgetProps) => {
const _onChange = ({
target: {value},
}: React.ChangeEvent<HTMLInputElement>) =>
onChange(value === "" ? options.emptyValue : value);
const _onBlur = ({target: {value}}: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value);
const _onFocus = ({
target: {value},
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);
const inputType = (type || schema.type) === "string" ? "text" : `${type || schema.type}`;
// const classNames = [rawErrors.length > 0 ? "is-invalid" : "", type === 'file' ? 'custom-file-label': ""]
return (
<Form.Group className="mb-0 row">
<Form.Label className={"col-sm-4 col-form-label " + (rawErrors.length > 0 ? "text-danger" : "")}>
{uiSchema["ui:title"] || schema.title || label}
{(label || uiSchema["ui:title"] || schema.title) && required ? "*" : null}
</Form.Label>
<div className="col-sm-8">
<Form.Control
id={id}
placeholder={placeholder}
autoFocus={autofocus}
required={required}
disabled={disabled}
readOnly={readonly}
className={rawErrors.length > 0 ? "is-invalid" : ""}
list={schema.examples ? `examples_${id}` : undefined}
type={inputType}
value={value || value === 0 ? value : ""}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
/>
</div>
{schema.examples ? (
<datalist id={`examples_${id}`}>
{(schema.examples as string[])
.concat(schema.default ? ([schema.default] as string[]) : [])
.map((example: any) => {
return <option key={example} value={example}/>;
})}
</datalist>
) : null}
</Form.Group>
);
};
export default TextWidget;

Some files were not shown because too many files have changed in this diff Show More