From 300232df775f991c91cb868f3f323220ef3d4b2c Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Sat, 15 Sep 2018 11:04:35 +0200 Subject: [PATCH] implemented mv rpc --- .gitignore | 2 + Makefile | 13 ++--- main.go | 67 ++++++++++++----------- script.js | 156 ++++++++++++++++++++++++++++++++--------------------- style.css | 27 ++++++---- 5 files changed, 157 insertions(+), 108 deletions(-) diff --git a/.gitignore b/.gitignore index c3bd858..1104fc1 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ debug test +gossa.go gossa gossa-linux gossa-linux-arm @@ -7,6 +8,7 @@ gossa-linux-arm64 gossa-mac gossa-windows.exe +.vscode fixture/* fixture/*/* !fixture/compress diff --git a/Makefile b/Makefile index 1500c9e..c4326d4 100755 --- a/Makefile +++ b/Makefile @@ -29,9 +29,8 @@ clean: embed: echo "embedding css and js into binary" cp main.go gossa.go - perl -pe 's/some_css/`cat style.css`/ge' -i gossa.go - perl -pe 's/some_js/`cat script.js`/ge' -i gossa.go - go build gossa.go + perl -pe 's/css_will_be_here/`cat style.css`/ge' -i gossa.go + perl -pe 's/js_will_be_here/`cat script.js`/ge' -i gossa.go ci: go fmt @@ -42,8 +41,10 @@ ci: ci-watch: ls main.go script.js main_test.go | entr -rc make ci -debug-watch: - ls main.go script.js | entr -rc go run main.go fixture +watch: + ls main.go script.js | entr -rc make run run: - go run main.go fixture + make embed + go run gossa.go fixture + rm gossa.go diff --git a/main.go b/main.go index 7439e2f..c6aee2f 100755 --- a/main.go +++ b/main.go @@ -31,8 +31,8 @@ var verb = flag.Bool("verb", true, "verbosity") var skipHidden = flag.Bool("k", true, "skip hidden files") var initPath = "" -var css = `some_css` -var jsTag = `some_js` +var css = `css_will_be_here` // js will be embedded here +var jsTag = `js_will_be_here` // id. var units = [8]string{"k", "M", "G", "T", "P", "E", "Z", "Y"} type rpcCall struct { @@ -71,10 +71,20 @@ func row(name string, href string, size float64, ext string) string { ` + sizeToString(size) + ` - ` + name + ` + ` + name + ` ` } +func extraFolder(loc string) string { + if !strings.HasSuffix(loc, "/") { + loc = loc + "/" + } + if !strings.HasPrefix(loc, "/") { + loc = "/" + loc + } + return `` + loc + `` +} + func replyList(w http.ResponseWriter, path string) { if !strings.HasSuffix(path, "/") { path += "/" @@ -88,12 +98,11 @@ func replyList(w http.ResponseWriter, path string) { - -
-
+ +
+
` + extraFolder("/hols/aaa") + `
Drop here to upload
-

.` + html.EscapeString(path) + `

