Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/404 #142

Closed
wants to merge 2 commits into from
Closed
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
5 changes: 5 additions & 0 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,15 @@ def do_GET(self):
fext = path.splitext(self.path)[1]
normPath = path.normpath(self.path).split('/')
normPath.remove('')

fpath = path.join(args.directory, '{os.sep}'.join(normPath))
# handle /instances/12 -> /instances/[index].html mapping
ipath = path.join(normPath[0], "[index].html")

if not path.isfile(fpath) and not fext and path.isfile(fpath + '.html'):
self.path = self.path + '.html'
elif len(normPath) == 2 and path.isfile(path.join(args.directory, ipath)):
self.path = path.join("/", ipath)

return SimpleHTTPRequestHandler.do_GET(self)

Expand Down
24 changes: 24 additions & 0 deletions src/actions/instances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,17 @@ export const setInstanceDataRefValueOnRemote = throttle((instance: InstanceState
oscQueryBridge.sendPacket(writePacket(message));
}, 100);

export const setInstanceParameterMetaOnRemote = (instance: InstanceStateRecord, param: ParameterRecord, value: string): AppThunk =>
() => {
const message = {
address: `${param.path}/meta`,
args: [
{ type: "s", value }
]
};

oscQueryBridge.sendPacket(writePacket(message));
};

// Updates in response to remote OSCQuery Updates
export const updateInstancePresetEntries = (index: number, entries: OSCQueryRNBOInstancePresetEntries): AppThunk =>
Expand Down Expand Up @@ -408,3 +419,16 @@ export const updateInstanceParameterValueNormalized = (index: number, id: Parame
console.log(e);
}
};

export const updateInstanceParameterMeta = (index: number, id: ParameterRecord["id"], value: string): AppThunk =>
(dispatch, getState) => {
try {
const state = getState();
const instance = getInstanceByIndex(state, index);
if (!instance) return;

dispatch(setInstance(instance.setParameterMeta(id, value)));
} catch (e) {
console.log(e);
}
};
82 changes: 51 additions & 31 deletions src/components/instance/paramTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { ParameterRecord } from "../../models/parameter";
import classes from "./instance.module.css";
import { useAppDispatch } from "../../hooks/useAppDispatch";
import { InstanceStateRecord } from "../../models/instance";
import { setInstanceParameterValueNormalizedOnRemote } from "../../actions/instances";
import { setInstanceParameterMetaOnRemote, setInstanceParameterValueNormalizedOnRemote } from "../../actions/instances";
import { OrderedSet as ImmuOrderedSet } from "immutable";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowDownAZ, faArrowUpAZ, faSearch, faSort, faXmark } from "@fortawesome/free-solid-svg-icons";
import { faArrowDownAZ, faArrowUpAZ, faInfoCircle, faSearch, faSort, faXmark } from "@fortawesome/free-solid-svg-icons";
import { setAppSetting } from "../../actions/settings";
import { AppSetting, AppSettingRecord } from "../../models/settings";
import { useDebouncedCallback, useDisclosure } from "@mantine/hooks";
import { ParamMetaEditorModal } from "../parameter/metaEditorModal";

