11use leptos:: prelude:: * ;
2+ use leptos_fetch:: QueryClient ;
23use leptos_router:: hooks:: use_params_map;
3- use models:: { dvf:: RecordId , Entry } ;
4+ use models:: {
5+ dvf:: { RecordId , Visibility } ,
6+ Cache , Entry , PvCache , StorePath ,
7+ } ;
48
5- use crate :: { hooks:: EntryHook , pages:: UnauthorizedPage } ;
9+ use crate :: {
10+ components:: { CacheItemLink , CopyButton , LockClosedHeroIcon } ,
11+ hooks:: EntryHook ,
12+ pages:: UnauthorizedPage ,
13+ } ;
614
715#[ component]
816pub fn EntryPage ( ) -> impl IntoView {
@@ -13,25 +21,201 @@ pub fn EntryPage() -> impl IntoView {
1321 . parse :: < RecordId < _ > > ( )
1422 . ok ( ) ;
1523
16- requested_entry
17- . map ( |e| view ! { <EntryTile id=e /> } . into_any ( ) )
18- . unwrap_or ( view ! { <UnauthorizedPage /> } . into_any ( ) )
24+ let Some ( requested_entry) = requested_entry else {
25+ return view ! { <UnauthorizedPage /> } . into_any ( ) ;
26+ } ;
27+
28+ let entry_hook = EntryHook :: new ( move || requested_entry) ;
29+ let intrensic = entry_hook. intrensic ( ) ;
30+ let intrensic_suspend = move || {
31+ Suspend :: new ( async move {
32+ match intrensic. await {
33+ Ok ( Some ( entry) ) => view ! { <EntryInner entry=entry /> } . into_any ( ) ,
34+ Ok ( None ) => view ! { <MissingEntryPage /> } . into_any ( ) ,
35+ Err ( _) => view ! { <ErrorEntryPage /> } . into_any ( ) ,
36+ }
37+ } )
38+ } ;
39+
40+ view ! {
41+ <Suspense fallback=|| ( ) >{ intrensic_suspend } </Suspense >
42+ }
43+ . into_any ( )
44+ }
45+
46+ #[ component]
47+ fn EntryInner ( entry : Entry ) -> impl IntoView {
48+ view ! {
49+ <div class="flex flex-col gap-4" >
50+ <TitleTile store_path={ entry. store_path. clone( ) } />
51+ <div class="grid gap-4 grid-flow-col" >
52+ <StorePathTile store_path={ entry. store_path. clone( ) } />
53+ <CachesTile entry={ entry. clone( ) } />
54+ </div>
55+ </div>
56+ }
57+ }
58+
59+ #[ component]
60+ fn TitleTile ( store_path : StorePath < String > ) -> impl IntoView {
61+ let path = store_path. to_absolute_path ( ) ;
62+ view ! {
63+ <div class="p-6 elevation-flat flex flex-row gap-2 items-center" >
64+ <p class="text-base-12 text-xl" >
65+ { path. clone( ) }
66+ </p>
67+ <CopyButton copy_content={ path} { ..} class="size-6" />
68+ </div>
69+ }
70+ }
71+
72+ #[ component]
73+ fn StorePathTile ( store_path : StorePath < String > ) -> impl IntoView {
74+ let string = store_path. to_string ( ) ;
75+ let separator_index =
76+ string. find ( '-' ) . expect ( "no separator found in store path" ) ;
77+ let ( digest, _) = string. split_at ( separator_index) ;
78+ let name = store_path. name ( ) . clone ( ) ;
79+
80+ const KEY_CLASS : & str = "place-self-end" ;
81+ const VALUE_CLASS : & str = "text-base-12 font-medium" ;
82+
83+ let value_element = move |s : & str | {
84+ view ! {
85+ <div class="flex flex-row gap-2 items-center" >
86+ <p class=VALUE_CLASS >{ s } </p>
87+ <CopyButton
88+ copy_content={ s. to_string( ) }
89+ { ..} class="size-4"
90+ />
91+ </div>
92+ }
93+ } ;
94+
95+ view ! {
96+ <div class="p-6 elevation-flat flex flex-col gap-4" >
97+ <p class="subtitle" >
98+ "Store Path Breakdown"
99+ </p>
100+ <div class="grid gap-x-4 gap-y-1 grid-cols-[repeat(2,auto)]" >
101+ <p class=KEY_CLASS >"Prefix" </p>
102+ { value_element( "/nix/store/" ) }
103+ <p class=KEY_CLASS >"Digest" </p>
104+ { value_element( digest) }
105+ <p class=KEY_CLASS >"Name" </p>
106+ { value_element( & name) }
107+ </div>
108+ </div>
109+ }
19110}
20111
21112#[ component]
22- fn EntryTile ( id : RecordId < Entry > ) -> impl IntoView {
23- let entry_hook = EntryHook :: new ( move || id) ;
24- let all = entry_hook. all ( ) ;
25- let all_suspend =
26- move || Suspend :: new ( async move { format ! ( "{:#?}" , all. await ) } ) ;
113+ fn CachesTile ( entry : Entry ) -> impl IntoView {
114+ let caches = Signal :: stored ( entry. caches . clone ( ) ) ;
115+ let store_path = Signal :: stored ( entry. store_path ) ;
116+ view ! {
117+ <div class="p-6 elevation-flat flex flex-col gap-4" >
118+ <p class="subtitle" >
119+ "Resident Caches"
120+ </p>
121+
122+ <table class="table" >
123+ <thead>
124+ <th>"Name" </th>
125+ <th>"Download Url" </th>
126+ <th>"Visibility" </th>
127+ </thead>
128+ <tbody class="animate-fade-in min-h-10" >
129+ <For each=caches key=|r| * r children=move |r| view! {
130+ <CachesTileRow store_path=store_path cache_id=r />
131+ } />
132+ </tbody>
133+ </table>
134+ </div>
135+ }
136+ }
137+
138+ #[ component]
139+ fn CachesTileRow (
140+ store_path : Signal < StorePath < String > > ,
141+ cache_id : RecordId < Cache > ,
142+ ) -> impl IntoView {
143+ let query_client = expect_context :: < QueryClient > ( ) ;
144+
145+ let query_scope = crate :: resources:: cache:: cache_query_scope ( ) ;
146+ let resource = query_client. resource ( query_scope, move || cache_id) ;
147+
148+ let suspend = move || {
149+ Suspend :: new ( async move {
150+ match resource. await {
151+ Ok ( Some ( c) ) => {
152+ view ! { <CachesTileDataRow cache=c store_path=store_path /> }
153+ . into_any ( )
154+ }
155+ Ok ( None ) => None :: < ( ) > . into_any ( ) ,
156+ Err ( e) => format ! ( "Error: {e}" ) . into_any ( ) ,
157+ }
158+ } )
159+ } ;
27160
28161 view ! {
29- <div class="elevation-flat p-4 flex flex-col gap-4" >
30- <p class="title" >"Entry" </p>
162+ <Transition fallback=|| ( ) >{ suspend } </Transition >
163+ }
164+ }
165+
166+ #[ component]
167+ fn CachesTileDataRow (
168+ store_path : Signal < StorePath < String > > ,
169+ cache : PvCache ,
170+ ) -> impl IntoView {
171+ let download_url = format ! (
172+ "/api/v1/c/{cache_name}/download/{store_path}" ,
173+ cache_name = cache. name,
174+ store_path = store_path( ) ,
175+ ) ;
176+ let vis_icon =
177+ matches ! ( cache. visibility, Visibility :: Private ) . then_some ( view ! {
178+ <LockClosedHeroIcon { ..} class="size-4 stroke-base-11/75 stroke-[2.0]" />
179+ } ) ;
31180
32- <div class="bg-base-2 rounded border-[1.5px] border-base-6 p-4 overflow-x-auto" ><pre>
33- <Suspense fallback=|| ( ) >{ all_suspend } </Suspense >
34- </pre></div>
181+ view ! {
182+ <tr>
183+ <th scope="row" >
184+ <CacheItemLink id=cache. id extra_class="text-link-primary" />
185+ </th>
186+ <td>
187+ <a href={ download_url} class="text-link text-link-primary" >"Download" </a>
188+ </td>
189+ <td class="flex flex-row items-center gap-1" >
190+ { cache. visibility. to_string( ) }
191+ { vis_icon }
192+ </td>
193+ </tr>
194+ }
195+ }
196+
197+ #[ component]
198+ fn MissingEntryPage ( ) -> impl IntoView {
199+ view ! {
200+ <div class="p-6 elevation-flat flex flex-col gap-4 items-center" >
201+ <p class="title" >
202+ "( ˶°ㅁ°) !!"
203+ " We don't have that entry"
204+ </p>
205+ <p>"Looks like we don't have that entry! Try uploading it through the CLI." </p>
206+ </div>
207+ }
208+ }
209+
210+ #[ component]
211+ fn ErrorEntryPage ( ) -> impl IntoView {
212+ view ! {
213+ <div class="p-6 elevation-flat flex flex-col gap-4 items-center" >
214+ <p class="title" >
215+ "ヽ(°〇°)ノ"
216+ " Something went wrong"
217+ </p>
218+ <p>"Looks like something went wrong when finding your entry. We apologize!" </p>
35219 </div>
36220 }
37221}
0 commit comments