kempo 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import LazyComponent from"./LazyComponent.js";import Component from"./Component.js";import"./Icon.js";import{onEvent,offEvent}from"../utils/element.js";import watchDirectoryHandle from"../utils/watchDirectoryHandle.js";const handle=Symbol(),selectDirectoryClickHandler=Symbol(),openToggleClickHandler=Symbol(),fileSelectedHandler=Symbol(),watchId=Symbol(),changeCallback=Symbol();export default class DirectoryViewer extends LazyComponent{constructor(e=!1){super(),this[handle]=e,this[selectDirectoryClickHandler]=()=>this.selectDirectory(),this[openToggleClickHandler]=()=>{this.root?this.clearDirectoryHandle():this.toggleOpen()},this[changeCallback]=()=>{this.updateContents()},this.slot="content",this.registerAttributes({root:!e,hasHandle:!!e,opened:!e}),this[watchId]=!1}async render(e){return!!await super.render(e)&&(onEvent(this.shadowRoot,"fileselected",this[fileSelectedHandler]),onEvent(this.shadowRoot.getElementById("selectDirectory"),"click",this[selectDirectoryClickHandler]),onEvent(this.shadowRoot.getElementById("openToggle"),"click",this[openToggleClickHandler]),this.renderDirectory(),!0)}async renderDirectory(){this[handle]&&this.rendered&&(this.innerHTML=this[handle].name,this.renderContents())}async renderContents(){if(this.querySelectorAll('[slot="content"]').forEach((e=>e.remove())),this.opened||this.root){for await(const e of this[handle].values())"directory"===e.kind&&this.appendChild(new DirectoryViewer(e,!1));for await(const e of this[handle].values())"file"===e.kind&&this.appendChild(new File(e));this[watchId]||(this[watchId]=watchDirectoryHandle(this[handle],this[changeCallback]))}else this[watchId]&&(clearInterval(this[watchId]),this[watchId]=!1)}disconnectedCallback(){super.disconnectedCallback(),offEvent(this.shadowRoot,"fileselected",this[fileSelectedHandler]),offEvent(this.shadowRoot.getElementById("selectDirectory"),"click",this[selectDirectoryClickHandler]),offEvent(this.shadowRoot.getElementById("openToggle"),"click",this[openToggleClickHandler])}attributeChangedCallback(e,t,n){"opened"===e&&t!==n&&(!n&&this.root?this.opened=!0:this.renderContents())}async selectDirectory(){window.showDirectoryPicker&&(this[handle]=await window.showDirectoryPicker(),this[handle]&&(this.hasHandle=!0,this.renderDirectory()))}open(){this.opened=!0}close(){this.root||(this.opened=!1)}toggleOpen(){this.root||(this.opened=!this.opened)}clearDirectoryHandle(){this[handle]=!1,this.hasHandle=!1,this.innerHTML=""}async updateContents(){if(this.opened||this.root){const e=new Set([...this.querySelectorAll("k-directory-viewer")]);for await(const t of this[handle].values())if("directory"===t.kind){const n=[...e].find((e=>t.isSameEntry(e[handle])&&t.name===e[handle].name));n?(this.appendChild(n),e.delete(n)):this.appendChild(new DirectoryViewer(t,!1))}e.forEach((e=>e.remove()));const t=new Set([...this.querySelectorAll("k-directory-viewer-file")]);for await(const e of this[handle].values())if("file"===e.kind){const n=[...t].find((t=>e.isSameEntry(t[handle])&&e.name===t[handle].name));n?(this.appendChild(n),t.delete(n)):this.appendChild(new File(e))}t.forEach((e=>e.remove())),this[watchId]||(this[watchId]=watchDirectoryHandle(this[handle],this[changeCallback]))}else this[watchId]&&(clearInterval(this[watchId]),this[watchId]=!1)}get shadowTemplate(){return`\n <button\n id="selectDirectory"\n >\n <slot name="selectButton">\n <k-icon name="folder-create"></k-icon> Select Directory\n </slot>\n </button>\n <button\n id="openToggle"\n class="no-btn"\n >\n <k-icon name="folder" id="folder-closed"></k-icon>\n <k-icon name="folder-open" id="folder-opened"></k-icon>\n <k-icon name="folder-clear" id="folder-clear"></k-icon>\n ${super.shadowTemplate}\n </button>\n <div id="tree">\n <slot name="content"></slot>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host([has-handle]) #selectDirectory,\n :host([opened]) #folder-closed,\n :host(:not([opened])) #folder-opened,\n :host(:not([has-handle])) #folder-closed,\n :host(:not([has-handle])) #folder-opened,\n :host(:not([has-handle])) #folder-clear,\n :host(:not([root])) #folder-clear,\n :host([root]) #folder-opened {\n display: none;\n }\n #openToggle {\n display: block;\n width: 100%;\n flex: 1 1 auto;\n }\n #tree {\n padding-left: var(--spacer);\n }\n #wrapper {\n display: flex;\n }\n #clearSelectedDirectory {\n flex: none;\n }\n `}static observedAttributes=[...LazyComponent.observedAttributes,"opened"];static watchInterval=1e3}window.customElements.define("k-directory-viewer",DirectoryViewer);const selectFileClickHandler=Symbol();export class File extends Component{constructor(e=!1){super(),this[handle]=e,this[selectFileClickHandler]=()=>this.select(),this.slot="content",this.registerAttribute("selected",!1)}async render(e){if(await super.render(e)){if(this[handle]){this.innerHTML=this[handle].name;const e=this[handle].name.split(".").pop(),t=this.shadowRoot.getElementById("extIcon");["png","gif","jpeg","jpg","tif","webp"].includes(e)?t.name="image":["txt","rtf","js","css","html"].includes(e)?t.name="file-text":t.name="file",onEvent(this.shadowRoot.getElementById("selectFile"),"click",this[selectFileClickHandler])}return!0}return!1}disconnectedCallback(){super.disconnectedCallback(),offEvent(this.shadowRoot.getElementById("selectFile"),"click",this[selectFileClickHandler])}select(){const e=this.closest("k-directory-viewer[root]").querySelector("k-directory-viewer-file[selected]");e!==this&&(e&&(e.selected=!1),this.selected=!0,this.dispatchEvent(new CustomEvent("fileselected",{detail:{fileHandle:this[handle],fileComponent:this},bubbles:!0,composed:!0})))}get shadowTemplate(){return`\n <button\n id="selectFile"\n class="no-btn"\n >\n <k-icon\n id="extIcon"\n name=""\n ></k-icon>\n ${super.shadowTemplate}\n </button>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n width: 100%;\n }\n :host([selected]) {\n background-color: var(--c_primary);\n color: var(--tc_on_primary);\n border-radius: var(--raidus);\n }\n #selectFile {\n width: 100%;\n }\n `}}window.customElements.define("k-directory-viewer-file",File);
@@ -1 +1 @@
1
- import Component from"./Component.js";const getIconByName=t=>{const e=async e=>{try{const n=await fetch(`${e}/${t}.svg`);if(200===n.status)return await n.text()}catch(t){console.log(t)}return!1};return new Promise((async(t,n)=>{let s;for(let t=0;t<Icon.pathToIcons.length&&!s;t++)s=await e(Icon.pathToIcons[t]);s?t(s):n("Icon not found")}))};export default class Icon extends Component{constructor(){super(),this.registerAttributes({src:"",name:""})}connectedCallback(){}async render(t){if(await super.render(t)){if(this.src){const t=await fetch(this.src);200===t.status?(this.innerHTML=await t.text(),this.fixSVG()):this.innerHTML.length||(this.innerHTML=Icon.fallback)}else if(this.name){const t=await getIconByName(this.name);t?(this.innerHTML=t,this.fixSVG()):this.innerHTML.length||(this.innerHTML=Icon.fallback)}else this.rendered=!1,this.innerHTML.length||(this.innerHTML=Icon.fallback);return!0}return!1}attributeChangedCallback(t,e,n){super.attributeChangedCallback(t,e,n),["src","name"].includes(t)&&n&&this.render(!0)}fixSVG(){const t=this.querySelector("svg");t.removeAttribute("width"),t.removeAttribute("height"),t.querySelectorAll("path, rect, circle").forEach((t=>{t.setAttribute("fill","currentColor")}))}get shadowStyles(){return"\n :host {\n display: inline-block;\n vertical-align: bottom;\n }\n ::slotted(svg){\n height: 1.35em;\n vertical-align: middle;\n }\n "}static observedAttributes=["name","src"];static pathToIcons=["/icons"];static fallback='\n <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currnetColor" d="M480-79q-16 0-30.5-6T423-102L102-423q-11-12-17-26.5T79-480q0-16 6-31t17-26l321-321q12-12 26.5-17.5T480-881q16 0 31 5.5t26 17.5l321 321q12 11 17.5 26t5.5 31q0 16-5.5 30.5T858-423L537-102q-11 11-26 17t-31 6Zm0-80 321-321-321-321-321 321 321 321Zm-40-281h80v-240h-80v240Zm40 120q17 0 28.5-11.5T520-360q0-17-11.5-28.5T480-400q-17 0-28.5 11.5T440-360q0 17 11.5 28.5T480-320Zm0-160Z"/></svg>\n '}window.customElements.define("k-icon",Icon);
1
+ import Component from"./Component.js";const cache={},getIconByPath=async t=>(cache[t]||(cache[t]=new Promise((async(e,n)=>{try{const n=await fetch(t);200===n.status&&e(await n.text())}catch(t){}return n()}))),await cache[t]),getIconByName=t=>{const e=async e=>getIconByPath(`${e}/${t}.svg`);return new Promise((async(t,n)=>{let s;for(let t=0;t<Icon.pathToIcons.length&&!s;t++)s=await e(Icon.pathToIcons[t]);s?t(s):n("Icon not found")}))};export default class Icon extends Component{constructor(){super(),this.registerAttributes({src:"",name:""})}connectedCallback(){}async render(t){if(await super.render(t)){if(this.src){const t=await getIconByPath(this.src);t?(this.innerHTML=t,this.fixSVG()):this.innerHTML.length||(this.innerHTML=Icon.fallback)}else if(this.name){const t=await getIconByName(this.name);t?(this.innerHTML=t,this.fixSVG()):this.innerHTML.length||(this.innerHTML=Icon.fallback)}else this.rendered=!1,this.innerHTML.length||(this.innerHTML=Icon.fallback);return!0}return!1}attributeChangedCallback(t,e,n){super.attributeChangedCallback(t,e,n),["src","name"].includes(t)&&n&&this.render(!0)}fixSVG(){const t=this.querySelector("svg");t.removeAttribute("width"),t.removeAttribute("height"),t.querySelectorAll("path, rect, circle").forEach((t=>{t.setAttribute("fill","currentColor")}))}get shadowStyles(){return"\n :host {\n display: inline-block;\n vertical-align: bottom;\n }\n ::slotted(svg){\n height: 1.35em;\n vertical-align: middle;\n }\n "}static observedAttributes=["name","src"];static pathToIcons=["/icons"];static fallback='\n <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currnetColor" d="M480-79q-16 0-30.5-6T423-102L102-423q-11-12-17-26.5T79-480q0-16 6-31t17-26l321-321q12-12 26.5-17.5T480-881q16 0 31 5.5t26 17.5l321 321q12 11 17.5 26t5.5 31q0 16-5.5 30.5T858-423L537-102q-11 11-26 17t-31 6Zm0-80 321-321-321-321-321 321 321 321Zm-40-281h80v-240h-80v240Zm40 120q17 0 28.5-11.5T520-360q0-17-11.5-28.5T480-400q-17 0-28.5 11.5T440-360q0 17 11.5 28.5T480-320Zm0-160Z"/></svg>\n '}window.customElements.define("k-icon",Icon);
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import Card from"./components/Card.js";import Component from"./components/Component.js";import Dialog from"./components/Dialog.js";import FocusCapture from"./components/FocusCapture.js";import Icon from"./components/Icon.js";import Import from"./components/Import.js";import LazyComponent from"./components/LazyComponent.js";import ReactiveComponent from"./components/ReactiveComponent.js";import ReactiveLazyComponent from"./components/ReactiveLazyComponent.js";import Resize from"./components/Resize.js";import Search from"./components/Search.js";import ShowMore from"./components/ShowMore.js";import SideMenu from"./components/SideMenu.js";import Split from"./components/Split.js";import Table from"./components/Table.js";import Tabs from"./components/Tabs.js";import Tags from"./components/Tags.js";import ThemeSwitcher from"./components/ThemeSwitcher.js";import Timestamp from"./components/Timestamp.js";import Toggle from"./components/Toggle.js";import cli from"./utils/cli.js";import cookie from"./utils/cookie.js";import debounce from"./utils/debounce.js";import drag from"./utils/drag.js";import element from"./utils/element.js";import formatTimestamp from"./utils/formatTimestamp.js";import fuzzyMatch from"./utils/fuzzyMatch.js";import getPreferredTheme from"./utils/getPreferredTheme.js";import loadCss from"./utils/loadCss.js";import loadScript from"./utils/loadScript.js";import object from"./utils/object.js";import string from"./utils/string.js";import toFunction from"./utils/toFunction.js";import type from"./utils/type.js";import waitFrames from"./utils/waitFrames.js";export default{Card:Card,Component:Component,Dialog:Dialog,FocusCapture:FocusCapture,Icon:Icon,Import:Import,LazyComponent:LazyComponent,ReactiveComponent:ReactiveComponent,ReactiveLazyComponent:ReactiveLazyComponent,Resize:Resize,Search:Search,ShowMore:ShowMore,SideMenu:SideMenu,Split:Split,Table:Table,Tabs:Tabs,Tags:Tags,ThemeSwitcher:ThemeSwitcher,Timestamp:Timestamp,Toggle:Toggle,cli:cli,cookie:cookie,debounce:debounce,drag:drag,element:element,formatTimestamp:formatTimestamp,fuzzyMatch:fuzzyMatch,getPreferredTheme:getPreferredTheme,loadCss:loadCss,loadScript:loadScript,object:object,string:string,toFunction:toFunction,type:type,waitFrames:waitFrames};
1
+ import Card from"./components/Card.js";import Component from"./components/Component.js";import Dialog from"./components/Dialog.js";import DirectoryViewer from"./components/DirectoryViewer.js";import FocusCapture from"./components/FocusCapture.js";import Icon from"./components/Icon.js";import Import from"./components/Import.js";import LazyComponent from"./components/LazyComponent.js";import ReactiveComponent from"./components/ReactiveComponent.js";import ReactiveLazyComponent from"./components/ReactiveLazyComponent.js";import Resize from"./components/Resize.js";import Search from"./components/Search.js";import ShowMore from"./components/ShowMore.js";import SideMenu from"./components/SideMenu.js";import Split from"./components/Split.js";import Table from"./components/Table.js";import Tabs from"./components/Tabs.js";import Tags from"./components/Tags.js";import ThemeSwitcher from"./components/ThemeSwitcher.js";import Timestamp from"./components/Timestamp.js";import Toggle from"./components/Toggle.js";import cli from"./utils/cli.js";import cookie from"./utils/cookie.js";import debounce from"./utils/debounce.js";import drag from"./utils/drag.js";import element from"./utils/element.js";import files from"./utils/files.js";import formatTimestamp from"./utils/formatTimestamp.js";import fuzzyMatch from"./utils/fuzzyMatch.js";import getPreferredTheme from"./utils/getPreferredTheme.js";import loadCss from"./utils/loadCss.js";import loadScript from"./utils/loadScript.js";import object from"./utils/object.js";import string from"./utils/string.js";import toFunction from"./utils/toFunction.js";import type from"./utils/type.js";import waitFrames from"./utils/waitFrames.js";import watchDirectoryHandle from"./utils/watchDirectoryHandle.js";export default{Card:Card,Component:Component,Dialog:Dialog,DirectoryViewer:DirectoryViewer,FocusCapture:FocusCapture,Icon:Icon,Import:Import,LazyComponent:LazyComponent,ReactiveComponent:ReactiveComponent,ReactiveLazyComponent:ReactiveLazyComponent,Resize:Resize,Search:Search,ShowMore:ShowMore,SideMenu:SideMenu,Split:Split,Table:Table,Tabs:Tabs,Tags:Tags,ThemeSwitcher:ThemeSwitcher,Timestamp:Timestamp,Toggle:Toggle,cli:cli,cookie:cookie,debounce:debounce,drag:drag,element:element,files:files,formatTimestamp:formatTimestamp,fuzzyMatch:fuzzyMatch,getPreferredTheme:getPreferredTheme,loadCss:loadCss,loadScript:loadScript,object:object,string:string,toFunction:toFunction,type:type,waitFrames:waitFrames,watchDirectoryHandle:watchDirectoryHandle};
@@ -0,0 +1 @@
1
+ export const getDir=async()=>{};let id,interval=1e3;const watchers=new Map,poll=()=>{};export const watchDir=async(t,c,e=1e3)=>{};export const unwatchDir=async(t,c)=>{};
@@ -0,0 +1 @@
1
+ const serializeDir=async a=>{const e=[];for await(const{kind:i,name:t}of a.values())e.push(`${i}:${t}`);return e.join("|")};export default async(a,e,i=1e3)=>{let t=await serializeDir(a);return setInterval((async()=>{const i=await serializeDir(a);i!=t&&(t=i,e())}),i)};
@@ -0,0 +1,74 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Template - Components - Kempo Docs - A Web Components Solution</title>
7
+ <link rel="stylesheet" href="../kempo/kempo-vars.css" /><link rel="stylesheet" href="../kempo/kempo-styles.css" />
8
+ <link rel="stylesheet" href="../kempo/kempo-hljs.css" />
9
+ </head>
10
+ <body>
11
+ <k-import src="../nav-1.inc.html"></k-import>
12
+ <h1 class="ta-center">DirectoryViewer</h1>
13
+ <main>
14
+ <details
15
+ class="b r mb"
16
+ >
17
+ <summary class="p">Table of Contents</summary>
18
+ <div class="m mt0 pl">
19
+ <a href="#basicUsage">Basic Usage</a><br />
20
+ <a href="#constructor">Constructor</a><br />
21
+ <a href="#requirements">Requirements</a><br />
22
+ <a href="#attributes">Attributes</a><br />
23
+ <a href="#properties">Properties</a><br />
24
+ <a href="#methods">Methods</a><br />
25
+ </div>
26
+ </details>
27
+
28
+ <h3>Description</h3>
29
+ <p>The directory viewer allows the user to select a directory from their local filesystem, and the component allows them to open/close subdirectories and select files.</p>
30
+
31
+ <h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
32
+ <div class="row -mx">
33
+ <div class="col d-span-6 t-span-6 m-span-12 px">
34
+ <pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">k-directory-viewer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">k-directory-viewer</span>&gt;</span></code></pre>
35
+ </div>
36
+ <div class="col d-span-6 t-span-6 m-span-12 px">
37
+ <k-directory-viewer></k-directory-viewer>
38
+ </div>
39
+ </div>
40
+
41
+
42
+
43
+ <h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
44
+ <h6>Extends <a href="./lazycomponent.html">LazyComponent</a></h6>
45
+ <h5>
46
+ <code>new DirectoryViewer()</code><br />
47
+ </h5>
48
+
49
+ <h3 id="requirements"><a href="#requirements" class="no-link">Requirements</a></h3>
50
+ <ul>
51
+ <li><a href="">class <code>LazyComponent</code></a></li>
52
+ <li><a href="">class <code>Component</code></a></li>
53
+ <li><a href="">class <code>Icon</code></a></li>
54
+ <li><a href="">util <code>element.js</code></a></li>
55
+ <li><a href="">util <code>watchDirectoryHandle.js</code></a></li>
56
+ </ul>
57
+
58
+ <h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
59
+ <h5><code>async selectDirectory()<i>: undefined</i></code></h5>
60
+ <p>Opens the directory selection dialog. When a dialog is selected it will render the directory's contents.</p>
61
+ <h5><code>open()<i>: undefined</i></code></h5>
62
+ <p>Opens the directory, this should only be called on auto-generated sub-directories.</p>
63
+ <h5><code>close()<i>: undefined</i></code></h5>
64
+ <p>Closes the directory, this should only be called on auto-generated sub-directories.</p>
65
+ <h5><code>toggleOpen()<i>: undefined</i></code></h5>
66
+ <p>Opens/Closes the directory, this should only be called on auto-generated sub-directories.</p>
67
+ <h5><code>clearDirectoryHandle()<i>: undefined</i></code></h5>
68
+ <p>Clears the handle that represents the user selected directory, allowing the user to choose a new directory.</p>
69
+
70
+ </main>
71
+ <script type="module" src="../kempo/components/Import.js"></script>
72
+ <script type="module" src="../kempo/components/DirectoryViewer.js"></script>
73
+ </body>
74
+ </html>
@@ -0,0 +1 @@
1
+ import LazyComponent from"./LazyComponent.js";import Component from"./Component.js";import"./Icon.js";import{onEvent,offEvent}from"../utils/element.js";import watchDirectoryHandle from"../utils/watchDirectoryHandle.js";const handle=Symbol(),selectDirectoryClickHandler=Symbol(),openToggleClickHandler=Symbol(),fileSelectedHandler=Symbol(),watchId=Symbol(),changeCallback=Symbol();export default class DirectoryViewer extends LazyComponent{constructor(e=!1){super(),this[handle]=e,this[selectDirectoryClickHandler]=()=>this.selectDirectory(),this[openToggleClickHandler]=()=>{this.root?this.clearDirectoryHandle():this.toggleOpen()},this[changeCallback]=()=>{this.updateContents()},this.slot="content",this.registerAttributes({root:!e,hasHandle:!!e,opened:!e}),this[watchId]=!1}async render(e){return!!await super.render(e)&&(onEvent(this.shadowRoot,"fileselected",this[fileSelectedHandler]),onEvent(this.shadowRoot.getElementById("selectDirectory"),"click",this[selectDirectoryClickHandler]),onEvent(this.shadowRoot.getElementById("openToggle"),"click",this[openToggleClickHandler]),this.renderDirectory(),!0)}async renderDirectory(){this[handle]&&this.rendered&&(this.innerHTML=this[handle].name,this.renderContents())}async renderContents(){if(this.querySelectorAll('[slot="content"]').forEach((e=>e.remove())),this.opened||this.root){for await(const e of this[handle].values())"directory"===e.kind&&this.appendChild(new DirectoryViewer(e,!1));for await(const e of this[handle].values())"file"===e.kind&&this.appendChild(new File(e));this[watchId]||(this[watchId]=watchDirectoryHandle(this[handle],this[changeCallback]))}else this[watchId]&&(clearInterval(this[watchId]),this[watchId]=!1)}disconnectedCallback(){super.disconnectedCallback(),offEvent(this.shadowRoot,"fileselected",this[fileSelectedHandler]),offEvent(this.shadowRoot.getElementById("selectDirectory"),"click",this[selectDirectoryClickHandler]),offEvent(this.shadowRoot.getElementById("openToggle"),"click",this[openToggleClickHandler])}attributeChangedCallback(e,t,n){"opened"===e&&t!==n&&(!n&&this.root?this.opened=!0:this.renderContents())}async selectDirectory(){window.showDirectoryPicker&&(this[handle]=await window.showDirectoryPicker(),this[handle]&&(this.hasHandle=!0,this.renderDirectory()))}open(){this.opened=!0}close(){this.root||(this.opened=!1)}toggleOpen(){this.root||(this.opened=!this.opened)}clearDirectoryHandle(){this[handle]=!1,this.hasHandle=!1,this.innerHTML=""}async updateContents(){if(this.opened||this.root){const e=new Set([...this.querySelectorAll("k-directory-viewer")]);for await(const t of this[handle].values())if("directory"===t.kind){const n=[...e].find((e=>t.isSameEntry(e[handle])&&t.name===e[handle].name));n?(this.appendChild(n),e.delete(n)):this.appendChild(new DirectoryViewer(t,!1))}e.forEach((e=>e.remove()));const t=new Set([...this.querySelectorAll("k-directory-viewer-file")]);for await(const e of this[handle].values())if("file"===e.kind){const n=[...t].find((t=>e.isSameEntry(t[handle])&&e.name===t[handle].name));n?(this.appendChild(n),t.delete(n)):this.appendChild(new File(e))}t.forEach((e=>e.remove())),this[watchId]||(this[watchId]=watchDirectoryHandle(this[handle],this[changeCallback]))}else this[watchId]&&(clearInterval(this[watchId]),this[watchId]=!1)}get shadowTemplate(){return`\n <button\n id="selectDirectory"\n >\n <slot name="selectButton">\n <k-icon name="folder-create"></k-icon> Select Directory\n </slot>\n </button>\n <button\n id="openToggle"\n class="no-btn"\n >\n <k-icon name="folder" id="folder-closed"></k-icon>\n <k-icon name="folder-open" id="folder-opened"></k-icon>\n <k-icon name="folder-clear" id="folder-clear"></k-icon>\n ${super.shadowTemplate}\n </button>\n <div id="tree">\n <slot name="content"></slot>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host([has-handle]) #selectDirectory,\n :host([opened]) #folder-closed,\n :host(:not([opened])) #folder-opened,\n :host(:not([has-handle])) #folder-closed,\n :host(:not([has-handle])) #folder-opened,\n :host(:not([has-handle])) #folder-clear,\n :host(:not([root])) #folder-clear,\n :host([root]) #folder-opened {\n display: none;\n }\n #openToggle {\n display: block;\n width: 100%;\n flex: 1 1 auto;\n }\n #tree {\n padding-left: var(--spacer);\n }\n #wrapper {\n display: flex;\n }\n #clearSelectedDirectory {\n flex: none;\n }\n `}static observedAttributes=[...LazyComponent.observedAttributes,"opened"];static watchInterval=1e3}window.customElements.define("k-directory-viewer",DirectoryViewer);const selectFileClickHandler=Symbol();export class File extends Component{constructor(e=!1){super(),this[handle]=e,this[selectFileClickHandler]=()=>this.select(),this.slot="content",this.registerAttribute("selected",!1)}async render(e){if(await super.render(e)){if(this[handle]){this.innerHTML=this[handle].name;const e=this[handle].name.split(".").pop(),t=this.shadowRoot.getElementById("extIcon");["png","gif","jpeg","jpg","tif","webp"].includes(e)?t.name="image":["txt","rtf","js","css","html"].includes(e)?t.name="file-text":t.name="file",onEvent(this.shadowRoot.getElementById("selectFile"),"click",this[selectFileClickHandler])}return!0}return!1}disconnectedCallback(){super.disconnectedCallback(),offEvent(this.shadowRoot.getElementById("selectFile"),"click",this[selectFileClickHandler])}select(){const e=this.closest("k-directory-viewer[root]").querySelector("k-directory-viewer-file[selected]");e!==this&&(e&&(e.selected=!1),this.selected=!0,this.dispatchEvent(new CustomEvent("fileselected",{detail:{fileHandle:this[handle],fileComponent:this},bubbles:!0,composed:!0})))}get shadowTemplate(){return`\n <button\n id="selectFile"\n class="no-btn"\n >\n <k-icon\n id="extIcon"\n name=""\n ></k-icon>\n ${super.shadowTemplate}\n </button>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n width: 100%;\n }\n :host([selected]) {\n background-color: var(--c_primary);\n color: var(--tc_on_primary);\n border-radius: var(--raidus);\n }\n #selectFile {\n width: 100%;\n }\n `}}window.customElements.define("k-directory-viewer-file",File);
@@ -1 +1 @@
1
- import Component from"./Component.js";const getIconByName=t=>{const e=async e=>{try{const n=await fetch(`${e}/${t}.svg`);if(200===n.status)return await n.text()}catch(t){console.log(t)}return!1};return new Promise((async(t,n)=>{let s;for(let t=0;t<Icon.pathToIcons.length&&!s;t++)s=await e(Icon.pathToIcons[t]);s?t(s):n("Icon not found")}))};export default class Icon extends Component{constructor(){super(),this.registerAttributes({src:"",name:""})}connectedCallback(){}async render(t){if(await super.render(t)){if(this.src){const t=await fetch(this.src);200===t.status?(this.innerHTML=await t.text(),this.fixSVG()):this.innerHTML.length||(this.innerHTML=Icon.fallback)}else if(this.name){const t=await getIconByName(this.name);t?(this.innerHTML=t,this.fixSVG()):this.innerHTML.length||(this.innerHTML=Icon.fallback)}else this.rendered=!1,this.innerHTML.length||(this.innerHTML=Icon.fallback);return!0}return!1}attributeChangedCallback(t,e,n){super.attributeChangedCallback(t,e,n),["src","name"].includes(t)&&n&&this.render(!0)}fixSVG(){const t=this.querySelector("svg");t.removeAttribute("width"),t.removeAttribute("height"),t.querySelectorAll("path, rect, circle").forEach((t=>{t.setAttribute("fill","currentColor")}))}get shadowStyles(){return"\n :host {\n display: inline-block;\n vertical-align: bottom;\n }\n ::slotted(svg){\n height: 1.35em;\n vertical-align: middle;\n }\n "}static observedAttributes=["name","src"];static pathToIcons=["/icons"];static fallback='\n <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currnetColor" d="M480-79q-16 0-30.5-6T423-102L102-423q-11-12-17-26.5T79-480q0-16 6-31t17-26l321-321q12-12 26.5-17.5T480-881q16 0 31 5.5t26 17.5l321 321q12 11 17.5 26t5.5 31q0 16-5.5 30.5T858-423L537-102q-11 11-26 17t-31 6Zm0-80 321-321-321-321-321 321 321 321Zm-40-281h80v-240h-80v240Zm40 120q17 0 28.5-11.5T520-360q0-17-11.5-28.5T480-400q-17 0-28.5 11.5T440-360q0 17 11.5 28.5T480-320Zm0-160Z"/></svg>\n '}window.customElements.define("k-icon",Icon);
1
+ import Component from"./Component.js";const cache={},getIconByPath=async t=>(cache[t]||(cache[t]=new Promise((async(e,n)=>{try{const n=await fetch(t);200===n.status&&e(await n.text())}catch(t){}return n()}))),await cache[t]),getIconByName=t=>{const e=async e=>getIconByPath(`${e}/${t}.svg`);return new Promise((async(t,n)=>{let s;for(let t=0;t<Icon.pathToIcons.length&&!s;t++)s=await e(Icon.pathToIcons[t]);s?t(s):n("Icon not found")}))};export default class Icon extends Component{constructor(){super(),this.registerAttributes({src:"",name:""})}connectedCallback(){}async render(t){if(await super.render(t)){if(this.src){const t=await getIconByPath(this.src);t?(this.innerHTML=t,this.fixSVG()):this.innerHTML.length||(this.innerHTML=Icon.fallback)}else if(this.name){const t=await getIconByName(this.name);t?(this.innerHTML=t,this.fixSVG()):this.innerHTML.length||(this.innerHTML=Icon.fallback)}else this.rendered=!1,this.innerHTML.length||(this.innerHTML=Icon.fallback);return!0}return!1}attributeChangedCallback(t,e,n){super.attributeChangedCallback(t,e,n),["src","name"].includes(t)&&n&&this.render(!0)}fixSVG(){const t=this.querySelector("svg");t.removeAttribute("width"),t.removeAttribute("height"),t.querySelectorAll("path, rect, circle").forEach((t=>{t.setAttribute("fill","currentColor")}))}get shadowStyles(){return"\n :host {\n display: inline-block;\n vertical-align: bottom;\n }\n ::slotted(svg){\n height: 1.35em;\n vertical-align: middle;\n }\n "}static observedAttributes=["name","src"];static pathToIcons=["/icons"];static fallback='\n <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currnetColor" d="M480-79q-16 0-30.5-6T423-102L102-423q-11-12-17-26.5T79-480q0-16 6-31t17-26l321-321q12-12 26.5-17.5T480-881q16 0 31 5.5t26 17.5l321 321q12 11 17.5 26t5.5 31q0 16-5.5 30.5T858-423L537-102q-11 11-26 17t-31 6Zm0-80 321-321-321-321-321 321 321 321Zm-40-281h80v-240h-80v240Zm40 120q17 0 28.5-11.5T520-360q0-17-11.5-28.5T480-400q-17 0-28.5 11.5T440-360q0 17 11.5 28.5T480-320Zm0-160Z"/></svg>\n '}window.customElements.define("k-icon",Icon);
@@ -1 +1 @@
1
- import Card from"./components/Card.js";import Component from"./components/Component.js";import Dialog from"./components/Dialog.js";import FocusCapture from"./components/FocusCapture.js";import Icon from"./components/Icon.js";import Import from"./components/Import.js";import LazyComponent from"./components/LazyComponent.js";import ReactiveComponent from"./components/ReactiveComponent.js";import ReactiveLazyComponent from"./components/ReactiveLazyComponent.js";import Resize from"./components/Resize.js";import Search from"./components/Search.js";import ShowMore from"./components/ShowMore.js";import SideMenu from"./components/SideMenu.js";import Split from"./components/Split.js";import Table from"./components/Table.js";import Tabs from"./components/Tabs.js";import Tags from"./components/Tags.js";import ThemeSwitcher from"./components/ThemeSwitcher.js";import Timestamp from"./components/Timestamp.js";import Toggle from"./components/Toggle.js";import cli from"./utils/cli.js";import cookie from"./utils/cookie.js";import debounce from"./utils/debounce.js";import drag from"./utils/drag.js";import element from"./utils/element.js";import formatTimestamp from"./utils/formatTimestamp.js";import fuzzyMatch from"./utils/fuzzyMatch.js";import getPreferredTheme from"./utils/getPreferredTheme.js";import loadCss from"./utils/loadCss.js";import loadScript from"./utils/loadScript.js";import object from"./utils/object.js";import string from"./utils/string.js";import toFunction from"./utils/toFunction.js";import type from"./utils/type.js";import waitFrames from"./utils/waitFrames.js";export default{Card:Card,Component:Component,Dialog:Dialog,FocusCapture:FocusCapture,Icon:Icon,Import:Import,LazyComponent:LazyComponent,ReactiveComponent:ReactiveComponent,ReactiveLazyComponent:ReactiveLazyComponent,Resize:Resize,Search:Search,ShowMore:ShowMore,SideMenu:SideMenu,Split:Split,Table:Table,Tabs:Tabs,Tags:Tags,ThemeSwitcher:ThemeSwitcher,Timestamp:Timestamp,Toggle:Toggle,cli:cli,cookie:cookie,debounce:debounce,drag:drag,element:element,formatTimestamp:formatTimestamp,fuzzyMatch:fuzzyMatch,getPreferredTheme:getPreferredTheme,loadCss:loadCss,loadScript:loadScript,object:object,string:string,toFunction:toFunction,type:type,waitFrames:waitFrames};
1
+ import Card from"./components/Card.js";import Component from"./components/Component.js";import Dialog from"./components/Dialog.js";import DirectoryViewer from"./components/DirectoryViewer.js";import FocusCapture from"./components/FocusCapture.js";import Icon from"./components/Icon.js";import Import from"./components/Import.js";import LazyComponent from"./components/LazyComponent.js";import ReactiveComponent from"./components/ReactiveComponent.js";import ReactiveLazyComponent from"./components/ReactiveLazyComponent.js";import Resize from"./components/Resize.js";import Search from"./components/Search.js";import ShowMore from"./components/ShowMore.js";import SideMenu from"./components/SideMenu.js";import Split from"./components/Split.js";import Table from"./components/Table.js";import Tabs from"./components/Tabs.js";import Tags from"./components/Tags.js";import ThemeSwitcher from"./components/ThemeSwitcher.js";import Timestamp from"./components/Timestamp.js";import Toggle from"./components/Toggle.js";import cli from"./utils/cli.js";import cookie from"./utils/cookie.js";import debounce from"./utils/debounce.js";import drag from"./utils/drag.js";import element from"./utils/element.js";import files from"./utils/files.js";import formatTimestamp from"./utils/formatTimestamp.js";import fuzzyMatch from"./utils/fuzzyMatch.js";import getPreferredTheme from"./utils/getPreferredTheme.js";import loadCss from"./utils/loadCss.js";import loadScript from"./utils/loadScript.js";import object from"./utils/object.js";import string from"./utils/string.js";import toFunction from"./utils/toFunction.js";import type from"./utils/type.js";import waitFrames from"./utils/waitFrames.js";import watchDirectoryHandle from"./utils/watchDirectoryHandle.js";export default{Card:Card,Component:Component,Dialog:Dialog,DirectoryViewer:DirectoryViewer,FocusCapture:FocusCapture,Icon:Icon,Import:Import,LazyComponent:LazyComponent,ReactiveComponent:ReactiveComponent,ReactiveLazyComponent:ReactiveLazyComponent,Resize:Resize,Search:Search,ShowMore:ShowMore,SideMenu:SideMenu,Split:Split,Table:Table,Tabs:Tabs,Tags:Tags,ThemeSwitcher:ThemeSwitcher,Timestamp:Timestamp,Toggle:Toggle,cli:cli,cookie:cookie,debounce:debounce,drag:drag,element:element,files:files,formatTimestamp:formatTimestamp,fuzzyMatch:fuzzyMatch,getPreferredTheme:getPreferredTheme,loadCss:loadCss,loadScript:loadScript,object:object,string:string,toFunction:toFunction,type:type,waitFrames:waitFrames,watchDirectoryHandle:watchDirectoryHandle};
@@ -0,0 +1 @@
1
+ export const getDir=async()=>{};let id,interval=1e3;const watchers=new Map,poll=()=>{};export const watchDir=async(t,c,e=1e3)=>{};export const unwatchDir=async(t,c)=>{};
@@ -0,0 +1 @@
1
+ const serializeDir=async a=>{const e=[];for await(const{kind:i,name:t}of a.values())e.push(`${i}:${t}`);return e.join("|")};export default async(a,e,i=1e3)=>{let t=await serializeDir(a);return setInterval((async()=>{const i=await serializeDir(a);i!=t&&(t=i,e())}),i)};
@@ -40,9 +40,9 @@
40
40
  <a href="../components/reactivelazycomponent.html">ReactiveLazyComponent</a>
