Skip to content

Commit

Permalink
WiP on finder-browser component
Browse files Browse the repository at this point in the history
WiP on finder-browser component

WiP on finder-browser component

WiP on finder-browser component
  • Loading branch information
jrief committed Oct 16, 2024
1 parent ab175cf commit 1089315
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 5 deletions.
8 changes: 8 additions & 0 deletions client/browser/FinderFileSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, {
useState,
} from 'react';
import FigureLabels from '../finder/FigureLabels';
import FileUploader from '../finder/FileUploader';
import ArrowDownIcon from '../icons/arrow-down.svg';
import ArrowRightIcon from '../icons/arrow-right.svg';
import EmptyIcon from '../icons/empty.svg';
Expand Down Expand Up @@ -179,11 +180,18 @@ export default function FinderFileSelect(props) {
}
}

function handleUpload(folderId) {
folderListRef.current.fetchFiles
}

return structure.root_folder && (<>
<ul className="folder-structure">
<FolderStructure baseUrl={baseUrl} folder={structure.root_folder} folderListRef={folderListRef} refreshStructure={() => {
setStructure({...structure});
}}/>
</ul>
<FileUploader folderId={structure.root_folder} handleUpload={handleUpload}>
<FolderList baseUrl={baseUrl} files={structure.files} ref={folderListRef} />
</FileUploader>
</>);
}
21 changes: 21 additions & 0 deletions client/browserbuild.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {build} from 'esbuild';
import svgr from 'esbuild-plugin-svgr';
import parser from 'yargs-parser';
const buildOptions = parser(process.argv.slice(2), {
boolean: ['debug', 'minify'],
});

