1
0

[mod] add card

This commit is contained in:
Andy Bunce 2024-04-03 23:24:13 +01:00
parent dcd18dd3d5
commit 2b131ee71a
9 changed files with 378 additions and 117 deletions

6
package-lock.json generated Normal file
View File

@ -0,0 +1,6 @@
{
"name": "pdfbox",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

29
src/webapp/pdf/api.xqm Normal file
View File

@ -0,0 +1,29 @@
(:~
pdf
:)
module namespace api = 'pdf/api';
import module namespace functx = "http://www.functx.com";
(:~ list slugs :)
declare
%rest:path('/pdf/api/sources')
%output:method("json")
%output:json("format=xquery")
function api:apt()
{
let $base:="C:/Users/mrwhe/git/expkg-zone58/pdfbox/data/"
let $d:="1e/"
let $f:=file:list($base || $d,true(),"*.pdf")[not(contains(.,"\outputs\"))]
return map{
"count": count($f),
"items": array{$f!api:path-info(.)}
}
};
declare function api:path-info($file as xs:string)
as map(*)
{
map{
"id": $file,
"slug":functx:substring-before-last($file,"\"),
"filename": file:name($file)
}
};

View File

@ -1,7 +1,5 @@
(:~
: Common RESTXQ access points.
:
: @author Christian Grün, BaseX Team 2005-23, BSD License
pdf
:)
module namespace pdf = 'pdf/common';
@ -21,21 +19,6 @@ declare
function pdf:spa($file)as element(){
pdf:home()
};
(:~ list slugs :)
declare
%rest:path('/pdf/api/sources')
%output:method("json")
%output:json("format=xquery")
function pdf:apt()
{
let $base:="C:/Users/mrwhe/git/expkg-zone58/pdfbox/data/"
let $d:="1e/"
let $f:=file:list($base || $d,true(),"*.pdf")[not(contains(.,"\outputs\"))]
return map{
"count": count($f),
"items": array{$f!map{"id":position(),"name":.}}
}
};
(:~
: Returns a file.
@ -58,30 +41,7 @@ function pdf:file(
),
file:read-binary($path)
)else
web:forward("/pdf/api/404")
()
};
(:~
: Shows a 'page not found' error.
: @param $path path to unknown page
: @return page
:)
declare
%rest:path('/pdf/api/404')
%output:method('html')
function pdf:unknown(
$path as xs:string
) as element(*) {
<tr>
<td>
<h2>Page not found:</h2>
<ul>
<li>Page: dba/{ $path }</li>
<li>Method: { request:method() }</li>
</ul>
</td>
</tr>
};

164
src/webapp/pdf/package-lock.json generated Normal file
View File

@ -0,0 +1,164 @@
{
"name": "pdf",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@shoelace-style/shoelace": "^2.15.0"
}
},
"node_modules/@ctrl/tinycolor": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.0.3.tgz",
"integrity": "sha512-e9nEVehVJwkymQpkGhdSNzLT2Lr9UTTby+JePq4Z2SxBbOQjY7pLgSouAaXvfaGQVSAaY0U4eJdwfSDmCbItcw==",
"engines": {
"node": ">=14"
}
},
"node_modules/@floating-ui/core": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
"integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==",
"dependencies": {
"@floating-ui/utils": "^0.2.1"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz",
"integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==",
"dependencies": {
"@floating-ui/core": "^1.0.0",
"@floating-ui/utils": "^0.2.0"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz",
"integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q=="
},
"node_modules/@lit-labs/ssr-dom-shim": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz",
"integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g=="
},
"node_modules/@lit/react": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@lit/react/-/react-1.0.4.tgz",
"integrity": "sha512-6HBvk3AwF46z17fTkZp5F7/EdCJW9xqqQgYKr3sQGgoEJv0TKV1voWydG4UQQA2RWkoD4SHjy08snSpzyoyd0w==",
"peerDependencies": {
"@types/react": "17 || 18"
}
},
"node_modules/@lit/reactive-element": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
"integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.2.0"
}
},
"node_modules/@shoelace-style/animations": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@shoelace-style/animations/-/animations-1.1.0.tgz",
"integrity": "sha512-Be+cahtZyI2dPKRm8EZSx3YJQ+jLvEcn3xzRP7tM4tqBnvd/eW/64Xh0iOf0t2w5P8iJKfdBbpVNE9naCaOf2g==",
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/claviska"
}
},
"node_modules/@shoelace-style/localize": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@shoelace-style/localize/-/localize-3.1.2.tgz",
"integrity": "sha512-Hf45HeO+vdQblabpyZOTxJ4ZeZsmIUYXXPmoYrrR4OJ5OKxL+bhMz5mK8JXgl7HsoEowfz7+e248UGi861de9Q=="
},
"node_modules/@shoelace-style/shoelace": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/@shoelace-style/shoelace/-/shoelace-2.15.0.tgz",
"integrity": "sha512-Lcg938Y8U2VsHqIYewzlt+H1rbrXC4GRSUkTJgXyF8/0YAOlI+srd5OSfIw+/LYmwLP2Peyh398Kae/6tg4PDA==",
"dependencies": {
"@ctrl/tinycolor": "^4.0.2",
"@floating-ui/dom": "^1.5.3",
"@lit/react": "^1.0.0",
"@shoelace-style/animations": "^1.1.0",
"@shoelace-style/localize": "^3.1.2",
"composed-offset-position": "^0.0.4",
"lit": "^3.0.0",
"qr-creator": "^1.0.0"
},
"engines": {
"node": ">=14.17.0"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/claviska"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
"peer": true
},
"node_modules/@types/react": {
"version": "18.2.74",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.74.tgz",
"integrity": "sha512-9AEqNZZyBx8OdZpxzQlaFEVCSFUM2YXJH46yPOiOpm078k6ZLOCcuAzGum/zK8YBwY+dbahVNbHrbgrAwIRlqw==",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
},
"node_modules/composed-offset-position": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/composed-offset-position/-/composed-offset-position-0.0.4.tgz",
"integrity": "sha512-vMlvu1RuNegVE0YsCDSV/X4X10j56mq7PCIyOKK74FxkXzGLwhOUmdkJLSdOBOMwWycobGUMgft2lp+YgTe8hw=="
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"peer": true
},
"node_modules/lit": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lit/-/lit-3.1.2.tgz",
"integrity": "sha512-VZx5iAyMtX7CV4K8iTLdCkMaYZ7ipjJZ0JcSdJ0zIdGxxyurjIn7yuuSxNBD7QmjvcNJwr0JS4cAdAtsy7gZ6w==",
"dependencies": {
"@lit/reactive-element": "^2.0.4",
"lit-element": "^4.0.4",
"lit-html": "^3.1.2"
}
},
"node_modules/lit-element": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.4.tgz",
"integrity": "sha512-98CvgulX6eCPs6TyAIQoJZBCQPo80rgXR+dVBs61cstJXqtI+USQZAbA4gFHh6L/mxBx9MrgPLHLsUgDUHAcCQ==",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.2.0",
"@lit/reactive-element": "^2.0.4",
"lit-html": "^3.1.2"
}
},
"node_modules/lit-html": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.2.tgz",
"integrity": "sha512-3OBZSUrPnAHoKJ9AMjRL/m01YJxQMf+TMHanNtTHG68ubjnZxK0RFl102DPzsw4mWnHibfZIBJm3LWCZ/LmMvg==",
"dependencies": {
"@types/trusted-types": "^2.0.2"
}
},
"node_modules/qr-creator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/qr-creator/-/qr-creator-1.0.0.tgz",
"integrity": "sha512-C0cqfbS1P5hfqN4NhsYsUXePlk9BO+a45bAQ3xLYjBL3bOIFzoVEjs79Fado9u9BPBD3buHi3+vY+C8tHh4qMQ=="
}
}
}

