Skip to content

feat: improve customization of filedrop components #993

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
62 changes: 59 additions & 3 deletions solara/components/file_drop.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@

import solara
import solara.hooks as hooks
from solara.lab import theme, use_dark_effective


@solara.component
def FileHoverIndicator():
dark_theme = use_dark_effective()
border_color = theme.themes.dark.success if dark_theme else theme.themes.light.success
with solara.Row(
justify="center",
style={
"height": "100%",
"width": "100%",
"align-items": "center",
"border-radius": "8px",
"border": f"2px dashed {border_color}",
},
):
solara.v.Icon(children=["mdi-upload"], size="48px")
solara.HTML(tag="h3", unsafe_innerHTML="Drop file here")


class FileInfo(TypedDict):
Expand All @@ -23,6 +42,8 @@ class FileDropZone(FileInput):
# override to narrow traitlet of FileInput
template = traitlets.Instance(Template).tag(sync=True, **widget_serialization)
template_file = (__file__, "file_drop.vue")
children = traitlets.List(cast(List[solara.Element], [])).tag(sync=True, **widget_serialization)
file_hover_indicator = traitlets.Any(allow_none=True).tag(sync=True, **widget_serialization)
items = traitlets.List(default_value=cast(List[Any], [])).tag(sync=True)
label = traitlets.Unicode().tag(sync=True)
multiple = traitlets.Bool(True).tag(sync=True)
Expand All @@ -35,6 +56,8 @@ def _FileDrop(
on_file: Optional[Callable[[Union[FileInfo, List[FileInfo]]], None]] = None,
lazy: bool = True,
multiple: bool = False,
children: List[solara.Element] = [],
file_hover_indicator: Optional[solara.Element] = None,
):
"""Generic implementation used by FileDrop and FileDropMultiple.

Expand All @@ -44,7 +67,14 @@ def _FileDrop(
file_info, set_file_info = solara.use_state(None)
wired_files, set_wired_files = solara.use_state(cast(Optional[typing.List[FileInfo]], None))

file_drop = FileDropZone.element(label=label, on_total_progress=on_total_progress, on_file_info=set_file_info, multiple=multiple) # type: ignore
file_drop = FileDropZone.element(
label=label,
on_total_progress=on_total_progress,
on_file_info=set_file_info,
multiple=multiple,
children=children,
file_hover_indicator=file_hover_indicator,
)

def wire_files():
if not file_info:
Expand Down Expand Up @@ -87,6 +117,8 @@ def FileDrop(
on_total_progress: Optional[Callable[[float], None]] = None,
on_file: Optional[Callable[[FileInfo], None]] = None,
lazy: bool = True,
children: List[solara.Element] = [],
file_hover_indicator: Optional[solara.Element] = FileHoverIndicator(),
):
"""Region a user can drop a file into for file uploading.

Expand All @@ -110,6 +142,9 @@ class FileInfo(typing.TypedDict):
* `on_file`: Will be called with a `FileInfo` object, which contains the file `.name`, `.length` and a `.file_obj` object.
* `lazy`: Whether to load the file contents into memory or not. If `False`,
the file contents will be loaded into memory via the `.data` attribute of file object(s).
* `children`: Elements to display inside the file drop zone. These will override the default drop zone.
* `file_hover_indicator`: Element to display when a file is hovered over the drop zone. The container of
this element is absolutely positioned over the whole area of the drop zone.

## Load into Pandas
To load the data into a Pandas DF, set `lazy=False` and use `file['file_obj']` (be careful of memory)<br>
Expand All @@ -133,7 +168,15 @@ def load_file_df(file):

"""

return _FileDrop(label=label, on_total_progress=on_total_progress, on_file=on_file, lazy=lazy, multiple=False)
return _FileDrop(
label=label,
on_total_progress=on_total_progress,
on_file=on_file,
lazy=lazy,
multiple=False,
children=children,
file_hover_indicator=file_hover_indicator,
)


@solara.component
Expand All @@ -142,6 +185,8 @@ def FileDropMultiple(
on_total_progress: Optional[Callable[[float], None]] = None,
on_file: Optional[Callable[[List[FileInfo]], None]] = None,
lazy: bool = True,
children: List[solara.Element] = [],
file_hover_indicator: Optional[solara.Element] = FileHoverIndicator(),
):
"""Region a user can drop multiple files into for file uploading.

