import Thorium , { Components , Controller , UserInterface, useState } from "thoriumjs"; import style from '../../../styles/tree.module.css'; // COMPONENTS import { DropZone , DropZoneInitOptions } from "../dropzone/dropzone"; import {LineControl , LineControlInitOptions } from "../../elements/linecontrols/linecontrols"; import {Icon , IconInitOptions} from "../../elements/icon/icon"; // MODULES export interface FileOptionsInit{ name:string; id?:string; controls?:LineControlInitOptions['controls']; onLineMouseDown?:LineControlInitOptions['onLineMouseDown']; prop?:LineControlInitOptions['prop']; proto?:LineControlInitOptions['proto'] | any; icon?:LineControlInitOptions['icon']; } class File extends LineControl{ constructor(file:FileOptionsInit){ super({ icon : (file.icon ? file.icon : { path : './assets/file-empty.svg' , type : 'mask' }), text:file.name, prop : { ...(file.prop ? file.prop : {}), id : file.id, }, ...(file.icon ? {icon : file.icon} : {}), ...(file.controls ? {controls : file.controls} : {}), // controls:[], onLineMouseDown:function(node){ if(file.onLineMouseDown)file.onLineMouseDown(node); }, ...(file.proto ? {proto : file.proto} : {}) }) } } // export interface FolderOptionsInit{ name:string; id?:string; files?:FileOptionsInit[]; folders?:FolderOptionsInit[]; content?:DropZoneInitOptions['content']; onHeaderMouseDown?:DropZoneInitOptions['onHeaderMouseDown']; controls?:DropZoneInitOptions['controls']; icon?:IconInitOptions; icon_open?:IconInitOptions; prop?:Thorium.TemplateInterface['prop']; proto?:Partial['proto']> | any; } class Folder extends DropZone{ constructor(options:DropZoneInitOptions){ super({ icon : (options.icon ? options.icon : { path : './assets/folder-close.svg' , type : 'mask' }), icon_open : (options.icon_open ? options.icon_open : { path : './assets/folder-open.svg' , type : 'mask' }), controls : null, ...options, proto : { addFolder(options:FolderOptionsInit){ new UserInterface.NodeUI([buildFolder(options)]).BuildIn(this.children['content']).then((node) => { node.Initialise(); for(const value of (this.context('tree') as TreeController)._EventStack.addFolder.values()){ value.callback({ type:'file', method:'add', target:node.elements[0] }); } }) }, addFile(options:FileOptionsInit){ new UserInterface.NodeUI([buildFile(options)]).BuildIn(this.children['content']).then((node) => { node.Initialise() for(const value of (this.context('tree') as TreeController)._EventStack.addFile.values()){ value.callback({ type:'file', method:'add', target:node.elements[0] }); } }) }, map(searchTemplate?:Record){ const content = { name : (this.children['head'] ? this.children['head'].children['title'].innerText : this.getAttribute('name')), id : this.getAttribute('id'), target : this, ...(Array.from( this.children['content'].children , (element:TreeController) => { let elmementName = element.getAttribute('name'); if(elmementName == 'dropZone'){ return ['folders',element.map(searchTemplate)]; } if(elmementName == 'line'){ return ['files',{ target:element, name : element.children['text'].innerText, id : element.getAttribute('id'), ...(searchTemplate ? Object.fromEntries(new Map(Array.from(Object.keys(searchTemplate) , (key , iterator) => { return [(searchTemplate[key] ? searchTemplate[key] : key),element.getAttribute(key)]; }))) : {}) }]; } }).filter((x) => x) as [string,any][]).reduce((result:any,treeElement) => { let [type,content] = treeElement; if(!result[type])result[type] = []; result[type].push(content); return result; } , {}) }; return content; }, findById(serachId:string , map?){ if(!map)map = this.map(); return [ ...( map.folders ? Array.from(map.folders , (folder:{id:string,name:string}|any) => { if(folder.id == serachId)return folder; else return folder.target.findById(serachId,folder) }).filter((x) => x) : []), ...( map.files ? Array.from(map.files , (file:{id:string,name:string}) => { if(file.id == serachId)return file; }).filter((x) => x) : []) ].flat(); }, findByName(findName:string , map?){ if(!map)map = this.map(); return [ ...( map.folders ? Array.from(map.folders , (folder:{id:string,name:string}|any) => { return [(folder.name == findName ? folder : null) , folder.target.findByName(findName,folder)].flat().filter((x) => x); }).filter((x) => x) : []), ...( map.files ? Array.from(map.files , (file:{id:string,name:string}) => { if(file.name == findName)return file; }).filter((x) => x) : []) ].flat(); }, searchByName(searchStr:string , map?){ if(!map)map = this.map(); return [ ...( map.folders ? Array.from(map.folders , (folder:{id:string,name:string}|any) => { return [(folder.name.toLowerCase().includes(searchStr.toLowerCase()) ? folder : null) , folder.target.searchByName(searchStr,folder)].flat().filter((x) => x); }).filter((x) => x) : []), ...( map.files ? Array.from(map.files , (file:{id:string,name:string}) => { if(file.name.includes(searchStr))return file; }).filter((x) => x) : []) ].flat(); }, ...(options.proto ? options.proto : {}) }, }) } } export type TreeOptionsInit = FolderOptionsInit[]; type TreeEventTypes = 'addFolder'|'removeFile'|'addFile'|'removeFolder'; export interface TreeController extends Controller{ _EventStack:Recordvoid}>>; addEvent:(eventName:string,callback:(treeMutation:TreeMutation)=>void)=>string; removeEvent:(eventId:string) => void; addFolder:(options:FolderOptionsInit) => void; addFile:(options:FileOptionsInit) => void; map:(searchTemplate?:Record)=>Record; findById:(serachId:string)=>void; findByName:(findName:string)=>void; searchByName:(searchName:string)=>void; onTreeChange?:(mutation:TreeMutation)=>void; sort?:(filter:string)=>void; } const buildFile = (file:FileOptionsInit) => { return new File(file) } const buildFolder = (folder:FolderOptionsInit) => { return new Folder({ title : folder.name, prop : { id : folder.id , ...(folder.prop ? folder.prop : {}) }, content : [ ...(folder.files ? Array.from(folder.files , (file , iterator) => { return buildFile(file); }) : []), ...(folder.folders ? Array.from(folder.folders , (folder , iterator) => { return buildFolder(folder); }) : []), ], ...(folder.icon ? {icon : folder.icon as IconInitOptions} : null), ...(folder.icon_open ? {icon_open : folder.icon_open as IconInitOptions} : null), onHeaderMouseDown : function(event:Event){ if(folder.onHeaderMouseDown)folder.onHeaderMouseDown(event); }, controls : (folder.controls ? folder.controls : null), ...(folder.proto ? {proto : folder.proto} : {}) }) } const treeWorkspace = (workspace:TreeOptionsInit) => { return Array.from( workspace , (project , iterator) => { return buildFolder(project); }); } export interface TreeMutation{ type:'folder'|'file'; target:TreeController|Controller; method:'add'|'remove'; } export default class Tree extends Components.Div{ constructor(tree:TreeOptionsInit,options?:{ prop?:Thorium.ElementInterface['prop']; proto?:Thorium.ElementInterface>['proto']; }){ super({ prop : { id : crypto.randomUUID(), name : 'tree', class : 'context' }, childrens : [ new Components.Div({ prop : { name : 'content', ':viewtype' : 'repertory', }, childrens : treeWorkspace(tree), proto : { } }) ], proto : { _EventStack : { addFolder : new Map(), removeFolder : new Map(), addFile : new Map(), removeFile : new Map() }, AfterInitialise(){ const onAddFolderEventId = this.addEvent('addFolder' , (mutation:TreeMutation) => { if('onTreeChange' in this)this.onTreeChange(mutation); }); const onAddFileEventId = this.addEvent('addFile' , (mutation:TreeMutation) => { if('onTreeChange' in this)this.onTreeChange(mutation); }); }, addEvent(eventName:string,callback:(treeMutation:TreeMutation)=>void){ let eventId = crypto.randomUUID(); this._EventStack[eventName].set(eventId , {id:eventId,callback:callback}); return eventId; }, removeEvent(eventId:string){ return Array.from(Object.keys(this._EventStack) , (key:string) => { if(this._EventStack[key].has(eventId))return this._EventStack[key].delete(eventId); }).filter((x,i) => x); }, addFolder(options:FolderOptionsInit){ new UserInterface.NodeUI([buildFolder(options)]).BuildIn(this.children['content']).then((node) => { node.Initialise(); for(const value of (this as TreeController)._EventStack.addFolder.values()){ value.callback({ type:'folder', method:'add', target:node.elements[0] }); } }) }, addFile(options:FileOptionsInit){ new UserInterface.NodeUI([buildFile(options)]).BuildIn(this.children['content']).then((node) => { node.Initialise(); for(const value of (this as TreeController)._EventStack.addFile.values()){ value.callback({ type:'file', method:'add', target:node.elements[0] }); } }) }, map(searchTemplate?:Record){ const content = { name : (this.children['head'] ? this.children['head'].children['title'].innerText : this.getAttribute('name')), id : this.getAttribute('id'), target : this, ...(Array.from( this.children['content'].children , (element:TreeController) => { let elmementName = element.getAttribute('name'); if(elmementName == 'dropZone'){ return ['folders',element.map(searchTemplate)]; } if(elmementName == 'line'){ return ['files',{ target:element, name : element.getAttribute('name'), id : element.getAttribute('id'), ...(searchTemplate ? Object.fromEntries(new Map(Array.from(Object.keys(searchTemplate) , (key , iterator) => { return [(searchTemplate[key] ? searchTemplate[key] : key),element.getAttribute(key)]; }))) : {}) }]; } }).filter((x) => x) as [string,any][]).reduce((result:any,treeElement) => { let [type,content] = treeElement; if(!result[type])result[type] = []; result[type].push(content); return result; } , {}) }; return content; }, findById(serachId:string , map?){ if(!map)map = this.map(); return [ ...( map.folders ? Array.from(map.folders , (folder:{id:string,name:string}|any) => { if(folder.id == serachId)return folder; else return folder.target.findById(serachId,folder) }).filter((x) => x) : []), ...( map.files ? Array.from(map.files , (file:{id:string,name:string}) => { if(file.id == serachId)return file; }).filter((x) => x) : []) ].flat(); }, findByName(findName:string , map?){ if(!map)map = this.map(); return [ ...( map.folders ? Array.from(map.folders , (folder:{id:string,name:string}|any) => { return [(folder.name == findName ? folder : null) , folder.target.findByName(findName,folder)].flat().filter((x) => x); }).filter((x) => x) : []), ...( map.files ? Array.from(map.files , (file:{id:string,name:string}) => { if(file.name == findName)return file; }).filter((x) => x) : []) ].flat(); }, searchByName(searchStr:string , map?){ if(!map)map = this.map(); return [ ...( map.folders ? Array.from(map.folders , (folder:{id:string,name:string}|any) => { return [(folder.name.toLowerCase().includes(searchStr.toLowerCase()) ? folder : null) , folder.target.searchByName(searchStr,folder)].flat().filter((x) => x); }).filter((x) => x) : []), ...( map.files ? Array.from(map.files , (file:{id:string,name:string}) => { if(file.name.includes(searchStr))return file; }).filter((x) => x) : []) ].flat(); }, ...(options && options.proto ? options.proto : {}) } }) } }