View File

@ -0,0 +1,5 @@
{
"dependencies": {
"@shoelace-style/shoelace": "^2.15.0"
}
}

View File

@ -1,5 +1,7 @@
Uses
* https://www.npmjs.com/package/slick-router
* https://github.com/blikblum/slick-router#readme
* https://dev.to/blikblum/slick-router-a-powerful-router-for-web-components-3fck
## random html in markdown
<style>table, th, td {
border: 1px solid black;
border-collapse: collapse;

View File

@ -1,28 +1,57 @@
import { withRouterLinks } from 'https://unpkg.com/slick-router@2.5.0/middlewares/router-links.js'
class ApplicationView extends withRouterLinks(HTMLElement) {
constructor() {
super()
this.addEventListener('change', e => {
if (e.target.matches('#animation-type')) {
const animation = e.target.value
if (animation) {
this.outlet.setAttribute('animation', e.target.value)
} else {
this.outlet.removeAttribute('animation')
customElements.define('application-view',
class ApplicationView extends withRouterLinks(HTMLElement) {
constructor() {
super()
this.addEventListener('change', e => {
if (e.target.matches('#animation-type')) {
const animation = e.target.value
if (animation) {
this.outlet.setAttribute('animation', e.target.value)
} else {
this.outlet.removeAttribute('animation')
}
}
}
})
}
})
// Custom function to emit toast notifications
function notify(message, variant = 'primary', icon = 'info-circle', duration = 3000) {
const alert = Object.assign(document.createElement('sl-alert'), {
variant,
closable: true,
duration: duration,
innerHTML: `
<sl-icon name="${icon}" slot="icon"></sl-icon>
${message}
`
});
connectedCallback() {
super.connectedCallback()
this.innerHTML = `
document.body.append(alert);
return alert.toast();
}
// Always escape HTML for text arguments!
function escapeHtml(html) {
const div = document.createElement('div');
div.textContent = html;
return div.innerHTML;
}
this.addEventListener('click', e => {
if (e.target.matches('#more-toast')) {
const alert = this.querySelector('#toaster')
notify(`This is custom toast `);
}
})
}
connectedCallback() {
super.connectedCallback()
this.innerHTML = `
<div class='App'>
<div class='App-header'>
<h1>Application</h1>
<ul class='Nav' routerlinks>
<li class='Nav-item'><a route="home" >Home</a></li>
<li class='Nav-item'><a route="tweets" >Tweets</a></li>
<li class='Nav-item'><a route="messages">Messages</a></li>
<li class='Nav-item'><a route="profile.index" param-user="scrobblemuch">Profile</a></li>
<li class='Nav-item'><a route="settings" >Settings</a></li>
@ -40,22 +69,54 @@ class ApplicationView extends withRouterLinks(HTMLElement) {
<option value="fade" selected>Fade</option>
<option value="slide-fade">Slide Fade</option>
<option value="bounce">Bounce</option>
</select>
</select>
<button id="more-toast">toast</button>
</div>
</div>
</div>
`
this.outlet = this.querySelector('router-outlet')
this.outlet = this.querySelector('router-outlet')
}
}
}
)
customElements.define('home-view',
class HomeView extends withRouterLinks(HTMLElement) {
customElements.define('application-view', ApplicationView)
connectedCallback() {
this.getModel();
}
getModel() {
return new Promise((res, rej) => {
fetch('/pdf/api/sources')
.then(data => data.json())
.then((json) => {
this.renderPosts(json);
res();
})
.catch((error) => rej(error));
})
}
renderPosts(data) {
const count = data.count
const shadowRoot = this.attachShadow({ mode: "closed" });
data.items.forEach(item => {
shadowRoot.appendChild(Object.assign(
document.createElement('sl-card'), {
textContent: item.slug
}
))
})
}
}
)
class HomeView extends withRouterLinks(HTMLElement) {
connectedCallback() {
super.connectedCallback()
this.innerHTML = `
customElements.define('tweet-view',
class TweetView extends withRouterLinks(HTMLElement) {
connectedCallback() {
super.connectedCallback()
this.innerHTML = `
<div class='Home' routerlinks>
<h2>Tweets</h2>
<div class='Tweet'>
@ -81,59 +142,84 @@ class HomeView extends withRouterLinks(HTMLElement) {
</div>
</div>
`
}
}
}
customElements.define('home-view', HomeView)
class MessagesView extends HTMLElement {
connectedCallback() {
this.innerHTML = `
)
customElements.define('messages-view',
class MessagesView extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div class='Messages'>
<h2>Messages</h2>
<p>You have no direct messages</p>
<sl-tree>
<sl-tree-item lazy>Available Trees</sl-tree-item>
</sl-tree>
<script type="module">
const lazyItem = document.querySelector('sl-tree-item[lazy]');
lazyItem.addEventListener('sl-lazy-load', () => {
alert("heelo");
});
</script>
</div>
`
}
}
}
customElements.define('messages-view', MessagesView)
class SettingsView extends HTMLElement {
connectedCallback() {
this.innerHTML = `
)
customElements.define('settings-view',
class SettingsView extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div class='Messages'>
<h2>Sett</h2>
<p>You have no direct messages</p>
<a href="/pdf/api/sources" target="_blank">DATA</a>
<h2>Settings</h2>
<div>
Animation
<select id="animation-type2">
<option value="">None</option>
<option value="fade" selected>Fade</option>
<option value="slide-fade">Slide Fade</option>
<option value="bounce">Bounce</option>
</select>
</div>
<sl-alert variant="neutral" duration="3000" closable >
<sl-icon slot="icon" name="gear"></sl-icon>
<strong>Your settings have been updated</strong><br />
Settings will take effect on next login.
</sl-alert>
</div>
`
}
}
}
customElements.define('settings-view', SettingsView)
)
customElements.define('profile-view',
class ProfileView extends HTMLElement {
static get outlet() {
return '.Container'
}
class ProfileView extends HTMLElement {
static get outlet() {
return '.Container'
}
connectedCallback() {
this.innerHTML = `
connectedCallback() {
this.innerHTML = `
<div class='Profile'>
<div class='Container'></div>
</div>
`
}
}
}
)
customElements.define('profile-view', ProfileView)
class ProfileIndexView extends HTMLElement {
connectedCallback() {
this.innerHTML = `
customElements.define('profile-index-view',
class ProfileIndexView extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div class='ProfileIndex'>
<h2>${this.$route.params.user} profile</h2>
</div>
`
}
}
}
)
customElements.define('profile-index-view', ProfileIndexView)

View File

@ -1,18 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<base href="/pdf/static/"/>
<title>PDFS453 </title>
<link rel="icon" href="favicon.png">
<script type="module" src="index.js"></script>
<link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="animations.css" />
</head>
<body>
</body>
</html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<base href="/pdf/static/" />
<title>PDFS453 </title>
<link rel="icon" href="favicon.png">
<link rel="stylesheet" href="/static/shoelace@2.15.0/cdn/themes/light.css" />
<script type="module" src="/static/shoelace@2.15.0/cdn/shoelace-autoloader.js"></script>
<script type="module" src="/static/shoelace@2.15.0/cdn/components/alert/alert.js"></script>
<script type="module" src="index.js"></script>
<link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="animations.css" />
</head>
<body>
</body>
</html>

View File

@ -19,8 +19,10 @@ const router = new Router({
// in this particular case we configure components by its tag name
router.map(route => {
route('application', { path: '/pdf/', component: 'application-view' }, () => {
route('home', { path: '', component: 'home-view' })
route('application', { path: '/pdf/',
component: 'application-view' }, () => {
route('home', { path: '', component: 'home-view' })
route('tweets', { component: 'tweet-view' })
route('messages', { component: 'messages-view' })
route('status', { path: ':user/status/:id' })
route('profile', { path: 'profile/:user', component: 'profile-view' }, () => {