Skip to content

Commit d6d7f11

Browse files
headriherrdivad
andauthored
Md to react (#6)
* added aggrid * added aggrid # Conflicts: # docs/index.html # profile_manager/index.md * replaced readers with aggrid * added profile table & removed markdown * Delete profile_manager/__pycache__/__init__.cpython-312.pyc * Delete profile_manager/__pycache__/parse_ast.cpython-312.pyc --------- Co-authored-by: David H <113030128+herrdivad@users.noreply.github.com>
1 parent 442e293 commit d6d7f11

7 files changed

Lines changed: 734 additions & 873 deletions

File tree

build/index.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
Chemotion Converter
33
===================
44

5-
6-
Chemotion Converter is a very powerful python file converter running as a stand-alone flask server or included in an ELN and called via API during file upload. For local and offline users, it is also possible to use it as an CLI tool.
75
# Profiles
86

97

docs/index.html

Lines changed: 125 additions & 686 deletions
Large diffs are not rendered by default.

docs/loop_docu.html

Lines changed: 488 additions & 0 deletions
Large diffs are not rendered by default.

profile_manager/__main__.py

Lines changed: 113 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@
66
from importlib.resources import files
77
from pathlib import Path
88

9-
import markdown
109
import pyprojroot
1110
from converter_app.profile_migration.utils.registration import Migrations
1211
from converter_app.validation import validate_profile
1312

14-
from mdutils.mdutils import MdUtils # https://github.com/didix21/mdutils
15-
1613
from profile_manager.parse_ast import read_metadata_from_readercode
1714

1815
program_name = "Chemotion Converter"
@@ -48,13 +45,6 @@ def build_index():
4845
docs_reader_dir = Path(base_path, "docs", "atch", "server", "readers")
4946
os.makedirs(docs_profile_dir, exist_ok=True)
5047
os.makedirs(docs_reader_dir, exist_ok=True)
51-
md_file = MdUtils(file_name='index', title=program_name)
52-
# Additional Markdown syntax...
53-
md_file.new_paragraph(f"{program_name} is a very powerful python file converter "
54-
f"running as a stand-alone flask server or included in an ELN or scientific Repository "
55-
f"(like the {md_file.new_inline_link(link="https://chemotion.net/", text="Chemotion ELN")}, other ELNs we cannot guarantee) "
56-
f"and called via API during file upload. "
57-
f"For local and offline users, it is also possible to use it as an CLI tool.")
5848

5949
reader_dir = files("converter_app") / "readers"
6050

@@ -74,7 +64,6 @@ def build_index():
7464
shutil.copyfileobj(
7565
cast(BinaryIO, source), cast(BinaryIO, dest)
7666
)
77-
reader_link = f"<a href=\"atch/server/readers/{reader.name}\" download>{reader.name}</a>"
7867

7968
reader_entry = {
8069
"class name": my_ast[0],
@@ -83,24 +72,18 @@ def build_index():
8372
"check": my_ast[3].strip() if my_ast[3] else "",
8473
}
8574

86-
readers_dict[reader_link] = reader_entry
75+
readers_dict[reader.name] = reader_entry
8776

8877
except Exception as e:
8978
print(f"Skipping {reader.name}: {e}")
9079
continue
9180

92-
md_file.new_header(level=1, title='Readers')
93-
94-
md_file.new_paragraph("A reader is a python class file handling the translation of your input file format to a usable python object."
95-
" It is created by providing an example file to the developers or python coders and used and defined by the " +
96-
md_file.new_inline_link(link="https://github.com/ComPlat/chemotion-converter-app",
97-
text="converter app backend") + ".")
98-
9981
table_header = ["file name (click to download from this GitHub.io mirror)"]
10082
if reader_entry:
10183
table_header += list(reader_entry.keys())
102-
dict_to_md_table(md_file, table_header, readers_dict)
10384

85+
readers_row_data, readers_column_defs = readers_dict_to_grid_config()
86+
readers_table = dict_to_ag_grid_html(readers_row_data, readers_column_defs, "readers")
10487

10588
for profile in profile_dir.glob("*.json"):
10689
with open(profile, "r") as file:
@@ -130,88 +113,136 @@ def build_index():
130113

131114
# Copy profile JSON to docs and link to the local docs path
132115
shutil.copy2(profile, docs_profile_dir / profile.name)
133-
profile_link = (
134-
f"<a href=\"atch/server/profiles/{profile_id}.json\" download>{profile_id}</a>"
135-
)
136-
profiles_dict[profile_link] = profile_entry
137-
138-
md_file.new_header(level=1, title='Profiles')
139-
140-
md_file.new_paragraph("A profile is JSON file defining a ruleset on how to convert your input file."
141-
" Normally, it is created by uploading an example of your input file to the GUI of the "+
142-
md_file.new_inline_link(link="https://github.com/ComPlat/chemotion-converter-client", text="converter client frontend") + ".")
116+
profiles_dict[profile_id] = profile_entry
143117

144118
table_header = ["id (click to download from this GitHub.io mirror)"] + list(profile_entry.keys())
145119
profiles_sorted = dict(sorted(
146120
profiles_dict.items(),
147121
key=lambda item: (item[1].get("extension") or "").lower(),
148122
))
149-
dict_to_md_table(md_file, table_header, profiles_sorted)
150-
151-
md_file.create_md_file()
123+
profiles_row_data, profiles_column_defs = profiles_dict_to_grid_config()
124+
profiles_table = dict_to_ag_grid_html(profiles_row_data, profiles_column_defs, "profiles")
152125

153126
template_path = Path(__file__).parent.joinpath("index_template.html")
154-
fill_md_into_html(md_file, template_path)
127+
fill_data_into_html(template_path, readers_table, profiles_table)
155128

156129

130+
def readers_dict_to_grid_config():
131+
row_data = [
132+
{"file name": k, **v}
133+
for k, v in readers_dict.items()
134+
]
157135

158-
def fill_md_into_html(md_file: MdUtils, html_file: Path):
136+
column_defs = [
137+
{"field": "file name", "pinned": "left", "cellRenderer": "linkRenderer"},
138+
*[
139+
{
140+
"field": key,
141+
**({"cellRenderer": "codeCellRenderer", "flex": 3} if key == "check" else {})
142+
}
143+
for key in next(iter(readers_dict.values()))
144+
],
145+
]
146+
return row_data, column_defs
147+
148+
def profiles_dict_to_grid_config():
149+
row_data = [
150+
{"id": k, **v}
151+
for k, v in profiles_dict.items()
152+
]
153+
column_defs = [
154+
{"field": "id", "pinned": "left", "cellRenderer": "linkRenderer"},
155+
*[
156+
{
157+
"field": key,
158+
**({"valueFormatter": "value && value.map(v => `${v[0]}: ${v[1]}`).join(', ')"}
159+
if key in ["identifiers", "software", "devices"] else {}
160+
)
161+
}
162+
for key in next(iter(profiles_dict.values()))
163+
],
164+
]
165+
return row_data, column_defs
166+
167+
168+
def dict_to_ag_grid_html(row_data, column_defs, dict_type):
169+
grid_id = f"""{dict_type}Grid"""
170+
171+
return f"""<div id="{grid_id}" class="ag-theme-alpine" style="height: 400px; width: 100%;"></div>
172+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ag-grid-community/styles/ag-grid.css">
173+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ag-grid-community/styles/ag-theme-alpine.css">
174+
<script src="https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js"></script>
175+
176+
<script>
177+
document.addEventListener("DOMContentLoaded", function () {{
178+
179+
function codeCellRenderer(params) {{
180+
if (!params.value) return "";
181+
182+
return `
183+
<code style="
184+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
185+
white-space: pre-wrap;
186+
display: block;
187+
font-size: 12px;
188+
background: #f6f8fa;
189+
padding: 6px 8px;
190+
border-radius: 6px;
191+
line-height: 1.4;
192+
">${{params.value}}</code>
193+
`;
194+
}}
195+
196+
function linkRenderer(params) {{
197+
if (!params.value) return "";
198+
return `<a href="atch/server/{dict_type}/${{params.value}}{'.json' if dict_type == 'profiles' else ''}" download
199+
target="_blank"
200+
rel="noopener"
201+
>
202+
${{params.value}}
203+
</a>`;
204+
}}
205+
206+
const gridOptions = {{
207+
theme: "legacy",
208+
columnDefs: {json.dumps(column_defs)},
209+
rowData: {json.dumps(row_data)},
210+
defaultColDef: {{
211+
flex: 1,
212+
sortable: true,
213+
filter: true,
214+
resizable: true,
215+
wrapText: true,
216+
autoHeight: true
217+
}},
218+
components: {{
219+
codeCellRenderer: codeCellRenderer,
220+
linkRenderer: linkRenderer,
221+
}}
222+
}};
223+
224+
agGrid.createGrid(
225+
document.getElementById("{grid_id}"),
226+
gridOptions
227+
);
228+
}});
229+
</script>
230+
"""
231+
232+
233+
234+
def fill_data_into_html(html_file: Path, readers_table, profiles_table):
159235
with open(html_file, "r") as file:
160236
html_content = file.read()
161-
markdown_content = markdown.markdown(md_file.file_data_text, extensions=["tables", "fenced_code"])
162237
html_content = html_content.replace("{{ PROGRAM_NAME }}", program_name)
163-
html_content = html_content.replace("{{ TABLE_CONTENT }}", markdown_content)
238+
html_content = html_content.replace("{{ READERS_TABLE }}", readers_table)
239+
html_content = html_content.replace("{{ PROFILES_TABLE }}", profiles_table)
164240
base_path = pyprojroot.find_root(pyprojroot.has_dir("build"))
165241
os.makedirs(os.path.join(base_path, "docs"), exist_ok=True)
166242
index_path = Path(base_path, "docs", "index.html")
167243
with open(index_path, "w") as file:
168244
file.write(html_content)
169245

170-
def convert_docs_md_to_html():
171-
docs_dir = Path(__file__).parent.parent.joinpath("docs")
172-
if not docs_dir.exists():
173-
return
174-
for md_file in docs_dir.glob("*.md"):
175-
with open(md_file, "r") as file:
176-
md_text = file.read()
177-
html_body = markdown.markdown(md_text, extensions=["tables", "fenced_code"])
178-
html_title = md_file.stem.replace("_", " ").title()
179-
html_text = (
180-
"<!doctype html>\n"
181-
"<html lang=\"en\">\n"
182-
"<head>\n"
183-
" <meta charset=\"utf-8\">\n"
184-
f" <title>{html_title}</title>\n"
185-
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
186-
" <style>\n"
187-
" body { font-family: Arial, sans-serif; margin: 2rem; line-height: 1.6; }\n"
188-
" pre { overflow-x: auto; }\n"
189-
" code { font-family: \"Courier New\", monospace; }\n"
190-
" table { border-collapse: collapse; }\n"
191-
" th, td { border: 1px solid #ccc; padding: 0.4rem 0.6rem; }\n"
192-
" </style>\n"
193-
"</head>\n"
194-
"<body>\n"
195-
f"{html_body}\n"
196-
"</body>\n"
197-
"</html>\n"
198-
)
199-
html_path = md_file.with_suffix(".html")
200-
with open(html_path, "w") as file:
201-
file.write(html_text)
202-
203-
def dict_to_md_table(md_file, table_header, dict_to_write):
204-
table_content = []
205-
for key in dict_to_write:
206-
row = [key] + [clean_value(value) for value in dict_to_write[key].values()]
207-
table_content.append(row)
208-
table = [table_header] + table_content
209-
# Flatten for Markdown or similar tools
210-
flat_table = [cell for row in table for cell in row]
211-
md_file.new_line()
212-
md_file.new_table(columns=len(table_header), rows=int(len(flat_table) / len(table_header)), text=flat_table)
213-
214-
215246
def validate_profiles():
216247
profile_dir = Path(__file__).parent.parent.joinpath('profiles/public')
217248
for profile in profile_dir.glob("*.json"):
@@ -229,5 +260,4 @@ def migrate_profiles():
229260
if len(sysargs) >= 2:
230261
if sys.argv[1] == 'build_index':
231262
build_index()
232-
convert_docs_md_to_html()
233263
print("EOC reached")

0 commit comments

Comments
 (0)