rename part to thing
This commit is contained in:
parent
74ed83486b
commit
04690f0f4d
8 changed files with 71 additions and 73 deletions
|
@ -1 +1 @@
|
|||
Subproject commit 22218093a040cbf3ff4f68b7da5ce34435be35e5
|
||||
Subproject commit 436944abb59e999526374875557e76f08346b5de
|
1
flinventory_gui/inventory_io.py
Symbolic link
1
flinventory_gui/inventory_io.py
Symbolic link
|
@ -0,0 +1 @@
|
|||
flinventory/inventory_io.py
|
|
@ -1 +0,0 @@
|
|||
bikepartsigns-data/locations.json
|
|
@ -1 +0,0 @@
|
|||
flinventory/part.py
|
|
@ -1 +0,0 @@
|
|||
flinventory/part_list_io.py
|
|
@ -1 +0,0 @@
|
|||
bikepartsigns-data/parts.json
|
|
@ -1,4 +1,4 @@
|
|||
"""Search in a parts list."""
|
||||
"""Search in a thing list."""
|
||||
import argparse
|
||||
import asyncio
|
||||
import itertools
|
||||
|
@ -9,18 +9,18 @@ import nicegui
|
|||
from nicegui import ui
|
||||
|
||||
import location
|
||||
from part import Part
|
||||
import part_list_io
|
||||
from thing import Thing
|
||||
import inventory_io
|
||||
|
||||
"""Global (module-wide) variables."""
|
||||
gl_options: argparse.Namespace
|
||||
gl_parts: list[Part] = []
|
||||
gl_things: list[Thing] = []
|
||||
|
||||
|
||||
def get_options() -> argparse.Namespace:
|
||||
"""Abuse argparse for collecting file names."""
|
||||
parser = argparse.ArgumentParser()
|
||||
part_list_io.add_file_args(parser)
|
||||
inventory_io.add_file_args(parser)
|
||||
parser.add_argument("--port", "-p",
|
||||
help="Port on which to serve the website.",
|
||||
type=int,
|
||||
|
@ -37,13 +37,13 @@ gl_options = get_options()
|
|||
|
||||
|
||||
def load_data():
|
||||
"""Load data from text files and save to global part list.
|
||||
"""Load data from text files and save to global thing list.
|
||||
|
||||
Could implement that data is only loaded when necessary.
|
||||
Then an argument force would be useful to reload.
|
||||
"""
|
||||
start = time.monotonic()
|
||||
gl_parts[:] = part_list_io.get_parts(gl_options)
|
||||
gl_things[:] = inventory_io.get_things(gl_options)
|
||||
end = time.monotonic()
|
||||
print(f"Data loaded in {end-start} seconds.")
|
||||
ui.notify(f"Data loaded in {end-start} seconds.", position="top", type="positive")
|
||||
|
@ -53,8 +53,8 @@ load_data()
|
|||
|
||||
|
||||
def save_data() -> None:
|
||||
"""Save parts to files."""
|
||||
part_list_io.save_parts(gl_parts, gl_options.input_file, gl_options.locations_file)
|
||||
"""Save things to files."""
|
||||
inventory_io.save_things(gl_things, gl_options.input_file, gl_options.locations_file)
|
||||
ui.notify(
|
||||
message="Data saved",
|
||||
position="top",
|
||||
|
@ -67,15 +67,15 @@ def antilen(string: str):
|
|||
return 1 / len(string) if string else 0
|
||||
|
||||
|
||||
async def find_parts(parts: list[Part], search_string: str, max_number: int = 10) -> Iterable[Part]:
|
||||
"""Gives parts that the user might have searched for.
|
||||
async def find_things(things: list[Thing], search_string: str, max_number: int = 10) -> Iterable[Thing]:
|
||||
"""Gives things that the user might have searched for.
|
||||
|
||||
Args:
|
||||
parts: the list of parts to search in
|
||||
things: the list of things to search in
|
||||
search_string: Input of user
|
||||
max_number: maximum number of returned parts
|
||||
max_number: maximum number of returned things
|
||||
Returns:
|
||||
list of parts that somehow include the search string
|
||||
list of things that somehow include the search string
|
||||
"""
|
||||
fuzzy = re.compile(".*" + ".*".join(search_string) + ".*", flags=re.IGNORECASE)
|
||||
score_categories = ("startswith", "startswithLower", "inLower", "fuzzy")
|
||||
|
@ -105,24 +105,24 @@ async def find_parts(parts: list[Part], search_string: str, max_number: int = 10
|
|||
except ValueError: # if iterable is empty
|
||||
return {category: 0 for category in score_categories}
|
||||
|
||||
def match_score(part):
|
||||
def match_score(thing):
|
||||
"""Return sortable tuple of decreasing importance.
|
||||
|
||||
Good matches have high numbers.
|
||||
"""
|
||||
alt_names = itertools.chain(part.name_alt_de if hasattr(part, "name_alt_de") else [],
|
||||
part.name_alt_en if hasattr(part, "name_alt_en") else [])
|
||||
score_name = match_score_one_string(part.name)
|
||||
alt_names = itertools.chain(thing.name_alt_de if hasattr(thing, "name_alt_de") else [],
|
||||
thing.name_alt_en if hasattr(thing, "name_alt_en") else [])
|
||||
score_name = match_score_one_string(thing.name)
|
||||
score_name_lang = max_scores(tuple(map(match_score_one_string,
|
||||
(part.get("name_en", None),
|
||||
part.get("name_de", None)
|
||||
(thing.get("name_en", None),
|
||||
thing.get("name_de", None)
|
||||
)
|
||||
)
|
||||
))
|
||||
score_name_alt = max_scores(tuple(map(match_score_one_string, alt_names)))
|
||||
score_description = max_scores(tuple(map(match_score_one_string,
|
||||
(part.get("description_en", None),
|
||||
part.get("description_de", None))
|
||||
(thing.get("description_en", None),
|
||||
thing.get("description_de", None))
|
||||
)
|
||||
))
|
||||
return (
|
||||
|
@ -142,58 +142,58 @@ async def find_parts(parts: list[Part], search_string: str, max_number: int = 10
|
|||
score_description['fuzzy']
|
||||
)
|
||||
if search_string:
|
||||
scored_parts = [(part, match_score(part)) for part in parts]
|
||||
scored_things = [(thing, match_score(thing)) for thing in things]
|
||||
return map(lambda pair: pair[0],
|
||||
sorted(
|
||||
filter(lambda pair: any(pair[1]),
|
||||
scored_parts),
|
||||
scored_things),
|
||||
key=lambda pair: pair[1],
|
||||
reverse=True
|
||||
)[:max_number])
|
||||
return []
|
||||
|
||||
|
||||
async def list_parts(ui_element: nicegui.ui.element, parts: Iterable[Part]) -> None:
|
||||
"""Replaces content of ui_element with information about the parts.
|
||||
async def list_things(ui_element: nicegui.ui.element, things: Iterable[Thing]) -> None:
|
||||
"""Replaces content of ui_element with information about the things.
|
||||
|
||||
Args:
|
||||
ui_element: Some UI element that can be changed.
|
||||
parts: parts to be displayed
|
||||
things: things to be displayed
|
||||
"""
|
||||
# gives other searches 10 ms time to abort this display which might take long
|
||||
await asyncio.sleep(0.01)
|
||||
ui_element.clear()
|
||||
with ui_element:
|
||||
for part in parts:
|
||||
for thing in things:
|
||||
card = ui.card()
|
||||
# supplying card and part as default arguments makes it use the current
|
||||
# supplying card and thing as default arguments makes it use the current
|
||||
# value instead of the value at the time of usage
|
||||
|
||||
def change_card(_, c: ui.element=card, p: Part=part):
|
||||
show_part_changer(c, p)
|
||||
def change_card(_, c: ui.element=card, p: Thing=thing):
|
||||
show_thing_changer(c, p)
|
||||
with card:
|
||||
print(f"Create card {id(card)} for {part.name}.")
|
||||
print(f"Create card {id(card)} for {thing.name}.")
|
||||
with ui.row(wrap=False):
|
||||
with ui.column():
|
||||
with ui.row():
|
||||
ui.label(text=part.name)
|
||||
name_en = part.get("name_en", default=None)
|
||||
if name_en and name_en != part.name:
|
||||
ui.label(text=thing.name)
|
||||
name_en = thing.get("name_en", default=None)
|
||||
if name_en and name_en != thing.name:
|
||||
ui.label(text=f"({name_en})").style('font-size: 70%')
|
||||
ui.button("🖉").on_click(change_card)
|
||||
other_names = ", ".join(itertools.chain(
|
||||
part.get("name_alt_de", default=[]),
|
||||
part.get("name_alt_en", default=[])))
|
||||
thing.get("name_alt_de", default=[]),
|
||||
thing.get("name_alt_en", default=[])))
|
||||
if other_names:
|
||||
ui.label(other_names).style('font-size: 70%')
|
||||
for member in ("description_de", "description_en", ""):
|
||||
if hasattr(part, member):
|
||||
ui.markdown(part.get(member)).style('font-size: 70%')
|
||||
if part.where:
|
||||
with ui.label(part.where):
|
||||
ui.tooltip(part.location.long_name)
|
||||
if hasattr(part, "image"):
|
||||
ui.image("/images_landscape/" + part.image).props("width=50%").props(
|
||||
if hasattr(thing, member):
|
||||
ui.markdown(thing.get(member)).style('font-size: 70%')
|
||||
if thing.where:
|
||||
with ui.label(thing.where):
|
||||
ui.tooltip(thing.location.long_name)
|
||||
if hasattr(thing, "image"):
|
||||
ui.image("/images_landscape/" + thing.image).props("width=50%").props(
|
||||
"height=100px").props("fit='scale-down'")
|
||||
|
||||
|
||||
|
@ -201,7 +201,7 @@ async def list_parts(ui_element: nicegui.ui.element, parts: Iterable[Part]) -> N
|
|||
def search_page() -> None:
|
||||
"""Create a NiceGUI page with a search input field and search results.
|
||||
|
||||
Uses global gl_parts list.
|
||||
Uses global gl_things list.
|
||||
"""
|
||||
print("(Re)build search page.")
|
||||
# UI container for the search results.
|
||||
|
@ -210,7 +210,7 @@ def search_page() -> None:
|
|||
# Search queries (max. 1) running. Here to be cancellable by different search coroutines.
|
||||
running_queries: list[asyncio.Task] = []
|
||||
|
||||
# should use the parts as they are when clicked: global variable
|
||||
# should use the things as they are when clicked: global variable
|
||||
ui.button("Save").on_click(lambda click_event_arguments: save_data())
|
||||
|
||||
async def search(event: nicegui.events.ValueChangeEventArguments) -> None:
|
||||
|
@ -229,7 +229,7 @@ def search_page() -> None:
|
|||
except asyncio.exceptions.CancelledError:
|
||||
# the next letter was already typed, do not search and rerender for this query
|
||||
return
|
||||
query = asyncio.create_task(find_parts(gl_parts, event.value))
|
||||
query = asyncio.create_task(find_things(gl_things, event.value))
|
||||
running_queries.append(query)
|
||||
try:
|
||||
start = time.monotonic()
|
||||
|
@ -241,7 +241,7 @@ def search_page() -> None:
|
|||
pass
|
||||
else:
|
||||
if results:
|
||||
display = asyncio.create_task(list_parts(results, response))
|
||||
display = asyncio.create_task(list_things(results, response))
|
||||
running_queries.append(display)
|
||||
try:
|
||||
start = time.monotonic()
|
||||
|
@ -282,38 +282,38 @@ def try_conversion(value: str,
|
|||
return value
|
||||
|
||||
|
||||
def show_part_changer(ui_element: nicegui.ui.element, part: Part) -> None:
|
||||
def show_thing_changer(ui_element: nicegui.ui.element, thing: Thing) -> None:
|
||||
"""Clear content of ui element and instead display editing fields.
|
||||
|
||||
Args:
|
||||
ui_element: the ui element (e.g. a card) on which to show the part changing ui
|
||||
part: the part to change
|
||||
ui_element: the ui element (e.g. a card) on which to show the thing changing ui
|
||||
thing: the thing to change
|
||||
"""
|
||||
input_fields = {}
|
||||
|
||||
def save_value(event, member):
|
||||
"""Copy input field value to part member."""
|
||||
"""Copy input field value to thing member."""
|
||||
if not event.value:
|
||||
del vars(part)[member]
|
||||
del vars(thing)[member]
|
||||
else:
|
||||
vars(part)[member] = event.value
|
||||
vars(thing)[member] = event.value
|
||||
|
||||
def save_sign_value(event, member):
|
||||
"""Copy input field value to sign member."""
|
||||
if not event.value:
|
||||
del vars(part.sign)[member]
|
||||
del vars(thing.sign)[member]
|
||||
else:
|
||||
vars(part.sign)[member] = try_conversion(event.value, (int, float, "bool"))
|
||||
vars(thing.sign)[member] = try_conversion(event.value, (int, float, "bool"))
|
||||
|
||||
def save_list_value(event, member):
|
||||
"""Split input field at '; ' and save ta part member."""
|
||||
"""Split input field at '; ' and save ta thing member."""
|
||||
if not event.value:
|
||||
del vars(part)[member]
|
||||
del vars(thing)[member]
|
||||
else:
|
||||
values = event.value.split(";")
|
||||
values = [value.strip() for value in values]
|
||||
values = [value for value in values if value]
|
||||
vars(part)[member] = values
|
||||
vars(thing)[member] = values
|
||||
|
||||
def update_location_element(location_ui_element, focus: Optional[str] = None):
|
||||
"""List location information with input fields.
|
||||
|
@ -325,8 +325,8 @@ def show_part_changer(ui_element: nicegui.ui.element, part: Part) -> None:
|
|||
"""
|
||||
location_ui_element.clear()
|
||||
with location_ui_element:
|
||||
location_info = part.location.json
|
||||
schema_hierachy = part.location.schema.get_schema_hierachy(dict(iter(part.location)))
|
||||
location_info = thing.location.json
|
||||
schema_hierachy = thing.location.schema.get_schema_hierachy(dict(iter(thing.location)))
|
||||
ui.label(schema_hierachy[0].name)
|
||||
for level in schema_hierachy:
|
||||
try:
|
||||
|
@ -365,10 +365,10 @@ def show_part_changer(ui_element: nicegui.ui.element, part: Part) -> None:
|
|||
except location.InvalidLocation:
|
||||
print("Do not save")
|
||||
else:
|
||||
part.location.set(schema.levelname, value)
|
||||
thing.location.set(schema.levelname, value)
|
||||
update_location_element(location_ui_element, focus=schema.levelname)
|
||||
|
||||
print(f"Try to let edit {part.name} with {id(ui_element)}.")
|
||||
print(f"Try to let edit {thing.name} with {id(ui_element)}.")
|
||||
ui_element.clear()
|
||||
with ui_element:
|
||||
with ui.row():
|
||||
|
@ -376,7 +376,7 @@ def show_part_changer(ui_element: nicegui.ui.element, part: Part) -> None:
|
|||
for member in ("name_de", "name_en", "description_de", "description_en"):
|
||||
input_fields[member] = ui.input(
|
||||
label=member,
|
||||
value=part.get(member, "")
|
||||
value=thing.get(member, "")
|
||||
).props(
|
||||
'autogrow dense'
|
||||
).on_value_change(
|
||||
|
@ -384,7 +384,7 @@ def show_part_changer(ui_element: nicegui.ui.element, part: Part) -> None:
|
|||
for member in ("name_alt_de", "name_alt_en"):
|
||||
input_fields[member] = ui.input(
|
||||
label=member + " (;-seperated):",
|
||||
value="; ".join(part.get(member, []))
|
||||
value="; ".join(thing.get(member, []))
|
||||
).props(
|
||||
'autogrow dense'
|
||||
).on_value_change(
|
||||
|
@ -394,15 +394,15 @@ def show_part_changer(ui_element: nicegui.ui.element, part: Part) -> None:
|
|||
"fontsize_de", "fontsize_en", "location_shift_down"):
|
||||
input_fields[f"sign.{sign_member}"] = ui.input(
|
||||
label="sign: " + sign_member,
|
||||
value=vars(part.sign).get(sign_member, "")
|
||||
value=vars(thing.sign).get(sign_member, "")
|
||||
).props('autogrow dense'
|
||||
).on_value_change(
|
||||
lambda e, m=sign_member: save_sign_value(e, m)
|
||||
)
|
||||
with ui.column() as location_column:
|
||||
update_location_element(location_column)
|
||||
if hasattr(part, "image"):
|
||||
ui.image("/images_landscape/" + part.image).props("width=50%").props(
|
||||
if hasattr(thing, "image"):
|
||||
ui.image("/images_landscape/" + thing.image).props("width=50%").props(
|
||||
"height=100px").props("fit='scale-down'")
|
||||
|
||||
|
||||
|
|
1
flinventory_gui/thing.py
Symbolic link
1
flinventory_gui/thing.py
Symbolic link
|
@ -0,0 +1 @@
|
|||
flinventory/thing.py
|
Loading…
Reference in a new issue