Skip to content
Merged
16 changes: 14 additions & 2 deletions src/features/ConfigFile.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import { Editor, OnMount, useMonaco } from "@monaco-editor/react";
import { skipToken } from "@reduxjs/toolkit/query";
import { useEffect, useRef } from "react";
import { Button } from "react-bootstrap";
import useAppParams from "../shared/hooks/useAppParams";
import { useGetConfigQuery } from "../store/apiSlice";

type IConfigViewer = Parameters<OnMount>[0];

const ConfigFile = () => {
const { appId } = useAppParams();
const { data: config } = useGetConfigQuery(appId ? { appId } : skipToken);
const { data: config, refetch, isFetching } = useGetConfigQuery(appId ? { appId } : skipToken);

if (!config) {
return <div>Config Data Loading or Not Available</div>;
}

return <ConfigFileRender config={config} />;
return (
<div className="d-flex flex-column h-100">
<div className="mb-2 d-flex justify-content-end">
<Button variant="outline-secondary" size="sm" onClick={refetch} disabled={isFetching}>
{isFetching ? 'Refreshing…' : 'Refresh Config'}
</Button>
</div>
<div className="flex-grow-1 overflow-hidden">
<ConfigFileRender config={config} />
</div>
</div>
);
};

export default ConfigFile;
Expand Down
12 changes: 11 additions & 1 deletion src/features/DebugConsole/DebugConsole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import {
useSetLoadConfigMutation,
useSetRestartMutation
} from "../../store/apiSlice";
import { selectSearchText } from '../../store/debugConsole/debugConsoleSelectors';
import { debugConsoleActions } from '../../store/debugConsole/debugConsoleSlice';
import { useAppDispatch, useAppSelector } from '../../store/hooks';
import { RootState } from '../../store/store';
import ConsoleWindow from "./ConsoleWindow";
import { DebugFilters } from "./DebugFilters";
Expand All @@ -21,8 +24,10 @@ const DebugConsole = ({isConnected, join, stop, clear}: DebugConsoleProps) => {
//* HOOKS ***********************************************************/
const [showModal, setShowModal] = useState(false);
const { appId } = useAppParams();
const dispatch = useAppDispatch();
const messages = useSelector((state: RootState) => state.websocket.messages);
const failedUrl = useSelector((state: RootState) => state.websocket.failedUrl);
const searchText = useAppSelector(selectSearchText);
const certUrl = failedUrl
Comment thread
ndorin marked this conversation as resolved.
? new URL(failedUrl).origin.replace(/^wss:/, 'https:').replace(/^ws:/, 'http:')
: null;
Expand Down Expand Up @@ -119,7 +124,12 @@ const DebugConsole = ({isConnected, join, stop, clear}: DebugConsoleProps) => {
{', accept the certificate, then try "Start Debug Session" again.'}
</Alert>
)}
<ListFiltersHeader showSearch filters={<DebugFilters />} />
<ListFiltersHeader
showSearch
searchValue={searchText}
onSearchChange={(val) => dispatch(debugConsoleActions.setSearchText(val))}
filters={<DebugFilters />}
/>
<ConsoleWindow filteredItems={filteredItems}/>
</div>

Expand Down
3 changes: 2 additions & 1 deletion src/features/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ const LoginForm = () => {
}

