Spaces:
Runtime error
Runtime error
Commit
·
8f70819
1
Parent(s):
40d9e16
feature: added multiple file drop support
Browse files
tomorrowcities/components/file_drop.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import threading
|
| 2 |
+
import typing
|
| 3 |
+
from typing import Callable, List, Optional, Union, cast
|
| 4 |
+
|
| 5 |
+
import traitlets
|
| 6 |
+
from ipyvue import Template
|
| 7 |
+
from ipyvuetify.extra import FileInput
|
| 8 |
+
from ipywidgets import widget_serialization
|
| 9 |
+
from typing_extensions import TypedDict
|
| 10 |
+
|
| 11 |
+
import solara
|
| 12 |
+
import solara.hooks as hooks
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class FileInfo(TypedDict):
|
| 16 |
+
name: str
|
| 17 |
+
size: int
|
| 18 |
+
file_obj: typing.BinaryIO
|
| 19 |
+
data: Optional[bytes]
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class FileDropZone(FileInput):
|
| 23 |
+
# override to narrow traitlet of FileInput
|
| 24 |
+
template = traitlets.Instance(Template).tag(sync=True, **widget_serialization)
|
| 25 |
+
template_file = (__file__, "file_drop.vue")
|
| 26 |
+
items = traitlets.List(default_value=[]).tag(sync=True)
|
| 27 |
+
label = traitlets.Unicode().tag(sync=True)
|
| 28 |
+
multiple = traitlets.Bool(True).tag(sync=True)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@solara.component
|
| 32 |
+
def _FileDrop(
|
| 33 |
+
label="Drop file(s) here",
|
| 34 |
+
on_total_progress: Optional[Callable[[float], None]] = None,
|
| 35 |
+
on_file: Optional[Callable[[Union[FileInfo, List[FileInfo]]], None]] = None,
|
| 36 |
+
lazy: bool = True,
|
| 37 |
+
multiple: bool = False,
|
| 38 |
+
):
|
| 39 |
+
"""Generic implementation used by FileDrop and FileDropMultiple.
|
| 40 |
+
|
| 41 |
+
If multiple=True, multiple files can be uploaded.
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
file_info, set_file_info = solara.use_state(None)
|
| 45 |
+
wired_files, set_wired_files = solara.use_state(cast(Optional[typing.List[FileInfo]], None))
|
| 46 |
+
|
| 47 |
+
file_drop = FileDropZone.element(label=label, on_total_progress=on_total_progress, on_file_info=set_file_info, multiple=multiple) # type: ignore
|
| 48 |
+
|
| 49 |
+
def wire_files():
|
| 50 |
+
if not file_info:
|
| 51 |
+
return
|
| 52 |
+
|
| 53 |
+
real = cast(FileDropZone, solara.get_widget(file_drop))
|
| 54 |
+
|
| 55 |
+
# workaround for @observe being cleared
|
| 56 |
+
real.version += 1
|
| 57 |
+
real.reset_stats()
|
| 58 |
+
|
| 59 |
+
set_wired_files(cast(typing.List[FileInfo], real.get_files()))
|
| 60 |
+
|
| 61 |
+
solara.use_side_effect(wire_files, [file_info])
|
| 62 |
+
|
| 63 |
+
def handle_file(cancel: threading.Event):
|
| 64 |
+
if not wired_files:
|
| 65 |
+
return
|
| 66 |
+
if on_file:
|
| 67 |
+
for i in range(len(wired_files)):
|
| 68 |
+
if not lazy:
|
| 69 |
+
wired_files[i]["data"] = wired_files[i]["file_obj"].read()
|
| 70 |
+
else:
|
| 71 |
+
wired_files[i]["data"] = None
|
| 72 |
+
if multiple:
|
| 73 |
+
on_file(wired_files)
|
| 74 |
+
else:
|
| 75 |
+
on_file(wired_files[0])
|
| 76 |
+
|
| 77 |
+
result: solara.Result = hooks.use_thread(handle_file, [wired_files])
|
| 78 |
+
if result.error:
|
| 79 |
+
raise result.error
|
| 80 |
+
|
| 81 |
+
return file_drop
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
@solara.component
|
| 85 |
+
def FileDrop(
|
| 86 |
+
label="Drop file here",
|
| 87 |
+
on_total_progress: Optional[Callable[[float], None]] = None,
|
| 88 |
+
on_file: Optional[Callable[[FileInfo], None]] = None,
|
| 89 |
+
lazy: bool = True,
|
| 90 |
+
):
|
| 91 |
+
"""Region a user can drop a file into for file uploading.
|
| 92 |
+
|
| 93 |
+
If lazy=True, no file content will be loaded into memory,
|
| 94 |
+
nor will any data be transferred by default.
|
| 95 |
+
If lazy=False, file content will be loaded into memory and passed to the `on_file` callback via the `FileInfo.data` attribute.
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
A file object is of the following argument type:
|
| 99 |
+
```python
|
| 100 |
+
class FileInfo(typing.TypedDict):
|
| 101 |
+
name: str # file name
|
| 102 |
+
size: int # file size in bytes
|
| 103 |
+
file_obj: typing.BinaryIO
|
| 104 |
+
data: Optional[bytes]: bytes # only present if lazy=False
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
## Arguments
|
| 109 |
+
* `on_total_progress`: Will be called with the progress in % of the file upload.
|
| 110 |
+
* `on_file`: Will be called with a `FileInfo` object, which contains the file `.name`, `.length` and a `.file_obj` object.
|
| 111 |
+
* `lazy`: Whether to load the file contents into memory or not. If `False`,
|
| 112 |
+
the file contents will be loaded into memory via the `.data` attribute of file object(s).
|
| 113 |
+
|
| 114 |
+
"""
|
| 115 |
+
|
| 116 |
+
return _FileDrop(label=label, on_total_progress=on_total_progress, on_file=on_file, lazy=lazy, multiple=False)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
@solara.component
|
| 120 |
+
def FileDropMultiple(
|
| 121 |
+
label="Drop files here",
|
| 122 |
+
on_total_progress: Optional[Callable[[float], None]] = None,
|
| 123 |
+
on_file: Optional[Callable[[List[FileInfo]], None]] = None,
|
| 124 |
+
lazy: bool = True,
|
| 125 |
+
):
|
| 126 |
+
"""Region a user can drop multiple files into for file uploading.
|
| 127 |
+
|
| 128 |
+
Almost identical to `FileDrop` except that multiple files can be dropped and `on_file` is called
|
| 129 |
+
with a list of `FileInfo` objects.
|
| 130 |
+
|
| 131 |
+
## Arguments
|
| 132 |
+
* `on_total_progress`: Will be called with the progress in % of the file(s) upload.
|
| 133 |
+
* `on_file`: Will be called with a `List[FileInfo]`.
|
| 134 |
+
Each `FileInfo` contains the file `.name`, `.length`, `.file_obj` object, and `.data` attributes.
|
| 135 |
+
* `lazy`: Whether to load the file contents into memory or not.
|
| 136 |
+
|
| 137 |
+
"""
|
| 138 |
+
|
| 139 |
+
return _FileDrop(label=label, on_total_progress=on_total_progress, on_file=on_file, lazy=lazy, multiple=True)
|
tomorrowcities/components/file_drop.vue
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<div ref="dropzone" class="solara-file-drop" effectAllowed="move">
|
| 3 |
+
<template v-if="file_info && file_info.length > 0">
|
| 4 |
+
<template v-if="multiple">
|
| 5 |
+
<div v-for="file in file_info">
|
| 6 |
+
{{ file.name }}
|
| 7 |
+
</div>
|
| 8 |
+
</template>
|
| 9 |
+
<template v-else>
|
| 10 |
+
{{ file_info[0].name }}
|
| 11 |
+
</template>
|
| 12 |
+
</template>
|
| 13 |
+
<template v-else>
|
| 14 |
+
{{ label }}
|
| 15 |
+
</template>
|
| 16 |
+
</div>
|
| 17 |
+
</template>
|
| 18 |
+
|
| 19 |
+
<script>
|
| 20 |
+
module.exports = {
|
| 21 |
+
mounted() {
|
| 22 |
+
this.chunk_size = 2 * 1024 * 1024;
|
| 23 |
+
this.$refs.dropzone.addEventListener('dragover', event => {
|
| 24 |
+
event.preventDefault();
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
this.$refs.dropzone.addEventListener('drop', async event => {
|
| 28 |
+
event.preventDefault();
|
| 29 |
+
const items = await Promise.all([...event.dataTransfer.items]);
|
| 30 |
+
const files = items.map(i => i.webkitGetAsEntry())
|
| 31 |
+
const fileHolders = files.filter(f => f.isFile)
|
| 32 |
+
const nativeFilesPromises = fileHolders.map(fileHolder => new Promise((rs, rj) => fileHolder.file(rs, rj)))
|
| 33 |
+
const nativeFiles = await Promise.all(nativeFilesPromises)
|
| 34 |
+
|
| 35 |
+
this.native_file_info = nativeFiles
|
| 36 |
+
this.file_info = this.native_file_info.map(
|
| 37 |
+
({name, size}) => ({
|
| 38 |
+
name,
|
| 39 |
+
isFile: true,
|
| 40 |
+
size,
|
| 41 |
+
}));
|
| 42 |
+
});
|
| 43 |
+
},
|
| 44 |
+
methods: {
|
| 45 |
+
jupyter_clear() {
|
| 46 |
+
this.native_file_info = [];
|
| 47 |
+
this.file_info = [];
|
| 48 |
+
},
|
| 49 |
+
jupyter_read(chunk) {
|
| 50 |
+
const {id, file_index, offset, length} = chunk;
|
| 51 |
+
let to_do = length;
|
| 52 |
+
let sub_offset = offset;
|
| 53 |
+
|
| 54 |
+
(async () => {
|
| 55 |
+
while (to_do > 0) {
|
| 56 |
+
console.log(this.chunk_size, to_do);
|
| 57 |
+
const sub_length = Math.min(to_do, this.chunk_size);
|
| 58 |
+
|
| 59 |
+
const file = this.native_file_info[file_index];
|
| 60 |
+
const blob = file.slice(sub_offset, sub_offset + sub_length);
|
| 61 |
+
const buff = await blob.arrayBuffer();
|
| 62 |
+
|
| 63 |
+
const msg = {id, file_index, offset: sub_offset, length: sub_length}
|
| 64 |
+
this.upload(msg, [buff]);
|
| 65 |
+
|
| 66 |
+
to_do -= sub_length
|
| 67 |
+
sub_offset += sub_length
|
| 68 |
+
}
|
| 69 |
+
})();
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
</script>
|
| 74 |
+
|
| 75 |
+
<style id="solara-file-drop">
|
| 76 |
+
.solara-file-drop {
|
| 77 |
+
height: 100px;
|
| 78 |
+
border: 1px dashed gray;
|
| 79 |
+
margin: 8px 0;
|
| 80 |
+
padding: 8px;
|
| 81 |
+
overflow: auto;
|
| 82 |
+
}
|
| 83 |
+
</style>
|
tomorrowcities/pages/engine.py
CHANGED
|
@@ -26,6 +26,7 @@ from .settings import threshold_flood, threshold_flood_distance, threshold_road_
|
|
| 26 |
from ..backend.engine import compute, compute_power_infra, compute_road_infra, calculate_metrics, generate_exposure
|
| 27 |
from ..backend.utils import building_preprocess, identity_preprocess, ParameterFile
|
| 28 |
from .utilities import S3FileBrowser, extension_list, extension_list_w_dots
|
|
|
|
| 29 |
from .docs import data_import_help
|
| 30 |
import ipywidgets
|
| 31 |
import ipydatagrid
|
|
@@ -1374,7 +1375,7 @@ def ImportDataZone():
|
|
| 1374 |
the below area. Supported formats are Excel, GeoTIFF, JSON, GeoJSON, and GEM XML.
|
| 1375 |
For more information, please refer to [Data Formats](/docs/data).
|
| 1376 |
''')
|
| 1377 |
-
|
| 1378 |
on_file=on_file_deneme,
|
| 1379 |
lazy=False)
|
| 1380 |
|
|
|
|
| 26 |
from ..backend.engine import compute, compute_power_infra, compute_road_infra, calculate_metrics, generate_exposure
|
| 27 |
from ..backend.utils import building_preprocess, identity_preprocess, ParameterFile
|
| 28 |
from .utilities import S3FileBrowser, extension_list, extension_list_w_dots
|
| 29 |
+
from ..components.file_drop import FileDropMultiple
|
| 30 |
from .docs import data_import_help
|
| 31 |
import ipywidgets
|
| 32 |
import ipydatagrid
|
|
|
|
| 1375 |
the below area. Supported formats are Excel, GeoTIFF, JSON, GeoJSON, and GEM XML.
|
| 1376 |
For more information, please refer to [Data Formats](/docs/data).
|
| 1377 |
''')
|
| 1378 |
+
FileDropMultiple(on_total_progress=progress,
|
| 1379 |
on_file=on_file_deneme,
|
| 1380 |
lazy=False)
|
| 1381 |
|