├── .gitignore ├── LICENSE ├── Ontology.md ├── README.md ├── assets ├── linked-bookmarks.css ├── menu-dropdown.css ├── modalButton.css └── solid-rainbow.png ├── demo.html ├── demo ├── draggable │ ├── dragNdrop.html │ ├── draggable-js.html │ ├── draggable.html │ ├── draggable.ttl │ └── test-data.ttl ├── grid.html ├── login.html ├── selectorPanel.html └── to-do.md ├── dist ├── main.js ├── solid-ui-components.bundle.js ├── solid-ui-components.bundle.js.map └── umd │ ├── databrowser.js │ ├── index.js │ ├── mimer.min.js │ ├── model │ ├── profile.js │ ├── rss.js │ └── sparql.js │ ├── template.js │ ├── user.js │ └── utils.js ├── package-lock.json ├── package.json ├── src ├── databrowser.js ├── default-templates.js ├── default-templates.ttl ├── index.js ├── login.js ├── mimer.min.js ├── model │ ├── dataSource.js │ ├── feed.js │ ├── profile.js │ ├── rss.js │ └── sparql.js ├── template.js ├── templates │ └── rolodex.ttl ├── user.js ├── utils-bak.js ├── utils.js └── view │ ├── accordion-menu.js │ ├── accordion.js │ ├── anchorList.js │ ├── app-new.js │ ├── app-old.js │ ├── app.js │ ├── app.solidos.js │ ├── bookmarkTree.js │ ├── button.js │ ├── buttonListMenu.js │ ├── buttonListMenu.old.js │ ├── componentButton.js │ ├── draggable.js │ ├── extras │ ├── form.js │ ├── linked-bookmarks.js │ ├── mediaList.js │ ├── menu.js │ ├── menu.new.js │ ├── menuMenu.js │ ├── modal.js │ ├── optionSelector.js │ ├── reader.js │ ├── searchButton.js │ ├── selector.js │ ├── selector.new.js │ ├── selector.org.js │ ├── selectorPanel.js │ ├── slideshow.js │ ├── solidOSpage.js │ ├── table.js │ ├── tabs-old.js │ └── tabs.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log* 2 | yarn-debug.log* 3 | yarn-error.log* 4 | node_modules/ 5 | .npm 6 | coverage/ 7 | test-folder/ 8 | SolidOS/ 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jeff Zucker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Ontology.md: -------------------------------------------------------------------------------- 1 | # Notes on UI ontology needs of Solid UI Components 2 | 3 | * Classes 4 | * ui:Component 5 | * ui:Form 6 | * ui:DataSource 7 | * ui:SparqlQuery 8 | * ui:Collection 9 | * ui:Link 10 | * ui:Container 11 | * ui:Feed 12 | * ui:Template 13 | * ui:AppTemplate 14 | * ui:AccordionTemplate 15 | * ui:TableTemplate 16 | * ui:SlideshowTemplate 17 | * ui:ModalTemplate 18 | * ui:MenuTemplate 19 | * ui:TabsetTemplate 20 | * Properties 21 | * Domain ui:Component 22 | * ui:template 23 | * ui:dataSource 24 | * Domain ui:Form 25 | * ui:dataSource (the Form subject) 26 | * ui:template (the From field definitions) 27 | * ui:results (where to place results if different from subject) 28 | * Domain ui:Template 29 | * ui:before (top of template pattern) 30 | * ui:recurring (recurring portion of template pattern) 31 | * ui:after (bottom portion of template pattern) 32 | * Domain ui:AppTemplate 33 | * ui:logo 34 | * ui:title 35 | * ui:menu 36 | * ui:stylesheet 37 | * ui:initialContent 38 | * Domain ui:SlideshowTemplate 39 | * ui:delay 40 | * ui:autoplay 41 | * Domain ui:Collection 42 | * ui:parts (x y z) where x/y/z are any ui:Component 43 | * Domain ui:SparqlQuery 44 | * ui:endpoint (can be multiple) 45 | * ui:query 46 | * Domain ui:Link 47 | * ui:content (use instead of href for direct inclusion of content) 48 | * ui:href 49 | * ui:acceptType [a media-type] 50 | * ui:outputFormat [a media-type] (e.g. to display markdown as HTML or HTML as text/plain) 51 | * ui:needsProxy 52 | * ui:canBeIframed 53 | 54 | These existing properties may be used on most components 55 | ui:label, ui:background, ui:color, ui:orientation, ui:position, ui:height, ui:width 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solid UI Components - WiP 2 | 3 | Use RDF to create data-driven apps, web pages, and user interfaces 4 | 5 | This is a *Work in Progress*. It works fine but I have not finalized the ontology, cleaned the code or re-added things like forms which worked in a previous version. 6 | 7 | Please see this [demo](https://jeff-zucker.github.io/solid-ui-components/demo.html) for simple examples and [this website](https://jeff-zucker.solidcommunity.net/sp4/) for a website that is entirely built using this library. 8 | 9 | ## Prerequisites 10 | 11 | You need to include mashlib and comunica in your HTML. The easiest way is using CDNs. See the sample.html. 12 | 13 | ## Overview 14 | 15 | A *Component* combines a way to get data (a *DataSource*) with a way to display it (a *Template*). 16 | ```turtle 17 | <#MyMenu> 18 | a ui:Component ; 19 | ui:dataSource [a ui:DataSource] ; 20 | ui:template [a ui:Template] . 21 | ``` 22 | There are built in templates for many common interactive user interface widgets - menus, tabs, accordions, tables, with slideshows, forms, and more coming soon. For built-in templates, you can simply name the template and optionally use template predicates (see below) to customize the template. 23 | ``` 24 | <#MyBuiltInTemplate> 25 | a ui:MenuTemplate ; 26 | ui:orientation "horizontal" ; 27 | ui:position "right" . 28 | ``` 29 | You can also create any template you'd like using Javascript template strings. For example this would create a list, populating each item with a row of data from the dataSource. 30 | ``` 31 | <#MyCustomTemplate> 32 | a ui:Template; 33 | ui:before "" . 36 | ``` 37 | There are three basic kinds of DataSources - SparqlQuery, Collection, and Link. 38 | ``` 39 | <#MySparqlDataSource> 40 | a ui:SparqlQuery ; 41 | endpoint ; 42 | query """My SPARQL Query String""" . 43 | 44 | <#MyCollectionDataSource> 45 | a ui:Collection ; 46 | ui:parts ( <#A> <#B> <#C> ) . 47 | 48 | <#MyLinkDataSource> 49 | a ui:Link ; 50 | ui:label "MyLabel" ; 51 | ui:acceptFormat "application/json" ; 52 | ui:href . 53 | ``` 54 | Components may be included anywhere in a web page like so: 55 | ```html 56 | Stuff Before 57 |
58 | Stuff After 59 | ``` 60 | Coponents can call other components, so it's quite possible to have an entire site or app generated from a single HTML insert. While the components can make use of Javascript you supply, no coding is necessary - the components have interactivity built in. 61 | 62 | Components can also be included as a library and called from a script. For example, this returns a fully active DOM element. 63 | ```Javascript 64 | const element = await solidUIC.processComponent('component-URL'); 65 | ``` 66 | You can now walk the DOM tree of the element or insert it into a page. 67 | 68 | © Jeff Zucker, 2021 all rights reserved; May be freely distributed under an MIT license. 69 | -------------------------------------------------------------------------------- /assets/linked-bookmarks.css: -------------------------------------------------------------------------------- 1 | /* 2 | body { 3 | overflow:hidden 4 | font-size:16px; 5 | font-family:"Helvetica Neue",Helvetica,Arial,sans-serif; 6 | } 7 | */ 8 | #ocbStart { 9 | display:block; 10 | display:none !important; 11 | margin-bottom:0.5rem; 12 | margin-top:0.5rem; 13 | // margin-left:-0.25rem; 14 | } 15 | .bookmarkTree #mainMenu { 16 | display:block; 17 | text-align:right; 18 | width:100%; 19 | } 20 | .bookmarkTree #sidebar { 21 | position:absolute; 22 | top:1vh; 23 | left:1vw; 24 | bottom:0; 25 | width:24vw; 26 | margin-right:0; 27 | padding-right:0; 28 | overflow-y:scroll; 29 | } 30 | .bookmarkTree #MyDisplay, .bookmarkTree #MyDisplay iframe { 31 | position:absolute; 32 | top:0; 33 | left:25vw; 34 | bottom:0; 35 | height:100%; 36 | width:75vw; 37 | border:none; 38 | overflow-y:scroll; 39 | } 40 | /* 41 | body.collapsed #sidebar { width:4vw; } 42 | body.collapsed #MyDisplay { width:96vw; left:4vw;} 43 | body.collapsed #bmarkMenu { display:none; } 44 | */ 45 | .bookmarkTree .item { 46 | margin-top:0.4em; 47 | } 48 | ul.bookmarkTree { 49 | list-style-type : none; 50 | margin:0; 51 | padding:0; 52 | margin-left:0.25em; 53 | } 54 | ul.bookmarkTree ul { 55 | list-style-type : none; 56 | margin-left:0.75em !important; 57 | padding-left:0; 58 | } 59 | .bookmarkTree li.item { 60 | margin-bottom:0; 61 | } 62 | 63 | /* Remove default bullets */ 64 | .bookmarkTree #myUL ul { 65 | list-style-type: none; 66 | padding-left:1em; 67 | } 68 | 69 | /* Remove margins and padding from the parent ul */ 70 | .bookmarkTree #myUL { 71 | margin: 0; 72 | padding: 0; 73 | } 74 | /* Style the caret/arrow */ 75 | .bookmarkTree .caret { 76 | cursor: pointer; 77 | user-select: none; /* Prevent text selection */ 78 | } 79 | 80 | .bookmarkTree .item { 81 | cursor: pointer; 82 | user-select: none; /* Prevent text selection */ 83 | } 84 | .bookmarkTree .item::before { 85 | content: "\274f"; 86 | content: "\2740"; 87 | color: gray; 88 | display: inline-block; 89 | margin-right: 6px; 90 | } 91 | 92 | /* Create the caret/arrow with a unicode, and style it */ 93 | .bookmarkTree .caret::before { 94 | content: "\25B6"; 95 | /* content: "\266C"; // music notes */ 96 | color: gray; 97 | display: inline-block; 98 | margin-right: 6px; 99 | } 100 | #ocbStart.caret::before { 101 | content:""; 102 | } 103 | #ocbStart { 104 | font-weight:bold; 105 | } 106 | 107 | /* Rotate the caret/arrow icon when clicked on (using JavaScript) */ 108 | .bookmarkTree .caret-down::before { 109 | transform: rotate(90deg); 110 | } 111 | 112 | /* Hide the nested list */ 113 | .bookmarkTree .nested { 114 | display: none; 115 | } 116 | 117 | /* Show the nested list when the user clicks on the caret/arrow (with JavaScript) */ 118 | .bookmarkTree .active { 119 | display: block; 120 | } 121 | -------------------------------------------------------------------------------- /assets/menu-dropdown.css: -------------------------------------------------------------------------------- 1 | .solid-uic-dropdown-menu { 2 | position:absolute; 3 | top:0; 4 | } 5 | 6 | .solid-uic-dropdown-menu *, 7 | .solid-uic-dropdown-menu *:before, 8 | .solid-uic-dropdown-menu *:after { 9 | font-family:Sans-Serif; 10 | box-sizing: inherit; 11 | } 12 | .solid-uic-dropdown-menu ul { 13 | Padding: 0; 14 | margin: 0; 15 | list-style: none; 16 | position: relative; 17 | } 18 | .solid-uic-dropdown-menu ul li { 19 | display:inline-block; 20 | z-index:10001; 21 | } 22 | .solid-uic-dropdown-menu ul ul { 23 | display: none; 24 | position: absolute; 25 | top: 40px; 26 | } 27 | .solid-uic-dropdown-menu .main, h2 { 28 | text-align:left; 29 | } 30 | .solid-uic-dropdown-menu h2 { 31 | font-size:1.4rem; 32 | padding-left:1rem; 33 | } 34 | .solid-uic-dropdown-menu .leftColumn { 35 | padding-left:1rem; 36 | } 37 | .solid-uic-dropdown-menu button { 38 | // background:"transparent"; 39 | } 40 | .solid-uic-dropdown-menu button.selected, button:hover { 41 | // background:"#ddd"; 42 | } 43 | /* 44 | .solid-uic-dropdown-menu li > a:after { content: ' ▼'; } 45 | .solid-uic-dropdown-menu li > a:only-child:after { content: ''; } 46 | .solid-uic-dropdown-menu li > span:after { content: ' ▼'; font-size:0.85em;} 47 | .solid-uic-dropdown-menu li > span:after { content: ' ▼'; font-size:0.85em;} 48 | .solid-uic-dropdown-menu li > span:only-child:after { content: ''; } 49 | */ 50 | .solid-uic-dropdown-menu li > span { 51 | margin-right:0.5rem; 52 | margin-left:0.5rem; 53 | } 54 | .solid-uic-dropdown-menu ul li:hover > ul { 55 | display:block; 56 | } 57 | .solid-uic-dropdown-menu ul ul li { 58 | width:230px !important; 59 | float:none; 60 | display:list-item; 61 | position: relative; 62 | text-align:center; 63 | z-index:10001; 64 | } 65 | .solid-uic-dropdown-menu ul ul ul li { 66 | position: relative; 67 | top:-40px; 68 | left:230px; 69 | z-index:10001; 70 | } 71 | .solid-uic-dropdown-menu ul ul li { 72 | border: 1px solid white; 73 | } 74 | 75 | 76 | .solid-uic-dropdown-menu ul, 77 | .solid-uic-dropdown-menu ul li { 78 | } 79 | .solid-uic-dropdown-menu span { 80 | display:inline-block;width:100%; } 81 | 82 | .leftColumn button.selected { 83 | background:#083575; 84 | } 85 | .leftColumn button:hover { 86 | background:#6d8ecb; 87 | } 88 | 89 | .uic-accordion DIV:hover { 90 | background: #6d8ecb; 91 | } 92 | .uic-accordion DIV.selected { 93 | background:#083575; 94 | } 95 | 96 | .solid-uic-dropdown-menu span:hover { 97 | } 98 | .solid-uic-dropdown-menu span { 99 | display:inline-block; 100 | // padding:0.20rem; 101 | // font-size:1rem; 102 | line-height: 40px; 103 | text-decoration:none; 104 | } 105 | .solid-uic-dropdown-menu > ul > li > span { 106 | } 107 | .solid-uic-dropdown-menu .nested .item { 108 | display:block !important; 109 | } 110 | .solid-uic-dropdown-menu .item { 111 | padding:0 !important; 112 | } 113 | -------------------------------------------------------------------------------- /assets/modalButton.css: -------------------------------------------------------------------------------- 1 | /* MODALS 2 | see https://www.w3schools.com/howto/howto_css_modals.asps 3 | */ 4 | 5 | /* The background page over which the modal appears 6 | */ 7 | .modal { 8 | display: none; /* Hidden by default */ 9 | position: fixed; /* Stay in place */ 10 | z-index: 1; /* Sit on top */ 11 | left: 0; 12 | top: 0; 13 | width: 100%; /* Full width */ 14 | height: 100%; /* Full height */ 15 | overflow: auto; /* Enable scroll if needed */ 16 | background-color: rgb(0,0,0); /* Fallback color */ 17 | background-color: rgba(0,0,0,0.2); /* Black w/ opacity */ 18 | } 19 | 20 | /* Modal Content/Box */ 21 | .modal .modal-content { 22 | background-color: #fefefe; 23 | margin: 15% auto; /* 15% from the top and centered */ 24 | padding: 1em; 25 | border: 1px solid #888; 26 | width: 40%; 27 | } 28 | 29 | /* The Close Button */ 30 | .modal .close { 31 | color:red; 32 | text-align:right; 33 | margin-bottom: 0.25em; 34 | font-size: 2em; 35 | font-weight: bold; 36 | } 37 | 38 | .modal .close:hover, 39 | .close:focus { 40 | color: black; 41 | text-decoration: none; 42 | cursor: pointer; 43 | } 44 | -------------------------------------------------------------------------------- /assets/solid-rainbow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeff-zucker/solid-ui-components/100efe843044b3e7cba71cc68e1a5ab1aeb3d366/assets/solid-rainbow.png -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 13 | 17 | 18 | 19 | 20 | 71 | 72 | 73 | 86 | 87 | 88 | 89 | 101 | 102 | 103 | 104 | 105 | 123 | 124 | 125 | 126 | 137 | 138 | 139 | 140 | 141 | 152 | 153 | 154 | 155 | 174 | 175 | 176 |

Horizontal Tabs

74 |
85 |

Vertical Tabs

90 |
100 |

Vertical Tabs from SPARQL

106 |
122 |

Accordion

127 |
136 |

Modal Button

142 |
151 |

Table

156 |
173 |
177 | 178 | 179 | 184 | 185 | 186 | 190 | 191 | -------------------------------------------------------------------------------- /demo/draggable/dragNdrop.html: -------------------------------------------------------------------------------- 1 | 2 | My Drag-and-Drop Example 3 | 4 | 5 | 40 | 60 | 61 |

The App Cupboard

62 |
63 | 64 |
68 | 69 |
74 | Notepad 75 |
76 |
81 | Address Book 82 |
83 |
88 | Task Manager 89 |
90 |
95 | Chat 96 |
97 |
102 | Edit My Profile 103 |
104 |
109 | Edit My Preferences 110 |
111 | 112 |
117 | Local Pod (my desktop) 118 |
119 | 120 |
125 | Remote Pod 126 |
127 | 128 |
133 | News Reader 134 |
135 | 136 |
141 | Open Culture Browser 142 |
143 | 144 |
149 | Solid Specifications 150 |
151 | 152 |
153 | 154 |
158 |

Selected Pods, Tools, And Apps

159 |
160 | 161 |
162 | 163 | 164 | -------------------------------------------------------------------------------- /demo/draggable/draggable-js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /demo/draggable/draggable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 | 6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/draggable/draggable.ttl: -------------------------------------------------------------------------------- 1 | @prefix : <#>. 2 | @prefix ui: . 3 | 4 | # NOTEPAD 5 | # 6 | :NotepadButton 7 | a ui:ComponentButton; 8 | ui:label "Notepad"@en; 9 | ui:dataSource :NotepadDraggable. 10 | 11 | :NotepadDraggable 12 | a ui:Draggable; 13 | ui:label "Draggable Notepad"@en; 14 | ui:style "left:300px;top:1em;"; 15 | ui:dataSource :NotepadSource. 16 | 17 | :NotepadSource 18 | a ui:SolidOSLink; 19 | ui:href . 20 | 21 | # CONTAINER 22 | # 23 | :ContainerButton 24 | a ui:ComponentButton; 25 | ui:label "Container"@en; 26 | ui:dataSource :ContainerDraggable. 27 | 28 | :ContainerDraggable 29 | a ui:Draggable; 30 | ui:label "Draggable Container"@en; 31 | ui:dataSource :ContainerSource. 32 | 33 | :ContainerSource 34 | a ui:SolidOSLink; 35 | ui:pane "folder"; 36 | ui:href "https://jeff-zucker.solidcommunity.net/public/test/". 37 | 38 | # QUERY 39 | # 40 | :QueryButton 41 | a ui:ComponentButton; ui:label "Query"@en; ui:dataSource :QueryDraggable. 42 | 43 | :QueryDraggable 44 | a ui:Draggable; 45 | ui:label "Draggable Query Results"@en; 46 | ui:dataSource :QueryTable. 47 | 48 | :QueryTable 49 | a ui:Table; 50 | ui:dataSource :QuerySource. 51 | 52 | :QuerySource 53 | a ui:SparqlQuery; 54 | ui:endpoint ; 55 | ui:query """ 56 | PREFIX foaf: 57 | SELECT ?Name ?Age WHERE { 58 | ?x a foaf:Person; 59 | foaf:name ?Name; 60 | foaf:age ?Age. 61 | } 62 | """. 63 | -------------------------------------------------------------------------------- /demo/draggable/test-data.ttl: -------------------------------------------------------------------------------- 1 | <#this> a ; 2 | <#keisha>, <#juan>. 3 | <#keisha> a ; 4 | "Keisha"; 5 | 43. 6 | <#juan> a ; 7 | "Juan"; 8 | 36. 9 | -------------------------------------------------------------------------------- /demo/grid.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 |
8 |
9 | 29 | -------------------------------------------------------------------------------- /demo/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/selectorPanel.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 26 | 38 | -------------------------------------------------------------------------------- /demo/to-do.md: -------------------------------------------------------------------------------- 1 | data-renew 2 | always - performs the query every time // the default 3 | never - always uses cached results 4 | daily - re-runs the query if cached results are older than 24 hours 5 | 6 | data-replace 7 |
10 | 11 | ## Embedding Query Results in an HTML Page 12 | 13 | Once you have created and stored a SPARQL query component (see below), you can embed it in a web page using the Solid UI Components (SUIC) framework. This example would look in the "myQueries.ttl" document for the stored query named "ContainerSearch". The stored query would contain the query, its endpoint, and a specifier for a display format, e.g. an HTML table. The table will be displayed in the div on page load: 14 | 15 |
16 | 17 | data-suic attribute and also optionally specify , data-renrew, data-replace, and data-display attributes. 18 | 19 | * **data-suic** : the suic (Solid UI Component) for a SPARQL query can be specified using the library name and the component name e.g. this uses the "ContainerSearch" query in the "myQueryLibrary.ttl" library. The library address may be either absolute or relative. 20 | 21 | 22 | This will perform the query, format it as specified in the component and place the results inside the div. 23 | 24 | 25 | *SPARQL Fiddle* supports creating, testing, storing, embeding, reusing, and sharing SPARQL queries using a variety of built-in and custom display formats . 26 | 27 | ### Embedding 28 | You can directly embed a query in an HTML page like so : 29 | 30 |
{ 102 | mungeLoginArea(); 103 | }); 104 | UI.authn.authSession.onLogout(() => { 105 | mungeLoginArea(); 106 | }); 107 | UI.authn.authSession.onSessionRestore(url => { 108 | mungeLoginArea(); 109 | }); 110 | } // document.addEventListener('DOMContentLoaded',()=>{mungeLoginArea();}); 111 | 112 | 113 | mungeLoginArea(); 114 | } // ENDS 115 | 116 | }); -------------------------------------------------------------------------------- /dist/umd/model/profile.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | if (typeof define === "function" && define.amd) { 3 | define(["exports"], factory); 4 | } else if (typeof exports !== "undefined") { 5 | factory(exports); 6 | } else { 7 | var mod = { 8 | exports: {} 9 | }; 10 | factory(mod.exports); 11 | global.profile = mod.exports; 12 | } 13 | })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports) { 14 | "use strict"; 15 | 16 | Object.defineProperty(_exports, "__esModule", { 17 | value: true 18 | }); 19 | _exports.loadProfile = loadProfile; 20 | const $rdf = panes.UI.rdf; 21 | const RDFS = $rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#'); 22 | const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/'); 23 | const SOLID = $rdf.Namespace('http://www.w3.org/ns/solid/terms#'); 24 | const PIM = $rdf.Namespace('http://www.w3.org/ns/pim/space#'); 25 | const LDP = $rdf.Namespace('http://www.w3.org/ns/ldp#'); 26 | const VCARD = $rdf.Namespace('http://www.w3.org/2006/vcard/ns#'); 27 | /* loadProfile() 28 | */ 29 | 30 | async function loadProfile(webId) { 31 | const kb = window.kb; 32 | const fetcher = $rdf.fetcher(kb); 33 | const node = $rdf.sym(webId); 34 | 35 | try { 36 | await fetcher.load(webId); 37 | } catch (e) { 38 | console.log("Couldn't load profile : " + e); 39 | return {}; 40 | } 41 | 42 | let extendedDocs = kb.each(webId, RDFS('seeAlso')); 43 | extendedDocs = extendedDocs.concat(kb.each(node, FOAF('primaryTopicOf'))); 44 | 45 | for (let doc of extendedDocs) { 46 | await fetcher.load(doc); 47 | } 48 | 49 | let me = { 50 | webId, 51 | name: getObject(kb, webId, FOAF('name')) || getObject(kb, webId, VCARD('fn')) || getObject(kb, webId, FOAF('nick')) || webId, 52 | nick: getObject(kb, webId, FOAF('nick')) || "", 53 | image: getObject(kb, webId, VCARD('hasPhoto')) || "", 54 | inbox: getObject(kb, webId, LDP('inbox')) || "", 55 | preferences: getObject(kb, webId, PIM('preferencesFile')) || "", 56 | privateTypeIndex: getObject(kb, webId, SOLID('privateTypeIndex')) || "", 57 | publicTypeIndex: getObject(kb, webId, SOLID('publicTypeIndex')) || "", 58 | storages: getObjects(kb, webId, PIM('storage')) || "", 59 | issuers: getObjects(kb, webId, SOLID('oidcIssuer')) || "" 60 | }; 61 | return me; // I'm defective, get your money back ;-) 62 | } 63 | 64 | function getObject(kb, subject, predicate) { 65 | subject = kb.sym(subject); 66 | let value = kb.any(subject, predicate); 67 | return value ? value.value : ""; 68 | } 69 | 70 | function getObjects(kb, subject, predicate) { 71 | subject = kb.sym(subject); 72 | let value = kb.each(subject, predicate); 73 | let results = []; 74 | 75 | for (let v of value) { 76 | results.push(v ? v.value : ""); 77 | } 78 | 79 | return results; 80 | } 81 | }); -------------------------------------------------------------------------------- /dist/umd/model/rss.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | if (typeof define === "function" && define.amd) { 3 | define(["exports"], factory); 4 | } else if (typeof exports !== "undefined") { 5 | factory(exports); 6 | } else { 7 | var mod = { 8 | exports: {} 9 | }; 10 | factory(mod.exports); 11 | global.rss = mod.exports; 12 | } 13 | })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports) { 14 | "use strict"; 15 | 16 | Object.defineProperty(_exports, "__esModule", { 17 | value: true 18 | }); 19 | _exports.Feed = void 0; 20 | 21 | /* 22 | * myFeed = new Feed(solidUI,feedUri) 23 | * 24 | 25 | proxy 26 | href || link 27 | displayType 28 | standAlone 29 | label 30 | selBackground 31 | selColor 32 | height 33 | width 34 | 35 | */ 36 | class Feed { 37 | async fetchAndParse(feedUri, proxy) { 38 | // fetch feed URI & parse into a Dom structure 39 | // 40 | feedUri = (proxy || "") + encodeURI(feedUri); 41 | let response = await fetch(feedUri); 42 | let feedContent = await response.text(); 43 | const domParser = new window.DOMParser(); 44 | let feedDom = domParser.parseFromString(feedContent, "text/xml"); // find items (RSS) or entries (Atom) 45 | // 46 | 47 | let items = feedDom.querySelectorAll("item") || null; 48 | items = items.length < 1 ? feedDom.querySelectorAll("entry") : items; // parse items 49 | // 50 | 51 | let parsedItems = []; 52 | items.forEach(el => { 53 | // find item link, account for specific kinds of quirks 54 | // 55 | let link = el.querySelector("link").innerHTML; // vox 56 | 57 | if (!link) link = el.querySelector('link').getAttribute('href'); // reddit 58 | 59 | if (!link || link.match(/ /)) { 60 | link = el.querySelector('content').innerHTML.replace(/.*\[link\]/, '').replace(/a href="/, '').replace(/">.*/, '').replace(/.*</, ''); 61 | } // engadget 62 | 63 | 64 | if (!link.match(/^http/)) link = link.replace(/.*\[CDATA\[/, ''); // always use https, not http 65 | 66 | link = link.replace(/^http:/, 'https:'); // get the title 67 | 68 | let title = el.querySelector("title").innerHTML; 69 | title = title.replace(/^\<\!\[CDATA\[/, ''); 70 | title = title.replace(/\]\].*\>/, '').trim(); 71 | parsedItems.push({ 72 | title, 73 | link 74 | }); 75 | }); 76 | return parsedItems; 77 | } // END OF fetchAndParse() 78 | 79 | 80 | async render(solidUI, json) { 81 | let items = ""; 82 | let externalLinkIcon = UI.icons.iconBase + `/noun_189137.svg`; 83 | externalLinkIcon = ``; 84 | 85 | for (let i of await this.fetchAndParse(json.href || json.link, json.proxy)) { 86 | if (json.displayTarget === "window") { 87 | /* 88 | items += ` 89 |
  • 90 | 91 | ${i.title} 92 | 93 |
  • 94 | `; 95 | */ 96 | } else { 97 | items += ` 98 |
  • 99 | 100 | ${i.title} 101 | 102 | ${externalLinkIcon} 103 |
  • 104 | `; 105 | } 106 | } 107 | 108 | const wrapper = document.createElement('DIV'); 109 | wrapper.property = "xmlns:rss"; 110 | wrapper.content = "http://purl.org/rss/1.0/"; 111 | 112 | if (json.standAlone) { 113 | wrapper.innerHTML = ` 114 |
    115 | ${json.label} 116 |
      117 | ${items} 118 |
    119 |
    120 | `; 121 | wrapper.querySelector('B').style = `padding:1em;padding-right:0;background:${json.selBackground};color:${json.selColor};border:1px solid grey;width:100%;display:inline-block`; 122 | } else { 123 | wrapper.innerHTML = ` 124 |
    125 | 126 |
      127 | ${items} 128 |
    129 |
    130 | `; 131 | wrapper.querySelector('UL').style = `padding:0;padding-left:0;list-style:none;margin-top:0;width:100%;`; 132 | } 133 | 134 | wrapper.querySelector('UL').style = `padding:0;border:1px solid grey;list-style:none;margin-top:0;width:100%;height:${json.height};overflow-y:auto;`; 135 | let anchors = wrapper.querySelectorAll('A'); 136 | 137 | for (let anchor of anchors) { 138 | anchor.style = "text-decoration:none;"; 139 | } 140 | 141 | let listItems = wrapper.querySelectorAll('LI'); 142 | 143 | for (let li of listItems) { 144 | li.style = "padding:0.5em;border-bottom:1px solid grey"; 145 | } 146 | 147 | if (json.width) wrapper.style.width = json.width; 148 | return wrapper; 149 | } 150 | 151 | } 152 | 153 | _exports.Feed = Feed; 154 | }); -------------------------------------------------------------------------------- /dist/umd/model/sparql.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | if (typeof define === "function" && define.amd) { 3 | define(["exports"], factory); 4 | } else if (typeof exports !== "undefined") { 5 | factory(exports); 6 | } else { 7 | var mod = { 8 | exports: {} 9 | }; 10 | factory(mod.exports); 11 | global.sparql = mod.exports; 12 | } 13 | })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports) { 14 | "use strict"; 15 | 16 | Object.defineProperty(_exports, "__esModule", { 17 | value: true 18 | }); 19 | _exports.Sparql = void 0; 20 | 21 | class Sparql { 22 | async sparqlQuery(endpoint, queryString, forceReload) { 23 | if (typeof Comunica != "undefined") return await this.comunicaQuery(endpoint, queryString, forceReload);else return await this.rdflibQuery(endpoint, queryString, forceReload); 24 | } 25 | /* REPLACED 26 | async rdflibQuery(solidUI,kb,endpoint,queryString,json){ 27 | await solidUI.loadUnlessLoaded(endpoint); 28 | */ 29 | 30 | /* NEW */ 31 | 32 | 33 | async rdflibQuery(endpoint, queryString, forceReload) { 34 | /* 35 | kb = UI.store 36 | const fetcher = UI.store.fetcher; 37 | */ 38 | const kb = UI.rdf.graph(); 39 | const fetcher = UI.rdf.fetcher(kb); // if(forceReload || !kb.any(null,null,null,UI.rdf.sym(endpoint))) 40 | 41 | await kb.fetcher.load(endpoint); 42 | /* END-NEW */ 43 | 44 | try { 45 | const preparedQuery = await UI.rdf.SPARQLToQuery(queryString, false, kb); 46 | let wanted = preparedQuery.vars.map(stm => stm.label); 47 | let table = []; 48 | let results = kb.querySync(preparedQuery); 49 | 50 | for (let r of results) { 51 | let row = {}; 52 | 53 | for (let w of wanted) { 54 | let value = r['?' + w]; 55 | row[w] = value ? value.value : ""; 56 | } 57 | 58 | table.push(row); 59 | } 60 | 61 | table = table.sort((a, b) => a.label > b.label ? 1 : -1); 62 | return table; 63 | } catch (e) { 64 | console.log(e); 65 | } 66 | } 67 | 68 | async comunicaQuery(endpoint, sparqlStr, forceReload) { 69 | try { 70 | let comunica = Comunica.newEngine(); 71 | if (forceReload) comunica.invalidateHttpCache(); 72 | 73 | function munge(x) { 74 | return x ? x.replace(/^"/, '').replace(/"[^"]*$/, '') : ""; 75 | } 76 | 77 | let result; 78 | let r = await UI.store.fetcher.webOperation('GET', endpoint); 79 | 80 | if (!r.ok) { 81 | alert(r.statusText); 82 | return; 83 | } 84 | 85 | try { 86 | result = await comunica.query(sparqlStr, { 87 | sources: [endpoint] 88 | }); 89 | } catch (e) { 90 | alert(e); 91 | return; 92 | } 93 | 94 | ; 95 | let wanted = result.variables; 96 | result = await result.bindings(); 97 | let table = []; 98 | let hash = {}; 99 | 100 | for (let e of result.entries()) { 101 | if (!e[1] || !e[1]._root || !e[1]._root.entries) continue; 102 | e = e[1]._root.entries; 103 | let row = {}; 104 | 105 | for (let i in e) { 106 | let key = munge(e[i][0].replace(/^\?/, '')); 107 | row[key] = row[key] || ""; 108 | let value = munge(e[i][1].id); 109 | if (typeof row[key] != 'ARRAY') row[key] = [row[key]]; 110 | if (typeof row[key] === "ARRAY") row[key].push(value);else row[key] = value; 111 | } // include keys even for empty values 112 | 113 | 114 | for (let key of wanted) { 115 | key = key.replace(/^\?/, ''); 116 | row[key] = row[key] || ""; 117 | } 118 | 119 | table.push(row); 120 | } 121 | 122 | if (!table.length) console.log('No results!'); 123 | return table; 124 | } catch (e) { 125 | console.log(e); 126 | } 127 | } 128 | 129 | flatten(results, groupOn) { 130 | const newResults = {}; 131 | 132 | for (let row of results) { 133 | let key = row[groupOn]; 134 | if (!newResults[key]) newResults[key] = {}; 135 | 136 | for (let k of Object.keys(row)) { 137 | if (!newResults[key][k]) { 138 | newResults[key][k] = row[k]; 139 | continue; 140 | } 141 | 142 | if (newResults[key][k].includes(row[k])) continue; 143 | if (typeof newResults[key][k] != "object") newResults[key][k] = [newResults[key][k]]; 144 | newResults[key][k].push(row[k]); 145 | } 146 | } 147 | 148 | results = []; 149 | 150 | for (let n of Object.keys(newResults)) { 151 | results.push(newResults[n]); 152 | } 153 | 154 | return results; 155 | } 156 | 157 | } // class Sparql 158 | 159 | 160 | _exports.Sparql = Sparql; 161 | }); -------------------------------------------------------------------------------- /dist/umd/template.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | if (typeof define === "function" && define.amd) { 3 | define([], factory); 4 | } else if (typeof exports !== "undefined") { 5 | factory(); 6 | } else { 7 | var mod = { 8 | exports: {} 9 | }; 10 | factory(); 11 | global.template = mod.exports; 12 | } 13 | })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function () { 14 | "use strict"; 15 | 16 | const $rdf = require('rdflib'); 17 | 18 | const store = $rdf.graph(); 19 | const fetcher = $rdf.fetcher(store); 20 | const UI = $rdf.Namespace("http://www.w3.org/ns/ui#"); 21 | 22 | module.exports.getComponent = async function getComponent(componentURL) { 23 | await fetcher.load(componentURL); 24 | let subject = $rdf.sym(componentURL); 25 | let templateURL = (store.any(subject, UI("template")) || {}).value; 26 | let dataURL = (store.any(subject, UI("dataSource")) || {}).value; 27 | let dataSourceURL = (store.any(subject, UI("dataSource")) || {}).value; 28 | let data = await getDataSource(dataURL); 29 | console.log(data); 30 | let template = await getTemplate(templateURL); 31 | template.middle = ""; 32 | 33 | for (const row of data) { 34 | template.middle += template.recurring.interpolate(row); 35 | } 36 | 37 | return; 38 | template.before.interpolate(data) + template.middle + template.after.interpolate(data); 39 | }; 40 | 41 | async function getTemplate(uri) { 42 | let template = {}; 43 | 44 | try { 45 | await fetcher.load(uri); 46 | } catch (e) { 47 | console.log(e); 48 | process.exit(); 49 | } 50 | 51 | let subject = $rdf.sym(uri); 52 | template.before = (store.any(subject, UI("before")) || {}).value; 53 | template.recurring = (store.any(subject, UI("recurring")) || {}).value; 54 | template.after = (store.any(subject, UI("after")) || {}).value; 55 | return template; 56 | } 57 | 58 | async function getDataSource(uri) { 59 | let subject = $rdf.sym(uri); 60 | const datasource = {}; 61 | 62 | try { 63 | await fetcher.load(uri); 64 | datasource.endpoint = (store.any(subject, UI("endpoint")) || {}).value; 65 | datasource.query = (store.any(subject, UI("query")) || {}).value; 66 | console.log(datasource.query); 67 | if (!datasource.endpoint || !datasource.query) return []; 68 | await fetcher.load(datasource.endpoint); 69 | const preparedQuery = await $rdf.SPARQLToQuery(datasource.query, false, store); 70 | let wanted = preparedQuery.vars.map(stm => stm.label); 71 | let table = []; 72 | let results = store.querySync(preparedQuery); 73 | 74 | for (let r of results) { 75 | let row = {}; 76 | 77 | for (let w of wanted) { 78 | let value = r['?' + w]; 79 | row[w] = value ? value.value : ""; 80 | } 81 | 82 | table.push(row); 83 | } 84 | 85 | table = table.sort((a, b) => a.label > b.label ? 1 : -1); 86 | return table; 87 | } catch (e) { 88 | console.log(e); 89 | } 90 | } 91 | /* https://stackoverflow.com/a/41015840/15781258 92 | * usage : 93 | * const template = 'Hello ${var1}!'; 94 | * const data = { var1: 'world'}; 95 | * const interpolated = template.interpolate(data); 96 | */ 97 | 98 | 99 | String.prototype.interpolate = function (params) { 100 | const names = Object.keys(params); 101 | const vals = Object.values(params); 102 | return new Function(...names, `return \`${this}\`;`)(...vals); 103 | }; 104 | /* getDatasource() 105 | * 106 | * When called as core.getDatasource("http://example.com/foo.ttl#bar"), 107 | * this method will look in the file "http://example.com/foo.ttl" for a 108 | * triple like the one below where ?IRI is an RDF data source and STRING 109 | * is a SPARQL query. The method will return results as an array of hashes 110 | * e.g. [{field1:"foo",field2:"bar"},{field1:"baz",field2:"bop"}]. 111 | * 112 | * <#bar> 113 | * a ui:Datasource ; 114 | * ui:endpoint ?IRI ; 115 | * ui:query ?STRING . 116 | */ 117 | 118 | /* processComponent(ComponentURL) 119 | * 120 | * params : compenentURL should point to a statement in this format: 121 | * 122 | * <#ComponentURL> 123 | * a ui:Component ; 124 | * ui:dataSource [ 125 | * a ui:DataSource ; 126 | * ui:endpoint ; 127 | * ui:query 128 | * ] ; 129 | * . 130 | * 131 | /* getTemplate() 132 | * 133 | * looks for [ 134 | * 135 | * <#TempateName> 136 | * a ui:Template ; 137 | * ui:before ; 138 | * ui:after ; 139 | * 140 | */ 141 | 142 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solid-ui-components", 3 | "version": "1.0.4", 4 | "description": "generate high level HTML UI components from RDF", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "build:umd": "babel --plugins @babel/plugin-transform-modules-umd src --out-dir dist/umd" 9 | }, 10 | "keywords": [], 11 | "author": "Jeff Zucker", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@babel/cli": "^7.18.6", 15 | "@babel/core": "^7.18.6", 16 | "@babel/plugin-transform-modules-umd": "^7.12.1", 17 | "babel-loader": "^8.2.5", 18 | "solid-ui": "^2.4.23", 19 | "webpack": "^5.65.0", 20 | "webpack-cli": "^4.9.1" 21 | }, 22 | "dependencies": { 23 | "mime": "^3.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/databrowser.js: -------------------------------------------------------------------------------- 1 | import {loadProfile} from './model/profile.js'; 2 | 3 | export async function applyProfile(domElement,me,type) { 4 | domElement ||= document; 5 | let origin = window.origin; 6 | me ||= `${origin}/profile/card#me`; 7 | me = await loadProfile( me ); 8 | const storage = me.storages ?me.storages[0] :null ; // TBD MULTIPLE STORAGES 9 | me.name = me.name || me.nick || me.webId; 10 | let logo = domElement.querySelector('#currentPodLogo') 11 | if(logo) logo.src = me.image; 12 | let title = domElement.querySelector('#currentPodTitle'); 13 | if(title) title.innerHTML=`${me.name}'s Pod`; 14 | solidUI.vars = { 15 | podRoot : storage, 16 | podName : me.name, 17 | podNick : me.nick, 18 | podWebID : me.webId, 19 | podImage : me.image, 20 | podInbox : me.inbox, 21 | podProfile : me.webId, 22 | }; 23 | return me; 24 | } 25 | 26 | export function setHistory(uri){ 27 | const params = new URLSearchParams(location.search) 28 | params.set('uri', (uri||"")); 29 | window.history.replaceState({}, '', `${location.origin}${location.pathname}?${params}`); 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | } 52 | 53 | export async function createLoginBox(domElement){ 54 | const loginButtonArea = domElement.querySelector("#loginArea"); 55 | const UI = panes.UI; 56 | let tabulator = domElement.querySelector('#suicTabulator') 57 | /* 58 | tabulator.style= ` 59 | display: none; 60 | position: fixed; 61 | z-index: 1; 62 | left: 0; 63 | top: 10vh; 64 | width: 100%; 65 | overflow: auto; 66 | background-color: rgb(0,0,0); 67 | background-color: rgba(0,0,0,0.2); 68 | `; 69 | */ 70 | async function mungeLoginArea(uri){ 71 | await initAuth(); 72 | loginButtonArea.innerHTML=""; 73 | loginButtonArea.appendChild(UI.authn.loginStatusBox(document, null, {})) 74 | let loginButtons = loginButtonArea.querySelectorAll('input'); 75 | /* 76 | for(let button of loginButtons){ 77 | button.style = ` 78 | border:none; 79 | background:transparent; 80 | font-size:1rem; 81 | position: absolute; 82 | top:3.5rem; 83 | right:7.2rem; 84 | cursor:pointer; 85 | `; 86 | } 87 | */ 88 | loginButtons[0].value = loginButtons[0].value.replace(/WebID\s*/,''); 89 | if(loginButtons[1]) loginButtons[1].style.display="none"; 90 | } 91 | async function initAuth(){ 92 | if( !UI.authn.currentUser() ) { 93 | await UI.authn.checkUser(); 94 | } 95 | } 96 | if( UI.authn.authSession ) { 97 | UI.authn.authSession.onLogin(() => { 98 | mungeLoginArea(); 99 | }) 100 | UI.authn.authSession.onLogout(() => { 101 | mungeLoginArea(); 102 | }) 103 | UI.authn.authSession.onSessionRestore((url) => { 104 | mungeLoginArea(); 105 | 106 | }) 107 | } 108 | // document.addEventListener('DOMContentLoaded',()=>{mungeLoginArea();}); 109 | mungeLoginArea(); 110 | } // ENDS 111 | -------------------------------------------------------------------------------- /src/default-templates.js: -------------------------------------------------------------------------------- 1 | export const suicTemplate = { 2 | 3 | Rolodex: ` 4 |
    5 | [~LOOP~] 6 |
    7 |
    [~key~]
    8 |
    [~val~]
    9 |
    10 | [~LOOP~] 11 |
    12 | `, 13 | 14 | RecordsList: ` 15 |
    16 | [~LOOP~] 17 |
    18 |
    [~key~]
    19 |
    [~val~]
    20 |
    21 | [~LOOP~] 22 |
    23 | `, 24 | 25 | }; 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/default-templates.ttl: -------------------------------------------------------------------------------- 1 | @prefix : <#>. 2 | @prefix ui: . 3 | 4 | :Rolodex a ui:CustomTemplate; ui:content """ 5 | 6 | [START-LOOP] 7 | 8 | 9 | 10 | 11 | [END-LOOP] 12 |
    ${col}${row[col]}
    13 | """. -------------------------------------------------------------------------------- /src/login.js: -------------------------------------------------------------------------------- 1 | const authSession = UI.authn.authSession; 2 | const loginButtonArea = document.querySelector("[data-suic=login]"); 3 | 4 | document.addEventListener('DOMContentLoaded', function() { 5 | 6 | if (authSession && loginButtonArea) { 7 | loginButtonArea.style.display="none"; 8 | authSession.onLogin(mungeLoginArea); 9 | authSession.onLogout(mungeLoginArea); 10 | authSession.onSessionRestore(mungeLoginArea); 11 | } 12 | mungeLoginArea(); 13 | 14 | }); 15 | 16 | export async function mungeLoginArea(appearanceOnly){ 17 | solidUI.initApp ||= async()=>{}; 18 | if(!loginButtonArea && !appearanceOnly) return await solidUI.initApp(); 19 | loginButtonArea.innerHTML=""; 20 | loginButtonArea.appendChild(UI.login.loginStatusBox(document, null, {})); 21 | const signupButton = loginButtonArea.querySelectorAll('input')[1]; 22 | if(signupButton) signupButton.style.display="none"; 23 | let me = await UI.authn.checkUser(); 24 | let button = loginButtonArea.querySelector('input'); 25 | let dataset = loginButtonArea.dataset; 26 | let inLabel = dataset.inlabel; 27 | let outLabel = dataset.outlabel; 28 | let transparent = dataset.transparent; 29 | if (me) { 30 | loginButtonArea.style.display="inline-block"; 31 | button.value = outLabel || "Log out!"; 32 | button.title = "--- logged in as " + me.value + "\n--- click to logout"; 33 | } 34 | else { 35 | loginButtonArea.style.display="inline-block"; 36 | button.value = inLabel || "Log in!"; 37 | button.title = "--- click to log in!"; 38 | } 39 | if(transparent) button.style.backgroundColor="transparent"; 40 | button = solidUI.styleButton(button,{}); 41 | if(me) button.style.color="green"; 42 | if(typeof solidUI !="undefined" && !appearanceOnly) await solidUI.initApp(); 43 | } 44 | -------------------------------------------------------------------------------- /src/model/dataSource.js: -------------------------------------------------------------------------------- 1 | export async function getDataSource(DataSource){ 2 | if(typeof DataSource==="string") DataSource = await this.getComponentHash(DataSource); 3 | if(dataSource && dataSource.type==='SparqlQuery') { 4 | let endpoint = dataSource.endpoint; 5 | let query = dataSource.query.replace(/\$\{[^\}]*\}/g,''); 6 | json.parts = await this.sparqlQuery(endpoint,query,json); 7 | if(json.type==='SparqlQuery') return json.parts; 8 | } 9 | if(json.type==='SparqlQuery') { 10 | json.parts = await this.sparqlQuery(json.endpoint,json.query,json); 11 | if(json.type==='SparqlQuery') return json.parts; 12 | } 13 | if(json.type==='AnchorList') { 14 | for(let l of json.content.split(/\n/) ){ 15 | 16 | } 17 | } 18 | if(json.parts && json.groupOn){ 19 | json.parts = sparql.flatten(json.parts,json.groupOn) 20 | console.log(json.groupOn,json.parts) 21 | } 22 | -------------------------------------------------------------------------------- /src/model/profile.js: -------------------------------------------------------------------------------- 1 | /* loadProfile() 2 | */ 3 | export async function loadProfile(webId) { 4 | 5 | const $rdf = UI.rdf; 6 | 7 | const RDFS = $rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#'); 8 | const FOAF = $rdf.Namespace('http://xmlns.com/foaf/0.1/'); 9 | const SOLID = $rdf.Namespace('http://www.w3.org/ns/solid/terms#'); 10 | const PIM = $rdf.Namespace('http://www.w3.org/ns/pim/space#'); 11 | const LDP = $rdf.Namespace('http://www.w3.org/ns/ldp#'); 12 | const VCARD = $rdf.Namespace('http://www.w3.org/2006/vcard/ns#'); 13 | 14 | const kb = window.kb; 15 | const fetcher = $rdf.fetcher(kb); 16 | const node = $rdf.sym(webId); 17 | try { 18 | await fetcher.load(webId); 19 | } 20 | catch(e){ 21 | console.log("Couldn't load profile : "+e); 22 | return {}; 23 | } 24 | let extendedDocs = kb.each( webId, RDFS('seeAlso') ); 25 | extendedDocs = extendedDocs.concat( kb.each( node, FOAF('primaryTopicOf') ) ); 26 | for(let doc of extendedDocs){ 27 | await fetcher.load(doc); 28 | } 29 | let me = { 30 | webId, 31 | name : getObject(kb,webId,FOAF('name')) || getObject(kb,webId,VCARD('fn')) || getObject(kb,webId,FOAF('nick')) || webId, 32 | nick : getObject(kb,webId,FOAF('nick')) || "", 33 | image : getObject(kb,webId,VCARD('hasPhoto')) || "", 34 | inbox : getObject(kb,webId,LDP('inbox')) || "", 35 | preferences : getObject(kb,webId,PIM('preferencesFile')) || "", 36 | privateTypeIndex : getObject(kb,webId,SOLID('privateTypeIndex')) || "", 37 | publicTypeIndex : getObject(kb,webId,SOLID('publicTypeIndex')) || "", 38 | storages : getObjects(kb,webId,PIM('storage')) || "", 39 | issuers : getObjects(kb,webId,SOLID('oidcIssuer')) || "", 40 | } 41 | return me; // I'm defective, get your money back ;-) 42 | } 43 | 44 | function getObject(kb,subject,predicate){ 45 | subject = kb.sym(subject); 46 | let value = kb.any(subject,predicate); 47 | return value ?value.value :""; 48 | } 49 | function getObjects(kb,subject,predicate){ 50 | subject = kb.sym(subject); 51 | let value = kb.each(subject,predicate); 52 | let results = []; 53 | for(let v of value){ 54 | results.push(v ?v.value :""); 55 | } 56 | return results; 57 | } 58 | -------------------------------------------------------------------------------- /src/model/rss.js: -------------------------------------------------------------------------------- 1 | /* 2 | * myFeed = new Feed(solidUI,feedUri) 3 | * 4 | 5 | proxy 6 | href || link 7 | displayType 8 | standAlone 9 | label 10 | selBackground 11 | selColor 12 | height 13 | width 14 | 15 | */ 16 | 17 | export class Feed { 18 | 19 | async fetchAndParse(feedUri,proxy){ 20 | 21 | // fetch feed URI & parse into a Dom structure 22 | // 23 | feedUri = (proxy||"")+encodeURI( feedUri ); 24 | 25 | let response = await fetch( feedUri ) 26 | let feedContent = await response.text(); 27 | const domParser = new window.DOMParser(); 28 | let feedDom = domParser.parseFromString(feedContent, "text/xml") 29 | 30 | // find items (RSS) or entries (Atom) 31 | // 32 | let items = feedDom.querySelectorAll("item") || null; 33 | items = items.length<1 ?feedDom.querySelectorAll("entry") :items; 34 | 35 | // parse items 36 | // 37 | let parsedItems=[]; 38 | items.forEach( el => { 39 | 40 | // find item link, account for specific kinds of quirks 41 | // 42 | let link = el.querySelector("link").innerHTML; 43 | // vox 44 | if(!link) link = el.querySelector('link').getAttribute('href'); 45 | // reddit 46 | if(!link || link.match(/ /)){ 47 | link = el.querySelector('content').innerHTML.replace(/.*\[link\]/ ,'').replace(/a href="/,'').replace(/">.*/,'').replace(/.*</,''); 48 | } 49 | // engadget 50 | if(!link.match(/^http/)) link = link.replace(/.*\[CDATA\[/,''); 51 | 52 | // always use https, not http 53 | link = link.replace(/^http:/,'https:'); 54 | 55 | // get the title 56 | let title = el.querySelector("title").innerHTML; 57 | title = title.replace(/^\<\!\[CDATA\[/,''); 58 | title = title.replace(/\]\].*\>/,'').trim(); 59 | 60 | parsedItems.push({title,link}); 61 | }); 62 | return parsedItems; 63 | } // END OF fetchAndParse() 64 | 65 | 66 | async render(solidUI,json){ 67 | let items = ""; 68 | let externalLinkIcon = UI.icons.iconBase+`/noun_189137.svg`; 69 | externalLinkIcon = ``; 70 | for(let i of await this.fetchAndParse((json.href||json.link),json.proxy)){ 71 | if(json.displayTarget==="window"){ 72 | /* 73 | items += ` 74 |
  • 75 | 76 | ${i.title} 77 | 78 |
  • 79 | `; 80 | */ 81 | } 82 | else { 83 | items += ` 84 |
  • 85 | 86 | ${i.title} 87 | 88 | ${externalLinkIcon} 89 |
  • 90 | `; 91 | } 92 | } 93 | const wrapper = document.createElement('DIV'); 94 | wrapper.property = "xmlns:rss"; 95 | wrapper.content = "http://purl.org/rss/1.0/"; 96 | if(json.standAlone) { 97 | wrapper.innerHTML = ` 98 |
    99 | ${json.label} 100 |
      101 | ${items} 102 |
    103 |
    104 | `; 105 | 106 | wrapper.querySelector('B').style=`padding:1em;padding-right:0;background:${json.selBackground};color:${json.selColor};border:1px solid grey;width:100%;display:inline-block`; 107 | } 108 | else { 109 | wrapper.innerHTML = ` 110 |
    111 | 112 |
      113 | ${items} 114 |
    115 |
    116 | `; 117 | wrapper.querySelector('UL').style=`padding:0;padding-left:0;list-style:none;margin-top:0;width:100%;`; 118 | } 119 | wrapper.querySelector('UL').style=`padding:0;border:1px solid grey;list-style:none;margin-top:0;width:100%;height:${json.height};overflow-y:auto;`; 120 | let anchors = wrapper.querySelectorAll('A'); 121 | for(let anchor of anchors){ 122 | anchor.style="text-decoration:none;" 123 | } 124 | let listItems = wrapper.querySelectorAll('LI'); 125 | for(let li of listItems){ 126 | li.style="padding:0.5em;border-bottom:1px solid grey"; 127 | } 128 | if(json.width) wrapper.style.width = json.width; 129 | return wrapper; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/model/sparql.js: -------------------------------------------------------------------------------- 1 | /* 2 | const results = runQuery({ 3 | ?template : String|Null, 4 | ?displayArea : String|Null, 5 | endpoint : String, 6 | query : String 7 | }); 8 | 9 | Note : If template is provided, results will be an HTMLElement, else an array. 10 | Note : If template and displayArea are provided, the displayArea will be populated 11 | with the template interpolated with the results. 12 | 13 | */ 14 | 15 | export async function runQuery(options){ 16 | const sparql = new Sparql(); 17 | let results = await sparql.sparqlQuery(options.endpoint,options.query); 18 | options.template ||= 'ui:Table'; 19 | results = await solidUI.processTemplate(options.template,results); 20 | if(options.displayArea){ 21 | const d = (typeof options.displayArea==="string") ?document.querySelector(options.displayArea) :options.displayArea; 22 | d.innerHTML=""; 23 | d.appendChild(results); 24 | } 25 | return results; 26 | } 27 | 28 | export class Sparql { 29 | 30 | async sparqlQuery(endpoint,queryString,forceReload){ 31 | if( endpoint.startsWith('/') ){ 32 | endpoint = window.origin + endpoint; 33 | } 34 | else if( endpoint.startsWith('./') ){ 35 | let loc = window.location.href; 36 | endpoint = loc.replace(/\/[^\/]*$/,'') + endpoint.replace(/^\./,''); 37 | } 38 | if(typeof Comunica !="undefined") 39 | return await this.comunicaQuery(endpoint,queryString,forceReload); 40 | else 41 | return await this.rdflibQuery(endpoint,queryString,forceReload); 42 | } 43 | 44 | async rdflibQuery(endpoint,queryString,forceReload){ 45 | /* 46 | kb = UI.store 47 | const fetcher = UI.store.fetcher; 48 | */ 49 | const kb = UI.rdf.graph(); 50 | const fetcher = UI.rdf.fetcher(kb); 51 | await kb.fetcher.load(endpoint); 52 | try { 53 | const preparedQuery=await UI.rdf.SPARQLToQuery(queryString,false,kb); 54 | let wanted = preparedQuery.vars.map( stm=>stm.label ); 55 | let table = []; 56 | let results = kb.querySync(preparedQuery); 57 | for(let r of results){ 58 | let row = {}; 59 | for(let w of wanted){ 60 | let value = r['?'+w]; 61 | row[w] = value ?value.value :""; 62 | } 63 | table.push(row); 64 | } 65 | table = table.sort((a,b)=>a.label > b.label ?1 :-1); 66 | return table 67 | } 68 | catch(e) { console.log(e); } 69 | } 70 | 71 | async comunicaQuery(endpoint,sparqlStr,forceReload){ 72 | try { 73 | let comunica = Comunica.newEngine() ; 74 | if(forceReload) comunica.invalidateHttpCache(); 75 | function munge(x){ 76 | return x ? x.replace(/^"/,'').replace(/"[^"]*$/,'') :""; 77 | } 78 | let result; 79 | let r = await UI.store.fetcher.webOperation('GET',endpoint); 80 | if(!r.ok) { alert(r.statusText); return; } 81 | try { result = await comunica.query(sparqlStr,{sources:[endpoint]}) ; } 82 | catch(e){alert(e); return}; 83 | let wanted = result.variables; 84 | result = await result.bindings() 85 | let table = []; 86 | let hash = {}; 87 | for(let e of result.entries()) { 88 | if( !e[1] || !e[1]._root || !e[1]._root.entries ) continue; 89 | e = e[1]._root.entries 90 | let row = {} ; 91 | for(let i in e.reverse()){ 92 | let key = munge( e[i][0].replace(/^\?/,'')) 93 | row[key] = row[key] || ""; 94 | let value = munge(e[i][1].id) 95 | if( typeof row[key] != 'ARRAY' ) row[key]= [row[key]] 96 | if( typeof row[key] === "ARRAY" ) row[key].push(value) 97 | else row[key] = value; 98 | } 99 | // include keys even for empty values 100 | for(let key of wanted){ 101 | key = key.replace(/^\?/,''); 102 | row[key] = row[key] || "" 103 | } 104 | table.push(row); 105 | } 106 | if(!table.length) console.log('No results!'); 107 | return table; 108 | } 109 | catch(e){console.log(e)} 110 | } 111 | flatten(results,groupOn){ 112 | const newResults = {}; 113 | for(let row of results) { 114 | let key = row[groupOn]; 115 | if(!newResults[key]) newResults[key]={}; 116 | for(let k of Object.keys(row)){ 117 | if(!newResults[key][k]) { 118 | newResults[key][k]=row[k]; 119 | continue; 120 | } 121 | if(newResults[key][k].includes(row[k])) continue; 122 | if(typeof newResults[key][k]!="object") newResults[key][k]=[newResults[key][k]] 123 | newResults[key][k].push(row[k]) 124 | } 125 | } 126 | results = []; 127 | for(let n of Object.keys(newResults)){ 128 | results.push(newResults[n]) 129 | } 130 | return results; 131 | } 132 | 133 | } // class Sparql 134 | 135 | -------------------------------------------------------------------------------- /src/template.js: -------------------------------------------------------------------------------- 1 | const $rdf = require('rdflib'); 2 | const store = $rdf.graph(); 3 | const fetcher = $rdf.fetcher(store); 4 | const UI = $rdf.Namespace("http://www.w3.org/ns/ui#"); 5 | 6 | module.exports.getComponent = async function getComponent(componentURL) { 7 | await fetcher.load(componentURL); 8 | let subject = $rdf.sym(componentURL); 9 | let templateURL = (store.any( subject,UI("template") ) ||{}).value; 10 | let dataURL = (store.any( subject,UI("dataSource") ) ||{}).value; 11 | let dataSourceURL = (store.any( subject,UI("dataSource")) ||{}).value; 12 | let data = await getDataSource(dataURL) 13 | console.log(data); 14 | let template = await getTemplate(templateURL) 15 | template.middle = "" 16 | for(const row of data){ 17 | template.middle += template.recurring.interpolate(row); 18 | } 19 | return 20 | template.before.interpolate(data) 21 | + template.middle 22 | + template.after.interpolate(data) 23 | ; 24 | } 25 | async function getTemplate(uri){ 26 | let template = {}; 27 | try { 28 | await fetcher.load(uri); 29 | } 30 | catch(e){console.log(e); process.exit();} 31 | let subject = $rdf.sym(uri); 32 | template.before = (store.any( subject,UI("before")) ||{}).value; 33 | template.recurring = (store.any(subject,UI("recurring")) ||{}).value; 34 | template.after = (store.any( subject,UI("after") ) ||{}).value; 35 | return template; 36 | } 37 | async function getDataSource(uri){ 38 | let subject = $rdf.sym(uri); 39 | const datasource = {}; 40 | try { 41 | await fetcher.load(uri); 42 | datasource.endpoint = (store.any( subject,UI("endpoint")) ||{}).value; 43 | datasource.query = (store.any( subject,UI("query")) ||{}).value; 44 | console.log(datasource.query) 45 | if( !datasource.endpoint || !datasource.query ) return []; 46 | await fetcher.load(datasource.endpoint) 47 | const preparedQuery=await $rdf.SPARQLToQuery(datasource.query,false,store); 48 | let wanted = preparedQuery.vars.map( stm=>stm.label ); 49 | let table = []; 50 | let results = store.querySync(preparedQuery); 51 | for(let r of results){ 52 | let row = {}; 53 | for(let w of wanted){ 54 | let value = r['?'+w]; 55 | row[w] = value ?value.value :""; 56 | } 57 | table.push(row); 58 | } 59 | table = table.sort((a,b)=>a.label > b.label ?1 :-1); 60 | return table 61 | } 62 | catch(e) { console.log(e); } 63 | } 64 | /* https://stackoverflow.com/a/41015840/15781258 65 | * usage : 66 | * const template = 'Hello ${var1}!'; 67 | * const data = { var1: 'world'}; 68 | * const interpolated = template.interpolate(data); 69 | */ 70 | String.prototype.interpolate = function(params) { 71 | const names = Object.keys(params); 72 | const vals = Object.values(params); 73 | return new Function(...names, `return \`${this}\`;`)(...vals); 74 | } 75 | 76 | 77 | 78 | /* getDatasource() 79 | * 80 | * When called as core.getDatasource("http://example.com/foo.ttl#bar"), 81 | * this method will look in the file "http://example.com/foo.ttl" for a 82 | * triple like the one below where ?IRI is an RDF data source and STRING 83 | * is a SPARQL query. The method will return results as an array of hashes 84 | * e.g. [{field1:"foo",field2:"bar"},{field1:"baz",field2:"bop"}]. 85 | * 86 | * <#bar> 87 | * a ui:Datasource ; 88 | * ui:endpoint ?IRI ; 89 | * ui:query ?STRING . 90 | */ 91 | /* processComponent(ComponentURL) 92 | * 93 | * params : compenentURL should point to a statement in this format: 94 | * 95 | * <#ComponentURL> 96 | * a ui:Component ; 97 | * ui:dataSource [ 98 | * a ui:DataSource ; 99 | * ui:endpoint ; 100 | * ui:query 101 | * ] ; 102 | * . 103 | * 104 | /* getTemplate() 105 | * 106 | * looks for [ 107 | * 108 | * <#TempateName> 109 | * a ui:Template ; 110 | * ui:before ; 111 | * ui:after ; 112 | * 113 | */ 114 | 115 | -------------------------------------------------------------------------------- /src/templates/rolodex.ttl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
    ${col}${row[col]}
    9 | -------------------------------------------------------------------------------- /src/view/accordion-menu.js: -------------------------------------------------------------------------------- 1 | 2 | async renderAccordionLinks (json) { 3 | const results = json; 4 | let bgColor = "#ddd"; 5 | const accordion = this.createElement('DIV','accordion-menu'); 6 | accordion.classList.add('horizontal'); 7 | if(!results || !results.length) return accordion; 8 | let cols = Object.keys(results[0]); 9 | for(let row of results){ 10 | let item = this.createElement( 'DIV' ); 11 | let name = row[cols[0]]; 12 | let rowhead = this.createElement( 'DIV','' ); 13 | name = this.createElement('SPAN','',name) ; 14 | name.style.display="table-cell"; 15 | name.style.width="100%"; 16 | rowhead.appendChild(name); 17 | let caret = this.createElement('SPAN','caret-down') ; 18 | caret.style.display="table-cell"; 19 | caret.textAlign="right"; 20 | rowhead.appendChild(caret); 21 | rowhead.style.backgroundColor = bgColor; 22 | rowhead.style.padding="0.75em"; 23 | rowhead.style.border="1px solid grey"; 24 | rowhead.style.cursor = "pointer"; 25 | let rowContent = ""; 26 | for(let i=1; i${key} : ${value}

    `; 30 | } 31 | rowContent = this.createElement( 'DIV',null,rowContent ); 32 | rowContent.style.padding="0.75em"; 33 | rowContent.style.border="1px solid grey"; 34 | rowContent.style.borderTop="none"; 35 | rowContent.style.display = "none"; 36 | rowhead.onclick = (e)=>{ 37 | let showing = rowContent.style.display === "block" ; 38 | let items = accordion.children; 39 | for(let i of items) { 40 | i.children[1].style.display="none"; 41 | } 42 | rowContent.style.display = showing ?"none" :"block"; 43 | } 44 | item.appendChild(rowhead); 45 | item.appendChild(rowContent); 46 | item.style.marginBottom = "1em"; 47 | accordion.appendChild(item); 48 | } 49 | return await this.initInternal(accordion); 50 | } 51 | 52 | async renderAccordionLinks2 (json) { 53 | const results = json.parts; 54 | let bgColor = "#ddd"; 55 | const accordion = this.createElement('DIV','accordion-menu'); 56 | accordion.classList.add('horizontal'); 57 | if(!results || !results.length) return accordion; 58 | let cols = Object.keys(results[0]); 59 | for(let row of results){ 60 | let item = this.createElement( 'DIV' ); 61 | let name = row[cols[0]]; 62 | let rowhead = this.createElement( 'DIV','' ); 63 | name = this.createElement('SPAN','',name) ; 64 | name.style.display="table-cell"; 65 | name.style.width="100%"; 66 | rowhead.appendChild(name); 67 | let caret = this.createElement('SPAN','caret-down') ; 68 | caret.style.display="table-cell"; 69 | caret.textAlign="right"; 70 | rowhead.appendChild(caret); 71 | rowhead.style.backgroundColor = bgColor; 72 | rowhead.style.padding="0.75em"; 73 | rowhead.style.border="1px solid grey"; 74 | rowhead.style.cursor = "pointer"; 75 | let rowContent = ""; 76 | for(let i=1; i${key} : ${value}

    `; 80 | } 81 | rowContent = this.createElement( 'DIV',null,rowContent ); 82 | rowContent.style.padding="0.75em"; 83 | rowContent.style.border="1px solid grey"; 84 | rowContent.style.borderTop="none"; 85 | rowContent.style.display = "none"; 86 | rowhead.onclick = (e)=>{ 87 | let showing = rowContent.style.display === "block" ; 88 | let items = accordion.children; 89 | for(let i of items) { 90 | i.children[1].style.display="none"; 91 | } 92 | rowContent.style.display = showing ?"none" :"block"; 93 | } 94 | item.appendChild(rowhead); 95 | item.appendChild(rowContent); 96 | item.style.marginBottom = "1em"; 97 | accordion.appendChild(item); 98 | } 99 | return await this.initInternal(accordion); 100 | } 101 | 102 | 103 | /* ACCORDION MENU 104 | */ 105 | async renderAccordionMenu(json){ 106 | const results = json.parts; 107 | // const accordion = this.createElement('DIV','accordion-menu'); 108 | const accordion = this.createElement('DIV','dropdown-menu'); 109 | // accordion.classList.add('dropdown-menuhorizontal'); 110 | const leftCol = this.createElement('DIV','left-column'); 111 | let mid=""; 112 | let got = {} 113 | let topics = results.map((row)=>{ 114 | if(!got[row.topic]) { 115 | got[row.topic]=1; 116 | return row.topic; 117 | } 118 | }).filter(row=>typeof row != "undefined") 119 | for(let topic of topics) { 120 | let itemElement = this.createElement('SPAN','',topic) ; 121 | itemElement.onclick= (e) => { 122 | let links = accordion.querySelectorAll('BUTTON'); 123 | let sib = e.target.nextSibling; 124 | sib.style.display="block" 125 | let sublinks = sib.querySelectorAll('BUTTON'); 126 | let showing = sublinks[0].style.display==='block'; 127 | for(let link of links){ 128 | link.style.display="none"; 129 | } 130 | for(let link of sublinks){ 131 | if(showing) link.style.display="none"; 132 | else link.style.display="block"; 133 | } 134 | } 135 | let contentElement = this.createElement('DIV'); 136 | contentElement.tabindex=0; 137 | contentElement.onblur = ()=>{alert(3)} 138 | let rows=results.filter((row)=>row['topic']===topic); 139 | for(let row of rows){ 140 | let button=this.createElement('BUTTON','',row.label); 141 | button.value = row.link ; 142 | button.onclick = (e) => { 143 | e.target.parentElement.style.display="none"; 144 | showIframe(e.target.value,iframe); 145 | } 146 | button.style.display = "none"; 147 | contentElement.appendChild(button); 148 | } 149 | leftCol.appendChild(itemElement) 150 | leftCol.appendChild(contentElement) 151 | } 152 | accordion.appendChild(leftCol); 153 | accordion.appendChild( iframe ); 154 | return await this.initInternal(accordion); 155 | } 156 | -------------------------------------------------------------------------------- /src/view/accordion.js: -------------------------------------------------------------------------------- 1 | export class Accordion { 2 | async render(solidUI,json) { 3 | const parts = json.parts; 4 | const accordion = solidUI.createElement('DIV','accordion'); 5 | if(!parts || !parts.length) return accordion; 6 | for(let row of parts){ 7 | let item = solidUI.createElement( 'DIV' ); 8 | let rowhead = solidUI.createElement( 'DIV','' ); 9 | let name = solidUI.createElement('SPAN','',row.label || row.topic) ; 10 | name.style.display="table-cell"; 11 | name.style.width="100%"; 12 | rowhead.appendChild(name); 13 | let caret = solidUI.createElement('SPAN','caret-down','\u2304') ; 14 | caret.style.display="table-cell"; 15 | caret.textAlign="right"; 16 | rowhead.appendChild(caret); 17 | rowhead.style.backgroundColor = json.unselBackground; 18 | rowhead.style.color = json.unselColor; 19 | rowhead.style.padding="0.75em"; 20 | rowhead.style.border="1px solid grey"; 21 | rowhead.style.cursor = "pointer"; 22 | let rowContent = solidUI.createElement( 'DIV'); 23 | if(row.content) rowContent.innerHTML = row.content; 24 | else { 25 | console.log(json) 26 | for(let i=0;i { 37 | showByClass(e); 38 | } 39 | rowContent.appendChild(button); 40 | } 41 | } 42 | rowContent.style.padding="0.75em"; 43 | rowContent.style.border="1px solid grey"; 44 | rowContent.style.borderTop="none"; 45 | rowContent.style.display = "none"; 46 | rowhead.onclick = (e)=>{ 47 | let showing = rowContent.style.display === "block" ; 48 | let items = accordion.children; 49 | for(let i of items) { 50 | i.children[1].style.display="none"; 51 | } 52 | rowContent.style.display = showing ?"none" :"block"; 53 | } 54 | rowContent.style.backgroundColor = json.background; 55 | rowContent.style.color = json.color; 56 | item.appendChild(rowhead); 57 | item.appendChild(rowContent); 58 | item.style.marginBottom = "1em"; 59 | accordion.appendChild(item); 60 | } 61 | solidUI.simulateClick(accordion.querySelector('DIV DIV DIV')) 62 | return await solidUI.initInternal(accordion); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/view/anchorList.js: -------------------------------------------------------------------------------- 1 | /* 2 | Creates a menu from a list of anchor elements; 3 | params 4 | contentArea - selector for area to put the menu buttons 5 | displayArea - selector for area to show results of clicks on the buttons 6 | */ 7 | export async function anchorList(o){ 8 | let el = document.querySelector(o.contentArea); 9 | for(let anchor of as){ 10 | anchor.target = o.displayArea; 11 | anchor.addEventListener('click',async (e)=>{ 12 | e.preventDefault(); 13 | anchor.setAttribute('data-contentType','text/html'); 14 | await u.show('text/html',anchor.href,null,o.displayArea); 15 | }); 16 | el.classList.add('suic-anchor-list'); 17 | el.appendChild(anchor); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/view/app-new.js: -------------------------------------------------------------------------------- 1 | import {createLoginBox,applyProfile} from '../databrowser.js'; 2 | 3 | export class App { 4 | async render(solidUI,app){ 5 | 6 | // GET/SET DEFAULTS 7 | // 8 | app = solidUI.setDefaults(app); 9 | if(app.fullPage){ 10 | window.addEventListener('resize',()=>{ 11 | app = mainHeight(app); 12 | let mm = document.getElementById('mainMain') 13 | let tab = document.getElementById('suicTabulator') 14 | if(mm) mm.style.height = app.mainHeight; 15 | if(tab) tab.style.height = app.mainHeight; 16 | }); 17 | document.body.style.overflow="hidden"; 18 | } 19 | 20 | // FIND NUMBER OF PIXELS IN 1 REM - VARIES WITH BROWSER/USER SETTINGS 21 | function rem2px(){ 22 | let el = document.createElement('DIV'); 23 | el.style="height:1rem"; 24 | document.body.appendChild(el); 25 | let remSize = el.clientHeight; 26 | document.body.removeChild(el); 27 | return(remSize); 28 | } 29 | // SET BODY HEIGHT TO WINDOW HIEGHT MINUS HEADER + MENU 30 | function mainHeight(app){ 31 | let rem = rem2px(); 32 | app.menuHeight = 2*rem; 33 | app.headerHeight = 6*rem; 34 | window.SolidAppContext = { scroll : app.headerHeight } 35 | app.mainHeight = ( window.innerHeight-app.headerHeight ) 36 | app.headerHeight = app.headerHeight.toString()+'px'; 37 | app.mainHeight = app.mainHeight.toString()+'px'; 38 | return app; 39 | } 40 | app = mainHeight(app); 41 | 42 | // DISPLAY HEADER IF app.logo OR app.title 43 | // 44 | app.logo ||= ""; 45 | app.title ||= ""; 46 | app.headerHeight = ( app.logo || app.title || app.adminMenu ) ?app.headerHeight : "0"; 47 | 48 | // DISPLAY MENU AT TOP OR LEFT DEPENDING ON app.orientation 49 | // 50 | app.leftMenu = "" ; 51 | app.siteMenu = "" ; 52 | if(app.orientation==="horizontal"){ 53 | app.leftMenuStyle = "display:none"; 54 | } 55 | else { 56 | app.leftMenuStyle = ` 57 | display:block; 58 | width:36vw !important; 59 | height:${app.mainHeight}; 60 | overflow-y:scroll; 61 | margin:0; 62 | padding:0; 63 | `; 64 | } 65 | 66 | let appString = await this.getHTML(app); 67 | let element = solidUI.createElement('SPAN','',appString); 68 | let solidLogo = element.querySelector('#solidLogo') 69 | if(solidLogo) solidLogo.src = 'https://solidproject.org/assets/img/solid-emblem.svg'; 70 | 71 | // if(typeof app.currentPodMenu==='object')app.currentPodMenu.target = element.querySelector('.main'); 72 | let menuElement = element.querySelector("#currentPodMenu"); 73 | if( app.orientation==="vertical"){ 74 | menuElement = element.querySelector("NAV"); 75 | } 76 | const menu = await solidUI.processComponent(menuElement,app.currentPodMenu); 77 | if(menu && menuElement) menuElement.appendChild(menu); 78 | if(app.userMenu) { 79 | const amenu = await solidUI.processComponent(menuElement,app.userMenu); 80 | let umenu = element.querySelector('#userMenu') 81 | if( umenu) umenu.appendChild(amenu); 82 | } 83 | if(app.loginBox) await createLoginBox(element); 84 | await applyProfile(element); // munge site menu 85 | if(app.initialContent) { 86 | let content = await solidUI.getComponentHash(app.initialContent); 87 | content = content.content.interpolate(solidUI.vars) 88 | let main = element.querySelector('#mainMain'); 89 | if(main) main.innerHTML = content; 90 | } 91 | return element; 92 | } 93 | 94 | rem2vw(rem) { 95 | const viewportWithoutScroll = document.body.clientWidth; 96 | const pxPerVw = 100/viewportWithoutScroll; 97 | return( rem * pxPerVw); 98 | } 99 | rem2vh(rem) { 100 | const viewportWithoutScroll = window.innerHeight; 101 | const pxPerVw = 100/viewportWithoutScroll; 102 | return( rem * 16 * pxPerVw); 103 | } 104 | async getHTML(app){ 105 | app.leftColumnColumnStyle ||= "display:none"; 106 | app.leftColumnColumnMenu ||= ""; 107 | app.siteMenu ||= ""; 108 | app.leftColumnColumnStyle ||= ""; 109 | app.iframeSrc ||= ""; 110 | app.iframeContent ||= ""; 111 | app.logoStyle = ` 112 | height: 3rem; 113 | display: inline-block; 114 | padding-left: 0.5rem; 115 | `; 116 | app.titleStyle = ` 117 | display:inline-block; 118 | vertical-align:top; align:left; 119 | font-size: 2rem; 120 | padding-top: 0.4rem; 121 | padding-left:1rem;" 122 | `; 123 | app.appStye = ` 124 | display: flex; 125 | flex-direction: column; 126 | width: 100%; 127 | height: 100%; 128 | `; 129 | app.mainStyle = ` 130 | padding:0rem; 131 | width:100%; 132 | overflow:scroll; 133 | position:absolute; 134 | top:${app.headerHeight} !important; 135 | left:0; 136 | height:${app.mainHeight}; 137 | `; 138 | if(app.theme) { 139 | try { 140 | // let response = await panes.UI.store.fetcher.webOperation('GET',app.theme); 141 | let response = await window.fetch(app.theme); 142 | let content = await response.text(); 143 | content = solidUI.fillTemplate(content,[app]); 144 | return await content; 145 | } 146 | catch(e){console.log(e)} 147 | } 148 | return ""; 149 | return ` 150 | 151 |
    152 | 153 | 154 |
    155 | 156 | 157 | 158 |
    159 | 160 | 161 | 162 |
    163 |
    164 | 165 | 166 | 167 |
    ${app.siteMenu}
    168 | 169 |
    170 | 171 | 172 | 173 |
    174 | 175 | 176 |
    ${app.leftMenu}
    177 | 178 | 179 |
    180 | 181 | 182 |
    183 |
    184 |
    185 |
    186 | 187 |
    188 | 189 |
    190 | 226 | 227 | `; 228 | } 229 | } 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /src/view/app-old.js: -------------------------------------------------------------------------------- 1 | import {createLoginBox,applyProfile} from '../databrowser.js'; 2 | 3 | export class App { 4 | async render(solidUI,app){ 5 | 6 | // GET/SET DEFAULTS 7 | // 8 | app = solidUI.setDefaults(app); 9 | if(app.fullPage){ 10 | window.addEventListener('resize',()=>{ 11 | app = mainHeight(app); 12 | let mm = document.getElementById('mainMain') 13 | let tab = document.getElementById('suicTabulator') 14 | if(mm) mm.style.height = app.mainHeight; 15 | if(tab) tab.style.height = app.mainHeight; 16 | }); 17 | document.body.style.overflow="hidden"; 18 | } 19 | 20 | // FIND NUMBER OF PIXELS IN 1 REM - VARIES WITH BROWSER/USER SETTINGS 21 | function rem2px(){ 22 | let el = document.createElement('DIV'); 23 | el.style="height:1rem"; 24 | document.body.appendChild(el); 25 | let remSize = el.clientHeight; 26 | document.body.removeChild(el); 27 | return(remSize); 28 | } 29 | // SET BODY HEIGHT TO WINDOW HIEGHT MINUS HEADER + MENU 30 | function mainHeight(app){ 31 | let rem = rem2px(); 32 | app.menuHeight = 2*rem; 33 | app.headerHeight = 6*rem; 34 | window.SolidAppContext = { scroll : app.headerHeight } 35 | app.mainHeight = ( window.innerHeight-app.headerHeight ) 36 | app.headerHeight = app.headerHeight.toString()+'px'; 37 | app.mainHeight = app.mainHeight.toString()+'px'; 38 | return app; 39 | } 40 | app = mainHeight(app); 41 | 42 | // DISPLAY HEADER IF app.logo OR app.title 43 | // 44 | app.logo ||= ""; 45 | app.title ||= ""; 46 | app.headerHeight = ( app.logo || app.title || app.adminMenu ) ?app.headerHeight : "0"; 47 | 48 | // DISPLAY MENU AT TOP OR LEFT DEPENDING ON app.orientation 49 | // 50 | app.leftMenu = "" ; 51 | app.siteMenu = "" ; 52 | if(app.orientation==="horizontal"){ 53 | app.leftMenuStyle = "display:none"; 54 | } 55 | else { 56 | app.leftMenuStyle = ` 57 | display:block; 58 | width:36vw !important; 59 | height:${app.mainHeight}; 60 | overflow-y:scroll; 61 | margin:0; 62 | padding:0; 63 | `; 64 | } 65 | 66 | let appString = await this.getHTML(app); 67 | let element = solidUI.createElement('SPAN','',appString); 68 | let solidLogo = element.querySelector('#solidLogo') 69 | if(solidLogo) solidLogo.src = 'https://solidproject.org/assets/img/solid-emblem.svg'; 70 | 71 | // if(typeof app.currentPodMenu==='object')app.currentPodMenu.target = element.querySelector('.main'); 72 | let menuElement = element.querySelector("#currentPodMenu"); 73 | if( app.orientation==="vertical"){ 74 | menuElement = element.querySelector("NAV"); 75 | } 76 | const menu = await solidUI.processComponent(menuElement,app.currentPodMenu); 77 | if(menu && menuElement) menuElement.appendChild(menu); 78 | if(app.userMenu) { 79 | const amenu = await solidUI.processComponent(menuElement,app.userMenu); 80 | let umenu = element.querySelector('#userMenu') 81 | if( umenu) umenu.appendChild(amenu); 82 | } 83 | if(app.loginBox) await createLoginBox(element); 84 | await applyProfile(element); // munge site menu 85 | if(app.initialContent) { 86 | let content = await solidUI.getComponentHash(app.initialContent); 87 | content = content.content.interpolate(solidUI.vars) 88 | let main = element.querySelector('#mainMain'); 89 | if(main) main.innerHTML = content; 90 | } 91 | return element; 92 | } 93 | 94 | rem2vw(rem) { 95 | const viewportWithoutScroll = document.body.clientWidth; 96 | const pxPerVw = 100/viewportWithoutScroll; 97 | return( rem * pxPerVw); 98 | } 99 | rem2vh(rem) { 100 | const viewportWithoutScroll = window.innerHeight; 101 | const pxPerVw = 100/viewportWithoutScroll; 102 | return( rem * 16 * pxPerVw); 103 | } 104 | async getHTML(app){ 105 | app.leftColumnColumnStyle ||= "display:none"; 106 | app.leftColumnColumnMenu ||= ""; 107 | app.siteMenu ||= ""; 108 | app.leftColumnColumnStyle ||= ""; 109 | app.iframeSrc ||= ""; 110 | app.iframeContent ||= ""; 111 | app.logoStyle = ` 112 | height: 3rem; 113 | display: inline-block; 114 | padding-left: 0.5rem; 115 | `; 116 | app.titleStyle = ` 117 | display:inline-block; 118 | vertical-align:top; align:left; 119 | font-size: 2rem; 120 | padding-top: 0.4rem; 121 | padding-left:1rem;" 122 | `; 123 | app.appStye = ` 124 | display: flex; 125 | flex-direction: column; 126 | width: 100%; 127 | height: 100%; 128 | `; 129 | app.mainStyle = ` 130 | padding:0rem; 131 | width:100%; 132 | overflow:scroll; 133 | position:absolute; 134 | top:${app.headerHeight} !important; 135 | left:0; 136 | height:${app.mainHeight}; 137 | `; 138 | if(app.theme) { 139 | try { 140 | // let response = await panes.UI.store.fetcher.webOperation('GET',app.theme); 141 | let response = await window.fetch(app.theme); 142 | let content = await response.text(); 143 | content = solidUI.fillTemplate(content,[app]); 144 | return await content; 145 | } 146 | catch(e){console.log(e)} 147 | } 148 | return ""; 149 | return ` 150 | 151 |
    152 | 153 | 154 |
    155 | 156 | 157 | 158 |
    159 | 160 | 161 | 162 |
    163 |
    164 | 165 | 166 | 167 |
    ${app.siteMenu}
    168 | 169 |
    170 | 171 | 172 | 173 |
    174 | 175 | 176 |
    ${app.leftMenu}
    177 | 178 | 179 |
    180 | 181 | 182 |
    183 |
    184 |
    185 |
    186 | 187 |
    188 | 189 |
    190 | 226 | 227 | `; 228 | } 229 | } 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /src/view/app.js: -------------------------------------------------------------------------------- 1 | import {createLoginBox,applyProfile} from '../databrowser.js'; 2 | 3 | export class App { 4 | async render(solidUI,app){ 5 | 6 | // GET/SET DEFAULTS 7 | // 8 | app = solidUI.setDefaults(app); 9 | if(app.fullPage){ 10 | window.addEventListener('resize',()=>{ 11 | app = mainHeight(app); 12 | let mm = document.getElementById('mainMain') 13 | let tab = document.getElementById('suicTabulator') 14 | if(mm) mm.style.height = app.mainHeight; 15 | if(tab) tab.style.height = app.mainHeight; 16 | }); 17 | document.body.style.overflow="hidden"; 18 | } 19 | 20 | // FIND NUMBER OF PIXELS IN 1 REM - VARIES WITH BROWSER/USER SETTINGS 21 | function rem2px(){ 22 | let el = document.createElement('DIV'); 23 | el.style="height:1rem"; 24 | document.body.appendChild(el); 25 | let remSize = el.clientHeight; 26 | document.body.removeChild(el); 27 | return(remSize); 28 | } 29 | // SET BODY HEIGHT TO WINDOW HIEGHT MINUS HEADER + MENU 30 | function mainHeight(app){ 31 | let rem = rem2px(); 32 | app.menuHeight = 2*rem; 33 | app.headerHeight = 6*rem; 34 | window.SolidAppContext = { scroll : app.headerHeight } 35 | app.mainHeight = ( window.innerHeight-app.headerHeight ) 36 | app.headerHeight = app.headerHeight.toString()+'px'; 37 | app.mainHeight = app.mainHeight.toString()+'px'; 38 | return app; 39 | } 40 | app = mainHeight(app); 41 | 42 | // DISPLAY HEADER IF app.logo OR app.title 43 | // 44 | app.logo ||= ""; 45 | app.title ||= ""; 46 | app.headerHeight = ( app.logo || app.title || app.adminMenu ) ?app.headerHeight : "0"; 47 | 48 | // DISPLAY MENU AT TOP OR LEFT DEPENDING ON app.orientation 49 | // 50 | app.leftMenu = "" ; 51 | app.siteMenu = "" ; 52 | if(app.orientation==="horizontal"){ 53 | app.leftMenuStyle = "display:none"; 54 | } 55 | else { 56 | app.leftMenuStyle = ` 57 | display:block; 58 | width:36vw !important; 59 | height:${app.mainHeight}; 60 | overflow-y:scroll; 61 | margin:0; 62 | padding:0; 63 | `; 64 | } 65 | 66 | let appString = await this.getHTML(app); 67 | let element = solidUI.createElement('SPAN','',appString); 68 | let solidLogo = element.querySelector('#solidLogo') 69 | if(solidLogo) solidLogo.src = 'https://solidproject.org/assets/img/solid-emblem.svg'; 70 | 71 | // if(typeof app.currentPodMenu==='object')app.currentPodMenu.target = element.querySelector('.main'); 72 | let menuElement = element.querySelector("#currentPodMenu"); 73 | if( app.orientation==="vertical"){ 74 | menuElement = element.querySelector("NAV"); 75 | } 76 | const menu = await solidUI.processComponent(menuElement,app.currentPodMenu); 77 | if(menu && menuElement) menuElement.appendChild(menu); 78 | if(app.userMenu) { 79 | const amenu = await solidUI.processComponent(menuElement,app.userMenu); 80 | let umenu = element.querySelector('#userMenu') 81 | if( umenu) umenu.appendChild(amenu); 82 | } 83 | if(app.loginBox) await createLoginBox(element); 84 | await applyProfile(element); // munge site menu 85 | if(app.initialContent) { 86 | let content = await solidUI.getComponentHash(app.initialContent); 87 | content = content.content.interpolate(solidUI.vars) 88 | let main = element.querySelector('#mainMain'); 89 | if(main) main.innerHTML = content; 90 | } 91 | return element; 92 | } 93 | 94 | rem2vw(rem) { 95 | const viewportWithoutScroll = document.body.clientWidth; 96 | const pxPerVw = 100/viewportWithoutScroll; 97 | return( rem * pxPerVw); 98 | } 99 | rem2vh(rem) { 100 | const viewportWithoutScroll = window.innerHeight; 101 | const pxPerVw = 100/viewportWithoutScroll; 102 | return( rem * 16 * pxPerVw); 103 | } 104 | async getHTML(app){ 105 | app.leftColumnColumnStyle ||= "display:none"; 106 | app.leftColumnColumnMenu ||= ""; 107 | app.siteMenu ||= ""; 108 | app.leftColumnColumnStyle ||= ""; 109 | app.iframeSrc ||= ""; 110 | app.iframeContent ||= ""; 111 | app.logoStyle = ` 112 | height: 3rem; 113 | display: inline-block; 114 | padding-left: 0.5rem; 115 | `; 116 | app.titleStyle = ` 117 | display:inline-block; 118 | vertical-align:top; align:left; 119 | font-size: 2rem; 120 | padding-top: 0.4rem; 121 | padding-left:1rem;" 122 | `; 123 | app.appStye = ` 124 | display: flex; 125 | flex-direction: column; 126 | width: 100%; 127 | height: 100%; 128 | `; 129 | app.mainStyle = ` 130 | padding:0rem; 131 | width:100%; 132 | overflow:scroll; 133 | position:absolute; 134 | top:${app.headerHeight} !important; 135 | left:0; 136 | height:${app.mainHeight}; 137 | `; 138 | if(app.theme) { 139 | try { 140 | // let response = await panes.UI.store.fetcher.webOperation('GET',app.theme); 141 | let response = await window.fetch(app.theme); 142 | let content = await response.text(); 143 | content = solidUI.fillTemplate(content,[app]); 144 | return await content; 145 | } 146 | catch(e){console.log(e)} 147 | } 148 | return ""; 149 | return ` 150 | 151 |
    152 | 153 | 154 |
    155 | 156 | 157 | 158 |
    159 | 160 | 161 | 162 |
    163 |
    164 | 165 | 166 | 167 |
    ${app.siteMenu}
    168 | 169 |
    170 | 171 | 172 | 173 |
    174 | 175 | 176 |
    ${app.leftMenu}
    177 | 178 | 179 |
    180 | 181 | 182 |
    183 |
    184 |
    185 |
    186 | 187 |
    188 | 189 |
    190 | 226 | 227 | `; 228 | } 229 | } 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /src/view/app.solidos.js: -------------------------------------------------------------------------------- 1 | import {createLoginBox,applyProfile} from '../databrowser.js'; 2 | /* 3 |
    12 | */ 13 | export class App { 14 | async render(solidUI,app){ 15 | let appString = ""; 16 | try { 17 | //let response=await panes.UI.store.fetcher.webOperation('GET',app.theme); 18 | let response = await window.fetch(app.theme); 19 | appString = solidUI.fillTemplate(await response.text(),[app]); 20 | } 21 | catch(e){console.log(e)} 22 | app = solidUI.setDefaults(app); 23 | let element = solidUI.createElement('SPAN','',appString); 24 | /* 25 | currentPodMenu 26 | userMenu 27 | initialContent 28 | podRoot : storage, 29 | podName : me.name, 30 | podNick : me.nick, 31 | podWebid : me.webId, 32 | podImage : me.image, 33 | podInbox : me.inbox, 34 | podProfile : me.webId, 35 | 36 | hard-coded 37 | solidLogo 38 | loginBox 39 | on pod-load 40 | get currentPod root,name,nick,webid,image,inbox 41 | fill 42 | on login 43 | get currentUser root,name,nick,webid,image,inbox 44 | */ 45 | 46 | // solidLogo 47 | // 48 | let solidLogo = element.querySelector('#solidLogo') 49 | if(solidLogo) solidLogo.src = 'https://solidproject.org/assets/img/solid-emblem.svg'; 50 | 51 | // currentPodMenu 52 | // 53 | /* 54 | let menuElement = element.querySelector("#currentPodMenu"); 55 | if( app.orientation==="vertical"){ 56 | menuElement = element.querySelector("NAV"); 57 | } 58 | const menu = await solidUI.processComponent(menuElement,app.currentPodMenu); 59 | if(menu && menuElement) menuElement.appendChild(menu); 60 | 61 | 62 | // userMenu 63 | // 64 | if(app.userMenu) { 65 | const amenu = await solidUI.processComponent(menuElement,app.userMenu); 66 | let umenu = element.querySelector('#userMenu') 67 | if( umenu) umenu.appendChild(amenu); 68 | } 69 | */ 70 | // loginBox & initialContent 71 | // 72 | if(app.loginBox) await createLoginBox(element); 73 | await applyProfile(element,'','appLoad'); // munge site menu 74 | 75 | /* 76 | if(app.initialContent) { 77 | let content = await solidUI.getComponentHash(app.initialContent); 78 | content = content.content.interpolate(solidUI.vars) 79 | let main = element.querySelector('#mainMain'); 80 | if(main) main.innerHTML = content; 81 | } 82 | */ 83 | //console.log(element); 84 | element = await solidUI.initInternal(element); 85 | //console.log(nel); 86 | return element; 87 | } 88 | } 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /src/view/bookmarkTree.js: -------------------------------------------------------------------------------- 1 | export class BookmarkTree { 2 | 3 | constructor(){ 4 | this.store = UI.rdf.graph(); 5 | this.fetcher = UI.rdf.fetcher(this.store,{fetch:UI.store.fetcher._fetch}); 6 | } 7 | async init(){ 8 | for(let containerElm of document.getElementsByClassName('ocb')){ 9 | let subject = containerElm.dataset.component 10 | this.bookmarksDisplay = containerElm.dataset.display 11 | try { 12 | subject = await this.loadUnlessLoaded(subject) 13 | containerElm = containerElm.appendChild(document.createElement('UL') ) 14 | await this.getTopic(subject,containerElm,this.bookmarksDisplay,'start') 15 | document.getElementById('ocbStart').click(); 16 | } 17 | catch(e){ alert(e) } 18 | } 19 | } 20 | async getTopic(topic,containingElement,bookmarksDisplay,start){ 21 | const self=this 22 | if(typeof containingElement==="string") containingElement = document.querySelector(containingElement); 23 | bookmarksDisplay ||= containingElement.dataset.display; 24 | this.bookmarksDisplay ||= bookmarksDisplay; 25 | 26 | let book = UI.rdf.Namespace("http://www.w3.org/2002/01/bookmark#") 27 | const rdf= UI.rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#') 28 | const rdfs=UI.rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#') 29 | const ui=UI.rdf.Namespace('http://www.w3.org/ns/ui#'); 30 | 31 | topic = await this.loadUnlessLoaded(topic); 32 | let topicTitle = this.store.any( topic, rdf('label'),null,topic.doc() ) 33 | topicTitle = (typeof topicTitle ==="undefined") ? "???" : topicTitle.value 34 | let span = document.createElement('SPAN') 35 | span.appendChild(document.createTextNode(topicTitle)) 36 | span.classList.add('caret') 37 | span.addEventListener("click", function() { 38 | let elm = this.parentElement.querySelector(".nested") 39 | if(elm) elm.classList.toggle("active"); 40 | this.classList.toggle("caret-down"); 41 | }); 42 | let li = document.createElement('LI') 43 | if(start){ 44 | span.id = "ocbStart"; 45 | // containingElement.appendChild(span) 46 | } 47 | // else { 48 | li.appendChild(span) 49 | containingElement.appendChild(li) 50 | // } 51 | let subTopics = this.store.each( null, book('subTopicOf'), topic ) 52 | if(subTopics.length>0){ 53 | let ul = document.createElement('UL') 54 | ul.classList.add('bookmarkTree') 55 | ul.classList.add('nested') 56 | li.appendChild(ul) 57 | for(let t in subTopics){ 58 | this.getTopic(subTopics[t],ul) 59 | } 60 | } 61 | else { 62 | let bookmarks = this.store.each( null, book('hasTopic'), topic ) 63 | let ul2 = document.createElement('UL') 64 | ul2.classList.add('nested') 65 | for(let b in bookmarks){ 66 | let bookmark = bookmarks[b] 67 | let bookmarkTitle = this.store.any( bookmark, rdf('label'),null,topic.doc() ) 68 | let isFeed=(this.store.match(bookmark,rdfs('type'),ui('Feed'))).length; 69 | bookmarkTitle = bookmarkTitle ? bookmarkTitle.value : "???" 70 | let bookmarkLink = this.store.any( bookmark, book('recalls'),null,topic.doc() ) 71 | let bookElement = document.createElement("LI") 72 | if(isFeed) bookElement.classList.add('caret') 73 | else bookElement.classList.add('item') 74 | if(isFeed){ 75 | bookElement.addEventListener("click", async function() { 76 | alert("Feed Me!"); 77 | }); 78 | } 79 | else { 80 | bookElement.addEventListener("click", async function() { 81 | document.querySelector(self.bookmarksDisplay).innerHTML=``; 82 | }); 83 | } 84 | bookElement.appendChild(document.createTextNode(bookmarkTitle)) 85 | let d = document.createElement('DIV') 86 | d.classList.add('closed') 87 | bookElement.appendChild(d) 88 | ul2.appendChild(bookElement) 89 | } 90 | li.appendChild(ul2) 91 | } 92 | } 93 | async loadUnlessLoaded(subject){ 94 | try { 95 | const base = document.location.href.replace(/\/[^\/]*$/,'/') 96 | subject = (typeof subject==="string") ? subject : subject.uri 97 | subject = subject.match('//') ? subject : base + subject 98 | subject = UI.rdf.sym(subject) 99 | if(subject.termType==='BlankNode') return subject 100 | if(!this.store.any(null,null,null,subject.doc())) 101 | await this.fetcher.load(subject) 102 | } 103 | catch(e) { alert(e) } 104 | return subject 105 | } 106 | } 107 | 108 | /* 109 | window.addEventListener('DOMContentLoaded', async (event) => { 110 | const ocb = new BookmarkTree() 111 | ocb.init() 112 | }) 113 | */ 114 | -------------------------------------------------------------------------------- /src/view/button.js: -------------------------------------------------------------------------------- 1 | export async function button(component){ 2 | const targetElement = component.contentArea || document.body; 3 | let button = document.createElement('BUTTON'); 4 | button.innerHTML = component.label; 5 | if(component.title) button.title = component.title; 6 | if(component.style) button.style = component.style; 7 | /* 8 | button.style = component.style; 9 | button.style.cursor ||= "pointer"; 10 | button.style.color ||= solidUI.buttonColor; 11 | button.style.backgroundColor ||= solidUI.buttonBackgroundColor; 12 | */ 13 | button = solidUI.styleButton(button,component); 14 | 15 | button.addEventListener('click',(e)=>{ 16 | alert(component.onclick) 17 | window[component.onclick](e); 18 | }); 19 | /* 20 | let script = `(()=>{${component.onclick}})()`; 21 | button.onclick = async ()=>{ 22 | Function('"use strict";return ('+script+')')(); 23 | }; 24 | */ 25 | targetElement.appendChild(button); 26 | } 27 | -------------------------------------------------------------------------------- /src/view/buttonListMenu.js: -------------------------------------------------------------------------------- 1 | export async function buttonListMenu(options){ 2 | if(options.displayArea && options.startingContent){ 3 | await solidUI.util.show('',options.startingContent,'',options.displayArea); 4 | } 5 | let displayIn = options.displayArea; 6 | displayIn=typeof displayIn==="string" ?document.querySelector(displayIn) :displayIn; 7 | let div = document.createElement('DIV'); 8 | // div.id = options.contentSource.replace(/.*\#/,''); 9 | let html = ""; 10 | let parts = options.dataSource; 11 | if(typeof parts.length==="undefined")parts=[parts]; 12 | for(let p of parts){ 13 | let attrs = ""; 14 | let b = document.createElement('BUTTON'); 15 | b.dataset.link = p.link || p.dataSource; 16 | b.dataset.label = p.label; 17 | b.dataset.linktype = p.linktype || p.pluginType; 18 | b.innerHTML = p.label; 19 | b.value = p.dataSource; 20 | b = solidUI.styleButton(b,options); 21 | b.style['font-size'] = "100%"; 22 | b.addEventListener('click',async(e)=>{ 23 | return solidUI.handleLinkClick(e,options); 24 | /* 25 | e.preventDefault(); 26 | let link = b.dataset.link; 27 | let linkType = b.dataset.linktype || ""; 28 | if(solidUI.hideTabulator) solidUI.hideTabulator(); 29 | if(linkType==='Replace'){ 30 | window.location.href=link; 31 | } 32 | else if(linkType==='External'){ 33 | return u.showIframeSrc(link,options.displayArea); 34 | } 35 | else if(linkType==='SolidOS'){ 36 | return u.show('SolidOSLink',link,null,null,null,options) 37 | } 38 | else if(linkType==='Component') { 39 | let newDiv = document.createElement('DIV'); 40 | newDiv.dataset["suic"]=link; 41 | displayIn.innerHTML = ""; 42 | displayIn.appendChild(newDiv); 43 | displayIn = await solidUI.initInternal(displayIn) ; 44 | } 45 | */ 46 | }); 47 | div.appendChild(b) 48 | } 49 | div.style['text-align']="center"; 50 | return div; 51 | } 52 | -------------------------------------------------------------------------------- /src/view/buttonListMenu.old.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export async function buttonListMenu(json){ 4 | if(json.displayArea && json.startingContent){ 5 | await solidUI.util.show('',json.startingContent,'',json.displayArea); 6 | } 7 | let div = document.createElement('DIV'); 8 | div.id = json.contentSource.replace(/.*\#/,''); 9 | let html = ""; 10 | // if(typeof json.dataSource==="string") json.dataSource = await solidUI.processComponentSubject(json.dataSource); 11 | //console.log(33,json.dataSource) 12 | for(let ds of json.dataSource){ 13 | let attrs = ""; 14 | if(!ds.dataSource) { 15 | ds = await solidUI.getComponentHash(ds); 16 | } 17 | let b = document.createElement('BUTTON'); 18 | for(let k of Object.keys(ds)){ 19 | b.setAttribute(`data-${k}`,ds[k]); 20 | } 21 | b.innerHTML = ds.label; 22 | b = solidUI.styleButton(b,json); 23 | b.addEventListener('click',async(e)=>{ 24 | e.preventDefault(); 25 | let link = b.getAttribute('data-link') || b.getAttribute('data-dataSource') 26 | await solidUI.showPage(null,{link,displayArea:json.displayArea},ds); 27 | }); 28 | div.appendChild(b) 29 | } 30 | let targetElement; 31 | try{ targetElement = document.querySelector(json.contentArea); } 32 | catch(e) { targetElement = document.createElement('DIV'); } 33 | targetElement.innerHTML = ""; 34 | targetElement.appendChild(div) 35 | return targetElement 36 | 37 | let buttons = targetElement.querySelectorAll('BUTTON'); 38 | for(let button of buttons){ 39 | button = solidUI.styleButton(button,json); 40 | let tag = button.getAttribute('about'); 41 | button.addEventListener('click',async(e)=>{ 42 | e.preventDefault(); 43 | let link = button.getAttribute('data-link') || button.getAttribute('data-dataSource') 44 | await solidUI.showPage(null,{link,displayArea:targetElement}); 45 | }); 46 | } 47 | return targetElement; 48 | } 49 | -------------------------------------------------------------------------------- /src/view/componentButton.js: -------------------------------------------------------------------------------- 1 | export async function componentButton(thing){ 2 | 3 | let component = thing; 4 | if(typeof thing!="object") component = await getButtonComponent(thing); 5 | let button = document.createElement('BUTTON'); 6 | button.innerHTML = component.icon ?component.icon :component.label; 7 | button.addEventListener('click',async (e)=>{ 8 | await solidUI.processComponent(null,component.dataSource); 9 | }); 10 | button.style = component.style; 11 | button.style.cursor ||= "pointer"; 12 | button.style.background ||= "#2196F3"; 13 | button.style.color ||= "#ffffff"; 14 | button.style.padding ||= "0.5em"; 15 | button.style["border-radius"] ||= "0.2em"; 16 | if(component.targetSelector) component.targetSelector.appendChild(button); 17 | return button; 18 | 19 | async function getButtonComponent(uri){ 20 | let ui = UI.rdf.Namespace("http://www.w3.org/ns/ui#"); 21 | let c = {}; 22 | uri = UI.rdf.sym(uri); 23 | await UI.store.fetcher.load(uri); 24 | c.label = (UI.store.any(uri,ui('label'))||{}).value; 25 | c.content = (UI.store.any(uri,ui('icon'))||{}).value; 26 | c.style = (UI.store.any(uri,ui('style'))||{}).value; 27 | c.headerstyle = (UI.store.any(uri,ui('headerStyle'))||{}).value; 28 | c.dataSource = (UI.store.any(uri,ui('dataSource'))||{}).value; 29 | return c; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/view/draggable.js: -------------------------------------------------------------------------------- 1 | 2 | export async function draggable(thing){ 3 | let component = thing; 4 | if(typeof thing!="object") component = await getFloatComponent(thing); 5 | let id = getDraggableId(component); 6 | let draggableDomElement = document.getElementById(id); 7 | 8 | /* If it already exists, show it and return 9 | */ 10 | if(draggableDomElement){ 11 | draggableDomElement.style.display = "block"; 12 | return; 13 | } 14 | 15 | let div = document.createElement('DIV'); 16 | let header = document.createElement('DIV'); 17 | let close = document.createElement('SPAN'); 18 | let content = document.createElement('DIV'); 19 | let pageEl = document.getElementById('right-column-tabulator'); 20 | content.innerHTML = pageEl.innerHTML; 21 | pageEl.innerHTML = ""; 22 | // pageEl.style.display="none"; 23 | // content = await getDraggableContent(component,content); 24 | div.id = id; 25 | 26 | close.innerHTML=" X "; 27 | close.style.color = "#ffeeee"; 28 | close.style["font-weight"] = "bold"; 29 | close.style["margin-left"] = "2em"; 30 | close.style.background = "#000000"; 31 | close.style.padding = "0.2em;" 32 | close.style["border-radius"] ||= "0.2em"; 33 | close.addEventListener('click',(e)=>{ 34 | e.target.parentNode.parentNode.style.display = "none"; 35 | }); 36 | 37 | 38 | /* STYLE OF THE DRAGGABLE DIV */ 39 | div.style = component.style || {}; 40 | div.style.position ||= "absolute"; 41 | div.style["z-index"] ||= 3; 42 | div.style.background ||= "#f1f1f1"; 43 | div.style.border ||= "1px solid #d3d3d3"; 44 | div.style["text-align"] ||= ":center"; 45 | // div.style.height="80%"; 46 | // div.style.width="80%"; 47 | 48 | /* STYLE OF DRAGGABLE DIV'S HEADER */ 49 | header.style = component.headerStyle || {}; 50 | header.style.padding ||= "10px"; 51 | header.style.cursor ||= "move"; 52 | header.style["z-index"] ||= 4; 53 | header.style["text-align"] ||= "center"; 54 | header.style.background ||= "#2196F3"; 55 | header.style.color ||= "#ffffff"; 56 | 57 | /* STYLE OF DRAGGABLE DIV'S CONTENT */ 58 | content.style = component.style || {}; 59 | content.style.padding ||= "1em"; 60 | 61 | /* MOUSE ACTIONS */ 62 | let pos1,pos2,pos3,pos4; 63 | div.onmousedown = (e)=>{ /* DRAG ELEMENT CLICK */ 64 | e = e || window.event; 65 | e.preventDefault(); 66 | pos3 = e.clientX; 67 | pos4 = e.clientY; 68 | document.onmouseup = (e)=>{ /* DRAG ELEMNT DROP */ 69 | document.onmouseup = null; 70 | document.onmousemove = null; 71 | } 72 | document.onmousemove = (e)=>{ /* DRAG ELEMENT DRAG */ 73 | e = e || window.event; 74 | e.preventDefault(); 75 | pos1 = pos3 - e.clientX; 76 | pos2 = pos4 - e.clientY; 77 | pos3 = e.clientX; 78 | pos4 = e.clientY; 79 | div.style.top = (div.offsetTop - pos2) + "px"; 80 | div.style.left = (div.offsetLeft - pos1) + "px"; 81 | } 82 | }; 83 | 84 | header.appendChild(close); 85 | div.appendChild(header); 86 | div.appendChild(content); 87 | document.body.appendChild(div); 88 | return div; 89 | 90 | /* UTILITY FUNCTIONS */ 91 | function getDraggableId(component){ 92 | return component.label.replace(/\s+/g,"_"); 93 | } 94 | async function getDraggableContent(component,content){ 95 | let ds; 96 | header.innerHTML = `${component.label}`; 97 | if(component.content) { 98 | content.innerHTML = component.content; 99 | } 100 | else if(component.dataSource) { 101 | ds = await solidUI.processComponent(null,component.dataSource); 102 | content.appendChild( ds ); 103 | } 104 | else if(component.dataSourceType) { 105 | ds=await solidUI.util.show(component.dataSourceType,null,null,null,null,component); 106 | content.appendChild( ds ); 107 | } 108 | return content; 109 | } 110 | async function getDraggableComponent(uri){ 111 | let ui = UI.rdf.Namespace("http://www.w3.org/ns/ui#"); 112 | let c = {}; 113 | uri = UI.rdf.sym(uri); 114 | await UI.store.fetcher.load(uri); 115 | c.label = (UI.store.any(uri,ui('label'))||{}).value; 116 | c.content = (UI.store.any(uri,ui('content'))||{}).value; 117 | c.style = (UI.store.any(uri,ui('style'))||{}).value; 118 | c.headerstyle = (UI.store.any(uri,ui('headerStyle'))||{}).value; 119 | c.dataSource = (UI.store.any(uri,ui('dataSource'))||{}).value; 120 | return c; 121 | } 122 | 123 | } 124 | 125 | -------------------------------------------------------------------------------- /src/view/extras: -------------------------------------------------------------------------------- 1 |
    10 |
    11 | 12 | 13 | 14 |
    15 |
      16 |
    • 17 |
    • 18 |
    • 19 |
    • 20 |
    21 |
      22 |
    • 23 |
    • 24 |
    • 25 |
    • 26 |
    27 |
    28 | 29 |
    30 | 31 |
    32 |
    33 | -------------------------------------------------------------------------------- /src/view/form.js: -------------------------------------------------------------------------------- 1 | import * as u from '../utils.js'; 2 | /* 3 | form.render() // define options via Javascript parameters 4 | form.fromUrl() // define options via ui:Component file 5 | form.fromMem() // define options via a ui:Component string 6 | 7 | */ 8 | export class Form { 9 | 10 | /** 11 | * @param {String} form - the URL of the form definition document 12 | * @param {String} formSubject - the data for the form 13 | * @param {String} [formString] - a string form definition 14 | * @param {HTMLElement} [dom=document] - DOM container to hold form 15 | * @param {Boolean} [forceReload] - empty store before loading 16 | * @param {String} [formResultDocument] - place to store form results 17 | * @param {Function} [script] - Javascript to execute on form change 18 | * @returns an HTML DIV element containing the form 19 | */ 20 | async fromURL(url){ 21 | let node = UI.rdf.sym(url); 22 | await UI.fetcher.load(node); 23 | return await this.render({ 24 | form : url, 25 | formSubject : u.getProperty(node,'ui:formSubject'), 26 | script : u.getProperty(node,'ui:script'), 27 | resultDocument : u.getProperty(node,'ui:formResultDocument'), 28 | }); 29 | } 30 | async render(o){ 31 | o.form ||= o.contentSource; 32 | const dom = o.dom || document; 33 | const container = document.createElement("DIV"); 34 | container.classList.add('uic-form'); 35 | let form = UI.rdf.sym(o.form); 36 | let subject; 37 | if(o.formSubject) subject = UI.rdf.sym(o.formSubject) ; 38 | if(o.formString){ 39 | UI.rdf.parse(string,UI.store,o.form,'text/turtle'); 40 | } 41 | else await UI.store.fetcher.load(o.form); 42 | if(!subject){ 43 | let fSubj = UI.rdf.sym('http://www.w3.org/ns/ui#formSubject'); 44 | subject = UI.store.any(form,fSubj) 45 | } 46 | if(o.forceReload){ 47 | if(subject && subject.doc) UI.store.removeDocument(subject.doc()); 48 | } 49 | if(subject) await UI.store.fetcher.load(subject.doc()); 50 | else { 51 | container.innerHTML="ERROR : Could not load form subject " 52 | return container; 53 | } 54 | const title = document.createElement("H2"); 55 | let doc = o.formResultDocument; 56 | if(subject && !doc && subject.doc) doc = subject.doc(); 57 | const script = o.script || function (){ 58 | UI.store.fetcher.putBack(subject) // tempory work-around ui:ordered bug 59 | }; 60 | try { 61 | UI.widgets.appendForm(dom, container, {}, subject, form, doc, script); 62 | } 63 | catch(e){ 64 | // console.log(dom,container,subject,form,doc) 65 | container.innerHTML = "FORM ERROR:"+e; 66 | } 67 | return container; 68 | } 69 | 70 | } 71 | 72 | /* OLD 73 | async render(solidUI,options){ 74 | const container = document.createElement("DIV",'uic-form'); 75 | const dom = window.document; 76 | const form = await solidUI.loadUnlessLoaded(options.form); 77 | await solidUI.loadUnlessLoaded(options.formSubject); 78 | const subject = await solidUI.loadUnlessLoaded(options.formSubject); 79 | if(!subject) return console.log("ERROR : Couldn not load form subject ",options.formSubject) 80 | let doc = options.formResultDocument; 81 | if(!doc && subject.doc) doc = subject.doc(); 82 | const script = options.script || function(){}; 83 | try { 84 | UI.widgets.appendForm(dom, container, {}, subject, form, doc, script); 85 | } 86 | catch(e){return console.log(e)} 87 | if(options.onchange) { 88 | const vals = container.querySelectorAll('.formFieldValue button'); 89 | for(let v of vals){ 90 | v.onclick = "alert(3)"; 91 | } 92 | } 93 | return container; 94 | } 95 | 96 | */ 97 | -------------------------------------------------------------------------------- /src/view/linked-bookmarks.js: -------------------------------------------------------------------------------- 1 | export class OpenCultureBrowser { 2 | 3 | constructor(){ 4 | this.store = UI.rdf.graph(); 5 | this.fetcher = UI.rdf.fetcher(this.store); 6 | } 7 | async init(){ 8 | for(let containerElm of document.getElementsByClassName('ocb')){ 9 | let subject = containerElm.dataset.component 10 | this.bookmarksDisplay = containerElm.dataset.display 11 | try { 12 | subject = await this.loadUnlessLoaded(subject) 13 | containerElm = containerElm.appendChild(document.createElement('UL') ) 14 | await this.getTopic(subject,containerElm,'start') 15 | document.getElementById('ocbStart').click(); 16 | } 17 | catch(e){ alert(e) } 18 | } 19 | } 20 | async getTopic(topic,containingElement,start){ 21 | const self=this 22 | 23 | let book = UI.rdf.Namespace("http://www.w3.org/2002/01/bookmark#") 24 | const rdf= UI.rdf.Namespace('http://www.w3.org/2000/01/rdf-schema#') 25 | const rdfs=UI.rdf.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#') 26 | const ui=UI.rdf.Namespace('http://www.w3.org/ns/ui#'); 27 | 28 | topic = await this.loadUnlessLoaded(topic); 29 | let topicTitle = this.store.any( topic, rdf('label'),null,topic.doc() ) 30 | topicTitle = (typeof topicTitle ==="undefined") ? "???" : topicTitle.value 31 | let span = document.createElement('SPAN') 32 | span.appendChild(document.createTextNode(topicTitle)) 33 | span.classList.add('caret') 34 | span.addEventListener("click", function() { 35 | this.parentElement.querySelector(".nested").classList.toggle("active"); 36 | this.classList.toggle("caret-down"); 37 | }); 38 | let li = document.createElement('LI') 39 | li.appendChild(span) 40 | containingElement.appendChild(li) 41 | if(start){ 42 | span.id = "ocbStart"; 43 | } 44 | let subTopics = this.store.each( null, book('subTopicOf'), topic ) 45 | if(subTopics.length>0){ 46 | let ul = document.createElement('UL') 47 | ul.classList.add('nested') 48 | li.appendChild(ul) 49 | for(let t in subTopics){ 50 | this.getTopic(subTopics[t],ul) 51 | } 52 | } 53 | else { 54 | let bookmarks = this.store.each( null, book('hasTopic'), topic ) 55 | let ul2 = document.createElement('UL') 56 | ul2.classList.add('nested') 57 | for(let b in bookmarks){ 58 | let bookmark = bookmarks[b] 59 | let bookmarkTitle = this.store.any( bookmark, rdf('label'),null,topic.doc() ) 60 | let isFeed=(this.store.match(bookmark,rdfs('type'),ui('Feed'))).length; 61 | bookmarkTitle = bookmarkTitle ? bookmarkTitle.value : "???" 62 | let bookmarkLink = this.store.any( bookmark, book('recalls'),null,topic.doc() ) 63 | let bookElement = document.createElement("LI") 64 | if(isFeed) bookElement.classList.add('caret') 65 | else bookElement.classList.add('item') 66 | if(isFeed){ 67 | bookElement.addEventListener("click", async function() { 68 | alert("Feed Me!"); 69 | }); 70 | } 71 | else { 72 | bookElement.addEventListener("click", async function() { 73 | // document.getElementById(self.bookmarksDisplay).src=bookmarkLink.uri 74 | document.getElementById(self.bookmarksDisplay).innerHTML=``; 75 | }); 76 | } 77 | bookElement.appendChild(document.createTextNode(bookmarkTitle)) 78 | let d = document.createElement('DIV') 79 | d.classList.add('closed') 80 | bookElement.appendChild(d) 81 | ul2.appendChild(bookElement) 82 | } 83 | li.appendChild(ul2) 84 | } 85 | } 86 | async loadUnlessLoaded(subject){ 87 | try { 88 | const base = document.location.href.replace(/\/[^\/]*$/,'/') 89 | subject = (typeof subject==="string") ? subject : subject.uri 90 | subject = subject.match('//') ? subject : base + subject 91 | subject = UI.rdf.sym(subject) 92 | if(subject.termType==='BlankNode') return subject 93 | if(!this.store.any(null,null,null,subject.doc())) 94 | await this.fetcher.load(subject) 95 | } 96 | catch(e) { alert(e) } 97 | return subject 98 | } 99 | } 100 | 101 | window.addEventListener('DOMContentLoaded', async (event) => { 102 | const ocb = new OpenCultureBrowser() 103 | ocb.init() 104 | }) 105 | 106 | -------------------------------------------------------------------------------- /src/view/mediaList.js: -------------------------------------------------------------------------------- 1 | /* 2 | mediaList() 3 | mediaItem() 4 | _fillMediaTemplate() 5 | */ 6 | 7 | export async function mediaList(options){ 8 | const elm = document.createElement('SPAN'); 9 | for(let track of options.parts){ 10 | options.id = track.id; 11 | elm.appendChild( await mediaItem(options) ); 12 | } 13 | return elm; 14 | } 15 | 16 | export async function mediaItem(options){ 17 | options.orientation ||= options.contentArea.dataset.orientation; 18 | options.compact ||= 1; 19 | if(options.includeMetadata || options.contentArea.dataset.includemetadata){options.compact=0}; 20 | options.allowDownload ||= options.contentArea.dataset.allowdownload; 21 | let subject = UI.rdf.sym(options.id); 22 | await UI.store.fetcher.load(subject); 23 | let item = _getItemFromStore(subject); 24 | item. vertical = document.body.offsetWidth<750 || options.orientation==1; 25 | item.isVideo = false; 26 | let types = UI.store.match(subject,UI.ns.rdf('type')); 27 | for(let t of types){ 28 | if(t.object.value.match(/video/i)) item.isVideo =true; 29 | } 30 | return(_fillMediaTemplate(item,options) ); 31 | } 32 | 33 | function _getItemFromStore(subject){ 34 | return({ 35 | byArtist : (UI.store.any(subject,UI.ns.schema('byArtist'))||{}).value, 36 | encoding : (UI.store.any(subject,UI.ns.schema('encoding'))||{}).value, 37 | image : (UI.store.any(subject,UI.ns.schema('image'))||{}).value, 38 | description : (UI.store.any(subject,UI.ns.schema('description'))||{}).value, 39 | country : (UI.store.any(subject,UI.ns.schema('countryOfOrigin'))||{}).value, 40 | }); 41 | } 42 | 43 | function _fillMediaTemplate(i,options){ 44 | let table = document.createElement('DIV'); 45 | i.download = options.allowDownload ?"" :`controlslist="nodownload"`; 46 | if(i.vertical){ 47 | i.align ="center"; 48 | i.width="310px"; 49 | i.audioWidth="272px !important"; 50 | i.imageStyle = "width:272px;margin-top:0.5rem;margin-bottom:0.4rem;"; 51 | } 52 | else { 53 | i.audioWidth="272px !important"; 54 | i.imageStyle = "height:180px;float:left;margin-right:1rem;margin-top:0.5rem;"; 55 | i.width = "590px"; 56 | i.align ="left"; 57 | } 58 | if(i.isVideo){ 59 | i.audio = ""; 60 | i.visual = ``; 61 | } 62 | else { 63 | i.visual = ``; 64 | i.audio = `` 65 | } 66 | if(options.compact) { 67 | let wrapper = document.createElement('SPAN'); 68 | wrapper.innerHTML = i.isVideo ?i.visual :i.audio; 69 | return wrapper; 70 | } 71 | table.style = `min-height:212px; margin:0.8rem;text-align:${i.align}; padding:0.5rem; border-radius:0.5rem; background:#bb99ff; color:black; width:${i.width}`; 72 | table.innerHTML = ` 73 |
    74 | ${i.byArtist} - ${i.country} 75 |
    76 | ${i.visual} 77 |

    ${i.description}

    78 | ${i.audio} 79 | `; 80 | return table; 81 | // else { 82 | table.innerHTML = ` 83 | 84 | 85 | ${i.byArtist} - ${i.country} 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 |

    ${i.description}

    94 | 97 | 98 | 99 | `; 100 | // } 101 | return table; 102 | } 103 | -------------------------------------------------------------------------------- /src/view/menu.js: -------------------------------------------------------------------------------- 1 | export class Menu { 2 | 3 | async render( solidUI, json,element ){ 4 | if(typeof json==='string'){ 5 | json = await solidUI.processComponent(element,json); 6 | } 7 | if(json.displayArea && json.landingPage){ 8 | await solidUI.processComponentSubject(json.landingPage); 9 | } 10 | let nav = solidUI.createElement('NAV','solid-uic-dropdown-menu') 11 | let topUL = document.createElement('UL') 12 | let mainDisplay = document.createElement('DIV') 13 | this.pluginMenuArea = document.createElement('DIV'); 14 | this.pluginMenuArea.id = "pluginMenuArea"; 15 | this.pluginMenuArea.style.paddingTop = "3rem"; 16 | const wrapper = solidUI.createElement('DIV'); 17 | wrapper.style.width="100%"; 18 | wrapper.style["overflow-x"]="hidden"; 19 | topUL = _styleUL(topUL,json); 20 | wrapper.appendChild(nav); 21 | wrapper.appendChild(mainDisplay); 22 | wrapper.appendChild(this.pluginMenuArea); 23 | nav = _styleNav(nav,json); 24 | mainDisplay = _styleMain(mainDisplay,json); 25 | mainDisplay.innerHTML=""; 26 | mainDisplay.classList.add('main'); 27 | nav.appendChild(topUL) 28 | for(var i of json.parts){ 29 | topUL.appendChild( await this.renderMenuItem(i,json,mainDisplay) ) 30 | } 31 | return(wrapper); 32 | } 33 | 34 | async renderMenuItem(i,json,mainDisplay){ 35 | if(i.menu && i.menu.type && i.menu.type === "SparqlQuery"){ 36 | let menu = await solidUI.processComponent(null,null,i.menu); 37 | if(typeof menu==="object") i.parts = menu 38 | } 39 | if(!i.parts && i.dataSource ){ 40 | if(!i.pluginType || i.pluginType!="resourceSelector"){ 41 | let x = await solidUI.getComponentHash(i.dataSource); 42 | i.parts = x && x.parts ?x.parts :null; 43 | } 44 | } 45 | let li = document.createElement('LI') 46 | const sp = document.createElement('SPAN') 47 | sp.innerHTML = i.label; 48 | li.appendChild(sp) 49 | li.onmouseover = ()=>{ 50 | li.style.color = solidUI.menuColor; 51 | li.style.background = solidUI.menuHighlight; 52 | } 53 | li.onmouseout = ()=>{ 54 | li.style.color = solidUI.menuColor; 55 | li.style.background = solidUI.menuBackground; 56 | } 57 | if(i.popout || !i.parts){ 58 | /* 59 | *
  • ${i.label}
  • 60 | */ 61 | li.classList.add('item') 62 | // 63 | li.name = i.href || i.dataSource && i.dataSource.dataSource ?i.dataSource.dataSource :i.dataSource; 64 | li.pluginType = i.pluginType; 65 | 66 | li.dataset.link = li.name; 67 | li.dataset.linktype = i.pluginType; 68 | li.dataset.label = i.label; 69 | 70 | const self = this 71 | li.onclick = async (e)=>{ 72 | let pluginURI = li.dataset.link; 73 | let pluginType = li.dataset.linktype || li.dataset.link || ""; 74 | let pluginLabel = li.dataset.label; 75 | let pArea = this.pluginMenuArea; 76 | if(pluginType==="SolidOS"){ 77 | pArea = document.getElementById('outline') 78 | } 79 | let newId = pluginLabel.replace(/\s/g,"_"); 80 | let newArea = pArea.querySelector('#'+newId); 81 | let processedPlugin = await solidUI.processComponentSubject(pluginURI); 82 | // = Plugin JSON 83 | processedPlugin ||= i; 84 | if(processedPlugin.landingPage){ 85 | await solidUI.processComponentSubject(processedPlugin.landingPage); 86 | } 87 | processedPlugin.contentArea = '#' + newId; 88 | for(let plugin of pArea.childNodes){ 89 | plugin.style.display="none"; 90 | } 91 | if(!newArea && pluginType != "SolidOS"){ 92 | newArea = document.createElement('DIV'); 93 | newArea.id = newId; 94 | pArea.appendChild(newArea); 95 | newArea.innerHTML=`

    ${e.target.innerHTML}

    `; 96 | if(processedPlugin.type){ 97 | processedPlugin = await solidUI.processComponentJson(processedPlugin); 98 | newArea.appendChild(processedPlugin); 99 | } 100 | else if(!processedPlugin.dataSource || !pluginType==="SolidOS") { 101 | newArea.appendChild(processedPlugin) 102 | } 103 | if(pluginType==="resourceSelector"){ 104 | newArea.appendChild(processedPlugin) 105 | } 106 | } 107 | if(newArea) { 108 | newArea.style.display="block"; 109 | newArea.focus(); 110 | } 111 | } 112 | li = _styleLI(li,json); 113 | } 114 | else { 115 | /* 116 |
  • 117 | ${i.title} 118 |
      119 |
    120 |
  • 121 | */ 122 | li.classList.add('caret') 123 | let ul2 = document.createElement('UL') 124 | ul2.style.background="white"; 125 | ul2.style.width="100%"; 126 | li.appendChild(ul2) 127 | ul2.classList.add('nested') 128 | if(typeof i.parts.length==="undefined") i.parts = [i.parts]; 129 | for(var m in i.parts){ 130 | let newItem = i.parts[m] 131 | if(typeof newItem==='object') newItem.target=json.target; 132 | ul2.appendChild( await this.renderMenuItem(newItem,json,mainDisplay) ) 133 | } 134 | } 135 | return li 136 | } 137 | } 138 | 139 | // MENU CONTAINER 140 | function _styleNav(nav,json){ 141 | // nav.style.width="100%"; 142 | nav.style.padding="0 !impotant"; 143 | nav.style.background=json.color || solidUI.menuBackground; 144 | nav.style.color=json.background || solidUI.menuColor; 145 | nav.style.display="inline-block"; 146 | nav.style.textAlign=json.position; 147 | return nav; 148 | } 149 | 150 | // INITIAL STATE OF MENU 151 | // 152 | function _styleUL(ul,json){ 153 | // ul.style.width="100%"; 154 | ul.style.color = solidUI.menuColor; 155 | ul.style.background = solidUI.menuBackground; 156 | 157 | ul.style.padding="0 !impotant"; 158 | ul.style.margin="0 !impotant"; 159 | ul.style.textAlign=json.position; 160 | ul.style['padding-inline-start']="0 !important"; 161 | return ul; 162 | } 163 | // TOP MENU ITEM DISPLAY 164 | function _styleLI(li,json){ 165 | li.style.cursor="pointer" 166 | li.style.display="inline-block"; 167 | li.style.padding="0.5rem"; 168 | li.style.margin="0.25rem"; 169 | li.style.color = json.color || solidUI.menuColor; 170 | li.style.background = json.background || solidUI.menuBackground; 171 | return li; 172 | } 173 | // MENU DISPLAY 174 | function _styleMain(mainDisplay,json){ 175 | mainDisplay.style.width="100%"; 176 | /* 177 | mainDisplay.style.backgroundColor = 'red' || json.background || solidUI.menuBackground; 178 | mainDisplay.style.color = 'blue' || json.color || solidUI.menuColor; 179 | */ 180 | return mainDisplay; 181 | } 182 | 183 | -------------------------------------------------------------------------------- /src/view/menu.new.js: -------------------------------------------------------------------------------- 1 | export class Menu { 2 | 3 | async render( solidUI, json,element ){ 4 | if(typeof json==='string'){ 5 | json = await solidUI.processComponent(element,json); 6 | } 7 | let nav = solidUI.createElement('NAV') 8 | if(json.orientation==="horizontal") nav.classList.add('solid-uic-dropdown-menu'); 9 | let topUL = document.createElement('UL') 10 | let mainDisplay = document.createElement('DIV') 11 | nav.style.background=json.unselBackground; 12 | nav.style.color=json.unselColor; 13 | mainDisplay.style.width="100%"; 14 | mainDisplay.style.backgroundColor = json.background; 15 | mainDisplay.style.color = json.color; 16 | mainDisplay.innerHTML=""; 17 | mainDisplay.classList.add('main'); 18 | nav.appendChild(topUL) 19 | for(var i of json.parts){ 20 | topUL.appendChild( await this.renderMenuItem(i,json,mainDisplay) ) 21 | } 22 | const wrapper = solidUI.createElement('SPAN'); 23 | nav.style.display="inline-block"; 24 | nav.style.textAlign=json.position; 25 | topUL.style.textAlign=json.position; 26 | topUL.style.padding="0"; 27 | wrapper.appendChild(nav); 28 | wrapper.appendChild(mainDisplay); 29 | return(wrapper); 30 | } 31 | 32 | async renderMenuItem(i,json,mainDisplay){ 33 | if(i.menu && i.menu.type && i.menu.type === "SparqlQuery"){ 34 | let menu = await solidUI.processComponent(null,null,i.menu); 35 | if(typeof menu==="object") i.parts = menu 36 | } 37 | const li = document.createElement('LI') 38 | const sp = document.createElement('SPAN') 39 | sp.innerHTML = i.label 40 | li.appendChild(sp) 41 | li.style.cursor="pointer" 42 | li.style.display = "inline-block"; 43 | li.style.padding = "0.5em"; 44 | li.style.backgroundColor = json.unselBackground; 45 | li.style.color = json.unselColor; 46 | li.onmouseover = ()=>{ 47 | li.style.backgroundColor = json.selBackground; 48 | li.style.color = json.selColor; 49 | } 50 | li.onmouseout = ()=>{ 51 | li.style.backgroundColor = json.unselBackground; 52 | li.style.color = json.unselColor; 53 | } 54 | if(i.popout || !i.parts){ 55 | // i.target=json.target; 56 | /* 57 | *
  • ${i.label}
  • 58 | */ 59 | li.classList.add('item') 60 | li.name = i.href 61 | const self = this 62 | /* 63 | li.addEventListener('click',(e)=>{ 64 | window.displayLink(e,i,mainDisplay) 65 | }) 66 | We can't easily override the eventListener later so use onclick instead. 67 | */ 68 | li.onclick = (e)=>{ 69 | i.href = e.target.parentElement.name; 70 | window.displayLink(e,i,mainDisplay) 71 | } 72 | } 73 | else { 74 | /* 75 |
  • 76 | ${i.title} 77 |
      78 |
    79 |
  • 80 | */ 81 | li.classList.add('caret') 82 | let ul2 = document.createElement('UL') 83 | li.appendChild(ul2) 84 | ul2.classList.add('nested') 85 | for(var m in i.parts){ 86 | let newItem = i.parts[m] 87 | if(typeof newItem==='object') newItem.target=json.target; 88 | ul2.appendChild( await this.renderMenuItem(newItem,json,mainDisplay) ) 89 | } 90 | } 91 | return li 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/view/menuMenu.js: -------------------------------------------------------------------------------- 1 | export async function menuOfMenus(json){ 2 | let target = document.querySelector(json.displayArea); 3 | if(target && json.startingContent){ 4 | let content = await solidUI.processComponentSubject(json.startingContent) 5 | if(content) target.appendChild(content) 6 | // await solidUI.util.show('',content,'',json.displayArea); 7 | } 8 | let div = document.createElement('DIV'); 9 | div.id = json.contentSource.replace(/.*\#/,''); 10 | let html = ""; 11 | // if(!json.dataSource.dataSource) json.dataSource.dataSource = json.dataSource; 12 | for(let ds of json.dataSource){ 13 | let component = ds.directDisplay ?ds.dataSource :ds.id 14 | ds.directDisplay ||= ""; 15 | if(ds.plugin && ds.plugin.match(/External/)) ds.external = "true"; 16 | html += ``; 17 | } 18 | div.innerHTML = html; 19 | let targetElement = typeof json.contentArea==="string" ?document.querySelector(json.contentArea) :solidUI.currentElement; 20 | targetElement.innerHTML = ""; 21 | targetElement.appendChild(div) 22 | let moreHtml = "" 23 | for(let ds2 of json.dataSource){ 24 | let div2 = document.createElement('DIV'); 25 | div2.id=ds2.label.replace(/\s*/g,'_'); 26 | div2.setAttribute('data-suic',ds2.id); 27 | div2.style="display:none"; 28 | targetElement.appendChild(div2); 29 | } 30 | let buttons = targetElement.querySelectorAll('BUTTON'); 31 | for(let button of buttons){ 32 | button = solidUI.styleButton(button,json); 33 | let tag = button.getAttribute('about'); 34 | button.addEventListener('click',async(e)=>{ 35 | e.preventDefault(); 36 | let external = button.getAttribute('data-external'); 37 | external = external==="undefined" ?null :external; 38 | let direct = button.getAttribute('data-directDisplay'); 39 | direct = direct==="undefined" ?null :direct; 40 | let href = button.getAttribute('about'); 41 | if(direct){ 42 | await solidUI.showPage(null,{link:href,displayArea:targetElement}); 43 | return; 44 | } 45 | if(external) { 46 | return solidUI.processComponentSubject(href) 47 | } 48 | let divs = targetElement.querySelectorAll(json.contentArea + ' > DIV'); 49 | for(let d of divs){ 50 | let target = d.getAttribute('data-suic'); 51 | if(target===tag){ 52 | d.style.display="block"; 53 | await solidUI.activateComponent(d,targetElement); 54 | } 55 | else d.style.display="none"; 56 | } 57 | }); 58 | } 59 | return targetElement; 60 | } 61 | -------------------------------------------------------------------------------- /src/view/modal.js: -------------------------------------------------------------------------------- 1 | export class Modal { 2 | /* 3 | <#MyModal> 4 | a ui:Modal ; 5 | ui:label "BUTTON_LABEL" ; 6 | ui:unselColor "BUTTON_COLOR" ; 7 | ui:unselBackground "BUTTON_BACKGROUND_COLOR" ; 8 | ui:width "POPUP-BOX_WIDTH" . 9 | 10 | Modal.render({ 11 | label // button label 12 | unselColor // button color 13 | unselBackground // button background color 14 | width // popup-box width 15 | height // popup-box height 16 | content // popup-box content string 17 | iframe // popup-box url for iframe 18 | }); 19 | */ 20 | 21 | async render (o,solidUI) { 22 | let dom = o.dom || document; 23 | let modal = document.createElement('SPAN'); 24 | if(o.targetSelector){ 25 | modal = dom.querySelector(o.targetSelector); 26 | } 27 | const s = _getCSS(o); 28 | if(o.iframe){ 29 | let iframe = document.createElement("IFRAME"); 30 | o.content = ``; 31 | } 32 | if(!o.content && o.dataSource){ 33 | o.content = (await solidUI.processComponentSubject(o.dataSource)).outerHTML; 34 | } 35 | modal.innerHTML = ` 36 | 39 |
    40 |
    41 |
    42 | × 43 |
    44 | ${o.content} 45 |
    46 |
    47 | `; 48 | let button = modal.querySelector('button') 49 | button = solidUI.styleButton(button,o); 50 | if(solidUI) modal = await solidUI.initInternal(modal); 51 | if(o.contentArea) o.contentArea.appendChild(modal) 52 | return modal; 53 | } 54 | } 55 | window.openModal = function (element,action){ 56 | element.parentElement.children[1].style.display = "block" ; 57 | } 58 | window.closeModal = function (element,action){ 59 | element.parentElement.parentElement.style.display = "none" ; 60 | } 61 | 62 | function _getCSS(current){ 63 | return { 64 | "button": ` 65 | background-color:${current.darkBackground}; 66 | color:${current.unselColor}; 67 | cursor:pointer; 68 | `, 69 | ".modal": ` 70 | display: none; /* Hidden by default */ 71 | position: fixed; /* Stay in place */ 72 | left: 0; 73 | top: 0; 74 | overflow: auto; /* Enable scroll if needed */ 75 | background-color: rgb(0,0,0); /* Fallback color */ 76 | background-color: rgba(0,0,0,0.8); /* Black w/ opacity */ 77 | width: 100%; /* Full width */ 78 | height: 100%; /* Full height */ 79 | z-index:20000; 80 | text-align:center; 81 | `, 82 | ".modal-content": ` 83 | // background-color: #fefefe; 84 | background-color: ${current.lightBackground}; 85 | margin: 5% auto; /* 15% from the top and centered */ 86 | width:80vw; 87 | height:85vh; 88 | padding: 1rem; 89 | border: 1px solid #888; 90 | border-radius:0.5rem; 91 | `, 92 | ".close": ` 93 | color:red; 94 | text-align:right; 95 | margin-bottom: 0.25rem; 96 | font-size: 2rem; 97 | font-weight: bold; 98 | cursor:pointer; 99 | ` 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/view/optionSelector.js: -------------------------------------------------------------------------------- 1 | /* 2 | color // a ui:Color for text of the selector 3 | background // a ui:Color for the background of the selector 4 | highlight // a ui:Color for the selector options background 5 | size // the number of options to show 6 | parts // an array of options 7 | dataSource // a component address that yields an array of options 8 | onchange // action to take on selection 9 | */ 10 | export function optionSelector(j) { 11 | const hasSuic = typeof solidUI !="undefined"; 12 | j.color ||= hasSuic ?solidUI.menuColor :"#000000"; 13 | j.background ||= hasSuic ?solidUI.menuBackground : "#c0c0c0"; 14 | j.highlight ||= hasSuic ?solidUI.menuHighlight : "#909090"; 15 | j.size ||= hasSuic ?solidUI.selectorSize :0; 16 | const borderColor = j.color; 17 | const wrapper = document.createElement('DIV'); 18 | let collectionSelector = document.createElement('DIV'); 19 | collectionSelector.style = `border:1px solid ${borderColor};border-bottom:none`; 20 | collectionSelector.style.background=j.background; 21 | collectionSelector.style.color= j.color; 22 | j.parts ||= j.dataSource.parts; 23 | const firstOption = j.label || j.parts[0] ?(j.parts[0]).label || j.parts[0]:""; 24 | collectionSelector.innerHTML = `
    ${firstOption}
    `; 25 | let collectionsList = document.createElement('DIV'); 26 | collectionSelector.querySelector('div').onclick=()=>{ 27 | collectionsList.style.display||="block"; 28 | if(collectionsList.style.display==="block") collectionsList.style.display="none"; 29 | else collectionsList.style.display="block"; 30 | } 31 | for(let p of j.parts){ 32 | p.link ||= p.value; 33 | p.type ||= p.contentType; 34 | collectionsList.innerHTML += `${p.label}`; 35 | } 36 | let anchorList = collectionsList.querySelectorAll('A'); 37 | for(let anchor of anchorList){ 38 | anchor.onclick = (e)=>{ 39 | e.preventDefault(); 40 | let el = e.target; 41 | for(let a of anchorList){ 42 | if(a==el) a.style.background=j.background; 43 | else a.style.background=j.highlight; 44 | } 45 | if(!j.size) { 46 | el.parentElement.style.display="none"; 47 | collectionSelector.querySelector('DIV SPAN').innerHTML=el.innerHTML; 48 | } 49 | console.log(99,el.href,el.dataset.type,el.dataset.link) 50 | return j.onchange(el.innerHTML,el.href,el.dataset.type); 51 | } 52 | } 53 | if(j.size){ 54 | if(j.size>j.parts.length) j.size=j.parts.length; 55 | j.size = (j.size*2)+1+"rem"; 56 | collectionsList.style = `border:1px solid ${borderColor};border-top:none;height:${j.size}`; 57 | collectionsList.style.overflow="auto"; 58 | collectionsList.style["max-height"]=j.size; 59 | // collectionsList.style["border-radius"]="0.4rem"; 60 | } 61 | else { 62 | collectionsList.style=`border:1px solid ${borderColor};border-top:none;display:none`; 63 | // collectionSelector.style["border-radius"]="0.4rem"; 64 | collectionSelector.style["margin-bottom"]="0.4rem"; 65 | wrapper.appendChild(collectionSelector); 66 | } 67 | collectionsList.style["margin-bottom"]="0.4rem"; 68 | wrapper.appendChild(collectionsList); 69 | return wrapper; 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/view/reader.js: -------------------------------------------------------------------------------- 1 | export class FeedReader { 2 | async makeTopicSelector(options,targetSelector){ 3 | } 4 | } 5 | 6 | :myFeedReader 7 | 8 | :topics [ 9 | a ui:SparqlQuery ; 10 | ui:endpoint ; 11 | ui:query """ 12 | PREFIX schema: 13 | PREFIX ui: 14 | SELECT DISTINCT ?label WHERE { 15 | ?x a ui:Feed; schema:about ?label . 16 | } 17 | """ 18 | ] . 19 | 20 | :collections [ 21 | a ui:SparqlQuery ; 22 | ui:endpoint ; 23 | ui:query """ 24 | PREFIX schema: 25 | PREFIX ui: 26 | SELECT DISTINCT ?label ?href WHERE { 27 | ?x a ui:Feed; 28 | schema:about ${wantedTopic} ; 29 | ui:label ?label ; 30 | ui:href ?href . 31 | } 32 | """ 33 | ] . 34 | -------------------------------------------------------------------------------- /src/view/searchButton.js: -------------------------------------------------------------------------------- 1 | /* 2 | 29 | 30 | 31 | 34 | */ 35 | export async function searchButton(component){ 36 | 37 | const searchBox = document.createElement('DIV'); 38 | const targetElement = component.contentArea || document.body; 39 | const searchButton = document.createElement('BUTTON'); 40 | const searchInput = document.createElement('INPUT'); 41 | 42 | searchButton.title = component.title; 43 | searchBox.appendChild(searchInput); 44 | targetElement.appendChild(searchButton); 45 | targetElement.appendChild(searchBox); 46 | 47 | searchBox.style = "display:none;z-index:10;position:absolute;left:2.2rem; margin-top:-1rem; padding:0.4rem;background:brown;border-radius:0.4rem;" 48 | 49 | searchButton.innerHTML = component.label || "⚲" 50 | searchButton.style = "transform:rotate(315deg);font-size:1.75rem;"; 51 | searchButton.addEventListener("click",async()=>{ 52 | searchBox.style.display="block"; 53 | }); 54 | 55 | searchInput.style= "padding:0.5rem;width:60ch;border-radius:0.2rem"; 56 | searchInput.placeholder = component.placeholder; 57 | searchInput.addEventListener("keypress",async(event)=>{ 58 | if (event.key === "Enter") { 59 | searchBox.style.display="none"; 60 | const term = searchInput.value; 61 | if(term) solidUI.util.show('SolidOSLink',term,'','#display') 62 | } 63 | }); 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/view/selector.js: -------------------------------------------------------------------------------- 1 | import {optionSelector} from './optionSelector.js'; 2 | 3 | /* 4 | containerSelector 5 | url 6 | targetSelector 7 | resourceOnChange 8 | collectionSize 9 | resourceSize 10 | 11 | podSelector({ 12 | parts : [ {label,dataSource}, ... ], 13 | contentArea : display target of resource-picker 14 | displayArea : display target of resource 15 | collectionSize : integer, max height of selector element for collections 16 | resourceSize : integer, max height of selector element for resources 17 | onchange : action to occur when resource is selected 18 | }); 19 | 20 | podSelector({ 21 | parts : [ {label,dataSource}, ... ], 22 | contentArea : display target of resource-picker 23 | displayArea : display target of resource 24 | collectionSize : integer, max height of selector element for collections 25 | resourceSize : integer, max height of selector element for resources 26 | onchange : action to occur when resource is selected 27 | }); 28 | 29 | */ 30 | export async function podSelector(component){ 31 | let pods = []; 32 | for(let pod of component.parts){ 33 | pods.push({value:pod.dataSource,label:pod.label}); 34 | } 35 | let podsOnchange = async (selectorElement)=>{ 36 | let newHost = selectorElement.target.value; 37 | component.dataSource = newHost; 38 | return await resourceSelector(component); 39 | }; 40 | pods = await selector(pods,podsOnchange,null,null,6,component); 41 | // pods.style.height="2rem"; 42 | let container = pods[0] ?pods[0].value :pods; 43 | 44 | let hostEl = document.createElement('DIV'); 45 | hostEl.style = "width:100% !important;text-align:right"; 46 | hostEl.innerHTML = ` 47 | Pod Explorer 48 | 49 | `; 50 | let targetArea = typeof component.contentArea==="string" ?document.querySelector(component.contentArea) :component.contentArea; 51 | targetArea.innerHTML=""; 52 | targetArea.appendChild(hostEl); 53 | targetArea.appendChild(pods); 54 | component.dataSource = pods[0] ?pods[0].value :pods; 55 | let resources = await resourceSelector(component); 56 | } 57 | 58 | export async function resourceSelector(json){ 59 | let url = json.dataSource || json.podLink; 60 | let targetSelector = json.contentArea; 61 | let collectionSize = json.collectionSize || 6; 62 | let resourceSize = json.resourceSize || 10; 63 | let resourceOnchange = json.onchange; 64 | if(typeof solidUI !="undefined") { 65 | resourceOnchange ||= async (label,link,cType)=>{ 66 | await solidUI.showPage(null,null,{label,link,cType}) 67 | }; 68 | } 69 | const ldp = UI.rdf.Namespace("http://www.w3.org/ns/ldp#"); 70 | if(!url) return ""; 71 | let container = url.replace(/\/[^\/]*$/,'/'); // in case we get passed a resource 72 | let host = _host(container); 73 | const base = UI.rdf.sym(container); 74 | try { 75 | await UI.store.fetcher.load(base); 76 | } 77 | catch(e) { 78 | alert(e) 79 | } 80 | let files = UI.store.each(base,ldp("contains"),null,base); 81 | let resources = []; 82 | let containers=[]; 83 | containers.push({value:base.uri,label:_pathname(base.uri)}); 84 | for(let file of files.sort()){ 85 | let name = file.value 86 | let contentType=_findContentType(file); 87 | if( _isHidden(name) ) continue; 88 | if(name.endsWith('/')) containers.push({value:name,label:_pathname(name),contentType:'text/turtle'}); 89 | else resources.push({value:name,label:_pathname(name),contentType}); 90 | } 91 | let parent = base.uri.replace(/\/$/,'').replace(/\/[^\/]*$/,'/'); 92 | if(parent) { 93 | if(!parent.endsWith("//")) { 94 | containers.splice(1,0,{value:parent,label:"../"}); 95 | } 96 | } 97 | let x = ()=>{}; 98 | let containerOnchange = async (selectorElement,link)=>{ 99 | let newContainer = link ?link :selectorElement.target.value; 100 | json.dataSource = newContainer; 101 | /* 102 | let thisContainerArea = document.querySelector('#PodSelector .containerSelector'); 103 | */ 104 | let newC = await resourceSelector(json); 105 | /* 106 | thisContainerArea.replaceWith(newC); 107 | return thisContainerArea; 108 | */ 109 | let areaToWriteIn = document.querySelector('#PodSelector'); 110 | if(areaToWriteIn){ 111 | areaToWriteIn.innerHTML = ""; 112 | areaToWriteIn.appendChild(newC); 113 | } 114 | return newC; 115 | }; 116 | containers = await selector(containers,containerOnchange,url,null,collectionSize,json) 117 | resources = await selector(resources,resourceOnchange,url,null,resourceSize,json) 118 | containers.classList = ["containerSelector"]; 119 | if(resources) resources.classList = ["resourceSelector"]; 120 | if(targetSelector && typeof targetSelector==="string") targetSelector = document.querySelector(targetSelector); 121 | if(!targetSelector){ 122 | let myid = json.label.replace(/\s/g,"_"); 123 | targetSelector = document.getElementById(myid); 124 | } 125 | // if(targetSelector) div = targetSelector.querySelector("#PodSelector"); 126 | let div=document.createElement('DIV'); 127 | let hostEl = document.createElement('DIV'); 128 | hostEl.style = "width:100% !important;text-align:right"; 129 | hostEl.innerHTML = ` 130 | Pod Explorer 131 | 132 | `; 133 | div.innerHTML = ""; 134 | div.appendChild(hostEl); 135 | div.id = "PodSelector" 136 | if(containers) div.appendChild(containers); 137 | if(resources) div.appendChild(resources); 138 | /* 139 | let anchor = targetSelector.querySelector('A') 140 | if(anchor)anchor.addEventListener('click',(e)=>{ 141 | e.preventDefault(); 142 | solidUI.showPage(e,{link:container,displayArea:json.displayArea}); 143 | }); 144 | */ 145 | return div; 146 | } 147 | 148 | function _findContentType(fileNode){ 149 | const isa = UI.rdf.sym("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); 150 | let types = UI.store.each(fileNode,isa).map((s)=>{return s.value}); 151 | for(let type of types){ 152 | if(type.match(/http:\/\/www.w3.org\/ns\/iana\/media-types/)) 153 | return type.replace(/http:\/\/www.w3.org\/ns\/iana\/media-types\//,'').replace(/\#Resource/,''); 154 | } 155 | } 156 | 157 | function _isHidden(path){ 158 | if(!path) return true; 159 | let name = new URL(path); 160 | name = name.pathname; 161 | name= name.endsWith('/') ?name.replace(/^\//,'') :name.replace(/.*\//,''); 162 | if(name.startsWith('.') ) return true; 163 | if(name.match(/\/\./) ) return true; 164 | if(name.endsWith('~') ) return true; 165 | } 166 | function _pathname(path){ 167 | try{ 168 | let p = new URL(path); 169 | return p.pathname 170 | } 171 | catch(e){console.log(path,e); return path }; 172 | } 173 | function _host(path){ 174 | try{ 175 | let p = new URL(path); 176 | return p.host 177 | } 178 | catch(e){console.log(path,e); return path }; 179 | } 180 | function _origin(path){ 181 | try{ 182 | let p = new URL(path); 183 | return p.origin 184 | } 185 | catch(e){console.log(path,e); return path }; 186 | } 187 | 188 | 189 | /* 190 | @paramoptions : an array of arrays [ [value1,label1], [value2,label2] ] 191 | onchange : callback function when item selected 192 | selected : value to select (from options) 193 | targetSelector : an optional css selector e.g. "#foo .bar" where output should be placed 194 | size : optional integer size of selector 195 | returns an HTML select element 196 | */ 197 | export function selector(options,onchange,selected,targetSelector,size,o){ 198 | 199 | function mungeLabel(label){ 200 | if(!label) return ""; 201 | label = decodeURI(label); 202 | if(!label.endsWith("/")) label = label.replace(/.*\//,'') 203 | return label || "/"; 204 | } 205 | //... 206 | for(let o of options){ 207 | o.label = mungeLabel(o.label); 208 | } 209 | return optionSelector({ 210 | parts : options, 211 | onchange, 212 | size, 213 | }); 214 | //... 215 | 216 | function addAttributes(option,optionEl){ 217 | if(Object.keys(option).length>2){ 218 | for(let k of Object.keys(option)){ 219 | if(k==="value"||k==="label")continue; 220 | optionEl.dataset ||= {}; 221 | optionEl.dataset[k] = option[k]; 222 | } 223 | } 224 | return optionEl; 225 | } 226 | 227 | let computedSize = options.length; 228 | size ||= 0; 229 | size = computedSize <= size ?computedSize :size; 230 | size ||=1; 231 | /* 232 | if(size ===1) { 233 | let button = document.createElement('BUTTON'); 234 | button.value = options[0].value; 235 | button.innerHTML = mungeLabel(options[0].label); 236 | button.addEventListener('click',(e)=>{ 237 | onchange(e) 238 | // onchange(e.target) 239 | }) 240 | return button; 241 | } 242 | */ 243 | let selectEl = document.createElement('SELECT'); 244 | let optgroupEl = document.createElement('OPTGROUP'); 245 | optgroupEl.style.margin=0; 246 | optgroupEl.style.padding=0; 247 | // selectEl.appendChild(optgroupEl); 248 | selectEl.style.background = o.darkBackground; 249 | selectEl.style["margin-top"] = "1rem"; 250 | for(let option of options){ 251 | let value,label; 252 | if(typeof option==="string") value = label = option; 253 | else { 254 | value = option.value 255 | label = option.label || option.value 256 | } 257 | if(!label || !option) continue; 258 | label=mungeLabel(label); 259 | // label = decodeURI(label); 260 | // if(!label.endsWith("/")) 261 | // label = label.replace(/.*\//,'') 262 | // label ||= "/"; 263 | let optionEl = document.createElement('OPTION'); 264 | optionEl.style["font-size"]="large"; 265 | // optionEl.style = "padding:0.25em;"; 266 | optionEl.style.background = o.background || solidUI.buttonBackground; 267 | optionEl.style.color = o.color || solidUI.buttonColor; 268 | optionEl.value = value; 269 | optionEl.title = value+"\n"+(option.contentType||""); 270 | optionEl.dataset ||= {}; 271 | optionEl.dataset.contenttype=option.contentType||""; 272 | optionEl.innerHTML = label; 273 | optionEl = addAttributes(option,optionEl); 274 | /* 275 | if(Object.keys(option).length>2){ 276 | for(let k of Object.keys(option)){ 277 | if(k==="value"||k==="label")continue; 278 | optionEl.dataset ||= {}; 279 | optionEl.dataset[k] = option[k]; 280 | } 281 | } 282 | */ 283 | //optgroupEl.appendChild(optionEl); 284 | selectEl.appendChild(optionEl); 285 | } 286 | 287 | /* HIGHLIGHT SELECTED */ 288 | if(selected) selectEl.value = selected; 289 | // selectEl.value ||= selectEl.childNodes[0].value; 290 | 291 | // selectEl.addEventListener('click',async(e)=>{ 292 | // onchange(e.target) 293 | // }) 294 | selectEl.addEventListener('change',async(e)=>{ 295 | onchange(e) 296 | // onchange(e.target) 297 | }) 298 | selectEl.size = size 299 | selectEl.style.width="100%"; 300 | if(targetSelector) { 301 | let targetEl = document.querySelector(targetSelector) 302 | targetEl.innerHTML = "" 303 | targetEl.appendChild(selectEl); 304 | } 305 | return selectEl; 306 | } 307 | 308 | -------------------------------------------------------------------------------- /src/view/selector.new.js: -------------------------------------------------------------------------------- 1 | /* 2 | containerSelector 3 | url 4 | targetSelector 5 | resourceOnChange 6 | collectionSize 7 | resourceSize 8 | 9 | podSelector({ 10 | parts : [ {label,dataSource}, ... ], 11 | contentArea : display target of resource-picker 12 | displayArea : display target of resource 13 | collectionSize : integer, max height of selector element for collections 14 | resourceSize : integer, max height of selector element for resources 15 | onchange : action to occur when resource is selected 16 | }); 17 | 18 | podSelector({ 19 | parts : [ {label,dataSource}, ... ], 20 | contentArea : display target of resource-picker 21 | displayArea : display target of resource 22 | collectionSize : integer, max height of selector element for collections 23 | resourceSize : integer, max height of selector element for resources 24 | onchange : action to occur when resource is selected 25 | }); 26 | 27 | */ 28 | export async function podSelector(component){ 29 | let pods = []; 30 | for(let pod of component.parts){ 31 | pods.push({value:pod.dataSource,label:pod.label}); 32 | } 33 | let podsOnchange = async (selectorElement)=>{ 34 | let newHost = selectorElement.target.value; 35 | component.dataSource = newHost; 36 | return await resourceSelector(component); 37 | }; 38 | pods = await selector(pods,podsOnchange,null,null,6,component); 39 | // pods.style.height="2rem"; 40 | let container = pods[0].value; 41 | /* 42 | let hostEl = document.createElement('DIV'); 43 | hostEl.style = "width:100% !important;text-align:right"; 44 | hostEl.innerHTML = ` 45 | Pod Explorer 46 | 47 | `; 48 | */ 49 | let targetArea = typeof component.contentArea==="string" ?document.querySelector(component.contentArea) :component.contentArea; 50 | console.log(78,targetArea); 51 | targetArea.innerHTML=""; 52 | // targetArea.appendChild(hostEl); 53 | targetArea.appendChild(pods); 54 | component.dataSource = pods[0].value; 55 | let resources = await resourceSelector(component); 56 | } 57 | 58 | export async function resourceSelector(json){ 59 | let url = json.dataSource || json.podLink; 60 | let targetSelector = json.contentArea; 61 | let collectionSize = json.collectionSize || 6; 62 | let resourceSize = json.resourceSize || 10; 63 | let resourceOnchange = json.onchange; 64 | if(typeof solidUI !="undefined") resourceOnchange ||= async (e)=>{await solidUI.showPage(e,json)}; 65 | const ldp = UI.rdf.Namespace("http://www.w3.org/ns/ldp#"); 66 | if(!url) return ""; 67 | let container = url.replace(/\/[^\/]*$/,'/'); // in case we get passed a resource 68 | let host = _host(container); 69 | const base = UI.rdf.sym(container); 70 | await UI.store.fetcher.load(base); 71 | let files = UI.store.each(base,ldp("contains"),null,base); 72 | let resources = []; 73 | let containers=[]; 74 | containers.push({value:base.uri,label:_pathname(base.uri)}); 75 | for(let file of files.sort()){ 76 | let name = file.value 77 | let contentType=_findContentType(file); 78 | if( _isHidden(name) ) continue; 79 | if(name.endsWith('/')) containers.push({value:name,label:_pathname(name),contentType}); 80 | else resources.push({value:name,label:_pathname(name),contentType}); 81 | } 82 | let parent = base.uri.replace(/\/$/,'').replace(/\/[^\/]*$/,'/'); 83 | if(parent) { 84 | if(!parent.endsWith("//")) { 85 | containers.splice(1,0,{value:parent,label:"../"}); 86 | } 87 | } 88 | let x = ()=>{}; 89 | let containerOnchange = async (selectorElement)=>{ 90 | let newContainer = selectorElement.target.value; 91 | json.dataSource = newContainer; 92 | let thisContainerArea = document.querySelector('#PodSelector .containerSelector'); 93 | let newC = await resourceSelector(json); 94 | thisContainerArea.replaceWith(newC); 95 | console.log(88,newC) 96 | return thisContainerArea; 97 | }; 98 | containers = await selector(containers,containerOnchange,url,null,collectionSize,json) 99 | resources = await selector(resources,resourceOnchange,url,null,resourceSize,json) 100 | containers.classList = ["containerSelector"]; 101 | if(resources) resources.classList = ["resourceSelector"]; 102 | if(targetSelector && typeof targetSelector==="string") targetSelector = document.querySelector(targetSelector); 103 | if(!targetSelector){ 104 | let myid = json.label.replace(/\s/g,"_"); 105 | targetSelector = document.getElementById(myid); 106 | } 107 | // if(targetSelector) div = targetSelector.querySelector("#PodSelector"); 108 | let div=document.createElement('DIV'); 109 | div.innerHTML = ""; 110 | div.id = "PodSelector" 111 | if(containers) div.appendChild(containers); 112 | if(resources) div.appendChild(resources); 113 | /* 114 | let anchor = targetSelector.querySelector('A') 115 | if(anchor)anchor.addEventListener('click',(e)=>{ 116 | e.preventDefault(); 117 | solidUI.showPage(e,{link:container,displayArea:json.displayArea}); 118 | }); 119 | */ 120 | return div; 121 | } 122 | 123 | function _findContentType(fileNode){ 124 | const isa = UI.rdf.sym("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); 125 | let types = UI.store.each(fileNode,isa).map((s)=>{return s.value}); 126 | for(let type of types){ 127 | if(type.match(/http:\/\/www.w3.org\/ns\/iana\/media-types/)) 128 | return type.replace(/http:\/\/www.w3.org\/ns\/iana\/media-types\//,'').replace(/\#Resource/,''); 129 | } 130 | } 131 | 132 | function _isHidden(path){ 133 | if(!path) return true; 134 | let name = new URL(path); 135 | name = name.pathname; 136 | name= name.endsWith('/') ?name.replace(/^\//,'') :name.replace(/.*\//,''); 137 | if(name.startsWith('.') ) return true; 138 | if(name.endsWith('~') ) return true; 139 | } 140 | function _pathname(path){ 141 | try{ 142 | let p = new URL(path); 143 | return p.pathname 144 | } 145 | catch(e){console.log(path,e); return path }; 146 | } 147 | function _host(path){ 148 | try{ 149 | let p = new URL(path); 150 | return p.host 151 | } 152 | catch(e){console.log(path,e); return path }; 153 | } 154 | function _origin(path){ 155 | try{ 156 | let p = new URL(path); 157 | return p.origin 158 | } 159 | catch(e){console.log(path,e); return path }; 160 | } 161 | 162 | 163 | /* 164 | @paramoptions : an array of arrays [ [value1,label1], [value2,label2] ] 165 | onchange : callback function when item selected 166 | selected : value to select (from options) 167 | targetSelector : an optional css selector e.g. "#foo .bar" where output should be placed 168 | size : optional integer size of selector 169 | returns an HTML select element 170 | */ 171 | export function selector(options,onchange,selected,targetSelector,size,o){ 172 | 173 | function mungeLabel(label){ 174 | if(!label) return ""; 175 | label = decodeURI(label); 176 | if(!label.endsWith("/")) label = label.replace(/.*\//,'') 177 | return label || "/"; 178 | } 179 | function addAttributes(option,optionEl){ 180 | if(Object.keys(option).length>2){ 181 | for(let k of Object.keys(option)){ 182 | if(k==="value"||k==="label")continue; 183 | optionEl.dataset ||= {}; 184 | optionEl.dataset[k] = option[k]; 185 | } 186 | } 187 | return optionEl; 188 | } 189 | 190 | let computedSize = options.length; 191 | size ||= 0; 192 | size = computedSize <= size ?computedSize :size; 193 | size ||=1; 194 | /* 195 | if(size ===1) { 196 | let button = document.createElement('BUTTON'); 197 | button.value = options[0].value; 198 | button.innerHTML = mungeLabel(options[0].label); 199 | button.addEventListener('click',(e)=>{ 200 | onchange(e) 201 | // onchange(e.target) 202 | }) 203 | return button; 204 | } 205 | */ 206 | let selectEl = document.createElement('SELECT'); 207 | let optgroupEl = document.createElement('OPTGROUP'); 208 | optgroupEl.style.margin=0; 209 | optgroupEl.style.padding=0; 210 | // selectEl.appendChild(optgroupEl); 211 | selectEl.style.background = o.darkBackground; 212 | selectEl.style["margin-top"] = "1rem"; 213 | for(let option of options){ 214 | let value,label; 215 | if(typeof option==="string") value = label = option; 216 | else { 217 | value = option.value 218 | label = option.label || option.value 219 | } 220 | if(!label || !option) continue; 221 | label=mungeLabel(label); 222 | // label = decodeURI(label); 223 | // if(!label.endsWith("/")) 224 | // label = label.replace(/.*\//,'') 225 | // label ||= "/"; 226 | let optionEl = document.createElement('OPTION'); 227 | optionEl.style["font-size"]="18px"; 228 | // optionEl.style = "padding:0.25em;"; 229 | optionEl.style.background = o.lightBackground; 230 | optionEl.style.color = o.lightBackground; 231 | optionEl.value = value; 232 | optionEl.title = value+"\n"+(option.contentType||""); 233 | optionEl.dataset ||= {}; 234 | optionEl.dataset.contenttype=option.contentType||""; 235 | optionEl.innerHTML = label; 236 | optionEl = addAttributes(option,optionEl); 237 | /* 238 | if(Object.keys(option).length>2){ 239 | for(let k of Object.keys(option)){ 240 | if(k==="value"||k==="label")continue; 241 | optionEl.dataset ||= {}; 242 | optionEl.dataset[k] = option[k]; 243 | } 244 | } 245 | */ 246 | //optgroupEl.appendChild(optionEl); 247 | selectEl.appendChild(optionEl); 248 | } 249 | 250 | /* HIGHLIGHT SELECTED */ 251 | if(selected) selectEl.value = selected; 252 | // selectEl.value ||= selectEl.childNodes[0].value; 253 | 254 | // selectEl.addEventListener('click',async(e)=>{ 255 | // onchange(e.target) 256 | // }) 257 | selectEl.addEventListener('change',async(e)=>{ 258 | onchange(e) 259 | // onchange(e.target) 260 | }) 261 | selectEl.size = size 262 | selectEl.style.width="100%"; 263 | if(targetSelector) { 264 | let targetEl = document.querySelector(targetSelector) 265 | targetEl.innerHTML = "" 266 | targetEl.appendChild(selectEl); 267 | } 268 | return selectEl; 269 | } 270 | 271 | -------------------------------------------------------------------------------- /src/view/selector.org.js: -------------------------------------------------------------------------------- 1 | /* containerSelector 2 | url 3 | targetSelector 4 | resourceOnChange 5 | collectionSize 6 | resourceSize 7 | */ 8 | export async function containerSelector(json){ 9 | let url = json.dataSource; 10 | let targetSelector = json.contentArea; 11 | let collectionSize = json.collectionSize || 6; 12 | let resourceSize = json.resourceSize || 12; 13 | let resourceOnchange = async (e)=>{await solidUI.showPage(e,json)}; 14 | const ldp = UI.rdf.Namespace("http://www.w3.org/ns/ldp#"); 15 | if(!url) return ""; 16 | let container = url.replace(/\/[^\/]*$/,'/'); // in case we get passed a resource 17 | let host = _host(container); 18 | const base = UI.rdf.sym(container); 19 | await UI.store.fetcher.load(base); 20 | let files = UI.store.each(base,ldp("contains"),null,base); 21 | let resources = []; 22 | let containers=[]; 23 | containers.push({value:base.uri,label:_pathname(base.uri)}); 24 | for(let file of files.sort()){ 25 | let name = file.value 26 | let contentType=_findContentType(file); 27 | if( _isHidden(name) ) continue; 28 | if(name.endsWith('/')) containers.push({value:name,label:_pathname(name),contentType}); 29 | else resources.push({value:name,label:_pathname(name),contentType}); 30 | } 31 | let parent = base.uri.replace(/\/$/,'').replace(/\/[^\/]*$/,'/'); 32 | if(parent) { 33 | if(!parent.endsWith("//")) { 34 | containers.splice(1,0,{value:parent,label:"../"}); 35 | } 36 | } 37 | let x = ()=>{}; 38 | let containerOnchange = async (selectorElement)=>{ 39 | let newContainer = selectorElement.target.value; 40 | json.dataSource = newContainer; 41 | return await containerSelector(json); 42 | }; 43 | containers = await selector(containers,containerOnchange,url,null,collectionSize) 44 | resources = await selector(resources,resourceOnchange,url,null,resourceSize) 45 | containers.classList = ["containerSelector"]; 46 | if(resources) resources.classList = ["resourceSelector"]; 47 | if(targetSelector && typeof targetSelector==="string") targetSelector = document.querySelector(targetSelector); 48 | let div = targetSelector ?targetSelector :document.createElement('DIV'); 49 | let hostEl = document.createElement('DIV'); 50 | hostEl.style = "width:100% !important;text-align:right"; 51 | hostEl.innerHTML = ` 52 | ${host} 53 | 54 | `; 55 | hostEl.querySelector('A').addEventListener('click',(e)=>{ 56 | //alert(e) 57 | e.preventDefault(); 58 | solidUI.showPage(e,{link:container,displayArea:json.displayArea}); 59 | }); 60 | div.innerHTML = ""; 61 | if(hostEl) div.appendChild(hostEl); 62 | if(containers) div.appendChild(containers); 63 | if(resources) div.appendChild(resources); 64 | return div; 65 | } 66 | 67 | function _findContentType(fileNode){ 68 | const isa = UI.rdf.sym("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); 69 | let types = UI.store.each(fileNode,isa).map((s)=>{return s.value}); 70 | for(let type of types){ 71 | if(type.match(/http:\/\/www.w3.org\/ns\/iana\/media-types/)) 72 | return type.replace(/http:\/\/www.w3.org\/ns\/iana\/media-types\//,'').replace(/\#Resource/,''); 73 | } 74 | } 75 | 76 | function _isHidden(path){ 77 | if(!path) return true; 78 | let name = new URL(path); 79 | name = name.pathname; 80 | name= name.endsWith('/') ?name.replace(/^\//,'') :name.replace(/.*\//,''); 81 | if(name.startsWith('.') ) return true; 82 | if(name.endsWith('~') ) return true; 83 | } 84 | function _pathname(path){ 85 | try{ 86 | let p = new URL(path); 87 | return p.pathname 88 | } 89 | catch(e){console.log(path,e); return path }; 90 | } 91 | function _host(path){ 92 | try{ 93 | let p = new URL(path); 94 | return p.host 95 | } 96 | catch(e){console.log(path,e); return path }; 97 | } 98 | function _origin(path){ 99 | try{ 100 | let p = new URL(path); 101 | return p.origin 102 | } 103 | catch(e){console.log(path,e); return path }; 104 | } 105 | 106 | 107 | /* 108 | @paramoptions : an array of arrays [ [value1,label1], [value2,label2] ] 109 | onchange : callback function when item selected 110 | selected : value to select (from options) 111 | targetSelector : an optional css selector e.g. "#foo .bar" where output should be placed 112 | size : optional integer size of selector 113 | returns an HTML select element 114 | */ 115 | export function selector(options,onchange,selected,targetSelector,size){ 116 | 117 | function mungeLabel(label){ 118 | if(!label) return ""; 119 | label = decodeURI(label); 120 | if(!label.endsWith("/")) label = label.replace(/.*\//,'') 121 | return label || "/"; 122 | } 123 | function addAttributes(option,optionEl){ 124 | if(Object.keys(option).length>2){ 125 | for(let k of Object.keys(option)){ 126 | if(k==="value"||k==="label")continue; 127 | optionEl.dataset ||= {}; 128 | optionEl.dataset[k] = option[k]; 129 | } 130 | } 131 | return optionEl; 132 | } 133 | 134 | 135 | 136 | let computedSize = options.length; 137 | size ||= 0; 138 | size = computedSize <= size ?computedSize :size; 139 | if(size <1) return; 140 | if(size ===1) { 141 | let button = document.createElement('BUTTON'); 142 | button.value = options[0].value; 143 | button.innerHTML = mungeLabel(options[0].label); 144 | button.addEventListener('click',(e)=>{ 145 | onchange(e) 146 | // onchange(e.target) 147 | }) 148 | return button; 149 | } 150 | let selectEl = document.createElement('SELECT'); 151 | for(let option of options){ 152 | let value,label; 153 | if(typeof option==="string") value = label = option; 154 | else { 155 | value = option.value 156 | label = option.label || option.value 157 | } 158 | if(!label || !option) continue; 159 | label=mungeLabel(label); 160 | // label = decodeURI(label); 161 | // if(!label.endsWith("/")) 162 | // label = label.replace(/.*\//,'') 163 | // label ||= "/"; 164 | let optionEl = document.createElement('OPTION'); 165 | optionEl.value = value; 166 | optionEl.title = value+"\n"+(option.contentType||""); 167 | optionEl.dataset ||= {}; 168 | optionEl.dataset.contenttype=option.contentType||""; 169 | optionEl.innerHTML = label; 170 | optionEl.style = "padding:0.25em;"; 171 | optionEl = addAttributes(option,optionEl); 172 | /* 173 | if(Object.keys(option).length>2){ 174 | for(let k of Object.keys(option)){ 175 | if(k==="value"||k==="label")continue; 176 | optionEl.dataset ||= {}; 177 | optionEl.dataset[k] = option[k]; 178 | } 179 | } 180 | */ 181 | selectEl.appendChild(optionEl); 182 | } 183 | 184 | /* HIGHLIGHT SELECTED */ 185 | if(selected) selectEl.value = selected; 186 | // selectEl.value ||= selectEl.childNodes[0].value; 187 | 188 | // selectEl.addEventListener('click',async(e)=>{ 189 | // onchange(e.target) 190 | // }) 191 | selectEl.addEventListener('change',async(e)=>{ 192 | onchange(e) 193 | // onchange(e.target) 194 | }) 195 | selectEl.size = size 196 | selectEl.style="padding:0.5em;width:100%" 197 | if(targetSelector) { 198 | let targetEl = document.querySelector(targetSelector) 199 | targetEl.innerHTML = "" 200 | targetEl.appendChild(selectEl); 201 | } 202 | return selectEl; 203 | } 204 | 205 | -------------------------------------------------------------------------------- /src/view/selectorPanel.js: -------------------------------------------------------------------------------- 1 | /* 2 | color // a ui:Color for text of the selector 3 | background // a ui:Color for the background of the selector 4 | highlight // a ui:Color for the selector options background 5 | size // the number of options to show 6 | parts // an array of options 7 | dataSource // a component address that yields an array of options 8 | onchange // action to take on selection 9 | */ 10 | export function optionSelector(j) { 11 | const hasSuic = typeof solidUI !="undefined"; 12 | j.color ||= hasSuic ?solidUI.menuColor :"#000000"; 13 | j.background ||= hasSuic ?solidUI.menuBackground : "#c0c0c0"; 14 | j.highlight ||= hasSuic ?solidUI.menuHighlight : "#909090"; 15 | j.size ||= hasSuic ?solidUI.selectorSize :0; 16 | const borderColor = j.color; 17 | const wrapper = document.createElement('DIV'); 18 | let collectionSelector = document.createElement('DIV'); 19 | collectionSelector.style = `border:1px solid ${borderColor};border-bottom:none`; 20 | collectionSelector.style.background=j.background; 21 | collectionSelector.style.color= j.color; 22 | j.parts ||= j.dataSource.parts; 23 | const firstOption = j.label || (j.parts[0]).label || j.parts[0]; 24 | collectionSelector.innerHTML = `
    ${firstOption}
    `; 25 | let collectionsList = document.createElement('DIV'); 26 | collectionSelector.querySelector('div').onclick=()=>{ 27 | collectionsList.style.display||="block"; 28 | if(collectionsList.style.display==="block") collectionsList.style.display="none"; 29 | else collectionsList.style.display="block"; 30 | } 31 | for(let p of j.parts){ 32 | collectionsList.innerHTML += `${p.label}`; 33 | } 34 | let anchorList = collectionsList.querySelectorAll('A'); 35 | for(let anchor of anchorList){ 36 | anchor.onclick = (e)=>{ 37 | e.preventDefault(); 38 | let el = e.target; 39 | for(let a of anchorList){ 40 | if(a==el) a.style.background=j.background; 41 | else a.style.background=j.highlight; 42 | } 43 | if(!j.size) { 44 | el.parentElement.style.display="none"; 45 | collectionSelector.querySelector('DIV SPAN').innerHTML=el.innerHTML; 46 | } 47 | console.log(99,el.href,el.dataset.type,el.dataset.link) 48 | return j.onchange(el.innerHTML,el.href,el.dataset.type); 49 | } 50 | } 51 | if(j.size){ 52 | if(j.size>j.parts.length) j.size=j.parts.length; 53 | j.size = (j.size*2)+1+"rem"; 54 | collectionsList.style = `border:1px solid ${borderColor};border-top:none;height:${j.size}`; 55 | collectionsList.style.overflow="auto"; 56 | collectionsList.style["max-height"]=j.size; 57 | collectionsList.style["border-radius"]="0.4rem"; 58 | } 59 | else { 60 | collectionsList.style=`border:1px solid ${borderColor};border-top:none;display:none`; 61 | collectionSelector.style["border-radius"]="0.4rem"; 62 | wrapper.appendChild(collectionSelector); 63 | } 64 | wrapper.appendChild(collectionsList); 65 | return wrapper; 66 | } 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/view/slideshow.js: -------------------------------------------------------------------------------- 1 | class Slideshow { 2 | render(component,results) { 3 | const height = component.height || "320px"; 4 | const width = component.width || "480px"; 5 | const slideshow = solidUI.createElement('DIV','','',`height:${height};width:${width};text-align:center`); 6 | let i = 0; 7 | for(let row of results){ 8 | let slide = solidUI.createElement('DIV','mySlides',''); 9 | let img = solidUI.createElement('IMG','','',`height:${height};max-width:${width}`) 10 | const label = solidUI.createElement('DIV','',row.label) 11 | img.src = row.image; 12 | if(i) slide.style.display="none"; 13 | i++; 14 | slide.appendChild(img); 15 | slide.appendChild(label); 16 | slideshow.appendChild(slide); 17 | } 18 | const prev = solidUI.createElement('A','','❮',"cursor:pointer"); 19 | const next = solidUI.createElement('A','','❯',"cursor:pointer"); 20 | const play = solidUI.createElement('A','','\u23f5',"font-size:2rem;font-weight:bold;cursor:pointer;"); 21 | const pause = solidUI.createElement('A','','\u23f8','font-size:2rem;font-weight:bold;cursor:pointer;display:none;'); 22 | // prev.onclick=(event)=>{changeSlide(event,-1)}; 23 | // next.onclick=(event)=>{changeSlide(event,1)}; 24 | play.onclick=(event)=>{playSlides(event,pause,play)}; 25 | play.title="play slides"; 26 | pause.title="pause slides"; 27 | pause.onclick=(event)=>{pauseSlides(event,pause,play)}; 28 | // slideshow.appendChild(prev); 29 | slideshow.appendChild(play); 30 | slideshow.appendChild(pause); 31 | // slideshow.appendChild(next); 32 | return slideshow 33 | } 34 | } // end class Slideshow 35 | 36 | var slideIndex = 1; 37 | var paused = false; 38 | 39 | function changeSlide(event,n) { 40 | paused = true; 41 | showSlidesManual(event,slideIndex += n); 42 | } 43 | 44 | function showSlidesManual(event,n) { 45 | var i; 46 | var slides = document.querySelectorAll(".mySlides"); 47 | if (n > slides.length) {slideIndex = 1} 48 | if (n < 1) {slideIndex = slides.length} 49 | for (i = 0; i < slides.length; i++) { 50 | slides[i].style.display = "none"; 51 | } 52 | slides[slideIndex-1].style.display = "block"; 53 | } 54 | function pauseSlides(event,pause,play) { 55 | paused=true; 56 | play.style.display="block"; 57 | pause.style.display="none"; 58 | } 59 | function playSlides(event,pause,play){ 60 | play.style.display="none"; 61 | pause.style.display="block"; 62 | paused = false; 63 | showSlides(); 64 | } 65 | 66 | function showSlides() { 67 | var i; 68 | var slides = document.getElementsByClassName("mySlides"); 69 | for (i = 0; i < slides.length; i++) { 70 | slides[i].style.display = "none"; 71 | } 72 | slideIndex++; 73 | if (slideIndex > slides.length) {slideIndex = 1} 74 | slides[slideIndex-1].style.display = "block"; 75 | if(!paused) 76 | setTimeout(showSlides, 2000); // Change image every 2 seconds 77 | } -------------------------------------------------------------------------------- /src/view/solidOSpage.js: -------------------------------------------------------------------------------- 1 | export class solidOSpage { 2 | render(json){ 3 | let tabulator = document.getElementById('suicTabulator') 4 | if(!window.outliner) { 5 | tabulator.style= ` 6 | display: none; /* Hidden by default */ 7 | position: fixed; /* Stay in place */ 8 | z-index: 1; /* Sit on top */ 9 | left: 0; 10 | top: 10vh; 11 | height: 90vh; /* Full height */ 12 | width: 100%; /* Full width */ 13 | overflow: auto; /* Enable scroll if needed */ 14 | background-color: rgb(0,0,0); /* Fallback color */ 15 | background-color: rgba(0,0,0,0.2); /* Black w/ opacity */ 16 | ` 17 | window.outliner = panes.getOutliner(document); 18 | } 19 | window.outliner.GotoSubject(window.kb.sym(url),true,undefined,true); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/view/table.js: -------------------------------------------------------------------------------- 1 | export class Table { 2 | render(options) { 3 | let results = options.parts; 4 | if(!results || !results.length) { 5 | console.log("No results!"); 6 | return document.createElement('SPAN'); 7 | } 8 | if(options.sortOn) { 9 | // results.sort((a,b)=> a[options.sortOn] - b[options.sortOn] ) 10 | results = results.reverse(); 11 | } 12 | let table = document.createElement('TABLE'); 13 | let columnHeaders = Object.keys(results[0]); 14 | let headerRow = document.createElement('TR'); 15 | for(let c of columnHeaders){ 16 | let cell = document.createElement('TH'); 17 | cell.innerHTML = c; 18 | cell.style.border="1px solid black"; 19 | cell.style.padding="0.5em"; 20 | cell.style.background=options.unselBackground; 21 | cell.style.color=options.unselColor; 22 | headerRow.appendChild(cell); 23 | } 24 | table.appendChild(headerRow); 25 | for(let row of results){ 26 | let rowElement = document.createElement('TR'); 27 | for(let col of columnHeaders){ 28 | if(options.cleanNodes){ 29 | row[col] = row[col].replace(/.*\#/,'').replace(/.*\//,''); 30 | } 31 | let cell = document.createElement('TD'); 32 | cell.innerHTML = row[col]; 33 | cell.style.border="1px solid black"; 34 | cell.style.padding="0.5em"; 35 | cell.style.backgroundColor=options.background; 36 | cell.style.color=options.color; 37 | rowElement.appendChild(cell); 38 | } 39 | table.appendChild(rowElement); 40 | } 41 | table.style.borderCollapse="collapse"; 42 | return table; 43 | } 44 | } 45 | // ENDS 46 | -------------------------------------------------------------------------------- /src/view/tabs-old.js: -------------------------------------------------------------------------------- 1 | export class Tabs { 2 | async render(solidUI,json) { 3 | let parts = json.parts 4 | const tabs = solidUI.createElement('DIV','solid-uic-tabs'); 5 | if(json.style) tabs.style = json.style; 6 | const nav = solidUI.createElement('NAV'); 7 | if(json.orientation==="vertical") 8 | nav.style.textAlign = "right"; 9 | else 10 | nav.style.textAlign = json.position; 11 | let mainDisplay = solidUI.createElement( 'DIV' ); 12 | mainDisplay.style.backgroundColor = solidUI.pageBackground || json.background; 13 | mainDisplay.style.color = solidUI.pageColor || json.color; 14 | let r = 0; 15 | if(!parts.length) parts = [parts]; 16 | for(let row of parts){ 17 | r++; 18 | let rowhead = solidUI.createElement( 'BUTTON',r,row.label ); 19 | rowhead.style.backgroundColor = solidUI.menuBackgroundColor || json.background; 20 | rowhead.style.color = solidUI.menuColor || json.color; 21 | rowhead.style.padding="0.5em"; 22 | // rowhead.style.border="1px solid grey"; 23 | rowhead.style.border="none"; 24 | rowhead.style.cursor = "pointer"; 25 | solidUI.currentElement = json.contentArea; 26 | //JZ let rc = row.dataSource ? await solidUI.processComponentSubject(row.dataSource) : row.content; 27 | rowhead.setAttribute("name",row.dataSource); 28 | let rowContent = solidUI.createElement( 'DIV',r ); 29 | if(row.content) rowContent.appendChild(row.content); 30 | rowContent.style.padding="0.75em"; 31 | // if(json.orientation==="horizontal") rowContent.style.border="1px solid grey"; 32 | rowContent.style.display = "none"; 33 | if(row.type==="SolidOSLink"){ 34 | let id = `tab-${r}-outline`; 35 | rowContent.innerHTML = ` 36 |
    37 |
    38 |
    39 |
    40 | `; 41 | let subject = window.kb.sym(row.href); 42 | let targetArea = rowContent.querySelector('#'+id); 43 | targetArea.style="display:table-row;background-color:c0c0c0" 44 | window.outliner.GotoSubject(subject,true,undefined,true,undefined,targetArea); 45 | } 46 | rowhead.onclick = (e)=>{ 47 | e.preventDefault(); 48 | alert(44,e.target.getAttribute('name')); 49 | console.log(44,e.target); 50 | let items = tabs.querySelector('DIV').children; 51 | for(let i of items) { 52 | i.style.display="none"; 53 | } 54 | for(let n of nav.children) { 55 | n.style.backgroundColor = solidUI.menuBackground || json.background; 56 | n.style.color = solidUI.menuColor || json.color; 57 | } 58 | rowhead.style.backgroundColor = solidUI.menuColor || json.color; 59 | rowhead.style.color = solidUI.menuBackground || json.background; 60 | rowContent.style.display = "block"; 61 | } 62 | nav.appendChild(rowhead); 63 | mainDisplay.appendChild(rowContent); 64 | if(json.orientation==="vertical"){ 65 | rowhead.style.display="block"; 66 | rowhead.style.marginBottom = "0.5em"; 67 | rowhead.style.width="100%" 68 | tabs.style.width="100%"; 69 | tabs.style.height="100%"; 70 | tabs.style.display = "table-row"; 71 | nav.style.display = "table-cell"; 72 | mainDisplay.style.display = "table-cell"; 73 | mainDisplay.style.display="block" 74 | mainDisplay.style.height = "100%"; 75 | rowContent.style.height = "100%"; 76 | } 77 | else { 78 | rowhead.style.marginRight = "0.25em"; 79 | rowhead.style.borderBottom = "none"; 80 | } 81 | } 82 | tabs.appendChild(nav); 83 | const closeButton = document.createElement('SPAN'); 84 | closeButton.style = ` 85 | color:red; 86 | text-align:right; 87 | position:absolute; 88 | top:0.25rem; 89 | right:0.25rem; 90 | font-size: 2rem; 91 | font-weight: bold; 92 | cursor:pointer 93 | `; 94 | closeButton.addEventListener('click',(e)=>{ 95 | e.preventDefault(); 96 | e.target.parentElement.style.display="none"; 97 | }); 98 | closeButton.innerHTML = "×"; 99 | 100 | tabs.appendChild(closeButton); 101 | tabs.appendChild(mainDisplay); 102 | console.log(99,json); 103 | await solidUI.simulateClick(tabs.querySelector('BUTTON')) 104 | return tabs 105 | //JZ return await solidUI.initInternal(tabs); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/view/tabs.js: -------------------------------------------------------------------------------- 1 | export async function tabSet(options) { 2 | const items = options.parts.map(part=>part.dataSource); 3 | if(options.displayArea && options.startingContent){ 4 | await solidUI.processComponentSubject(options.startingContent); 5 | } 6 | options.renderTab ||= (div,source)=>{ 7 | const labels = (s)=>{ 8 | for(let p of options.parts){ 9 | if(p.dataSource===s)return p.label 10 | } 11 | } 12 | div.innerHTML = labels(source); 13 | } 14 | options.renderMain ||= async (div,source)=>{ 15 | // div = inner main 16 | div.setAttribute('data-suic-area','tab-display'); 17 | div.innerHTML = ""; 18 | if(options.style) div.style="options.style"; 19 | let c = await solidUI.processComponentSubject(source) ; 20 | div.appendChild( c ); 21 | } 22 | const tabOptions = { 23 | items, 24 | backgroundColor : options.backgroundColor || solidUI.menuBackground, 25 | orientation : options.orientation, 26 | dom : options.dom, 27 | onClose : options.onClose, 28 | ordered : options.ordered, 29 | selectedTab: options.selectedTab, 30 | startEmpty : options.startEmpty, 31 | renderTab : options.renderTab, 32 | renderMain : options.renderMain, 33 | renderTabSettings : options.renderTabSettings, 34 | } 35 | return UI.tabs.tabWidget(tabOptions); 36 | } 37 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // Configurations shared between all builds 4 | const common = { 5 | mode: 'production', 6 | entry: './src/index.js', 7 | output: { 8 | filename: 'solid-ui-components.bundle.js', 9 | library: 'solidUI', 10 | libraryExport: 'default', 11 | }, 12 | devtool: 'source-map', 13 | } 14 | 15 | // Configurations specific to the window build 16 | const window = { 17 | ...common, 18 | name: 'window', 19 | output: { 20 | ...common.output, 21 | path: path.resolve(__dirname, 'dist'), 22 | libraryTarget: 'umd', 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | loader: 'babel-loader', 28 | exclude: /node_modules/, 29 | }, 30 | ], 31 | }, 32 | } 33 | 34 | // Configurations specific to the node build 35 | /* 36 | const node = { 37 | ...common, 38 | name: 'node', 39 | output: { 40 | ...common.output, 41 | path: path.resolve(__dirname, 'dist', 'node'), 42 | libraryTarget: 'commonjs2', 43 | }, 44 | } 45 | */ 46 | const resolve = {resolve:{ fallback: { "path": false } }} 47 | module.exports = [ 48 | resolve, 49 | window 50 | ] 51 | --------------------------------------------------------------------------------