return (
<div className="d-flex flex-column justify-content-center align-items-center h-100">
<div className="d-flex flex-column justify-content-center align-items-center h-100 position-relative">
Comment thread
ndorin marked this conversation as resolved.
Outdated
<span className="position-absolute top-0 end-0 p-2 text-muted small">Version: {APP_VERSION}</span>
<h1 className="mb-5 text-center">PepperDash Essentials Developer Tools</h1>
<div className="w-100" style={{ maxWidth: '360px' }}>
<h2 className="mb-4">Sign In</h2>
Expand Down
70 changes: 52 additions & 18 deletions src/shared/FilterSearchText.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,59 @@
import { ChangeEvent, useEffect, useState } from 'react';
import { FormControl } from 'react-bootstrap';
import { useSearchParams } from 'react-router-dom';
import { ChangeEvent, useEffect, useRef, useState } from "react";
import { FormControl } from "react-bootstrap";
import { useSearchParams } from "react-router-dom";
Comment thread
ndorin marked this conversation as resolved.
Outdated

export const FilterSearchText = ({
disabled,
placeholder,
value: controlledValue,
onChangeValue,
}: FilterSearchTextProps) => {
/* HOOKS ***********************************************************/
/** Debounce timer for search box */
let searchTimerHandle: NodeJS.Timeout;
const PARAM = 'searchText';
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const PARAM = "searchText";
const [searchParams, setSearchParams] = useSearchParams();
const [searchText, setSearchText] = useState<string>('');
const [searchText, setSearchText] = useState<string>(controlledValue ?? "");

Comment thread
ndorin marked this conversation as resolved.
/* FUNCTIONS *******************************************************/
/** Handles search text change, after 1s debounce */
function searchTextChange(change: ChangeEvent<HTMLInputElement>) {
setSearchText(change.target.value);

if (searchTimerHandle) clearTimeout(searchTimerHandle);
searchTimerHandle = setTimeout(() => {
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
timerRef.current = null;
const val: string = change.target.value.trim();
const tokens = val.split(' ');
searchParams.delete(PARAM);
if (val.length) tokens.forEach((t) => searchParams.append(PARAM, t));
setSearchParams(searchParams);

if (onChangeValue) {
// Controlled (Redux) mode — call the provided callback
onChangeValue(val);
} else {
// URL params mode (default)
const tokens = val.split(" ");
Comment thread
ndorin marked this conversation as resolved.
searchParams.delete(PARAM);
if (val.length) tokens.forEach((t) => searchParams.append(PARAM, t));
setSearchParams(searchParams);
}
}, 1000);
}

/* EFFECTS *********************************************************/
/** Watch params for relevant changes and update dropdowns **/
/** Clear any pending debounce timer on unmount */
useEffect(() => {
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
}, []);

/** In URL-params mode, sync local state from params. In controlled mode, sync from prop. **/
useEffect(() => {
setSearchText(searchParams.getAll(PARAM).join(' '));
}, [searchParams]);
if (onChangeValue) {
setSearchText(controlledValue ?? "");
} else {
setSearchText(searchParams.getAll(PARAM).join(" "));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [controlledValue, searchParams]);
Comment thread
ndorin marked this conversation as resolved.
Comment thread
ndorin marked this conversation as resolved.

/* RENDER **********************************************************/
return (
Expand All @@ -49,7 +70,20 @@ export const FilterSearchText = ({
);
};

interface FilterSearchTextProps {
type FilterSearchTextBaseProps = {
disabled?: boolean;
placeholder?: string;
}
};
type FilterSearchTextControlledProps = FilterSearchTextBaseProps & {
/** Controlled value (Redux mode). */
value: string;
onChangeValue: (val: string) => void;
};
type FilterSearchTextUncontrolledProps = FilterSearchTextBaseProps & {
value?: undefined;
onChangeValue?: undefined;
};

type FilterSearchTextProps =
| FilterSearchTextControlledProps
| FilterSearchTextUncontrolledProps;
8 changes: 6 additions & 2 deletions src/shared/ListFiltersHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
groupBy,
listTypeButtons,
showSearch,
searchValue,
onSearchChange,
rightContent,
}: ListFiltersHeaderProps) => {
return (
<div className="d-flex justify-content-between mb-2 user-select-none align-items-center flex-nowrap">
<div className="ps-2 d-flex justify-content-between mb-2 user-select-none align-items-center flex-nowrap">
<div className="row row-cols-sm-auto g-3 user-select-none flex-nowrap">
{showSearch && (
<div className="col-8">
<FilterSearchText />
<FilterSearchText value={searchValue} onChangeValue={onSearchChange} />

Check failure on line 19 in src/shared/ListFiltersHeader.tsx

View workflow job for this annotation

GitHub Actions / build / Build

Type '{ value: string | undefined; onChangeValue: ((val: string) => void) | undefined; }' is not assignable to type 'IntrinsicAttributes & FilterSearchTextProps'.
</div>
Comment thread
ndorin marked this conversation as resolved.
)}
<div className="col-16 d-none d-lg-block">{filters}</div>
Expand All @@ -37,6 +39,8 @@

interface ListFiltersHeaderProps {
showSearch?: boolean;
searchValue?: string;
onSearchChange?: (val: string) => void;
filters: ReactNode;
groupBy?: ReactNode;
listTypeButtons?: ReactNode;
Expand Down
Loading