Site > Add file tree

This commit is contained in:
brunosimon
2017-08-09 23:32:26 +02:00
parent 02a59ba5be
commit c7cf01747a
23 changed files with 950 additions and 20 deletions
+2 -2
View File
@@ -131,14 +131,14 @@ class Files
const result = {} const result = {}
result.count = this.count result.count = this.count
result.items = {} result.items = []
// Each file // Each file
for(const _filePath in this.items) for(const _filePath in this.items)
{ {
const file = this.items[_filePath] const file = this.items[_filePath]
result.items[_filePath] = file.describe() result.items.push(file.describe())
} }
return result return result
+4 -2
View File
@@ -1,5 +1,6 @@
import Connexion from '@/components/connexion' import Connexion from '@/components/connexion'
import ProjectsHub from '@/components/projects-hub' import ProjectsHub from '@/components/projects-hub'
import Project from '@/components/project'
export default export default
{ {
@@ -8,7 +9,8 @@ export default
components: components:
{ {
Connexion, Connexion,
ProjectsHub ProjectsHub,
Project
}, },
data() data()
@@ -27,7 +29,7 @@ export default
project() project()
{ {
return this.$store.state.currentProject return this.$store.state.project
} }
}, },
@@ -2,7 +2,5 @@
{{ counter }} {{ counter }}
<connexion></connexion> <connexion></connexion>
<projects-hub></projects-hub> <projects-hub></projects-hub>
<div v-if="project"> <project v-if="project"></project>
{{ project.name }}
</div>
</div> </div>
+57 -5
View File
@@ -4,19 +4,71 @@ export default
{ {
name: 'connexion', name: 'connexion',
computed:
{
project()
{
return this.$store.state.project
}
},
watch:
{
project: 'onProjectChange'
},
created() created()
{ {
this.url = `${process.env.NODE_ENV === 'production' ? '' : 'http://localhost:1571'}/projects` const projectsUrl = `${process.env.NODE_ENV === 'production' ? '' : 'http://localhost:1571'}/projects`
this.socket = socketIoClient(this.url) this.projectsSocket = socketIoClient(projectsUrl)
this.socket.on('connect', () => this.projectsSocket.on('connect', () =>
{ {
console.log('connected') // console.log('projects connected')
}) })
this.socket.on('update_projects', (data) => this.projectsSocket.on('update_projects', (data) =>
{ {
// console.log('update_projects', data)
this.$store.commit('updateProjects', data.all) this.$store.commit('updateProjects', data.all)
}) })
},
methods:
{
onProjectChange(value)
{
const projectURL = `${process.env.NODE_ENV === 'production' ? '' : 'http://localhost:1571'}/project/${value.slug}`
this.projectSocket = socketIoClient(projectURL)
this.projectSocket.on('connect', () =>
{
// console.log('project connected')
})
this.projectSocket.on('update_project', (data) =>
{
// console.log('update_project', data)
this.$store.commit('updateFiles', data.files.items)
})
this.projectSocket.on('create_file', (data) =>
{
// console.log('create_file', data)
this.$store.commit('createFile', data)
})
this.projectSocket.on('delete_file', (data) =>
{
// console.log('delete_file', data)
this.$store.commit('deleteFile', data)
})
this.projectSocket.on('createVersion', (data) =>
{
// console.log('createVersion', data)
})
}
} }
} }
@@ -0,0 +1,3 @@
<template src="./template.html"></template>
<script src="./script.js"></script>
<style src="./style.styl" lang="stylus"></style>
@@ -0,0 +1,18 @@
export default
{
name: 'files-tree-file',
props:
{
depth: { type: Number, default: 0 },
content: { type: Object }
},
created()
{
},
methods:
{
}
}
@@ -0,0 +1,5 @@
<div>
<div class="name" :style="{paddingLeft:depth * 20 + 'px'}">
{{ content.name }}
</div>
</div>
@@ -0,0 +1,3 @@
<template src="./template.html"></template>
<script src="./script.js"></script>
<style src="./style.styl" lang="stylus"></style>
@@ -0,0 +1,24 @@
import FilesTreeFile from '@/components/files-tree-file'
export default
{
name: 'files-tree-folder',
components:
{
FilesTreeFile
},
props:
{
depth: { type: Number, default: 0 },
content: { type: Object }
},
created()
{
},
methods:
{
}
}
@@ -0,0 +1,7 @@
<div>
<div class="name" :style="{paddingLeft:depth * 20 + 'px'}">
{{ content.name }}
</div>
<files-tree-folder v-for="folder of content.folders" :key="+ new Date()" :content="folder" :depth="depth + 1"></files-tree-folder>
<files-tree-file v-for="file of content.files" :key="+ new Date()" :content="file" :depth="depth + 1"></files-tree-file>
</div>
+3
View File
@@ -0,0 +1,3 @@
<template src="./template.html"></template>
<script src="./script.js"></script>
<style src="./style.styl" lang="stylus"></style>
+60
View File
@@ -0,0 +1,60 @@
import FilesTreeFile from '@/components/files-tree-file'
import FilesTreeFolder from '@/components/files-tree-folder'
import FileTree from '@/utils/FileTree'
// const fileTree = new FileTree({ autoWash: true })
// fileTree.addFolder('./toto', { hey: 'hoy' })
// fileTree.addFolder('./toto/uhuh', { hey: 'a' })
// fileTree.addFolder('./lorem')
// fileTree.addFile('./test-1.txt')
// fileTree.addFile('./test-2.txt')
// window.setInterval(() =>
// {
// fileTree.addFile('./toto/uhuh/' + new Date())
// fileTree.removeFolder('./lorem')
// fileTree.removeFile('./test-1.txt')
// }, 1000)
export default
{
name: 'files-tree',
components:
{
FilesTreeFile,
FilesTreeFolder
},
data()
{
return {}
},
computed:
{
files()
{
return this.$store.state.files
}
},
watch:
{
files: 'onFilesChange'
},
created()
{
},
methods:
{
onFilesChange(newValue, oldValue)
{
console.log('onFilesChange')
console.log(newValue)
console.log(oldValue)
}
}
}
@@ -0,0 +1,3 @@
<div>
<files-tree-folder v-for="folder of files.folders" :key="+ new Date()" :content="folder" :depth="0"></files-tree-folder>
</div>
+3
View File
@@ -0,0 +1,3 @@
<template src="./template.html"></template>
<script src="./script.js"></script>
<style src="./style.styl" lang="stylus"></style>
+27
View File
@@ -0,0 +1,27 @@
import FilesTree from '@/components/files-tree'
export default
{
name: 'project',
components:
{
FilesTree
},
computed:
{
project()
{
return this.$store.state.project
}
},
created()
{
},
methods:
{
}
}
@@ -0,0 +1,4 @@
<div>
{{ project.name }}
<files-tree></files-tree>
</div>
+1 -1
View File
@@ -19,7 +19,7 @@ export default
{ {
onProjectClick(projectSlug) onProjectClick(projectSlug)
{ {
this.$store.commit('setCurrentProject', projectSlug) this.$store.commit('setProject', projectSlug)
} }
} }
} }
+36 -6
View File
@@ -1,27 +1,34 @@
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import FileTree from '@/utils/FileTree'
Vue.use(Vuex) Vue.use(Vuex)
export default new Vuex.Store({ export default new Vuex.Store({
state: state:
{ {
projects: {}, projects: {},
currentProject: null project: null,
files: new FileTree({ autoWash: true }),
file: null
}, },
mutations: mutations:
{ {
/**
* Projects
*/
updateProjects(state, data) updateProjects(state, data)
{ {
state.projects = data state.projects = data
// Has a current project // Has a current project
if(state.currentProject) if(state.project)
{ {
// Current project has been removed // Current project has been removed
if(typeof state.projects[data] === 'undefined') if(typeof state.projects[data] === 'undefined')
{ {
state.currentProject = null state.project = null
} }
} }
@@ -33,7 +40,7 @@ export default new Vuex.Store({
// If only one project, set has current // If only one project, set has current
if(projectKeys.length === 1) if(projectKeys.length === 1)
{ {
state.currentProject = state.projects[projectKeys[0]] state.project = state.projects[projectKeys[0]]
} }
} }
}, },
@@ -43,13 +50,36 @@ export default new Vuex.Store({
state.projects.push(data) state.projects.push(data)
}, },
setCurrentProject(state, data) setProject(state, data)
{ {
// Test of project exist // Test of project exist
if(typeof state.projects[data] !== 'undefined') if(typeof state.projects[data] !== 'undefined')
{ {
state.currentProject = state.projects[data] state.project = state.projects[data]
} }
},
/**
* Files
*/
updateFiles(state, data)
{
state.files = new FileTree({ autoWash: true })
for(const file of data)
{
state.files.addFile(file.path.full, file)
}
},
createFile(state, data)
{
state.files.addFile(data.path.full, data)
},
deleteFile(state, data)
{
state.files.removeFile(data.path.full)
} }
} }
}) })
+688
View File
@@ -0,0 +1,688 @@
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
// 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(/\/$/, '')
console.log(path)
// Starting `.`
path = path.replace(/^\./, '')
// Starting `/`
path = path.replace(/^\//, '')
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,
data: _data
}
// Save
folders.push(folder)
folders = folder.folders
}
}
// 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,
data: _data
}
// Save
folder.files.push(file)
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.data.onRemove === 'function')
{
_folder.data.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.data.onRemove === 'function')
{
_file.data.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.data.onRemove === 'function')
{
folder.data.onRemove.apply(this, [folder])
}
// Auto wash
if(this.autoWash)
{
this.removeEmptyFolders()
}
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.data.onRemove === 'function')
{
file.data.onRemove.apply(this, [file])
}
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.data.onRemove === 'function')
{
_folder.data.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])
return removedCount
}
/**
* 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