Skip to content

Commit

Permalink
feat: Implement player & character search, and persist UI state (#153)
Browse files Browse the repository at this point in the history
* feat: Implement player & character search

* refactor: Run lint / format

---------

Co-authored-by: False Spring <false_spring@protonmail.com>
  • Loading branch information
AristocratMC and false-spring authored Jul 7, 2024
1 parent 61607c3 commit 7e415b9
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 11 deletions.
4 changes: 4 additions & 0 deletions src-tauri/lang/en/ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
"player-overmasteries": "Overmasteries",
"select-enemy": "Select Enemy",
"select-quest": "Select Quest",
"select-player": "Select Player",
"select-character": "Select Character",
"sba": {
"OnAttemptSBA": "Attempted SBA",
"OnPerformSBA": "Executed SBA",
Expand Down Expand Up @@ -76,6 +78,8 @@
"saved-count_other": "{{count}} logs saved",
"delete-selected-btn": "Delete Selected ({{count}})",
"delete-all-btn": "Delete All",
"show-advanced-filters": "Show Advanced Filters",
"hide-advanced-filters": "Hide Advanced Filters",
"date": "Date",
"name": "Name",
"primary-target": "Enemy",
Expand Down
106 changes: 105 additions & 1 deletion src-tauri/src/db/logs.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use rusqlite::Connection;
use sea_query::{Expr, Iden, Order, Query, SqliteQueryBuilder};
use sea_query::{Expr, Condition, Iden, Order, Query, SqliteQueryBuilder};
use sea_query_rusqlite::RusqliteBinder;
use serde::Serialize;

Expand Down Expand Up @@ -87,6 +87,8 @@ pub fn get_logs(
sort_by: &SortType,
sort_direction: &SortDirection,
cleared: Option<bool>,
filter_by_player_id: &Option<String>,
filter_by_player_character: &Option<String>
) -> anyhow::Result<Vec<LogEntry>> {
let sort_column = match sort_by {
SortType::Time => Logs::Time,
Expand Down Expand Up @@ -141,6 +143,56 @@ pub fn get_logs(
},
|_| {},
)
.conditions(
filter_by_player_id.is_some() && filter_by_player_character.is_some(),
|q| {
let player_id = filter_by_player_id.as_ref().unwrap();
let player_character = filter_by_player_character.as_ref().unwrap();

q.cond_where(
Condition::any()
.add(Expr::col(Logs::P1Name).eq(player_id.clone())
.and(Expr::col(Logs::P1Type).eq(player_character.clone())))
.add(Expr::col(Logs::P2Name).eq(player_id.clone())
.and(Expr::col(Logs::P2Type).eq(player_character.clone())))
.add(Expr::col(Logs::P3Name).eq(player_id.clone())
.and(Expr::col(Logs::P3Type).eq(player_character.clone())))
.add(Expr::col(Logs::P4Name).eq(player_id)
.and(Expr::col(Logs::P4Type).eq(player_character))),
);
},
|_| {},
)
.conditions(
filter_by_player_id.is_some() && filter_by_player_character.is_none(),
|q| {
let player_id = filter_by_player_id.as_ref().unwrap();

q.cond_where(
Condition::any()
.add(Expr::col(Logs::P1Name).eq(player_id.clone()))
.add(Expr::col(Logs::P2Name).eq(player_id.clone()))
.add(Expr::col(Logs::P3Name).eq(player_id.clone()))
.add(Expr::col(Logs::P4Name).eq(player_id)),
);
},
|_| {},
)
.conditions(
filter_by_player_id.is_none() && filter_by_player_character.is_some(),
|q| {
let player_character = filter_by_player_character.as_ref().unwrap();

q.cond_where(
Condition::any()
.add(Expr::col(Logs::P1Type).eq(player_character.clone()))
.add(Expr::col(Logs::P2Type).eq(player_character.clone()))
.add(Expr::col(Logs::P3Type).eq(player_character.clone()))
.add(Expr::col(Logs::P4Type).eq(player_character)),
);
},
|_| {},
)
.order_by_with_nulls(sort_column, order, sea_query::NullOrdering::Last)
.limit(per_page.into())
.offset(offset.into())
Expand Down Expand Up @@ -182,6 +234,8 @@ pub fn get_logs_count(
filter_by_enemy_id: Option<u32>,
filter_by_quest_id: Option<u32>,
cleared: Option<bool>,
filter_by_player_id: &Option<String>,
filter_by_player_character: &Option<String>
) -> Result<i32> {
let (sql, values) = Query::select()
.expr(Expr::col(Logs::Id).count())
Expand All @@ -207,6 +261,56 @@ pub fn get_logs_count(
},
|_| {},
)
.conditions(
filter_by_player_id.is_some() && filter_by_player_character.is_some(),
|q| {
let player_id = filter_by_player_id.as_ref().unwrap();
let player_character = filter_by_player_character.as_ref().unwrap();

q.cond_where(
Condition::any()
.add(Expr::col(Logs::P1Name).eq(player_id.clone())
.and(Expr::col(Logs::P1Type).eq(player_character.clone())))
.add(Expr::col(Logs::P2Name).eq(player_id.clone())
.and(Expr::col(Logs::P2Type).eq(player_character.clone())))
.add(Expr::col(Logs::P3Name).eq(player_id.clone())
.and(Expr::col(Logs::P3Type).eq(player_character.clone())))
.add(Expr::col(Logs::P4Name).eq(player_id)
.and(Expr::col(Logs::P4Type).eq(player_character))),
);
},
|_| {},
)
.conditions(
filter_by_player_id.is_some() && filter_by_player_character.is_none(),
|q| {
let player_id = filter_by_player_id.as_ref().unwrap();

q.cond_where(
Condition::any()
.add(Expr::col(Logs::P1Name).eq(player_id.clone()))
.add(Expr::col(Logs::P2Name).eq(player_id.clone()))
.add(Expr::col(Logs::P3Name).eq(player_id.clone()))
.add(Expr::col(Logs::P4Name).eq(player_id)),
);
},
|_| {},
)
.conditions(
filter_by_player_id.is_none() && filter_by_player_character.is_some(),
|q| {
let player_character = filter_by_player_character.as_ref().unwrap();

q.cond_where(
Condition::any()
.add(Expr::col(Logs::P1Type).eq(player_character.clone()))
.add(Expr::col(Logs::P2Type).eq(player_character.clone()))
.add(Expr::col(Logs::P3Type).eq(player_character.clone()))
.add(Expr::col(Logs::P4Type).eq(player_character)),
);
},
|_| {},
)
.build_rusqlite(SqliteQueryBuilder);

let mut stmt = conn.prepare(&sql).unwrap();
Expand Down
46 changes: 42 additions & 4 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ struct SearchResult {
enemy_ids: Vec<u32>,
/// IDs of the quests that can be filtered by.
quest_ids: Vec<u32>,
/// Names of the Players that can be filtered by.
player_ids: Vec<String>,
/// Names of the Characters that can be filtered by.
player_types: Vec<String>,
}

#[tauri::command]
Expand All @@ -144,6 +148,8 @@ fn fetch_logs(
sort_direction: Option<String>,
sort_type: Option<String>,
quest_completed: Option<bool>,
filter_by_player_id: Option<String>,
filter_by_player_character: Option<String>,
) -> Result<SearchResult, String> {
let conn = db::connect_to_db().map_err(|e| e.to_string())?;
let page = page.unwrap_or(1);
Expand Down Expand Up @@ -175,6 +181,8 @@ fn fetch_logs(
&sort_type_param,
&sort_direction_param,
quest_completed,
&filter_by_player_id,
&filter_by_player_character
)
.map_err(|e| e.to_string())?;

Expand All @@ -183,29 +191,41 @@ fn fetch_logs(
filter_by_enemy_id,
filter_by_quest_id,
quest_completed,
&filter_by_player_id,
&filter_by_player_character
)
.map_err(|e| e.to_string())?;

let page_count = (log_count as f64 / per_page as f64).ceil() as u32;

let mut enemy_ids = Vec::new();
let mut quest_ids = Vec::new();
let mut player_ids = Vec::new();
let mut player_types = Vec::new();

let mut query = conn
.prepare("SELECT primary_target, quest_id from logs")
.prepare("SELECT primary_target, quest_id, p1_name, p1_type, p2_name, p2_type, p3_name, p3_type, p4_name, p4_type from logs")
.map_err(|e| e.to_string())?;

let rows = query
.query_map([], |row| {
Ok((
row.get::<usize, Option<u32>>(0)?,
row.get::<usize, Option<u32>>(1)?,
row.get::<usize, Option<u32>>(0)?, // primary_target
row.get::<usize, Option<u32>>(1)?, // quest_id
row.get::<usize, Option<String>>(2)?, // p1_name
row.get::<usize, Option<String>>(3)?, // p1_type
row.get::<usize, Option<String>>(4)?, // p2_name
row.get::<usize, Option<String>>(5)?, // p2_type
row.get::<usize, Option<String>>(6)?, // p3_name
row.get::<usize, Option<String>>(7)?, // p3_type
row.get::<usize, Option<String>>(8)?, // p4_name
row.get::<usize, Option<String>>(9)?, // p4_type
))
})
.map_err(|e| e.to_string())?;

for row in rows {
let (primary_target, quest_id) = row.map_err(|e| e.to_string())?;
let (primary_target, quest_id, p1_name, p1_type, p2_name, p2_type, p3_name, p3_type, p4_name, p4_type) = row.map_err(|e| e.to_string())?;

if let Some(primary_target) = primary_target {
if !enemy_ids.contains(&primary_target) {
Expand All @@ -218,6 +238,22 @@ fn fetch_logs(
quest_ids.push(quest_id);
}
}

for p_name in [p1_name, p2_name, p3_name, p4_name] {
if let Some(p_name) = p_name {
if !player_ids.contains(&p_name) {
player_ids.push(p_name)
}
}
}

for p_type in [p1_type, p2_type, p3_type, p4_type] {
if let Some(p_type) = p_type {
if !player_types.contains(&p_type) {
player_types.push(p_type)
}
}
}
}

Ok(SearchResult {
Expand All @@ -227,6 +263,8 @@ fn fetch_logs(
log_count,
enemy_ids,
quest_ids,
player_ids,
player_types
})
}

Expand Down
80 changes: 75 additions & 5 deletions src/pages/logs/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const IndexPage = () => {
toggleSort,
setFilters,
filters,
toggleAdvancedFilters,
} = useIndex();

const { streamer_mode, show_display_names } = useMeterSettingsStore(
Expand Down Expand Up @@ -115,11 +116,26 @@ export const IndexPage = () => {
)}
</Box>
</Group>
<Group>
<SelectableEnemy targetIds={searchResult.enemyIds} setFilters={setFilters} filters={filters} />
<SelectableQuest questIds={searchResult.questIds} setFilters={setFilters} filters={filters} />
<SelectableQuestCompletion setFilters={setFilters} filters={filters} />
</Group>
<Box py={"xs"}>
<Group>
<SelectableEnemy targetIds={searchResult.enemyIds} setFilters={setFilters} filters={filters} />
<SelectableQuest questIds={searchResult.questIds} setFilters={setFilters} filters={filters} />
<SelectableQuestCompletion setFilters={setFilters} filters={filters} />
<Button size="s" variant="default" onClick={toggleAdvancedFilters}>
{filters.showAdvancedFilters ? t("ui.logs.hide-advanced-filters") : t("ui.logs.show-advanced-filters")}
</Button>
</Group>
</Box>
<Box>
<Group>
{filters.showAdvancedFilters && (
<SelectablePlayer playerIds={searchResult.playerIds} setFilters={setFilters} filters={filters} />
)}
{filters.showAdvancedFilters && (
<SelectablePlayerType playerTypes={searchResult.playerTypes} setFilters={setFilters} filters={filters} />
)}
</Group>
</Box>
{searchResult.logs.length === 0 && <BlankTable />}
{searchResult.logs.length > 0 && (
<Box>
Expand Down Expand Up @@ -367,3 +383,57 @@ function SelectableQuestCompletion({
/>
);
}

function SelectablePlayer({
playerIds,
filters,
setFilters,
}: {
playerIds: string[];
filters: FilterState;
setFilters: (filters: Partial<FilterState>) => void;
}) {
const { t } = useTranslation();
const targetOptions = useMemo(
() => playerIds.map((id) => ({ value: id.toString(), label: id.toString() })),
[playerIds]
);

return (
<Select
data={targetOptions}
onChange={(value) => setFilters({ filterByPlayerId: value ? String(value) : null })}
placeholder={t("ui.select-player")}
value={filters.filterByPlayerId ?? null}
searchable
clearable
/>
);
}

function SelectablePlayerType({
playerTypes,
filters,
setFilters,
}: {
playerTypes: string[];
filters: FilterState;
setFilters: (filters: Partial<FilterState>) => void;
}) {
const { t } = useTranslation();
const targetOptions = useMemo(
() => playerTypes.map((id) => ({ value: id.toString(), label: t(`characters:${id}`, `ui:characters.${id}`) })),
[playerTypes]
);

return (
<Select
data={targetOptions}
onChange={(value) => setFilters({ filterByPlayerCharacter: value ? String(value) : null })}
value={filters.filterByPlayerCharacter ?? null}
placeholder={t("ui.select-character")}
searchable
clearable
/>
);
}
5 changes: 5 additions & 0 deletions src/pages/logs/useIndex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ export default function useIndex() {
fetchLogs();
};

const toggleAdvancedFilters = () => {
setFilters({ showAdvancedFilters: !filters.showAdvancedFilters });
};

const toggleSort = (newSortType: LogSortType) => {
setCurrentPage(1);

Expand All @@ -100,6 +104,7 @@ export default function useIndex() {
currentPage,
filters,
setFilters,
toggleAdvancedFilters,
toggleSort,
};
}
Loading

0 comments on commit 7e415b9

Please sign in to comment.