This reverts commit 3c8912d3dfba91869fb0cb914b348458ea218e32.pull/1140/head
parent
e6a36a3fe1
commit
e638250a52
@ -0,0 +1,702 @@ |
||||
/* global FileReader */ |
||||
/* global fetch */ |
||||
const async = require('async') |
||||
const Gists = require('gists') |
||||
const modalDialogCustom = require('../ui/modal-dialog-custom') |
||||
const tooltip = require('../ui/tooltip') |
||||
const QueryParams = require('../../lib/query-params') |
||||
const helper = require('../../lib/helper') |
||||
const yo = require('yo-yo') |
||||
const Treeview = require('../ui/TreeView') |
||||
const modalDialog = require('../ui/modaldialog') |
||||
const EventManager = require('../../lib/events') |
||||
const contextMenu = require('../ui/contextMenu') |
||||
const css = require('./styles/file-explorer-styles') |
||||
const globalRegistry = require('../../global/registry') |
||||
const queryParams = new QueryParams() |
||||
let MENU_HANDLE |
||||
|
||||
function fileExplorer (localRegistry, files, menuItems, plugin) { |
||||
var self = this |
||||
this.events = new EventManager() |
||||
// file provider backend
|
||||
this.files = files |
||||
// element currently focused on
|
||||
this.focusElement = null |
||||
// path currently focused on
|
||||
this.focusPath = null |
||||
const allItems = |
||||
[ |
||||
{ |
||||
action: 'createNewFile', |
||||
title: 'Create New File', |
||||
icon: 'fas fa-plus-circle' |
||||
}, |
||||
{ |
||||
action: 'publishToGist', |
||||
title: 'Publish all [browser] explorer files to a github gist', |
||||
icon: 'fab fa-github' |
||||
}, |
||||
{ |
||||
action: 'uploadFile', |
||||
title: 'Add Local file to the Browser Storage Explorer', |
||||
icon: 'far fa-folder-open' |
||||
}, |
||||
{ |
||||
action: 'updateGist', |
||||
title: 'Update the current [gist] explorer', |
||||
icon: 'fab fa-github' |
||||
} |
||||
] |
||||
// menu items
|
||||
this.menuItems = allItems.filter( |
||||
(item) => { |
||||
if (menuItems) return menuItems.find((name) => { return name === item.action }) |
||||
} |
||||
) |
||||
|
||||
self._components = {} |
||||
self._components.registry = localRegistry || globalRegistry |
||||
self._deps = { |
||||
config: self._components.registry.get('config').api, |
||||
editor: self._components.registry.get('editor').api, |
||||
fileManager: self._components.registry.get('filemanager').api |
||||
} |
||||
|
||||
self.events.register('focus', function (path) { |
||||
self._deps.fileManager.open(path) |
||||
}) |
||||
|
||||
self._components.registry.put({ api: self, name: `fileexplorer/${self.files.type}` }) |
||||
|
||||
// warn if file changed outside of Remix
|
||||
function remixdDialog () { |
||||
return yo`<div>This file has been changed outside of Remix IDE.</div>` |
||||
} |
||||
|
||||
this.files.event.register('fileExternallyChanged', (path, file) => { |
||||
if (self._deps.config.get('currentFile') === path && self._deps.editor.currentContent() && self._deps.editor.currentContent() !== file.content) { |
||||
if (this.files.isReadOnly(path)) return self._deps.editor.setText(file.content) |
||||
|
||||
modalDialog(path + ' changed', remixdDialog(), |
||||
{ |
||||
label: 'Replace by the new content', |
||||
fn: () => { |
||||
self._deps.editor.setText(file.content) |
||||
} |
||||
}, |
||||
{ |
||||
label: 'Keep the content displayed in Remix', |
||||
fn: () => {} |
||||
} |
||||
) |
||||
} |
||||
}) |
||||
|
||||
// register to event of the file provider
|
||||
files.event.register('fileRemoved', fileRemoved) |
||||
files.event.register('fileRenamed', fileRenamed) |
||||
files.event.register('fileRenamedError', fileRenamedError) |
||||
files.event.register('fileAdded', fileAdded) |
||||
files.event.register('folderAdded', folderAdded) |
||||
|
||||
function fileRenamedError (error) { |
||||
modalDialogCustom.alert(error) |
||||
} |
||||
|
||||
function fileAdded (filepath) { |
||||
self.ensureRoot(() => { |
||||
const folderpath = filepath.split('/').slice(0, -1).join('/') |
||||
const currentTree = self.treeView.nodeAt(folderpath) |
||||
if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath) |
||||
if (currentTree) { |
||||
self.files.resolveDirectory(folderpath, (error, fileTree) => { |
||||
if (error) console.error(error) |
||||
if (!fileTree) return |
||||
fileTree = normalize(folderpath, fileTree) |
||||
self.treeView.updateNodeFromJSON(folderpath, fileTree, true) |
||||
self.focusElement = self.treeView.labelAt(self.focusPath) |
||||
// TODO: here we update the selected file (it applicable)
|
||||
// cause we are refreshing the interface of the whole directory when there's a new file.
|
||||
if (self.focusElement && !self.focusElement.classList.contains('bg-secondary')) { |
||||
self.focusElement.classList.add('bg-secondary') |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
function extractNameFromKey (key) { |
||||
const keyPath = key.split('/') |
||||
|
||||
return keyPath[keyPath.length - 1] |
||||
} |
||||
|
||||
function folderAdded (folderpath) { |
||||
self.ensureRoot(() => { |
||||
folderpath = folderpath.split('/').slice(0, -1).join('/') |
||||
self.files.resolveDirectory(folderpath, (error, fileTree) => { |
||||
if (error) console.error(error) |
||||
if (!fileTree) return |
||||
fileTree = normalize(folderpath, fileTree) |
||||
self.treeView.updateNodeFromJSON(folderpath, fileTree, true) |
||||
if (!self.treeView.isExpanded(folderpath)) self.treeView.expand(folderpath) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
function fileRemoved (filepath) { |
||||
const label = self.treeView.labelAt(filepath) |
||||
filepath = filepath.split('/').slice(0, -1).join('/') |
||||
|
||||
if (label && label.parentElement) { |
||||
label.parentElement.removeChild(label) |
||||
} |
||||
|
||||
self.updatePath(filepath) |
||||
} |
||||
|
||||
function fileRenamed (oldName, newName, isFolder) { |
||||
fileRemoved(oldName) |
||||
fileAdded(newName) |
||||
} |
||||
|
||||
// make interface and register to nodeClick, leafClick
|
||||
self.treeView = new Treeview({ |
||||
extractData: function extractData (value, tree, key) { |
||||
var newValue = {} |
||||
// var isReadOnly = false
|
||||
var isFile = false |
||||
Object.keys(value).filter(function keep (x) { |
||||
if (x === '/content') isFile = true |
||||
if (x[0] !== '/') return true |
||||
}).forEach(function (x) { newValue[x] = value[x] }) |
||||
return { |
||||
path: (tree || {}).path ? tree.path + '/' + key : key, |
||||
children: isFile ? undefined |
||||
: value instanceof Array ? value.map((item, index) => ({ |
||||
key: index, value: item |
||||
})) : value instanceof Object ? Object.keys(value).map(subkey => ({ |
||||
key: subkey, value: value[subkey] |
||||
})) : undefined |
||||
} |
||||
}, |
||||
formatSelf: function formatSelf (key, data, li) { |
||||
const isRoot = data.path === self.files.type |
||||
const isFolder = !!data.children |
||||
return yo` |
||||
<div class="${css.items}"> |
||||
<span |
||||
title="${data.path}" |
||||
class="${css.label} ${!isRoot ? !isFolder ? css.leaf : css.folder : ''}" |
||||
data-path="${data.path}" |
||||
style="${isRoot ? 'font-weight:bold;' : ''}" |
||||
onkeydown=${editModeOff} |
||||
onblur=${editModeOff} |
||||
> |
||||
${key.split('/').pop()} |
||||
</span> |
||||
${isRoot ? self.renderMenuItems() : ''} |
||||
</div> |
||||
` |
||||
} |
||||
}) |
||||
|
||||
/** |
||||
* Extracts first two folders as a subpath from the path. |
||||
**/ |
||||
function extractExternalFolder (path) { |
||||
const firstSlIndex = path.indexOf('/', 1) |
||||
if (firstSlIndex === -1) return '' |
||||
const secondSlIndex = path.indexOf('/', firstSlIndex + 1) |
||||
if (secondSlIndex === -1) return '' |
||||
return path.substring(0, secondSlIndex) |
||||
} |
||||
|
||||
self.treeView.event.register('nodeRightClick', function (key, data, label, event) { |
||||
if (self.files.readonly) return |
||||
if (key === self.files.type) return |
||||
MENU_HANDLE && MENU_HANDLE.hide(null, true) |
||||
const actions = {} |
||||
const provider = self._deps.fileManager.fileProviderOf(key) |
||||
actions['Create File'] = () => self.createNewFile(key) |
||||
actions['Create Folder'] = () => self.createNewFolder(key) |
||||
// @todo(#2386) not fully implemented. Readd later when fixed
|
||||
if (provider.isExternalFolder(key)) { |
||||
/* actions['Discard changes'] = () => { |
||||
modalDialogCustom.confirm( |
||||
'Discard changes', |
||||
'Are you sure you want to discard all your changes?', |
||||
() => { self.files.discardChanges(key) }, |
||||
() => {} |
||||
) |
||||
} */ |
||||
} else { |
||||
const folderPath = extractExternalFolder(key) |
||||
actions.Rename = () => { |
||||
if (self.files.isReadOnly(key)) { return tooltip('cannot rename folder. ' + self.files.type + ' is a read only explorer') } |
||||
var name = label.querySelector('span[data-path="' + key + '"]') |
||||
if (name) editModeOn(name) |
||||
} |
||||
actions.Delete = () => { |
||||
if (self.files.isReadOnly(key)) { return tooltip('cannot delete folder. ' + self.files.type + ' is a read only explorer') } |
||||
const currentFoldername = extractNameFromKey(key) |
||||
|
||||
modalDialogCustom.confirm('Confirm to delete folder', `Are you sure you want to delete ${currentFoldername} folder?`, |
||||
async () => { |
||||
const fileManager = self._deps.fileManager |
||||
const removeFolder = await fileManager.remove(key) |
||||
|
||||
if (!removeFolder) { |
||||
tooltip(`failed to remove ${key}. Make sure the directory is empty before removing it.`) |
||||
} |
||||
}, () => {}) |
||||
} |
||||
if (folderPath === 'browser/gists') { |
||||
actions['Push changes to gist'] = () => { |
||||
const id = key.substr(key.lastIndexOf('/') + 1, key.length - 1) |
||||
modalDialogCustom.confirm( |
||||
'Push back to Gist', |
||||
'Are you sure you want to push all your changes back to Gist?', |
||||
() => { self.toGist(id) }, |
||||
() => {} |
||||
) |
||||
} |
||||
} |
||||
} |
||||
MENU_HANDLE = contextMenu(event, actions) |
||||
}) |
||||
|
||||
self.treeView.event.register('leafRightClick', function (key, data, label, event) { |
||||
if (key === self.files.type) return |
||||
MENU_HANDLE && MENU_HANDLE.hide(null, true) |
||||
const actions = {} |
||||
const provider = self._deps.fileManager.fileProviderOf(key) |
||||
if (!provider.isExternalFolder(key)) { |
||||
actions['Create Folder'] = () => self.createNewFolder(self._deps.fileManager.extractPathOf(key)) |
||||
actions.Rename = () => { |
||||
if (self.files.isReadOnly(key)) { return tooltip('cannot rename file. ' + self.files.type + ' is a read only explorer') } |
||||
var name = label.querySelector('span[data-path="' + key + '"]') |
||||
if (name) editModeOn(name) |
||||
} |
||||
actions.Delete = () => { |
||||
if (self.files.isReadOnly(key)) { return tooltip('cannot delete file. ' + self.files.type + ' is a read only explorer') } |
||||
const currentFilename = extractNameFromKey(key) |
||||
|
||||
modalDialogCustom.confirm( |
||||
'Delete file', `Are you sure you want to delete ${currentFilename} file?`, |
||||
async () => { |
||||
const fileManager = self._deps.fileManager |
||||
const removeFile = await fileManager.remove(key) |
||||
|
||||
if (!removeFile) { |
||||
tooltip(`Failed to remove file ${key}.`) |
||||
} |
||||
}, |
||||
() => {} |
||||
) |
||||
} |
||||
if (key.endsWith('.js')) { |
||||
actions.Run = async () => { |
||||
provider.get(key, (error, content) => { |
||||
if (error) return console.log(error) |
||||
plugin.call('scriptRunner', 'execute', content) |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
MENU_HANDLE = contextMenu(event, actions) |
||||
}) |
||||
|
||||
self.treeView.event.register('leafClick', function (key, data, label) { |
||||
self.events.trigger('focus', [key]) |
||||
}) |
||||
|
||||
self.treeView.event.register('nodeClick', function (path, childrenContainer) { |
||||
if (!childrenContainer) return |
||||
if (childrenContainer.style.display === 'none') return |
||||
self.updatePath(path) |
||||
}) |
||||
|
||||
// register to main app, trigger when the current file in the editor changed
|
||||
self._deps.fileManager.events.on('currentFileChanged', (newFile) => { |
||||
const provider = self._deps.fileManager.fileProviderOf(newFile) |
||||
if (self.focusElement && self.focusPath !== newFile) { |
||||
self.focusElement.classList.remove('bg-secondary') |
||||
self.focusElement = null |
||||
self.focusPath = null |
||||
} |
||||
if (provider && (provider.type === files.type)) { |
||||
self.focusElement = self.treeView.labelAt(newFile) |
||||
if (self.focusElement) { |
||||
self.focusElement.classList.add('bg-secondary') |
||||
self.focusPath = newFile |
||||
} |
||||
} |
||||
}) |
||||
|
||||
self._deps.fileManager.events.on('noFileSelected', () => { |
||||
if (self.focusElement) { |
||||
self.focusElement.classList.remove('bg-secondary') |
||||
self.focusElement = null |
||||
self.focusPath = null |
||||
} |
||||
}) |
||||
|
||||
var textUnderEdit = null |
||||
|
||||
function selectElementContents (el) { |
||||
var range = document.createRange() |
||||
range.selectNodeContents(el) |
||||
var sel = window.getSelection() |
||||
sel.removeAllRanges() |
||||
sel.addRange(range) |
||||
} |
||||
|
||||
function editModeOn (label) { |
||||
textUnderEdit = label.innerText |
||||
label.setAttribute('contenteditable', true) |
||||
label.classList.add('bg-light') |
||||
label.focus() |
||||
selectElementContents(label) |
||||
} |
||||
|
||||
function editModeOff (event) { |
||||
const label = this |
||||
|
||||
const isFolder = label.className.indexOf('folder') !== -1 |
||||
function rename () { |
||||
var newPath = label.dataset.path |
||||
newPath = newPath.split('/') |
||||
newPath[newPath.length - 1] = label.innerText |
||||
newPath = newPath.join('/') |
||||
if (label.innerText === '') { |
||||
modalDialogCustom.alert('File name cannot be empty') |
||||
label.innerText = textUnderEdit |
||||
} else if (helper.checkSpecialChars(label.innerText)) { |
||||
modalDialogCustom.alert('Special characters are not allowed') |
||||
label.innerText = textUnderEdit |
||||
} else { |
||||
files.exists(newPath, (error, exist) => { |
||||
if (error) return modalDialogCustom.alert('Unexpected error while renaming: ' + error) |
||||
if (!exist) { |
||||
files.rename(label.dataset.path, newPath, isFolder) |
||||
} else { |
||||
modalDialogCustom.alert('File already exists.') |
||||
label.innerText = textUnderEdit |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
if (event.which === 13) event.preventDefault() |
||||
if ((event.type === 'blur' || event.which === 13) && label.getAttribute('contenteditable')) { |
||||
var save = textUnderEdit !== label.innerText |
||||
if (save) { |
||||
modalDialogCustom.confirm( |
||||
'Confirm to rename a ' + (isFolder ? 'folder' : 'file'), |
||||
'Are you sure you want to rename ' + textUnderEdit + '?', |
||||
() => { rename() }, |
||||
() => { label.innerText = textUnderEdit } |
||||
) |
||||
} |
||||
label.removeAttribute('contenteditable') |
||||
label.classList.remove('bg-light') |
||||
} |
||||
} |
||||
} |
||||
|
||||
fileExplorer.prototype.updatePath = function (path) { |
||||
this.files.resolveDirectory(path, (error, fileTree) => { |
||||
if (error) console.error(error) |
||||
if (!fileTree) return |
||||
var newTree = normalize(path, fileTree) |
||||
this.treeView.updateNodeFromJSON(path, newTree, true) |
||||
}) |
||||
} |
||||
|
||||
fileExplorer.prototype.hide = function () { |
||||
if (this.container) this.container.style.display = 'none' |
||||
} |
||||
|
||||
fileExplorer.prototype.show = function () { |
||||
if (this.container) this.container.style.display = 'block' |
||||
} |
||||
|
||||
fileExplorer.prototype.init = function () { |
||||
this.container = yo`<div></div>` |
||||
return this.container |
||||
} |
||||
|
||||
fileExplorer.prototype.publishToGist = function () { |
||||
modalDialogCustom.confirm( |
||||
'Create a public gist', |
||||
'Are you sure you want to publish all your files in browser directory anonymously as a public gist on github.com? Note: this will not include directories.', |
||||
() => { this.toGist() } |
||||
) |
||||
} |
||||
|
||||
fileExplorer.prototype.uploadFile = function (event) { |
||||
// TODO The file explorer is merely a view on the current state of
|
||||
// the files module. Please ask the user here if they want to overwrite
|
||||
// a file and then just use `files.add`. The file explorer will
|
||||
// pick that up via the 'fileAdded' event from the files module.
|
||||
|
||||
const self = this |
||||
|
||||
;[...event.target.files].forEach((file) => { |
||||
const files = this.files |
||||
function loadFile () { |
||||
var fileReader = new FileReader() |
||||
fileReader.onload = async function (event) { |
||||
if (helper.checkSpecialChars(file.name)) { |
||||
modalDialogCustom.alert('Special characters are not allowed') |
||||
return |
||||
} |
||||
var success = await files.set(name, event.target.result) |
||||
if (!success) { |
||||
modalDialogCustom.alert('Failed to create file ' + name) |
||||
} else { |
||||
self.events.trigger('focus', [name]) |
||||
} |
||||
} |
||||
fileReader.readAsText(file) |
||||
} |
||||
var name = files.type + '/' + file.name |
||||
files.exists(name, (error, exist) => { |
||||
if (error) console.log(error) |
||||
if (!exist) { |
||||
loadFile() |
||||
} else { |
||||
modalDialogCustom.confirm('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() }) |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
fileExplorer.prototype.toGist = function (id) { |
||||
const proccedResult = function (error, data) { |
||||
if (error) { |
||||
modalDialogCustom.alert('Failed to manage gist: ' + error) |
||||
console.log('Failed to manage gist: ' + error) |
||||
} else { |
||||
if (data.html_url) { |
||||
modalDialogCustom.confirm('Gist is ready', `The gist is at ${data.html_url}. Would you like to open it in a new window?`, () => { |
||||
window.open(data.html_url, '_blank') |
||||
}) |
||||
} else { |
||||
modalDialogCustom.alert(data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t')) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* This function is to get the original content of given gist |
||||
* @params id is the gist id to fetch |
||||
*/ |
||||
async function getOriginalFiles (id) { |
||||
if (!id) { |
||||
return [] |
||||
} |
||||
|
||||
const url = `https://api.github.com/gists/${id}` |
||||
const res = await fetch(url) |
||||
const data = await res.json() |
||||
return data.files || [] |
||||
} |
||||
|
||||
// If 'id' is not defined, it is not a gist update but a creation so we have to take the files from the browser explorer.
|
||||
const folder = id ? '/gists/' + id : '/' |
||||
this.packageFiles(this.files, folder, (error, packaged) => { |
||||
if (error) { |
||||
console.log(error) |
||||
modalDialogCustom.alert('Failed to create gist: ' + error.message) |
||||
} else { |
||||
// check for token
|
||||
var tokenAccess = this._deps.config.get('settings/gist-access-token') |
||||
if (!tokenAccess) { |
||||
modalDialogCustom.alert( |
||||
'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.' |
||||
) |
||||
} else { |
||||
const description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' + |
||||
queryParams.get().version + '&optimize=' + queryParams.get().optimize + '&runs=' + queryParams.get().runs + '&gist=' |
||||
const gists = new Gists({ token: tokenAccess }) |
||||
|
||||
if (id) { |
||||
const originalFileList = getOriginalFiles(id) |
||||
// Telling the GIST API to remove files
|
||||
const updatedFileList = Object.keys(packaged) |
||||
const allItems = Object.keys(originalFileList) |
||||
.filter(fileName => updatedFileList.indexOf(fileName) === -1) |
||||
.reduce((acc, deleteFileName) => ({ |
||||
...acc, |
||||
[deleteFileName]: null |
||||
}), originalFileList) |
||||
// adding new files
|
||||
updatedFileList.forEach((file) => { |
||||
const _items = file.split('/') |
||||
const _fileName = _items[_items.length - 1] |
||||
allItems[_fileName] = packaged[file] |
||||
}) |
||||
|
||||
tooltip('Saving gist (' + id + ') ...') |
||||
gists.edit({ |
||||
description: description, |
||||
public: true, |
||||
files: allItems, |
||||
id: id |
||||
}, (error, result) => { |
||||
proccedResult(error, result) |
||||
if (!error) { |
||||
for (const key in allItems) { |
||||
if (allItems[key] === null) delete allItems[key] |
||||
} |
||||
} |
||||
}) |
||||
} else { |
||||
// id is not existing, need to create a new gist
|
||||
tooltip('Creating a new gist ...') |
||||
gists.create({ |
||||
description: description, |
||||
public: true, |
||||
files: packaged |
||||
}, (error, result) => { |
||||
proccedResult(error, result) |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
// return all the files, except the temporary/readonly ones..
|
||||
fileExplorer.prototype.packageFiles = function (filesProvider, directory, callback) { |
||||
const ret = {} |
||||
filesProvider.resolveDirectory(directory, (error, files) => { |
||||
if (error) callback(error) |
||||
else { |
||||
async.eachSeries(Object.keys(files), (path, cb) => { |
||||
if (filesProvider.isDirectory(path)) { |
||||
cb() |
||||
} else { |
||||
filesProvider.get(path, (error, content) => { |
||||
if (error) return cb(error) |
||||
if (/^\s+$/.test(content) || !content.length) { |
||||
content = '// this line is added to create a gist. Empty file is not allowed.' |
||||
} |
||||
ret[path] = { content } |
||||
cb() |
||||
}) |
||||
} |
||||
}, (error) => { |
||||
callback(error, ret) |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
fileExplorer.prototype.createNewFile = function (parentFolder = '/') { |
||||
const self = this |
||||
modalDialogCustom.prompt('Create new file', 'File Name (e.g Untitled.sol)', 'Untitled.sol', (input) => { |
||||
if (!input) input = 'New file' |
||||
helper.createNonClashingName(parentFolder + '/' + input, self.files, async (error, newName) => { |
||||
if (error) return tooltip('Failed to create file ' + newName + ' ' + error) |
||||
const fileManager = self._deps.fileManager |
||||
const createFile = await fileManager.writeFile(newName, '') |
||||
|
||||
if (!createFile) { |
||||
tooltip('Failed to create file ' + newName) |
||||
} else { |
||||
await fileManager.open(newName) |
||||
if (newName.includes('_test.sol')) { |
||||
self.events.trigger('newTestFileCreated', [newName]) |
||||
} |
||||
} |
||||
}) |
||||
}, null, true) |
||||
} |
||||
|
||||
fileExplorer.prototype.createNewFolder = function (parentFolder) { |
||||
const self = this |
||||
modalDialogCustom.prompt('Create new folder', '', 'New folder', (input) => { |
||||
if (!input) { |
||||
return tooltip('Failed to create folder. The name can not be empty') |
||||
} |
||||
|
||||
const currentPath = !parentFolder ? self._deps.fileManager.currentPath() : parentFolder |
||||
let newName = currentPath ? currentPath + '/' + input : self.files.type + '/' + input |
||||
|
||||
newName = newName + '/' |
||||
self.files.exists(newName, (error, exist) => { |
||||
if (error) return tooltip('Unexpected error while creating folder: ' + error) |
||||
if (!exist) { |
||||
self.files.set(newName, '') |
||||
} else { |
||||
tooltip('Folder already exists.', () => {}) |
||||
} |
||||
}) |
||||
}, null, true) |
||||
} |
||||
|
||||
fileExplorer.prototype.renderMenuItems = function () { |
||||
let items = '' |
||||
if (this.menuItems) { |
||||
items = this.menuItems.map(({ action, title, icon }) => { |
||||
if (action === 'uploadFile') { |
||||
return yo` |
||||
<label |
||||
id=${action} |
||||
data-id="fileExplorerUploadFile${action}" |
||||
class="${icon} mb-0 ${css.newFile}" |
||||
title="${title}" |
||||
> |
||||
<input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onchange=${(event) => { |
||||
event.stopPropagation() |
||||
this.uploadFile(event) |
||||
}} multiple /> |
||||
</label> |
||||
` |
||||
} else { |
||||
return yo` |
||||
<span |
||||
id=${action} |
||||
data-id="fileExplorerNewFile${action}" |
||||
onclick=${(event) => { event.stopPropagation(); this[action]() }} |
||||
class="newFile ${icon} ${css.newFile}" |
||||
title=${title} |
||||
> |
||||
</span> |
||||
` |
||||
} |
||||
}) |
||||
} |
||||
return yo`<span class=" ${css.menu}">${items}</span>` |
||||
} |
||||
|
||||
fileExplorer.prototype.ensureRoot = function (cb) { |
||||
cb = cb || (() => {}) |
||||
var self = this |
||||
if (self.element) return cb() |
||||
const root = {} |
||||
root[this.files.type] = {} |
||||
var element = self.treeView.render(root, false) |
||||
element.classList.add(css.fileexplorer) |
||||
element.events = self.events |
||||
element.api = self.api |
||||
self.container.appendChild(element) |
||||
self.element = element |
||||
if (cb) cb() |
||||
self.treeView.expand(self.files.type) |
||||
} |
||||
|
||||
function normalize (path, filesList) { |
||||
var prefix = path.split('/')[0] |
||||
var newList = {} |
||||
Object.keys(filesList).forEach(key => { |
||||
newList[prefix + '/' + key] = filesList[key].isDirectory ? {} : { '/content': true } |
||||
}) |
||||
return newList |
||||
} |
||||
|
||||
module.exports = fileExplorer |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue