From c0e12531a249dd2b3f18c8785266e33f7e887831 Mon Sep 17 00:00:00 2001 From: Anna Date: Mon, 4 Oct 2021 15:14:40 -0400 Subject: [PATCH] feat: add basic search and filtering --- assets/listings.css | 100 +++++++++++---------- assets/listings.js | 41 +++++++++ src/listing.rs | 39 ++++++++ src/web.rs | 8 ++ templates/listings.html | 191 ++++++++++++++++++++++------------------ 5 files changed, 240 insertions(+), 139 deletions(-) create mode 100644 assets/listings.js diff --git a/assets/listings.css b/assets/listings.css index 8cd61bf..1618a1f 100644 --- a/assets/listings.css +++ b/assets/listings.css @@ -35,51 +35,54 @@ body { color: var(--text); } -body > .listing { - display: flex; - flex-flow: row nowrap; - justify-content: space-between; +#listings > .listing { + display: grid; + grid-template-columns: 3fr auto 2fr; + gap: 1em; padding: 1em; - margin-bottom: 1em; background-color: var(--row-background); } -body > .listing .left { - flex: 2 0 0; -} - -body > .listing .middle { - flex: 1 0 0 ; -} - -body > .listing:nth-child(2n) { +#listings > .listing:nth-child(2n) { background-color: var(--row-background-alternate); } -body > .listing .description { +#listings > .listing .description { white-space: pre-wrap; word-break: break-word; } -body > .listing .stat { +#listings > .listing .description .desc-green { + color: var(--green-text); +} + +#listings > .listing .description .desc-blue { + color: var(--light-blue-text); +} + +#listings > .listing .description .desc-yellow { + color: var(--gold-text); +} + +#listings > .listing .stat { color: var(--meta-text); } -body > .listing .duty { +#listings > .listing .duty { font-size: 1.2em; } -body > .listing .duty.cross { +#listings > .listing .duty.cross { color: var(--cross-duty-text); } -body > .listing .duty.local { +#listings > .listing .duty.local { color: var(--local-duty-text); } -body > .listing .meta { +#listings > .listing .meta { display: flex; flex-direction: column; @@ -89,13 +92,13 @@ body > .listing .meta { text-align: right; } -body > .listing .meta > .item { +#listings > .listing .meta > .item { display: flex; flex-flow: row nowrap; align-self: flex-end; } -body > .listing .meta > .item .icon { +#listings > .listing .meta > .item .icon { height: 1em; width: 1em; fill: var(--text); @@ -104,7 +107,7 @@ body > .listing .meta > .item .icon { justify-self: center; } -body > .listing .party { +#listings > .listing .party { margin-top: .5em; display: grid; @@ -112,19 +115,19 @@ body > .listing .party { gap: .5em; } -body > .listing .party > .total { +#listings > .listing .party > .total { align-self: center; justify-self: center; } -body > .listing .party > .slot { +#listings > .listing .party > .slot { width: 2em; height: 2em; border: 1px solid currentColor; margin-right: .5em; } -body > .listing .party > .slot:not(.filled).dps.tank { +#listings > .listing .party > .slot:not(.filled).dps.tank { background: linear-gradient( to bottom, var(--tank-blue) 0%, @@ -133,7 +136,7 @@ body > .listing .party > .slot:not(.filled).dps.tank { ); } -body > .listing .party > .slot:not(.filled).dps.healer { +#listings > .listing .party > .slot:not(.filled).dps.healer { background: linear-gradient( to bottom, var(--healer-green) 0%, @@ -142,7 +145,7 @@ body > .listing .party > .slot:not(.filled).dps.healer { ); } -body > .listing .party > .slot:not(.filled).tank.healer { +#listings > .listing .party > .slot:not(.filled).tank.healer { background: linear-gradient( to bottom, var(--tank-blue) 0%, @@ -151,7 +154,7 @@ body > .listing .party > .slot:not(.filled).tank.healer { ); } -body > .listing .party > .slot:not(.filled).tank.healer.dps { +#listings > .listing .party > .slot:not(.filled).tank.healer.dps { background: linear-gradient( to bottom, var(--tank-blue) 0%, @@ -162,70 +165,65 @@ body > .listing .party > .slot:not(.filled).tank.healer.dps { ); } -body > .listing .party > .slot:not(.filled).dps { +#listings > .listing .party > .slot:not(.filled).dps { background-color: var(--dps-red); } -body > .listing .party > .slot.filled { +#listings > .listing .party > .slot.filled { background-color: var(--slot-background); border-color: var(--icon-gold); } -body > .listing .party > .slot.empty { +#listings > .listing .party > .slot.empty { background-color: var(--slot-empty); } -body > .listing .party > .slot.dps { +#listings > .listing .party > .slot.dps { background-color: var(--dps-red); } -body > .listing .party > .slot.healer { +#listings > .listing .party > .slot.healer { background-color: var(--healer-green); } -body > .listing .party > .slot.tank { +#listings > .listing .party > .slot.tank { background-color: var(--tank-blue); } -body > .listing .party > .slot > svg { +#listings > .listing .party > .slot > svg { width: 100%; height: 100%; fill: var(--icon-gold); } -body > .listing .party > .slot.filled:not(.dps):not(.healer):not(.tank) > svg { +#listings > .listing .party > .slot.filled:not(.dps):not(.healer):not(.tank) > svg { fill: #C6C6C6; } -/* Really, this could be 26em */ -@media (max-width: 30em) { - body > .listing { - flex-flow: column nowrap; +@media (max-width: 50em) { + #listings > .listing { + grid-template-columns: repeat(auto-fit, 100%); } - body > .listing > :not(:first-child) { - margin-top: .5em; - margin-left: 0; + #listings > .listing > :not(:first-child) { + margin: 0; } - body > .listing .meta { - flex-grow: 1; - - margin-left: 0; + #listings > .listing .meta { text-align: unset; } - body > .listing .meta > .item { + #listings > .listing .meta > .item { align-self: unset; } - body > .listing .meta > .item .icon { + #listings > .listing .meta > .item .icon { order: 1; margin-left: 0; margin-right: .5em; } - body > .listing .meta > .item > .text { + #listings > .listing .meta > .item .text { order: 2; } } diff --git a/assets/listings.js b/assets/listings.js new file mode 100644 index 0000000..4829e4d --- /dev/null +++ b/assets/listings.js @@ -0,0 +1,41 @@ +(function () { + let select = document.getElementById('data-centre-filter'); + + let data_centres = []; + for (let elem of document.querySelectorAll('#listings > .listing')) { + let centre = elem.dataset['centre']; + if (data_centres.indexOf(centre) === -1) { + data_centres.push(centre); + let opt = document.createElement('option'); + opt.innerText = centre; + select.appendChild(opt); + } + } + + let options = { + valueNames: [ + 'duty', + 'creator', + 'description', + {data: ['centre']}, + ], + }; + let list = new List('container', options); + + select.addEventListener('change', () => { + let centre = select.value; + if (centre === 'All') { + list.filter(); + return; + } + + console.log(`looking for ${centre}`); + + list.filter(item => { + console.log(item.values().centre === centre) + return item.values().centre === centre; + // console.log(item.elm.dataset['centre']); + // return item.elm.dataset['centre'] === centre; + }); + }); +})(); diff --git a/src/listing.rs b/src/listing.rs index 4616206..c585ac1 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -109,6 +109,45 @@ impl PartyFinderListing { .map(|world| Cow::from(world.name())) .unwrap_or_else(|| Cow::from(self.home_world.to_string())) } + + pub fn prepend_flags(&self) -> (&'static str, String) { + let mut colour_class = ""; + let mut flags = Vec::new(); + + if self.objective.contains(ObjectiveFlags::PRACTICE) { + flags.push("[Practice]"); + colour_class = "desc-green"; + } + + if self.objective.contains(ObjectiveFlags::DUTY_COMPLETION) { + flags.push("[Duty Completion]"); + colour_class = "desc-blue"; + } + + if self.objective.contains(ObjectiveFlags::LOOT) { + flags.push("[Loot]"); + colour_class = "desc-yellow"; + } + + if self.conditions.contains(ConditionFlags::DUTY_COMPLETE) { + flags.push("[Duty Complete]"); + } + + if self.conditions.contains(ConditionFlags::DUTY_INCOMPLETE) { + flags.push("[Duty Incomplete]"); + } + + if self.search_area.contains(SearchAreaFlags::ONE_PLAYER_PER_JOB) { + flags.push("[One Player per Job]"); + } + + (colour_class, flags.join("")) + } + + pub fn data_centre_name(&self) -> Option<&'static str> { + crate::ffxiv::WORLDS.get(&u32::from(self.created_world)) + .map(|w| w.data_center().name()) + } } #[derive(Debug, Deserialize, Serialize, PartialEq)] diff --git a/src/web.rs b/src/web.rs index 8aca920..4486e51 100644 --- a/src/web.rs +++ b/src/web.rs @@ -79,6 +79,7 @@ fn assets() -> BoxedFilter<(impl Reply, )> { icons() .or(minireset()) .or(listings_css()) + .or(listings_js()) ) .boxed() } @@ -104,6 +105,13 @@ fn listings_css() -> BoxedFilter<(impl Reply, )> { .boxed() } +fn listings_js() -> BoxedFilter<(impl Reply, )> { + warp::path("listings.js") + .and(warp::path::end()) + .and(warp::fs::file("./assets/listings.js")) + .boxed() +} + fn index() -> BoxedFilter<(impl Reply, )> { let route = warp::path::end() .map(|| warp::redirect(Uri::from_static("/listings"))); diff --git a/templates/listings.html b/templates/listings.html index 88fd28d..e2b4cb2 100644 --- a/templates/listings.html +++ b/templates/listings.html @@ -6,99 +6,114 @@ Remote Party Finder {% block head %} + + {% endblock %} {% block body %} -{% for container in containers %} -{% let listing = container.listing.borrow() %} -
-
- {% let duty_class %} - {% if listing.is_cross_world() %} - {% let duty_class = " cross" %} - {% else %} - {% let duty_class = " local" %} - {% endif %} -
{{ listing.duty_name() }}
-
- {%- let desc = listing.description.full_text() %} - {%- if desc.is_empty() -%} - None - {%- else -%} - {{ desc }} - {%- endif -%} -
-
- {% for slot in listing.slots() %} - {% let filled %} - {% let title %} - {% let role_class %} - {% match slot %} - {% when Ok with (slot) %} - {% let filled = " filled" %} - {% match slot.role() %} - {% when Some with (role) %} - {% let role_class = " {}"|format(role.as_str().to_lowercase()) %} - {% when None %} - {% let role_class = "".to_string() %} - {% endmatch %} - {% let title = slot.code().to_string() %} - {% when Err with (tuple) %} - {% let filled = "" %} - {% let title = tuple.1.clone() %} - {% let role_class = " {}"|format(tuple.0) %} - {% endmatch %} -
- {% if !filled.is_empty() %} - - - - {% endif %} +
+ + +
+ {% for container in containers %} + {% let listing = container.listing.borrow() %} +
+
+ {% let duty_class %} + {% if listing.is_cross_world() %} + {% let duty_class = " cross" %} + {% else %} + {% let duty_class = " local" %} + {% endif %} +
{{ listing.duty_name() }}
+
+ {%- let desc = listing.description.full_text() %} + {%- if desc.trim().is_empty() -%} + None + {%- else -%} + {%- let (colour_class, prepend_flags) = listing.prepend_flags() -%} + {{ prepend_flags }} + {{- desc.trim() }} + {%- endif -%}
- {% endfor %} -
{{ listing.slots_filled() }}/{{ listing.slots_available }}
-
-
-
-
-
Min IL
-
{{ listing.min_item_level }}
-
-
-
-
- {{ listing.name.full_text() }} @ {{ listing.home_world_string() }} - - - - - -
-
- {{ listing.created_world_string() }} - - - - - -
-
- {{ container.human_time_left() }} - - - - - -
-
- {{ container.human_since_updated() }} - - - - - +
+ {% for slot in listing.slots() %} + {% let filled %} + {% let title %} + {% let role_class %} + {% match slot %} + {% when Ok with (slot) %} + {% let filled = " filled" %} + {% match slot.role() %} + {% when Some with (role) %} + {% let role_class = " {}"|format(role.as_str().to_lowercase()) %} + {% when None %} + {% let role_class = "".to_string() %} + {% endmatch %} + {% let title = slot.code().to_string() %} + {% when Err with (tuple) %} + {% let filled = "" %} + {% let title = tuple.1.clone() %} + {% let role_class = " {}"|format(tuple.0) %} + {% endmatch %} +
+ {% if !filled.is_empty() %} + + + + {% endif %} +
+ {% endfor %} +
{{ listing.slots_filled() }}/{{ listing.slots_available }}
+
+
+
+
+
Min IL
+
{{ listing.min_item_level }}
+
+
+
+
+ {{ listing.name.full_text() }} @ {{ listing.home_world_string() }} + + + + + +
+
+ {{ listing.created_world_string() }} + + + + + +
+
+ {{ container.human_time_left() }} + + + + + +
+
+ {{ container.human_since_updated() }} + + + + + +
+
+ {% endfor %}
-{% endfor %} {% endblock %}