Expand All @@ -153,7 +198,18 @@ def FileDropMultiple(
* `on_file`: Will be called with a `List[FileInfo]`.
Each `FileInfo` contains the file `.name`, `.length`, `.file_obj` object, and `.data` attributes.
* `lazy`: Whether to load the file contents into memory or not.
* `children`: Elements to display inside the file drop zone. These will override the default drop zone.
* `file_hover_indicator`: Element to display when a file is hovered over the drop zone. The container of
this element is absolutely positioned over the whole area of the drop zone.

"""

return _FileDrop(label=label, on_total_progress=on_total_progress, on_file=on_file, lazy=lazy, multiple=True)
return _FileDrop(
label=label,
on_total_progress=on_total_progress,
on_file=on_file,
lazy=lazy,
multiple=True,
children=children,
file_hover_indicator=file_hover_indicator,
)
40 changes: 28 additions & 12 deletions solara/components/file_drop.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
<template>
<div ref="dropzone" class="solara-file-drop" effectAllowed="move">
<template v-if="file_info && file_info.length > 0">
<template v-if="multiple">
<div v-for="file in file_info">
{{ file.name }}
</div>
</template>
<template v-else>
{{ file_info[0].name }}
</template>
<div ref="dropzone" effectAllowed="move" style="position: relative;">
<div ref="dropIndicator" style="display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 31;">
<jupyter-widget v-if="file_hover_indicator" :widget="file_hover_indicator"></jupyter-widget>
</div>
<template v-if="children && children.length > 0">
<jupyter-widget v-for="child in children" :widget="child" :key="child.model_id"></jupyter-widget>
</template>
<template v-else>
{{ label }}
<!-- Default content -->
<div class="solara-file-drop">
<template v-if="file_info && file_info.length > 0">
<template v-if="multiple">
<div v-for="file in file_info">
{{ file.name }}
</div>
</template>
<template v-else>
{{ file_info[0].name }}
</template>
</template>
<template v-else>
{{ label }}
</template>
</div>
</template>
</div>
</template>
Expand All @@ -21,7 +32,12 @@ module.exports = {
mounted() {
this.chunk_size = 2 * 1024 * 1024;
this.$refs.dropzone.addEventListener('dragover', event => {
event.preventDefault();
this.$refs.dropIndicator.style.display = 'unset';
});
this.$refs.dropzone.addEventListener('dragleave', event => {
if (!event.relatedTarget || !this.$refs.dropzone.contains(event.relatedTarget)) {
this.$refs.dropIndicator.style.display = 'none';
}
});

this.$refs.dropzone.addEventListener('drop', async event => {
Expand Down
24 changes: 24 additions & 0 deletions solara/website/pages/documentation/components/input/file_drop.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,27 @@ def Page():
__doc__ += apidoc(solara.FileDrop.f) # type: ignore
__doc__ += "# FileDropMultiple"
__doc__ += apidoc(solara.FileDropMultiple.f) # type: ignore
__doc__ += """# Customization Example

```solara
import solara

@solara.component
def CustomHoverIndicator():
style = {"height": "100%", "width": "100%", "align-items": "center", "border": "2px dashed limegreen", "opacity": "0.85"}
with solara.Row(justify="center", style=style):
solara.HTML(tag="h3", unsafe_innerHTML="Drop file here")
solara.SpinnerSolara()

@solara.component
def Page():
number_of_files = solara.use_reactive(1)

with solara.FileDropMultiple(file_hover_indicator=CustomHoverIndicator()):
with solara.Card("Upload your file(s) here"):
solara.InputInt("Number of files", value=number_of_files)
for i in range(number_of_files.value):
solara.InputText("File name")

```
"""
Loading