Skip to content

Commit

Permalink
feat: add external windonw button to record details modal
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewinci committed Jan 9, 2023
1 parent cb56b58 commit ab80738
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 27 deletions.
26 changes: 13 additions & 13 deletions frontend/src/components/new-window-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,23 @@ import { IconExternalLink } from "@tabler/icons";
import { WebviewWindow } from "@tauri-apps/api/window";
import { useParsedUrl } from "../hooks";

type NewWindowButtonProps = {
type OpenNewWindowParams = {
url: string;
windowTitle: string;
iconSize?: number;
beforeOpen?: () => void;
afterOpen?: () => void;
};

type NewWindowButtonProps = {
iconSize?: number;
} & OpenNewWindowParams;
export const NewWindowButton = (props: NewWindowButtonProps) => {
const { url, windowTitle, iconSize } = props;
const { iconSize } = props;
const { isModal, openNewWindow } = useWindowHandler();
return (
<Tooltip hidden={isModal} label="Open in a new window">
<ActionIcon
hidden={isModal}
onClick={() =>
openNewWindow({
url,
windowTitle,
})
}>
<ActionIcon hidden={isModal} onClick={() => openNewWindow(props)}>
<IconExternalLink size={iconSize ?? 22} />
</ActionIcon>
</Tooltip>
Expand All @@ -31,15 +30,16 @@ export const useWindowHandler = () => {
const { isModal, clusterName } = useParsedUrl();
return {
isModal,
openNewWindow: (params: { url: string; windowTitle: string }) => {
const { url, windowTitle } = params;
openNewWindow: (params: OpenNewWindowParams) => {
const { url, windowTitle, beforeOpen, afterOpen } = params;
// check if the window is already open
const currentWebView = WebviewWindow.getByLabel(url);
if (currentWebView) {
// just focus the already open window
currentWebView.setFocus();
return;
}
if (beforeOpen) beforeOpen();
const webview = new WebviewWindow(url, {
url,
title: `${clusterName} - ${windowTitle}`,
Expand All @@ -52,7 +52,7 @@ export const useWindowHandler = () => {
// since the webview window is created asynchronously,
// Tauri emits the `tauri://created` and `tauri://error` to notify you of the creation response
webview.once("tauri://created", () => {
//console.log("Created");
if (afterOpen) afterOpen();
});
webview.once("tauri://error", (e) => {
console.error(`Unable to open the new window`);
Expand Down
26 changes: 22 additions & 4 deletions frontend/src/components/resizable-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { Modal, Title } from "@mantine/core";
import { ActionIcon, Group, Modal, Title } from "@mantine/core";
import { IconX } from "@tabler/icons";
import { NewWindowButton } from "./new-window-button";

type ResizableModalProps = {
children: React.ReactNode;
title: string;
opened: boolean;
minHeight?: number;
maxHeight?: number | string;
newWindowSettings?: {
url: string;
windowTitle: string;
beforeOpen?: () => void;
};
onClose: () => void;
};

export const ResizableModal = (props: ResizableModalProps) => {
const { children, title, opened, minHeight, maxHeight, onClose } = props;
const { children, title, opened, minHeight, maxHeight, newWindowSettings, onClose } = props;
return (
<Modal
withCloseButton={false}
onClose={onClose}
opened={opened}
title={<Title order={3}>{title}</Title>}
closeOnClickOutside={false}
styles={{
modal: {
Expand All @@ -32,7 +39,18 @@ export const ResizableModal = (props: ResizableModalProps) => {
height: "calc(100% - 50px)",
},
}}>
{children}
<>
<Group mb={10} position="apart">
<Title order={3}>{title}</Title>
<Group spacing={2}>
{newWindowSettings && <NewWindowButton {...newWindowSettings} afterOpen={onClose} />}
<ActionIcon onClick={onClose}>
<IconX size={18} />
</ActionIcon>
</Group>
</Group>
{children}
</>
</Modal>
);
};
3 changes: 3 additions & 0 deletions frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Topic } from "./pages/topics/topic/main";
import { Schema } from "./pages/schema-registry/schema";
import { withPropsFromUrlParams } from "./helpers/with-props-from-url";
import { useInitMonaco } from "./init-monaco";
import { RecordDetailsWindow } from "./pages/topics/modals/record-view-modal";

const AppContainer = () => {
return (
Expand Down Expand Up @@ -50,6 +51,7 @@ const ModalContainer = () => {
// modal pages
const SingleTopicPage = withPropsFromUrlParams(Topic);
const SingleSchemaPage = withPropsFromUrlParams(Schema);
const SingleRecordPage = withPropsFromUrlParams(RecordDetailsWindow);

const InsulatorRoutes = () => {
useInitMonaco();
Expand All @@ -58,6 +60,7 @@ const InsulatorRoutes = () => {
<Routes>
<Route path="/modal" element={<ModalContainer />}>
<Route path="cluster/:clusterId/topic/:topicName" element={<SingleTopicPage />} />
<Route path="cluster/:clusterId/topic/:topicName/record/:id" element={<SingleRecordPage />} />
<Route path="cluster/:clusterId/schema/:schemaName" element={<SingleSchemaPage />} />
</Route>
<Route path="/" element={<AppContainer />}>
Expand Down
64 changes: 54 additions & 10 deletions frontend/src/pages/topics/modals/record-view-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,80 @@
import { Input, Group, Stack, TextInput } from "@mantine/core";
import { Input, Group, Stack, TextInput, Container, Title } from "@mantine/core";

import dayjs from "dayjs";
import { CodeEditor, ResizableModal } from "../../../components";
import { pretty } from "../../../helpers/json";
import { KafkaRecord } from "../../../models";

type RecordDetailsModalProps = {
clusterId: string;
topic: string;
record: KafkaRecord;
opened: boolean;
onClose: () => void;
};

export const RecordDetailsModal = (props: RecordDetailsModalProps) => {
const { record, topic, opened, onClose } = props;
const RecordDetailsForm = (props: RecordDetailsModalProps & { heightOffset: number }) => {
const { record, topic, heightOffset } = props;
const timestamp = record?.timestamp ? dayjs(record.timestamp).toISOString() : "N/A";
return (
<ResizableModal onClose={onClose} opened={opened} title={"Record details"}>
<>
<Stack spacing={3}>
<Group grow position="apart">
<TextInput label="Topic name" value={topic} />
<TextInput label="Timestamp(UTC)" value={timestamp} />
<TextInput readOnly label="Topic name" value={topic} />
<TextInput readOnly label="Timestamp(UTC)" value={timestamp} />
</Group>
<Group grow position="apart">
<TextInput label="Partition" value={record.partition} />
<TextInput label="Offset" value={record.offset} />
<TextInput readOnly label="Partition" value={record.partition} />
<TextInput readOnly label="Offset" value={record.offset} />
</Group>
<TextInput label="Key" value={record.key} />
<TextInput readOnly label="Key" value={record.key} />
</Stack>
<Input.Wrapper mt={3} style={{ height: "calc(100% - 210px)" }} label="Value">
<Input.Wrapper mt={3} style={{ height: `calc(100% - ${heightOffset}px)` }} label="Value">
<CodeEditor path={topic} language="json" height={"100%"} value={pretty(record.payload)} readOnly />
</Input.Wrapper>
</>
);
};
export const RecordDetailsModal = (props: RecordDetailsModalProps) => {
const { clusterId, record, topic, opened, onClose } = props;
const id = recordId({
...record,
topic,
clusterId: "123",
});
return (
<ResizableModal
onClose={onClose}
opened={opened}
title={"Record details"}
newWindowSettings={{
url: `/modal/cluster/${clusterId}/topic/${topic}/record/${id}`,
windowTitle: `Record ${topic} ${record.key}`,
beforeOpen: () => storeProps(id, props),
}}>
<RecordDetailsForm {...props} heightOffset={200} />
</ResizableModal>
);
};

export const RecordDetailsWindow = ({ id }: { id: string } & JSX.IntrinsicAttributes) => {
const props = JSON.parse(localStorage.getItem(id) ?? "{}") as RecordDetailsModalProps;
return (
<Container pt={10} px={20} style={{ height: "100%", maxWidth: "unset" }}>
<Title mb={10} order={3}>
Record details
</Title>
<RecordDetailsForm {...props} heightOffset={270} />
</Container>
);
};

const recordId = (props: { clusterId: string; topic: string; offset: number; partition: number }) => {
const { clusterId, topic, offset, partition } = props;
return `${clusterId}-${topic}-${partition}-${offset}`;
};

const storeProps = (id: string, props: RecordDetailsModalProps): string => {
localStorage.setItem(id, JSON.stringify(props));
return id;
};
1 change: 1 addition & 0 deletions frontend/src/pages/topics/topic/record-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export const RecordsList = forwardRef<RecordsListRef, RecordsListProps>((props,
</div>
{recordModalState.record && (
<RecordDetailsModal
clusterId={clusterId}
record={recordModalState.record}
onClose={() => setRecordModalState((s) => ({ ...s, opened: false }))}
opened={recordModalState.opened}
Expand Down

0 comments on commit ab80738

Please sign in to comment.