From 9279c239b44a5b93498c012d8e0d6281c9cefae5 Mon Sep 17 00:00:00 2001 From: Norm Rasmussen Date: Mon, 7 Oct 2024 13:31:17 -0400 Subject: [PATCH] Cleaned up the grid that shows a person's db entries. Created basic card for the dashboard page. --- __pycache__/dashboard.cpython-312.pyc | Bin 0 -> 1333 bytes __pycache__/main.cpython-312.pyc | Bin 0 -> 12331 bytes __pycache__/theme.cpython-312.pyc | Bin 1402 -> 1793 bytes dashboard.py | 13 +++ homepage_sandbox.py => homepage.py | 0 main.py | 124 +++++++++++++++----------- memorization_tasks.db | Bin 8192 -> 8192 bytes theme.py | 8 +- 8 files changed, 90 insertions(+), 55 deletions(-) create mode 100644 __pycache__/dashboard.cpython-312.pyc create mode 100644 __pycache__/main.cpython-312.pyc create mode 100644 dashboard.py rename homepage_sandbox.py => homepage.py (100%) diff --git a/__pycache__/dashboard.cpython-312.pyc b/__pycache__/dashboard.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d315b2b6db8e325978e21021deeaa7b4e40ff5c9 GIT binary patch literal 1333 zcmZ`(UuYaf7@yg_y}iBe=Co)|NE$aP(ZyUZxp<|7Ajk!6OB)KQlq3(bw==mv?w@03 zm*fsBEfh4gkUq6O`K+YR;;Rk#B9sehOj%n{^u@Q92!VTY=I+KzHO{i%zxmDY`}Uie z{mC%WfQ{cWiOP=sKmb7(EORZn(36%W$Zgq`y~J_?0tsNL-enU6 zJ&h7ujF}Il+yq-a6D%j2&`SQvy#))#QdDD7cdFR=6r)d&9onsQ9@(^74s3#){;IB0 zndG${JY>B0K-gyope`MS^=t%1_9B2l;}x@KtB-^rRK%KkQdk3xr2F#G^+(XD0ZLfn z+v+DoWS|FKs2hW*u#oaUS%w5+pixp_kIkcs*%!X;IG&V19?PSN+3qgC(6fK{unFD- z(}2P?b|RKomm#=)e_dVzYw$nuvE$=S(2HB?g;<*xyWUFMQ5cxh#i_gVSJC-PWgn!ikfI(C?0ui3smxEiM$%~<>AqL7n}iyvwFH51pt zAKK97$i~Q>l|60zaY{dwLHdoqk|3>bW;e25jsC1nJTWqxS2wQSTG}_pc8sz1>lb&8 zOZ&#+jYPak$UKscy{2uyqVmXyQhH0ZRKhP_tok(vSE5?K%bs2HNrF!#8g>PDyFfC`CoggF zG83KDoEMDLC~jOkF?uMfOJa%h6pp{9IDu1$ZC> KDcdu8;F?q! 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 M0*S9?B31>`--HRg^Z)<= delta 480 zcmZqV`^Ck3nwOW00SGoLGo%|#t(YgksZOipAHp7_R&nRPYe zWJe}xMz+cEOrn$H7#BsW!fcnOer!SJJj;R_oBD|@?VqvwRo6_z_9F9b$iWr@DS$=xs6DcLK1 zhl8u1qm$!;p!#JFjq4l+7dZ?zNMGTwo!rT)Dh!iTyuzXMotJ@2^b6nQU#yCr{6J=h z^sn!H3|zJsIBotEn*uFs(sSWv+~CCJY|MPfN*l;Ntjg+a&wN;m6T~)Vb+%zYY{3p< z+q1fGupeOovM0B)Y07|{yprKFkYp$l1QNeEY;yBcN^?@}iX None: with ui.grid(columns=1).classes('self-center justify-items-center content-center p-2 m-2 rounded-xl'): toggle = ui.toggle(["Hannah", "Fiona", "Liam"], clearable=True, on_change=lambda: show_person(toggle)) - with ui.row().classes('absolute bottom-0 left-0 p-2 m-2'): - with ui.element('q-fab').props('icon=navigation color=green'): - ui.element('q-fab-action').props('icon=add color=blue-5').on('click', lambda: add_verse(toggle)) - ui.element('q-fab-action').props('icon=remove color=red-3').on('click', lambda: remove_verse(toggle)) + with ui.row().classes('fixed bottom-0 left-0 p-2 m-2'): + with ui.element('q-fab').props('icon=navigation color=accent'): + ui.element('q-fab-action').props('icon=add color=secondary').on('click', lambda: add_verse(toggle)) + ui.element('q-fab-action').props('icon=remove color=negative').on('click', lambda: remove_verse(toggle)) with ui.row().style('justify-content: center') as showperson: pass showperson.set_visibility(False) + # FEAT: Allow editing of the verse. Awana will sometimes only give the student part of a verse (i.e 1 John 4:14 is actually 14b). + # FEAT: Find how to pin/bind a verse to a second page @ui.refreshable def show_person(person): @@ -42,20 +44,29 @@ def index_page() -> None: else: showperson.set_visibility(True) #False if showperson.visible else True) # showoptions.set_visibility(True) - with showperson.classes('border-8'): - with ui.grid(columns=5).classes('rounded-md items-center border-8'): + if person.value == "Hannah": + setcolor = "rgba(0, 255, 0, .5)" + elif person.value == "Fiona": + setcolor = "rgba(255, 0, 0, .5)" + else: + setcolor = "rgba(0, 0, 255, .5)" + with showperson.classes(f'border-8').style(f'border: {setcolor} 1px solid;'): + with ui.grid(columns=5).classes('items-center w-full'): + ui.separator().classes('col-span-5 h-2') for item in dbinfo: label_verse = ui.label(text=item[0]).classes('hidden') label_passage = ui.label(text=item[1]).classes('hidden') label_status = ui.label(text=item[2]).classes('hidden') - ui.chip(label_verse.text, icon='ads_click', on_click=lambda: ui.notify(label_passage.text)) + ui.label(label_verse.text).classes('font-bold') + # ui.chip(label_verse.text, icon='ads_click', on_click=lambda: ui.notify(label_passage.text)).classes('border p-1') ui.label(label_passage.text).classes('text-wrap') if label_status.text == '0': - ui.chip("Incomplete", selectable=True, icon="add", color="orange", on_selection_change=lambda: toggle_completion(label_verse.text, person, status=1)) + ui.chip("Incomplete", selectable=True, icon="add", color="negative", on_selection_change=lambda: toggle_completion(label_verse.text, person, status=1)) else: - ui.chip("Complete", selectable=True, icon="add", color="green", on_selection_change=lambda: toggle_completion(label_verse.text, person, status=0)) + ui.chip("Complete", selectable=True, icon="add", color="accent", on_selection_change=lambda: toggle_completion(label_verse.text, person, status=0)) ui.checkbox('Pin Verse')#.bind_visibility_from(card, 'value') ui.checkbox('Mark as Completed')#.bind_value(item[2]) + ui.separator().classes('col-span-5 h-2') else: showperson.clear() @@ -68,6 +79,7 @@ def index_page() -> None: def add_verse(person): + # TODO: Add check of existing verses so that a user can't add duplicates. if person.value is None: ui.notify("Oops! You haven't selected a person") else: @@ -76,55 +88,63 @@ def index_page() -> None: with ui.card().classes('flex-wrap items-center'): result = ui.input(label="Add Verse Here") with ui.row().classes('justify-content-center'): - ui.button().props('push glossy icon=add_circle color=indigo-5').on('click', lambda: submit_verse(result, person)) - ui.button().props('push glossy icon=cancel color=orange-3').on('click', lambda: addverse.close()) + ui.button().props('push glossy icon=add_circle color=secondary').on('click', lambda: submit_verse(result, person)) + ui.button().props('push glossy icon=cancel color=negative').on('click', lambda: addverse.close()) def submit_verse(result, person): - # Verify the format of a Bible Verse - if not re.match(r'((^\d\s\w{1,}\s|^\w{1,}\s)(\d{1,2}:)(\d{1,2}-\d{1,2}|\d{1,2}))', result.value): - ui.notify("Verse was input incorrectly.") - addverse.clear() + # Check if Verse is already in db + dbcheck = cur.execute(f"select * from tasks where person = '{person.value.lower()}' and verse = '{result.value}';") + check_for_verse = dbcheck.fetchone() + if check_for_verse is not None: + ui.notify(f"{result.value} already exists for {person.value}! You can't have duplicates") else: - # Split up the string for later - colon = result.value.split(':') - precolon = colon[0].split(' ')[1].strip() - postcolon = colon[1].strip() - if '-' in postcolon: - start = postcolon.split('-')[0] - end = postcolon.split('-')[1] + # Verify the format of a Bible Verse + if not re.match(r'((^\d\s\w{1,}\s|^\w{1,}\s)(\d{1,2}:)(\d{1,2}-\d{1,2}|\d{1,2}))', result.value): + ui.notify("Verse was input incorrectly.") + addverse.clear() else: - start = postcolon - - # Get Verse Endpoint - url = f"https://api.esv.org/v3/passage/text/?q={result.value}" - response = requests.get(url, headers=HEADERS) - if response.status_code == 200 or response.status_code == 202: - vpass = response.json() - passage = str(vpass["passages"]) - # npassage gets rid of any carriage return characters - npassage = passage.replace('\\n', '').strip() - # xpassage removes Footnotes section, if it exists - if 'Footnotes' in npassage: - xpassage = re.sub(r'Footnotes.*', '', npassage) + # Split up the string for later + colon = result.value.split(':') + precolon = colon[0].split(' ')[1].strip() + postcolon = colon[1].strip() + if '-' in postcolon: + start = postcolon.split('-')[0] + end = postcolon.split('-')[1] else: - xpassage = npassage.replace("(ESV)']", '') - # vpassage grabs the actual verses, dropping the title (starting from first verse in result) - print(xpassage) - vpassage = re.search(fr'\[{start}\].*', xpassage).group(0) - # zpassage - remove any parenthesis or brackets references - zpassage = re.sub(r'\[\d+\]|\(\d+\)|\s{2,}', '', vpassage).strip() - if re.match(r'^\“', zpassage): - zpassage = f'{zpassage}”' + start = postcolon + + # Get Verse Endpoint + url = f"https://api.esv.org/v3/passage/text/?q={result.value}" + response = requests.get(url, headers=HEADERS) + if response.status_code == 200 or response.status_code == 202: + vpass = response.json() + passage = str(vpass["passages"]) + # npassage gets rid of any carriage return characters + npassage = passage.replace('\\n', '').strip() + # xpassage removes Footnotes section, if it exists + if 'Footnotes' in npassage: + xpassage = re.sub(r'Footnotes.*', '', npassage) + else: + xpassage = npassage.replace("(ESV)']", '') + # vpassage grabs the actual verses, dropping the title (starting from first verse in result) + print(xpassage) + vpassage = re.search(fr'\[{start}\].*', xpassage).group(0) + # zpassage - remove any parenthesis or brackets references + zpassage = re.sub(r'\[\d+\]|\(\d+\)|\s{2,}', '', vpassage).strip() + if re.match(r'^\“', zpassage): + zpassage = f'{zpassage}”' + else: + pass + print(zpassage) else: - pass - print(zpassage) - else: - print(response.text) - ui.notify("Uh oh. Something went wrong.") + print(response.text) + ui.notify("Uh oh. Something went wrong.") - ins = cur.execute(f"insert into tasks values('{person.value.lower()}', '{result.value}', '{zpassage}', 0, 0, 'none');") - db.commit() - show_person(person) + ins = cur.execute(f"replace into tasks values('{person.value.lower()}', '{result.value}', '{zpassage}', 0, 0, 'none');") + db.commit() + show_person(person) +def checked_data() -> None: + datavals = [] ui.run(storage_secret='b601785a-855c-41d1-adeb-68f0330d8186') diff --git a/memorization_tasks.db b/memorization_tasks.db index 9f484f4868123afa06f97f0db11c7c28aac383f1..f6738a923c4fa4f9cb324199f6fc3ad213380e42 100644 GIT binary patch delta 74 zcmZp0XmFSy&1f-E#+l!OK`(C?F9QPuBY!Icf9u9V5&p?d{Pprc5eEJ*4BU|*$aNbT Oa!%r$1X4G#;S2!M2^IMO literal 8192 zcmeI1&u`N(6vvZk&}?le9Hy!u)HikkX{%PIO=2gusZ30}u!U%`=QC+&tb2_bQ)gsrBwpMQPd$EowG9KPA-DTPobF-c*~x$n4c zy(urtF)TuIGGdl`1ALpaN*wB$8Q_pUK_NyCCW$v|8{Z_rKmhPSS-~CqQvi!6B zcK++!4{Wf>60ig;0ZYIVummgtOW<|_qq6VSmqy(478ntNumc$rq2T%I=K5|zX$mA# z5>iH?%d(`)fD2vY2{4%{+JwAUJcJI73F%UhA?(Tk zh|n|KT)HzfhDooK;Y+Aua@eCPQ)A~yM`j{G3HYYU2+G}jRQ47c7Y_qU`yPE)TukV%BWuvrGHJuQ==|58LlIMiXaX+qi*;p&axSWp@*<1r}QsVFeC zFbwC4K?i9XXEDl71c{JhNSjyZU(6=d=e0V2%G5nE+8)k%^~Obca=T6oyBwV7)pBtm zmx4@}wvyh|ay9>DO^dWvzNvQ&H`F#&+*WgOccxd|%516r*k4>M&h8UDNc1_v#rQAu nbxHezkAUOb9|Qlyx-#=uYMO4T-uPX-IR&pZUzZB?6PdpNeP(5# diff --git a/theme.py b/theme.py index c6ca66d..ed2d119 100644 --- a/theme.py +++ b/theme.py @@ -5,13 +5,15 @@ from nicegui import ui @contextmanager def frame(navigation_title: str): """Custom page frame to share the same styling and behavior across all pages""" - ui.colors(primary='#6E93D6', secondary='#53B689', accent='#111B1E', positive='#53B689') + ui.colors(primary='#023047', secondary='#219ebc', accent='#8ecae6', positive='#ffb703', negative='#fb8500') with ui.header(): - ui.label('Awana Verse Practice').classes('font-bold') + ui.label('Awana Verse Practice').classes('font-bold font-16') ui.space() ui.space() ui.space() ui.space() - ui.label(navigation_title) + with ui.row(): + ui.link('Admin', '/').classes(replace='text-white') + ui.link('Dashboard', '/dashboard').classes(replace='text-white') with ui.column().classes('absolute-center items-center'): yield