Files.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import * as React from "react";
  2. import { CommandBar, ContextualMenuItemType, ICommandBarItemProps, Icon, Link, Image } from "@fluentui/react";
  3. import * as domClass from "dojo/dom-class";
  4. import * as WsDfu from "src/WsDfu";
  5. import * as ESPLogicalFile from "src/ESPLogicalFile";
  6. import { formatCost } from "src/Session";
  7. import * as Utility from "src/Utility";
  8. import nlsHPCC from "src/nlsHPCC";
  9. import { useConfirm } from "../hooks/confirm";
  10. import { useFluentPagedGrid } from "../hooks/grid";
  11. import { useBuildInfo } from "../hooks/platform";
  12. import { HolyGrail } from "../layouts/HolyGrail";
  13. import { pushParams } from "../util/history";
  14. import { AddToSuperfile } from "./forms/AddToSuperfile";
  15. import { CopyFile } from "./forms/CopyFile";
  16. import { DesprayFile } from "./forms/DesprayFile";
  17. import { Fields } from "./forms/Fields";
  18. import { Filter } from "./forms/Filter";
  19. import { RemoteCopy } from "./forms/RemoteCopy";
  20. import { RenameFile } from "./forms/RenameFile";
  21. import { ShortVerticalDivider } from "./Common";
  22. import { SizeMe } from "react-sizeme";
  23. const FilterFields: Fields = {
  24. "LogicalName": { type: "string", label: nlsHPCC.Name, placeholder: nlsHPCC.somefile },
  25. "Description": { type: "string", label: nlsHPCC.Description, placeholder: nlsHPCC.SomeDescription },
  26. "Owner": { type: "string", label: nlsHPCC.Owner, placeholder: nlsHPCC.jsmi },
  27. "Index": { type: "checkbox", label: nlsHPCC.Index },
  28. "NodeGroup": { type: "target-group", label: nlsHPCC.Group, placeholder: nlsHPCC.Cluster },
  29. "FileSizeFrom": { type: "string", label: nlsHPCC.FromSizes, placeholder: "4096" },
  30. "FileSizeTo": { type: "string", label: nlsHPCC.ToSizes, placeholder: "16777216" },
  31. "FileType": { type: "file-type", label: nlsHPCC.FileType },
  32. "FirstN": { type: "string", label: nlsHPCC.FirstN, placeholder: "-1" },
  33. // "Sortby": { type: "file-sortby", label: nlsHPCC.FirstNSortBy, disabled: (params: Fields) => !params.FirstN.value },
  34. "StartDate": { type: "datetime", label: nlsHPCC.FromDate },
  35. "EndDate": { type: "datetime", label: nlsHPCC.ToDate },
  36. };
  37. function formatQuery(_filter) {
  38. const filter = { ..._filter };
  39. if (filter.Index) {
  40. filter.ContentType = "key";
  41. delete filter.Index;
  42. }
  43. if (filter.StartDate) {
  44. filter.StartDate = new Date(filter.StartDate).toISOString();
  45. }
  46. if (filter.EndDate) {
  47. filter.EndDate = new Date(filter.StartDate).toISOString();
  48. }
  49. return filter;
  50. }
  51. const defaultUIState = {
  52. hasSelection: false,
  53. };
  54. interface FilesProps {
  55. filter?: object;
  56. store?: any;
  57. }
  58. const emptyFilter = {};
  59. export const Files: React.FunctionComponent<FilesProps> = ({
  60. filter = emptyFilter,
  61. store
  62. }) => {
  63. const hasFilter = React.useMemo(() => Object.keys(filter).length > 0, [filter]);
  64. const [showFilter, setShowFilter] = React.useState(false);
  65. const [showRemoteCopy, setShowRemoteCopy] = React.useState(false);
  66. const [showCopy, setShowCopy] = React.useState(false);
  67. const [showRenameFile, setShowRenameFile] = React.useState(false);
  68. const [showAddToSuperfile, setShowAddToSuperfile] = React.useState(false);
  69. const [showDesprayFile, setShowDesprayFile] = React.useState(false);
  70. const [mine, setMine] = React.useState(false);
  71. const [uiState, setUIState] = React.useState({ ...defaultUIState });
  72. const [, { currencyCode }] = useBuildInfo();
  73. // Grid ---
  74. const gridStore = React.useMemo(() => {
  75. return store ? store : ESPLogicalFile.CreateLFQueryStore({});
  76. }, [store]);
  77. const query = React.useMemo(() => {
  78. return formatQuery(filter);
  79. }, [filter]);
  80. const { Grid, GridPagination, selection, refreshTable, copyButtons } = useFluentPagedGrid({
  81. persistID: "files",
  82. store: gridStore,
  83. query,
  84. filename: "logicalfiles",
  85. columns: {
  86. col1: {
  87. width: 27,
  88. disabled: function (item) {
  89. return item ? item.__hpcc_isDir : true;
  90. },
  91. selectorType: "checkbox"
  92. },
  93. IsProtected: {
  94. headerIcon: "LockSolid",
  95. width: 25,
  96. sortable: false,
  97. formatter: function (_protected) {
  98. if (_protected === true) {
  99. return <Icon iconName="LockSolid" />;
  100. }
  101. return "";
  102. }
  103. },
  104. IsCompressed: {
  105. headerIcon: "ZipFolder",
  106. width: 25,
  107. sortable: false,
  108. formatter: function (compressed) {
  109. if (compressed === true) {
  110. return <Icon iconName="ZipFolder" />;
  111. }
  112. return "";
  113. }
  114. },
  115. __hpcc_displayName: {
  116. label: nlsHPCC.LogicalName, width: 600,
  117. formatter: function (name, row) {
  118. if (row.__hpcc_isDir) {
  119. return name;
  120. }
  121. const url = "#/files/" + (row.NodeGroup ? row.NodeGroup + "/" : "") + name;
  122. return <>
  123. <Image src={row.getStateImage ? row.getStateImage() : ""} />
  124. &nbsp;
  125. <Link href={url}>{name}</Link>
  126. </>;
  127. },
  128. },
  129. Owner: { label: nlsHPCC.Owner, width: 75 },
  130. SuperOwners: { label: nlsHPCC.SuperOwner, width: 150 },
  131. Description: { label: nlsHPCC.Description, width: 150 },
  132. NodeGroup: { label: nlsHPCC.Cluster, width: 108 },
  133. RecordCount: {
  134. label: nlsHPCC.Records, width: 85,
  135. renderCell: function (object, value, node, options) {
  136. domClass.add(node, "justify-right");
  137. node.innerText = Utility.valueCleanUp(value);
  138. },
  139. },
  140. IntSize: {
  141. label: nlsHPCC.Size, width: 100,
  142. renderCell: function (object, value, node, options) {
  143. domClass.add(node, "justify-right");
  144. node.innerText = Utility.convertedSize(value);
  145. },
  146. },
  147. Parts: {
  148. label: nlsHPCC.Parts, width: 60,
  149. renderCell: function (object, value, node, options) {
  150. domClass.add(node, "justify-right");
  151. node.innerText = Utility.valueCleanUp(value);
  152. },
  153. },
  154. Modified: { label: nlsHPCC.ModifiedUTCGMT, width: 162 },
  155. AtRestCost: {
  156. label: nlsHPCC.FileCostAtRest, width: 100,
  157. formatter: function (cost, row) {
  158. return `${formatCost(cost ?? 0)} (${currencyCode || "$"})`;
  159. }
  160. },
  161. AccessCost: {
  162. label: nlsHPCC.FileAccessCost, width: 100,
  163. formatter: function (cost, row) {
  164. return `${formatCost(cost ?? 0)} (${currencyCode || "$"})`;
  165. }
  166. }
  167. }
  168. });
  169. const [DeleteConfirm, setShowDeleteConfirm] = useConfirm({
  170. title: nlsHPCC.Delete,
  171. message: nlsHPCC.DeleteSelectedFiles,
  172. items: selection.map(s => s.Name),
  173. onSubmit: React.useCallback(() => {
  174. WsDfu.DFUArrayAction(selection, "Delete").then(() => refreshTable(true));
  175. }, [refreshTable, selection])
  176. });
  177. // Command Bar ---
  178. const buttons = React.useMemo((): ICommandBarItemProps[] => [
  179. {
  180. key: "refresh", text: nlsHPCC.Refresh, iconProps: { iconName: "Refresh" },
  181. onClick: () => refreshTable()
  182. },
  183. { key: "divider_1", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
  184. {
  185. key: "open", text: nlsHPCC.Open, disabled: !uiState.hasSelection, iconProps: { iconName: "WindowEdit" },
  186. onClick: () => {
  187. if (selection.length === 1) {
  188. window.location.href = "#/files/" + (selection[0].NodeGroup ? selection[0].NodeGroup + "/" : "") + selection[0].Name;
  189. } else {
  190. for (let i = selection.length - 1; i >= 0; --i) {
  191. window.open("#/files/" + (selection[i].NodeGroup ? selection[i].NodeGroup + "/" : "") + selection[i].Name, "_blank");
  192. }
  193. }
  194. }
  195. },
  196. {
  197. key: "delete", text: nlsHPCC.Delete, disabled: !uiState.hasSelection, iconProps: { iconName: "Delete" },
  198. onClick: () => setShowDeleteConfirm(true)
  199. },
  200. { key: "divider_2", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
  201. {
  202. key: "remoteCopy", text: nlsHPCC.RemoteCopy,
  203. onClick: () => setShowRemoteCopy(true)
  204. },
  205. { key: "divider_3", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
  206. {
  207. key: "copy", text: nlsHPCC.Copy, disabled: !uiState.hasSelection,
  208. onClick: () => setShowCopy(true)
  209. },
  210. {
  211. key: "rename", text: nlsHPCC.Rename, disabled: !uiState.hasSelection,
  212. onClick: () => setShowRenameFile(true)
  213. },
  214. {
  215. key: "addToSuperfile", text: nlsHPCC.AddToSuperfile, disabled: !uiState.hasSelection,
  216. onClick: () => setShowAddToSuperfile(true)
  217. },
  218. {
  219. key: "despray", text: nlsHPCC.Despray, disabled: !uiState.hasSelection,
  220. onClick: () => setShowDesprayFile(true)
  221. },
  222. { key: "divider_4", itemType: ContextualMenuItemType.Divider, onRender: () => <ShortVerticalDivider /> },
  223. {
  224. key: "filter", text: nlsHPCC.Filter, disabled: !!store, iconProps: { iconName: hasFilter ? "FilterSolid" : "Filter" },
  225. onClick: () => {
  226. setShowFilter(true);
  227. }
  228. },
  229. {
  230. key: "mine", text: nlsHPCC.Mine, disabled: true, iconProps: { iconName: "Contact" }, canCheck: true, checked: mine,
  231. onClick: () => {
  232. setMine(!mine);
  233. }
  234. },
  235. ], [hasFilter, mine, refreshTable, selection, setShowDeleteConfirm, store, uiState.hasSelection]);
  236. // Filter ---
  237. const filterFields: Fields = {};
  238. for (const field in FilterFields) {
  239. filterFields[field] = { ...FilterFields[field], value: filter[field] };
  240. }
  241. // Selection ---
  242. React.useEffect(() => {
  243. const state = { ...defaultUIState };
  244. for (let i = 0; i < selection.length; ++i) {
  245. state.hasSelection = true;
  246. // TODO: More State
  247. }
  248. setUIState(state);
  249. }, [selection]);
  250. return <HolyGrail
  251. header={<CommandBar items={buttons} farItems={copyButtons} />}
  252. main={
  253. <>
  254. <SizeMe monitorHeight>{({ size }) =>
  255. <div style={{ position: "relative", width: "100%", height: "100%" }}>
  256. <div style={{ position: "absolute", width: "100%", height: `${size.height}px` }}>
  257. <Grid height={`${size.height}px`} />
  258. </div>
  259. </div>
  260. }</SizeMe>
  261. <Filter showFilter={showFilter} setShowFilter={setShowFilter} filterFields={filterFields} onApply={pushParams} />
  262. <RemoteCopy showForm={showRemoteCopy} setShowForm={setShowRemoteCopy} refreshGrid={refreshTable} />
  263. <CopyFile logicalFiles={selection.map(s => s.Name)} showForm={showCopy} setShowForm={setShowCopy} refreshGrid={refreshTable} />
  264. <RenameFile logicalFiles={selection.map(s => s.Name)} showForm={showRenameFile} setShowForm={setShowRenameFile} refreshGrid={refreshTable} />
  265. <AddToSuperfile logicalFiles={selection.map(s => s.Name)} showForm={showAddToSuperfile} setShowForm={setShowAddToSuperfile} refreshGrid={refreshTable} />
  266. <DesprayFile logicalFiles={selection.map(s => s.Name)} showForm={showDesprayFile} setShowForm={setShowDesprayFile} />
  267. <DeleteConfirm />
  268. </>
  269. }
  270. footer={<GridPagination />}
  271. />;
  272. };