Skip to content

Commit

Permalink
Merge pull request #132 from Cycling74/fde/stored_sorting
Browse files Browse the repository at this point in the history
Persist parameter sorting as part of AppSettings
  • Loading branch information
fde31 authored Jun 18, 2024
2 parents c30d741 + 46a7d92 commit 0a9b904
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 98 deletions.
41 changes: 2 additions & 39 deletions src/actions/instances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,12 @@ import { PresetRecord } from "../models/preset";
import { AppSetting } from "../models/settings";
import { DataRefRecord } from "../models/dataref";
import { DataFileRecord } from "../models/datafile";
import { ParameterSortAttr, SortOrder } from "../lib/constants";

export enum InstanceActionType {
SET_INSTANCE = "SET_INSTANCE",
SET_INSTANCES = "SET_INSTANCES",
DELETE_INSTANCE = "DELETE_INSTANCE",
DELETE_INSTANCES = "DELETE_INSTANCES",
SET_INSTANCE_PARAMTER_SORT_ATTR = "SET_INSTANCE_PARAMTER_SORT_ATTR",
SET_INSTANCE_PARAMTER_SORT_ORDER = "SET_INSTANCE_PARAMTER_SORT_ORDER"
DELETE_INSTANCES = "DELETE_INSTANCES"
}

export interface ISetInstance extends ActionBase {
Expand Down Expand Up @@ -53,21 +50,7 @@ export interface IDeleteInstances extends ActionBase {
};
}

export interface ISetInstanceParameterSortAttr extends ActionBase {
type: InstanceActionType.SET_INSTANCE_PARAMTER_SORT_ATTR;
payload: {
attr: ParameterSortAttr;
};
}

export interface ISetInstanceParameterSortOrder extends ActionBase {
type: InstanceActionType.SET_INSTANCE_PARAMTER_SORT_ORDER;
payload: {
order: SortOrder;
};
}

export type InstanceAction = ISetInstance | ISetInstances | IDeleteInstance | IDeleteInstances | ISetInstanceParameterSortAttr | ISetInstanceParameterSortOrder;
export type InstanceAction = ISetInstance | ISetInstances | IDeleteInstance | IDeleteInstances;

export const setInstance = (instance: InstanceStateRecord): ISetInstance => ({
type: InstanceActionType.SET_INSTANCE,
Expand Down Expand Up @@ -425,23 +408,3 @@ export const updateInstanceParameterValueNormalized = (index: number, id: Parame
console.log(e);
}
};


// Parameter Display Settings
export const setParameterSortAttribute = (attr: ParameterSortAttr): InstanceAction => {
return {
type: InstanceActionType.SET_INSTANCE_PARAMTER_SORT_ATTR,
payload: {
attr
}
};
};

export const setParameterSortOrder = (order: SortOrder): InstanceAction => {
return {
type: InstanceActionType.SET_INSTANCE_PARAMTER_SORT_ORDER,
payload: {
order
}
};
};
138 changes: 109 additions & 29 deletions src/components/instance/paramTab.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,85 @@
import { Button, Group, Menu, SegmentedControl, Stack, Tabs } from "@mantine/core";
import { FunctionComponent, memo, useCallback, useEffect, useState } from "react";
import { ActionIcon, Button, Group, Popover, SegmentedControl, Select, Stack, Tabs, Text, TextInput } from "@mantine/core";
import { ChangeEvent, FC, FunctionComponent, KeyboardEvent, memo, useCallback, useEffect, useRef, useState } from "react";
import { InstanceTab, ParameterSortAttr, SortOrder } from "../../lib/constants";
import ParameterList from "../parameter/list";
import { ParameterRecord } from "../../models/parameter";
import classes from "./instance.module.css";
import { useAppDispatch, useAppSelector } from "../../hooks/useAppDispatch";
import { InstanceStateRecord } from "../../models/instance";
import { setInstanceParameterValueNormalizedOnRemote, setParameterSortAttribute, setParameterSortOrder } from "../../actions/instances";
import { setInstanceParameterValueNormalizedOnRemote } from "../../actions/instances";
import { Seq } from "immutable";
import { RootStateType } from "../../lib/store";
import { getParameterSortAttribute, getParameterSortOrder } from "../../selectors/instances";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowDownAZ, faArrowUpAZ, faSort } from "@fortawesome/free-solid-svg-icons";
import { faArrowDownAZ, faArrowUpAZ, faSearch, faSort, faXmark } from "@fortawesome/free-solid-svg-icons";
import { setAppSetting } from "../../actions/settings";
import { AppSetting } from "../../models/settings";
import { useDebouncedCallback, useDisclosure } from "@mantine/hooks";

