11<template >
2- <slot ></slot >
2+ <dl >
3+ <div class =" header" >
4+ <span class =" header" >{{ props.name }}</span >
5+ <span class =" header" >Description</span >
6+ </div >
7+ <slot ></slot >
8+ </dl >
39</template >
410
511<script setup lang="ts">
612import { onMounted } from ' vue' ;
713
14+ const props = defineProps <{
15+ name: string ;
16+ showall? : boolean ;
17+ }>();
18+
819let selectedItem: TreeItem | null = null ;
920
10- // Each TR is either a folder or a file
11- // The second TD is the description/content of the file or folder
12- // The first TD is clickable
13- // If it's a folder, clicking it will show/hide the TRs below that are inside that folder
14- // If it's a file, clicking it will show/hide the content in the second TD
21+ // Each div.file is either a folder or a file
22+ // The DD element is the description/content of the file or folder
23+ // The DT element is clickable
24+ // If it's a folder, clicking it will show/hide the divs below that are inside that folder
25+ // If it's a file, clicking it will show/hide the content in the DD element
1526
1627const canToggleFolders = false ;
1728
1829class TreeItem {
19- element: HTMLTableCellElement ; // The first TD
20- contentElement: HTMLTableCellElement ; // The second TD
30+ element: HTMLElement ; // The DT element
31+ contentElement: HTMLElement ; // The DD element
2132 isFolder: boolean ; // Whether this item is a folder or a file
2233 isExpanded: boolean = true ; // Whether the folder is expanded or not
23- children: TableTree ; // Children of this item, if it's a folder
34+ children: FileTree ; // Children of this item, if it's a folder
2435
2536 toggleChildren(state : boolean | undefined ) {
2637 if (state === undefined ) state = ! this .isExpanded ;
2738 this .isExpanded = state ;
28- this .children .items .forEach ((childData , childTr ) => {
29- childTr .classList .toggle (' hidden' , state );
39+ this .children .items .forEach ((childData , childDiv ) => {
40+ childDiv .classList .toggle (' hidden' , ! state );
3041 childData .toggleChildren (state );
3142 });
43+ this .element .classList .toggle (' closed' , ! state );
3244 }
3345}
3446
35- class TableTree {
36- items: Map <HTMLTableRowElement , TreeItem > = new Map ();
47+ class FileTree {
48+ items: Map <HTMLDivElement , TreeItem > = new Map ();
3749 pathToItem: Map <string , TreeItem > = new Map ();
3850}
3951
40- let tableTree = new TableTree ();
52+ let fileTree = new FileTree ();
4153
4254onMounted (() => {
43- // find all second TDs and add a class to them
44- const tds = document .querySelectorAll (' td:nth-child(2)' );
45- tds .forEach (td => {
46- td .classList .add (' hidden' );
55+ // find all DD elements and add a class to them
56+ const dds = document .querySelectorAll (' dd' );
57+ dds .forEach (dd => {
58+ if (! props .showall )
59+ dd .classList .add (' hidden' );
4760 });
4861
4962 let firstItem: TreeItem | null = null ;
5063
51- // make all first TDs clickable
52- const firstTds = document .querySelectorAll (' td:first-child ' ) as NodeListOf <HTMLTableCellElement >;
53- firstTds .forEach (td => {
64+ // make all DT elements clickable
65+ const dts = document .querySelectorAll (' dt ' ) as NodeListOf <HTMLElement >;
66+ dts .forEach (dt => {
5467
5568 // If the text content looks like a path, split it by / and only keep the last part,
5669 // with a few tabs in front
5770 // If the last part is empty, it means this is a folder, show it with a folder icon
58- let content = td .textContent ?.trim () || ' ' ;
71+ let content = dt .textContent ?.trim () || ' ' ;
5972 let isFolder = content .endsWith (' /' );
6073
61- const parts = td .textContent ?.split (' /' ) || [];
74+ const parts = dt .textContent ?.split (' /' ) || [];
6275 const lastPartIndex = isFolder ? parts .length - 2 : parts .length - 1 ;
6376 const lastPart = parts [lastPartIndex ] || ' ' ; // Get the last part, or the second last if it's a folder
6477
65- if (isFolder )
66- td .classList .add (' folder' );
67- else
68- td .classList .add (' file' );
78+ if (isFolder ) {
79+ dt .classList .add (' folder' );
80+ }
81+ else {
82+ dt .classList .add (' file' );
83+ const extension = lastPart .split (' .' ).pop ()?.toLowerCase () || ' ' ;
84+ dt .classList .add (extension );
85+ }
6986
70- td .style .setProperty (' --depth' , ` ${lastPartIndex } ` ); // Set a custom property for depth
71- td .textContent = lastPart ; // Set the text content to the last part
87+
88+ dt .style .setProperty (' --depth' , ` ${lastPartIndex } ` ); // Set a custom property for depth
89+ dt .textContent = lastPart ; // Set the text content to the last part
7290
7391 const path = parts .slice (0 , lastPartIndex + 1 ).join (' /' );
74- td .addEventListener (' click' , () => {
75- const item = tableTree .pathToItem .get (path );
92+ if (! props .showall )
93+ dt .addEventListener (' click' , () => {
94+ const item = fileTree .pathToItem .get (path );
7695 if (isFolder && canToggleFolders ) {
7796 if (item ) {
7897 item .toggleChildren (! item .isExpanded );
7998 }
8099 }
81100 else {
82- const nextTd = td .nextElementSibling as HTMLTableCellElement ;
83- const isOn = nextTd .classList .contains (' hidden' );
84- nextTd .classList .toggle (' hidden' , ! isOn );
101+ const nextDd = dt .nextElementSibling as HTMLElement ;
102+ const isOn = nextDd .classList .contains (' hidden' );
103+ nextDd .classList .toggle (' hidden' , ! isOn );
85104 if (selectedItem && selectedItem !== item ) {
86105 selectedItem .contentElement .classList .toggle (' hidden' , isOn );
87106 selectedItem .element .classList .remove (' selected' );
@@ -91,78 +110,105 @@ onMounted(() => {
91110 }
92111 });
93112
94- // Add the item to the table tree
95- const tr = td .parentElement as HTMLTableRowElement ;
96- const contentTd = td .nextElementSibling as HTMLTableCellElement ;
113+ // Add the item to the file tree
114+ const fileDiv = dt .parentElement as HTMLDivElement ;
115+ const contentDd = dt .nextElementSibling as HTMLElement ;
97116
98117 let newItem = new TreeItem ();
99- newItem .element = td ;
100- newItem .contentElement = contentTd ;
118+ newItem .element = dt ;
119+ newItem .contentElement = contentDd ;
101120 newItem .isFolder = isFolder ;
102- newItem .children = new TableTree ();
103-
121+ newItem .children = new FileTree ();
122+
104123 if (firstItem === null )
105124 firstItem = newItem ;
106125
107126 if (lastPartIndex == 0 ) {
108- tableTree .items .set (tr , newItem );
127+ fileTree .items .set (fileDiv , newItem );
109128 }
110129 else {
111130 // Find the parent item based on the path
112131 const parentPath = parts .slice (0 , lastPartIndex ).join (' /' );
113132 // Add the item to the parent's children
114- const parentData = tableTree .pathToItem .get (parentPath );
133+ const parentData = fileTree .pathToItem .get (parentPath );
115134 if (parentData ) {
116- parentData .children .items .set (tr , newItem );
135+ parentData .children .items .set (fileDiv , newItem );
136+ }
137+ else {
138+ // Traverse from the start and create the missing parents
139+ // TODO
140+ console .warn (` Parent not found for path: ${parentPath }. Creating implicit parents is not implemented yet. ` );
117141 }
118142 }
119143 // Update the path to item map
120- tableTree .pathToItem .set (path , newItem );
144+ fileTree .pathToItem .set (path , newItem );
121145 });
122146
123- // The very first TD should be visible by default
124- if (firstItem ) {
147+ // The very first DT should be visible by default
148+ if (! props . showall && firstItem ) {
125149 let item = firstItem as TreeItem ;
126150 item .element .classList .remove (' hidden' );
127151 item .element .classList .add (' selected' ); // Highlight the first item
128- const nextTd = item .contentElement ;
129- if (nextTd ) {
130- nextTd .classList .remove (' hidden' );
131- selectedItem = item ; // Set the selected TD to the first one
152+ const nextDd = item .contentElement ;
153+ if (nextDd ) {
154+ nextDd .classList .remove (' hidden' );
155+ selectedItem = item ; // Set the selected DD to the first one
132156 }
133157 }
134158});
135159 </script >
136160
137161<style >
138162
139- table {
163+ .file {
140164 position : relative ;
165+ display : flex ;
166+ align-items : stretch ;
167+ }
168+
169+ div .header {
170+ border-bottom : 1px solid #eee ;
141171}
142172
143- tr {
144- border : none !important ;
145- width : 100% ;
173+ span .header {
174+ width : 250px ;
175+ font-weight : bold ;
176+ display : inline-block ;
177+ margin-bottom : 0.5em ;
178+ margin-left : 0.1em ;
146179}
147180
148- tr :hover {
181+ dt :hover {
149182 background-color : rgb (241 , 241 , 241 ) !important ;
150183}
151184
152- html [data-theme = ' dark' ] tr :hover {
185+ html [data-theme = ' dark' ] dt :hover {
153186 background-color : rgb (50 , 50 , 50 ) !important ;
154187}
155188
156- tr . hidden , td .hidden {
189+ .file. hidden , dt .hidden , dd .hidden {
157190 display : none ;
158191}
159192
160- td {
193+ dl {
194+ overflow-x : auto ;
195+ }
196+
197+ dd {
198+ min-width : 250px ;
199+ }
200+
201+ dt , dd {
161202 padding-bottom : 0.3em !important ;
162203 padding-top : 0.3em !important ;
204+ margin : 0 ;
163205}
164206
165- td :first-child {
207+ dd > p {
208+ margin : 0 ;
209+ }
210+
211+ dt {
166212 --depth : 0 ;
167213 cursor : pointer ;
168214 font-family : monospace ;
@@ -171,24 +217,51 @@ td:first-child {
171217 padding-left : calc (var (--depth ) * 1.5em ) !important ;
172218 width : calc (250px - var (--depth ) * 1.5em );
173219 border-right : 1px solid #eee !important ;
220+ flex-shrink : 0 ;
174221
175222 &.selected {
176223 font-weight : bold ;
177224 }
178225}
179226
180- td .folder ::before {
181- content : ' 📁' ;
182- margin-right : 1em ;
227+ dt .folder {
228+ &::before {
229+ content : ' folder_open' ;
230+ font-family : ' Material Symbols Outlined' ;
231+ margin-right : 1em ;
232+ }
233+
234+ &.closed ::before {
235+ content : ' folder' ;
236+ }
183237}
184238
185- td .file ::before {
186- content : ' 📄' ;
187- margin-right : 1em ;
239+ dt .file {
240+ &::before {
241+ content : ' draft' ;
242+ font-family : ' Material Symbols Outlined' ;
243+ margin-right : 1em ;
244+ }
245+
246+ &.js ::before , &.ts ::before , &.json ::before {
247+ content : ' code' ;
248+ }
249+
250+ &.html ::before {
251+ content : ' draft' ;
252+ }
253+
254+ &.css ::before {
255+ content : ' code' ;
256+ }
257+
258+ &.glb ::before {
259+ content : ' deployed_code' ;
260+ }
188261}
189262
190- td :nth-child ( 2 ) {
263+ dd {
191264 padding-left : 1em !important ;
192- width : initial ;
265+ flex : 1 ;
193266}
194267 </style >
0 commit comments