mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	feat(react/dialogs): port bulk actions
This commit is contained in:
		
							
								
								
									
										143
									
								
								apps/client/src/widgets/dialogs/bulk_actions.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								apps/client/src/widgets/dialogs/bulk_actions.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| import { useEffect, useState } from "preact/hooks"; | ||||
| import { EventData } from "../../components/app_context"; | ||||
| import { closeActiveDialog, openDialog } from "../../services/dialog"; | ||||
| import { t } from "../../services/i18n"; | ||||
| import Modal from "../react/Modal"; | ||||
| import ReactBasicWidget from "../react/ReactBasicWidget"; | ||||
| import "./bulk_actions.css"; | ||||
| import { BulkActionAffectedNotes } from "@triliumnext/commons"; | ||||
| import server from "../../services/server"; | ||||
| import FormCheckbox from "../react/FormCheckbox"; | ||||
| import Button from "../react/Button"; | ||||
| import bulk_action from "../../services/bulk_action"; | ||||
| import toast from "../../services/toast"; | ||||
| import RenameNoteBulkAction from "../bulk_actions/note/rename_note"; | ||||
| import { RawHtmlBlock } from "../react/RawHtml"; | ||||
| import FNote from "../../entities/fnote"; | ||||
| import froca from "../../services/froca"; | ||||
|  | ||||
| interface BulkActionProps { | ||||
|     bulkActionNote?: FNote | null; | ||||
|     selectedOrActiveNoteIds?: string[]; | ||||
| } | ||||
|  | ||||
| function BulkActionComponent({ selectedOrActiveNoteIds, bulkActionNote }: BulkActionProps) { | ||||
|     const [ includeDescendants, setIncludeDescendants ] = useState(false); | ||||
|     const [ affectedNoteCount, setAffectedNoteCount ] = useState(0); | ||||
|     const [ existingActions, setExistingActions ] = useState<RenameNoteBulkAction[]>([]); | ||||
|  | ||||
|     if (!selectedOrActiveNoteIds || !bulkActionNote) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     useEffect(() => { | ||||
|         server.post<BulkActionAffectedNotes>("bulk-action/affected-notes", { | ||||
|             noteIds: selectedOrActiveNoteIds, | ||||
|             includeDescendants | ||||
|         }).then(({ affectedNoteCount }) => setAffectedNoteCount(affectedNoteCount)); | ||||
|     }, [ selectedOrActiveNoteIds, includeDescendants ]); | ||||
|  | ||||
|     // Refresh is forced by the entities reloaded event outside React. | ||||
|     useEffect(() => {         | ||||
|         setExistingActions(bulk_action.parseActions(bulkActionNote)); | ||||
|     }, []); | ||||
|  | ||||
|     return ( selectedOrActiveNoteIds && | ||||
|         <Modal | ||||
|             className="bulk-actions-dialog" | ||||
|             size="xl" | ||||
|             title={t("bulk_actions.bulk_actions")} | ||||
|             footer={<Button text={t("bulk_actions.execute_bulk_actions")} primary />} | ||||
|             onSubmit={async () => { | ||||
|                 await server.post("bulk-action/execute", { | ||||
|                     noteIds: selectedOrActiveNoteIds, | ||||
|                     includeDescendants | ||||
|                 }); | ||||
|  | ||||
|                 toast.showMessage(t("bulk_actions.bulk_actions_executed"), 3000); | ||||
|                 closeActiveDialog(); | ||||
|             }} | ||||
|         > | ||||
|             <h4>{t("bulk_actions.affected_notes")}: <span>{affectedNoteCount}</span></h4> | ||||
|             <FormCheckbox  | ||||
|                 name="include-descendants" label={t("bulk_actions.include_descendants")} | ||||
|                 currentValue={includeDescendants} onChange={setIncludeDescendants} | ||||
|             /> | ||||
|  | ||||
|             <h4>{t("bulk_actions.available_actions")}</h4> | ||||
|             <AvailableActionsList /> | ||||
|  | ||||
|             <h4>{t("bulk_actions.chosen_actions")}</h4> | ||||
|             <ExistingActionsList existingActions={existingActions} /> | ||||
|         </Modal> | ||||
|     ) | ||||
| } | ||||
|  | ||||
| function AvailableActionsList() { | ||||
|     return <table class="bulk-available-action-list"> | ||||
|         {bulk_action.ACTION_GROUPS.map((actionGroup) => { | ||||
|             return ( | ||||
|                 <tr> | ||||
|                     <td>{ actionGroup.title }:</td> | ||||
|                     {actionGroup.actions.map(({ actionName, actionTitle }) => | ||||
|                         <Button | ||||
|                             small text={actionTitle} | ||||
|                             onClick={() => bulk_action.addAction("_bulkAction", actionName)} | ||||
|                         /> | ||||
|                     )} | ||||
|                 </tr> | ||||
|             ); | ||||
|         })} | ||||
|     </table>; | ||||
| } | ||||
|  | ||||
| function ExistingActionsList({ existingActions }: { existingActions?: RenameNoteBulkAction[] }) { | ||||
|     return ( | ||||
|         <table class="bulk-existing-action-list"> | ||||
|             { existingActions | ||||
|                 ? existingActions | ||||
|                     .map(action => { | ||||
|                         const renderedAction = action.render(); | ||||
|                         if (renderedAction) { | ||||
|                             return <RawHtmlBlock | ||||
|                                 html={renderedAction[0].innerHTML} | ||||
|                                 style={{ display: "flex", alignItems: "center" }} /> | ||||
|                         } else { | ||||
|                             return null; | ||||
|                         } | ||||
|                     }) | ||||
|                     .filter(renderedAction => renderedAction !== null)             | ||||
|                 : <p>{t("bulk_actions.none_yet")}</p> | ||||
|             } | ||||
|         </table> | ||||
|     ); | ||||
| } | ||||
|  | ||||
| export default class BulkActionsDialog extends ReactBasicWidget { | ||||
|  | ||||
|     private props: BulkActionProps = {}; | ||||
|  | ||||
|     get component() { | ||||
|         return <BulkActionComponent {...this.props} /> | ||||
|     } | ||||
|  | ||||
|     async openBulkActionsDialogEvent({ selectedOrActiveNoteIds }: EventData<"openBulkActionsDialog">) {                 | ||||
|         this.props = { | ||||
|             selectedOrActiveNoteIds, | ||||
|             bulkActionNote: await froca.getNote("_bulkAction") | ||||
|         }; | ||||
|         this.doRender(); | ||||
|         openDialog(this.$widget); | ||||
|     } | ||||
|  | ||||
|     entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { | ||||
|         // only refreshing deleted attrs, otherwise components update themselves | ||||
|         if (loadResults.getAttributeRows().find((row) => row.type === "label" && row.name === "action" && row.noteId === "_bulkAction" && row.isDeleted)) { | ||||
|             // this may be triggered from e.g., sync without open widget, then no need to refresh the widget | ||||
|             if (this.props.selectedOrActiveNoteIds && this.$widget.is(":visible")) { | ||||
|                 this.doRender(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user