export type InstanceParameterTabProps = {
instance: InstanceStateRecord;

type ParameterSearchInputProps = {
onSearch: (query: string) => any;
}

const ParameterSearchInput: FC<ParameterSearchInputProps> = memo(function WrappedParameterSearchInput({
onSearch
}) {

const [showSearchInput, showSearchInputActions] = useDisclosure();
const [searchValue, setSearchValue] = useState<string>("");
const searchInputRef = useRef<HTMLInputElement>();

const onChangeSearchValue = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setSearchValue(e.target.value);
}, [setSearchValue]);

const onBlur = useCallback(() => {
if (!searchValue?.length) showSearchInputActions.close();
}, [searchValue, showSearchInputActions]);

const onClear = useCallback(() => {
setSearchValue("");
searchInputRef.current?.focus();
}, [setSearchValue]);

const onKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Escape") {
if (searchValue.length) {
setSearchValue("");
} else {
searchInputRef.current?.blur();
}
}
}, [setSearchValue, searchInputRef, searchValue]);

useEffect(() => {
onSearch(searchValue);
}, [searchValue, onSearch]);

return (
showSearchInput || searchValue?.length ? (
<TextInput
autoFocus
ref={ searchInputRef }
onKeyDown={ onKeyDown }
onBlur={ onBlur }
onChange={ onChangeSearchValue }
leftSection={ <FontAwesomeIcon icon={ faSearch } size="xs" /> } size="xs"
rightSection={(
<ActionIcon variant="transparent" color="gray" onClick={ onClear } >
<FontAwesomeIcon icon={ faXmark } size="xs" />
</ActionIcon>
)}
value={ searchValue }
/>
) : (
<ActionIcon size="md" variant="default" onClick={ showSearchInputActions.open } >
<FontAwesomeIcon icon={ faSearch } size="xs" />
</ActionIcon>
)
);
});