41
41
  <a href="../components/focuscapture.html">FocusCapture</a>
42
42
  <hr />
43
- <a href="../components/codeeditor.html">Code Editor</a>
44
43
  <a href="../components/card.html">Card</a>
45
44
  <a href="../components/dialog.html">Dialog</a>
45
+ <a href="../components/directory-viewer.html">Directory Viewer</a>
46
46
  <a href="../components/icon.html">Icon</a>
47
47
  <a href="../components/import.html">Import</a>
48
48
  <a href="../components/resize.html">Resize</a>
@@ -52,7 +52,6 @@
52
52
  <a href="../components/table.html">Table</a>
53
53
  <a href="../components/tabs.html">Tabs</a>
54
54
  <a href="../components/toggle.html">Toggle</a>
55
- <a href="../components/websnippeteditor.html">Web Snippet Editor</a>
56
55
  </div>
57
56
  </menu>
58
57
  </k-side-menu>
package/docs/nav.inc.html CHANGED
@@ -43,6 +43,7 @@
43
43
  <a href="./components/codeeditor.html">Code Editor</a>
44
44
  <a href="./components/card.html">Card</a>
45
45
  <a href="./components/dialog.html">Dialog</a>
46
+ <a href="./components/directory-viewer.html">Directory Viewer</a>
46
47
  <a href="./components/icon.html">Icon</a>
