Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions tavern/internal/www/build/asset-manifest.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tavern/internal/www/build/index.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions tavern/internal/www/src/components/UserFilterBar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { gql, useQuery } from "@apollo/client";
import { useQuery } from "@apollo/client";
import Select from "react-select";
import { UserQueryTopLevel } from "../utils/interfacesQuery";
import { useFilters } from "../context/FilterContext/FilterContext";
import { GET_USER_QUERY } from "../utils/queries";

const UserFilterBar = () => {
const { filters, updateFilters } = useFilters();
const { filters, updateFilters, isLocked } = useFilters();
const { data, loading } = useQuery<UserQueryTopLevel>(GET_USER_QUERY);

const options = data?.users.edges.map((edge) => ({
Expand All @@ -19,7 +19,7 @@ const UserFilterBar = () => {
<div className="flex flex-col gap-1">
<label className="text-gray-700">User</label>
<Select
isDisabled={filters.isLocked || loading}
isDisabled={isLocked || loading}
isLoading={loading}
isClearable
placeholder="Filter by User"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useToast, Tooltip as ChakraTooltip, TooltipProps as ChakraTooltipProps } from "@chakra-ui/react";
import { Tooltip as ChakraTooltip, TooltipProps as ChakraTooltipProps } from "@chakra-ui/react";

const Tooltip = ({ label, ...props }: ChakraTooltipProps) => {
return <ChakraTooltip
Expand Down
60 changes: 38 additions & 22 deletions tavern/internal/www/src/context/FilterContext/FilterContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export enum FilterFieldType {
const STORAGE_KEY = 'realm-filters-v1.1'

export type Filters = {
isLocked: boolean,
questName: string,
taskOutput: string,
beaconFields: Array<FilterBarOption>,
Expand All @@ -25,8 +24,10 @@ export type Filters = {
userId: string,
}

// Storage format includes isLocked alongside filter values
type StoredFilters = Filters & { isLocked: boolean }

const defaultFilters: Filters = {
isLocked: false,
questName: "",
taskOutput: "",
beaconFields: [],
Expand All @@ -48,13 +49,13 @@ function isValidFilterBarOption(item: any): item is FilterBarOption {
)
}

function validateStoredFilters(data: any): Filters {
//If isLocked is not the set state, reset filters
function validateStoredFilters(data: any): StoredFilters {
//If isLocked is not set, reset filters
if (!data || typeof data !== 'object' || !data.isLocked) {
return defaultFilters
return { ...defaultFilters, isLocked: false }
}

const schema: Record<keyof Filters, (value: any) => boolean> = {
const schema: Record<keyof StoredFilters, (value: any) => boolean> = {
isLocked: (v) => typeof v === 'boolean',
questName: (v) => typeof v === 'string',
taskOutput: (v) => typeof v === 'string',
Expand All @@ -67,29 +68,33 @@ function validateStoredFilters(data: any): Filters {

for (const [key, validator] of Object.entries(schema)) {
if (!(key in data) || !validator(data[key])) {
return defaultFilters
return { ...defaultFilters, isLocked: false }
}
}

return data as Filters
return data as StoredFilters
}

function loadFiltersFromStorage(): Filters {
function loadFromStorage(): { filters: Filters, isLocked: boolean } {
if (typeof window === 'undefined') {
return defaultFilters
return { filters: defaultFilters, isLocked: false }
}

const stored = sessionStorage.getItem(STORAGE_KEY)
if (!stored) {
return defaultFilters
return { filters: defaultFilters, isLocked: false }
}

try {
const validFilters = validateStoredFilters(JSON.parse(stored));
if (!validFilters.isLocked) return defaultFilters;
return validFilters
const validated = validateStoredFilters(JSON.parse(stored));
// Only restore filters if they were locked
if (!validated.isLocked) {
return { filters: defaultFilters, isLocked: false }
}
const { isLocked, ...filters } = validated;
return { filters, isLocked }
} catch {
return defaultFilters
return { filters: defaultFilters, isLocked: false }
}
}

Expand Down Expand Up @@ -121,19 +126,26 @@ export function calculateTotalFilterCount(filters: Filters, fields: FilterFieldT
type FilterContextType = {
filters: Filters
filterCount: number
isLocked: boolean
setIsLocked: React.Dispatch<React.SetStateAction<boolean>>
updateFilters: (updates: Partial<Filters>) => void
resetFilters: () => void
}

const FilterContext = createContext<FilterContextType>({
filters: defaultFilters,
filterCount: 0,
isLocked: false,
setIsLocked: () => { },
updateFilters: () => { },
resetFilters: () => { },
});

export function FilterProvider({ children }: { children: React.ReactNode }) {
const [filters, setFilters] = useState<Filters>(loadFiltersFromStorage);
const initialState = loadFromStorage();
const [filters, setFilters] = useState<Filters>(initialState.filters);
const [isLocked, setIsLocked] = useState<boolean>(initialState.isLocked);

const allFields = Object.values(FilterFieldType);
const filterCount = useMemo(() => calculateTotalFilterCount(filters, allFields), [filters, allFields]);

Expand All @@ -153,31 +165,35 @@ export function FilterProvider({ children }: { children: React.ReactNode }) {

useEffect(() => {
if (previousPathname.current !== location.pathname) {
if (!filters.isLocked) {
if (!isLocked) {
resetFilters();
}
previousPathname.current = location.pathname;
}
}, [location.pathname, filters.isLocked]);
}, [location.pathname, isLocked]);

useEffect(() => {
if (typeof window !== 'undefined') {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(filters))
// Store both filters and isLocked together
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ ...filters, isLocked }))
}
}, [filters]); // Don't collapse these useEffects, this helps fix race condition
}, [filters, isLocked]); // Don't collapse these useEffects, this helps fix race condition

useEffect(() => {
const handleStorage = (event: StorageEvent) => {
if (event.key === STORAGE_KEY && event.newValue) {
setFilters(validateStoredFilters(JSON.parse(event.newValue)))
const validated = validateStoredFilters(JSON.parse(event.newValue));
const { isLocked: storedIsLocked, ...storedFilters } = validated;
setFilters(storedFilters);
setIsLocked(storedIsLocked);
}
}
window.addEventListener('storage', handleStorage)
return () => window.removeEventListener('storage', handleStorage)
}, [])

return (
<FilterContext.Provider value={{ filters, filterCount, updateFilters, resetFilters }}>
<FilterContext.Provider value={{ filters, filterCount, isLocked, setIsLocked, updateFilters, resetFilters }}>
{children}
</FilterContext.Provider>
)
Expand Down
24 changes: 12 additions & 12 deletions tavern/internal/www/src/context/FilterContext/FilterControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function FilterControls() {
const { pathname } = useLocation();
const fieldsToRender = getFilterFields(pathname);

const { filters, updateFilters } = useFilters();
const { filters, updateFilters, isLocked, setIsLocked } = useFilters();

if (!fieldsToRender) return null;

Expand All @@ -51,7 +51,7 @@ export default function FilterControls() {
key={field}
setFiltersSelected={(newValue) => updateFilters({ 'beaconFields': newValue })}
filtersSelected={filters.beaconFields}
isDisabled={filters.isLocked}
isDisabled={isLocked}
/>
</div>
)
Expand All @@ -61,7 +61,7 @@ export default function FilterControls() {
<div key={field}>
<FreeTextSearch
key={field}
isDisabled={filters.isLocked}
isDisabled={isLocked}
defaultValue={filters.questName}
setSearch={(newValue) => updateFilters({ 'questName': newValue })}
placeholder="Quest name"
Expand All @@ -74,7 +74,7 @@ export default function FilterControls() {
<div key={field}>
<FreeTextSearch
key={field}
isDisabled={filters.isLocked}
isDisabled={isLocked}
defaultValue={filters.taskOutput}
setSearch={(newValue) => updateFilters({ 'taskOutput': newValue })}
placeholder="Task output"
Expand All @@ -89,7 +89,7 @@ export default function FilterControls() {
key={field}
setFiltersSelected={(newValue) => updateFilters({ 'tomeFields': newValue })}
filtersSelected={filters.tomeFields}
isDisabled={filters.isLocked}
isDisabled={isLocked}
/>
</div>
);
Expand All @@ -99,7 +99,7 @@ export default function FilterControls() {
<div key={field}>
<FreeTextSearch
key={field}
isDisabled={filters.isLocked}
isDisabled={isLocked}
defaultValue={filters.tomeMultiSearch}
setSearch={(newValue) => updateFilters({ 'tomeMultiSearch': newValue })}
placeholder="Tome definition & values"
Expand All @@ -112,7 +112,7 @@ export default function FilterControls() {
<div key={field}>
<FreeTextSearch
key={field}
isDisabled={filters.isLocked}
isDisabled={isLocked}
defaultValue={filters.assetName}
setSearch={(newValue) => updateFilters({ 'assetName': newValue })}
placeholder="Asset name"
Expand All @@ -137,7 +137,7 @@ export default function FilterControls() {
<div className="flex flex-row justify-between pb-2 border-gray-100 border-b-2 items-center">
<h3 className="font-medium text-lg text-gray-700">Filters</h3>
<Tooltip
label={filters.isLocked ? "Click to unlock filter state" : "Click to lock filter state"}
label={isLocked ? "Click to unlock filter state" : "Click to lock filter state"}
bg="white"
color="gray.600"
borderWidth="1px"
Expand All @@ -146,10 +146,10 @@ export default function FilterControls() {
<Button
buttonVariant="ghost"
buttonStyle={{ color: "purple", size: "md" }}
onClick={() => updateFilters({ 'isLocked': !filters.isLocked })}
leftIcon={filters.isLocked ? <LockKeyhole className="w-5 h-5" /> : <UnlockKeyhole className="w-5 h-5" />}
aria-label={filters.isLocked ? "Unlock filters" : "Lock filters"}
aria-pressed={filters.isLocked}
onClick={() => setIsLocked((prev) => !prev)}
leftIcon={isLocked ? <LockKeyhole className="w-5 h-5" /> : <UnlockKeyhole className="w-5 h-5" />}
aria-label={isLocked ? "Unlock filters" : "Lock filters"}
aria-pressed={isLocked}
/>
</Tooltip>
</div>
Expand Down
Loading
Loading