plugins: support url icons

Massimo Melina committed Mar 26, 2023 at 11:55 UTC 77ed5b04e9c596e19e07c8fe7bf2bb4450f29392
4 files changed +30 -8
dev-plugins.md
+4 -2
@@ -194,9 +194,11 @@ This is a list of available frontend-events, with respective object parameter an
194 - output `FileMenuEntry | FileMenuEntry[]`
195 ```typescript
196 interface FileMenuEntry {
197 - label: ReactNode, icon?: string, href?: string,
197 + label: ReactNode,
198 + href?: string, // use this if you want your entry to be a link
199 + icon?: string, // supports: emoji, name from a limited set
200 onClick?: () => (Promisable<boolean>) // return false to not close menu dialog
199 - //...rest is transfered to <a> element
201 + //...rest is transfered to <a> element, for example 'target', or 'title'
202 }
203 ```
204 ## Publish your plug-in
frontend/src/BrowseFiles.ts
+4 -2
@@ -11,7 +11,7 @@ import {
11 useRef,
12 useState
13 } from 'react'
14 -import { domOn, formatBytes, ErrorMsg, hIcon, isMobile, newDialog, hfsEvent, getHFS } from './misc'
14 +import { domOn, formatBytes, ErrorMsg, hIcon, isMobile, newDialog, hfsEvent, getHFS, wantArray } from './misc'
15 import { Checkbox, CustomCode, Spinner } from './components'
16 import { Head } from './Head'
17 import { state, useSnapState } from './state'
@@ -227,7 +227,9 @@ const Entry = memo((entry: DirEntry & { midnight: Date, separator?: string }) =>
227 { label: "Download", href: href + '?dl', icon: 'download' },
228 can_delete && { label: "Delete", icon: 'trash', onClick: () => deleteFiles([href], base) }
229 ]
230 - hfsEvent('fileMenu', { entry, menu })
230 + const res = hfsEvent('fileMenu', { entry })
231 + if (res)
232 + menu.push(...res.flat())
233 const close = newDialog({
234 title: "File menu",
235 icon: () => ico,
frontend/src/icons.ts
+9 -4
@@ -35,14 +35,19 @@ document.fonts.ready.then(async ()=> {
35 state.iconsClass = ' ' // with fontello we don't need an additional class (unlike google material icons), but the empty space will cause reload
36 })
37
38 -export const Icon = memo(({ name, alt, className='', ...props }: { name:string, className?:string, alt?:string, style?:any }) => {
38 +interface IconProps { name:string, className?:string, alt?:string, [rest:string]: any }
39 +export const Icon = memo(({ name, alt, className='', ...props }: IconProps) => {
40 const [emoji,clazz] = ((SYS_ICONS as any)[name] || name).split(':')
41 const { iconsClass } = useSnapState()
41 - className += ' icon ' + (iconsClass ? 'fa-'+(clazz||name) : 'emoji')
42 + className += ' icon'
43 + const nameIsEmoji = name.length <= 2
44 + const nameIsFile = name.includes('.')
45 + className += nameIsEmoji ? ' emoji-icon' : nameIsFile ? ' file-icon' : iconsClass ? ' fa-'+(clazz||name) : ' emoji'
46 return h('span',{
43 - ...props,
47 'aria-label': alt,
48 role: 'img',
49 + ...props,
50 + ...nameIsFile ? { style: { backgroundImage: `url(${JSON.stringify(name)})`, ...props?.style } } : undefined,
51 className,
47 - }, iconsClass ? null : (emoji||'#'))
52 + }, nameIsEmoji ? name : iconsClass ? null : (emoji||'#'))
53 })
frontend/src/index.scss
+13
@@ -75,6 +75,19 @@ select { text-align: center; } /* it is surely cooler on the options dialog */
75 .icon {
76 font-size: 1.2em;
77 }
78 +.emoji-icon {
79 + display: inline-block;
80 + width: 1.4em;
81 + text-align: center;
82 +}
83 +.file-icon {
84 + @extend .emoji-icon;
85 + height: 1em;
86 + background-size: contain;
87 + background-repeat: no-repeat;
88 + background-position: center;
89 + vertical-align: text-bottom;
90 +}
91
92 .icon.mirror:before { transform: scaleX(-1); }
93 a {