47
48
  <a href="./components/import.html">Import</a>
48
49
  <a href="./components/resize.html">Resize</a>
package/docs/search.json CHANGED
@@ -1,4 +1,35 @@
1
1
  {
2
+ "/components/component.html": {
3
+ "name": "Components > Component",
4
+ "terms": [
5
+ "component",
6
+ "base",
7
+ "root",
8
+ "framework",
9
+ "parent"
10
+ ]
11
+ },
12
+ "/components/lazycomponent.html": {
13
+ "name": "Components > LazyComponent",
14
+ "terms": [
15
+ "lazy",
16
+ "component",
17
+ "base",
18
+ "root",
19
+ "framework",
20
+ "parent"
21
+ ]
22
+ },
23
+ "/components/directoryviewer.html": {
24
+ "name": "Components > DirectoryViewer",
25
+ "terms": [
26
+ "directory",
27
+ "viewer",
28
+ "pwa",
29
+ "folder",
30
+ "file"
31
+ ]
32
+ },
2
33
  "/components/resize.html": {
3
34
  "name": "Components > Resize",
4
35
  "terms": [
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M200-200h560v-367L567-760H200v560Zm0 80q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h400l240 240v400q0 33-23.5 56.5T760-120H200Zm80-160h400v-80H280v80Zm0-160h400v-80H280v80Zm0-160h280v-80H280v80Zm-80 400v-560 560Z"/></svg>
package/icons/file.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M480-280q66 0 113-47t47-113q0-66-47-113t-113-47q-66 0-113 47t-47 113q0 66 47 113t113 47Zm-46-248q11-5 22-8.5t24-3.5q42 0 71 29t29 71q0 13-3.5 24t-8.5 22L434-528Zm46 188q-42 0-71-29t-29-71q0-13 3.5-24t8.5-22l134 134q-11 5-22 8.5t-24 3.5ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640v400q0 33-23.5 56.5T800-160H160Zm0-80h640v-400H447l-80-80H160v480Zm0 0v-480 480Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M560-320h80v-80h80v-80h-80v-80h-80v80h-80v80h80v80ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640v400q0 33-23.5 56.5T800-160H160Zm0-80h640v-400H447l-80-80H160v480Zm0 0v-480 480Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640H447l-80-80H160v480l96-320h684L837-217q-8 26-29.5 41.5T760-160H160Zm84-80h516l72-240H316l-72 240Zm0 0 72-240-72 240Zm-84-400v-80 80Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h240l80 80h320q33 0 56.5 23.5T880-640v400q0 33-23.5 56.5T800-160H160Zm0-80h640v-400H447l-80-80H160v480Zm0 0v-480 480Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm40-80h480L570-480 450-320l-90-120-120 160Zm-40 80v-560 560Z"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "A web component library",
6
6
  "main": "dist/index.js",
package/scripts/docs.js CHANGED
@@ -21,8 +21,6 @@ const {
21
21
  src = false
22
22
  } = options;
23
23
 
24
- console.log(port, build, src);
25
-
26
24
  if(build){
27
25
  runChildNodeProcess(join(__dirname, 'build.js'));
28
26
  }
@@ -32,6 +30,15 @@ const app = express();
32
30
  if(src){
33
31
  app.use('/kempo', express.static('src'));
34
32
  app.use('/icons', express.static('icons'));
33
+ app.get('/kempo/kempo-styles.css', (req, res) => {
34
+ res.set('Cache-Control', 'no-cache');
35
+ res.sendFile(join(__dirname, '../src/kempo-styles.css'));
36
+ })
37
+ } else {
38
+ app.get('/kempo/kempo-styles.css', (req, res) => {
39
+ res.set('Cache-Control', 'private');
40
+ res.sendFile(join(__dirname, '../docs/kempo/kempo-styles.css'));
41
+ });
35
42
  }
36
43
  app.use(express.static('docs'));
37
44
 
@@ -0,0 +1,314 @@
1
+ import LazyComponent from './LazyComponent.js';
2
+ import Component from './Component.js';
3
+ import './Icon.js';
4
+ import {
5
+ onEvent,
6
+ offEvent
7
+ } from '../utils/element.js';
8
+ import watchDirectoryHandle from '../utils/watchDirectoryHandle.js';
9
+
10
+ const handle = Symbol(),
11
+ selectDirectoryClickHandler = Symbol(),
12
+ openToggleClickHandler = Symbol(),
13
+ fileSelectedHandler = Symbol(),
14
+ watchId = Symbol(),
15
+ changeCallback = Symbol();
16
+ export default class DirectoryViewer extends LazyComponent {
17
+ constructor(_handle = false){
18
+ super();
19
+
20
+ /* Private Members */
21
+ this[handle] = _handle;
22
+
23
+ /* Private Methods */
24
+ this[selectDirectoryClickHandler] = () => this.selectDirectory();
25
+ this[openToggleClickHandler] = () => {
26
+ if(!this.root){
27
+ this.toggleOpen();
28
+ } else {
29
+ this.clearDirectoryHandle();
30
+ }
31
+ }
32
+ this[changeCallback] = () => {
33
+ this.updateContents();
34
+ }
35
+
36
+ /* Init */
37
+ this.slot = 'content';
38
+ this.registerAttributes({
39
+ root: !_handle,
40
+ hasHandle: !!_handle,
41
+ opened: !_handle
42
+ });
43
+ this[watchId] = false;
44
+ }
45
+
46
+ /* Lifecycle Callbacks */
47
+ async render(force){
48
+ if(await super.render(force)){
49
+ onEvent(this.shadowRoot, 'fileselected', this[fileSelectedHandler]);
50
+ onEvent(this.shadowRoot.getElementById('selectDirectory'), 'click', this[selectDirectoryClickHandler]);
51
+ onEvent(this.shadowRoot.getElementById('openToggle'), 'click', this[openToggleClickHandler]);
52
+ this.renderDirectory();
53
+ return true;
54
+ }
55
+ return false;
56
+ }
57
+ async renderDirectory(){
58
+ if(!this[handle] || !this.rendered) return;
59
+ this.innerHTML = this[handle].name;
60
+ this.renderContents();
61
+ }
62
+ async renderContents(){
63
+ this.querySelectorAll('[slot="content"]').forEach(e=>e.remove());
64
+ if(this.opened || this.root){
65
+ for await(const entry of this[handle].values()){
66
+ if(entry.kind === 'directory'){
67
+ this.appendChild(new DirectoryViewer(entry, false));
68
+ }
69
+ }
70
+ for await(const entry of this[handle].values()){
71
+ if(entry.kind === 'file'){
72
+ this.appendChild(new File(entry));
73
+ }
74
+ }
75
+ if(!this[watchId]) this[watchId] = watchDirectoryHandle(this[handle], this[changeCallback]);
76
+ } else {
77
+ if(this[watchId]){
78
+ clearInterval(this[watchId]);
79
+ this[watchId] = false;
80
+ }
81
+ }
82
+ }
83
+ disconnectedCallback(){
84
+ super.disconnectedCallback();
85
+ offEvent(this.shadowRoot, 'fileselected', this[fileSelectedHandler]);
86
+ offEvent(this.shadowRoot.getElementById('selectDirectory'), 'click', this[selectDirectoryClickHandler]);
87
+ offEvent(this.shadowRoot.getElementById('openToggle'), 'click', this[openToggleClickHandler]);
88
+ }
89
+ attributeChangedCallback(n, oV, nV){
90
+ if(n === 'opened' && oV !== nV){
91
+ if(!nV && this.root){
92
+ this.opened = true; // root must be opened
93
+ } else {
94
+ this.renderContents();
95
+ }
96
+ }
97
+ }
98
+
99
+
100
+ /* Public Methods */
101
+ async selectDirectory(){
102
+ if(window.showDirectoryPicker){
103
+ this[handle] = await window.showDirectoryPicker();
104
+ if(this[handle]){
105
+ this.hasHandle = true;
106
+ this.renderDirectory();
107
+ }
108
+ }
109
+ }
110
+ open(){
111
+ this.opened = true;
112
+ }
113
+ close(){
114
+ if(!this.root) this.opened = false;
115
+ }
116
+ toggleOpen(){
117
+ if(!this.root) this.opened = !this.opened;
118
+ }
119
+ clearDirectoryHandle(){
120
+ this[handle] = false;
121
+ this.hasHandle = false;
122
+ this.innerHTML = '';
123
+ }
124
+ async updateContents(){
125
+ if(this.opened || this.root){
126
+ const existingSubDirSet = new Set([...this.querySelectorAll('k-directory-viewer')]);
127
+ for await(const entry of this[handle].values()){
128
+ if(entry.kind === 'directory'){
129
+ const existingSubDirsArray = [...existingSubDirSet];
130
+ const $existing = existingSubDirsArray.find(($e)=>entry.isSameEntry($e[handle])&&entry.name===$e[handle].name);
131
+ if($existing){
132
+ this.appendChild($existing)
133
+ existingSubDirSet.delete($existing);
134
+ } else this.appendChild(new DirectoryViewer(entry, false));
135
+ }
136
+ }
137
+ existingSubDirSet.forEach($e=>$e.remove()); // remove unused dirs
138
+
139
+ const existingFileSet = new Set([...this.querySelectorAll('k-directory-viewer-file')]);
140
+ for await(const entry of this[handle].values()){
141
+ if(entry.kind === 'file'){
142
+ const existingFileArray = [...existingFileSet];
143
+ const $existing = existingFileArray.find(($e)=>entry.isSameEntry($e[handle])&&entry.name===$e[handle].name);
144
+ if($existing){
145
+ this.appendChild($existing)
146
+ existingFileSet.delete($existing);
147
+ } else this.appendChild(new File(entry));
148
+ }
149
+ }
150
+ existingFileSet.forEach($e=>$e.remove()); // remove unused files
151
+ if(!this[watchId]) this[watchId] = watchDirectoryHandle(this[handle], this[changeCallback]);
152
+ } else {
153
+ if(this[watchId]){
154
+ clearInterval(this[watchId]);
155
+ this[watchId] = false;
156
+ }
157
+ }
158
+ }
159
+
160
+ /* Shadow DOM */
161
+ get shadowTemplate(){
162
+ return /*html*/`
163
+ <button
164
+ id="selectDirectory"
165
+ >
166
+ <slot name="selectButton">
167
+ <k-icon name="folder-create"></k-icon> Select Directory
168
+ </slot>
169
+ </button>
170
+ <button
171
+ id="openToggle"
172
+ class="no-btn"
173
+ >
174
+ <k-icon name="folder" id="folder-closed"></k-icon>
175
+ <k-icon name="folder-open" id="folder-opened"></k-icon>
176
+ <k-icon name="folder-clear" id="folder-clear"></k-icon>
177
+ ${super.shadowTemplate}
178
+ </button>
179
+ <div id="tree">
180
+ <slot name="content"></slot>
181
+ </div>
182
+ `;
183
+ }
184
+ get shadowStyles(){
185
+ return /*css*/`
186
+ ${super.shadowStyles}
187
+ :host([has-handle]) #selectDirectory,
188
+ :host([opened]) #folder-closed,
189
+ :host(:not([opened])) #folder-opened,
190
+ :host(:not([has-handle])) #folder-closed,
191
+ :host(:not([has-handle])) #folder-opened,
192
+ :host(:not([has-handle])) #folder-clear,
193
+ :host(:not([root])) #folder-clear,
194
+ :host([root]) #folder-opened {
195
+ display: none;
196
+ }
197
+ #openToggle {
198
+ display: block;
199
+ width: 100%;
200
+ flex: 1 1 auto;
201
+ }
202
+ #tree {
203
+ padding-left: var(--spacer);
204
+ }
205
+ #wrapper {
206
+ display: flex;
207
+ }
208
+ #clearSelectedDirectory {
209
+ flex: none;
210
+ }
211
+ `;
212
+ }
213
+
214
+ static observedAttributes = [
215
+ ...LazyComponent.observedAttributes,
216
+ 'opened'
217
+ ];
218
+
219
+ static watchInterval = 1000;
220
+ }
221
+ window.customElements.define('k-directory-viewer', DirectoryViewer);
222
+
223
+ const selectFileClickHandler = Symbol();
224
+ export class File extends Component {
225
+ constructor(_handle = false){
226
+ super();
227
+
228
+ /* Private Members */
229
+ this[handle] = _handle;
230
+
231
+ /* Private Methods */
232
+ this[selectFileClickHandler] = () => this.select();
233
+
234
+ /* Init */
235
+ this.slot = 'content';
236
+ this.registerAttribute('selected', false);
237
+ }
238
+
239
+ /* Lifecycle Callbacks */
240
+ async render(force){
241
+ if(await super.render(force)){
242
+ if(this[handle]){
243
+ this.innerHTML = this[handle].name;
244
+ const ext = this[handle].name.split('.').pop();
245
+ const $extIcon = this.shadowRoot.getElementById('extIcon');
246
+ if(['png', 'gif', 'jpeg', 'jpg', 'tif', 'webp'].includes(ext)){
247
+ $extIcon.name = 'image';
248
+ } else if(['txt', 'rtf', 'js', 'css', 'html',].includes(ext)){
249
+ $extIcon.name = 'file-text';
250
+ } else {
251
+ $extIcon.name = 'file';
252
+ }
253
+ onEvent(this.shadowRoot.getElementById('selectFile'), 'click', this[selectFileClickHandler]);
254
+ }
255
+ return true;
256
+ }
257
+ return false;
258
+ }
259
+ disconnectedCallback(){
260
+ super.disconnectedCallback();
261
+ offEvent(this.shadowRoot.getElementById('selectFile'), 'click', this[selectFileClickHandler]);
262
+ }
263
+
264
+ /* Public Methods */
265
+ select(){
266
+ const $currentSelection = this.closest('k-directory-viewer[root]').querySelector('k-directory-viewer-file[selected]');
267
+ if($currentSelection === this) return;
268
+ if($currentSelection) $currentSelection.selected = false;
269
+ this.selected = true;
270
+ this.dispatchEvent(new CustomEvent('fileselected', {
271
+ detail: {
272
+ fileHandle: this[handle],
273
+ fileComponent: this
274
+ },
275
+ bubbles: true,
276
+ composed: true
277
+ }));
278
+ }
279
+
280
+
281
+ /* Shadow DOM */
282
+ get shadowTemplate(){
283
+ return /*html*/`
284
+ <button
285
+ id="selectFile"
286
+ class="no-btn"
287
+ >
288
+ <k-icon
289
+ id="extIcon"
290
+ name=""
291
+ ></k-icon>
292
+ ${super.shadowTemplate}
293
+ </button>
294
+ `;
295
+ }
296
+ get shadowStyles(){
297
+ return /*css*/`
298
+ ${super.shadowStyles}
299
+ :host {
300
+ display: block;
301
+ width: 100%;
302
+ }
303
+ :host([selected]) {
304
+ background-color: var(--c_primary);
305
+ color: var(--tc_on_primary);
306
+ border-radius: var(--raidus);
307
+ }
308
+ #selectFile {
309
+ width: 100%;
310
+ }
311
+ `
312
+ }
313
+ }
314
+ window.customElements.define('k-directory-viewer-file', File);
@@ -1,15 +1,21 @@
1
1
  import Component from './Component.js';
2
2
 
3
+ const cache = {};
4
+
5
+ const getIconByPath = async (path) => {
6
+ if(!cache[path]){
7
+ cache[path] = new Promise(async (resolve, reject)=>{
8
+ try {
9
+ const response = await fetch(path);
10
+ if(response.status === 200) resolve(await (response).text());
11
+ } catch(e){ }
12
+ return reject();
13
+ });
14
+ }
15
+ return await cache[path];
16
+ }
3
17
  const getIconByName = (name) => {
4
- const tryDir = async (dir) => {
5
- try {
6
- const response = await fetch(`${dir}/${name}.svg`);
7
- if(response.status === 200) return await (response).text();
8
- } catch(e){
9
- console.log(e);
10
- }
11
- return false;
12
- }
18
+ const tryDir = async (dir) => getIconByPath(`${dir}/${name}.svg`);
13
19
 
14
20
  return new Promise( async (resolve, reject) => {
15
21
  let svg;
@@ -40,9 +46,9 @@ export default class Icon extends Component {
40
46
  async render(force){
41
47
  if(await super.render(force)){
42
48
  if(this.src){
43
- const response = await fetch(this.src);
44
- if(response.status === 200){
45
- this.innerHTML = await (response).text();
49
+ const svg = await getIconByPath(this.src);
50
+ if(svg){
51
+ this.innerHTML = svg;
46
52
  this.fixSVG();
47
53
  } else if(!this.innerHTML.length) this.innerHTML = Icon.fallback;
48
54
  } else if(this.name){
@@ -0,0 +1,15 @@
1
+ export const getDir = async () => {
2
+
3
+ }
4
+ let id,
5
+ interval = 1000;
6
+ const watchers = new Map();
7
+ const poll = () => {
8
+
9
+ };
10
+ export const watchDir = async (dirHandle, callback, interval = 1000) => {
11
+
12
+ }
13
+ export const unwatchDir = async (dirHandle, callback) => {
14
+
15
+ }
@@ -0,0 +1,18 @@
1
+ const serializeDir = async (dirHandle) => {
2
+ const entrySerializations = [];
3
+ for await(const {kind,name} of dirHandle.values()){
4
+ entrySerializations.push(`${kind}:${name}`);
5
+ }
6
+ return entrySerializations.join('|');
7
+ }
8
+
9
+ export default async (dirHandle, callback, timer = 1000) => {
10
+ let lastSerialization = await serializeDir(dirHandle);
11
+ return setInterval(async () => {
12
+ const newSerialization = await serializeDir(dirHandle);
13
+ if(newSerialization != lastSerialization){
14
+ lastSerialization = newSerialization;
15
+ callback();
16
+ }
17
+ }, timer);
18
+ }