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

514 hook up select multiple projects dialog #593

Merged
merged 3 commits into from
Oct 25, 2023
Merged
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
45 changes: 35 additions & 10 deletions extensions/src/hello-world/web-views/hello-world.web-view.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ScrVers, VerseRef } from '@sillsdev/scripture';
import { ScrVers, VerseRef } from '@sillsdev/scripture';
import papi from 'papi-frontend';
import {
Button,
Expand All @@ -18,6 +18,7 @@ import { Key, useCallback, useContext, useEffect, useMemo, useRef, useState } fr
import type { HelloWorldEvent } from 'hello-world';
import type { DialogTypes } from 'renderer/components/dialogs/dialog-definition.model';
import type { WebViewProps } from 'shared/data/web-view.model';
import { ProjectDataTypes } from 'papi-shared-types';
import Clock from './components/clock.component';

type Row = {
Expand All @@ -29,7 +30,15 @@ type Row = {
const {
react: {
context: { TestContext },
hooks: { useData, useDataProvider, usePromise, useEvent, useSetting, useDialogCallback },
hooks: {
useData,
useDataProvider,
useProjectData,
usePromise,
useEvent,
useSetting,
useDialogCallback,
},
},
logger,
} = papi;
Expand Down Expand Up @@ -69,10 +78,10 @@ globalThis.webViewComponent = function HelloWorld({
const [rows, setRows] = useState(initializeRows());
const [selectedRows, setSelectedRows] = useState(new Set<Key>());
const [scrRef, setScrRef] = useSetting('platform.verseRef', defaultScrRef);
/* const verseRef = useMemo(
const verseRef = useMemo(
() => new VerseRef(scrRef.bookNum, scrRef.chapterNum, scrRef.verseNum),
[scrRef],
); */
);

// Update the clicks when we are informed helloWorld has been run
useEvent(
Expand Down Expand Up @@ -120,6 +129,18 @@ globalThis.webViewComponent = function HelloWorld({
'Loading latest Scripture text...',
);

const [projects, selectProjects] = useDialogCallback(
'platform.selectMultipleProjects',
useRef({
prompt: 'Please select one or more projects for Hello World WebView:',
iconUrl: 'papi-extension://hello-world/assets/offline.svg',
title: 'Select List of Hello World Projects',
}).current,
// Assert as string type rather than string literal type.
// eslint-disable-next-line no-type-assertion/no-type-assertion
['None'] as DialogTypes['platform.selectMultipleProjects']['responseType'],
);

const [name, setName] = useState('Bill');

const peopleDataProvider = useDataProvider<PeopleDataProvider>('helloSomeone.people');
Expand All @@ -144,12 +165,11 @@ globalThis.webViewComponent = function HelloWorld({
'Loading John 1:1...',
);

// TODO: Uncomment this or similar sample code once https://github.com/paranext/paranext-core/issues/440 is resolved
/* const [webVerse] = useProjectData.VerseUSFM<ProjectDataTypes['ParatextStandard'], 'VerseUSFM'>(
'32664dc3288a28df2e2bb75ded887fc8f17a15fb',
const [webVerse] = useProjectData.VerseUSFM<ProjectDataTypes['ParatextStandard'], 'VerseUSFM'>(
project,
verseRef,
'Loading WEB Verse',
); */
);

return (
<div>
Expand Down Expand Up @@ -196,8 +216,13 @@ globalThis.webViewComponent = function HelloWorld({
<div>{john11}</div>
<h3>Psalm 1</h3>
<div>{psalm1}</div>
{/* <h3>{verseRef.toString()} WEB</h3>
<div>{webVerse}</div> */}
<h3>{verseRef.toString()} WEB</h3>
<div>{webVerse}</div>
<h3>List of Selected Project Id(s):</h3>
<div>{projects.join(', ')}</div>
<div>
<Button onClick={selectProjects}>Select Projects</Button>
</div>
<br />
<div>
<TextField label="Test Me" />
Expand Down
3 changes: 3 additions & 0 deletions lib/papi-dts/papi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3378,6 +3378,8 @@ declare module 'renderer/components/dialogs/dialog-definition.model' {
import { ReactElement } from 'react';
/** The tabType for the select project dialog in `select-project.dialog.tsx` */
export const SELECT_PROJECT_DIALOG_TYPE = 'platform.selectProject';
/** The tabType for the select multiple projects dialog in `select-multiple-projects.dialog.tsx` */
export const SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE = 'platform.selectMultipleProjects';
/**
* Mapped type for dialog functions to use in getting various types for dialogs
*
Expand All @@ -3387,6 +3389,7 @@ declare module 'renderer/components/dialogs/dialog-definition.model' {
*/
export interface DialogTypes {
[SELECT_PROJECT_DIALOG_TYPE]: DialogDataTypes<DialogOptions, string>;
[SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE]: DialogDataTypes<DialogOptions, string[]>;
}
/** Each type of dialog. These are the tab types used in the dock layout */
export type DialogTabTypes = keyof DialogTypes;
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/components/dialogs/dialog-definition.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ReactElement } from 'react';

/** The tabType for the select project dialog in `select-project.dialog.tsx` */
export const SELECT_PROJECT_DIALOG_TYPE = 'platform.selectProject';
/** The tabType for the select multiple projects dialog in `select-multiple-projects.dialog.tsx` */
export const SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE = 'platform.selectMultipleProjects';

/**
* Mapped type for dialog functions to use in getting various types for dialogs
Expand All @@ -14,7 +16,7 @@ export const SELECT_PROJECT_DIALOG_TYPE = 'platform.selectProject';
*/
export interface DialogTypes {
[SELECT_PROJECT_DIALOG_TYPE]: DialogDataTypes<DialogOptions, string>;
// 'platform.selectMultipleProjects': DialogDataTypes<DialogOptions, string[]>;
[SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE]: DialogDataTypes<DialogOptions, string[]>;
}

/** Each type of dialog. These are the tab types used in the dock layout */
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/components/dialogs/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SELECT_PROJECT_DIALOG from '@renderer/components/dialogs/select-project.dialog';
import { DialogDefinition, DialogTabTypes } from './dialog-definition.model';
import SELECT_MULTIPLE_PROJECTS_DIALOG from './select-multiple-projects-dialog.component';

/**
* Map of all available dialog definitions used to create dialogs
Expand All @@ -8,6 +9,7 @@ import { DialogDefinition, DialogTabTypes } from './dialog-definition.model';
*/
const DIALOGS: { [DialogTabType in DialogTabTypes]: DialogDefinition<DialogTabType> } = {
[SELECT_PROJECT_DIALOG.tabType]: SELECT_PROJECT_DIALOG,
[SELECT_MULTIPLE_PROJECTS_DIALOG.tabType]: SELECT_MULTIPLE_PROJECTS_DIALOG,
};

/** All tab types for available dialogs */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
.open-multiple-projects-dialog {
.select-multiple-projects-dialog {
overflow-y: auto;
}

.open-multiple-projects-submit-button {
.select-multiple-projects-submit-button {
display: flex !important;
justify-content: flex-end !important;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { ListItemIcon } from '@mui/material';
import { useMemo, useState } from 'react';
import FolderOpenIcon from '@mui/icons-material/FolderOpen';
import DoneIcon from '@mui/icons-material/Done';
import { Button } from 'papi-components';
import ProjectList from '@renderer/components/projects/project-list.component';
import './select-multiple-projects-dialog.component.scss';
import projectLookupService from '@shared/services/project-lookup.service';
import usePromise from '@renderer/hooks/papi-hooks/use-promise.hook';
import { ProjectMetadata } from '@shared/models/project-metadata.model';
import DIALOG_BASE, { DialogProps } from './dialog-base.data';
import { DialogDefinition, SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE } from './dialog-definition.model';

type SelectMultipleProjectDialogProps = DialogProps<string[]>;

function SelectMultipleProjectsDialog({ prompt, submitDialog }: SelectMultipleProjectDialogProps) {
const [downloadedProjects, isLoadingProjects] = usePromise(
projectLookupService.getMetadataForAllProjects,
useMemo(() => [], []),
);

const [selectedProjects, setSelectedProjects] = useState<ProjectMetadata[]>([]);

const handleProjectToggle = (projectId: string) => {
if (selectedProjects.some((project) => project.id === projectId)) {
setSelectedProjects(selectedProjects.filter((project) => project.id !== projectId));
} else {
const selectedProject = downloadedProjects.find((project) => project.id === projectId);
if (selectedProject) setSelectedProjects([...selectedProjects, selectedProject]);
}
};

return (
<div className="select-multiple-projects-dialog">
<div>{prompt}</div>
{isLoadingProjects ? (
<div>Loading Projects</div>
) : (
<ProjectList
projects={downloadedProjects}
handleSelectProject={handleProjectToggle}
selectedProjects={selectedProjects}
isMultiselect
isCheckable
>
<ListItemIcon>
<FolderOpenIcon />
</ListItemIcon>
</ProjectList>
)}
<div className="select-multiple-projects-submit-button">
<Button onClick={() => submitDialog(selectedProjects.map((p) => p.id))}>
<DoneIcon />
</Button>
</div>
</div>
);
}

const SELECT_MULTIPLE_PROJECTS_DIALOG: DialogDefinition<
typeof SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE
> = Object.freeze({
...DIALOG_BASE,
tabType: SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE,
defaultTitle: 'Select Projects',
initialSize: {
width: 500,
height: 350,
},
Component: SelectMultipleProjectsDialog,
});

export default SELECT_MULTIPLE_PROJECTS_DIALOG;
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,6 @@ import {
loadDownloadUpdateProjectTab,
TAB_TYPE_DOWNLOAD_UPDATE_PROJECT_DIALOG,
} from '@renderer/components/projects/download-update-project-tab.component';
import {
loadOpenMultipleProjectsTab,
TAB_TYPE_OPEN_MULTIPLE_PROJECTS_DIALOG,
} from '@renderer/components/projects/open-multiple-projects-tab.component';
import {
TAB_TYPE_EXTENSION_MANAGER,
loadExtensionManagerTab,
Expand Down Expand Up @@ -118,7 +114,6 @@ const tabLoaderMap = new Map<TabType, TabLoader>([
[TAB_TYPE_TEST, loadTestTab],
[TAB_TYPE_WEBVIEW, loadWebViewTab],
[TAB_TYPE_DOWNLOAD_UPDATE_PROJECT_DIALOG, loadDownloadUpdateProjectTab],
[TAB_TYPE_OPEN_MULTIPLE_PROJECTS_DIALOG, loadOpenMultipleProjectsTab],
[TAB_TYPE_EXTENSION_MANAGER, loadExtensionManagerTab],
[TAB_TYPE_SETTINGS_DIALOG, loadSettingsDialog],
[TAB_TYPE_RUN_BASIC_CHECKS, loadRunBasicChecksTab],
Expand Down

This file was deleted.

36 changes: 25 additions & 11 deletions src/renderer/components/projects/project-list.component.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { List, ListItem, ListItemButton, ListItemText, ListSubheader } from '@mui/material';
import { ProjectMetadata } from '@shared/models/project-metadata.model';
import { Checkbox } from 'papi-components';
import { ProjectTypes } from 'papi-shared-types';
import { PropsWithChildren, useCallback } from 'react';
import { PropsWithChildren, useCallback, JSX } from 'react';

export type Project = ProjectMetadata & {
id: string;
Expand Down Expand Up @@ -91,14 +92,21 @@ export type ProjectListProps = PropsWithChildren<{
isMultiselect?: boolean;

/**
* If multiple is selected, then the array of selected projects is passed to control the selected flag on ListItemButton
* If multiselect is selected, then the array of selected projects is passed to control
* the selected flag on ListItemButton
*/
selectedProjects?: ProjectMetadata[] | undefined;

/**
* Optional subheader
*/
subheader?: string;

/**
* Adds a checkbox to the end of each list item that reflects the selected state of
* each project
*/
isCheckable?: boolean;
}>;

/**
Expand All @@ -112,6 +120,7 @@ export default function ProjectList({
isMultiselect,
selectedProjects,
subheader,
isCheckable,
children,
}: ProjectListProps) {
const isSelected = useCallback(
Expand All @@ -124,20 +133,25 @@ export default function ProjectList({
[isMultiselect, selectedProjects],
);

const createListItemContents = (project: ProjectMetadata): JSX.Element => {
return (
<ListItemButton
selected={isSelected(project)}
onClick={() => handleSelectProject(project.id)}
>
{children}
<ListItemText primary={project.name} />
{isCheckable && <Checkbox isChecked={isSelected(project)} />}
</ListItemButton>
);
};

return (
<div className="project-list">
<List>
<ListSubheader>{subheader}</ListSubheader>
{projects.map((project) => (
<ListItem key={project.id}>
<ListItemButton
selected={isSelected(project)}
onClick={() => handleSelectProject(project.id)}
>
{children}
<ListItemText primary={project.name} />
</ListItemButton>
</ListItem>
<ListItem key={project.id}>{createListItemContents(project)}</ListItem>
))}
</List>
</div>
Expand Down