From 9012af93b69c20770bd5dffc8c6cf5dfb47bc2c3 Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Sun, 22 Dec 2024 23:49:32 +0100 Subject: [PATCH 01/11] File list contextual actions. Signed-off-by: ubi de feo --- ui/arduino/main.css | 72 +++++++++++++++++++-- ui/arduino/media/More.svg | 3 + ui/arduino/media/download.svg | 3 + ui/arduino/media/upload.svg | 3 + ui/arduino/store.js | 24 +++++++ ui/arduino/views/components/file-list.js | 80 +++++++++++++++++++++--- 6 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 ui/arduino/media/More.svg create mode 100644 ui/arduino/media/download.svg create mode 100644 ui/arduino/media/upload.svg diff --git a/ui/arduino/main.css b/ui/arduino/main.css index d9bce9a..825ba6e 100644 --- a/ui/arduino/main.css +++ b/ui/arduino/main.css @@ -73,9 +73,6 @@ button[disabled]:hover { button:hover, button.active { background: rgba(255, 255, 255, 1); } -/* button.inactive:hover { - background: rgba(255, 255, 255, 0.2); -} */ button .icon { width: 63%; @@ -101,7 +98,6 @@ button.small .icon { } .button .label { text-align: center; - /* color: #eee; */ color: rgba(255, 255, 255, 0.2); font-family: "OpenSans", sans-serif; } @@ -624,6 +620,7 @@ button.small .icon { background: #ECF1F1; height: 100%; overflow-y: scroll; + position: relative; } .file-list .list { @@ -646,6 +643,7 @@ button.small .icon { gap: 10px; align-self: stretch; transition: all 0.1s; + position: relative; } .file-list .item.selected, @@ -659,6 +657,7 @@ button.small .icon { align-items: center; align-self: stretch; cursor: pointer; + width: 22px; transition: all 0.1s; } @@ -715,6 +714,71 @@ button.small .icon { outline-color: #F4BA00; } +.popup-menu { + position: absolute; + top: auto; + bottom: auto; + background: white; + border: 1px solid #ddd; + border-radius: 8px; + display: flex; + z-index: 1000; + gap: 4px; + align-items: stretch; + height: 28px; + padding: 4px 4px; + margin: 0; + flex-direction: row; + right: 0px; +} + +.popup-menu-item { + cursor: pointer; + background: #ddd; + border-radius: 6px; + flex: auto; + display: flex; + width: 32px; + justify-content: center; + flex-direction: column; + align-items: center; +} + +.popup-menu-item img { + width: 24px; + height: 24px; + max-width: 24px; + max-height: 24px; +} + +.popup-menu-item:last-child { + flex: 0 0 18px; +} +.popup-menu-item:last-child:hover { + background-color: #bbb; +} +.popup-menu-item:hover { + background-color: #f5f5f5; +} + +.popup-menu-item.disabled { + color: #ccc; + cursor: default; + background: #eee; + opacity: 0.5; +} + +.options { + cursor: pointer; +} + +.options img{ + width: 28px; + height: 28px; + max-width: 28px; + max-height: 28px; +} + #code-editor .cm-panels { border-color: #DAE3E3; padding: 0 10px; diff --git a/ui/arduino/media/More.svg b/ui/arduino/media/More.svg new file mode 100644 index 0000000..5357a4f --- /dev/null +++ b/ui/arduino/media/More.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/arduino/media/download.svg b/ui/arduino/media/download.svg new file mode 100644 index 0000000..530736c --- /dev/null +++ b/ui/arduino/media/download.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/arduino/media/upload.svg b/ui/arduino/media/upload.svg new file mode 100644 index 0000000..ec176ff --- /dev/null +++ b/ui/arduino/media/upload.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/arduino/store.js b/ui/arduino/store.js index b40a1c8..b38e6dc 100644 --- a/ui/arduino/store.js +++ b/ui/arduino/store.js @@ -39,6 +39,7 @@ async function store(state, emitter) { state.boardFiles = [] state.openFiles = [] state.selectedFiles = [] + state.fileContextMenu = null state.newTabFileName = null state.editingFile = null @@ -1136,6 +1137,29 @@ async function store(state, emitter) { emitter.emit('render') }) + emitter.on('file-context-menu', (file, source, event) => { + state.selectedFiles = [] + let parentFolder = source == 'board' ? state.boardNavigationPath : state.diskNavigationPath + log('file-contextual-menu', file, source, event) + const isSelected = state.selectedFiles.find((f) => { + return f.fileName === file.fileName && f.source === source + }) + if (isSelected) { + state.selectedFiles = state.selectedFiles.filter((f) => { + return !(f.fileName === file.fileName && f.source === source) + }) + } else { + state.selectedFiles.push({ + fileName: file.fileName, + type: file.type, + source: source, + parentFolder: parentFolder + }) + } + state.fileContextMenu = state.selectedFiles[state.selectedFiles.length - 1] + emitter.emit('render') + }) + emitter.on('toggle-file-selection', (file, source, event) => { log('toggle-file-selection', file, source, event) let parentFolder = source == 'board' ? state.boardNavigationPath : state.diskNavigationPath diff --git a/ui/arduino/views/components/file-list.js b/ui/arduino/views/components/file-list.js index 83e7d59..73d48ad 100644 --- a/ui/arduino/views/components/file-list.js +++ b/ui/arduino/views/components/file-list.js @@ -19,6 +19,11 @@ function generateFileList(source) {
emit('finish-creating-file', e.target.value)}/>
+ ` const newFolderItem = html` @@ -27,8 +32,50 @@ function generateFileList(source) {
emit('finish-creating-folder', e.target.value)}/>
+ ` + function dismissContextMenu(e, item) { + console.log("click action", e, item) + e.stopPropagation() + state.fileContextMenu = null + state.selectedFiles = [] + emit('render') + } + function triggerRemove() { + emit('remove-files') + state.fileContextMenu = null + emit('render') + } + function triggerRename(item) { + emit('rename-file', source, item) + + state.fileContextMenu = null + emit('render') + } + function triggerTransfer() { + if (source === 'disk') { + emit('upload-files') + }else{ + emit('download-files') + } + state.fileContextMenu = null + emit('render') + } + function FileOptions(item, i){ + const popupMenu = html` + ` + return popupMenu + } function FileItem(item, i) { const renamingFileItem = html` @@ -43,6 +90,8 @@ function generateFileList(source) { const isChecked = state.selectedFiles.find( f => f.fileName === item.fileName && f.source === source ) + + const hasContextMenu = state.fileContextMenu && state.fileContextMenu.fileName === item.fileName && state.fileContextMenu.source === source function renameItem(e) { e.preventDefault() emit('rename-file', source, item) @@ -54,12 +103,26 @@ function generateFileList(source) { function openFile() { if (!state.renamingFile) emit(`open-file`, source, item) } + + function toggleContextMenu(item, source, e) { + e.stopPropagation() + console.log("show file options", item, source, e) + // const popupMenu = e.currentTarget.parentElement.querySelector('.popup-menu') + // popupMenu.classList.add('visible') + emit('file-context-menu', item, source, e) + } let fileName = item.fileName const isSelected = state.selectedFiles.find(f => f.fileName === fileName) if (state.renamingFile == source && isSelected) { fileName = renamingFileItem } + + contextMenuHtml = html`` + if (hasContextMenu) { + contextMenuHtml = html`${FileOptions(item, i)}` + } + if (item.type === 'folder') { return html`
${fileName}
-
- +
toggleContextMenu(item, source, e)}>}> +
+ ${contextMenuHtml}
` } else { + //
{contextualMenu(e, item)}}> return html`
${fileName}
-
- +
toggleContextMenu(item, source, e)}> +
+ ${contextMenuHtml}
` } } - +// // XXX: Use `source` to filter an array of files with a `source` as proprety const files = state[`${source}Files`].sort((a, b) => { const nameA = a.fileName.toUpperCase() @@ -116,8 +182,8 @@ function generateFileList(source) {
${source === 'disk' && state.diskNavigationPath != '/' ? parentNavigationDots : ''} ${source === 'board' && state.boardNavigationPath != '/' ? parentNavigationDots : ''} - ${state.creatingFile == source ? newFileItem : null} - ${state.creatingFolder == source ? newFolderItem : null} + ${source == state.creatingFile ? newFileItem : null} + ${source == state.creatingFolder ? newFolderItem : null} ${files.map(FileItem)}
From 315812caf69653fa589cd0e61df0f41e3afa87e1 Mon Sep 17 00:00:00 2001 From: ubi de feo Date: Mon, 23 Dec 2024 15:32:35 +0100 Subject: [PATCH 02/11] WIP: contextual actions on file/folder. Signed-off-by: ubi de feo --- ui/arduino/main.css | 27 ++++++++--- ui/arduino/store.js | 12 +++-- ui/arduino/views/components/file-list.js | 60 +++++++++++++++++------- 3 files changed, 70 insertions(+), 29 deletions(-) diff --git a/ui/arduino/main.css b/ui/arduino/main.css index 825ba6e..478d500 100644 --- a/ui/arduino/main.css +++ b/ui/arduino/main.css @@ -638,7 +638,7 @@ button.small .icon { height: 28px; min-height: 28px; max-height: 28px;; - padding: 5px 10px; + padding: 5px 0; align-items: center; gap: 10px; align-self: stretch; @@ -657,7 +657,6 @@ button.small .icon { align-items: center; align-self: stretch; cursor: pointer; - width: 22px; transition: all 0.1s; } @@ -665,6 +664,20 @@ button.small .icon { opacity: 1; } +.file-list .item.selected.actionable .options { + opacity: 0; +} + +.file-list .item .checkbox { + display: none; +} +.file-list .item.actionable .checkbox { + display: flex; +} +.file-list .item.selected.actionable .checkbox { + display: none; +} + .file-list .item .icon { width: 32px; height: 32px; @@ -715,18 +728,18 @@ button.small .icon { } .popup-menu { - position: absolute; + /* position: absolute; top: auto; - bottom: auto; - background: white; - border: 1px solid #ddd; + bottom: auto; */ + /* background: white; + border: 1px solid #ddd; */ border-radius: 8px; display: flex; z-index: 1000; gap: 4px; align-items: stretch; height: 28px; - padding: 4px 4px; + padding: 0px 4px; margin: 0; flex-direction: row; right: 0px; diff --git a/ui/arduino/store.js b/ui/arduino/store.js index b38e6dc..1c05853 100644 --- a/ui/arduino/store.js +++ b/ui/arduino/store.js @@ -30,7 +30,7 @@ async function store(state, emitter) { win.setWindowSize(720, 640) state.platform = window.BridgeWindow.getOS() - state.view = 'editor' + state.view = 'file-manager' state.diskNavigationPath = '/' state.diskNavigationRoot = getDiskNavigationRootFromStorage() state.diskFiles = [] @@ -39,7 +39,7 @@ async function store(state, emitter) { state.boardFiles = [] state.openFiles = [] state.selectedFiles = [] - state.fileContextMenu = null + state.itemActionMenu = null state.newTabFileName = null state.editingFile = null @@ -1138,7 +1138,7 @@ async function store(state, emitter) { }) emitter.on('file-context-menu', (file, source, event) => { - state.selectedFiles = [] + // state.selectedFiles = [] let parentFolder = source == 'board' ? state.boardNavigationPath : state.diskNavigationPath log('file-contextual-menu', file, source, event) const isSelected = state.selectedFiles.find((f) => { @@ -1156,13 +1156,14 @@ async function store(state, emitter) { parentFolder: parentFolder }) } - state.fileContextMenu = state.selectedFiles[state.selectedFiles.length - 1] + state.itemActionMenu = state.selectedFiles[state.selectedFiles.length - 1] emitter.emit('render') }) emitter.on('toggle-file-selection', (file, source, event) => { log('toggle-file-selection', file, source, event) let parentFolder = source == 'board' ? state.boardNavigationPath : state.diskNavigationPath + // Single file selection unless holding keyboard key if (event && !event.ctrlKey && !event.metaKey) { state.selectedFiles = [{ @@ -1171,7 +1172,9 @@ async function store(state, emitter) { source: source, parentFolder: parentFolder }] + state.itemActionMenu = null emitter.emit('render') + console.log(state.selectedFiles) return } @@ -1190,6 +1193,7 @@ async function store(state, emitter) { parentFolder: parentFolder }) } + console.log(state.selectedFiles) emitter.emit('render') }) emitter.on('open-selected-files', async () => { diff --git a/ui/arduino/views/components/file-list.js b/ui/arduino/views/components/file-list.js index 73d48ad..9306ffc 100644 --- a/ui/arduino/views/components/file-list.js +++ b/ui/arduino/views/components/file-list.js @@ -1,7 +1,8 @@ const DiskFileList = generateFileList('disk') const BoardFileList = generateFileList('board') -function generateFileList(source) { +function generateFileList(source, selectedFiles) { + return function FileList(state, emit) { function onKeyEvent(e) { if(e.key.toLowerCase() === 'enter') { @@ -13,6 +14,8 @@ function generateFileList(source) { } } + /* template for new file item, with focussed input + ESC to cancel, ENTER to finish */ const newFileItem = html`
@@ -26,6 +29,8 @@ function generateFileList(source) {
` + /* template for new folder item, with focussed input + ESC to cancel, ENTER to finish */ const newFolderItem = html`
@@ -39,22 +44,23 @@ function generateFileList(source) {
` + function dismissContextMenu(e, item) { console.log("click action", e, item) e.stopPropagation() - state.fileContextMenu = null + state.itemActionMenu = null state.selectedFiles = [] emit('render') } function triggerRemove() { emit('remove-files') - state.fileContextMenu = null + state.itemActionMenu = null emit('render') } function triggerRename(item) { emit('rename-file', source, item) - state.fileContextMenu = null + state.itemActionMenu = null emit('render') } function triggerTransfer() { @@ -63,10 +69,11 @@ function generateFileList(source) { }else{ emit('download-files') } - state.fileContextMenu = null + state.itemActionMenu = null emit('render') } - function FileOptions(item, i){ + + function ItemActions(item, i){ const popupMenu = html`