await build({
entryPoints: [
'client/finder-browser.ts',
],
bundle: true,
minify: buildOptions.minify,
sourcemap: buildOptions.debug,
outfile: 'finder/static/finder/js/browser.js',
format: 'esm',
jsx: 'automatic',
plugins: [svgr()],
loader: {'.svg': 'text', '.jsx': 'jsx' },
target: ['es2020', 'chrome84', 'firefox84', 'safari14', 'edge84']
}).catch(() => process.exit(1));
121 changes: 121 additions & 0 deletions client/finder-browser.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
finder-file-select {
display: flex;

.folder-structure {
flex-shrink: 0;
padding-right: 1rem;

ul ul {
padding-left: 1.5rem;
}

&, ul {
padding-left: 0;
list-style: none;
}

li {
i {
display: inline-block;
cursor: pointer;
width: 1.5rem;
color: #808080;
&:hover {
color: inherit;
}
}

svg {
height: 20px;
vertical-align: text-bottom;
margin-right: 0.5em;
}

span {
cursor: pointer;
&:hover {
text-decoration: underline;
text-decoration-style: dashed;
text-decoration-color: #808080;
}
}
}
}

.files-list {
&, ul {
list-style: none;
display: flex;
padding: 10px;
flex-wrap: wrap;
flex-direction: row;
align-content: flex-start;
justify-content: flex-start;
gap: 10px;
}

li {
min-height: 150px;
width: 125px;

&.status {
font-size: 18px;
font-weight: bold;
line-height: 50px;
color: #808080;
padding-left: 2em;
width: auto;
}

.figure {
width: 100%;
height: 100%;
margin: 0;
cursor: pointer;

div:has(> .figure-labels) {
position: relative;
}

.figure-labels {
position: absolute;
line-height: 0;
text-align: right;
overflow: hidden;
inset: 4px;

span {
width: 10px;
height: 10px;
display: inline-block;
border-radius: 50%;
margin-left: -4px;
}

}

img, video {
width: 100%;
border-radius: 0.25rem;
}

img:not([src$=".svg"]), video {
box-shadow: 0 0 0.5rem #808080;
}

figcaption {
text-align: center;
line-height: 1.5rem;
font-size: 0.8em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}

.file-uploader {
width: 100%;
}
}
2 changes: 1 addition & 1 deletion client/finder-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import FinderFileSelect from 'browser/FinderFileSelect';

window.addEventListener('DOMContentLoaded', (event) => {
window.customElements.define('finder-file-select', r2wc(FinderFileSelect, {
props: {'base-url': 'string', realm: 'string'}}
props: {baseurl: 'string', realm: 'string'}}
));
});
3 changes: 3 additions & 0 deletions client/icons/arrow-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions client/icons/arrow-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions client/icons/empty.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions client/icons/folder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added finder/api/__init__.py
Empty file.
39 changes: 39 additions & 0 deletions finder/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from django.urls import path
from django.views.i18n import JavaScriptCatalog

from finder.api.views import BrowserView


app_name = 'finder-api'
urlpatterns = [
path(
'structure/<slug:realm>',
BrowserView.as_view(action='structure'),
),
path(
'fetch/<uuid:folder_id>',
BrowserView.as_view(action='fetch'),
),
path(
'open/<uuid:folder_id>',
BrowserView.as_view(action='open'),
),
path(
'close/<uuid:folder_id>',
BrowserView.as_view(action='close'),
),
path(
'list/<uuid:folder_id>',
BrowserView.as_view(action='list'),
),
path(
'jsi18n/',
JavaScriptCatalog.as_view(packages=['finder']),
name="javascript-catalog",
),
path(
'',
BrowserView.as_view(),
name="base-url",
),
]
129 changes: 129 additions & 0 deletions finder/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
from django.contrib.sites.shortcuts import get_current_site
from django.core.exceptions import ObjectDoesNotExist
from django.http import JsonResponse, HttpResponseBadRequest, HttpResponseNotFound

Check failure on line 3 in finder/api/views.py

View workflow job for this annotation

GitHub Actions / flake8

'django.http.HttpResponseNotFound' imported but unused
from django.views import View

from finder.models.folder import FolderModel, RealmModel


class BrowserView(View):
"""
The view for web component <finder-browser>.
"""
action = None

def get(self, request, *args, **kwargs):
action = getattr(self, self.action, None)
if not callable(action):
return HttpResponseBadRequest(f"Action {self.action} not allowed.")
try:
return JsonResponse(action(request, *args, **kwargs))
except Exception as e:
return HttpResponseBadRequest(str(e))

def _get_children(cls, open_folders, parent):
children = []
for child in parent.subfolders:
child_id = str(child.id)
if child_id in open_folders:
grandchildren = cls._get_children(open_folders, child)
else:
grandchildren = None
children.append({
'id': child_id,
'name': child.name,
'children': grandchildren,
'is_open': grandchildren is not None,
'has_subfolders': child.subfolders.exists(),
})
return children

def structure(self, request, realm):
site = get_current_site(request)
try:
realm = RealmModel.objects.get(site=site, slug=realm)
except RealmModel.DoesNotExist:
raise ObjectDoesNotExist(f"Realm {realm} not found for {site.domain}.")
root_folder = FolderModel.objects.get_root_folder(realm)
root_folder_id = str(root_folder.id)
request.session.setdefault('finder.open_folders', [])
request.session.setdefault('finder.last_folder', root_folder_id)
if is_open := root_folder.subfolders.exists():
# direct children of the root folder are open regardless of the `open_folders` session
children = self._get_children(request.session['finder.open_folders'], root_folder)
else:
children = None
return {
'root_folder': {
'id': root_folder_id,
'name': None, # the root folder has no readable name
'is_root': True,
'is_open': is_open,
'children': children,
'has_subfolders': is_open,
},
'last_folder': request.session['finder.last_folder'],
**self.list(request, request.session['finder.last_folder']),
}

def fetch(self, request, folder_id):
"""
Open the given folder and fetch children data for the folder.
"""
folder = FolderModel.objects.get(id=folder_id)
folder_id = str(folder_id)
request.session.setdefault('finder.open_folders', [])
if folder_id not in request.session['finder.open_folders']:
request.session['finder.open_folders'].append(folder_id)
request.session.modified = True

return {
'id': folder_id,
'name': folder.name,
'children': self._get_children(request.session['finder.open_folders'], folder),
'is_open': True,
'has_subfolders': folder.subfolders.exists(),
}

def open(self, request, folder_id):
"""
Just open the folder.
"""
folder_id = str(folder_id)
request.session.setdefault('finder.open_folders', [])
if folder_id not in request.session['finder.open_folders']:
request.session['finder.open_folders'].append(folder_id)
request.session.modified = True

def close(self, request, folder_id):
"""
Just close the folder.
"""
folder_id = str(folder_id)
try:
request.session['finder.open_folders'].remove(folder_id)
except (KeyError, ValueError):
pass
else:
request.session.modified = True

def list(self, request, folder_id):
"""
List all the files of the given folder.
"""
folder = FolderModel.objects.get(id=folder_id)
request.session['finder.last_folder'] = str(folder_id)

folder.listdir(is_folder=False)

return {
'files': [{
'id': str(file.id),
'name': file.name,
'mime_type': file.mime_type,
'browser_component': file.casted.browser_component,
'thumbnail_url': file.casted.get_thumbnail_url(),
'sample_url': getattr(file.casted, 'get_sample_url', lambda: None)(),
'labels': file.serializable_value('labels'),
} for file in folder.listdir(is_folder=False)]
}
4 changes: 4 additions & 0 deletions finder/models/folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ def casted(self):
def folder(self):
return self

@property
def subfolders(self):
return FolderModel.objects.filter(parent=self)

@property
def num_children(self):
num_children = sum(inode_model.objects.filter(parent=self).count() for inode_model in InodeModel.real_models)
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
"devDependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^6.0.1",
"@r2wc/react-to-web-component": "^2.0.3",
"@types/react-dom": "^18.3.0",
"@wavesurfer/react": "^1.0.7",
"bootstrap": "^5.3.3",
"concurrently": "^8.2.0",
"esbuild": "^0.19.12",
"esbuild-plugin-svgr": "^2.1.0",
Expand Down
2 changes: 1 addition & 1 deletion testapp/templates/testapp.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<body>
<div class="container">
<div class="row">
<div class="col col-md-8 mx-auto my-5">
<div class="col mx-auto my-5">
<h1>Django Filer Demo</h1>
<finder-file-select base-url="{% url 'finder-api:base-url' %}" realm="admin"></finder-file-select>
</div>
Expand Down
Loading

0 comments on commit 1089315

Please sign in to comment.