From e72558b7ef270e417a751a7a5951c7d576cf9697 Mon Sep 17 00:00:00 2001 From: Silvia Zeni Date: Tue, 28 Jun 2022 10:24:29 +0100 Subject: [PATCH 01/16] feat: Add ZooDialog --- src/components/PageSelector.tsx | 28 ++++++++++++++++++++++++---- src/index.tsx | 9 ++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/components/PageSelector.tsx b/src/components/PageSelector.tsx index 2c63772b..de8c96c5 100644 --- a/src/components/PageSelector.tsx +++ b/src/components/PageSelector.tsx @@ -1,7 +1,6 @@ -import { ButtonGroup } from "@mui/material"; -import { ReactElement } from "react"; +import { ReactElement, ReactNode } from "react"; import { Link, useLocation, useResolvedPath } from "react-router-dom"; -import { IconButton, icons } from "@gliff-ai/style"; +import { IconButton, ButtonGroup, icons, MuiCard } from "@gliff-ai/style"; import { User, UserAccess } from "@/interfaces"; const pageIcons: { [name: string]: string } = { @@ -32,7 +31,12 @@ function NavLink({ name }: { name: string }): ReactElement { ); } -export function PageSelector({ user }: { user: User }): ReactElement { +interface Props { + user: User; + ZooDialog: ReactNode; +} + +export function PageSelector({ user, ZooDialog }: Props): ReactElement { let links; const isOwnerOrMember = @@ -50,6 +54,9 @@ export function PageSelector({ user }: { user: User }): ReactElement { return (
))} + {ZooDialog && ( + span > button": { + minWidth: "57px !important", + }, + }} + > + {ZooDialog} + + )}
); } diff --git a/src/index.tsx b/src/index.tsx index dceb67ea..166b47eb 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, ReactNode } from "react"; import { Routes, Route, Navigate } from "react-router-dom"; import { AppBar, @@ -60,6 +60,7 @@ interface Props { showAppBar: boolean; launchCurateCallback?: (projectUid: string) => void; launchAuditCallback?: (projectUid: string) => void; + ZooDialog?: ReactNode; } export function UserInterface(props: Props): JSX.Element { @@ -132,17 +133,14 @@ export function UserInterface(props: Props): JSX.Element { display: "flex", }} > - + } /> - } /> - } /> - } @@ -170,6 +168,7 @@ UserInterface.defaultProps = { user: undefined as User, launchCurateCallback: undefined, launchAuditCallback: undefined, + ZooDialog: null, }; export type { Services }; From b88160c073408e7afa2b086764e17c8f6cd9a234 Mon Sep 17 00:00:00 2001 From: Silvia Zeni Date: Wed, 29 Jun 2022 15:47:30 +0100 Subject: [PATCH 02/16] feat: Add Notepad for adding and editing plugin description --- src/components/plugins/AddPluginDialog.tsx | 16 ++++++++++++++++ src/components/plugins/EditPluginDialog.tsx | 15 ++++++++++++++- src/interfaces.ts | 1 + 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/components/plugins/AddPluginDialog.tsx b/src/components/plugins/AddPluginDialog.tsx index ad5598b0..426e9e90 100644 --- a/src/components/plugins/AddPluginDialog.tsx +++ b/src/components/plugins/AddPluginDialog.tsx @@ -20,6 +20,7 @@ import { ServiceFunctions } from "@/api"; import { FormLabelControl } from "./FormLabelControl"; import { ProductsRadioForm } from "./ProductsRadioForm"; import { ProjectsAutocomplete } from "./ProjectsAutocomplete"; +import { Notepad } from "../Notepad"; const whiteButtonStyle = { textTransform: "none", @@ -60,6 +61,7 @@ interface Props { const defaultPlugin = { type: PluginType.Javascript, name: "", + description: "", url: "", products: Product.ALL, enabled: false, @@ -210,6 +212,7 @@ export function AddPluginDialog({ ) => { setNewPlugin((p) => ({ ...p, name: e.target.value } as IPlugin)); }} @@ -219,12 +222,25 @@ export function AddPluginDialog({ variant="outlined" /> + { + setNewPlugin((p) => ({ + ...p, + description: event.target.value, + })); + }} + rows={6} + /> + ) => { const url = e.target.value.replace(/\/$/, ""); // remove trailing slash setValidUrl(isValidURL(url)); diff --git a/src/components/plugins/EditPluginDialog.tsx b/src/components/plugins/EditPluginDialog.tsx index 7e93371a..c831a062 100644 --- a/src/components/plugins/EditPluginDialog.tsx +++ b/src/components/plugins/EditPluginDialog.tsx @@ -15,6 +15,7 @@ import { IPlugin, PluginType, Project } from "@/interfaces"; import { ProjectsAutocomplete } from "./ProjectsAutocomplete"; import { ProductsRadioForm } from "./ProductsRadioForm"; import { ServiceFunctions } from "../../api"; +import { Notepad } from "../Notepad"; const tab = { display: "inline-block", @@ -131,6 +132,18 @@ export function EditPluginDialog({ variant="outlined" /> + { + setNewPlugin((p) => ({ + ...p, + description: event.target.value, + })); + }} + rows={6} + /> + Type: @@ -142,7 +155,7 @@ export function EditPluginDialog({ {plugin.url} - {" "} + diff --git a/src/interfaces.ts b/src/interfaces.ts index fb3ad129..761746ab 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -74,6 +74,7 @@ export interface IPlugin { username?: string; type: PluginType; name: string; + description: string; url: string; products: Product; enabled: boolean; From 211e0ecfaf323366eb0d24d342f8de0b5677e6da Mon Sep 17 00:00:00 2001 From: Silvia Zeni Date: Thu, 7 Jul 2022 14:42:12 +0100 Subject: [PATCH 03/16] feat: Rename, extend and export IPlugin interface --- examples/samples.ts | 8 ++++---- src/components/plugins/AddPluginDialog.tsx | 13 +++++++------ src/components/plugins/DeletePluginDialog.tsx | 6 +++--- src/components/plugins/EditPluginDialog.tsx | 10 +++++----- src/components/plugins/ProductsRadioForm.tsx | 8 ++++---- src/components/plugins/ProjectsAutocomplete.tsx | 6 +++--- src/index.tsx | 1 + src/interfaces.ts | 11 +++++++---- src/views/PluginsView.tsx | 10 +++++----- 9 files changed, 39 insertions(+), 34 deletions(-) diff --git a/examples/samples.ts b/examples/samples.ts index 0edbec51..89c9ec24 100644 --- a/examples/samples.ts +++ b/examples/samples.ts @@ -1,6 +1,6 @@ import { UserAccess, - IPlugin, + Plugin, PluginType, Product, Progress, @@ -107,7 +107,7 @@ export const config = { key: "key key key", email: "1234@trustedservice.gliff.app", }), - getPlugins: (data): Promise => + getPlugins: (data): Promise => Promise.resolve([ { username: "1234@trustedservice.gliff.app", @@ -117,7 +117,7 @@ export const config = { products: Product.ALL, enabled: false, collection_uids: ["1"], - } as IPlugin, + } as Plugin, { type: PluginType.Javascript, name: "js-plugin", @@ -125,7 +125,7 @@ export const config = { products: Product.CURATE, enabled: true, collection_uids: [], - } as IPlugin, + } as Plugin, ]), updatePlugin: (data): Promise => Promise.resolve(1), deletePlugin: (data): Promise => Promise.resolve(1), diff --git a/src/components/plugins/AddPluginDialog.tsx b/src/components/plugins/AddPluginDialog.tsx index 426e9e90..8adee160 100644 --- a/src/components/plugins/AddPluginDialog.tsx +++ b/src/components/plugins/AddPluginDialog.tsx @@ -15,7 +15,7 @@ import { Box, Divider, } from "@gliff-ai/style"; -import { IPlugin, Product, PluginType, Project } from "@/interfaces"; +import { Plugin, Product, PluginType, Project } from "@/interfaces"; import { ServiceFunctions } from "@/api"; import { FormLabelControl } from "./FormLabelControl"; import { ProductsRadioForm } from "./ProductsRadioForm"; @@ -66,6 +66,7 @@ const defaultPlugin = { products: Product.ALL, enabled: false, collection_uids: [] as string[], + is_public: false, }; export function AddPluginDialog({ @@ -78,7 +79,7 @@ export function AddPluginDialog({ const [key, setKey] = useState(null); const [creating, setCreating] = useState(false); const [dialogPage, setDialogPage] = useState(DialogPage.pickPluginType); - const [newPlugin, setNewPlugin] = useState(defaultPlugin); + const [newPlugin, setNewPlugin] = useState(defaultPlugin); const [validUrl, setValidUrl] = useState(true); useEffect(() => { @@ -103,7 +104,7 @@ export function AddPluginDialog({ if (!projects) return null; - const addPluginToProjects = async (plugin: IPlugin, email: string) => { + const addPluginToProjects = async (plugin: Plugin, email: string) => { await Promise.allSettled( plugin.collection_uids.map(async (projectUid) => { try { @@ -150,7 +151,7 @@ export function AddPluginDialog({ ) => { - setNewPlugin((p) => ({ ...p, type: e.target.value } as IPlugin)); + setNewPlugin((p) => ({ ...p, type: e.target.value } as Plugin)); }} >

What type of plug-in do you want to register?

@@ -214,7 +215,7 @@ export function AddPluginDialog({ placeholder="Plug-in Name" value={newPlugin.name} onChange={(e: ChangeEvent) => { - setNewPlugin((p) => ({ ...p, name: e.target.value } as IPlugin)); + setNewPlugin((p) => ({ ...p, name: e.target.value } as Plugin)); }} inputProps={{ maxLength: 50, // NOTE: name for python or AI plugins cannot be over 50 characters, otherwise 500 @@ -244,7 +245,7 @@ export function AddPluginDialog({ onChange={(e: ChangeEvent) => { const url = e.target.value.replace(/\/$/, ""); // remove trailing slash setValidUrl(isValidURL(url)); - setNewPlugin((p) => ({ ...p, url } as IPlugin)); + setNewPlugin((p) => ({ ...p, url } as Plugin)); }} /> diff --git a/src/components/plugins/DeletePluginDialog.tsx b/src/components/plugins/DeletePluginDialog.tsx index 9d1a7b20..c4ffcc29 100644 --- a/src/components/plugins/DeletePluginDialog.tsx +++ b/src/components/plugins/DeletePluginDialog.tsx @@ -16,7 +16,7 @@ import { Typography, AdvancedDialog, } from "@gliff-ai/style"; -import { IPlugin } from "@/interfaces"; +import { Plugin } from "@/interfaces"; import { ServiceFunctions } from "@/api"; const purpleText = { @@ -45,8 +45,8 @@ const purpleButtonStyle = { }; interface Props { - plugin: IPlugin; - setPlugins: Dispatch>; + plugin: Plugin; + setPlugins: Dispatch>; services: ServiceFunctions; } diff --git a/src/components/plugins/EditPluginDialog.tsx b/src/components/plugins/EditPluginDialog.tsx index c831a062..d3be1d72 100644 --- a/src/components/plugins/EditPluginDialog.tsx +++ b/src/components/plugins/EditPluginDialog.tsx @@ -11,7 +11,7 @@ import { Button, Typography, } from "@gliff-ai/style"; -import { IPlugin, PluginType, Project } from "@/interfaces"; +import { Plugin, PluginType, Project } from "@/interfaces"; import { ProjectsAutocomplete } from "./ProjectsAutocomplete"; import { ProductsRadioForm } from "./ProductsRadioForm"; import { ServiceFunctions } from "../../api"; @@ -40,9 +40,9 @@ const greenButtonStyle = { }; interface Props { - plugin: IPlugin; + plugin: Plugin; allProjects: Project[] | null; - updatePlugins: (prevPlugin: IPlugin, plugin: IPlugin) => void; + updatePlugins: (prevPlugin: Plugin, plugin: Plugin) => void; services: ServiceFunctions; setError: (error: string) => void; } @@ -54,7 +54,7 @@ export function EditPluginDialog({ services, setError, }: Props): ReactElement { - const [newPlugin, setNewPlugin] = useState(plugin); + const [newPlugin, setNewPlugin] = useState(plugin); const [closeDialog, setCloseDialog] = useState(false); const resetDefaults = (): void => { @@ -124,7 +124,7 @@ export function EditPluginDialog({ value={newPlugin.name} placeholder="Plug-in Name" onChange={(e: ChangeEvent) => { - setNewPlugin((p) => ({ ...p, name: e.target.value } as IPlugin)); + setNewPlugin((p) => ({ ...p, name: e.target.value } as Plugin)); }} inputProps={{ maxLength: 50, // NOTE: name for python or AI plugins cannot be over 50 characters, otherwise 500 diff --git a/src/components/plugins/ProductsRadioForm.tsx b/src/components/plugins/ProductsRadioForm.tsx index 47cd88e6..77495a25 100644 --- a/src/components/plugins/ProductsRadioForm.tsx +++ b/src/components/plugins/ProductsRadioForm.tsx @@ -2,7 +2,7 @@ import { ReactElement, ChangeEvent, Dispatch, SetStateAction } from "react"; import { RadioGroup, FormControl } from "@mui/material"; import makeStyles from "@mui/styles/makeStyles"; import { FormLabelControl } from "./FormLabelControl"; -import { IPlugin, Product } from "@/interfaces"; +import { Plugin, Product } from "@/interfaces"; const useStyles = makeStyles({ marginTop: { marginTop: "15px" }, @@ -11,8 +11,8 @@ const useStyles = makeStyles({ }); interface Props { - newPlugin: IPlugin; - setNewPlugin: Dispatch>; + newPlugin: Plugin; + setNewPlugin: Dispatch>; } export const ProductsRadioForm = ({ @@ -25,7 +25,7 @@ export const ProductsRadioForm = ({ ) => { - setNewPlugin((p) => ({ ...p, products: e.target.value } as IPlugin)); + setNewPlugin((p) => ({ ...p, products: e.target.value } as Plugin)); }} >

Add plugin to:

diff --git a/src/components/plugins/ProjectsAutocomplete.tsx b/src/components/plugins/ProjectsAutocomplete.tsx index 4b3617d0..6fb7d297 100644 --- a/src/components/plugins/ProjectsAutocomplete.tsx +++ b/src/components/plugins/ProjectsAutocomplete.tsx @@ -11,7 +11,7 @@ import { import makeStyles from "@mui/styles/makeStyles"; import SVG from "react-inlinesvg"; import { icons, lightGrey } from "@gliff-ai/style"; -import { IPlugin, Project } from "@/interfaces"; +import { Plugin, Project } from "@/interfaces"; const useStyles = makeStyles({ marginTop: { marginTop: "15px" }, @@ -32,8 +32,8 @@ const useStyles = makeStyles({ interface Props { allProjects: Project[]; - plugin: IPlugin; - setPlugin: Dispatch>; + plugin: Plugin; + setPlugin: Dispatch>; } export const ProjectsAutocomplete = ({ diff --git a/src/index.tsx b/src/index.tsx index 166b47eb..1c16f3c7 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -173,3 +173,4 @@ UserInterface.defaultProps = { export type { Services }; export { ProvideAuth } from "@/hooks/use-auth"; +export type { Plugin, Product, PluginType } from "./interfaces"; diff --git a/src/interfaces.ts b/src/interfaces.ts index 761746ab..4548bdb8 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -70,13 +70,16 @@ export enum PluginType { "AI" = "AI", } -export interface IPlugin { - username?: string; +export interface Plugin { + username?: string; // trusted-service username (i.e., email address) type: PluginType; name: string; description: string; - url: string; + url: string; // base_url for trusted-services and url for plugins products: Product; enabled: boolean; - collection_uids: string[]; + collection_uids: string[]; // collection uids for the projects the plugin has been added to + is_public: boolean; + public_key?: string; + encrypted_access_key?: string; } diff --git a/src/views/PluginsView.tsx b/src/views/PluginsView.tsx index 658cd786..14a6c592 100644 --- a/src/views/PluginsView.tsx +++ b/src/views/PluginsView.tsx @@ -28,7 +28,7 @@ import { TableRow, TableButtonsCell, } from "@/components"; -import { IPlugin, Project } from "@/interfaces"; +import { Plugin, Project } from "@/interfaces"; const useStyles = () => makeStyles(() => ({ @@ -65,14 +65,14 @@ interface Props { export const PluginsView = ({ services }: Props): ReactElement => { const auth = useAuth(); const [projects, setProjects] = useState(null); - const [plugins, setPlugins] = useState(null); + const [plugins, setPlugins] = useState(null); const [error, setError] = useState(null); const isMounted = useRef(false); const classes = useStyles()(); - const updatePlugins = (prevPlugin: IPlugin, plugin: IPlugin) => { + const updatePlugins = (prevPlugin: Plugin, plugin: Plugin) => { void services.updatePlugin({ ...plugin }).then((result) => { if (result && isMounted.current) { setPlugins((prevPlugins) => @@ -82,8 +82,8 @@ export const PluginsView = ({ services }: Props): ReactElement => { }); }; - const updateEnabled = (plugin: IPlugin) => { - const newPlugin: IPlugin = plugins.find( + const updateEnabled = (plugin: Plugin) => { + const newPlugin: Plugin = plugins.find( (p) => p.name === plugin.name && p.url === plugin.url ); newPlugin.enabled = !newPlugin.enabled; From 269b4d5ad510b7ead86b0fabd01cc858f772859a Mon Sep 17 00:00:00 2001 From: Silvia Zeni Date: Thu, 7 Jul 2022 14:52:53 +0100 Subject: [PATCH 04/16] feat: Add to PluginsView toggle button for is_public --- src/views/PluginsView.tsx | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/views/PluginsView.tsx b/src/views/PluginsView.tsx index 14a6c592..6c1a5d2a 100644 --- a/src/views/PluginsView.tsx +++ b/src/views/PluginsView.tsx @@ -82,11 +82,14 @@ export const PluginsView = ({ services }: Props): ReactElement => { }); }; - const updateEnabled = (plugin: Plugin) => { + const togglePluginButton = ( + plugin: Plugin, + key: "enabled" | "is_public" + ): void => { const newPlugin: Plugin = plugins.find( (p) => p.name === plugin.name && p.url === plugin.url ); - newPlugin.enabled = !newPlugin.enabled; + newPlugin[key] = !newPlugin[key]; if (newPlugin) { updatePlugins(plugin, newPlugin); @@ -161,8 +164,9 @@ export const PluginsView = ({ services }: Props): ReactElement => { "Type", "Product", "Products", - "Enabled", "Added To", + "Enabled", + "Public", ]} > {plugins.map((iplugin) => { @@ -172,6 +176,7 @@ export const PluginsView = ({ services }: Props): ReactElement => { type, products, enabled, + is_public: isPublic, collection_uids: collectionUids, } = iplugin; return ( @@ -180,15 +185,23 @@ export const PluginsView = ({ services }: Props): ReactElement => { {type} {url} {products} + {collectionUids.length} projects updateEnabled(iplugin)} + onChange={(e) => togglePluginButton(iplugin, "enabled")} + /> + + + togglePluginButton(iplugin, "is_public")} /> - {collectionUids.length} projects Date: Thu, 7 Jul 2022 15:17:15 +0100 Subject: [PATCH 05/16] fix: Export --- src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index 1c16f3c7..5bef6434 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -173,4 +173,4 @@ UserInterface.defaultProps = { export type { Services }; export { ProvideAuth } from "@/hooks/use-auth"; -export type { Plugin, Product, PluginType } from "./interfaces"; +export { Plugin, Product, PluginType } from "./interfaces"; From 8d16843b790654ddb67863091a3d2906426db28d Mon Sep 17 00:00:00 2001 From: Silvia Zeni Date: Tue, 12 Jul 2022 15:36:01 +0100 Subject: [PATCH 06/16] fix: import of types from @gliff-ai/manage --- {src => __tests__/jest}/index.test.tsx | 6 +- examples/samples.ts | 6 +- src/index.tsx | 181 +------------------------ src/ui.tsx | 177 ++++++++++++++++++++++++ tsconfig.json | 13 +- 5 files changed, 193 insertions(+), 190 deletions(-) rename {src => __tests__/jest}/index.test.tsx (96%) create mode 100644 src/ui.tsx diff --git a/src/index.test.tsx b/__tests__/jest/index.test.tsx similarity index 96% rename from src/index.test.tsx rename to __tests__/jest/index.test.tsx index bbb300b0..bc1c4223 100644 --- a/src/index.test.tsx +++ b/__tests__/jest/index.test.tsx @@ -2,9 +2,9 @@ import ReactDOM from "react-dom"; import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; import { fireEvent, screen, act } from "@testing-library/react"; -import { ProvideAuth, UserInterface } from "./index"; -import { user, config } from "../examples/samples"; -import { UserAccess } from "./interfaces"; +import { user, config } from "../../examples/samples"; +import { UserAccess } from "../../src/interfaces"; +import { ProvideAuth, UserInterface } from "../../src/index"; let container: HTMLDivElement; const getComponent = (userAccess: UserAccess, tierID: number): JSX.Element => ( diff --git a/examples/samples.ts b/examples/samples.ts index 89c9ec24..1a28960c 100644 --- a/examples/samples.ts +++ b/examples/samples.ts @@ -4,7 +4,7 @@ import { PluginType, Product, Progress, -} from "@/interfaces"; +} from "../src/interfaces"; import type { Services } from "../src"; export const user = { @@ -114,17 +114,21 @@ export const config = { type: PluginType.Python, name: "python-plugin", url: "https://ts.gliff.app", + description: "", products: Product.ALL, enabled: false, collection_uids: ["1"], + is_public: false, } as Plugin, { type: PluginType.Javascript, name: "js-plugin", url: "https://plugin.gliff.app", + description: "", products: Product.CURATE, enabled: true, collection_uids: [], + is_public: true, } as Plugin, ]), updatePlugin: (data): Promise => Promise.resolve(1), diff --git a/src/index.tsx b/src/index.tsx index 5bef6434..3f40f209 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,176 +1,5 @@ -import { useEffect, useRef, useState, ReactNode } from "react"; -import { Routes, Route, Navigate } from "react-router-dom"; -import { - AppBar, - CssBaseline, - Grid, - Toolbar, - ThemeProvider, - Theme, - StyledEngineProvider, -} from "@mui/material"; -import StylesProvider from "@mui/styles/StylesProvider"; -import { theme, generateClassName, Logo } from "@gliff-ai/style"; - -import { initApiRequest, ServiceFunctions } from "@/api"; -import { TeamView } from "@/views/TeamView"; -import { ProjectsView } from "@/views/ProjectsView"; -import { PluginsView } from "./views/PluginsView"; - -import { useAuth } from "@/hooks/use-auth"; -import { CollaboratorsView } from "@/views/CollaboratorsView"; - -import type { Services } from "@/api"; -import { PageSelector } from "./components/PageSelector"; -import { User } from "./interfaces"; -import { setStateIfMounted } from "./helpers"; - -declare module "@mui/styles/defaultTheme" { - // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface DefaultTheme extends Theme {} -} - -const defaultServices = { - queryTeam: "GET /team", - loginUser: "POST /user/login", - inviteUser: "POST /user/invite", - inviteCollaborator: "POST /user/invite/collaborator", - getProjects: "GET /projects", - updateProjectDetails: "POST /project/uid", - getProject: "GET /project", // TODO: Support named params for GET? Body works tho... - deleteProject: "DELETE /project", - getCollectionMembers: "GET /team/collectionmembers", - createProject: "POST /projects", - inviteToProject: "POST /projects/invite", - getCollectionsMembers: "GET /projects/collectionsmembers", - removeFromProject: "POST /user/delete/collaborator", - createPlugin: "POST /plugin", - getPlugins: "GET /plugin", - deletePlugin: "DELETE /plugin", - updatePlugin: "PUT /plugin", - getAnnotationProgress: "GET /progress", - launchDocs: "GET /docs", - downloadDemoData: "POST /demo-data", -} as Services; - -interface Props { - apiUrl: string; - services?: Readonly; - user?: User; // Optional mock user - showAppBar: boolean; - launchCurateCallback?: (projectUid: string) => void; - launchAuditCallback?: (projectUid: string) => void; - ZooDialog?: ReactNode; -} - -export function UserInterface(props: Props): JSX.Element { - const [services, setServices] = useState(null); - const auth = useAuth(); - - const isMounted = useRef(false); - - useEffect(() => { - // runs at mount - isMounted.current = true; - return () => { - // runs at dismount - isMounted.current = false; - }; - }, []); - - useEffect(() => { - // This loads all the services we use, which are either API requests, or functions that allow us to mock etc. - const newServices = initApiRequest(props.apiUrl, props.services); - setStateIfMounted(newServices, setServices, isMounted.current); - }, [props.apiUrl, props.services, isMounted]); - - useEffect(() => { - if (!auth) return; - // Autologin if we've been passed a login - if (props.user) { - auth?.saveUser(props.user); - } - }, [auth, props.user]); - - if (!auth?.user) return null; - - const appbar = props.showAppBar && ( - - - - - - - - - - ); - - return ( - - - - - {appbar} -
- - - } /> - } /> - } - /> - } - /> - - } - /> - -
-
-
-
- ); -} - -UserInterface.defaultProps = { - services: defaultServices, - user: undefined as User, - launchCurateCallback: undefined, - launchAuditCallback: undefined, - ZooDialog: null, -}; - -export type { Services }; -export { ProvideAuth } from "@/hooks/use-auth"; -export { Plugin, Product, PluginType } from "./interfaces"; +export type { Services } from "./api"; +export { ProvideAuth } from "./hooks/use-auth"; +export type { Plugin, User } from "./interfaces"; +export { Product, PluginType } from "./interfaces"; +export { UserInterface } from "./ui"; diff --git a/src/ui.tsx b/src/ui.tsx new file mode 100644 index 00000000..0d4028a1 --- /dev/null +++ b/src/ui.tsx @@ -0,0 +1,177 @@ +import { useEffect, useRef, useState, ReactNode } from "react"; +import { Routes, Route, Navigate } from "react-router-dom"; +import { + AppBar, + CssBaseline, + Grid, + Toolbar, + ThemeProvider, + Theme, + StyledEngineProvider, +} from "@mui/material"; +import StylesProvider from "@mui/styles/StylesProvider"; +import { theme, generateClassName, Logo } from "@gliff-ai/style"; + +import { initApiRequest, ServiceFunctions } from "@/api"; +import { TeamView } from "@/views/TeamView"; +import { ProjectsView } from "@/views/ProjectsView"; +import { PluginsView } from "./views/PluginsView"; + +import { useAuth } from "@/hooks/use-auth"; +import { CollaboratorsView } from "@/views/CollaboratorsView"; + +import type { Services } from "@/api"; +import { PageSelector } from "./components/PageSelector"; +import { User } from "./interfaces"; +import { setStateIfMounted } from "./helpers"; + +declare module "@mui/styles/defaultTheme" { + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface DefaultTheme extends Theme {} +} + +export const defaultServices = { + queryTeam: "GET /team", + loginUser: "POST /user/login", + inviteUser: "POST /user/invite", + inviteCollaborator: "POST /user/invite/collaborator", + getProjects: "GET /projects", + updateProjectDetails: "POST /project/uid", + getProject: "GET /project", // TODO: Support named params for GET? Body works tho... + deleteProject: "DELETE /project", + getCollectionMembers: "GET /team/collectionmembers", + createProject: "POST /projects", + inviteToProject: "POST /projects/invite", + getCollectionsMembers: "GET /projects/collectionsmembers", + removeFromProject: "POST /user/delete/collaborator", + createPlugin: "POST /plugin", + getPlugins: "GET /plugin", + deletePlugin: "DELETE /plugin", + updatePlugin: "PUT /plugin", + getAnnotationProgress: "GET /progress", + downloadDemoData: "POST /demo-data", +} as Services; + +interface Props { + apiUrl: string; + services?: Readonly; + user?: User; // Optional mock user + showAppBar: boolean; + launchCurateCallback?: (projectUid: string) => void; + launchAuditCallback?: (projectUid: string) => void; + launchDocs: () => Window | null; + ZooDialog?: ReactNode; +} + +export function UserInterface(props: Props): JSX.Element { + const [services, setServices] = useState(null); + const auth = useAuth(); + + const isMounted = useRef(false); + + useEffect(() => { + // runs at mount + isMounted.current = true; + return () => { + // runs at dismount + isMounted.current = false; + }; + }, []); + + useEffect(() => { + // This loads all the services we use, which are either API requests, or functions that allow us to mock etc. + const newServices = initApiRequest(props.apiUrl, props.services); + setStateIfMounted(newServices, setServices, isMounted.current); + }, [props.apiUrl, props.services, isMounted]); + + useEffect(() => { + if (!auth) return; + // Autologin if we've been passed a login + if (props.user) { + auth?.saveUser(props.user); + } + }, [auth, props.user]); + + if (!auth?.user) return null; + + const appbar = props.showAppBar && ( + + + + + + + + + + ); + + return ( + + + + + {appbar} +
+ + + } /> + } /> + + } + /> + } + /> + + } + /> + +
+
+
+
+ ); +} + +UserInterface.defaultProps = { + services: defaultServices, + user: undefined as User, + launchCurateCallback: undefined, + launchAuditCallback: undefined, + ZooDialog: null, +}; diff --git a/tsconfig.json b/tsconfig.json index d8645b54..248a5b0c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,15 +17,8 @@ } }, "include": [ - "typings/**/*", - "examples/**/*.ts*", - "src/**/*.tsx", - "src/**/*.ts", - "__tests__/**/*.ts", - "typings/**/*.ts", - "./jest-setup.ts", - "src/assets/*.svg" + "src/**/**/*.tsx", + "src/**/**/*.ts", ], - "exclude": ["src/**/*_.tsx", - "__tests__/*"] + "exclude": ["src/**/**/*_.tsx","src/**/**/*test.tsx"] } From ceb48c20d1e267cc41ac803df4ba269db3dc5065 Mon Sep 17 00:00:00 2001 From: Silvia Zeni Date: Tue, 12 Jul 2022 15:53:15 +0100 Subject: [PATCH 07/16] fix: Interfaces to match data passed by DOMINATE --- src/interfaces.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/interfaces.ts b/src/interfaces.ts index 4548bdb8..5b356302 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,8 +1,8 @@ export interface User { - email: string; - authToken: string; - userAccess: UserAccess; - tierID: number; + email: string | null; + authToken: string | null; + userAccess: UserAccess | null; + tierID: number | null; } export type Progress = { @@ -71,8 +71,9 @@ export enum PluginType { } export interface Plugin { - username?: string; // trusted-service username (i.e., email address) type: PluginType; + author?: string; // only for input plugin data + origin_id?: number | null; // only for output plugin data name: string; description: string; url: string; // base_url for trusted-services and url for plugins @@ -80,6 +81,7 @@ export interface Plugin { enabled: boolean; collection_uids: string[]; // collection uids for the projects the plugin has been added to is_public: boolean; - public_key?: string; - encrypted_access_key?: string; + username?: string; // python and AI plugins' username (i.e., email address) + public_key?: string; // python and AI plugins only + encrypted_access_key?: string; // python and AI plugins only } From 374f57d62ad1dd33f7d0d2002ceab322d639a963 Mon Sep 17 00:00:00 2001 From: Silvia Zeni Date: Tue, 12 Jul 2022 15:56:19 +0100 Subject: [PATCH 08/16] fix: pass launchDocs as prop to UserInterface and launchDocs from Services as it is not compatible with the Services interface --- examples/index.tsx | 3 +++ examples/samples.ts | 1 - src/api.ts | 1 - src/components/plugins/AddPluginDialog.tsx | 4 +++- src/views/PluginsView.tsx | 6 ++++-- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/index.tsx b/examples/index.tsx index f50ef0a0..327f51c3 100644 --- a/examples/index.tsx +++ b/examples/index.tsx @@ -17,6 +17,9 @@ ReactDOM.render( user={user} services={config.services} showAppBar + launchDocs={() => + window.open("https://docs.gliff.app/", "_blank") + } /> } /> diff --git a/examples/samples.ts b/examples/samples.ts index 1a28960c..9461db39 100644 --- a/examples/samples.ts +++ b/examples/samples.ts @@ -138,7 +138,6 @@ export const config = { 1: { total: 12, complete: 1 }, 2: { total: 0, complete: 0 }, }), - launchDocs: (): Promise => Promise.resolve(), downloadDemoData: (): Promise => Promise.resolve("2"), } as Services, }; diff --git a/src/api.ts b/src/api.ts index 71299bd9..96027534 100644 --- a/src/api.ts +++ b/src/api.ts @@ -30,7 +30,6 @@ interface Services { deletePlugin: APIRoute | ServiceFunction; updatePlugin: APIRoute | ServiceFunction; getAnnotationProgress: APIRoute | ServiceFunction; - launchDocs: APIRoute | ServiceFunction; downloadDemoData: APIRoute | ServiceFunction; } diff --git a/src/components/plugins/AddPluginDialog.tsx b/src/components/plugins/AddPluginDialog.tsx index 8adee160..bc6055e1 100644 --- a/src/components/plugins/AddPluginDialog.tsx +++ b/src/components/plugins/AddPluginDialog.tsx @@ -56,6 +56,7 @@ interface Props { services: ServiceFunctions; setError: (error: string) => void; getPlugins: () => void; + launchDocs: () => Window | null; } const defaultPlugin = { @@ -74,6 +75,7 @@ export function AddPluginDialog({ setError, projects, getPlugins, + launchDocs, }: Props): ReactElement { const [closeDialog, setCloseDialog] = useState(false); const [key, setKey] = useState(null); @@ -187,7 +189,7 @@ export function AddPluginDialog({ >