Skip to content
Open
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
55 changes: 41 additions & 14 deletions etna/packages/etna-js/actions/file_actions.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, {useRef, useState} from 'react';
import React, {useRef, useState, useCallback, useEffect} from 'react';
import Modal from 'react-modal';

import ButtonBar from '../components/button_bar';
import Icon from '../components/icon';

export const STUB = '::blank';
export const TEMP = '::temp';
import {PickBucket, PickFileOrFolder} from '../components/metis_exploration';

// We don't have a lot of content, so let's get a smaller Modal
export const customStyles = {
Expand Down Expand Up @@ -40,7 +41,28 @@ export const useFileInputActions = (
onChange,
onBlur
) => {
const metisPathRef = useRef(null);
const [metisPath, setMetisPath] = useState('');

const [bucketName, setBucketName] = useState('');
const [path, setPath] = useState('');
const [targetType, setTargetType] = useState(null);

function changeBucket(e) {
setPath('');
setTargetType(null);
setBucketName(e);
}
function reset() {
changeBucket('');
}

useEffect(() => {
if (targetType=='file') {
setMetisPath(`metis://${CONFIG.project_name}/${bucketName}/${path}`)
} else {
setMetisPath('')
}
}, [bucketName, path, targetType])

return {
metisSelector,
Expand Down Expand Up @@ -71,22 +93,28 @@ export const useFileInputActions = (
appElement={document.querySelector('#root')}
>
<div className='attribute modal file-metis-select'>
<h2>Enter a Metis path</h2>
<h2>Select a Metis file</h2>
<div className='input-box-wrapper'>
<label htmlFor='metis-path-input'>Metis path:</label>
<input
id='metis-path-input'
className='full_text metis-path-input'
type='text'
ref={metisPathRef}
placeholder='metis://<project>/<bucket>/<file-path>'
<PickBucket
bucket={bucketName}
label="Bucket"
setBucket={(e) => changeBucket(e)}
/>
<PickFileOrFolder
bucket={bucketName}
label="Destination Folder"
useTargetType={setTargetType}
setPath={(e) => setPath(e)}
basePath={''}
topLevelPlaceholder={'top-level of bucket'}
path={path}
/>
<div className='modal-button-wrapper'>
<ButtonBar
className='modal-buttons'
buttons={[
{type: 'check', click: () => selectMetisFile()},
{type: 'cancel', click: () => setMetis(false)}
{type: 'cancel', click: () => closeModal()}
]}
/>
{error ? (
Expand Down Expand Up @@ -116,11 +144,10 @@ export const useFileInputActions = (

function closeModal() {
setMetis(false);
reset();
}

function selectMetisFile() {
const metisPath = metisPathRef.current.value;

if (!METIS_PATH_MATCH(metisPath)) {
setError(true);
return;
Expand All @@ -132,7 +159,7 @@ export const useFileInputActions = (
onChange(formatFileRevision(metisPath));
onBlur();

metisPathRef.current.value = null;
reset();
}

function formatFileRevision(newValue, files) {
Expand Down
102 changes: 85 additions & 17 deletions etna/packages/etna-js/components/metis_exploration.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React, { useState, useCallback, useEffect, useMemo } from 'react';
import {metisPath} from 'etna-js/api/metis_api.js'
import Autocomplete from '@material-ui/lab/Autocomplete';
import { Grid, IconButton, TextField } from '@material-ui/core';
import Grid from '@material-ui/core/Grid';
import IconButton from '@material-ui/core/IconButton';
import TextField from '@material-ui/core/TextField';
import Tooltip from '@material-ui/core/Tooltip';
import AutorenewIcon from '@material-ui/icons/Autorenew';
import { json_get } from 'etna-js/utils/fetch';
import SubdirectoryArrowRightIcon from '@material-ui/icons/SubdirectoryArrowRight';
import AddIcon from '@material-ui/icons/Add';
Expand Down Expand Up @@ -176,6 +180,7 @@ export function PickFileOrFolder({ project_name=CONFIG.project_name, bucket, set
className?: string;
disablePortal?: boolean;
}) {
const [firstBucketPath, setFirstBucketPath] = useState([bucket,path]);
const [pathArray, setPathArray] = useState(pathToArray(path, basePath));
const [targetType, setTargetType] = useState(null as 'folder' | 'file' | null) // null = not yet determined
const labelUse = label!==undefined ? label : allowFiles ? "File or Folder" : "Folder"
Expand All @@ -184,6 +189,8 @@ export function PickFileOrFolder({ project_name=CONFIG.project_name, bucket, set
const [firstRender, setFirstRender] = useState(true) // tracked so we can leave newly rendered dropdowns closed when not the user's focus
const [fetchContents, setFetchContents] = useState([] as string[])
const [fetching, setFetching] = useState(false)
const [reset, setReset] = useState(false);
const [reseting, setReseting] = useState(false);

const pathInMetis = useCallback( (folderPath: string) => {
return `${project_name}/list/${bucket}/${folderPath}`
Expand Down Expand Up @@ -227,7 +234,18 @@ export function PickFileOrFolder({ project_name=CONFIG.project_name, bucket, set
const target = pathArray[pathArray.length-1]
// without passing upwards to not trigger extraneous updates in parent component updates at page refresh
if (target && target != '') {
useDetermineType(target, stripSlash(arrayToPath(containerPathArray, basePath)), true, false)
const newType = useDetermineType(target, stripSlash(arrayToPath(containerPathArray, basePath)), true, false)
// add + button if needed
if (firstRender && newType=='folder') {
const fullPath = stripSlash(arrayToPath(pathArray, basePath))
if (!pathSeen(fullPath)) {
fetchFolderContents(fullPath, (f) => {
if (contentUse(f, allowFiles).length > 0) {
setShowAddButton(pathArray.length-1);
}
})
}
}
}
if (target && target == '') {
useType('folder', false)
Expand All @@ -249,31 +267,32 @@ export function PickFileOrFolder({ project_name=CONFIG.project_name, bucket, set
},
[project_name, bucket, contentsSeen]);

function determineContentsNeeded(pathArray: string[]) {
let contentsNeeded = ['']
for (let i=1; i <= pathArray.length-1; i++) {
const thisPath = stripSlash(arrayToPath(pathArray.slice(0,i), basePath))
contentsNeeded.push(thisPath)
}
return contentsNeeded
}

// Determine folder contents to grab at very start, or once first valid bucket given
useEffect( () => {
if (bucket != '' && Object.keys(contentsSeen).length==0 && !fetching) {
let contentsNeeded = ['']
for (let i=1; i <= pathArray.length-1; i++) {
const thisPath = stripSlash(arrayToPath(pathArray.slice(0,i), basePath))
contentsNeeded.push(thisPath)
}
setFetchContents(contentsNeeded)
setFetchContents(determineContentsNeeded(pathArray))
}
}, [bucket])

// Get contents of paths in fetchContents, one at a time.
// Note: Content fetching for a path-targeted folder always happens elsewhere
useEffect( ()=>{
if (fetchContents.length>0) {
if (pathSeen(fetchContents[0])) {
setFetching(false)
setFetchContents(fetchContents.slice(1))
} else if (!fetching) {
setFetching(true)
if (path !='' && fetchContents[0]==path) {
fetchFolderContents(fetchContents[0], (f) => { if (contentUse(f, allowFiles).length > 0) setPathArray([...pathArray, '']) })
} else {
fetchFolderContents(fetchContents[0])
}
fetchFolderContents(fetchContents[0])
}
}
}, [fetchContents, contentsSeen, pathArray])
Expand All @@ -284,9 +303,41 @@ export function PickFileOrFolder({ project_name=CONFIG.project_name, bucket, set
setFirstRender(true)
setPathArray(pathToArray('', basePath))
setFetchContents([''])
setShowAddButton(-1);
}
}, [bucket])

// Reset when 'reset' button clicked
// clear contentsSeen, repopulate fetchContents, set pathArray to original if bucket was same
useEffect(() => {
if (reset) {
const resetPathArray = pathToArray(firstBucketPath[1], basePath)
if (!reseting) { // Initial time through
setFirstRender(true);
setContentsSeen({});
setShowAddButton(-1);
if (bucket == firstBucketPath[0]) {
setReseting(true); // Rely on next renders to fetch contents
setPathArray(resetPathArray);
setFetchContents(determineContentsNeeded(resetPathArray));
} else {
setReset(false); // Done in this render
setPathArray(pathToArray('', basePath));
setFetchContents(['']);
}
} else { // next times through
if (fetchContents.length < 1) {
if (resetPathArray.length > 1 || resetPathArray[0]!='') {
const end = resetPathArray.length-1
updateTarget(resetPathArray[end], resetPathArray.slice(0,-1), end, false, false)
}
setReseting(false);
setReset(false);
}
}
}
}, [reset, reseting, fetchContents])

// Update main readout (path) whenever anything updates pathArray
useEffect(() => {
setPath(stripSlash(arrayToPath(pathArray, basePath)));
Expand All @@ -301,8 +352,8 @@ export function PickFileOrFolder({ project_name=CONFIG.project_name, bucket, set
}, [path]);

// onChange for inners
const updateTarget = (newPath: string, currentPathSet: string[], depth: number) => {
setFirstRender(false)
const updateTarget = (newPath: string, currentPathSet: string[], depth: number, clearFirstRender = true, autoAddLevel = true) => {
if (clearFirstRender) setFirstRender(false)
setShowAddButton(-1)

// Also trim at the level just picked in case not actually the deepest
Expand All @@ -326,9 +377,15 @@ export function PickFileOrFolder({ project_name=CONFIG.project_name, bucket, set
if (!pathSeen(targetPath)) {
fetchFolderContents(targetPath, (f) => {
if (contentUse(f, allowFiles).length > 0) {
nextPathSet = [...nextPathSet, '']
if (autoAddLevel) {
setPathArray([...nextPathSet, '']);
} else {
setPathArray(nextPathSet);
setShowAddButton(depth);
}
} else {
setPathArray(nextPathSet)
}
setPathArray(nextPathSet)
})
} else {
if (contentUse(contentsSeen[pathInMetis(targetPath)] as folderSummary, allowFiles).length > 0) {
Expand Down Expand Up @@ -410,6 +467,17 @@ export function PickFileOrFolder({ project_name=CONFIG.project_name, bucket, set
</IconButton>
</Grid> : null
}
{index!=0 ? null : <Grid item container alignItems='flex-end' style={{width: 'auto'}}>
{/* <Tooltip title="Refresh Content Cache"> */}
<IconButton
aria-label="Refresh Content Cache"
size="small"
onClick={() => setReset(true)}>
<AutorenewIcon/>
</IconButton>
{/* </Tooltip> */}
</Grid>
}
</Grid>
)
})}
Expand Down
Loading