Skip to content

Commit

Permalink
feat: purchase date filter complete
Browse files Browse the repository at this point in the history
  • Loading branch information
Tormak9970 committed Jul 1, 2024
1 parent f7d25b4 commit b897875
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 9 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/commit-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ jobs:
fetch-depth: 0

- name: Setup Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 16
node-version: 18

- name: Setup Pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v4
with:
version: 6.0.2
version: latest

- name: Print versions
run: |
Expand Down
101 changes: 100 additions & 1 deletion src/components/filters/FilterOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ const SizeOnDiskFilterOptions: VFC<FilterOptionsProps<'size on disk'>> = ({ inde
};

/**
* The options for a time played filter.
* The options for a release date filter.
*/
const ReleaseDateFilterOptions: VFC<FilterOptionsProps<'release date'>> = ({ index, setContainingGroupFilters, filter, containingGroupFilters }) => {
const [date, setDate] = useState<DateObj | undefined>(filter.params.date);
Expand Down Expand Up @@ -630,6 +630,103 @@ const ReleaseDateFilterOptions: VFC<FilterOptionsProps<'release date'>> = ({ ind
);
};

/**
* The options for a purchase date filter.
*/
const PurchaseDateFilterOptions: VFC<FilterOptionsProps<'purchase date'>> = ({ index, setContainingGroupFilters, filter, containingGroupFilters }) => {
const [date, setDate] = useState<DateObj | undefined>(filter.params.date);
const [dateIncludes, setDateIncludes] = useState<DateIncludes>(filter.params.date ? (filter.params.date.day === undefined ? (filter.params.date.month === undefined ? DateIncludes.yearOnly : DateIncludes.monthYear) : DateIncludes.dayMonthYear) : DateIncludes.dayMonthYear);
const [thresholdType, setThresholdType] = useState<ThresholdCondition>(filter.params.condition);
const [byDaysAgo, setByDaysAgo] = useState(filter.params.daysAgo !== undefined);
const [daysAgo, setDaysAgo] = useState<number>(filter.params.daysAgo ?? 30);

function onDateChange(dateSelection: DateSelection) {
const updatedFilter = { ...filter };
updatedFilter.params.date = dateSelection.data;
const updatedFilters = [...containingGroupFilters];
updatedFilters[index] = updatedFilter;
setContainingGroupFilters(updatedFilters);
setDate(dateSelection.data);
}

function onThreshTypeChange({ data: threshType }: { data: ThresholdCondition; }) {
const updatedFilter = { ...filter };
updatedFilter.params.condition = threshType;
const updatedFilters = [...containingGroupFilters];
updatedFilters[index] = updatedFilter;
setContainingGroupFilters(updatedFilters);
setThresholdType(threshType);
}

function onByDaysAgoChange(byDaysAgo: boolean) {
const updatedFilter = { ...filter };
if (byDaysAgo) {
delete updatedFilter.params.date;
updatedFilter.params.daysAgo = daysAgo;
} else {
delete updatedFilter.params.daysAgo;
updatedFilter.params.date = date;
}
const updatedFilters = [...containingGroupFilters];
updatedFilters[index] = updatedFilter;
setContainingGroupFilters(updatedFilters);
setByDaysAgo(byDaysAgo);
}

function onSliderChange(value: number) {
const updatedFilter = { ...filter };
updatedFilter.params.daysAgo = value;
const updatedFilters = [...containingGroupFilters];
updatedFilters[index] = updatedFilter;
setContainingGroupFilters(updatedFilters);
setDaysAgo(value);
}

return (
<Field label={`Purchased ${byDaysAgo ? `${daysAgo} day${daysAgo === 1 ? '' : 's'} ago or ${thresholdType === 'above' ? 'later' : 'earlier'}` : `${dateIncludes === DateIncludes.dayMonthYear ? 'on' : 'in'} or ${thresholdType === 'above' ? 'after' : 'before'}...`}`}
description={
<Focusable style={{ display: 'flex', flexDirection: 'row' }}>
{byDaysAgo ?
<Slider value={daysAgo} min={0} max={3000} onChange={onSliderChange} /> :
<DatePicker
focusDropdowns={true}
modalType='simple'
buttonContainerStyle={{ flex: 1 }}
onChange={onDateChange}
dateIncludes={dateIncludes}
selectedDate={date}
toLocaleStringOptions={{ dateStyle: 'long' }}
animate={true}
transparencyMode={EnhancedSelectorTransparencyMode.selection}
focusRingMode={EnhancedSelectorFocusRingMode.transparentOnly}
/>}
<div style={{ margin: '0 10px' }}>
<Dropdown
rgOptions={[
{ label: 'By Day', data: DateIncludes.dayMonthYear },
{ label: 'By Month', data: DateIncludes.monthYear },
{ label: 'By Year', data: DateIncludes.yearOnly },
{ label: 'By Days Ago', data: 'byDaysAgo' }
]}
selectedOption={dateIncludes}
onChange={option => {
if (option.data === 'byDaysAgo') {
onByDaysAgoChange(true);
} else {
if (byDaysAgo) onByDaysAgoChange(false);
setDateIncludes(option.data);
}
}}
/>
</div>
<div>
<Dropdown rgOptions={[{ label: 'Earliest', data: 'above' }, { label: 'Latest', data: 'below' }]} selectedOption={thresholdType} onChange={onThreshTypeChange} />
</div>
</Focusable>}
/>
);
};

/**
* The options for a last played filter.
*/
Expand Down Expand Up @@ -924,6 +1021,8 @@ export const FilterOptions: VFC<FilterOptionsProps<FilterType>> = ({ index, filt
return <SizeOnDiskFilterOptions index={index} filter={filterCopy as TabFilterSettings<'size on disk'>} containingGroupFilters={containingGroupFilters} setContainingGroupFilters={setContainingGroupFilters} />;
case "release date":
return <ReleaseDateFilterOptions index={index} filter={filterCopy as TabFilterSettings<'release date'>} containingGroupFilters={containingGroupFilters} setContainingGroupFilters={setContainingGroupFilters} />;
case "purchase date":
return <PurchaseDateFilterOptions index={index} filter={filterCopy as TabFilterSettings<'purchase date'>} containingGroupFilters={containingGroupFilters} setContainingGroupFilters={setContainingGroupFilters} />;
case "last played":
return <LastPlayedFilterOptions index={index} filter={filterCopy as TabFilterSettings<'last played'>} containingGroupFilters={containingGroupFilters} setContainingGroupFilters={setContainingGroupFilters} />;
case "family sharing":
Expand Down
20 changes: 18 additions & 2 deletions src/components/filters/FilterPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,20 @@ const ReleaseDateFilterPreview: VFC<FilterPreviewProps<'release date'>> = ({ fil
return <FilterPreviewGeneric filter={filter} displayData={displayData} />;
};

const PurchaseDateFilterPreview: VFC<FilterPreviewProps<'purchase date'>> = ({ filter }) => {
let displayData: string;

if (filter.params.date) {
const { day, month, year } = filter.params.date;
displayData = `${!day ? 'In' : 'On'} or ${filter.params.condition === 'above' ? 'after' : 'before'} ${dateToLabel(year, month, day, { dateStyle: 'long' })}`;
} else {
const daysAgo = filter.params.daysAgo;
displayData = `${daysAgo} day${daysAgo === 1 ? '' : 's'} ago or ${filter.params.condition === 'above' ? 'later' : 'earlier'}`;
}

return <FilterPreviewGeneric filter={filter} displayData={displayData} />;
};

const LastPlayedFilterPreview: VFC<FilterPreviewProps<'last played'>> = ({ filter }) => {
let displayData: string;

Expand Down Expand Up @@ -127,8 +141,8 @@ const SteamFeaturesFilterPreview: VFC<FilterPreviewProps<'steam features'>> = ({
};

const AchievementsFilterPreview: VFC<FilterPreviewProps<'achievements'>> = ({ filter }) => {
const { completionPercentage, condition } = filter.params;
return <FilterPreviewGeneric filter={filter} displayData={`${completionPercentage}% or ${condition === 'above' ? 'more' : 'less'} achievements completed`} />;
const { threshold, thresholdType, condition } = filter.params;
return <FilterPreviewGeneric filter={filter} displayData={`${threshold}${thresholdType === "percent" ? "%" : ""} or ${condition === 'above' ? 'more' : 'less'} achievements completed`} />;
};

const SDCardFilterPreview: VFC<FilterPreviewProps<'sd card'>> = ({ filter }) => {
Expand Down Expand Up @@ -171,6 +185,8 @@ export const FilterPreview: VFC<FilterPreviewProps<FilterType>> = ({ filter }) =
return <SizeOnDiskFilterPreview filter={filter as TabFilterSettings<'size on disk'>} />;
case "release date":
return <ReleaseDateFilterPreview filter={filter as TabFilterSettings<'release date'>} />;
case "purchase date":
return <PurchaseDateFilterPreview filter={filter as TabFilterSettings<'purchase date'>} />;
case "last played":
return <LastPlayedFilterPreview filter={filter as TabFilterSettings<'last played'>} />;
case "family sharing":
Expand Down
44 changes: 42 additions & 2 deletions src/components/filters/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { MicroSDeckInterop } from '../../lib/controllers/MicroSDeckInterop';
import { PluginController } from "../../lib/controllers/PluginController";
import { DateIncludes, DateObj } from '../generic/DatePickers';
import { STEAM_FEATURES_ID_MAP } from "./SteamFeatures";
import { FaCheckCircle, FaHdd, FaSdCard, FaTrophy, FaUserFriends } from "react-icons/fa";
import { FaCheckCircle, FaHdd, FaSdCard, FaShoppingCart, FaTrophy, FaUserFriends } from "react-icons/fa";
import { IoGrid } from "react-icons/io5";
import { SiSteamdeck } from "react-icons/si";
import { FaAward, FaBan, FaCalendarDays, FaCloudArrowDown, FaCompactDisc, FaListCheck, FaPlay, FaRegClock, FaSteam, FaTags, FaUserPlus } from "react-icons/fa6";
import { BsClockHistory, BsRegex } from "react-icons/bs";
import { LuCombine } from "react-icons/lu";
import { LogController } from "../../lib/controllers/LogController";

export type FilterType = 'collection' | 'installed' | 'regex' | 'friends' | 'tags' | 'whitelist' | 'blacklist' | 'merge' | 'platform' | 'deck compatibility' | 'review score' | 'time played' | 'size on disk' | 'release date' | 'last played' | 'family sharing' | 'demo' | 'streamable' | 'steam features' | 'achievements' | 'sd card';
export type FilterType = 'collection' | 'installed' | 'regex' | 'friends' | 'tags' | 'whitelist' | 'blacklist' | 'merge' | 'platform' | 'deck compatibility' | 'review score' | 'time played' | 'size on disk' | 'release date' | 'purchase date' | 'last played' | 'family sharing' | 'demo' | 'streamable' | 'steam features' | 'achievements' | 'sd card';

export type TimeUnit = 'minutes' | 'hours' | 'days';
export type ThresholdCondition = 'above' | 'below';
Expand Down Expand Up @@ -41,6 +41,7 @@ type ReviewScoreFilterParams = { scoreThreshold: number, condition: ThresholdCon
type TimePlayedFilterParams = { timeThreshold: number, condition: ThresholdCondition, units: TimeUnit };
type SizeOnDiskFilterParams = { gbThreshold: number, condition: ThresholdCondition };
type ReleaseDateFilterParams = { date?: DateObj, daysAgo?: number, condition: ThresholdCondition };
type PurchaseDateFilterParams = { date?: DateObj, daysAgo?: number, condition: ThresholdCondition };
type LastPlayedFilterParams = { date?: DateObj, daysAgo?: number, condition: ThresholdCondition };
type FamilySharingFilterParams = { isFamilyShared: boolean };
type DemoFilterParams = { isDemo: boolean };
Expand All @@ -57,6 +58,8 @@ type AchievementsFilterParams = {
}
type SdCardParams = { card: undefined | string }; //use undefined for currently inserted card

// appOverview.rt_purchased_time

export type FilterParams<T extends FilterType> =
T extends 'collection' ? CollectionFilterParams :
T extends 'installed' ? InstalledFilterParams :
Expand All @@ -72,6 +75,7 @@ export type FilterParams<T extends FilterType> =
T extends 'time played' ? TimePlayedFilterParams :
T extends 'size on disk' ? SizeOnDiskFilterParams :
T extends 'release date' ? ReleaseDateFilterParams :
T extends 'purchase date' ? PurchaseDateFilterParams :
T extends 'last played' ? LastPlayedFilterParams :
T extends 'family sharing' ? FamilySharingFilterParams :
T extends 'demo' ? DemoFilterParams :
Expand Down Expand Up @@ -109,6 +113,7 @@ export const FilterDefaultParams: { [key in FilterType]: FilterParams<key> } = {
"time played": { timeThreshold: 60, condition: 'above', units: 'minutes' },
"size on disk": { gbThreshold: 10, condition: 'above' },
"release date": { date: undefined, condition: 'above' },
"purchase date": { date: undefined, condition: 'above' },
"last played": { date: undefined, condition: 'above' },
"family sharing": { isFamilyShared: true },
"demo": { isDemo: true },
Expand Down Expand Up @@ -136,6 +141,7 @@ export const FilterDescriptions: { [filterType in FilterType]: string } = {
"time played": "Selects apps based on your play time.",
"size on disk": "Selects apps based on their install size.",
"release date": "Selects apps based on their release date.",
"purchase date": "Selects apps based on when you purchased them.",
"last played": "Selects apps based on when they were last played.",
"family sharing": "Selects apps that are/aren't shared from family members.",
demo: "Selects apps that are/aren't demos.",
Expand Down Expand Up @@ -163,6 +169,7 @@ export const FilterIcons: { [filterType in FilterType]: IconType } = {
"time played": FaRegClock,
"size on disk": FaHdd,
"release date": FaCalendarDays,
"purchase date": FaShoppingCart,
"last played": BsClockHistory,
"family sharing": FaUserPlus,
demo: FaCompactDisc,
Expand Down Expand Up @@ -198,6 +205,7 @@ export function canBeInverted(filter: TabFilterSettings<FilterType>): boolean {
case "time played":
case "size on disk":
case "release date":
case "purchase date":
case "last played":
case "demo":
case "family sharing":
Expand Down Expand Up @@ -228,6 +236,7 @@ export function isValidParams(filter: TabFilterSettings<FilterType>): boolean {
case "merge":
return (filter as TabFilterSettings<'merge'>).params.filters.length !== 0;
case "release date":
case "purchase date":
case "last played":
return (filter as TabFilterSettings<'release date'>).params.date !== undefined || (filter as TabFilterSettings<'release date'>).params.daysAgo !== undefined;
case "steam features":
Expand Down Expand Up @@ -402,6 +411,7 @@ export function validateFilter(filter: TabFilterSettings<FilterType>): Validatio
case "review score":
case "time played":
case "release date":
case "purchase date":
case "last played":
case "demo":
case "family sharing":
Expand Down Expand Up @@ -513,6 +523,36 @@ export class Filter {
releaseTimeMs < new Date(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() + 1 - params.daysAgo!).getTime();
}
},
'purchase date': (params: FilterParams<'purchase date'>, appOverview: SteamAppOverview) => {
let purchaseTimeMs;
if (appOverview.rt_purchased_time) purchaseTimeMs = appOverview.rt_purchased_time * 1000;
else return false;

//by date case
if (params.date) {
const { day, month, year } = params.date;

if (params.condition === 'above') {
return purchaseTimeMs >= new Date(year, (month ?? 1) - 1, day ?? 1).getTime();
} else {
const dateIncludes = day === undefined ? (month === undefined ? DateIncludes.yearOnly : DateIncludes.monthYear) : DateIncludes.dayMonthYear;
switch (dateIncludes) {
case DateIncludes.dayMonthYear:
return purchaseTimeMs < new Date(year, month! - 1, day! + 1).getTime();
case DateIncludes.monthYear:
return purchaseTimeMs < new Date(year, month!, 1).getTime();
case DateIncludes.yearOnly:
return purchaseTimeMs < new Date(year + 1, 0, 1).getTime();
}
}
//by days ago case
} else {
const today = new Date();
return params.condition === 'above' ?
purchaseTimeMs >= new Date(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() - params.daysAgo!).getTime() :
purchaseTimeMs < new Date(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() + 1 - params.daysAgo!).getTime();
}
},
'last played': (params: FilterParams<'last played'>, appOverview: SteamAppOverview) => {
const lastPlayedTimeMs = appOverview.rt_last_time_played * 1000;
if (lastPlayedTimeMs === 0) return false;
Expand Down

0 comments on commit b897875

Please sign in to comment.