🎨 Add site and rename app
This commit is contained in:
@@ -0,0 +1,752 @@
|
||||
class FileTree
|
||||
{
|
||||
/**
|
||||
* Create tree and add ./ folder
|
||||
* @param {Object} _options
|
||||
* @param {Boolean} _options.auto_wash - Should clean empty folder on removes
|
||||
*/
|
||||
constructor(_options = {})
|
||||
{
|
||||
// Options
|
||||
this.autoWash = typeof _options.autoWash === 'undefined' ? false : _options.autoWash
|
||||
this.filesCount = 0
|
||||
this.foldersCount = 0
|
||||
this.allCount = 0
|
||||
|
||||
// Set up
|
||||
this.folders = []
|
||||
this.addFolder('.', {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean path
|
||||
* @param {String} _path - Path to clean
|
||||
* @return {String} Cleaned path
|
||||
*/
|
||||
cleanPath(_path = '')
|
||||
{
|
||||
// Errors
|
||||
if(typeof _path !== 'string')
|
||||
{
|
||||
console.warn('cleanPath: _path should be a string')
|
||||
return false
|
||||
}
|
||||
|
||||
let path = _path
|
||||
|
||||
// Trim
|
||||
path = path.trim()
|
||||
|
||||
// Repeating `/`
|
||||
path = path.replace(/\/+/g, '/')
|
||||
|
||||
// Ending `/`
|
||||
path = path.replace(/\/$/, '')
|
||||
|
||||
// Starting `/`
|
||||
path = path.replace(/^\//, '')
|
||||
|
||||
// Missing starting `./`
|
||||
if(path !== '.' && path.search('./') !== 0)
|
||||
{
|
||||
path = './' + path
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a folder
|
||||
* Can create nested folder in one path
|
||||
* @param {String} _path - Path to the folder (start with `./`)
|
||||
* @param {Object} _data - Properties to add to the folder
|
||||
* @returns {Object} Created folder
|
||||
*/
|
||||
addFolder(_path = '', _data = {})
|
||||
{
|
||||
// Errors
|
||||
if(typeof _path !== 'string')
|
||||
{
|
||||
console.warn('addFolder: _path should be a string')
|
||||
return false
|
||||
}
|
||||
if(typeof _data !== 'object')
|
||||
{
|
||||
console.warn('addFolder: _data should be an object')
|
||||
return false
|
||||
}
|
||||
|
||||
// Set up
|
||||
const path = this.cleanPath(_path)
|
||||
const pathParts = path.split('/')
|
||||
|
||||
let folders = this.folders
|
||||
let folder = null
|
||||
|
||||
// Each path part
|
||||
for(const _part of pathParts)
|
||||
{
|
||||
const index = folders.findIndex((folder) => _part === folder.name)
|
||||
|
||||
// Already exist
|
||||
if(index !== -1)
|
||||
{
|
||||
folder = folders[index]
|
||||
folders = folder.folders
|
||||
}
|
||||
|
||||
// Folder doesn't exist
|
||||
else
|
||||
{
|
||||
// Create folder
|
||||
folder = {
|
||||
folders: [],
|
||||
files: [],
|
||||
name: _part
|
||||
}
|
||||
|
||||
// Add data
|
||||
for(const dataKey in _data)
|
||||
{
|
||||
const data = _data[dataKey]
|
||||
folder[dataKey] = data
|
||||
}
|
||||
|
||||
// Save
|
||||
folders.push(folder)
|
||||
folders = folder.folders
|
||||
}
|
||||
}
|
||||
|
||||
// Update counts
|
||||
this.updateCounts()
|
||||
|
||||
// Return
|
||||
return folder
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file
|
||||
* Will create folders if needed
|
||||
* @param {String} _path - Path to the folder (start with `./`)
|
||||
* @param {Object} _data - Properties to add to the file
|
||||
* @returns {Object} Created file
|
||||
*/
|
||||
addFile(_path = '', _data = {})
|
||||
{
|
||||
// Errors
|
||||
if(typeof _path !== 'string')
|
||||
{
|
||||
console.warn('addFile: _path should be a string')
|
||||
return false
|
||||
}
|
||||
if(typeof _data !== 'object')
|
||||
{
|
||||
console.warn('addFile: _data should be an object')
|
||||
return false
|
||||
}
|
||||
|
||||
// Set up
|
||||
const path = this.cleanPath(_path)
|
||||
const pathParts = path.split('/')
|
||||
const filePart = pathParts.pop()
|
||||
|
||||
// Create folder
|
||||
const folder = this.addFolder(pathParts.join('/'))
|
||||
|
||||
// Create file
|
||||
const file = {
|
||||
name: filePart
|
||||
}
|
||||
|
||||
// Add data
|
||||
for(const dataKey in _data)
|
||||
{
|
||||
const data = _data[dataKey]
|
||||
file[dataKey] = data
|
||||
}
|
||||
|
||||
// Save
|
||||
folder.files.push(file)
|
||||
|
||||
// Update counts
|
||||
this.updateCounts()
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove folder
|
||||
* Will delete contained folders and contained files
|
||||
* @param {String} _path - Folder to delete (start with `./`)
|
||||
* @returns {Boolean} Folder deleted or not
|
||||
*/
|
||||
removeFolder(_path = '')
|
||||
{
|
||||
// Recursive emptying
|
||||
const emptyFolder = function(folder)
|
||||
{
|
||||
// Delete folders
|
||||
for(const _folderKey in folder.folders)
|
||||
{
|
||||
const _folder = folder.folders[_folderKey]
|
||||
|
||||
emptyFolder(_folder)
|
||||
|
||||
folder.folders.splice(_folderKey, 1)
|
||||
|
||||
// Callback
|
||||
if(typeof _folder.onRemove === 'function')
|
||||
{
|
||||
_folder.onRemove.apply(this, [_folder])
|
||||
}
|
||||
}
|
||||
|
||||
// Delete files
|
||||
for(const _fileKey in folder.files)
|
||||
{
|
||||
const _file = folder.files[_fileKey]
|
||||
|
||||
folder.files.splice(_fileKey, 1)
|
||||
|
||||
// Callback
|
||||
if(typeof _file.onRemove === 'function')
|
||||
{
|
||||
_file.onRemove.apply(this, [_file])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Errors
|
||||
if(typeof _path !== 'string')
|
||||
{
|
||||
console.warn('removeFolder: _path should be a string')
|
||||
return false
|
||||
}
|
||||
|
||||
// Set up
|
||||
const path = this.cleanPath(_path)
|
||||
const pathParts = path.split('/')
|
||||
const folderPart = pathParts.pop()
|
||||
|
||||
let folders = this.folders
|
||||
let folder = null
|
||||
|
||||
// Each path part
|
||||
for(const _part of pathParts)
|
||||
{
|
||||
const index = folders.findIndex((folder) => _part === folder.name)
|
||||
|
||||
// Found
|
||||
if(index !== -1)
|
||||
{
|
||||
folder = folders[index]
|
||||
folders = folder.folders
|
||||
}
|
||||
|
||||
// Not found
|
||||
else
|
||||
{
|
||||
folder = null
|
||||
folders = null
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Found
|
||||
const index = folders.findIndex((folder) => folderPart === folder.name)
|
||||
if(folders && index !== -1)
|
||||
{
|
||||
const folder = folders[index]
|
||||
|
||||
// Delete
|
||||
emptyFolder(folder)
|
||||
folders.splice(index, 1)
|
||||
|
||||
// Callback
|
||||
if(typeof folder.onRemove === 'function')
|
||||
{
|
||||
folder.onRemove.apply(this, [folder])
|
||||
}
|
||||
|
||||
// Auto wash
|
||||
if(this.autoWash)
|
||||
{
|
||||
this.removeEmptyFolders()
|
||||
}
|
||||
|
||||
// Update counts
|
||||
this.updateCounts()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove file
|
||||
* Will delete contained folders and contained files
|
||||
* @param {String} _path - File to delete (start with `./`)
|
||||
* @returns {Boolean} File deleted or not
|
||||
*/
|
||||
removeFile(_path = '')
|
||||
{
|
||||
// Errors
|
||||
if(typeof _path !== 'string')
|
||||
{
|
||||
console.warn('removeFile: _path should be a string')
|
||||
return false
|
||||
}
|
||||
|
||||
// Set up
|
||||
const path = this.cleanPath(_path)
|
||||
const pathParts = path.split('/')
|
||||
const filePart = pathParts.pop()
|
||||
|
||||
let folders = this.folders
|
||||
let folder = null
|
||||
|
||||
// Each path part
|
||||
for(const _part of pathParts)
|
||||
{
|
||||
const index = folders.findIndex((folder) => _part === folder.name)
|
||||
|
||||
// Found
|
||||
if(index !== -1)
|
||||
{
|
||||
folder = folders[index]
|
||||
folders = folder.folders
|
||||
}
|
||||
|
||||
// Not found
|
||||
else
|
||||
{
|
||||
folder = null
|
||||
folders = null
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Folder found
|
||||
if(folders && folder)
|
||||
{
|
||||
const index = folder.files.findIndex((file) => filePart === file.name)
|
||||
|
||||
// File found
|
||||
if(index !== -1)
|
||||
{
|
||||
const file = folder.files[index]
|
||||
|
||||
// Delete
|
||||
folder.files.splice(index, 1)
|
||||
|
||||
// Auto wash
|
||||
if(this.autoWash)
|
||||
{
|
||||
this.removeEmptyFolders()
|
||||
}
|
||||
|
||||
// Callback
|
||||
if(typeof file.onRemove === 'function')
|
||||
{
|
||||
file.onRemove.apply(this, [file])
|
||||
}
|
||||
|
||||
// Update counts
|
||||
this.updateCounts()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file
|
||||
* @param {String} _path - Path to file
|
||||
* @returns {Object} File
|
||||
*/
|
||||
getFile(_path = '')
|
||||
{
|
||||
// Errors
|
||||
if(typeof _path !== 'string')
|
||||
{
|
||||
console.warn('getFile: _path should be a string')
|
||||
return false
|
||||
}
|
||||
|
||||
// Set up
|
||||
const path = this.cleanPath(_path)
|
||||
const pathParts = path.split('/')
|
||||
const filePart = pathParts.pop()
|
||||
|
||||
let folders = this.folders
|
||||
let folder = null
|
||||
|
||||
// Each path part
|
||||
for(const _part of pathParts)
|
||||
{
|
||||
const index = folders.findIndex((folder) => _part === folder.name)
|
||||
|
||||
// Found
|
||||
if(index !== -1)
|
||||
{
|
||||
folder = folders[index]
|
||||
folders = folder.folders
|
||||
}
|
||||
|
||||
// Not found
|
||||
else
|
||||
{
|
||||
folder = null
|
||||
folders = null
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Folder found
|
||||
if(folders && folder)
|
||||
{
|
||||
const index = folder.files.findIndex((file) => filePart === file.name)
|
||||
|
||||
if(index !== -1)
|
||||
{
|
||||
const file = folder.files[index]
|
||||
return file
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get folder
|
||||
* @param {String} _path - Path to folder
|
||||
* @returns {Object} Folder
|
||||
*/
|
||||
getFolder(_path = '')
|
||||
{
|
||||
// Errors
|
||||
if(typeof _path !== 'string')
|
||||
{
|
||||
console.warn('getFolder: _path should be a string')
|
||||
return false
|
||||
}
|
||||
|
||||
// Set up
|
||||
const path = this.cleanPath(_path)
|
||||
const pathParts = path.split('/')
|
||||
|
||||
let folders = this.folders
|
||||
let folder = null
|
||||
|
||||
// Each path part
|
||||
for(const _part of pathParts)
|
||||
{
|
||||
const index = folders.findIndex((folder) => _part === folder.name)
|
||||
|
||||
// Found
|
||||
if(index !== -1)
|
||||
{
|
||||
folder = folders[index]
|
||||
folders = folder.folders
|
||||
}
|
||||
|
||||
// Folder doesn't exist
|
||||
else
|
||||
{
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Return
|
||||
return folder
|
||||
}
|
||||
|
||||
/**
|
||||
* Browse every folders and remove empty ones
|
||||
* @return {Number} Number of removed folders
|
||||
*/
|
||||
removeEmptyFolders()
|
||||
{
|
||||
// Set up
|
||||
let removedCount = 0
|
||||
|
||||
// Recursive remove
|
||||
const canRemoveFolder = function(folder)
|
||||
{
|
||||
// Each folder inside current folder
|
||||
for(const _folderKey in folder.folders)
|
||||
{
|
||||
// Try to remove folder
|
||||
const _folder = folder.folders[_folderKey]
|
||||
const canRemove = canRemoveFolder(_folder)
|
||||
|
||||
// Remove folder
|
||||
if(canRemove)
|
||||
{
|
||||
removedCount++
|
||||
|
||||
// Delete
|
||||
folder.folders.splice(_folderKey, 1)
|
||||
|
||||
// Callback
|
||||
if(typeof _folder.onRemove === 'function')
|
||||
{
|
||||
_folder.onRemove.apply(this, [_folder])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Can be removed
|
||||
const folderKeys = Object.keys(folder.folders)
|
||||
const filesKeys = Object.keys(folder.files)
|
||||
|
||||
if(folderKeys.length === 0 && filesKeys.length === 0)
|
||||
{
|
||||
return true
|
||||
}
|
||||
else
|
||||
{
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Try from ./
|
||||
canRemoveFolder(this.folders[0])
|
||||
|
||||
// Update counts
|
||||
this.updateCounts()
|
||||
|
||||
return removedCount
|
||||
}
|
||||
|
||||
/**
|
||||
* Update counts
|
||||
*/
|
||||
updateCounts()
|
||||
{
|
||||
let filesCount = 0
|
||||
let foldersCount = 0
|
||||
|
||||
// Recursive emptying
|
||||
const traverseFolder = function(folder)
|
||||
{
|
||||
// Each folder
|
||||
for(const _folderKey in folder.folders)
|
||||
{
|
||||
const _folder = folder.folders[_folderKey]
|
||||
|
||||
traverseFolder(_folder)
|
||||
|
||||
foldersCount++
|
||||
}
|
||||
|
||||
// Each file
|
||||
for(const _fileKey in folder.files)
|
||||
{
|
||||
filesCount++
|
||||
}
|
||||
}
|
||||
|
||||
traverseFolder(this.folders[0])
|
||||
|
||||
this.filesCount = filesCount
|
||||
this.foldersCount = foldersCount
|
||||
this.allCount = this.filesCount + this.foldersCount
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the tree in ASCII (├ ─ │ └)
|
||||
* @param {Boolean} _log - Directly log to console
|
||||
* @param {Boolean} _colored - Colored tree (only work well in Chrome)
|
||||
* @return {String} Tree
|
||||
*/
|
||||
describe(_log = false, _colored = false)
|
||||
{
|
||||
// Set up
|
||||
const depth = 0
|
||||
const styles = []
|
||||
|
||||
let stringTree = ''
|
||||
|
||||
const addToString = function(value = '', type = null)
|
||||
{
|
||||
if(_colored)
|
||||
{
|
||||
stringTree += '%c'
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case 'structure':
|
||||
styles.push('color:#999;')
|
||||
break
|
||||
|
||||
case 'folder':
|
||||
styles.push('color:#999;')
|
||||
break
|
||||
case 'file':
|
||||
styles.push('color:#333;font-weight:bold;')
|
||||
break
|
||||
|
||||
default:
|
||||
styles.push('')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
stringTree += value
|
||||
}
|
||||
|
||||
// Recursive describe
|
||||
const describeFolder = function(folder, depth, last = [])
|
||||
{
|
||||
// Each folders
|
||||
for(let i = 0; i < folder.folders.length; i++)
|
||||
{
|
||||
// Set up
|
||||
const _folder = folder.folders[i]
|
||||
|
||||
// Add to tree string
|
||||
addToString('\n')
|
||||
|
||||
for(let j = 0; j < depth; j++)
|
||||
{
|
||||
if(j === depth - 1)
|
||||
{
|
||||
if(i === folder.folders.length - 1 && folder.files.length === 0)
|
||||
{
|
||||
addToString(' └', 'structure')
|
||||
}
|
||||
else
|
||||
{
|
||||
addToString(' ├', 'structure')
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(last[j])
|
||||
{
|
||||
addToString(' ', 'structure')
|
||||
}
|
||||
else
|
||||
{
|
||||
addToString(' │', 'structure')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addToString('─', 'structure')
|
||||
addToString(_folder.name, 'folder')
|
||||
addToString('/', 'structure')
|
||||
|
||||
// Last
|
||||
last.push(i === folder.folders.length - 1 && folder.files.length === 0)
|
||||
|
||||
// Continue
|
||||
describeFolder(_folder, depth + 1, last)
|
||||
}
|
||||
|
||||
// Each files
|
||||
for(let i = 0; i < folder.files.length; i++)
|
||||
{
|
||||
// Set up
|
||||
const _file = folder.files[i]
|
||||
|
||||
// Add to tree string
|
||||
stringTree += '\n'
|
||||
|
||||
for(let j = 0; j < depth; j++)
|
||||
{
|
||||
if(j === depth - 1)
|
||||
{
|
||||
if(i === folder.files.length - 1)
|
||||
{
|
||||
addToString(' └', 'structure')
|
||||
}
|
||||
else
|
||||
{
|
||||
addToString(' ├', 'structure')
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(last[j])
|
||||
{
|
||||
addToString(' ', 'structure')
|
||||
}
|
||||
else
|
||||
{
|
||||
addToString(' │', 'structure')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addToString('─', 'structure')
|
||||
addToString(_file.name, 'file')
|
||||
}
|
||||
}
|
||||
|
||||
// Describe from ./
|
||||
addToString('.', 'folder')
|
||||
addToString('/', 'structure')
|
||||
describeFolder(this.folders[0], depth + 1)
|
||||
|
||||
// Log
|
||||
if(_log)
|
||||
{
|
||||
console.log(...[stringTree].concat(styles))
|
||||
}
|
||||
|
||||
return stringTree
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// /**
|
||||
// * Tests
|
||||
// */
|
||||
// const filesTree = new FileTree()
|
||||
|
||||
// filesTree.addFolder('./hey/hoy', { active: false, notifs: 0 })
|
||||
// filesTree.addFolder('./hey/hoy/toto', { active: false, notifs: 0 })
|
||||
// filesTree.addFolder('./hey/hoy/tata', { active: false, notifs: 0 })
|
||||
|
||||
// filesTree.addFile('./test-1.txt', { active: false, notifs: 0 })
|
||||
// filesTree.addFile('./hey/hoy/test-2.txt', { active: false, notifs: 0 })
|
||||
// filesTree.addFile('./hey/hoy/test-3.txt', { active: false, notifs: 0 })
|
||||
// filesTree.addFile('./hey/hoy/tata/test-4.txt', { active: false, notifs: 0 })
|
||||
// filesTree.addFile('./hey/hoy/toto/test-5.txt', { active: false, notifs: 0 })
|
||||
|
||||
// filesTree.addFile('./hey/hoy/toto/test-6.txt', {
|
||||
// active: false,
|
||||
// notifs: 0,
|
||||
// onRemove: (file) =>
|
||||
// {
|
||||
// console.log('removed file :', file)
|
||||
// }
|
||||
// })
|
||||
// filesTree.removeFile('./hey/hoy/toto/test-6.txt')
|
||||
|
||||
// filesTree.addFolder('./pwet', {
|
||||
// onRemove: (folder) =>
|
||||
// {
|
||||
// console.log('removed folder :', folder)
|
||||
// }
|
||||
// })
|
||||
// filesTree.addFolder('./pwet/uh', {
|
||||
// onRemove: (folder) =>
|
||||
// {
|
||||
// console.log('removed folder :', folder)
|
||||
// }
|
||||
// })
|
||||
// filesTree.removeFolder('./pwet')
|
||||
|
||||
// filesTree.removeFile('./hey/hoy/test-2.txt')
|
||||
// filesTree.removeFolder('./hey')
|
||||
|
||||
// filesTree.describe(true, true)
|
||||
|
||||
export default FileTree
|
||||
Reference in New Issue
Block a user