From 7d0a27b2681d3a3f671ceaf5daeec1811c2196f2 Mon Sep 17 00:00:00 2001 From: nl_0 Date: Wed, 16 Dec 2020 21:21:57 +0500 Subject: [PATCH 1/4] utils/tagged: fix equality checks --- catalog/app/utils/tagged.js | 16 ++++------------ catalog/app/utils/tagged.spec.js | 24 +++++++++++++++++++++++- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/catalog/app/utils/tagged.js b/catalog/app/utils/tagged.js index 50ae3d9e1f0..88166e4e278 100644 --- a/catalog/app/utils/tagged.js +++ b/catalog/app/utils/tagged.js @@ -3,14 +3,7 @@ import * as R from 'ramda' const scope = 'app/utils/tagged' -const TAG = Symbol('tag') -const VALUE = Symbol('value') - -const getTag = (inst) => (inst ? inst[TAG] : undefined) - -const getValue = (inst) => (inst ? inst[VALUE] : undefined) - -const withValue = (fn) => (inst, ...rest) => fn(getValue(inst), ...rest) +const withValue = (fn) => (inst, ...rest) => fn(R.prop('value', inst), ...rest) const exhaustive = (variants, cases) => cases._ || variants.every((v) => cases[v]) @@ -44,12 +37,12 @@ export default (variants) => { const variantMap = R.invertObj(symbols) const getVariant = (inst) => { - const tag = getTag(inst) + const tag = R.prop('type', inst) return tag && variantMap[tag] } const mkConstructor = (tag, variant) => { - const constructor = (value) => ({ [TAG]: tag, [VALUE]: value, type: tag }) + const constructor = (value) => ({ type: tag, value }) constructor.is = (inst, pred) => getVariant(inst) === variant && (pred ? pred(constructor.unbox(inst)) : true) @@ -59,7 +52,7 @@ export default (variants) => { constructor.is(inst), `${scope}/unbox: must be called with an instance of type`, ) - return getValue(inst) + return R.prop('value', inst) } return constructor @@ -119,7 +112,6 @@ export default (variants) => { */ reducer: (cases) => (acc, next) => doCase(cases, next)(acc), - // TODO: mapCase ...constructors, } } diff --git a/catalog/app/utils/tagged.spec.js b/catalog/app/utils/tagged.spec.js index e1e7ddc3bec..a1d7b381f52 100644 --- a/catalog/app/utils/tagged.spec.js +++ b/catalog/app/utils/tagged.spec.js @@ -1,8 +1,30 @@ +import * as R from 'ramda' + // import { withInitialState } from 'utils/reduxTools'; import tagged from './tagged' -describe('tagged', () => { +describe('utils/tagged', () => { + describe('equality check', () => { + const Type = tagged(['A', 'B']) + + it('should return true for equal values', () => { + const a1 = Type.A({ a: 1 }) + const a11 = Type.A({ a: 1 }) + expect(R.equals(a1, a11)).toBe(true) + }) + it('should return false for different values of the same type', () => { + const a1 = Type.A({ a: 1 }) + const a2 = Type.A({ a: 2 }) + expect(R.equals(a1, a2)).toBe(false) + }) + it('should return false for different values of different types', () => { + const a1 = Type.A({ a: 1 }) + const b = Type.B() + expect(R.equals(a1, b)).toBe(false) + }) + }) + describe('case', () => { const Result = tagged(['Ok', 'Err']) const ok = Result.Ok('ok') From 41343deb765f36f1827912f3e54e31647bad8c26 Mon Sep 17 00:00:00 2001 From: nl_0 Date: Wed, 16 Dec 2020 21:37:21 +0500 Subject: [PATCH 2/4] Bucket/PackageUpdateDialog: tree view for files --- .../containers/Bucket/PackageUpdateDialog.js | 646 ++++++++++++++---- 1 file changed, 512 insertions(+), 134 deletions(-) diff --git a/catalog/app/containers/Bucket/PackageUpdateDialog.js b/catalog/app/containers/Bucket/PackageUpdateDialog.js index f75217f7c27..7cd34e8eeed 100644 --- a/catalog/app/containers/Bucket/PackageUpdateDialog.js +++ b/catalog/app/containers/Bucket/PackageUpdateDialog.js @@ -7,6 +7,7 @@ import { useDropzone } from 'react-dropzone' import * as RF from 'react-final-form' import { Link } from 'react-router-dom' import * as M from '@material-ui/core' +import { darken } from '@material-ui/core' import * as APIConnector from 'utils/APIConnector' import AsyncResult from 'utils/AsyncResult' @@ -25,7 +26,460 @@ import * as validators from 'utils/validators' import * as PD from './PackageDialog' import * as requests from './requests' -const TYPE_ORDER = ['added', 'modified', 'deleted', 'unchanged'] +const FilesEntry = tagged([ + 'Dir', // { name: str, type: enum, children: [FilesEntry] } + 'File', // { name: str, type: enum, size: num } +]) + +const FilesAction = tagged([ + 'Add', // { files: [File], prefix: str } + 'Delete', // path: str + 'DeleteDir', // prefix: str + 'Revert', // path: str + 'RevertDir', // prefix: str + 'Reset', +]) + +const insertIntoTree = (path = [], file, entries) => { + let inserted = file + let restEntries = entries + if (path.length) { + const [current, ...rest] = path + let baseDir = FilesEntry.Dir({ name: current, type: file.type, children: [] }) + const existingDir = entries.find( + FilesEntry.case({ + File: () => false, + Dir: R.propEq('name', current), + }), + ) + if (existingDir) { + restEntries = R.without([existingDir], entries) + baseDir = existingDir + } + inserted = insertIntoDir(rest, file, baseDir) + } + const getOrder = FilesEntry.case({ + Dir: (d) => [0, d.name], + File: (f) => [1, f.name], + }) + const tree = R.sortBy(getOrder, [inserted, ...restEntries]) + return tree +} + +const insertIntoDir = (path, file, dir) => { + const { name, children } = FilesEntry.Dir.unbox(dir) + const newChildren = insertIntoTree(path, file, children) + const type = newChildren + .map(FilesEntry.case({ Dir: R.prop('type'), File: R.prop('type') })) + .reduce((acc, entryType) => (acc === entryType ? acc : 'modified')) + return FilesEntry.Dir({ name, type, children: newChildren }) +} + +const dissocBy = (fn) => + R.pipe( + R.toPairs, + R.filter(([k]) => !fn(k)), + R.fromPairs, + ) + +/* +// TODO: use tree as the main data model / source of truth? +state type: { + existing: { [path]: file }, + added: { [path]: file }, + // TODO: use Array or Set? + deleted: { [path]: true }, +} +*/ + +const handleFilesAction = FilesAction.case({ + Add: ({ files, prefix }) => (state) => + files.reduce((acc, file) => { + const path = (prefix || '') + PD.getNormalizedPath(file) + return R.evolve( + { + added: R.assoc(path, file), + deleted: R.dissoc(path), + }, + acc, + ) + }, state), + Delete: (path) => R.evolve({ deleted: R.assoc(path, true) }), + // add all descendants from existing to deleted + DeleteDir: (prefix) => ({ existing, added, deleted }) => ({ + existing, + added, + deleted: R.mergeLeft( + Object.keys(existing).reduce( + (acc, k) => (k.startsWith(prefix) ? { ...acc, [k]: true } : acc), + {}, + ), + deleted, + ), + }), + Revert: (path) => R.evolve({ added: R.dissoc(path), deleted: R.dissoc(path) }), + // remove all descendants from added and deleted + RevertDir: (prefix) => + R.evolve({ + added: dissocBy(R.startsWith(prefix)), + deleted: dissocBy(R.startsWith(prefix)), + }), + Reset: (_, { initial }) => () => initial, +}) + +const computeEntries = ({ added, deleted, existing }) => { + const existingEntries = Object.entries(existing).map(([path, { size }]) => { + if (path in deleted) { + return { type: 'deleted', path, size } + } + if (path in added) { + const a = added[path] + return { type: 'modified', path, size: a.size } + } + return { type: 'unchanged', path, size } + }) + const addedEntries = Object.entries(added).reduce((acc, [path, { size }]) => { + if (path in existing) return acc + return acc.concat({ type: 'added', path, size }) + }, []) + const entries = [...existingEntries, ...addedEntries] + return entries.reduce((children, { path, ...rest }) => { + const parts = path.split('/') + const prefixPath = R.init(parts).map((p) => `${p}/`) + const name = R.last(parts) + const file = FilesEntry.File({ name, ...rest }) + return insertIntoTree(prefixPath, file, children) + }, []) +} + +const COLORS = { + default: { + main: M.colors.grey[600], + accent: M.colors.grey[900], + }, + added: { + main: M.colors.green[600], + accent: M.colors.green[900], + }, + modified: { + main: M.colors.yellow[800], + accent: darken(M.colors.yellow[800], 0.2), + }, + deleted: { + main: M.colors.red[600], + accent: M.colors.red[900], + }, +} + +const useFileStyles = M.makeStyles((t) => ({ + added: {}, + modified: {}, + deleted: {}, + unchanged: {}, + root: { + alignItems: 'center', + background: t.palette.background.paper, + cursor: 'default', + display: 'flex', + outline: 'none', + + color: COLORS.default.main, + '&:hover': { + color: COLORS.default.accent, + }, + '&$added': { + color: COLORS.added.main, + '&:hover': { + color: COLORS.added.accent, + }, + }, + '&$modified': { + color: COLORS.modified.main, + '&:hover': { + color: COLORS.modified.accent, + }, + }, + '&$deleted': { + color: COLORS.deleted.main, + '&:hover': { + color: COLORS.deleted.accent, + }, + }, + }, + icon: { + boxSizing: 'content-box', + fontSize: 18, + padding: 3, + }, + name: { + ...t.typography.body2, + flexGrow: 1, + marginRight: t.spacing(1), + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, + size: { + ...t.typography.body2, + marginRight: t.spacing(0.5), + opacity: 0.62, + }, +})) + +function File({ name, type, size, prefix, dispatch }) { + const classes = useFileStyles() + + const path = (prefix || '') + name + + const handle = React.useCallback( + (cons) => (e) => { + e.stopPropagation() + dispatch(cons(path)) + }, + [dispatch, path], + ) + + const action = React.useMemo( + () => + ({ + added: { hint: 'Remove', icon: 'clear', handler: handle(FilesAction.Revert) }, + modified: { + hint: 'Revert', + icon: 'restore', + handler: handle(FilesAction.Revert), + }, + deleted: { + hint: 'Restore', + icon: 'restore', + handler: handle(FilesAction.Revert), + }, + unchanged: { hint: 'Delete', icon: 'clear', handler: handle(FilesAction.Delete) }, + }[type]), + [type, handle], + ) + + const onClick = React.useCallback((e) => { + e.stopPropagation() + }, []) + + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events +
+ insert_drive_file +
+ {name} +
+
{readableBytes(size)}
+ + {action.icon} + +
+ ) +} + +const useDirStyles = M.makeStyles((t) => ({ + // TODO: support non-diff mode (for package creation) + diff: {}, + added: {}, + modified: {}, + deleted: {}, + unchanged: {}, + active: {}, + root: { + background: t.palette.background.paper, + cursor: 'pointer', + outline: 'none', + position: 'relative', + }, + head: { + alignItems: 'center', + display: 'flex', + outline: 'none', + + color: COLORS.default.main, + '$active > &, &:hover': { + color: COLORS.default.accent, + }, + '$added > &': { + color: COLORS.added.main, + }, + '$added$active > &, $added > &:hover': { + color: COLORS.added.accent, + }, + '$modified > &': { + color: COLORS.modified.main, + }, + '$modified$active > &, $modified > &:hover': { + color: COLORS.modified.accent, + }, + '$deleted > &': { + color: COLORS.deleted.main, + }, + '$deleted$active > &, $deleted > &:hover': { + color: COLORS.deleted.accent, + }, + }, + icon: { + boxSizing: 'content-box', + fontSize: 18, + padding: 3, + }, + name: { + ...t.typography.body2, + flexGrow: 1, + marginRight: t.spacing(1), + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }, + body: { + paddingLeft: 20, + }, + bar: { + bottom: 0, + left: 0, + opacity: 0.2, + position: 'absolute', + top: 24, + width: 24, + '$active > $head > &, $head:hover > &': { + opacity: 0.4, + }, + '&::before': { + background: 'currentColor', + borderRadius: 2, + bottom: 4, + content: '""', + left: 10, + position: 'absolute', + top: 4, + width: 4, + }, + }, + emptyDummy: { + height: 24, + }, + empty: { + ...t.typography.body2, + bottom: 0, + fontStyle: 'italic', + left: 24, + lineHeight: '24px', + opacity: 0.6, + overflow: 'hidden', + position: 'absolute', + right: 0, + top: 24, + }, +})) + +function Dir({ name, type, children, prefix, dispatch }) { + const classes = useDirStyles() + // TODO: move state out? + const [expanded, setExpanded] = React.useState(true) + + const toggleExpanded = React.useCallback( + (e) => { + e.stopPropagation() + setExpanded((x) => !x) + }, + [setExpanded], + ) + + const onClick = React.useCallback((e) => { + e.stopPropagation() + }, []) + + const path = (prefix || '') + name + + const onDrop = React.useCallback( + (files) => { + dispatch(FilesAction.Add({ prefix: path, files })) + }, + [dispatch, path], + ) + + const { getRootProps, isDragActive } = useDropzone({ + onDrop, + noDragEventsBubbling: true, + }) + + const handle = React.useCallback( + (cons) => (e) => { + e.stopPropagation() + dispatch(cons(path)) + }, + [dispatch, path], + ) + + const action = React.useMemo( + () => + ({ + added: { hint: 'Remove', icon: 'clear', handler: handle(FilesAction.RevertDir) }, + modified: { + hint: 'Revert', + icon: 'restore', + handler: handle(FilesAction.RevertDir), + }, + deleted: { + hint: 'Restore', + icon: 'restore', + handler: handle(FilesAction.RevertDir), + }, + unchanged: { + hint: 'Delete', + icon: 'clear', + handler: handle(FilesAction.DeleteDir), + }, + }[type]), + [type, handle], + ) + + return ( +
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */} +
+ {expanded ? 'folder_open' : 'folder'} +
{name}
+ + {action.icon} + +
+ {!children.length &&
{''}
} +
+ +
+ {children.length ? ( + children.map((entry) => { + const [Component, props] = FilesEntry.case( + { + Dir: (ps) => [Dir, ps], + File: (ps) => [File, ps], + }, + entry, + ) + return ( + + ) + }) + ) : ( +
+ )} +
+ +
+ ) +} const useFilesInputStyles = M.makeStyles((t) => ({ root: { @@ -65,7 +519,6 @@ const useFilesInputStyles = M.makeStyles((t) => ({ background: t.palette.action.hover, border: `1px solid ${t.palette.action.disabled}`, borderRadius: t.shape.borderRadius, - cursor: 'pointer', display: 'flex', flexDirection: 'column', minHeight: 140, @@ -84,6 +537,7 @@ const useFilesInputStyles = M.makeStyles((t) => ({ dropMsg: { ...t.typography.body2, alignItems: 'center', + cursor: 'pointer', display: 'flex', flexGrow: 1, justifyContent: 'center', @@ -98,6 +552,7 @@ const useFilesInputStyles = M.makeStyles((t) => ({ color: t.palette.warning.dark, }, filesContainer: { + direction: 'rtl', // show the scrollbar on the right borderBottom: `1px solid ${t.palette.action.disabled}`, maxHeight: 200, overflowX: 'hidden', @@ -109,41 +564,8 @@ const useFilesInputStyles = M.makeStyles((t) => ({ filesContainerWarn: { borderColor: t.palette.warning.dark, }, - added: {}, - modified: {}, - deleted: {}, - unchanged: {}, - fileEntry: { - alignItems: 'center', - background: t.palette.background.paper, - display: 'flex', - '&:not(:last-child)': { - borderBottomStyle: 'solid', - borderBottomWidth: '1px', - borderColor: 'inherit', - }, - '&$added': { - background: M.colors.green[100], - }, - '&$modified': { - background: M.colors.yellow[100], - }, - '&$deleted': { - background: M.colors.red[100], - }, - }, - filePath: { - ...t.typography.body2, - flexGrow: 1, - marginRight: t.spacing(1), - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - }, - fileSize: { - ...t.typography.body2, - color: t.palette.text.secondary, - marginRight: t.spacing(0.5), + filesContainerInner: { + direction: 'ltr', }, lock: { alignItems: 'center', @@ -182,15 +604,18 @@ const useFilesInputStyles = M.makeStyles((t) => ({ }, })) -function FilesInput({ input, meta, uploads, setUploads, errors = {} }) { +function FilesInput({ + input: { value, onChange }, + meta, + uploads, + onFilesAction, + errors = {}, +}) { const classes = useFilesInputStyles() - const value = input.value || { added: {}, deleted: {}, existing: {} } const disabled = meta.submitting || meta.submitSucceeded const error = meta.submitFailed && meta.error - const onInputChange = input.onChange - const totalSize = React.useMemo( () => Object.values(value.added).reduce((sum, f) => sum + f.size, 0), [value.added], @@ -209,80 +634,38 @@ function FilesInput({ input, meta, uploads, setUploads, errors = {} }) { 'Drop files here or click to browse' ) - const pipeValue = useMemoEq([onInputChange, value], () => (...fns) => - R.pipe(...fns, onInputChange)(value), - ) + const ref = React.useRef() + ref.current = { + value, + disabled, + initial: meta.initial, + onChange, + onFilesAction, + } + const { current: dispatch } = React.useRef((action) => { + const cur = ref.current + if (cur.disabled) return + const newValue = handleFilesAction(action, { initial: cur.initial })(cur.value) + cur.onChange(newValue) + if (cur.onFilesAction) cur.onFilesAction(action, cur.value, newValue) + }) const onDrop = React.useCallback( (files) => { - if (disabled) return - pipeValue( - R.reduce( - (acc, file) => { - const path = PD.getNormalizedPath(file) - return R.evolve( - { - added: R.assoc(path, file), - deleted: R.dissoc(path), - }, - acc, - ) - }, - R.__, // eslint-disable-line no-underscore-dangle - files, - ), - ) + dispatch(FilesAction.Add({ files })) }, - [disabled, pipeValue], + [dispatch], ) - const rm = (path) => { - pipeValue(R.evolve({ added: R.dissoc(path) })) - setUploads(R.dissoc(path)) - } - - const del = (path) => { - pipeValue(R.evolve({ deleted: R.assoc(path, true) })) - } - - const restore = (path) => { - pipeValue(R.evolve({ deleted: R.dissoc(path) })) - } - - const handleAction = (handler, ...args) => (e) => { - e.stopPropagation() - if (disabled) return - handler(...args) - } - - const revertFiles = React.useCallback(() => { - if (disabled) return - onInputChange(meta.initial) - setUploads({}) - }, [disabled, onInputChange, setUploads, meta.initial]) + const resetFiles = React.useCallback(() => { + dispatch(FilesAction.Reset()) + }, [dispatch]) const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop }) const totalProgress = React.useMemo(() => getTotalProgress(uploads), [uploads]) - const computedEntries = useMemoEq(value, ({ added, deleted, existing }) => { - const existingEntries = Object.entries(existing).map(([path, { size }]) => { - if (path in deleted) { - return { type: 'deleted', path, size } - } - if (path in added) { - const a = added[path] - return { type: 'modified', path, size: a.size, delta: a.size - size } - } - return { type: 'unchanged', path, size } - }) - const addedEntries = Object.entries(added).reduce((acc, [path, { size }]) => { - if (path in existing) return acc - return acc.concat({ type: 'added', path, size }) - }, []) - const entries = [...existingEntries, ...addedEntries] - return R.sortBy((x) => [TYPE_ORDER.indexOf(x.type), x.path], entries) - }) + const computedEntries = useMemoEq(value, computeEntries) const stats = useMemoEq(value, ({ added, deleted, existing }) => ({ added: Object.values(added).reduce( @@ -333,7 +716,7 @@ function FilesInput({ input, meta, uploads, setUploads, errors = {} }) { {meta.dirty && ( restore} @@ -364,34 +747,18 @@ function FilesInput({ input, meta, uploads, setUploads, errors = {} }) { !error && warn && classes.filesContainerWarn, )} > - {computedEntries.map(({ type, path, size }) => { - const action = { - added: { hint: 'Remove', icon: 'clear', handler: rm }, - modified: { hint: 'Revert', icon: 'restore', handler: rm }, - deleted: { hint: 'Restore', icon: 'restore', handler: restore }, - unchanged: { hint: 'Delete', icon: 'clear', handler: del }, - }[type] - return ( - // TODO: dif display for dif types - // TODO: show delta for modified? -
- - {action.icon} - -
- {path} -
-
{readableBytes(size)}
-
- ) - })} +
+ {computedEntries.map((entry) => { + const [Component, props, key] = FilesEntry.case( + { + Dir: (ps) => [Dir, ps, `dir:${ps.name}`], + File: (ps) => [File, ps, `file:${ps.name}`], + }, + entry, + ) + return + })} +
)} @@ -485,6 +852,17 @@ function DialogForm({ const totalProgress = React.useMemo(() => getTotalProgress(uploads), [uploads]) + const onFilesAction = React.useMemo( + () => + FilesAction.case({ + _: () => {}, + Revert: (path) => setUploads(R.dissoc(path)), + RevertDir: (prefix) => setUploads(dissocBy(R.startsWith(prefix))), + Reset: () => setUploads({}), + }), + [setUploads], + ) + // eslint-disable-next-line consistent-return const onSubmit = async ({ name, msg, files, meta, workflow }) => { const toUpload = Object.entries(files.added).map(([path, file]) => ({ path, file })) @@ -662,7 +1040,7 @@ function DialogForm({ nonEmpty: 'Add files to create a package', }} uploads={uploads} - setUploads={setUploads} + onFilesAction={onFilesAction} isEqual={R.equals} initialValue={initialFiles} /> From 0315744edf8958e287a5492440ef98e7d2445c5b Mon Sep 17 00:00:00 2001 From: nl_0 Date: Thu, 17 Dec 2020 11:56:58 +0500 Subject: [PATCH 3/4] overlay icons, less saturation, revert icon -> undo --- .../containers/Bucket/PackageUpdateDialog.js | 156 ++++++++++-------- 1 file changed, 86 insertions(+), 70 deletions(-) diff --git a/catalog/app/containers/Bucket/PackageUpdateDialog.js b/catalog/app/containers/Bucket/PackageUpdateDialog.js index 7cd34e8eeed..7e0f88e0ac8 100644 --- a/catalog/app/containers/Bucket/PackageUpdateDialog.js +++ b/catalog/app/containers/Bucket/PackageUpdateDialog.js @@ -153,22 +153,62 @@ const computeEntries = ({ added, deleted, existing }) => { } const COLORS = { - default: { - main: M.colors.grey[600], - accent: M.colors.grey[900], + default: M.colors.grey[900], + added: M.colors.green[900], + modified: darken(M.colors.yellow[900], 0.2), + deleted: M.colors.red[900], +} + +const useEntryIconStyles = M.makeStyles((t) => ({ + root: { + position: 'relative', }, - added: { - main: M.colors.green[600], - accent: M.colors.green[900], + icon: { + boxSizing: 'content-box', + display: 'block', + fontSize: 18, + padding: 3, }, - modified: { - main: M.colors.yellow[800], - accent: darken(M.colors.yellow[800], 0.2), + stateContainer: { + alignItems: 'center', + background: 'currentColor', + border: `1px solid ${t.palette.background.paper}`, + borderRadius: '100%', + bottom: 0, + display: 'flex', + height: 12, + justifyContent: 'center', + left: 0, + position: 'absolute', + width: 12, }, - deleted: { - main: M.colors.red[600], - accent: M.colors.red[900], + state: { + fontFamily: t.typography.fontFamily, + fontWeight: t.typography.fontWeightBold, + fontSize: 9, + color: t.palette.background.paper, }, +})) + +function EntryIcon({ state, children }) { + const classes = useEntryIconStyles() + const stateContents = + state && + { + added: '+', + deleted: <>–, + modified: '~', + }[state] + return ( +
+ {children} + {!!stateContents && ( +
+
{stateContents}
+
+ )} +
+ ) } const useFileStyles = M.makeStyles((t) => ({ @@ -177,39 +217,27 @@ const useFileStyles = M.makeStyles((t) => ({ deleted: {}, unchanged: {}, root: { - alignItems: 'center', background: t.palette.background.paper, + color: COLORS.default, cursor: 'default', - display: 'flex', outline: 'none', - - color: COLORS.default.main, - '&:hover': { - color: COLORS.default.accent, - }, '&$added': { - color: COLORS.added.main, - '&:hover': { - color: COLORS.added.accent, - }, + color: COLORS.added, }, '&$modified': { - color: COLORS.modified.main, - '&:hover': { - color: COLORS.modified.accent, - }, + color: COLORS.modified, }, '&$deleted': { - color: COLORS.deleted.main, - '&:hover': { - color: COLORS.deleted.accent, - }, + color: COLORS.deleted, }, }, - icon: { - boxSizing: 'content-box', - fontSize: 18, - padding: 3, + contents: { + alignItems: 'center', + display: 'flex', + opacity: 0.7, + '$root:hover > &': { + opacity: 1, + }, }, name: { ...t.typography.body2, @@ -222,7 +250,7 @@ const useFileStyles = M.makeStyles((t) => ({ size: { ...t.typography.body2, marginRight: t.spacing(0.5), - opacity: 0.62, + opacity: 0.6, }, })) @@ -245,12 +273,12 @@ function File({ name, type, size, prefix, dispatch }) { added: { hint: 'Remove', icon: 'clear', handler: handle(FilesAction.Revert) }, modified: { hint: 'Revert', - icon: 'restore', + icon: 'undo', handler: handle(FilesAction.Revert), }, deleted: { hint: 'Restore', - icon: 'restore', + icon: 'undo', handler: handle(FilesAction.Revert), }, unchanged: { hint: 'Delete', icon: 'clear', handler: handle(FilesAction.Delete) }, @@ -270,14 +298,16 @@ function File({ name, type, size, prefix, dispatch }) { role="button" tabIndex={0} > - insert_drive_file -
- {name} +
+ insert_drive_file +
+ {name} +
+
{readableBytes(size)}
+ + {action.icon} +
-
{readableBytes(size)}
- - {action.icon} -
) } @@ -298,37 +328,23 @@ const useDirStyles = M.makeStyles((t) => ({ }, head: { alignItems: 'center', + color: COLORS.default, display: 'flex', + opacity: 0.7, outline: 'none', - - color: COLORS.default.main, '$active > &, &:hover': { - color: COLORS.default.accent, + opacity: 1, }, '$added > &': { - color: COLORS.added.main, - }, - '$added$active > &, $added > &:hover': { - color: COLORS.added.accent, + color: COLORS.added, }, '$modified > &': { - color: COLORS.modified.main, - }, - '$modified$active > &, $modified > &:hover': { - color: COLORS.modified.accent, + color: COLORS.modified, }, '$deleted > &': { - color: COLORS.deleted.main, - }, - '$deleted$active > &, $deleted > &:hover': { - color: COLORS.deleted.accent, + color: COLORS.deleted, }, }, - icon: { - boxSizing: 'content-box', - fontSize: 18, - padding: 3, - }, name: { ...t.typography.body2, flexGrow: 1, @@ -343,7 +359,7 @@ const useDirStyles = M.makeStyles((t) => ({ bar: { bottom: 0, left: 0, - opacity: 0.2, + opacity: 0.3, position: 'absolute', top: 24, width: 24, @@ -423,12 +439,12 @@ function Dir({ name, type, children, prefix, dispatch }) { added: { hint: 'Remove', icon: 'clear', handler: handle(FilesAction.RevertDir) }, modified: { hint: 'Revert', - icon: 'restore', + icon: 'undo', handler: handle(FilesAction.RevertDir), }, deleted: { hint: 'Restore', - icon: 'restore', + icon: 'undo', handler: handle(FilesAction.RevertDir), }, unchanged: { @@ -449,7 +465,7 @@ function Dir({ name, type, children, prefix, dispatch }) { > {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
- {expanded ? 'folder_open' : 'folder'} + {expanded ? 'folder_open' : 'folder'}
{name}
{action.icon} @@ -719,7 +735,7 @@ function FilesInput({ onClick={resetFiles} disabled={disabled} size="small" - endIcon={restore} + endIcon={undo} > Undo changes From 23aa20738fda5113e1a8f580d623e03c9f5d9e51 Mon Sep 17 00:00:00 2001 From: nl_0 Date: Thu, 17 Dec 2020 12:00:23 +0500 Subject: [PATCH 4/4] changelog entry --- docs/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0387d5117ac..12694382dc0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -14,6 +14,7 @@ ## Catalog, Lambdas * [Added] Generate and resolve Quilt package URIs ([#1935](https://github.com/quiltdata/quilt/pull/1935)) +* [Changed] Tree view for files in package update dialog ([#1989](https://github.com/quiltdata/quilt/pull/1989)) # 3.3.0 - 2020-12-08 ## Python API