From 3aabe782828084d22912f31a0bb51360dc871f62 Mon Sep 17 00:00:00 2001 From: Kevin Lyda <kevin@lyda.ie> Date: Tue, 26 Nov 2024 19:57:16 +0000 Subject: [PATCH] Get static website working --- README.md | 24 +++++++++ api/boxes.yaml | 12 ++--- database/query.sql | 12 +++++ server/contents.go | 20 +++++-- server/server.go | 16 ++++++ web/files/favicon.ico | Bin 0 -> 1150 bytes web/files/images/icon-192x192.png | Bin 0 -> 4350 bytes web/files/images/icon-512x512.png | Bin 0 -> 12246 bytes web/files/index.html | 87 ++++++++++++++++++++++++++++++ web/files/manifest.json | 22 ++++++++ web/files/serviceworker.js | 19 +++++++ web/web.go | 9 ++++ 12 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 README.md create mode 100644 web/files/favicon.ico create mode 100644 web/files/images/icon-192x192.png create mode 100644 web/files/images/icon-512x512.png create mode 100644 web/files/index.html create mode 100644 web/files/manifest.json create mode 100644 web/files/serviceworker.js create mode 100644 web/web.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..958fba8 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Boxes - a home inventory system + +I live out in the country and have a number of hobbies. Some projects +require certain tools or materials which can take time to acquire. +In an effort to make this easier to organise, I wanted a way to store +information on where I put things over the weeks, months and years it +takes to gain the skills or things I need. + +The model for this app is that contents go in boxes and boxes go in +locations. Contents can be tagged. So you can have a "bird house" +tag for a bird house project and look for where all the things you need +for a bird house. + +## References + + * Simple PWA [tutorial](https://www.geeksforgeeks.org/making-a-simple-pwa-under-5-minutes/) + and also [this tutorial](https://viblo.asia/p/creating-a-basic-progressive-web-app-using-vanillajs-m68Z0RVX5kG) + * App manifest [generator](https://app-manifest.firebaseapp.com/) + * Echo [embed resources](https://echo.labstack.com/docs/cookbook/embed-resources) + * [Mithril](https://mithril.js.org/) + * Pure Go [database/sql](https://pkg.go.dev/database/sql) implementation + of a [sqlite driver](https://pkg.go.dev/modernc.org/sqlite) + * [sqlc docs](https://docs.sqlc.dev/en/latest/index.html) + * [sqlite docs](https://www.sqlite.org/docs.html) diff --git a/api/boxes.yaml b/api/boxes.yaml index 1f95a39..3a3be92 100644 --- a/api/boxes.yaml +++ b/api/boxes.yaml @@ -15,7 +15,7 @@ servers: - url: https://localhost:8080/ paths: - /location/{lid}: + /api/location/{lid}: get: summary: Location description: Get a location. @@ -89,7 +89,7 @@ paths: '404': $ref: '#/components/responses/Error' - /locations: + /api/locations: get: summary: Locations description: Get a list of locations. @@ -104,7 +104,7 @@ paths: '500': $ref: '#/components/responses/Error' - /box/{bid}: + /api/box/{bid}: get: summary: Get a box description: | @@ -180,7 +180,7 @@ paths: '404': $ref: '#/components/responses/Error' - /boxes: + /api/boxes: get: summary: Boxes description: Get a list of boxes. @@ -196,7 +196,7 @@ paths: '500': $ref: '#/components/responses/Error' - /content/{cid}: + /api/content/{cid}: get: summary: Content description: Get a content. @@ -270,7 +270,7 @@ paths: '404': $ref: '#/components/responses/Error' - /contents: + /api/contents: get: summary: Contents description: Get a list of contents. diff --git a/database/query.sql b/database/query.sql index 4e8da7c..ac8dc0b 100644 --- a/database/query.sql +++ b/database/query.sql @@ -82,4 +82,16 @@ SELECT * FROM contents; SELECT * FROM contents WHERE description LIKE ?; +-- name: GetContentsWithBox :many +SELECT * FROM contents +WHERE box = ?; + +-- name: GetContentsWithLocation :many +SELECT c.* FROM contents AS c, boxes AS b +WHERE c.box = b.id AND b.location = ?; + +-- name: GetContentsWithTag :many +SELECT * FROM contents +WHERE tags LIKE ?; + -- vim:et diff --git a/server/contents.go b/server/contents.go index f7cb4a2..4e4024e 100644 --- a/server/contents.go +++ b/server/contents.go @@ -135,10 +135,24 @@ func (ep *Endpoints) DeleteContent(ctx context.Context, req api.DeleteContentReq func (ep *Endpoints) GetContents(ctx context.Context, req api.GetContentsRequestObject) (api.GetContentsResponseObject, error) { var contents []store.Content var err error - if req.Params.Filter == nil { - contents, err = ep.db.GetContents(ctx) - } else { + if req.Params.Filter != nil { contents, err = ep.db.GetContentsWithFilter(ctx, fmt.Sprintf("%%%s%%", *req.Params.Filter)) + } else if req.Params.Box == nil { + contents, err = ep.db.GetContentsWithBox(ctx, *req.Params.Box) + } else if req.Params.Location == nil { + contents, err = ep.db.GetContentsWithLocation(ctx, *req.Params.Location) + } else if req.Params.Tags == nil { + if len(*req.Params.Tags) == 1 { + contents, err = ep.db.GetContentsWithTag(ctx, sql.NullString{ + String: fmt.Sprintf("%%,%s,%%", (*req.Params.Tags)[0]), + Valid: true, + }) + } else { + // TODO: figure out more than one tag. + contents, err = ep.db.GetContents(ctx) + } + } else { + contents, err = ep.db.GetContents(ctx) } if err != nil { log.Printf("error: %+v", err) diff --git a/server/server.go b/server/server.go index 1995215..1673dcb 100644 --- a/server/server.go +++ b/server/server.go @@ -5,12 +5,16 @@ import ( "context" "database/sql" "fmt" + "io/fs" + "mime" + "net/http" // The sqlite db and migration driver. _ "github.com/golang-migrate/migrate/v4/database/sqlite" "git.lyda.ie/kevin/boxes/api" "git.lyda.ie/kevin/boxes/database" + "git.lyda.ie/kevin/boxes/web" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/source/iofs" "github.com/labstack/echo/v4" @@ -19,6 +23,10 @@ import ( "modernc.org/sqlite" ) +func init() { + mime.AddExtensionType(".ico", "image/vnd.microsoft.icon") +} + // Run starts the metrics and API servers. func Run(cmd *cobra.Command, _ []string) { // Get configuration and basic pieces. @@ -55,6 +63,14 @@ func Run(cmd *cobra.Command, _ []string) { db, err := sql.Open("sqlite", dbfile) cobra.CheckErr(err) + // Get the static files served. + filesTree, err := fs.Sub(web.Files, "files") + cobra.CheckErr(err) + filesHandler := http.FileServer(http.FS(filesTree)) + e.GET("/", echo.WrapHandler(filesHandler)) + e.GET("/*.*", echo.WrapHandler(filesHandler)) + //e.GET("/images/*", echo.WrapHandler(http.StripPrefix("/images/", filesHandler))) + // Get the endpoint handler set up. endpoints := NewEndpoints(db) server := api.NewStrictHandler(endpoints, nil) diff --git a/web/files/favicon.ico b/web/files/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6d1f201f8a6cbfac967b665da57701b99dd3e183 GIT binary patch literal 1150 zcmZQzU}Ruq5D);-3Je)63=Con3=A3!3=9Gc3=9ek5OD?&U}0c5%m=|BE{!oK0|SFL z0|SF50|NsG33l-?Ffe#CFfgoRU|=}Lz`$^Xfq`KS0|SE>0|Ns{4wq5f3=9mB3=9mr z7#JA7GB7awXJBBs#=yXE1<L-yz`(GBfq@}{fq?;}7Tp{>1_p*tP__S{G_rn}7)XsH zx@HCj1{VefhHnfE3=bI?7`8GnFdS!KV7SP@z;J<qf#Eb%>;VG<!*>P-1`jOy%^4UN zHbUM1l!1Za1Oo%ZL<R<iDGUq@rx+L*Kw==jZDL?xutd_&3=QL61_p*F3=9k~cY^4> z3=9k)3}S=A14M(&>0@AE&|+X<0O^%vU|`q|^)E=xUj_z-+YAg0tqcqd`cMpFgTz2` zAT=Ni(gV^9a*7c&u0Z}PVqjp92PZp_3<NVWFfhn5Fff4R?nBjr^gv`87^I;728CT2 z0|SF17X2WxQfT;s!U3cQq#u;FrX#r@<j1=V3=EwN3=D=)+`+)W01^Yqf!q$lAUz<x zObiSRdJGH<*wld19|(iggD^-x$Q+Q_ps)ey1-Su)L4H(XU|=X`U|_h!z`$^sfq}sq zia~4;2FWR-`3<BO9fSM^QU?<Uu|Z-mKGo>|{}~wm*fTJE;Adbsz>JDvmM}IjFf=y8 Lcn~!p^&q_fg86SB literal 0 HcmV?d00001 diff --git a/web/files/images/icon-192x192.png b/web/files/images/icon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..dd77ff709ad3aa6362a6071593671b00fee0f357 GIT binary patch literal 4350 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 HP6<r_{_p$K literal 0 HcmV?d00001 diff --git a/web/files/images/icon-512x512.png b/web/files/images/icon-512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..5c2e10f35194116c39ea9f82f0a254ad987e7f5f GIT binary patch literal 12246 zcmeAS@N?(olHy`uVBq!ia0y~yU}6Aa4mJh`hA$OYelajGa29w(7BevL9R^{><M}I6 z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!y_VDNNt45^s&_HN}K z+2^tMKlYd1oc6Rfz9c33p2+Pk+4VtY0s>Bl@`M)nat5@UE)y^i(O`?pnlwdiVXMU+ z1;q&>bEY^6Uus&==n|I{UARYoQ_kt`JttPD+`P5swtbycNonc(tC4BXD$|~QuG_m$ z|NrC{HtFZi*zC9dtfSyLNoBC3wMN>d%qlCHj2jM@{Vz{{w9R9ZRD#$Cy$9L{!V;K2 zSU%9@yuIY#Sr$768=f9U>xR%H`LE<{CM~Iz;rL^+q3?x-+Ue(I|KCqp&fKVP`1Sqv zz8}4Rc~w1SIAdJ@EVU0kZfF<JD5jFRa#^v$hyE|$rSn+UG4^?13i1pJ>Tc|3ihi;F z@&=9**A?de|B=IL$2%vmezK}(X|&s&FXaXm?TLaD<|kVJ|5(ZUgQ>vtZ0>ZGs|l=y zljWB#-oSCfTXOFIBWu|1d7hD*pkjJO=0VW|u~TnVbeJx!Ui4XhzIB6kLuF~LqUWU- zVhrI-uUD>X-oW9p{MEVont3ei*!b2y?)IG2R%}wZxvsCuioLtx*VUUp`_FqHS#VK! z)&H(ZOX}u&-I)`=@L%6Sr3H2?t^R+!>BggEyZh05EzeoUSdW~oP|vhx?QVGW^uo`6 zf5YO0%Bk^DleQe^Nt*Ia>ry$B7~|IyYd_DQ5#9LxLD1nzA)Pyq_<qZ~bpL}c)2qqV z&+YB9I}a{r4=GYnS((hImba>Z;y<>0!GQH@rT-p(HZhd=`r%T4hf3*zjYoXHuwJ@< zL6_;(^(*J<Ys`e3gZ;IB-tY+0)>X`#wSLk+wmiWJZqgtJ`tgL-#?EB-oOEkZ#Hw=f zm-#ou7+-Dyxgu$0qUWnEGmo78z;@~W3|*#6*&rvxtFvuAr}y-PXOQz5<-EG@lm5Bo z2u^6%135v!@Wj^x7x@>eJk3@t++5xC#XPRNp{8W(XZiW*9ggeWd&)FaHT7+i67HW~ zvj5^Uq3(u+=ljdI2{E(z%RgP<6_oW{(0sG(lKmT>%~&VSSoSY?c|_KaMGO1asi<D~ zCipjX*=pB@x%K}w)A-Ivyz=l4I@%@pH(vQK@8-;xQGyB2?PK!P+0LJn{4>Ej=qDf3 zItDp~zr34HzVrggRmm}~W8#y1bHO{vvQNoP*WUY=bW-%CZ%PNw?Z30fg=d{@-2{2Z zNo}QEg_mzS{gO_QzI+WNw@bvB{oI@1*C#At+#~1y<@(fZqKsw#zAl+zS&&(8->u@i zUgF~XU(c>>-LjNp!)JftbUqKMQ!+_gKF(dTe`2YnuRhb8KbM~~CVf5o^Y_oSDy4T0 z9y$9c@Y4MqK1)`Dd=ppMC9$RYhROb!OZcqXc39f@|B{}1)7)2^>CK<dXVsDf-ggH7 ztD0o>(Bx6t2d$U+K54&9KsM#OZ4+#+n(p_{*eh!_cSm=<`pbNmW&fYVvE2A+uD`p( zai6yOPg~WkE*nGMXI;8qa^}OytsEOZ=hs~mXs%CJTq*iYNc^kqlKlr~6itd2On82O zos9<%n;nB^@0JNWI^@;=@*bTsKc<Vp`v13QnMnm79(rW8BzJayW4(0W!Dp*yEX$3b z<;t@=4<1ri*7SZZ!2EmL<bQ36ue?`*0<Bten$$i4&q$4FukO13l5X*wysMkRy8dy{ z;UJ}>9si^~>y-^Em#kAdaPEH3`P?H4k8gUcx?=MvX?On@@qH)tLqU<Bw#vqb$I4E? zbE%ck4z3#xzqs!{nY*fs!MgskzZuhYhU1EvD&3tIw?})_C(eGh<PFF$o>^;;EO=r( z!DK?}k)xZNzld+jOgp8;^ybf54b3#Zn5HF}qRPL5<F_o6l>`NbdFrv5%$Hb>cKn;| zS>HJ8Tj~vvAt6((I}V;Ho?w;5XqZ)I`%>TI@;ja=mK#53TYXa5nDug@hisuq;U#<b zU)&c;)c=Yxmi=4yZjTF3nH}TGrGBb@pTa%r56;X>t9NafyWewW?vV%2H&2LqRP$)b zJ<ng<XJ)Jf`C+nm*G%Tq6QwfP3^(M9zSLJsR!d6~On9DObaw5LhJ%qFGiQ6xSyMjo z-yy@aT_PZpGM%moH810LSXyY8X357`$5dfoQ1fV;$KPL{-u>TanyJ0yUoL|kTg@B^ zn-zi)3_Z^x7CqiCw0x6CP;4CAKb9W`A0i%YGnvMEpkNw<J#!4>8UH(Cp08HP9*Ad< zSBcx3#URGe9O?Mx)OXp6H!7Oy*O~WwpHT>qe{<wpzS_ly|G5e>K_&gNa_J9(4>lk9 zlI?qtDIx1~L%E>r|Cyb)l{}@+I3H;^Xs;xeaG6Dn;dd0r9`PTlG8gqdeYXq#V1Dqu zQ}xEy*{lcTx*O)JxP6tMv_p5&lA>HjJLfYp5wmybMs8qe_}mcBP$w{BE=VftEz>@h zKV}<wtJ6z&G8DM%S1FA)EPl{^q^o)ErLBGIB`*HYKHv(n_|)OI2i{-YIFmV6lYx(^ zPT~i{#*Tg!PvN!f|0eY5nBMuu^g!dm^3KrTeI9qjJcEpLnCx8l$laK*P4jAIHe&(T zM!Wz0!n@~sPBK}Md!Y2ddxr0-dEPH?g)zji{89R__sH)$&&kyjE3R-F{=Is3&61UG znf`J85Rjd3IbpHrkzeOmeqUpz_T7dv=IlM$e@u@$Z~o%BmG1K{l6TMKZ+{u@Y7{qo zXDFZauf;&^`yIzOahCln&ob(<=bc=z_t=wp>>Y=0$|h!q{Mq_R<E1}Ca?$w>^=p5G zoSpH{=w`6#)Hf236zu(f$={hV=d;ws%J@SsrZLV}`xfi}cW=*0%h?V6OzMj+&2f{t z67KPzS$cBuHlev+C8y*Y7Ru(Gd{8Ylxp<r4^@l+wTev%#cTE0bzrxV+x$B~0yS`Vl zo;IDgBQBR4FI#!j)uuDL^Ow=&iuj$gzSz$*-1$5)$>xmXm3%ovj_>~%Z)N+07i$;p zJ>xX_7vqDtOaB9oJ(*{EVE4)7@276ML|>cmZ->O>>UWI$WZypB?(zSy#N_IKeS759 z*Qwl^@ykMTa&ehZ{Rg$_hn?6?H?E)L9=+tw8<lBFe+54D9?5-q-6+ZRoV-H+a*?3C zI0d%Xc|JEgWTu_|>-Xl*(#|+Fwr_LY-lW`|a!!8HTKhBeByIGU=k`sRd$?n7)}&Jl z8D>o0#Tevf#<ZXFOT&ESY0q<4eLYfck@&qndiUyCCM}bL%@x|29yDIzonW*<><8zg zWd)`mVlG{qvN}THX~-k%b-uS{G`Y_&&vW2oJzy`yth_|;m(+*92WB4FdL+p%ck9$2 zed$8?e`aUSR#q*Ie#Q3W)4RX<joX*!wS-JIeBgcLz`lCFJJYV*^Wl-GvHp3df5|Fa zHXX$<ULUpT-0xT(sKq!(zTGDKL+3-{gLg-APo52Wz<hw8u~>M!>#9qS4{R5DeNSbo zHzR}Li<hT&|JUAnGVQ@+m*f8<9&LN_PU3-j<9A`@rAy5!Wh%5kB)n>Kh+}1lU|A76 zkIl}Hr=eQ0FnZRrga_`O%bDyXL!?u#{&cPbEBGnL@=yA${FU(bokxNW?{C??dgabd zum32XXJXJ{y5f`F(9bxZG0ywWv~4pP_cT|SPWW7O;*v{b*}t8y1$WO~8n^xCjF+FJ zHa>k`6f*sD;p&3RJvMG@I#w!v*}!2?nI?Uab8c&p<y49Ik6bU$vnQ*m{XS=ZJ)Gh3 z*AJx)pEh#-5c%NvpmU4w#XNUrd)7ZX1r?9Bd0d!q{GYD|!vQ6Sm$t4vuYPlUNP4s_ zqlmjfp1E!w&$Y<#bkjwj^JmHJ<1_gBobQ3{j?B=>XISoW-tqbM`FM%;P5W>8o8-h8 zm(5$FW;Jn{jq2qmVFJx2!Snr>{oVL^y(51&L$Gg`;NR&Bwne(}+`8lV>+_i{`epx2 zUnL4AoH`x0sMvye>)oWJX9vVC%`aajyYaLCf_B#iNnPp2?1sky*JU_kSaTHqR%XnS zw*KEz6~Qv&;AevZ)dGQ6yDE<;od4}vA2%!Q=sA04TOB5!`klQyZk~6$1uBcwF3n%A z_W#Du^d;@C4KuC!UR+n`n#sJ|qyF8jKS|H$FO(BwO!My&{41`+`#dJ8;6>S``RCR8 zZ~RPO)9&hUee+C-r+Yb8${c457uI%IX1CvW*Nva%4*cB>de0;usnxOsnTiQDyUVNp z{h9ZwcW!<2mkk^*ysinFKV_cq%5b`bPZj^m^Yy{CZ~jQHXm@p(y}4}%=Xocu#WO4m zY+tTt5B7cYM;c_&>`mMeu6Ih7&RN5-j=5~oKP$thN6zhMt%_hVvAx}K@rL)rPi0bu z9A85(#ha)7xbbtl2Y+{iTAFa7v2Eu3_>fb#4Ua6i^4zmtcILsP=l7j#b(m&NTHZ1H zdE(?heL<6De0YLtd%j$sdb9q`pJ<SM=OVrD=sfrT_}kMr^o-1o?l0G8&a^4}Cwt+r zlEU^iY<kB9|7Pu%Jm8fy_0*N?b8iY;|L1#|C^*6DXwcs2zW;+g*OuBD?zlRWIWzhB zeMegzrl2Jf7wfOp)hn))_ETirz0c{_>&@94Kfm7~C&pO%RP~Wsxs$zp&lN{^;pX`H z%74G!YQ6FEw9<k{HlBB`@V$Nie1cUzqoG8Z?aTG|w;ekd|9?@ttHaHmb0xmoEdM{> z@sh>Spu^^q|5fD$zxlJZUrvlMHN~{>@+`v(7pI%d6+N=xX6%yv$7ah|*ZZz)cRgUB z>Gmbw=!4*NlaACQ3b$mJ?0+{~%)0(<IDdCT-R7w~B<C8QX`E)VMb2<V8TZTg%)T9S z_y4+fSSevw4yYF+{pp97*71o4LJzQAx-VYTJ9j@DNHR%I$xU6};!on#kjBa*3vU1R zsGomAv+SQ#6n}Sv;RCZr&y1><EY@6^b@WW5v}gT&4_WK_-Zk#!-3;zuyw6^JI+MBV zpO$a`I}ygC9sjsbTNF&1WZf>YX7}7L-^H!#O@sQIII1|C(PUb08!>C=d$&~xG* z_4_w|4uAY5BVO=>Nnx<Xo-K=2G}E~|5ANxo{O@1#^Yv~oFZL-V$QBA)EV!~*d8Otv zq2}uMihuu}I2Yf0aq&*q15%F`%@6(NIPr?d(KC&&J?rZ?eGXUtlHn(~fjh!A=E)?J zsiFr;5BOcWzx~ahsaxeL#TjpRT-@)i{p;VPTQ5zL0zRx>vcLc4Pwi;iPufgog5vKx zuh!;z$`<e#di<!obpQIBKU0^=Rf;hB3yQx^46TdwoLg*QnDHa<(*5Ue{!E=K_p+;D z>ye|D`(Np6uDskeles+cx&GUw?t5JiWIZTK5?QZuHI;3{qzKb1=i+azb>HghurcI) zs%fpf=d5!-k0d<SO6R-rbG4x#d)&f1+z&z@L?w9k-gln#Yir96_o=qU4@4i$(@M=( z{9ryMp0QrYV&Ad%ik{6@^LB(!t^NO7`q4eD)NIZI^Q*RO#c}(W$Xwl+cYyi8&C*)M zOo9C6`(n412|i{ByY}g<N@kIGz~A5hKi*`i@z=fY^Xf3q1FfWjI9c%nM|N!6zr;$k znthGhS=)T^0^W0VK}p;V=O;f?nlXdDu-xAB>dD;i2lScRLw@xx)tuwD>!`u9)0dBA zHMEC+JF8lH(&<ru_i@kq`q?vnH*Q|_`_-j=`{w?+c5L2^vqu)pnXGNaHtE*eh)4aM zyN;Rh&h|Y}9$w6`#5QREdwI2^haAPj3T#xip0_-5VBgy%`|H0-A7@<=#NBXy;y)L8 z)zzH`ttXjW<a*TK8oOkFIPYxz2Rj(1zA<^=cWM5e)Td@i2It=x@lRTEOw;YleUD$i zgCckGTWmhlyyRV#^w0h8<<0m7o2&VB-e!1aHB}ywXZ$|#pIwM+9MitlTNW=pr+M%D z^{oe7PvxlGn=c!FYU1Q6tCc%{-(tITzkF5SV<wX+tOrV$*f+jD7nby)Upn98?Iq<j z*1bNze*4`0d4ETL!><yp0``~wx2Av^^)`<-PMLDeXwi!Mj=z3;ox0Qgm}!greKs5S zUz1Ijix{(~^%TUYUR}+r{Oj~N?*rw1MFCgi!V{+2#OA59@t&3Z#^Ajw#BkC7v`hE( zi_ZEb@wC)m;Ar@HVE&|kM{>N(1Xed&UJpBUm?_EZyz-L$_9m+JoH8ayHimW2VY%b= zYx2g<%1()pl0ygFV}m67l)4Jzqxq)aZVYC#eUa5}E-d|gi5=(cdAUavl+B+^cvZ+1 zShL^h*X#W+EBSt0JIJ?x*17M0pDv$aS#XnKfAf-mbNxkb$41Ova?V0;f$}rv`AUB| zuYX&^rpKQ4_3LVnS*LgsR0Cs{?^}0!-u?rHjB87U3b<eTZ_-QEHMGdAli%OJ#P*rc z=Dc@Ge@pKytznM1bNu~cwo2iB@BL>k?>Oky{`PtKq+Rb47G=~;`Eors{Ikvn-QT|# zUf~J9u%>qH?K!?j9xR=_?C;Sf?=0IiCf(lkG2(Ji4|jm`S#~?mU&1Hf%uafC;F4y& z?Mi7s<*rBJp7pV_YCqc-Y`=2MQZW2*mQxwy>W-_Ym*_=0=2$%I`1hw+x{o`f$6V<B zoZ$H;X&crkve{Y|JLHDyT&=c!x!&A3VxDSRvPSa0_xW|#1e+J|Ib^jbx%{_XvVY&1 z4}~!f%bqdTJN*(qGG%&thht#7vgu`~MKfyWe7QbZQ-A)^LdKgGf<L5Q`f~;Qnh7^A z5mPihT<G%gm(t7i^1;4&%vBmIMStwuuMO^rYWhDH+8kZOd$Tu(WuK(#!{7hjrI{Me zIJI^6)Mina!d=~8uD7i;w=RfrSo@5z-shM6y(Q;!k33j))8ng_+n4(;zg{0$a(@2f z15Z!QZ4jSx)0|(pdF`DO&Y`p2Mdp5={BMtW<<r*|VJnSJH>!L7SDsz7S#U?2M2qUw zmmZG--?Lqs|6J{U4$D5ysjs+_e%`oZzb?~ln^5z{a}H8xlaj<viCvn1eVJrojDzPh z#(I}u@>fiZkDh7tp1`)bM?<N6!ap0cvuEZ%+|jU5mj8~&FZqjGl+Vco*mnv3U3otI za^ISRg^bM6eeZAinC@(rIAY4KDWd%AuEUv-iRlg77w!A8@BWpE>YZ0_@wvusZJ1%Z zY2KITi!;k*#5c%)Zt$P*uifbGDeof+>xBctWqd{I&rkgK$N284<%KZ^>}u}k2c=6F z7S3zE^(ti1jq27f&u72PyT+id%%FGp`(L&BBF5~~7H_kduko&I(!W1OX+09+AH=NA zegCWW{Eioo#GKYyX^`&idF5oa(|iy9mR!h5I=bzOeS_iO6kWrJ9J^C@V-{uX?*8(; z*YNL?s7HI=wk&SYpZsruv~k8fCANOqwxVK#z$LbBzdj$C;aF_TIn7{wb>NiEjr)%a zUeDU*>vHv9;DgR1xi5DKcl7<QJ#gqq?nJkbEFWYKye+Lwn9F*=@c{FYj{23n+nDFG z+VR|(yF)j2v&avI56&eoKCX;<Z~4II!A&c>oHSYaO}G1#pRX^Q$^D0;K;5wTfv;!u zW!`+HzOQqSY~21ie6|eZpORSluyn=`+obHhS6$|LAbY@`*_`pd``+tex6i8dz4Sd| zSjYbE<$`Zj-m&r_&wW>gKTh{t8N*W}x1+y8|LvK^hiBFM9@ZW)+-s3ryd>(1nZ$;3 zObNxk^HrB_WBFn6LBp{4LC2D{nbTU29N1TS#4u*kHp`&3acf>TztUmY7xFmWbEgH@ z58elY50rP9z9<nBJpQ8d?*Gy=6L!7$f90!V={|SsDG|&+r%2fcpE~RJK)-V)^L>vy z(_(a#-M;Maj!^M=wTiuwd*w?_hr7&~=NR^J%nAGa+{^M;^#kVv_Dk|Sl(ZY;S@xNh zIxpc_qk4@oV)ib@P%D-nxk)|_udYt-IjNZ4P=2|0e_gup{ZihS{ksh<Q;aXF%Q>~n zIKE+jaBa>p7dbVy{jx5<&nRDev48%T<2Pr>Z0d~&{1RdHCS#Gn4X<DFM`p-;QrdW? z;B9tu_ydn5u_K$8*snYGWWrmQ<L@W^cs$2PW{T{R|BXE-6{9<A-<)4iDJJZCM(mQj zdvei<>;t71Y<W@XLgsttF8P13=cM9#!PEESoc>J{yuQA5kKx4FBVViaUfm8_6m`q> zm;8kpF`J|+)PL%>?rz#~a;cr&u_seP5B$u?5P#fzbVlb+<{eF2ym`-qy5fp|KN_el zUvup2L9eYcKa5`1M<f@WntViIxAV&LOi9}g{hG9|*)aEH?*a40r`8<VZ_zVz#`F%y z^3XmVyIC4$^G-kbEj2m#oyxqnm2Acx-xu6h_{*Q~<9E}I=aBX6PjkFP#Mj+SJacbn zd&K=iZ}$t&@C=FNpYYGcKn+woO*7uZUEtPdIQgky63;oEDR*QRaaDJGQExNc`84d& zuNTkLLTzHBr#kNLJ*nF-_&EOA!Z&ABS8d{5VjtLZQkP%Id_D7x>oJQwc33x;NllJ6 zV+uF?x%G0y&a<j|ryUQR7qGvP*m+y&=`+a({EhvB+~59aY?OW9sVS|RnqmBb{lI&M z*+ScwmgMR(aJ4hp^Z$`4D0sBZW2a_b2KVG`PN&YNJy1UIUa-1&OVDlZhC_l!_C0>T zJhi_-#dGORz8X+sT)D+}Wn4H{!=b<f`U3AAgV!xk^E`E&@4@5)`HcQ5dEU9Z!WcyS z*y^TMe7CeNj8^f?e8col{e$*_T}N_P#)fk@97=jHy;1(~&pDH>h$bI+-WbpR&i~G| zlwENQBGZK4*X)z;?^M}((_X;%$i8{I6j$E7`Sz{SBBdJlS8X1Xyh?39yl-5;B+o-n zhe2(-%#Z#9(L&puCz;He-4M_G&#=_FD4Ee<@6-eJZ1*PYQk=A;@eb!7vkyD3@SdE? z>QEoXB+oQ$X<mm)X4H3~<Xv|DJl`&h=rH^;7ye=Qz@E`tXuIpAE!)2ynY6dw=S|zn zzZ)1N?#Mh~@8tSC<w)+tCF`nXei(cxOo)E8&7_O<z=vl{_DnInGSj!HPP$@yUP$); z@rH2W?Mo->Fu0WqeAs*>j=5buuiK<!|GmHW-}~L-$}~7HoV=+{{>G#ox<U-x9Y<B; zRQ}$(ddz2f>;cmwJEln<sr((q_SPtE*B+la+uM~=UvWHAx$E?c``C=m%9#-#ix!nL z>8D<>OIAyhF3>ID338Tk6=7OG=^v9(8czk!j)s+UxoxI6TK7sSZ+o^^%tCJELO(UX zN3u)yFYqb<9Qx}w_bZO1qg$@LHNAT_`#|Y|l^(hqXK1jLP5Q@VeD~DWBWtGComHC` zw={42%dj#*+5eIrx?87MoGhRCugUoBIlc#fdERq9`kVdged4Y8p$C3?$QBBIV7(My zv21RoScUfDXg-^%2frJ>J(KmI*F&~c^22|Rdc|4ao|G+cuk>R9jZe+|_Jr-h><KJK z+MMsDoL#ZAFJjKBoX?H+ryu;Dnf9FTL6M4YmTH0YOZ||`YkK1EEZ5SLN@+W5>vH`4 ztIe0{&Zy{3`|Vl(aMqvAl0W2LeJ+dnc<IaZ_RfP#Hcv2_aPvTQL+O(J6MW}Y3RK8` zwU*tH-|=l$-jl!wUI}uO+>EBLSY$ro-yyS~Pv&=uss~%nnXzx`FN+y9n<iHHn#g-D zwVJSE)qJ(Tt$CN8MJ4dPJbTEsna9RGr+1%#=Ts|}AHE5~FZFAJ^S3d*SG9kydTZLR zA2X^pckU3EZCX+*<6HVZqo2jD!<xTL)t_nlr1jpNX3OmSmi@|6d}r$()O(%r|H4J} z(W}l@zPWkl(U&f}!*khh&%fQ^`s97ZgX|^tam!|f>~)Usx2;}QvvJY+30cK*)$um- z8T?kg*eu3a-($_cO}Cy;=0L`?3;*k0?LU{%U&60&YtFA7Ge2!=-mzV0k(9dN_hWA# z+g)aI=r!NRps}fUMN$9i7sk~)J|{?T>XwKwef3E5sM!AZFFx1TGk;(xc-Aq$T6JEt zum4r+FS4FCduviBo!`a~@YpwuL8Nk~#ZUg#FE*b)HkW<&EZ+m=Z+}nbdn5B;;obkw z6j%>Pe6ZPQ^i<;>+a8Bsk2BfMYd!e-``?nK7Bgn({kUW@Kc8{IvDxbwB<eTsZ+qo- zX`Z(T)5?5}W&78tc5M&zjoFxgfhnQq2lu~Kg10xeL~xmXEj0a`Dq8Sx$^IuV_>|5q zfBrk8owY$~{d}ecCpI5BYWV8+y_GvYC;a=l|G>iFzYVpsvd;cX<Z4i=chT`MUDMpS zf98|>razaQlKs_n^bFff)g;CR$E24qXnc~ms<(Kj(2mNEvYA=Mx*Pv|)_BHTZ*_FP zy%(c_+nc-h-a8yRxg#TB-pZo(eU?Xxo*m6z;Rx2ej6vfQ$JM+g56?AMrf7dqzWhI= z_U3(&J%QW}hKnrrCLAf-Cw+b03B~f@yL`1r+tT=ZF4%E1nBLQ1b(pBN{Zh&EgIg+@ zc4X||f9c=ec{dtTcb(T-%pjp$CjaN2(5>Bd_Vbfhoje@!zx}AWSH^OXS9_Qicueho z)#Y?xF8k}#yi<%tzufh)>&;3!rNwZpW2WkLf#+v8^fCQg*?->X>65@kkLACbUJeOk z+CJq*95;h$+)P%7iB@c{Z2}#4pPg6Jc9FgMoWeEE7vGIf>o9c1xaS0!uBkl2WW2Jz z-|V^Ms=Eu<uR3^Cj3H}}7FUB(sNZ$XGP#;nhrX2?ruwXImI(cSow=?|l=Xnb#~tMt zJMKT{`WDA}u4i6S`@ZJi|0UUizJ5spSvr-~VPfckO<lM4H@w&ob7tur=}+5v%6_Pq zY86};m>(Ys3S{R0%LMmdpWU%PEcm~l@A<SxU(c+#?72MXI#V*pek-m9!&UbSUKBi9 z&$(SePVc+%uS>fa@>72JZ92u2&{LFV%oBZQ#*E01fo`)`S<aRFs%MzTxZv38)eIV& zn17fQc)8v`K9`;S{QaPFTK$Xd)Yk7}&@g&c#m3#RUuq+7tk+fRFLOOa#G<qR#euBb z#oeG3nr~5Z>0SN(x$MjRB$b~kT3zm+dSTu!t_H(~Upk8!82H0MYXRKiFBYu2%5!#} zi2q+d*-sMh&zDGly&?$m_9~_Y9#^ILz7{+`c=D6hhxKjtYyK~~adLf++0{sfp1^Z` zP1A*+7reS}ygcOZuJ!YKt6p`mHc080G6pPp#oK<TvcO-C`JTb8hb-Ujom}5@B^ILR zxls4@CI4cJJ*HZJ;oTJIFT0T;;PKNS29cFjhZnAUZ)5SQ>St@G_0OpjMOY6=JoTxy znUt5G68dz?{Y0}@E0saMy90{Me2Xhb=d!O)-6H#W!L9Q9E4dn!7QfT5m@UyW?N|9a z<50a{rl;I&OOG%)^iI!a40s~ADqeigTWkJz3+5cHU3UBA`kp0v497H{bDk7EqHz7n z{_3ea=f0Z$S1xt&JS$M%IK7uaBj{<Hvq0;C>&8Fkr9O{5^^-fd9^^0?t_H(XHv<b; z-^MK6bV~Nuf;~{buh#@6PcxaU)90GEGk1Gi=>2{3B;2h?li^s$#J`K*X;df`Wb@{( zRpt-=J9&qZ`74NjmWeXBuB!PSA;QF;_QAIOtawAKs!VrX`6q2<OHqd8DVzNcIUabP zm6b2LvZ{TbTK`c{keO};x$nheOOyQP2RDg*mn>1=Z!{^IF(BoNEbHtY839+Vzidr3 z>jUMwm!RxwE|YcYT=Q(nzZ*@D1Z{7A2XdG$!!eC?_k-^j{8>3Ub@?mVU$?f%fr}(v z)&mk(*E>x6e6G2l>G|{zvcY*}lZzP*+$^oR<}t7FzVh#7&{w`%+gsa}!1>yj)nVc) zbD2}Q&kwF=aG(BRuiMR&>wBKWG4u#ZpWCzje3{?Vf=8X9uRf>syefa;y%ieoTQ@Rj z1buCDwtQn#p=%hH7_u1TtXTmJJ@)Sw^YqMionyUneebHW7v6heI(9;IXqL&{Vf$0T znX8|zzTfE55~hTni{&CMHzuq+$F!-!H|*-=jWMwFq9?-Oy6RrR3U}N0mgY?PnhW=) zf~^UIq{!)kf33cU_V=I6g+~4BAV?BkCG)aIdU3xSdxh-JwHxmp0cF%O5u3(W)0V6& zYTp+Fi$6Uvu=y`!PR)J&N4&>Oes920zq-^VD?thPjYh<`IPTE(6DNL#CdcbfnHHQ- z4%vO?<<g#SD{93JUoYtSdVRgheb&rbF$^M-rS6U$_cNwk_qfiSFT0U9I1`qR(-}9Y zm*!ZQyhy(1d!T&khnu1ApM<;J3V?VcKl#H~wN-Z(^3Q^WZY0B-*;?i@P5;W}V!V6Q zB|>FEsdt+QC`m1TAhby1?)~(u8=d1XLj%f&yJ6+$P`CJ>xmw1J@+qI@vX?W>UpObT zTbx;1?Prh7<z-9>JrS|*tAG9R3%2C>@k!+WzrRN;O<wKe53Uq0<XvLdxAPd2LvK3c zhW~$LuktRN<-11ke|)EAOL*WtnYX*1&*-1EO_U+|$dBIm^ey|(@iWyi{m?&9uYQap z#;w%)ORUG7&nx?5-+_{MB*T~aTIMoZ!CiCzobr(72IuaGcC*|o!R|T?T47fg>t;%5 zH=2g*UT|uK7{h<=AH7$2FUIMqb4_<$DQ(m#_wmb0iQhq2BbYK3#28Nb8uGr%TXnY} zLt;6TdEmJ<j8os+JP3PmcBS;D=#7ErYeTGg!=$=c8;VS&e_TJXH)Qw2fc^J4?r`Y% z-r@6o@A2sGuNBr@eYe`OJ9hqgzkFBgh9wgt7&OX{2Cc|mS$25g%NR!;lai38^Vc3c zW2`^)!kUY3)r}SDvRCz5b*CO;a_|kzY1=d}%Unik{f(3B*XTBeuGj8pOj-O+V~4%) z`LnH}_47iwxEV}+KZak6dT?J*`@_^+wvQ6u<s0U&eQ-a#c3*Er>E<O(_0@KTe0Dy& zmIq(Dpu=z_#GP@!y9m?!#dG#wNEfI#z2~l@Ip=hOQSURKq_UIym(CIVQQT3Sr(4MP zE6d~3k{I>{^VTS{@$yAqiSt?GJmFV$$V{{K%op<i$?aHi{;|PDpGPv$(+_@s;S|HZ zAjys~AbO6IkMy4Yh^ucq?EcxeGub=XyxQM+@%x<cfBRZ*Y)=$5yt`%oMiqAkd)GP9 zJAHkpwuL?Ch+N6%-C)mBH-Cn3ow=b!w)DobKZXZ5k2pDp>V03eXYHN2bB}!6m))q| zu)MQS?Q@4~>olhP1D{=VpLnj_Hj6iAll7&LG~s01dvkUv2HmY;RqzaAk7V$ebj4AO zVUkMe1l9%>PuUd4VI%DDb=%Us1{Ko_msd((C=+A28qL3F+AhIKR}9RT<~iJsWC(d5 z`e1#qxzi-6Cy5Wv2b(W?tHbc>BV&T|D>siIW*ep&>5s=JYo#y-^t(5hGyb2lO>)wd zwbL8<nVvIb3vXX4ti$kXGv5!j2XhaE3vYLwRP@p0!|DSk4}`iXE|7{~xRTBDL+PW* z1EvS(3mO(I$*bb{VfjGhL3xAtC9~z#It-V7T78gv!1Eya$3E{M^E+&Rq#xb;9{6DD zma{MQxEsD)V+?1qePF(kYuXjR>&*8#=kV7^-I%$}audUe<HiTB9#DF;@y27%Xl2jE zw_W#q+rtsTSfiL*Y%-74q0UUW|EQm;+~m8Hw^>dy*`Rwsy}`J%@%;8BxqWKMj1$(s zJ(9fXx?um=b^f<yJa4V<J+k_Y>w(_|R`J(#7(SW1*_cH$?qN*l^Eh?>@R8N0<vwbC z-Z`Z=uwI8@Rw}Py@dKZvX9uorIXm%6;dbHmTK>lc|Mn&`Uhv`H^!-eu_~dPFLDxl; zf5rDJ|B^n<wBY7EHM#9`dyLstLRU*%tT(K7`_iwPJJBtP@dBT4I^Uhy+bkzVrAQlo zd!du$^8ak+?8TcR7`B`~GLzYSNp9Z~wra5-u?dET-(ECsIU9IWhv8PH_{WMo%MWgk zHqPyTrSkQ$;J2O^|5G2FoYEUus>5(A-}K{+bvKF+%w(RVR?Pm9tB~`fSE2W<XDhPA z818PIHp6m*L^_|xt;G@-_w%@aDeq!+_-=FbUNxW1#4V<if;8N|#5XDbvOdkUV7HlI z|4%<fx!-rEZ?l|~70~m-es+cFkF8tI26~AxXr=M#sLC<lo3YJulGcV!3FZ<m!$tr9 zZ8;mrr^67Uk;WI(X3V~1TZDhl6Z@GG%v%(5i>C;4Gx#o>VOb#eaYupst!Exm7v{c^ zy!e0j0q@M&i)A7iGOWsl`nkmgjoJ5fdQ@t=eVOmR=ho(m>r-?XuJo(ltUk{uuk7^` z<isxJU#1M)47^E?&M>kIdA`h1kGrU=8h7*Vlx>z-Qrrx@$<iMyKIS|sTpZlF<d=x@ zuWr*{a}TXv7{QPs0vaSPc-9cIb$Q1|i|zuK{ay)-3pRlUWd-|HWBo$3j>jJO-Le12 z`eUhkLWQ^)roQESwDF11Bg3WAT^@%u-M-A{u3<9&6m(LDA!Mq1$MGq)cY;D*g&Qua zx9ocnepH7cWNUh7tB07dF}s|{Z5d7PqaFXQ3$DME=W$!6w2`&pRnQq3i|&dwmBqK7 zc|_hgb1(bL{n^_rUkPzDTwOcEvcT}1OhCD<Z_f+;YcI;9^RAv_a?lhtW{>MPX1~JA zFSI)ITY<}dq0I~de0l0}_4E8}_-a*ii<ewlt9b0d9M3h&UUt-Z&N?#jNWA-bq4n%O zmX+@FkN<Duxpk{eWc}`G-z)E^dhV8GiE%t5<56pz@2tb{>g125n^ZlOXL$CA?wI+- z{JYw5rUkb|W~a`*@w{-_q#~Y%SqmzrR0&q6@p<gjaQmXK<o3mW-ZslgSMpAG9A9JY z@qc=J@FU@dqR$E!AIe{NUN}`{YKGy*8>QSYty}ISGY0rw6WAP<_O;SR)l<}}Ylh_q znU~fbG0BVuHD!#eJ2q-eTEe5NAa{D5dhPOw(K-xl=gS_2UcO}4KUYO_qve682XvRb zcPZ**b@+eNXHS&X%Ja!;o~JJS$PL{s!`<-rdFG?gOAl-1v^|5E=lI*4yX*O@?--N9 z`Zb3I|4zD6wOpXTJYKQ(crxPzv-sm6AxrnmYiBZxF;oY=anbaAwIS)*fyGPS&(L95 zb-L_<)dSvIId;z=_cMxe?Dt)Nojb*}V2_CMFaLL+e|t}|%3}LiQ6c)$TA#b&;b)${ z7t>XEUozb?E?%I`dcgUJ_FU%QfA&pM@qHQc$newZCGQ{TFsPl+J)%A5W^J9n=cHd{ zJRc)|R$eNv5MvN@|KdF<tJvV<jGu;=${S*J7~+(}%J*zI|6av2Xyr4Z{<-hfYM0+= zWqObbnq!@R-*wUwud^z0&g<1`)o&hSN>Kjz>S>4F{$>@=t<jD;7L#wEX;c$q;D6jv z=NWW(j$52D`>YU#8fK6+QtM_|eqa(~`0Z`DsNQXU?o<`c>Z4~Gk8fm<n4jeG-?es| z|D+{uT4{WDoVRl~tUaRDz5M%4J<mx|3ujmcgtyQ3X51jSgUw>y`EMSRu0&~f9DlGo zBcJhu!AGtq3mo}+R6Jt~_%>cR8+I$0;ZC3OFICT1r#Bus(<q*q$M~S5&T~@RmI)O( z|5(myFo2eI@c->q^}MuyhP#d1{^>T{4M{HlC6#NvJtvv0OnP>p_emhb9+x#`6@O0L z_nD;fHTA+w=2S5T=?+7#_9>ryRXi^pIHxMNKY#fzrU!hFLXXV(G)v!elH10Ph=lZ2 zGnp5QFo0r3v$$QrnBB}EiZP()#r`c#W?qw)bcq<Vzf0c0^k9BRI9GbuWgX8+A?=X| tb~jxA{&T<mhC|yt2KOTJtMC6atJaDOEL!Sp$iTqB;OXk;vd$@?2>|}+3ON7( literal 0 HcmV?d00001 diff --git a/web/files/index.html b/web/files/index.html new file mode 100644 index 0000000..6acad3c --- /dev/null +++ b/web/files/index.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html> +<head> + +<!-- Responsive --> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<meta http-equiv="X-UA-Compatible" content="ie=edge"> + +<!-- Title --> +<title>PWA Tutorial</title> + +<!-- Meta Tags required for + Progressive Web App --> +<meta name="apple-mobile-web-app-status-bar" content="#aa7700"> +<meta name="theme-color" content="black"> + +<!-- Manifest File link --> +<link rel="manifest" href="manifest.json"> +</head> + +<body> +<h1>Boxes</h1> + +<div id="app"></div> + +<footer> + <p>Credits:</p> + <ul> + <li><a href="https://www.flaticon.com/free-icons/open-box" title="open box icons">Open box icons created by yoyonpujiono - Flaticon</a></li> + </ul> +</footer> + +<!-- +<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/2.2.11/mithril.min.js" integrity="sha512-2/N5u5OSxz7VyKmGbko8Jwx6o8fudoJ70t/Nvu16UoKCyqeDNfvxkDTmj11ibe4fwXSDdojQAxuV+y1Ut1JSkQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> +--> +<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/2.2.11/mithril.js" integrity="sha512-HQxrYG+jkimFdOc2fdtpe7urylm7yRsmYekrLMACDZk8GwF7UBnEfjG/e86r+lPUvfNsifVdQKK5iS6IBe70Og==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> +<script> + var LocationsRoute = { + view: function() { + return m("p", "TODO: a list of locations"); + } + } + + var BoxesRoute = { + view: function() { + return m("p", "TODO: a list of boxes"); + } + } + + var ContentsRoute = { + view: function() { + return m("p", "TODO: a list of contents"); + } + } + + window.addEventListener('load', () => { + registerSW(); + var root = document.getElementById("app"); + m.route(root, + "/", { + "/": LocationsRoute, + "/locations": LocationsRoute, + "/Boxes": BoxesRoute, + "/Contents": ContentsRoute, + } + ); + }); + + // Register the Service Worker + async function registerSW() { + if ('serviceWorker' in navigator) { + try { + await navigator + .serviceWorker + .register('serviceworker.js'); + } + catch (e) { + console.log('SW registration failed'); + } + } + console.log('SW registration succeeded'); + } + +</script> +</body> +</html> diff --git a/web/files/manifest.json b/web/files/manifest.json new file mode 100644 index 0000000..05e08e1 --- /dev/null +++ b/web/files/manifest.json @@ -0,0 +1,22 @@ +{ + "name":"Boxes", + "short_name":"PWA", + "start_url":"index.html", + "display":"standalone", + "background_color":"#5900b3", + "theme_color":"black", + "scope": ".", + "description":"This is a home inventory tool.", + "icons":[ + { + "src":"images/icon-192x192.png", + "sizes":"192x192", + "type":"image/png" + }, + { + "src":"images/icon-512x512.png", + "sizes":"512x512", + "type":"image/png" + } +] +} diff --git a/web/files/serviceworker.js b/web/files/serviceworker.js new file mode 100644 index 0000000..b00e371 --- /dev/null +++ b/web/files/serviceworker.js @@ -0,0 +1,19 @@ +var staticCacheName = "pwa"; + +self.addEventListener("install", function (e) { + e.waitUntil( + caches.open(staticCacheName).then(function (cache) { + return cache.addAll(["/"]); + }) + ); +}); + +self.addEventListener("fetch", function (event) { + console.log(event.request.url); + + event.respondWith( + caches.match(event.request).then(function (response) { + return response || fetch(event.request); + }) + ); +}); diff --git a/web/web.go b/web/web.go new file mode 100644 index 0000000..9bd1c4e --- /dev/null +++ b/web/web.go @@ -0,0 +1,9 @@ +// Package web contains the files for the static web elements. +package web + +import "embed" + +// Files contains all the static files that make up the website. +// +//go:embed files +var Files embed.FS -- GitLab