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>
4
.gitignore
vendored
@ -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
|
||||
|
4
misc/config_tools/MANIFEST.in
Normal file
@ -0,0 +1,4 @@
|
||||
graft data
|
||||
graft schema
|
||||
graft launch_config
|
||||
include configurator/src/assets/schema/scenario.json
|
9
misc/config_tools/build.cmd
Normal 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
|
4
misc/config_tools/configurator/.gitignore
vendored
@ -21,7 +21,3 @@ build
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# TS temp file
|
||||
tauri-plugin.js.map
|
||||
tauri-plugin.js
|
||||
|
@ -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.
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
BIN
misc/config_tools/configurator/public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
27
misc/config_tools/configurator/pyodide/README.md
Normal 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)
|
||||
```
|
1
misc/config_tools/configurator/pyodide/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .pyodide import *
|
@ -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()
|
98
misc/config_tools/configurator/pyodide/loadBoard.py
Normal 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()
|
78
misc/config_tools/configurator/pyodide/loadScenario.py
Normal 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()
|
60
misc/config_tools/configurator/pyodide/pyodide.py
Normal 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()
|
22
misc/config_tools/configurator/pyodide/tests.py
Normal 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()
|
54
misc/config_tools/configurator/pyodide/validateScenario.py
Normal 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()
|
@ -1,7 +1,3 @@
|
||||
lxml
|
||||
xmltodict
|
||||
xmlschema
|
||||
defusedxml
|
||||
build
|
||||
tqdm
|
||||
requests
|
||||
bs4
|
||||
sphinx
|
||||
|
@ -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
|
||||
|
1069
misc/config_tools/configurator/src-tauri/Cargo.lock
generated
@ -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
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
1
misc/config_tools/configurator/src-tauri/tauri.conf.json
Normal 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}}}
|
@ -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,
|
||||
|
@ -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
|
@ -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;
|
||||
}
|
@ -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
|
24
misc/config_tools/configurator/src/App.vue
Normal 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>
|
183
misc/config_tools/configurator/src/assets/css/index.scss
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 299 B After Width: | Height: | Size: 299 B |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@ -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)
|
@ -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%;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export class Banner extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="banner">{this.props.children}</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import './Banner.css'
|
||||
import {Banner} from "./Banner";
|
||||
|
||||
|
||||
export default Banner;
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
import Confirm from "./Confirm";
|
||||
|
||||
export default Confirm
|
@ -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>
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import Footer from "./footer";
|
||||
|
||||
export default Footer
|
21
misc/config_tools/configurator/src/components/HelloWorld.vue
Normal 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>
|
@ -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;
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import './Navbar.css'
|
||||
import {Navbar} from "./Navbar";
|
||||
|
||||
|
||||
export default Navbar;
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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 |
@ -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");
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
pyodide
|
||||
/pyodide.tar.bz2
|
@ -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)
|
||||
}
|
||||
}
|
144
misc/config_tools/configurator/src/lib/acrn.ts
Normal 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
|
@ -1,2 +0,0 @@
|
||||
*.js
|
||||
*.js.map
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./AddButton";
|
||||
export * from "./AddButton";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./ArrayFieldTemplate";
|
||||
export * from "./ArrayFieldTemplate";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./CheckboxWidget";
|
||||
export * from "./CheckboxWidget";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./CheckboxesWidget";
|
||||
export * from "./CheckboxesWidget";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./ColorWidget";
|
||||
export * from "./ColorWidget";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./DateTimeWidget";
|
||||
export * from "./DateTimeWidget";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./DateWidget";
|
||||
export * from "./DateWidget";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./DescriptionField";
|
||||
export * from "./DescriptionField";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./EmailWidget";
|
||||
export * from "./EmailWidget";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./ErrorList";
|
||||
export * from "./ErrorList";
|
@ -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;
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./FieldTemplate";
|
||||
export * from "./FieldTemplate";
|
@ -1,7 +0,0 @@
|
||||
import DescriptionField from "../DescriptionField/DescriptionField";
|
||||
import TitleField from "../TitleField/TitleField";
|
||||
|
||||
export default {
|
||||
DescriptionField,
|
||||
TitleField,
|
||||
};
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./Fields";
|
||||
export * from "./Fields";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./FileWidget";
|
||||
export * from "./FileWidget";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./Form";
|
||||
export * from "./Form";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./IconButton";
|
||||
export * from "./IconButton";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./ObjectFieldTemplate";
|
||||
export * from "./ObjectFieldTemplate";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./PasswordWidget";
|
||||
export * from "./PasswordWidget";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./RadioWidget";
|
||||
export * from "./RadioWidget";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./RangeWidget";
|
||||
export * from "./RangeWidget";
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
export {default} from "./SelectWidget";
|
||||
export * from "./SelectWidget";
|
@ -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;
|