export function NewFileDropdownMenu()

in packages/online-editor/src/editor/Toolbar/NewFileDropdownMenu.tsx [58:388]


export function NewFileDropdownMenu(props: {
  destinationDirPath: string;
  workspaceDescriptor: WorkspaceDescriptor;
  onAddFile: (file?: WorkspaceFile) => Promise<void>;
}) {
  const { env } = useEnv();
  const uploadFileInputRef = useRef<HTMLInputElement>(null);
  const editorsConfig = useEditorsConfig();

  const [menuDrilledIn, setMenuDrilledIn] = useState<string[]>([]);
  const [drilldownPath, setDrilldownPath] = useState<string[]>([]);
  const [menuHeights, setMenuHeights] = useState<{ [key: string]: number }>({});
  const [activeMenu, setActiveMenu] = useState(ROOT_MENU_ID);

  const drillIn = useCallback((_event, fromMenuId, toMenuId, pathId) => {
    setMenuDrilledIn((prev) => [...prev, fromMenuId]);
    setDrilldownPath((prev) => [...prev, pathId]);
    setActiveMenu(toMenuId);
  }, []);

  const drillOut = useCallback((_event, toMenuId) => {
    setMenuDrilledIn((prev) => prev.slice(0, prev.length - 1));
    setDrilldownPath((prev) => prev.slice(0, prev.length - 1));
    setActiveMenu(toMenuId);
  }, []);

  const setHeight = useCallback((menuId: string, height: number) => {
    setMenuHeights((prev) => {
      if (prev[menuId] === undefined || (menuId !== ROOT_MENU_ID && prev[menuId] !== height)) {
        return { ...prev, [menuId]: height };
      }
      return prev;
    });
  }, []);

  const workspaces = useWorkspaces();
  const routes = useRoutes();
  const editorEnvelopeLocator = useEditorEnvelopeLocator();
  const { gitConfig } = useAuthSession(props.workspaceDescriptor.gitAuthSessionId);

  const addEmptyFile = useCallback(
    async (extension: string) => {
      const file = await workspaces.addEmptyFile({
        workspaceId: props.workspaceDescriptor.workspaceId,
        destinationDirRelativePath: props.destinationDirPath,
        extension,
      });
      await props.onAddFile(file);
    },
    [props, workspaces]
  );

  const urlInputRef = useRef<HTMLInputElement>(null);
  useEffect(() => {
    if (activeMenu === "importFromUrlMenu") {
      setTimeout(() => {
        urlInputRef.current?.focus();
      }, 500);
    }
  }, [activeMenu, urlInputRef]);

  const [isImporting, setImporting] = useState(false);
  const [importingError, setImportingError] = useState<string>();

  const successfullyUploadedAlert = useGlobalAlert(
    useCallback(({ close }, staticArgs: { qtt: number }) => {
      return (
        <Alert
          variant="success"
          title={`Successfully uploaded ${staticArgs.qtt} file(s).`}
          actionClose={<AlertActionCloseButton onClose={close} />}
        />
      );
    }, []),
    { durationInSeconds: 4 }
  );

  const handleFileUpload = useCallback(
    async (e: React.ChangeEvent<HTMLInputElement>) => {
      const filesToUpload = await Promise.all(
        Array.from(e.target.files ?? []).map(async (file: File) => {
          return {
            path: file.name,
            content: await new Promise<string>((res) => {
              const reader = new FileReader();
              reader.onload = (event: ProgressEvent<FileReader>) =>
                res(decoder.decode(event.target?.result as ArrayBuffer));
              reader.readAsArrayBuffer(file);
            }),
          };
        })
      );

      const uploadedFiles = await Promise.all(
        filesToUpload.map(async (file) => {
          return workspaces.addFile({
            workspaceId: props.workspaceDescriptor.workspaceId,
            name: basename(file.path, extname(file.path)),
            extension: extname(file.path).replace(".", ""),
            content: file.content,
            destinationDirRelativePath: props.destinationDirPath,
          });
        })
      );

      // Since non-editable files are not checked for changes, manually stage and commit these files
      await Promise.all(
        uploadedFiles.map(async (file) => {
          if (!isEditable(file.relativePath)) {
            return workspaces.stageFile({
              workspaceId: props.workspaceDescriptor.workspaceId,
              relativePath: file.relativePath,
            });
          }
        })
      );
      await workspaces.createSavePoint({
        workspaceId: props.workspaceDescriptor.workspaceId,
        gitConfig,
        commitMessage: `${env.KIE_SANDBOX_APP_NAME}: Added files${uploadedFiles.map(
          (file) => `\n- ${file.relativePath}`
        )}`,
        forceHasChanges: true,
      });

      const fileToGoTo = uploadedFiles.filter((file) => editorEnvelopeLocator.hasMappingFor(file.relativePath)).pop();

      await props.onAddFile(fileToGoTo);
      successfullyUploadedAlert.show({ qtt: uploadedFiles.length });
    },
    [workspaces, props, gitConfig, env.KIE_SANDBOX_APP_NAME, successfullyUploadedAlert, editorEnvelopeLocator]
  );

  const [url, setUrl] = useState("");
  const [authSessionId, setAuthSessionId] = useState(props.workspaceDescriptor.gitAuthSessionId);

  const importableUrl = useImportableUrl(
    url,
    useMemo(
      () => [
        UrlType.FILE,
        UrlType.GIST_DOT_GITHUB_DOT_COM_FILE,
        UrlType.GITHUB_DOT_COM_FILE,
        UrlType.BITBUCKET_DOT_ORG_FILE,
        UrlType.BITBUCKET_DOT_ORG_SNIPPET_FILE,
      ],
      []
    )
  );

  const { authSession } = useAuthSession(authSessionId);
  const gitHubClient = useGitHubClient(authSession);
  const bitbucketClient = useBitbucketClient(authSession);

  // Select authSession based on the importableUrl domain (begin)
  const authProviders = useAuthProviders();
  const { authSessions, authSessionStatus } = useAuthSessions();

  useEffect(() => {
    if (importableUrl.error) {
      return;
    }

    const urlDomain = importableUrl.url?.host;

    const { compatible } = getCompatibleAuthSessionWithUrlDomain({
      authProviders,
      authSessions,
      authSessionStatus,
      urlDomain,
    });
    setAuthSessionId(compatible[0]!.id);
  }, [authProviders, authSessionStatus, authSessions, importableUrl]);
  // Select authSession based on the importableUrl domain (end)

  const importFromUrl = useCallback(
    async (importableUrl: ImportableUrl) => {
      if (!importableUrl.url) {
        return;
      }

      setImporting(true);
      setImportingError(undefined);

      try {
        const { error, rawUrl, content } = await fetchSingleFileContent(importableUrl, gitHubClient, bitbucketClient);
        if (error) {
          setImportingError(error);
          return;
        }

        const extension = extname(rawUrl!.pathname).replace(".", "");
        const name = decodeURIComponent(basename(rawUrl!.pathname, extname(rawUrl!.pathname)));

        const file = await workspaces.addFile({
          workspaceId: props.workspaceDescriptor.workspaceId,
          name,
          extension,
          content: content!,
          destinationDirRelativePath: props.destinationDirPath,
        });
        await props.onAddFile(file);
      } catch (e) {
        setImportingError(e.toString());
      } finally {
        setImporting(false);
      }
    },
    [gitHubClient, bitbucketClient, workspaces, props]
  );

  const sampleUrl = useCallback(
    (extension: string) =>
      `${window.location.origin}${window.location.pathname}${routes.static.sample.path({ type: extension })}`,
    [routes]
  );

  // TODO: Implement a better solution to dynamically create this array, based on the number of editors enabled in the editorsConfig.
  // This solution was devised as a temporary fix in response to allowing users to enable/disable the PMML editor. See kie-issues#311 for more details.

  const importableUrlSamples = new Map([
    [editorsConfig[0]?.extension, useImportableUrl(sampleUrl(editorsConfig[0]?.extension))],
    [editorsConfig[1]?.extension, useImportableUrl(sampleUrl(editorsConfig[1]?.extension))],
    [editorsConfig[2]?.extension, useImportableUrl(sampleUrl(editorsConfig[2]?.extension))],
    [editorsConfig[3]?.extension, useImportableUrl(sampleUrl(editorsConfig[3]?.extension))],
    [editorsConfig[4]?.extension, useImportableUrl(sampleUrl(editorsConfig[4]?.extension))],
  ]);

  return (
    <Menu
      tabIndex={1}
      style={{ boxShadow: "none", minWidth: "400px" }}
      id={ROOT_MENU_ID}
      containsDrilldown={true}
      onDrillIn={drillIn}
      onDrillOut={drillOut}
      activeMenu={activeMenu}
      onGetMenuHeight={setHeight}
      drilldownItemPath={drilldownPath}
      drilledInMenus={menuDrilledIn}
    >
      <MenuContent menuHeight={`${menuHeights[activeMenu]}px`}>
        <MenuList style={{ padding: 0 }}>
          {editorsConfig.map((config, index) => {
            return (
              <MenuItem
                key={index}
                itemId={`new${config.extension}ItemId`}
                onClick={() => addEmptyFile(config.extension)}
                description={config.card.description}
              >
                <b>
                  <FileLabel style={{ marginBottom: "4px" }} extension={config.extension} />
                </b>
              </MenuItem>
            );
          })}

          <Divider />
          <MenuItem
            description={"Try sample models"}
            itemId="samplesItemId"
            direction={"down"}
            drilldownMenu={
              <DrilldownMenu id={"samplesMenu"}>
                <MenuItem direction="up">Back</MenuItem>
                <Divider />

                {editorsConfig.map((config, index) => {
                  return (
                    <MenuGroup label={" "} key={index}>
                      <MenuItem
                        onClick={() => importFromUrl(importableUrlSamples.get(config.extension)!)}
                        description={config.card.description}
                      >
                        <Flex>
                          <FlexItem>Sample</FlexItem>
                          <FlexItem>
                            <FileLabel extension={config.extension} />
                          </FlexItem>
                        </Flex>
                      </MenuItem>
                    </MenuGroup>
                  );
                })}
              </DrilldownMenu>
            }
          >
            Samples
          </MenuItem>
          <Divider />
          <MenuItem
            itemId={"importFromUrlItemId"}
            direction={"down"}
            drilldownMenu={
              <DrilldownMenu id={"importFromUrlMenu"}>
                <MenuItem direction="up">Back</MenuItem>
                <Divider />
                {/* Allows for arrows to work when editing the text. */}
                <MenuSearch>
                  <MenuSearchInput onKeyDown={(e) => e.stopPropagation()}>
                    <ImportSingleFileFromUrlForm
                      authSessionSelectHelperText={`Changing it here won't change it on '${props.workspaceDescriptor.name}'`}
                      importingError={importingError}
                      importableUrl={importableUrl}
                      urlInputRef={urlInputRef}
                      url={url}
                      setUrl={(url) => {
                        setUrl(url);
                        setImportingError(undefined);
                      }}
                      authSessionId={authSessionId}
                      setAuthSessionId={setAuthSessionId}
                      onSubmit={() => importFromUrl(importableUrl)}
                    />
                  </MenuSearchInput>
                </MenuSearch>
                <MenuSearch>
                  <MenuSearchInput>
                    <Button
                      variant={ButtonVariant.primary}
                      isDisabled={!!importableUrl.error}
                      isLoading={isImporting}
                      onClick={() => importFromUrl(importableUrl)}
                    >
                      Import
                    </Button>
                  </MenuSearchInput>
                </MenuSearch>
              </DrilldownMenu>
            }