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