type ParameterSearchInputProps = {
onSearch: (query: string) => any;
Expand Down Expand Up @@ -119,6 +120,7 @@ const InstanceParameterTab: FunctionComponent<InstanceParameterTabProps> = memo(

const [searchValue, setSearchValue] = useState<string>("");
const [sortedParamIds, setSortedParamIds] = useState<ImmuOrderedSet<ParameterRecord["id"]>>(getSortedParameterIds(instance.parameters, sortAttr.value as ParameterSortAttr, sortOrder.value as SortOrder));
const [showMetaModal, { toggle: toggleMetaModal, close: closeMetaModal }] = useDisclosure();

const dispatch = useAppDispatch();

Expand All @@ -138,6 +140,10 @@ const InstanceParameterTab: FunctionComponent<InstanceParameterTabProps> = memo(
setSearchValue(query);
}, 150);

const onSaveParameterMeta = useCallback((param: ParameterRecord, metaValue: string) => {
dispatch(setInstanceParameterMetaOnRemote(instance, param, metaValue));
}, [dispatch, instance]);

useEffect(() => {
setSortedParamIds(getSortedParameterIds(instance.parameters, sortAttr.value as ParameterSortAttr, sortOrder.value as SortOrder));
}, [instance, sortAttr, sortOrder]);
Expand All @@ -148,37 +154,51 @@ const InstanceParameterTab: FunctionComponent<InstanceParameterTabProps> = memo(
return (
<Tabs.Panel value={ InstanceTab.Parameters } >
<Stack gap="md" h="100%">
<Group justify="flex-end" gap="xs">
<ParameterSearchInput onSearch={ onSearch } />
<Popover position="bottom-end" withArrow>
<Popover.Target>
<Button size="xs" variant="default" leftSection={ <FontAwesomeIcon icon={ faSort } /> } >
Sort
</Button>
</Popover.Target>
<Popover.Dropdown>
<Stack gap="sm">
<Select
size="xs"
label="Sort By"
name="sort_attribute"
onChange={ onChangeSortAttr }
data={ sortAttr.options }
value={ sortAttr.value as string }
/>
<div>
<Text size="xs">Sort Order</Text>
<SegmentedControl
{
showMetaModal ? (
<ParamMetaEditorModal
parameters={ parameters }
onClose={ closeMetaModal }
onSaveParameterMeta={ onSaveParameterMeta }
/>
) : null
}
<Group justify="space-between">
<Button leftSection={ <FontAwesomeIcon icon={ faInfoCircle } /> } onClick={ toggleMetaModal } disabled={ !parameters.size } size="xs" variant="default" >
Edit Meta
</Button>
<Group justify="flex-end" gap="xs">
<ParameterSearchInput onSearch={ onSearch } />
<Popover position="bottom-end" withArrow>
<Popover.Target>
<Button size="xs" variant="default" leftSection={ <FontAwesomeIcon icon={ faSort } /> } >
Sort
</Button>
</Popover.Target>
<Popover.Dropdown>
<Stack gap="sm">
<Select
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 }
label="Sort By"
name="sort_attribute"
onChange={ onChangeSortAttr }
data={ sortAttr.options }
value={ sortAttr.value as string }
/>
</div>
</Stack>
</Popover.Dropdown>
</Popover>
<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>
</Popover.Dropdown>
</Popover>
</Group>
</Group>
{
!instance.parameters.size ? (
Expand Down
178 changes: 178 additions & 0 deletions src/components/parameter/metaEditorModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { Button, Group, Modal, Select, Stack, Text, Textarea } from "@mantine/core";
import { ChangeEvent, FC, FormEvent, memo, useCallback, useRef, useState } from "react";
import { useIsMobileDevice } from "../../hooks/useIsMobileDevice";
import { OrderedSet } from "immutable";
import { ParameterRecord } from "../../models/parameter";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { modals } from "@mantine/modals";
import { AnyJson } from "../../lib/types";

export type ParamMetaEditorModalProps = {
onClose: () => any;
onSaveParameterMeta: (param: ParameterRecord, meta: string) => any;
parameters: OrderedSet<ParameterRecord>;
};

export const ParamMetaEditorModal: FC<ParamMetaEditorModalProps> = memo(function WrappedParamMetaEditorModal({
onClose,
onSaveParameterMeta,
parameters
}) {

const [hasChanges, setHasChanges] = useState<boolean>(false);
const [value, setValue] = useState<string | undefined>(parameters.first()?.meta);
const [error, setError] = useState<Error | undefined>();
const [activeParam, setActiveParam] = useState<ParameterRecord | undefined>(parameters.first());
const selectRef = useRef<HTMLInputElement>();

const showFullScreen = useIsMobileDevice();

const onTriggerClose = useCallback(() => {
if (hasChanges) {
modals.openConfirmModal({
title: "Unsaved Changes",
centered: true,
children: (
<Text size="sm" id="red">
The meta data of parameter {`'${activeParam.name}'`} has unsaved changes. Are you sure you want to discard them?
</Text>
),
labels: { confirm: "Discard", cancel: "Cancel" },
confirmProps: { color: "red" },
onConfirm: () => onClose()
});
} else {
onClose();
}
}, [onClose, activeParam, hasChanges]);

const onSelectParam = useCallback((id: string) => {
const p = parameters.find(p => p.id === id);
if (!p || id === activeParam.id) return;
if (!hasChanges) {
setActiveParam(p);
setValue(p.meta);
setHasChanges(false);
selectRef.current?.blur();
} else {
selectRef.current?.blur();
modals.openConfirmModal({
title: "Unsaved Changes",
centered: true,
children: (
<Text size="sm" id="red">
The meta data of parameter {`'${activeParam.name}'`} has unsaved changes. Are you sure you want to discard them?
</Text>
),
labels: { confirm: "Discard", cancel: "Cancel" },
confirmProps: { color: "red" },
onConfirm: () => {
setActiveParam(p);
setValue(p.meta);
setHasChanges(false);
}
});
}
}, [parameters, activeParam, setActiveParam, setValue, hasChanges, setHasChanges, selectRef]);

const onReset = useCallback(() => {
setValue(activeParam?.meta || "");
setHasChanges(false);
}, [setValue, setHasChanges, activeParam]);

const onInputChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
if (error) {
try {
JSON.parse(e.currentTarget.value); // ensure valid
setError(undefined);
} catch (err) {
setError(new Error("Invalid JSON."));
}
}
setValue(e.currentTarget.value);
setHasChanges(true);
}, [setValue, setHasChanges, error, setError]);

const onInputBlur = useCallback(() => {
try {
const j: AnyJson = JSON.parse(value); // ensure valid
setValue(JSON.stringify(j, null, 2));
setError(undefined);
} catch (err) {
setError(new Error("Invalid JSON."));
}
}, [value, setError, setValue]);

const onSaveValue = useCallback((e: FormEvent) => {
e.preventDefault();
try {
JSON.parse(value); // ensure valid
setHasChanges(false);
onSaveParameterMeta(activeParam, value);
} catch (err) {
setError(new Error("Invalid JSON."));
}
}, [setError, setHasChanges, activeParam, value]);


return (
<Modal.Root opened onClose={ onTriggerClose } fullScreen={ showFullScreen } size="xl">
<Modal.Overlay />
<Modal.Content>
<Modal.Header>
<Modal.Title>Edit Parameter Meta</Modal.Title>
<Modal.CloseButton />
</Modal.Header>
<Modal.Body>
<form onSubmit={ onSaveValue } >
<Stack gap="md">
<Select
data={ parameters.valueSeq().toArray().map(p => ({ label: p.name, value: p.id })) }
name="parameter_id"
label="Parameter"
description="Select the parameter of which you'd like to edit the meta data"
onChange={ onSelectParam }
value={ activeParam.id }
allowDeselect={ false }
searchable
nothingFoundMessage="Parameter not found..."
ref={ selectRef }
/>
<Textarea
label="Parameter Meta Data"
description="Parameter meta data as JSON"
placeholder="{}"
autosize
minRows={ 10 }
onChange={ onInputChange }
value={ value }
error={ error?.message }
onBlur={ onInputBlur }
/>
<Group justify="flex-end">
<Button.Group>
<Button
variant="light"
color="gray"
disabled={ !hasChanges }
leftSection={ <FontAwesomeIcon icon={ faXmark } /> }
onClick={ onReset }
>
Reset
</Button>
<Button
type="submit"
disabled={ !hasChanges }
>
Save
</Button>
</Button.Group>
</Group>
</Stack>
</form>
</Modal.Body>
</Modal.Content>
</Modal.Root>
);
});
Loading
Loading