Skip to content

Commit 5861b01

Browse files
author
ptwng
authored
Also updated Taginput to wrapper class (#57)
* Add a new DataprocWidget base class that injects MUI themes. * wrap tag input * wrap tag input
1 parent 7eafbe8 commit 5861b01

13 files changed

+183
-67
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@
7878
"react-arborist": "^3.1.0",
7979
"react-spinners": "^0.13.8",
8080
"react-table": "^7.8.0",
81-
"react-tagsinput": "^3.20.3",
8281
"react-toastify": "^9.1.3",
8382
"yup": "^1.2.0"
8483
},
@@ -117,6 +116,7 @@
117116
"prettier": "^2.8.7",
118117
"react": "^18.2.0",
119118
"react-dom": "^18.2.0",
119+
"react-tagsinput": "^3.20.3",
120120
"rimraf": "^4.4.1",
121121
"semantic-ui-react": "^2.1.4",
122122
"source-map-loader": "^1.0.2",

src/batches/batches.tsx

+3-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { ReactWidget } from '@jupyterlab/apputils';
1918
import React, { useState, useEffect, useRef } from 'react';
2019
import {
2120
authApi,
@@ -42,6 +41,7 @@ import 'react-toastify/dist/ReactToastify.css';
4241
import { deleteBatchAPI } from '../utils/batchService';
4342
import CreateBatch from './createBatch';
4443
import PollingTimer from '../utils/pollingTimer';
44+
import { DataprocWidget } from '../controls/DataprocWidget';
4545

4646
const iconDelete = new LabIcon({
4747
name: 'launcher:delete-icon',
@@ -320,12 +320,8 @@ const BatchesComponent = (): React.JSX.Element => {
320320
);
321321
};
322322

323-
export class Batches extends ReactWidget {
324-
constructor() {
325-
super();
326-
}
327-
328-
render(): React.JSX.Element {
323+
export class Batches extends DataprocWidget {
324+
renderInternal(): React.JSX.Element {
329325
return <BatchesComponent />;
330326
}
331327
}

src/batches/createBatch.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ import {
4444
SERVICE_ACCOUNT,
4545
STATUS_RUNNING
4646
} from '../utils/const';
47-
import TagsInput from 'react-tagsinput';
48-
import 'react-tagsinput/react-tagsinput.css';
4947
import LabelProperties from '../jobs/labelProperties';
5048
import { authApi } from '../utils/utils';
5149
import { ClipLoader } from 'react-spinners';
@@ -56,6 +54,7 @@ import { Select } from '../controls/MuiWrappedSelect';
5654
import { Input } from '../controls/MuiWrappedInput';
5755
import { Radio } from '@mui/material';
5856
import { Dropdown } from '../controls/MuiWrappedDropdown';
57+
import { TagsInput } from '../controls/MuiWrappedTagsInput';
5958

6059
type Project = {
6160
projectId: string;

src/cluster/cluster.tsx

+3-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { ReactWidget } from '@jupyterlab/apputils';
1918
import { LabIcon } from '@jupyterlab/ui-components';
2019
import React, { useEffect, useRef, useState } from 'react';
2120
import { ClipLoader } from 'react-spinners';
@@ -44,6 +43,7 @@ import {
4443
} from '../utils/utils';
4544
import ClusterDetails from './clusterDetails';
4645
import ListCluster from './listCluster';
46+
import { DataprocWidget } from '../controls/DataprocWidget';
4747

4848
const iconStart = new LabIcon({
4949
name: 'launcher:start-icon',
@@ -445,12 +445,8 @@ const ClusterComponent = (): React.JSX.Element => {
445445
);
446446
};
447447

448-
export class Cluster extends ReactWidget {
449-
constructor() {
450-
super();
451-
}
452-
453-
render(): React.JSX.Element {
448+
export class Cluster extends DataprocWidget {
449+
renderInternal(): React.JSX.Element {
454450
return <ClusterComponent />;
455451
}
456452
}

src/controls/DataprocWidget.tsx

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import React from 'react';
2+
import { ReactWidget, IThemeManager } from '@jupyterlab/apputils';
3+
import { ThemeProvider, createTheme } from '@mui/material';
4+
5+
/**
6+
* Theme to use when the current Jupyter Theme is light.
7+
*/
8+
export const lightTheme = createTheme({
9+
palette: {
10+
mode: 'light'
11+
}
12+
});
13+
14+
/**
15+
* Theme to use when the current Jupyter Theme is dark.
16+
*/
17+
export const darkTheme = createTheme({
18+
palette: {
19+
mode: 'dark',
20+
primary: {
21+
main: '#FF0064'
22+
},
23+
secondary: {
24+
main: '#7000F2'
25+
}
26+
}
27+
});
28+
29+
/**
30+
* Base Widget class that injects MUI themeprovider context so
31+
* mui components will update when the jupyter theme changes.
32+
* TODO: Add auth context provider here?
33+
*/
34+
export abstract class DataprocWidget extends ReactWidget {
35+
/**
36+
* Whether or not currently jupyter is displaying a light theme.
37+
*/
38+
isLight: boolean = true;
39+
40+
/**
41+
* @param themeManager Need thememanager to listen to theme changes
42+
* and to retrieve current theme properties.
43+
*/
44+
constructor(protected themeManager: IThemeManager) {
45+
super();
46+
this.themeManager.themeChanged.connect(this.onThemeChanged, this);
47+
this.updateIsLight();
48+
}
49+
50+
/**
51+
* Updates the isLight property based on current theme properties.
52+
* @returns Whether or not isLight is updated based on the current theme.
53+
*/
54+
private updateIsLight() {
55+
const prevIsLight = this.isLight;
56+
const currentTheme = this.themeManager.theme;
57+
if (currentTheme) {
58+
this.isLight = this.themeManager.isLight(currentTheme);
59+
} else {
60+
this.isLight = true;
61+
}
62+
return this.isLight !== prevIsLight;
63+
}
64+
65+
private onThemeChanged() {
66+
if (this.updateIsLight()) {
67+
// Force a rerender if the theme has changed.
68+
this.update();
69+
}
70+
}
71+
72+
dispose() {
73+
this.themeManager.themeChanged.disconnect(this.onThemeChanged, this);
74+
return super.dispose();
75+
}
76+
77+
/**
78+
* Renders the theme context providers as well as the child components
79+
* as specified by renderInternal.
80+
*/
81+
protected render(): React.ReactElement {
82+
return (
83+
<ThemeProvider theme={this.isLight ? lightTheme : darkTheme}>
84+
{this.renderInternal()}
85+
</ThemeProvider>
86+
);
87+
}
88+
89+
/**
90+
* Child classes are expected to implement this instead of render().
91+
*/
92+
protected abstract renderInternal(): React.ReactElement;
93+
}

src/controls/MuiWrappedTagsInput.tsx

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
import { styled } from '@mui/material';
18+
import { MuiChipsInput } from 'mui-chips-input';
19+
import React from 'react';
20+
import type { ReactTagsInputProps } from 'react-tagsinput';
21+
22+
function TagsInputInternal(props: ReactTagsInputProps) {
23+
const { className, value, onChange, addOnBlur, inputProps } = props;
24+
return (
25+
<MuiChipsInput
26+
className={className}
27+
onChange={tags => onChange(tags, [], [])}
28+
addOnBlur={!!addOnBlur}
29+
value={value}
30+
placeholder={inputProps?.placeholder}
31+
/>
32+
);
33+
}
34+
35+
export const TagsInput = styled(TagsInputInternal)<ReactTagsInputProps>({
36+
marginTop: '10px'
37+
});

src/dpms/databaseInfo.tsx

+8-10
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { ReactWidget } from '@jupyterlab/apputils';
1918
import React from 'react';
19+
import { IThemeManager } from '@jupyterlab/apputils';
20+
import { DataprocWidget } from '../controls/DataprocWidget';
2021

2122
interface IDatabaseProps {
2223
title: string;
@@ -63,21 +64,18 @@ const DatabaseInfo = ({
6364
);
6465
};
6566

66-
export class Database extends ReactWidget {
67-
dataprocMetastoreServices: string;
68-
databaseDetails: Record<string, string>;
67+
export class Database extends DataprocWidget {
6968
constructor(
7069
title: string,
71-
dataprocMetastoreServices: string,
72-
databaseDetails: Record<string, string>
70+
private dataprocMetastoreServices: string,
71+
private databaseDetails: Record<string, string>,
72+
themeManager: IThemeManager
7373
) {
74-
super();
74+
super(themeManager);
7575
this.title.label = title;
76-
this.dataprocMetastoreServices = dataprocMetastoreServices;
77-
this.databaseDetails = databaseDetails;
7876
}
7977

80-
render(): React.ReactElement {
78+
renderInternal(): React.ReactElement {
8179
return (
8280
<DatabaseInfo
8381
title={this.title.label}

src/dpms/dpmsWidget.tsx

+18-12
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { ReactWidget } from '@jupyterlab/apputils';
1918
import { JupyterLab } from '@jupyterlab/application';
2019
import React, { useEffect, useState } from 'react';
2120
import { Tree, NodeRendererProps } from 'react-arborist';
@@ -47,6 +46,8 @@ import { authApi } from '../utils/utils';
4746
import { Table } from './tableInfo';
4847
import { ClipLoader } from 'react-spinners';
4948
import { ToastContainer, toast } from 'react-toastify';
49+
import { DataprocWidget } from '../controls/DataprocWidget';
50+
import { IThemeManager } from '@jupyterlab/apputils';
5051

5152
const iconDatabase = new LabIcon({
5253
name: 'launcher:database-icon',
@@ -99,7 +100,13 @@ const calculateDepth = (node: any): number => {
99100
return depth;
100101
};
101102

102-
const DpmsComponent = ({ app }: { app: JupyterLab }): JSX.Element => {
103+
const DpmsComponent = ({
104+
app,
105+
themeManager
106+
}: {
107+
app: JupyterLab;
108+
themeManager: IThemeManager;
109+
}): JSX.Element => {
103110
const [searchTerm, setSearchTerm] = useState('');
104111
const [notebookValue, setNotebookValue] = useState<string>('');
105112
const [dataprocMetastoreServices, setDataprocMetastoreServices] =
@@ -268,7 +275,8 @@ fetching database name from fully qualified name structure */
268275
const content = new Database(
269276
node.data.name,
270277
dataprocMetastoreServices,
271-
databaseDetails
278+
databaseDetails,
279+
themeManager
272280
);
273281
const widget = new MainAreaWidget<Database>({ content });
274282
const widgetId = 'node-widget-db';
@@ -285,7 +293,8 @@ fetching database name from fully qualified name structure */
285293
dataprocMetastoreServices,
286294
database,
287295
column,
288-
tableDescription
296+
tableDescription,
297+
themeManager
289298
);
290299
const widget = new MainAreaWidget<Table>({ content });
291300
const widgetId = `node-widget-${uuidv4()}`;
@@ -707,15 +716,12 @@ fetching database name from fully qualified name structure */
707716
);
708717
};
709718

710-
export class dpmsWidget extends ReactWidget {
711-
app: JupyterLab;
712-
713-
constructor(app: JupyterLab) {
714-
super();
715-
this.app = app;
719+
export class dpmsWidget extends DataprocWidget {
720+
constructor(private app: JupyterLab, themeManager: IThemeManager) {
721+
super(themeManager);
716722
}
717723

718-
render(): JSX.Element {
719-
return <DpmsComponent app={this.app} />;
724+
renderInternal(): JSX.Element {
725+
return <DpmsComponent app={this.app} themeManager={this.themeManager} />;
720726
}
721727
}

src/dpms/tableInfo.tsx

+12-19
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
*/
1717

1818
import React from 'react';
19-
import { ReactWidget } from '@jupyterlab/apputils';
2019
import { useTable } from 'react-table';
20+
import { IThemeManager } from '@jupyterlab/apputils';
21+
import { DataprocWidget } from '../controls/DataprocWidget';
2122
interface IColumn {
2223
name: string;
2324
type: string;
@@ -41,8 +42,8 @@ const TableInfo = ({
4142
}: IDatabaseProps): React.JSX.Element => {
4243
const table = {
4344
'Table name': title,
44-
'Description': tableDescription[title],
45-
'Database': database,
45+
Description: tableDescription[title],
46+
Database: database,
4647
'Dataproc Metastore Instance': dataprocMetastoreServices
4748
};
4849

@@ -145,27 +146,19 @@ const TableInfo = ({
145146
);
146147
};
147148

148-
export class Table extends ReactWidget {
149-
dataprocMetastoreServices: string;
150-
database!: string;
151-
column: IColumn[];
152-
tableDescription: Record<string, string>;
153-
149+
export class Table extends DataprocWidget {
154150
constructor(
155151
title: string,
156-
dataprocMetastoreServices: string,
157-
database: string,
158-
column: IColumn[],
159-
tableDescription: Record<string, string>
152+
private dataprocMetastoreServices: string,
153+
private database: string,
154+
private column: IColumn[],
155+
private tableDescription: Record<string, string>,
156+
themeManager: IThemeManager
160157
) {
161-
super();
162-
this.dataprocMetastoreServices = dataprocMetastoreServices;
163-
this.database = database;
164-
this.column = column;
165-
this.tableDescription = tableDescription;
158+
super(themeManager);
166159
}
167160

168-
render(): React.JSX.Element {
161+
renderInternal(): React.JSX.Element {
169162
return (
170163
<TableInfo
171164
title={this.title.label}

0 commit comments

Comments
 (0)