const collator = new Intl.Collator("en-US");
const parameterComparators: Record<ParameterSortAttr, Record<SortOrder, (a: ParameterRecord, b: ParameterRecord) => number>> = {
[ParameterSortAttr.Index]: {
Expand Down Expand Up @@ -45,6 +108,9 @@ const getSortedParameterIds = (params: InstanceStateRecord["parameters"], attr:
return params.valueSeq().sort(parameterComparators[attr][order]).map(p => p.id);
};

export type InstanceParameterTabProps = {
instance: InstanceStateRecord;
}

const InstanceParameterTab: FunctionComponent<InstanceParameterTabProps> = memo(function WrappedInstanceParameterTab({
instance
Expand All @@ -55,55 +121,69 @@ const InstanceParameterTab: FunctionComponent<InstanceParameterTabProps> = memo(
getParameterSortOrder(state)
]);

const [sortedParamIds, setSortedParamIds] = useState<Seq.Indexed<string>>(getSortedParameterIds(instance.parameters, sortAttr, sortOrder));
const [searchValue, setSearchValue] = useState<string>("");

const [sortedParamIds, setSortedParamIds] = useState<Seq.Indexed<string>>(getSortedParameterIds(instance.parameters, sortAttr.value as ParameterSortAttr, sortOrder.value as SortOrder));

const dispatch = useAppDispatch();

const onChangeSortOrder = useCallback((value: string) => {
dispatch(setParameterSortOrder(value as SortOrder));
dispatch(setAppSetting(AppSetting.paramSortOrder, value));
}, [dispatch]);

const onChangeSortAttr = useCallback((value: string) => {
dispatch(setParameterSortAttribute(value as ParameterSortAttr));
dispatch(setAppSetting(AppSetting.paramSortAttribute, value));
}, [dispatch]);

const onSetNormalizedParamValue = useCallback((param: ParameterRecord, val: number) => {
dispatch(setInstanceParameterValueNormalizedOnRemote(instance, param, val));
}, [dispatch, instance]);

const onSearch = useDebouncedCallback((query: string) => {
setSearchValue(query);
}, 150);

useEffect(() => {
setSortedParamIds(getSortedParameterIds(instance.parameters, sortAttr, sortOrder));
setSortedParamIds(getSortedParameterIds(instance.parameters, sortAttr.value as ParameterSortAttr, sortOrder.value as SortOrder));
}, [instance.id, sortAttr, sortOrder]);

let parameters = sortedParamIds.map(id => instance.parameters.get(id)).filter(p => !!p);
if (searchValue?.length) parameters = parameters.filter(p => p.matchesQuery(searchValue));

return (
<Tabs.Panel value={ InstanceTab.Parameters } >
<Stack gap="md" h="100%">
<Group justify="flex-end" gap="xs">
<Menu position="bottom-end">
<Menu.Target>
<ParameterSearchInput onSearch={ onSearch } />
<Popover position="bottom-end" withArrow>
<Popover.Target>
<Button size="xs" variant="default" leftSection={ <FontAwesomeIcon icon={ faSort } /> } >
Sort
</Button>
</Menu.Target>
<Menu.Dropdown>
<Stack gap="xs" px="xs" py="xs">
<Menu.Label>Sort By</Menu.Label>
<SegmentedControl
</Popover.Target>
<Popover.Dropdown>
<Stack gap="sm">
<Select
size="xs"
label="Sort By"
name="sort_attribute"
onChange={ onChangeSortAttr }
data={ [{ label: "Name", value: ParameterSortAttr.Name }, { label: "Index", value: ParameterSortAttr.Index }] }
value={ sortAttr }
/>
<Menu.Label>Sort Order</Menu.Label>
<SegmentedControl
size="xs"
onChange={ onChangeSortOrder }
data={ [{ label: <FontAwesomeIcon icon={ faArrowDownAZ } size="sm" />, value: SortOrder.Asc }, { label: <FontAwesomeIcon icon={ faArrowUpAZ } size="sm" />, value: SortOrder.Desc }] }
value={ sortOrder }
data={ sortAttr.options }
value={ sortAttr.value as string }
/>
<div>
<Text size="xs">Sort Order</Text>
<SegmentedControl
size="xs"
fullWidth
onChange={ onChangeSortOrder }
data={ [{ label: <FontAwesomeIcon icon={ faArrowDownAZ } size="sm" />, value: SortOrder.Asc }, { label: <FontAwesomeIcon icon={ faArrowUpAZ } size="sm" />, value: SortOrder.Desc }] }
value={ sortOrder.value as string }
/>
</div>
</Stack>
</Menu.Dropdown>
</Menu>
</Popover.Dropdown>
</Popover>
</Group>
{
!instance.parameters.size ? (
Expand All @@ -112,7 +192,7 @@ const InstanceParameterTab: FunctionComponent<InstanceParameterTabProps> = memo(
</div>
) : (
<div className={ classes.paramSectionWrap } >
<ParameterList parameters={ sortedParamIds.map(id => instance.parameters.get(id)) } onSetNormalizedValue={ onSetNormalizedParamValue } />
<ParameterList parameters={ parameters } onSetNormalizedValue={ onSetNormalizedParamValue } />
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export enum SortOrder {
}

export enum ParameterSortAttr {
Index = "index",
Index = "displayorder",
Name = "name"
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AppSetting } from "../models/settings";
import { AppSettingRecord, AppSettingValue, appSettingDefaults } from "../models/settings";

const LS_KEY = "@@rnbo_runner_settings@@";
const LS_VERSION = 2;
const LS_VERSION = 3;

export const loadSettingsState = (): ImmuOrderedMap<AppSetting, AppSettingRecord> => {
let storedData: Partial<Record<AppSetting, AppSettingValue>> = {};
Expand Down
4 changes: 4 additions & 0 deletions src/models/parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,8 @@ export class ParameterRecord extends ImmuRecord<ParameterRecordProps>({
public setNormalizedValue(nv: number): ParameterRecord {
return this.set("normalizedValue", nv);
}

public matchesQuery(query: string): boolean {
return this.name.toLowerCase().includes(query);
}
}
24 changes: 21 additions & 3 deletions src/models/settings.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Record as ImmuRecord } from "immutable";
import { SettingsTab } from "../lib/constants";
import { ParameterSortAttr, SettingsTab, SortOrder } from "../lib/constants";

export enum AppSetting {
colorScheme = "colorscheme",
debugMessageOutput = "message_out_debug",
keyboardMIDIInput = "keyboard_midi_input"
keyboardMIDIInput = "keyboard_midi_input",
paramSortAttribute = "parameter_sort_attribute",
paramSortOrder = "parameter_sort_order"
}

export enum AppSettingType {
Expand All @@ -13,7 +15,7 @@ export enum AppSettingType {
Switch
}

export type AppSettingOptions = string[];
export type AppSettingOptions = string[] | Array<{ value: string; label: string; }>;
export type AppSettingValue = string | number | boolean;

export type AppSettingRecordProps = {
Expand Down Expand Up @@ -48,6 +50,22 @@ export const appSettingDefaults: Record<AppSetting, Omit<AppSettingRecordProps,
title: "Monitor Output Ports",
type: AppSettingType.Boolean,
value: true
},
[AppSetting.paramSortAttribute]: {
description: "Configure whether to sort instance parameters by name or 'displayorder'",
tab: SettingsTab.UI,
options: [{ label: "Displayorder", value: ParameterSortAttr.Index }, { label: "Name", value: ParameterSortAttr.Name }],
title: "Parameter Sort Attribute",
type: AppSettingType.String,
value: ParameterSortAttr.Name
},
[AppSetting.paramSortOrder]: {
description: "Configure in which order to sort instance parameters",
tab: SettingsTab.UI,
options: [{ label: "Ascending", value: SortOrder.Asc }, { label: "Descending", value: SortOrder.Desc }],
title: "Parameter Sort Order",
type: AppSettingType.String,
value: SortOrder.Asc
}
};

Expand Down
23 changes: 1 addition & 22 deletions src/reducers/instances.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { Map as ImmuMap } from "immutable";
import { InstanceStateRecord } from "../models/instance";
import { InstanceAction, InstanceActionType } from "../actions/instances";
import { ParameterSortAttr, SortOrder } from "../lib/constants";

export interface InstanceInstancesState {
instances: ImmuMap<InstanceStateRecord["id"], InstanceStateRecord>;
parameterSortAttribute: ParameterSortAttr;
parameterSortOrder: SortOrder;
}

export const instances = (state: InstanceInstancesState = {

instances: ImmuMap<InstanceStateRecord["id"], InstanceStateRecord>(),
parameterSortAttribute: ParameterSortAttr.Name,
parameterSortOrder: SortOrder.Asc
instances: ImmuMap<InstanceStateRecord["id"], InstanceStateRecord>()

}, action: InstanceAction): InstanceInstancesState => {

Expand Down Expand Up @@ -59,22 +54,6 @@ export const instances = (state: InstanceInstancesState = {
};
}

case InstanceActionType.SET_INSTANCE_PARAMTER_SORT_ATTR: {
const { attr } = action.payload;
return {
...state,
parameterSortAttribute: attr
};
}

case InstanceActionType.SET_INSTANCE_PARAMTER_SORT_ORDER: {
const { order } = action.payload;
return {
...state,
parameterSortOrder: order
};
}

default:
return state;
}
Expand Down
2 changes: 2 additions & 0 deletions src/reducers/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { loadSettingsState, purgeSettingsState, storeSettingsState } from "../li
export type AppSettings = {
[AppSetting.colorScheme]: string;
[AppSetting.debugMessageOutput]: boolean;
[AppSetting.paramSortAttribute]: boolean;
[AppSetting.paramSortOrder]: boolean;
}

export type SettingsState = {
Expand Down
Loading

0 comments on commit 0a9b904

Please sign in to comment.