` @@ -123,8 +132,10 @@ func replyList(w http.ResponseWriter, path string) { } var resp = head + dirs + files + `
+
+
Gossa 🎶
- ` + ` w.Write([]byte(resp)) } @@ -174,22 +185,28 @@ func upload(w http.ResponseWriter, r *http.Request) { } func rpc(w http.ResponseWriter, r *http.Request) { + var err error bodyBytes, _ := ioutil.ReadAll(r.Body) bodyString := string(bodyBytes) var payload rpcCall json.Unmarshal([]byte(bodyString), &payload) - unparsed, _ := url.PathUnescape(payload.Args[0]) - p, err := checkPath(unparsed) - logVerb("RPC", err, unparsed) - - if err != nil { - w.Write([]byte("error")) - return - } else if payload.Call == "mkdirp" { - os.MkdirAll(p, os.ModePerm) + for i := range payload.Args { + payload.Args[i], err = checkPath(payload.Args[i]) + if err != nil { + logVerb("Cant read path", err, payload) + w.Write([]byte("error")) + return + } } + if payload.Call == "mkdirp" { + err = os.MkdirAll(payload.Args[0], os.ModePerm) + } else if payload.Call == "mv" { + err = os.Rename(payload.Args[0], payload.Args[1]) + } + + logVerb("RPC", err, payload) w.Write([]byte("ok")) } @@ -198,7 +215,7 @@ func checkPath(p string) (string, error) { fp, err := filepath.Abs(p) if err != nil || !strings.HasPrefix(fp, initPath) { - return fp, errors.New("error") + return "", errors.New("error") } return fp, nil @@ -217,20 +234,6 @@ func main() { initPath, err = filepath.Abs(initPath) check(err) - // Read CSS file if not embedded - if len(css) < 10 { - c, err := ioutil.ReadFile("./style.css") - check(err) - css = string(c) - } - - // Read JS file if not embedded - if len(jsTag) < 10 { - j, err := ioutil.ReadFile("./script.js") - check(err) - jsTag = string(j) - } - var hostString = *host + ":" + *port fmt.Println("Gossa startig on directory " + initPath) fmt.Println("Listening on http://" + hostString) diff --git a/script.js b/script.js index 331d7fa..a37c820 100755 --- a/script.js +++ b/script.js @@ -1,25 +1,34 @@ +/* eslint-env browser */ +/* global allA */ +/* eslint-disable no-multi-str */ + function cancelDefault (e) { e.preventDefault() e.stopPropagation() } -const checkDupes = test => allA.find(a => a.innerText.replace('/', '') === test) - +// RPC function rpcFs (call, args, cb) { - const decodedPath = decodeURI(window.location.pathname) - args = args.map(a => a.startsWith('/') ? a.slice(1) : a) - args = args.map(a => encodeURIComponent(decodedPath + a)) + // Prefix path with pwd if not absolute + const decodedPath = decodeURI(location.pathname) + args = args.map(a => a.startsWith('/') ? a : decodedPath + a) + // args = args.map(a => encodeURIComponent(a)) + console.log('RPC', call, args) const xhr = new window.XMLHttpRequest() - xhr.open('POST', window.location.origin + '/rpc') + xhr.open('POST', location.origin + '/rpc') xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8') - xhr.send(JSON.stringify({call, args})) + xhr.send(JSON.stringify({ call, args })) xhr.onload = cb } +// RPC Handlers const mkdirCall = (path, cb) => rpcFs('mkdirp', [path], cb) -function mkdir () { +const mvCall = (path1, path2, cb) => rpcFs('mv', [path1, path2], cb) + +// Mkdir switch +window.mkdirBtn = function () { const folder = window.prompt('New folder name', '') if (!folder) { @@ -28,13 +37,14 @@ function mkdir () { return window.alert('Name already already exists') } - mkdirCall(folder, () => browseTo(location.href)) + mkdirCall(folder, refresh) } function warning (e) { return 'Leaving will interrupt transfer\nAre you sure you want to leave?' } +// File upload function newBar (name) { const id = Math.random().toString(36).substring(7) @@ -61,10 +71,12 @@ function shouldRefresh () { totalDone = 0 totalUploads = 0 document.getElementById('progressBars').innerHTML = '' - browseTo(location.href) + refresh() } } +const checkDupes = test => allA.find(a => a.innerText.replace('/', '') === test) + function postFile (file, path) { totalUploads += 1 window.onbeforeunload = warning @@ -72,8 +84,8 @@ function postFile (file, path) { const xhr = new window.XMLHttpRequest() path = decodeURI(location.pathname).slice(0, -1) + path - xhr.open('POST', window.location.origin + '/post') - xhr.setRequestHeader("gossa-path", encodeURIComponent(path)) + xhr.open('POST', location.origin + '/post') + xhr.setRequestHeader('gossa-path', encodeURIComponent(path)) xhr.upload.id = newBar(path) const formData = new window.FormData() @@ -100,7 +112,9 @@ function parseDomItem (domFile, shoudCheckDupes) { if (domFile.isFile) { domFile.file(f => postFile(f, domFile.fullPath)) } else { - mkdirCall(domFile.fullPath, () => parseDomFolder(domFile)) + // remove absolute path + const f = domFile.fullPath.startsWith('/') ? domFile.fullPath.slice(1) : domFile.fullPath + mkdirCall(f, () => parseDomFolder(domFile)) } } @@ -114,13 +128,35 @@ function pushEntry (entry) { parseDomItem(entry, true) } +// Move files and folders +const isTextEvent = e => e.dataTransfer.items[0].type === 'text/plain' + +const isFolder = e => e && e.href && e.innerText.endsWith('/') + +const resetBackgroundLinks = () => { allA.forEach(a => { a.parentElement.style.backgroundColor = 'unset' }) } + +const setBackgroundLinks = t => { t.style.backgroundColor = 'rgba(123, 123, 123, 0.2)' } + +const getLink = e => e.target.parentElement.querySelectorAll('a.list-links')[0] + const upGrid = document.getElementById('drop-grid') document.ondragenter = (e) => { if (isPicMode()) { return } cancelDefault(e) - e.dataTransfer.dropEffect = 'copy' - upGrid.style.display = 'flex' + + resetBackgroundLinks() + + if (isTextEvent(e) && (isFolder(e.target) || isFolder(e.target.firstChild))) { + const t = getLink(e) + if (!t) return + setBackgroundLinks(t.parentElement) + } + + if (!isTextEvent(e)) { + upGrid.style.display = 'flex' + e.dataTransfer.dropEffect = 'copy' + } } upGrid.ondragleave = (e) => { @@ -133,11 +169,24 @@ document.ondragover = (e) => { return false } +// Handle drop - upload or move document.ondrop = (e) => { cancelDefault(e) upGrid.style.display = 'none' + resetBackgroundLinks() + + if (isTextEvent(e)) { + const t = e.target.classList.contains('fav') ? e.target : getLink(e) + if (!t || !t.innerText.endsWith('/')) return + e.dataTransfer.items[0].getAsString(s => { + const root = decodeURI(s.replace(location.href, '')) + const dest = t.innerText + root + mvCall(root, dest, refresh) + }) + } else { + Array.from(e.dataTransfer.items).forEach(pushEntry) + } - Array.from(e.dataTransfer.items).forEach(pushEntry) return false } @@ -164,7 +213,7 @@ function clearArrowSelected () { function restoreCursorPos () { clearArrowSelected() - const hrefSelected = window.localStorage.getItem('last-selected' + location.href) + const hrefSelected = localStorage.getItem('last-selected' + location.href) let a = allA.find(el => el.href === hrefSelected) if (!a) { @@ -180,7 +229,7 @@ function restoreCursorPos () { scrollToArrow() } -const storeLastArrowSrc = src => window.localStorage.setItem('last-selected' + location.href, src) +const storeLastArrowSrc = src => localStorage.setItem('last-selected' + location.href, src) function moveArrow (down) { const all = Array.from(document.querySelectorAll('i.arrow-icon')) @@ -210,12 +259,9 @@ function moveArrow (down) { } } -function setCursorToClosest () { - const a = allA.find(el => el.innerText.toLocaleLowerCase().startsWith(path)) - if (!a) { return } - storeLastArrowSrc(a.href) - restoreCursorPos() -} +const refresh = () => browseTo(location.href) + +const prevPage = () => browseTo(location.href + '../') window.onpopstate = prevPage @@ -231,22 +277,12 @@ function browseTo (href) { document.head.querySelectorAll('title')[0].innerText = title document.body.querySelectorAll('h1')[0].innerText = '.' + title window.history.pushState({}, '', window.encodeURI(title)) - } + } init() })) } -function nextPage () { - const a = getASelected() - if (!a.href || !a.innerText.endsWith('/')) { return } - browseTo(a.href) -} - -function prevPage () { - browseTo(window.location.href + "../") -} - function cpPath () { var t = document.createElement('textarea') t.value = getASelected().href @@ -317,8 +353,16 @@ function picsNav (down) { return true } -let path = '' -let clearPathToken = null +let allA +let typedPath = '' +let typedToken = null + +function setCursorToClosestTyped () { + const a = allA.find(el => el.innerText.toLocaleLowerCase().startsWith(typedPath)) + if (!a) { return } + storeLastArrowSrc(a.href) + restoreCursorPos() +} // Kb handler document.body.addEventListener('keydown', e => { @@ -332,9 +376,11 @@ document.body.addEventListener('keydown', e => { e.preventDefault() return picsNav(false) || moveArrow(false) + case 'Enter': + case 'Space': case 'ArrowRight': e.preventDefault() - return picsOn(true) || picsNav(true) || nextPage() + return picsOn(true) || picsNav(true) || getASelected().click() case 'ArrowLeft': e.preventDefault() @@ -345,10 +391,6 @@ document.body.addEventListener('keydown', e => { e.preventDefault() return picsToggle() } - - case 'Enter': - e.preventDefault() - return picsOn(true) || picsNav(true) || getASelected().click() } // Ctrl keys @@ -356,7 +398,7 @@ document.body.addEventListener('keydown', e => { switch (e.code) { case 'KeyD': e.preventDefault() - return isPicMode() || mkdir() + return isPicMode() || window.mkdirBtn() case 'KeyC': e.preventDefault() @@ -366,31 +408,26 @@ document.body.addEventListener('keydown', e => { // Any other key, for text search if (e.code.includes('Key')) { - path += e.code.replace('Key', '').toLocaleLowerCase() - window.clearTimeout(clearPathToken) - clearPathToken = setTimeout(() => { path = '' }, 1000) - setCursorToClosest() + typedPath += e.code.replace('Key', '').toLocaleLowerCase() + window.clearTimeout(typedToken) + typedToken = setTimeout(() => { typedPath = '' }, 1000) + setCursorToClosestTyped() } }, false) -function partialBrowseOnClickFolders () { - allA.forEach(a => { - if (!a.innerText.endsWith('/')) { return } - a.addEventListener('click', e => { - e.preventDefault() - storeLastArrowSrc(e.target.href) - browseTo(e.target.href) - }) - }, false) +window.onClickLink = e => { + if (!e.target.innerText.endsWith('/')) { return true } + storeLastArrowSrc(e.target.href) + browseTo(e.target.href) + return false } function init () { - allA = Array.from(document.querySelectorAll('a')) + allA = Array.from(document.querySelectorAll('a.list-links')) allImgs = allA.map(el => el.href).filter(isPic) - document.getElementById('picsToggle').style.display = allImgs.length > 0 ? 'flex' : 'none' + document.getElementsByClassName('icon-large-images')[0].style.display = allImgs.length > 0 ? 'inline-block' : 'none' imgsIndex = 0 - partialBrowseOnClickFolders() restoreCursorPos() console.log('Browsed to ' + location.href) } @@ -399,4 +436,3 @@ init() window.picsToggle = picsToggle window.picsNav = () => picsNav(true) -window.mkdir = mkdir diff --git a/style.css b/style.css index 82437fc..353e863 100755 --- a/style.css +++ b/style.css @@ -31,28 +31,27 @@ td.file-size { td.display-name { padding-left: 0.2em; + width: 100%; } -#newFolder { - background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFG0lEQVRYR81XfUxbVRQ/77XQrgxWZB06ZZjNOFaklfIxxhbM5owaIqLgZGUhQUOCRBLJJKCwZPEPNBoXFgPuI+G/zT/mki2Zc5uZATNBRBiDoqXA6ootbdnaUWD9bj33ta9pS8vYaMJOcnLffb33nt/5nXPPeaVgjYVaY/vw5ADo7OwsMBgMzVwu10pYcbvdEcmhKMrO4XCOtLa2TseCPYaBlpaWNJFIdDEnJyfbarWCx+Nh1Ov1MiMRdqRpGqamppQqlSoPQS+sFgQDoLm5OTkpKalXKpVmGI1GxnuiLJBgQAQUgrinVqvfam9v74sJgLq6uvWpqan9BQUF4tnZ2QCAcCDEuB+ABxnownD8uFy4cC2no6Pj8nIgGQbq6+t5QqFwKC8vT6zX68HlcjEgyEi8Z9kgxon4gbgSEhIcAoGAPR/x+HIac4SwRNSrVCq/a2tra44Ggr0FVFNTkyI3N1es0+kCxqMxwAIhhxKjrPqNMgCIkvdarbYPQ1X4MADQ2Ng4gkmYhRvA7nSD1UkFmAjOhWDjQa4zxggA1nt21OsMt245Je+wa21cWBw5UWUM7GUfGhoaBgsLC2UzMzMwcjcRzJ4NwOXQhO8A7aFe+N4jB4HXzJM/DGQkc4fTYXdRfC27yE3TNqN+rnLgdOVwyG7Mg978/PxdhIHRxS3w6is7wWJzRc2fSBWMtb10k291PJeGnr+m51R3tAcGTx26FgKgtra2B3OgiAAYd2VA0W4Z6My2qAAe5wd+HAeG/tboNFpT+Z8n5cwVDjhSU1NzDQG8ptXq4DYtgd0FUpgxM0UxJuL2eGFdPAf6x9RTeq3l7YHT8rFwAJcwBMUanRH0PAm8LM0Aw/3YMODBPLI63CBK4sNv/ZNKg9n8+uCpKk0IgOrq6vPZ2dnv/qszgU20EzK2b4O78/ZVe0+M2xxY2nF8Jnkd/HxDqZhdXCwa/b7SHAKgqqrqrEQiOTilswB/axFsSU8D84JjVQBcSLsdPWfvS9pGAVy4rhjZpHXLzp07wHS7QA7I5fIuBFA9MfMAUjL3w0bRJliwOR8LAIm3w4UVFMdgSUsRwMVfFX3d7WWBwhQAUFFR0SmTyT5SGZwgemk/JCYmgg0LUiQhdJKzfSXZt4KMOMPSjZ2TfRm2+ekNPLjcM3al+/h7b7I/BQCUl5cfy8rKapgwceF52RvAE/DBiV4QIZ6w6sHnUL9WTlJKQhz88sf4me5jZYeWACgtLW3LzMz8bGJeCC/m7QM6Lh7j5wEX812wciPLrUyMp+D6wOTxG+3lnywBUFJSckQsFn8xad0ML2TvAYc3+tdaAM8ywMJ/IlWS63VB7+Dtz3/veP/LJQCKi4s/xQ+Sb8bt6ZC2IwfcgH0gXJg4LycRwuPfQCMCh90Gw4o7H/afONgVDoDeu3fvYeyGX4+7d8Bz26VoKJQBf0t6iHmSjZGXEAbum+acY6PDHyh+OHwWVzEJxlp5CjthDVbCr1QghdRtYqa1shLVa/YGrCBFiKF7erNlZODqx+qr3/6EUxMLgFgS4Q0o2bWv5KQ2LotKfjZ9BUc+2hICQKPW6G6eP1pm0f6jxuksYYFlQIiTlD3yo5e4fGGqFz+l8LrRkS6c1+uvDdGuhr8nUxSHFATKS1NWzvrNA85FE++/oQsd0zevkCZEvA8txfgiCTURlYf6AJWAipUo8aCtqKS5zKNa2IMj3TUSEqLoQsyEGCZn+ipbkDw5f81i5usjHrTmDPwPh1d5PwAYMkwAAAAASUVORK5CYII="); + + +.icHolder { position: fixed; right: 20px; top: 20px; - width: 32px; - height: 32px; - cursor: pointer; } -#picsToggle { - background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABxUlEQVRYR+2XyyuFQRjGz3HPNfIPiEL5D0RYyNJeVlYuycIlf4EkSRQLG1bWkrKwo7BSyg5LZUW533+PZvQ1nL7jO2echTP1NDPffO/7POed55vmxGMZbvEM88eyAoIVGGY7pkCx5225I/8EWBRPUMAN8xLP5Db9LYNSV8C7Wa2lP/MkpJG8Jyb3548PVsAK8GnMIjjvfQsohGAW9BqiNfpR8Ai09uBbgMw16GzhAnOZ/E8EXENU7gi4Yl4JCkwlvrbfhwcyLkDlHnIqMM98BOSDp1Q80ETwtjFSJ/2pQ6Sp9nkmYMJVxuOGOCUB1SQ5BDWG9Jy+GVz8ICLRozwWnqNUQMp3QIuT+Zh5K5DJbLO+sudKMCSygBWy9CX4WXs813bojK8HG+AFtINLJybXrOlx0iehzDMXUuZd1rfAGNDnpnYE2oC+Ctt+LaCLyE2gwChNwlQde/zmMH41iUIr0MCL+6AiCnMgRpXpBjJf0gKqePkA1KVIbsPXGfQAGfMtrAJyvL71jjSR2zTLDAaSEbDES/1pJrfpdOOaDKuAJ+5vaROa8P8K+MtLqbjKVOrgfUC3mGng+2asG7FOTJk9+88oW4HYB/NaXyF6NJlmAAAAAElFTkSuQmCC"); - position: fixed; - right: 60px; - top: 20px; +.ic { width: 32px; height: 32px; cursor: pointer; + display: inline-block; + overflow: hidden; + color: transparent; } + #picsToggleCinema { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAoElEQVRYhe2Wuw3AIAxEbZRRMwFlJiBLMCBDcClCCwq2gRRc7/cO8RFEOzt/DAAPIABwBixXWL5nKOBN1JQo8lhYQTooKqFmaAAWCxCDzOQSoLm8BzxM/kUwXN4STZM3SsyTV0qo5HMaW2bpFiw9hEuv4dKHqAdsXkICNCuhAalLWKxCxcDEL9lRmU1EdBPRxcxZWoCZM4Cz8JKUs7MzNA81r4TL7qAyXAAAAABJRU5ErkJggg=="); position: fixed; @@ -156,6 +155,14 @@ td.display-name { image-orientation: from-image; } +.icon-large-folder { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFG0lEQVRYR81XfUxbVRQ/77XQrgxWZB06ZZjNOFaklfIxxhbM5owaIqLgZGUhQUOCRBLJJKCwZPEPNBoXFgPuI+G/zT/mki2Zc5uZATNBRBiDoqXA6ootbdnaUWD9bj33ta9pS8vYaMJOcnLffb33nt/5nXPPeaVgjYVaY/vw5ADo7OwsMBgMzVwu10pYcbvdEcmhKMrO4XCOtLa2TseCPYaBlpaWNJFIdDEnJyfbarWCx+Nh1Ov1MiMRdqRpGqamppQqlSoPQS+sFgQDoLm5OTkpKalXKpVmGI1GxnuiLJBgQAQUgrinVqvfam9v74sJgLq6uvWpqan9BQUF4tnZ2QCAcCDEuB+ABxnownD8uFy4cC2no6Pj8nIgGQbq6+t5QqFwKC8vT6zX68HlcjEgyEi8Z9kgxon4gbgSEhIcAoGAPR/x+HIac4SwRNSrVCq/a2tra44Ggr0FVFNTkyI3N1es0+kCxqMxwAIhhxKjrPqNMgCIkvdarbYPQ1X4MADQ2Ng4gkmYhRvA7nSD1UkFmAjOhWDjQa4zxggA1nt21OsMt245Je+wa21cWBw5UWUM7GUfGhoaBgsLC2UzMzMwcjcRzJ4NwOXQhO8A7aFe+N4jB4HXzJM/DGQkc4fTYXdRfC27yE3TNqN+rnLgdOVwyG7Mg978/PxdhIHRxS3w6is7wWJzRc2fSBWMtb10k291PJeGnr+m51R3tAcGTx26FgKgtra2B3OgiAAYd2VA0W4Z6My2qAAe5wd+HAeG/tboNFpT+Z8n5cwVDjhSU1NzDQG8ptXq4DYtgd0FUpgxM0UxJuL2eGFdPAf6x9RTeq3l7YHT8rFwAJcwBMUanRH0PAm8LM0Aw/3YMODBPLI63CBK4sNv/ZNKg9n8+uCpKk0IgOrq6vPZ2dnv/qszgU20EzK2b4O78/ZVe0+M2xxY2nF8Jnkd/HxDqZhdXCwa/b7SHAKgqqrqrEQiOTilswB/axFsSU8D84JjVQBcSLsdPWfvS9pGAVy4rhjZpHXLzp07wHS7QA7I5fIuBFA9MfMAUjL3w0bRJliwOR8LAIm3w4UVFMdgSUsRwMVfFX3d7WWBwhQAUFFR0SmTyT5SGZwgemk/JCYmgg0LUiQhdJKzfSXZt4KMOMPSjZ2TfRm2+ekNPLjcM3al+/h7b7I/BQCUl5cfy8rKapgwceF52RvAE/DBiV4QIZ6w6sHnUL9WTlJKQhz88sf4me5jZYeWACgtLW3LzMz8bGJeCC/m7QM6Lh7j5wEX812wciPLrUyMp+D6wOTxG+3lnywBUFJSckQsFn8xad0ML2TvAYc3+tdaAM8ywMJ/IlWS63VB7+Dtz3/veP/LJQCKi4s/xQ+Sb8bt6ZC2IwfcgH0gXJg4LycRwuPfQCMCh90Gw4o7H/afONgVDoDeu3fvYeyGX4+7d8Bz26VoKJQBf0t6iHmSjZGXEAbum+acY6PDHyh+OHwWVzEJxlp5CjthDVbCr1QghdRtYqa1shLVa/YGrCBFiKF7erNlZODqx+qr3/6EUxMLgFgS4Q0o2bWv5KQ2LotKfjZ9BUc+2hICQKPW6G6eP1pm0f6jxuksYYFlQIiTlD3yo5e4fGGqFz+l8LrRkS6c1+uvDdGuhr8nUxSHFATKS1NWzvrNA85FE++/oQsd0zevkCZEvA8txfgiCTURlYf6AJWAipUo8aCtqKS5zKNa2IMj3TUSEqLoQsyEGCZn+ipbkDw5f81i5usjHrTmDPwPh1d5PwAYMkwAAAAASUVORK5CYII="); +} + +.icon-large-images { + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABxUlEQVRYR+2XyyuFQRjGz3HPNfIPiEL5D0RYyNJeVlYuycIlf4EkSRQLG1bWkrKwo7BSyg5LZUW533+PZvQ1nL7jO2echTP1NDPffO/7POed55vmxGMZbvEM88eyAoIVGGY7pkCx5225I/8EWBRPUMAN8xLP5Db9LYNSV8C7Wa2lP/MkpJG8Jyb3548PVsAK8GnMIjjvfQsohGAW9BqiNfpR8Ai09uBbgMw16GzhAnOZ/E8EXENU7gi4Yl4JCkwlvrbfhwcyLkDlHnIqMM98BOSDp1Q80ETwtjFSJ/2pQ6Sp9nkmYMJVxuOGOCUB1SQ5BDWG9Jy+GVz8ICLRozwWnqNUQMp3QIuT+Zh5K5DJbLO+sudKMCSygBWy9CX4WXs813bojK8HG+AFtINLJybXrOlx0iehzDMXUuZd1rfAGNDnpnYE2oC+Ctt+LaCLyE2gwChNwlQde/zmMH41iUIr0MCL+6AiCnMgRpXpBjJf0gKqePkA1KVIbsPXGfQAGfMtrAJyvL71jjSR2zTLDAaSEbDES/1pJrfpdOOaDKuAJ+5vaROa8P8K+MtLqbjKVOrgfUC3mGng+2asG7FOTJk9+88oW4HYB/NaXyF6NJlmAAAAAElFTkSuQmCC"); +} + .icon-blank { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWBJREFUeNqEUj1LxEAQnd1MVA4lyIEWx6UIKEGUExGsbC3tLfwJ/hT/g7VlCnubqxXBwg/Q4hQP/LhKL5nZuBsvuGfW5MGyuzM7jzdvVuR5DgYnZ+f99ai7Vt5t9K9unu4HLweI3qWYxI6PDosdy0fhcntxO44CcOBzPA7mfEyuHwf7ntQk4jcnywOxIlfxOCNYaLVgb6cXbkTdhJXq2SIlNMC0xIqhHczDbi8OVzpLSUa0WebRfmigLHqj1EcPZnwf7gbDIrYVRyEinurj6jTBHyI7pqVrFQqEbt6TEmZ9v1NRAJNC1xTYxIQh/MmRUlmFQE3qWOW1nqB2TWk1/3tgJV0waVvkFIEeZbHq4ElyKzAmEXOx6gnEVJuWBzmkRJBRPYGZBDsVaOlpSgVJE2yVaAe/0kx/3azBRO0VsbMFZE3CDSZKweZfYIVg+DZ6v7h9GDVOwZPw/PoxKu/fAgwALbDAXf7DdQkAAAAASUVORK5CYII="); }