Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Files] Filepicker #143111

Merged
merged 85 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from 70 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
f184096
added unscoped file client to the files components context
jloleysens Oct 10, 2022
ddf2173
wip: created some basic files and stories for new filepicker component
jloleysens Oct 10, 2022
41ed423
fix some types after require filesclient to be passed in
jloleysens Oct 10, 2022
a85a1dc
added file picker state and some basic tests
jloleysens Oct 10, 2022
2661683
added file picker context
jloleysens Oct 10, 2022
a2221d9
added missing file client value
jloleysens Oct 10, 2022
a129c4b
updated file picker stories
jloleysens Oct 10, 2022
42459a2
expanded files context;
jloleysens Oct 10, 2022
dfbaae5
remove the size observable and also added a test for file removal and…
jloleysens Oct 10, 2022
1538c92
added error content component
jloleysens Oct 10, 2022
70d7495
refactor upload component to not take files client as prop
jloleysens Oct 10, 2022
5f61346
updated shared file compoennts context value
jloleysens Oct 10, 2022
0dd866d
added test for adding multiple files at once
jloleysens Oct 10, 2022
1a3b5f7
allow passing in string or array of strings
jloleysens Oct 10, 2022
c87a239
added some i18n texts
jloleysens Oct 10, 2022
4c9f438
move the creation of a file kinds registry to a common location
jloleysens Oct 10, 2022
f080aec
set file kinds only once
jloleysens Oct 10, 2022
98fa226
a bunch of stuff: added title component, using grid, removed responsi…
jloleysens Oct 11, 2022
8391853
refactor layour components to own component using grid, added new i18…
jloleysens Oct 11, 2022
569de31
minor copy tweak
jloleysens Oct 11, 2022
3a49db2
added basic story with some files
jloleysens Oct 11, 2022
6844647
added file grid and refactored picker to only exist in modal for now
jloleysens Oct 11, 2022
f2e71a5
get the css grid algo that we want: auto-fill not auto-fit!
jloleysens Oct 11, 2022
69ba34b
override styling for content area of file
jloleysens Oct 11, 2022
abaf1f2
split stories of files
jloleysens Oct 11, 2022
51ba099
delete commented out code
jloleysens Oct 11, 2022
9bd9f75
give the modal a fixed width
jloleysens Oct 11, 2022
e56b10a
fix upload file state, where we do not want a fixed width modal
jloleysens Oct 11, 2022
92ca412
moved styles down to card, and combined margin removal rules
jloleysens Oct 12, 2022
1b8c4d6
optimize for filtering files, first pass just filter on names
jloleysens Oct 12, 2022
ed2c6da
include xxl
jloleysens Oct 12, 2022
a43cd1b
moved debounceTime to rxjs land, added test for filtering behaviour
jloleysens Oct 12, 2022
19b9c58
added story with more images
jloleysens Oct 12, 2022
c0cb93f
big ol wip
jloleysens Oct 12, 2022
1f551df
empty prompt when uploading a file
jloleysens Oct 13, 2022
2b93c44
added pagination
jloleysens Oct 13, 2022
24994ae
fixed tests and added some comments
jloleysens Oct 13, 2022
690a387
address lint
jloleysens Oct 13, 2022
70efb7e
moved copy to i18n and updated size and color of empty error prompt
jloleysens Oct 13, 2022
9469648
remove use of css`
jloleysens Oct 13, 2022
8b33acc
remove non existant prop
jloleysens Oct 13, 2022
ecf6c90
Merge branch 'main' into files-filepicker-KBNA2079
jloleysens Oct 13, 2022
2bb31ea
also reload files
jloleysens Oct 13, 2022
ec346b1
fileUpload -> files
jloleysens Oct 13, 2022
424879c
update logic for watching if selected
jloleysens Oct 13, 2022
126bfb6
disambiguate i18n ids
jloleysens Oct 13, 2022
51f1a28
Merge branch 'main' into files-filepicker-KBNA2079
jloleysens Oct 14, 2022
b576308
use abort signal and call sendRequest from file$, filtering done serv…
jloleysens Oct 14, 2022
954a5a4
fix a few off by one errors and hook up the new system to the ui
jloleysens Oct 14, 2022
6aac935
added test for in flight requests behaviour
jloleysens Oct 14, 2022
65c5a75
update the files example app
jloleysens Oct 14, 2022
2689c9b
fix minor card layout styling to make all cards the same size regardl…
jloleysens Oct 14, 2022
beb0360
added new file picker component
jloleysens Oct 14, 2022
57a0b65
make file cards a bit wider and text a bit smaller so that it wraps...
jloleysens Oct 14, 2022
2233c93
fix issue of throwing abort error and prematurely setting request to …
jloleysens Oct 14, 2022
e07decc
remove unused import
jloleysens Oct 14, 2022
3b4b4e2
replace filter i18n
jloleysens Oct 14, 2022
1373c52
a bunch of cool changes
jloleysens Oct 14, 2022
54eecae
added export for the file picker component
jloleysens Oct 14, 2022
9eef858
updated example app to use multiple file upload
jloleysens Oct 14, 2022
bb02c64
added some comments and made images load eagerly in file picker for n…
jloleysens Oct 14, 2022
217a4f5
complete ux for examples
jloleysens Oct 14, 2022
0a7d8a3
only files that are "READY" should be in the file picker
jloleysens Oct 14, 2022
e3eddcb
Merge branch 'main' into files-filepicker-KBNA2079
jloleysens Oct 17, 2022
60b5fbb
Merge branch 'main' into files-filepicker-KBNA2079
jloleysens Oct 18, 2022
0d1d530
set loading to false if error
jloleysens Oct 18, 2022
e742e2b
install data-test-subj everywhere!
jloleysens Oct 18, 2022
25b04ac
added some react component tests
jloleysens Oct 18, 2022
3276146
remove unused import
jloleysens Oct 18, 2022
d7b86c1
fix storybook case
jloleysens Oct 18, 2022
52db453
Merge branch 'main' into files-filepicker-KBNA2079
jloleysens Oct 19, 2022
eca2bd3
fix up where the files example plugin is listed, moved it to the deve…
jloleysens Oct 19, 2022
b3b52d5
fix potential flashing of loader by debouncing
jloleysens Oct 19, 2022
28dbef0
do not create new observable on every render
jloleysens Oct 19, 2022
786485e
i18n
jloleysens Oct 19, 2022
fe23fde
have only filepicker ctx used in filepicker components
jloleysens Oct 19, 2022
94e8caa
refactor loadImageEagerly -> lazy
jloleysens Oct 19, 2022
b05f2d9
useObservable instead of useEffect
jloleysens Oct 19, 2022
ba7f6bd
factor modal footer to own component and remove css util
jloleysens Oct 19, 2022
7b83fe1
use the middle dot luke
jloleysens Oct 19, 2022
8946c03
Merge branch 'main' into files-filepicker-KBNA2079
kibanamachine Oct 19, 2022
784cf1c
copy update in files example app
jloleysens Oct 20, 2022
151ead5
added filter story
jloleysens Oct 20, 2022
75adddf
Merge branch 'main' into files-filepicker-KBNA2079
kibanamachine Oct 22, 2022
c560b17
Merge branch 'main' into files-filepicker-KBNA2079
kibanamachine Oct 24, 2022
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
2 changes: 1 addition & 1 deletion x-pack/examples/files_example/public/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const renderApp = (
) => {
ReactDOM.render(
<QueryClientProvider client={queryClient}>
<FilesContext>
<FilesContext client={files.unscoped}>
<FilesExampleApp files={files} notifications={notifications} />
</FilesContext>
</QueryClientProvider>,
Expand Down
21 changes: 21 additions & 0 deletions x-pack/examples/files_example/public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '@elastic/eui';

import { CoreStart } from '@kbn/core/public';
import { MyFilePicker } from './file_picker';
import type { MyImageMetadata } from '../../common';
import type { FileClients } from '../types';
import { DetailsFlyout } from './details_flyout';
Expand All @@ -39,11 +40,19 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) =
files.example.list()
);
const [showUploadModal, setShowUploadModal] = useState(false);
const [showFilePickerModal, setShowFilePickerModal] = useState(false);
const [isDeletingFile, setIsDeletingFile] = useState(false);
const [selectedItem, setSelectedItem] = useState<undefined | FileJSON<MyImageMetadata>>();

const renderToolsRight = () => {
return [
<EuiButton
onClick={() => setShowFilePickerModal(true)}
isDisabled={isLoading || isDeletingFile}
iconType="eye"
>
Pick a file
</EuiButton>,
<EuiButton
onClick={() => setShowUploadModal(true)}
isDisabled={isLoading || isDeletingFile}
Expand Down Expand Up @@ -155,6 +164,18 @@ export const FilesExampleApp = ({ files, notifications }: FilesExampleAppDeps) =
}}
/>
)}
{showFilePickerModal && (
<MyFilePicker
onClose={() => setShowFilePickerModal(false)}
onDone={(ids) => {
notifications.toasts.addSuccess({
title: 'Selected files!',
text: 'IDS:' + JSON.stringify(ids, null, 2),
});
setShowFilePickerModal(false);
}}
/>
)}
</>
);
};
22 changes: 22 additions & 0 deletions x-pack/examples/files_example/public/components/file_picker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import type { FunctionComponent } from 'react';

import { exampleFileKind } from '../../common';

import { FilePicker } from '../imports';

interface Props {
onClose: () => void;
onDone: (ids: string[]) => void;
}

export const MyFilePicker: FunctionComponent<Props> = ({ onClose, onDone }) => {
return <FilePicker kind={exampleFileKind.id} onClose={onClose} onDone={onDone} />;
};
2 changes: 1 addition & 1 deletion x-pack/examples/files_example/public/components/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export const Modal: FunctionComponent<Props> = ({ onDismiss, onUploaded, client
</EuiModalHeader>
<EuiModalBody>
<UploadFile
multiple
kind={exampleFileKind.id}
client={client}
onDone={onUploaded}
meta={{ custom: 'meta' }}
/>
Expand Down
1 change: 1 addition & 0 deletions x-pack/examples/files_example/public/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export {
UploadFile,
FilesContext,
ScopedFilesClient,
FilePicker,
Image,
} from '@kbn/files-plugin/public';
15 changes: 14 additions & 1 deletion x-pack/plugins/files/public/components/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@

import React, { createContext, useContext, type FunctionComponent } from 'react';
import { FileKindsRegistry, getFileKindsRegistry } from '../../common/file_kinds_registry';
import type { FilesClient } from '../types';

export interface FilesContextValue {
registry: FileKindsRegistry;
/**
* A files client that will be used process uploads.
*/
client: FilesClient<any>;
}

const FilesContextObject = createContext<FilesContextValue>(null as unknown as FilesContextValue);
Expand All @@ -21,10 +26,18 @@ export const useFilesContext = () => {
}
return ctx;
};
export const FilesContext: FunctionComponent = ({ children }) => {

interface ContextProps {
/**
* A files client that will be used process uploads.
*/
client: FilesClient<any>;
}
export const FilesContext: FunctionComponent<ContextProps> = ({ client, children }) => {
return (
<FilesContextObject.Provider
value={{
client,
registry: getFileKindsRegistry(),
}}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import type { FunctionComponent } from 'react';
import { debounceTime } from 'rxjs';
import useObservable from 'react-use/lib/useObservable';
import { EuiLink } from '@elastic/eui';
import { css } from '@emotion/react';
import { useFilePickerContext } from '../context';

interface Props {
onClick: () => void;
}

export const ClearFilterButton: FunctionComponent<Props> = ({ onClick }) => {
const { state } = useFilePickerContext();
const query = useObservable(state.query$.pipe(debounceTime(100)));
jloleysens marked this conversation as resolved.
Show resolved Hide resolved
if (!query) {
return null;
}
return (
<div
css={css`
display: grid;
place-items: center;
`}
>
<EuiLink onClick={onClick}>Clear filter</EuiLink>
jloleysens marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import type { FunctionComponent } from 'react';
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { i18nTexts } from '../i18n_texts';
import { useFilePickerContext } from '../context';
import { useBehaviorSubject } from '../../use_behavior_subject';

interface Props {
error: Error;
}

export const ErrorContent: FunctionComponent<Props> = ({ error }) => {
const { state } = useFilePickerContext();
const isLoading = useBehaviorSubject(state.isLoading$);
return (
<EuiEmptyPrompt
data-test-subj="errorPrompt"
iconType="alert"
iconColor="danger"
titleSize="xs"
title={<h3>{i18nTexts.loadingFilesErrorTitle}</h3>}
body={error.message}
actions={
<EuiButton disabled={isLoading} onClick={state.retry}>
{i18nTexts.retryButtonLabel}
</EuiButton>
}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.filesFilePicker {
.euiCard__content, .euiCard__description {
margin :0; // make the cards a little bit more compact
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that I don't agree with you. But I'm not sure there's a significant reason to overwrite our EUI styles here. Ideally we wouldn't need to do this to save future maintenance (and work for you)

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import type { FunctionComponent } from 'react';
import numeral from '@elastic/numeral';
import useObservable from 'react-use/lib/useObservable';
import { EuiCard, EuiText, EuiIcon, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { FileImageMetadata, FileJSON } from '../../../../common';
import { Image } from '../../image';
import { isImage } from '../../util';
import { useFilesContext } from '../../context';
import { useFilePickerContext } from '../context';

import './file_card.scss';

interface Props {
file: FileJSON;
}

export const FileCard: FunctionComponent<Props> = ({ file }) => {
const { client } = useFilesContext();
const { kind, state } = useFilePickerContext();
jloleysens marked this conversation as resolved.
Show resolved Hide resolved
const { euiTheme } = useEuiTheme();
const displayImage = isImage({ type: file.mimeType });

const isSelected = useObservable(state.watchFileSelected$(file.id), false);

const imageHeight = `calc(${euiTheme.size.xxxl} * 2)`;
return (
<EuiCard
title=""
css={css`
place-self: stretch;
`}
paddingSize="s"
selectable={{
isSelected,
onClick: () => (isSelected ? state.unselectFile(file.id) : state.selectFile(file.id)),
}}
image={
<div
css={css`
display: grid;
place-items: center;
height: ${imageHeight};
margin: ${euiTheme.size.m};
`}
>
{displayImage ? (
<Image
alt={file.alt ?? ''}
css={css`
max-height: ${imageHeight};
`}
meta={file.meta as FileImageMetadata}
src={client.getDownloadHref({ id: file.id, fileKind: kind })}
// There is an issue where the intersection observer does not fire reliably.
// I'm not sure if this is becuause of the image being in a modal
// The result is that the image does not always get loaded.
// TODO: Investigate this behaviour further
jloleysens marked this conversation as resolved.
Show resolved Hide resolved
loadImageEagerly
/>
) : (
<div
css={css`
display: grid;
place-items: center;
height: ${imageHeight};
`}
>
<EuiIcon type="filebeatApp" size="xl" />
</div>
)}
</div>
}
description={
<>
<EuiText
size="s"
css={css`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`}
>
<strong>{file.name}</strong>
</EuiText>
<EuiText color="subdued" size="xs">
{numeral(file.size).format('0[.]0 b')}
</EuiText>
{file.extension ? (
<EuiText
css={css`
text-transform: uppercase;
`}
color="subdued"
size="xs"
>
{file.extension}
</EuiText>
) : null}
</>
}
hasBorder
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import type { FunctionComponent } from 'react';
import { useEuiTheme, EuiEmptyPrompt } from '@elastic/eui';
import { css } from '@emotion/react';
import useObservable from 'react-use/lib/useObservable';

import { i18nTexts } from '../i18n_texts';
import { useFilePickerContext } from '../context';
import { FileCard } from './file_card';

export const FileGrid: FunctionComponent = () => {
const { state } = useFilePickerContext();
const { euiTheme } = useEuiTheme();
const files = useObservable(state.files$, []);
if (!files.length) {
return <EuiEmptyPrompt title={<h3>{i18nTexts.emptyFileGridPrompt}</h3>} titleSize="s" />;
}
return (
<div
data-test-subj="fileGrid"
css={css`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(calc(${euiTheme.size.xxxxl} * 3), 1fr));
gap: ${euiTheme.size.m};
`}
>
{files.map((file, idx) => (
<FileCard key={idx} file={file} />
))}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import type { FunctionComponent } from 'react';
import { EuiPagination } from '@elastic/eui';
import { useFilePickerContext } from '../context';
import { useBehaviorSubject } from '../../use_behavior_subject';

export const Pagination: FunctionComponent = () => {
const { state } = useFilePickerContext();
const page = useBehaviorSubject(state.currentPage$);
const pageCount = useBehaviorSubject(state.totalPages$);
return <EuiPagination onPageClick={state.setPage} pageCount={pageCount} activePage={page} />;
};
Loading