Initial big commit that brings over all of the latest development from my sites. This will be refined and released soon, but right now I'm testing how it installs with ljsthw-bandolier's installer.

main
Zed A. Shaw 10 months ago
parent 9b59ad3e31
commit d2d7c9c9c0
  1. 176
      .gitignore
  2. 13
      admin/App.svelte
  3. 27
      admin/Header.svelte
  4. 48
      admin/Layout.svelte
  5. 116
      admin/bando/Bandolier.svelte
  6. 221
      admin/bando/Components.svelte
  7. 257
      admin/bando/Djenterator.svelte
  8. 194
      admin/bando/IconFinder.svelte
  9. 21
      admin/bando/demos/Accordion.svelte
  10. 7
      admin/bando/demos/Accordion.svelte.md
  11. 26
      admin/bando/demos/AspectRatio.svelte
  12. 12
      admin/bando/demos/AspectRatio.svelte.md
  13. 51
      admin/bando/demos/Badge.svelte
  14. 3
      admin/bando/demos/Badge.svelte.md
  15. 25
      admin/bando/demos/ButtonGroup.svelte
  16. 1
      admin/bando/demos/ButtonGroup.svelte.md
  17. 27
      admin/bando/demos/Calendar.svelte
  18. 1
      admin/bando/demos/Calendar.svelte.md
  19. 37
      admin/bando/demos/Callout.svelte
  20. 8
      admin/bando/demos/Callout.svelte.md
  21. 47
      admin/bando/demos/Cards.svelte
  22. 1
      admin/bando/demos/Cards.svelte.md
  23. 25
      admin/bando/demos/Carousel.svelte
  24. 1
      admin/bando/demos/Carousel.svelte.md
  25. 14
      admin/bando/demos/Chat.svelte
  26. 1
      admin/bando/demos/Chat.svelte.md
  27. 53
      admin/bando/demos/Code.svelte
  28. 25
      admin/bando/demos/Code.svelte.md
  29. 16
      admin/bando/demos/Countdown.svelte
  30. 1
      admin/bando/demos/Countdown.svelte.md
  31. 6
      admin/bando/demos/Darkmode.svelte
  32. 21
      admin/bando/demos/Darkmode.svelte.md
  33. 71
      admin/bando/demos/DataTable.svelte
  34. 3
      admin/bando/demos/DataTable.svelte.md
  35. 252
      admin/bando/demos/FairPay.svelte
  36. 16
      admin/bando/demos/FairPay.svelte.md
  37. 52
      admin/bando/demos/Flipper.svelte
  38. 13
      admin/bando/demos/Flipper.svelte.md
  39. 78
      admin/bando/demos/Form.svelte
  40. 10
      admin/bando/demos/Form.svelte.md
  41. 10
      admin/bando/demos/HLSVideo.svelte
  42. 114
      admin/bando/demos/Icon.svelte
  43. 56
      admin/bando/demos/IconImage.svelte
  44. 27
      admin/bando/demos/LiveStream.svelte
  45. 5
      admin/bando/demos/LiveStream.svelte.md
  46. 17
      admin/bando/demos/LoggedIn.svelte
  47. 16
      admin/bando/demos/LoggedIn.svelte.md
  48. 13
      admin/bando/demos/Login.svelte
  49. 5
      admin/bando/demos/Login.svelte.md
  50. 51
      admin/bando/demos/Markdown.svelte
  51. 32
      admin/bando/demos/Modal.svelte
  52. 8
      admin/bando/demos/Modal.svelte.md
  53. 42
      admin/bando/demos/OGPreview.svelte
  54. 32
      admin/bando/demos/OGPreview.svelte.md
  55. 33
      admin/bando/demos/Pagination.svelte
  56. 1
      admin/bando/demos/Pagination.svelte.md
  57. 6
      admin/bando/demos/Panels.svelte
  58. 21
      admin/bando/demos/PlaceHolder.svelte
  59. 3
      admin/bando/demos/PlaceHolder.svelte.md
  60. 33
      admin/bando/demos/Progress.svelte
  61. 13
      admin/bando/demos/Progress.svelte.md
  62. 26
      admin/bando/demos/Sidebar.svelte
  63. 6
      admin/bando/demos/Sidebar.svelte.md
  64. 35
      admin/bando/demos/SidebarCSS.svelte
  65. 3
      admin/bando/demos/SidebarCSS.svelte.md
  66. 15
      admin/bando/demos/SnapImage.svelte
  67. 29
      admin/bando/demos/SnapImage.svelte.md
  68. 13
      admin/bando/demos/Spinner.svelte
  69. 16
      admin/bando/demos/Spinner.svelte.md
  70. 39
      admin/bando/demos/StackLayer.svelte
  71. 25
      admin/bando/demos/StackLayer.svelte.md
  72. 48
      admin/bando/demos/Switch.svelte
  73. 4
      admin/bando/demos/Switch.svelte.md
  74. 22
      admin/bando/demos/Tabs.svelte
  75. 5
      admin/bando/demos/Tabs.svelte.md
  76. 32
      admin/bando/demos/Tiles.svelte
  77. 1
      admin/bando/demos/Tiles.svelte.md
  78. 35
      admin/bando/demos/Toast.svelte
  79. 4
      admin/bando/demos/Toast.svelte.md
  80. 19
      admin/bando/demos/Toastier.svelte
  81. 21
      admin/bando/demos/Toastier.svelte.md
  82. 66
      admin/bando/demos/Tooltip.svelte
  83. 12
      admin/bando/demos/Tooltip.svelte.md
  84. 16
      admin/bando/demos/Video.svelte
  85. 5
      admin/bando/demos/Video.svelte.md
  86. 23
      admin/bando/demos/WTVideo.svelte
  87. 24
      admin/bando/demos/WTVideo.svelte.md
  88. 10
      admin/main.js
  89. 66
      admin/pages/Create.svelte
  90. 84
      admin/pages/EmailConfig.svelte
  91. 147
      admin/pages/EmailDNS.svelte
  92. 61
      admin/pages/EmailSend.svelte
  93. 103
      admin/pages/Errors.svelte
  94. 101
      admin/pages/Home.svelte
  95. 151
      admin/pages/ReadUpdate.svelte
  96. 117
      admin/pages/Routes.svelte
  97. 342
      admin/pages/Stats.svelte
  98. 97
      admin/pages/Table.svelte
  99. 85
      admin/pages/TableIndex.svelte
  100. 63
      admin/pages/Tests.svelte
  101. Some files were not shown because too many files have changed in this diff Show More

176
.gitignore vendored

@ -1,153 +1,23 @@
# ---> Vim
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
/node_modules/
/public/
/rendered/build/
/rendered/public/
.*.sw*
.DS_Store
*.sqlite3*
debug/
static/thumbs
static/videos
secrets/*
coverage/
.coverage
static/module
client/config.js
emails/config.js
media
tests/fixtures
rendered/wip
junk/
static/images/sample_video.mp4
static/js/webtorrent.debug.js

@ -0,0 +1,13 @@
<script>
import Router from 'svelte-spa-router';
import routes from '$/admin/routes.js';
import Bandolier from '$/admin/bando/Bandolier.svelte';
import Reloader from "$/client/components/Reloader.svelte";
</script>
<Router {routes}/>
{#if process.env.DANGER_ADMIN}
<Reloader />
<Bandolier shown={ false }/>
{/if}

@ -0,0 +1,27 @@
<script>
import { logout_user } from '$/client/api.js';
import Icon from '$/client/components/Icon.svelte';
import Darkmode from '$/client/components/Darkmode.svelte';
import {link} from 'svelte-spa-router';
import { user } from "$/client/stores.js";
export let fixed = false;
</script>
<header class:fixed>
<nav>
{#if $user.authenticated}
<a href="/client/#/"><Icon name="home" tooltip="Back to app." size="36" /></a>
<ul>
{#if $user.admin }
<li><a href="/" use:link><Icon name="keyboard" tooltip="Admin Dashboard."/></a></li>
{/if}
<li><a href="/" on:click|preventDefault={ logout_user } data-testid="logout-link"><Icon name="log-out" tooltip="Log out."/></a></li>
<li><Darkmode /></li>
</ul>
{:else}
<a href="/client/#/">Login</a>
{/if}
</nav>
</header>

@ -0,0 +1,48 @@
<script>
import LoggedIn from '$/client/components/LoggedIn.svelte';
import Spinner from '$/client/components/Spinner.svelte';
import Footer from '$/client/Footer.svelte';
import Header from '$/admin/Header.svelte';
export let fixed = false;
export let footer = true;
export let header = true;
export let authenticated = false;
export let testid = "page";
export let centered = false;
export let fullscreen = false;
export let fullwidth = false;
export let auth_optional = false;
export let horizontal = false;
</script>
{#if authenticated || auth_optional}
{#if header}
<Header fixed={fixed}/>
{/if}
<LoggedIn optional={ auth_optional } redirect="/login" show_required_page={ false }>
<main class:horizontal class:fullwidth class:fullscreen class:centered slot="yes" data-testid={ testid }>
<slot></slot>
</main>
<main class:horizontal class:fullwidth class:fullscreen class:centered slot="no" data-testid={ testid }>
<Spinner color="var(--value8)" />
</main>
</LoggedIn>
{#if footer}
<Footer />
{/if}
{:else}
{#if header}
<Header fixed={fixed}/>
{/if}
<main class:horizontal class:fullwidth class:fullscreen class:centered data-testid={ testid }>
<slot></slot>
</main>
{#if footer}
<Footer />
{/if}
{/if}

@ -0,0 +1,116 @@
<script>
import Icon from '$/client/components/Icon.svelte';
import { onMount } from 'svelte';
import { fade } from "svelte/transition";
import { log } from "$/client/logging.js";
import api from '$/client/api.js';
export let shown = true;
let errors = [];
$: has_errors = errors.length > 0;
const rephresh = async () => {
let [status, data] = await api.get('/api/devtools/info');
if(status === 200) {
errors = data.errors;
log.debug("errors", errors);
} else {
log.error("Failed getting /api/devtools/devinfo", status, data);
}
}
const handle_keypress = (event) => {
if(event.ctrlKey && event.altKey) {
if(event.key == "b" || event.keyCode == 66) {
rephresh();
shown = !shown;
}
} else if(event.key === "Escape") {
shown = false;
}
}
onMount(() => rephresh());
</script>
<svelte:window on:keydown={ handle_keypress } />
<style>
bando {
display: grid;
grid-template-rows: 1fr;
grid-template-columns: repeat(5, 1fr);
grid-gap: 0.5rem;
width: 450px;
position: fixed;
bottom: 0;
right: 1rem;
background-color: var(--red);
opacity: 1;
z-index: 10000;
padding: 1rem;
color: var(--value9);
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
bando#errors {
display: flex;
align-items: start;
justify-content: start;
}
bando#errors ul {
margin: 0px;
padding: 0.3em;
list-style-type: none;
}
bando a {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--value9);
width: min-content;
}
</style>
{#if shown}
<bando transition:fade|local>
<a href="/admin/#/bando/iconfinder/">
<Icon name="feather" light={ true } size="64" />
<span>Icons</span>
</a>
<a href="/admin/#/bando/components/">
<Icon name="layout" light={ true } size="64" />
<span>Library</span>
</a>
<a href="/admin/#/bando/djenterator/">
<Icon name="edit" light={ true } size="64" />
<span>Templates</span>
</a>
<a href="/admin/#/bando/devinfo/routes/">
<Icon name="code" light={ true } size="64" />
<span>Routes</span>
</a>
<a href="/admin/#/bando/devinfo/errors/">
<Icon name="alert-circle" light={ true } size="64" />
<span>Errors</span>
</a>
</bando>
{:else if has_errors}
<bando id="errors" transition:fade|local>
<a href="/admin/#/bando/devinfo/errors/">
<Icon name="alert-circle" light={ true } size="64" />
<span>Errors</span>
</a>
<ul>
{#each errors as error, i}
<li>{ error.filename }</li>
{/each}
</ul>
</bando>
{/if}

@ -0,0 +1,221 @@
<script>
import Accordion from "./demos/Accordion.svelte";
import AspectRatio from "./demos/AspectRatio.svelte";
import Badge from "./demos/Badge.svelte";
import ButtonGroup from "./demos/ButtonGroup.svelte";
import Calendar from "./demos/Calendar.svelte";
import Callout from "./demos/Callout.svelte";
import Cards from "./demos/Cards.svelte";
import Carousel from "./demos/Carousel.svelte";
import Chat from "./demos/Chat.svelte";
import Code from "$/client/components/Code.svelte";
import CodeDemo from "./demos/Code.svelte";
import CodeFormatter from "$/client/components/CodeFormatter.svelte";
import Countdown from "./demos/Countdown.svelte";
import Darkmode from "./demos/Darkmode.svelte";
import DataTable from "./demos/DataTable.svelte";
import FairPay from "./demos/FairPay.svelte";
import Flipper from "./demos/Flipper.svelte";
import Form from "./demos/Form.svelte";
import HLSVideo from "./demos/HLSVideo.svelte";
import Icon from "$client/components/Icon.svelte";
import IconDemo from "./demos/Icon.svelte";
import IconImage from "./demos/IconImage.svelte";
import LiveStream from "./demos/LiveStream.svelte";
import LoggedIn from "./demos/LoggedIn.svelte";
import Login from "./demos/Login.svelte";
import Markdown from "$/client/components/Markdown.svelte";
import MarkdownDemo from "./demos/Markdown.svelte";
import Modal from "./demos/Modal.svelte";
import OGPreview from "./demos/OGPreview.svelte";
import Pagination from "./demos/Pagination.svelte";
import PlaceHolder from "./demos/PlaceHolder.svelte";
import Progress from "./demos/Progress.svelte";
import Sidebar from "$/client/components/Sidebar.svelte";
import SidebarCSS from "./demos/SidebarCSS.svelte";
import SidebarDemo from "./demos/Sidebar.svelte";
import SnapImage from "./demos/SnapImage.svelte";
import Spinner from "./demos/Spinner.svelte";
import StackLayer from "./demos/StackLayer.svelte";
import Switch from "./demos/Switch.svelte";
import Tabs from "./demos/Tabs.svelte";
import Tiles from "./demos/Tiles.svelte";
import Toast from "./demos/Toast.svelte";
import Toastier from "./demos/Toastier.svelte";
import Tooltip from "./demos/Tooltip.svelte";
import Video from "./demos/Video.svelte";
import WTVideo from "./demos/WTVideo.svelte";
import Layout from "../Layout.svelte";
import { link, replace } from "svelte-spa-router";
export let params = {};
/* WARNING: If you put any component that uses /api/login into the first slot
* or set active: true on them it will require a login.
*/
let panels = [
{title: "Accordion", active: false, icon: "align-justify", component: Accordion},
{title: "AspectRatio", active: false, icon: "copy", component: AspectRatio},
{title: "Badge", active: false, icon: "award", component: Badge},
{title: "ButtonGroup", active: false, icon: "server", component: ButtonGroup},
{title: "Calendar", active: false, icon: "calendar", component: Calendar},
{title: "Callout", active: false, icon: "file-plus", component: Callout},
{title: "Cards", active: false, icon: "credit-card", component: Cards},
{title: "Carousel", active: false, icon: "repeat", component: Carousel},
{title: "Chat", active: false, icon: "message-circle", component: Chat},
{title: "Code", active: false, icon: "code", component: CodeDemo},
{title: "Countdown", active: false, icon: "clock", component: Countdown},
{title: "Darkmode", active: false, icon: "sunrise", component: Darkmode},
{title: "DataTable", active: false, icon: "grid", component: DataTable},
{title: "FairPay", active: false, icon: "dollar-sign", component: FairPay},
{title: "Flipper", active: false, icon: "layers", component: Flipper},
{title: "Form", active: false, icon: "database", component: Form},
{title: "Icon", active: false, icon: "feather", component: IconDemo},
{title: "IconImage", active: false, icon: "image", component: IconImage},
{title: "LiveStream", active: false, icon: "cast", component: LiveStream},
{title: "LoggedIn", active: false, icon: "log-out", component: LoggedIn},
{title: "Login", active: false, icon: "log-in", component: Login},
{title: "Markdown", active: false, icon: "file", component: MarkdownDemo},
{title: "Modal", active: false, icon: "maximize", component: Modal},
{title: "OGPreview", active: false, icon: "external-link", component: OGPreview},
{title: "Pagination", active: false, icon: "skip-forward", component: Pagination},
{title: "PlaceHolder", active: false, icon: "image", component: PlaceHolder},
{title: "Progress", active: false, icon: "thermometer", component: Progress},
{title: "Sidebar", active: false, icon: "sidebar", component: SidebarDemo},
{title: "SidebarCSS", active: false, icon: "sidebar", component: SidebarCSS},
{title: "SnapImage", active: false, icon: "camera", component: SnapImage},
{title: "Spinner", active: false, icon: "rotate-cw", component: Spinner},
{title: "StackLayer", active: false, icon: "layers", component: StackLayer},
{title: "Switch", active: false, icon: "check-square", component: Switch},
{title: "Tabs", active: false, icon: "folder", component: Tabs},
{title: "Tiles", active: false, icon: "camera", component: Tiles},
{title: "Toast", active: false, icon: "message-square", component: Toast},
{title: "Toastier", active: false, icon: "message-square", component: Toastier},
{title: "Tooltip", active: false, icon: "help-circle", component: Tooltip},
{title: "HLSVideo", active: false, icon: "video", component: HLSVideo},
{title: "Video", active: false, icon: "video", component: Video},
{title: "WTVideo", active: false, icon: "video", component: WTVideo},
];
panels.forEach(p => (p.code = p.code || `/bando/demos/${p.title}.svelte`));
const select_named = () => {
if(params.name) {
return panels.find(p => p.title === params.name) || panels[0];
} else {
return panels[0];
}
}
let selected = select_named();
selected.active = true;
let show = "DEMO";
const load_docs = async (from) => {
const res = await fetch(`${from.code}.md`);
return res.status == 200 ? res.text() : undefined;
}
const sidebar_select = (event) => {
const {index, item} = event.detail;
show = "DEMO";
selected = item;
panels = panels.map((x, i) => {
x.active = i == index;
return x;
});
replace(`/bando/components/${selected.title}/`);
}
</script>
<style>
div[slot="top"] span {
display: none;
}
contents {
padding: 0.5rem;
width: 100%;
max-height: 100vh;
overflow-y: auto;
}
tabs {
margin-bottom: 1rem;
}
@media only screen and (max-width: 900px) {
div[slot="top"] h3 {
display: none;
}
div[slot="top"] span {
display: inline-block;
padding-top: 0.3rem;
}
div[slot="bottom"] {
display: none;
}
}
left {
max-height: 100vh;
overflow-y: auto;
overflow-x: hidden;
max-width: min-content;
width: min-content;
min-width: min-content;
}
</style>
<Layout fullscreen={ true } header={false} footer={ false } testid="page-bando-demos">
<left>
<Sidebar on:select={ sidebar_select } menu={ panels }>
<div slot="top">
<h3><a href="/" use:link><Icon name="arrow-left-circle" /> Components</h3>
<span><Icon name="home" size="36" /></span>
</div>
<div slot="bottom">
<p>Code is in <b>client/bando/demos</b></p>
</div>
</Sidebar>
</left>
<contents>
<tabs>
<a data-testid="tab-demo" class:active={ show == "DEMO" } on:click={ () => show = "DEMO" }>
<Icon name="eye" size="36px"/> Demo
</a>
<a data-testid="tab-docs" class:active={ show == "DOCS" } on:click={ () => show = "DOCS" }>
<Icon name="book-open" size="36px"/> Docs
</a>
<a data-testid="tab-code" class:active={ show == "CODE" } on:click={ () => show = "CODE" }>
<Icon name="code" size="36px" /> Code
</a>
</tabs>
<component data-testid="demo-{ selected.title }">
{#if show == "CODE"}
<Code src={ selected.code } />
{:else if show == "DOCS"}
{#await load_docs(selected) then docs }
{#if docs}
<Markdown content={ docs } />
{/if}
{/await}
{:else}
<h1>{selected.title}</h1>
<svelte:component this={selected.component} />
{/if}
</component>
</contents>
</Layout>
<CodeFormatter />

@ -0,0 +1,257 @@
<script>
import template from "lodash/template";
import { fade } from "svelte/transition";
import Icon from "$/client/components/Icon.svelte";
import { log } from "$/client/logging.js";
import Layout from "../Layout.svelte";
import api from '$/client/api.js';
import { onMount } from "svelte";
export let selected_template;
let showing_rendered = false;
let results = "";
let source = "";
let variable_json = "{ }";
let generators = [];
let variables = {};
let renderer = () => source;
let notice = "";
let last_good = "";
const list_generators = async () => {
let [status, data] = await api.get('/api/devtools/djenterator');
if(status == 200) {
generators = data;
selected_template = generators[0];
} else {
log.debug("failed to load generators", status);
}
}
const render_template = () => {
try {
// avoid rendering when the current template doesn't match the renderer
if(selected_template == renderer._template) {
variables = variable_json ? JSON.parse(variable_json): {};
results = renderer(variables);
notice = "";
last_good = results;
}
return true;
} catch(err) {
notice = err.message;
results = last_good;
return false;
}
}
const load_variables = async (template_name) => {
let res = await fetch(`/djenterator/${template_name}.vars`);
if(res.status == 200) {
variable_json = await res.text();
} else {
variable_json = '{}';
notice = `No ${template_name}.vars file found. ${res.status}`;
}
}
const load_template = async (template_name) => {
let res = await fetch(`/djenterator/${template_name}`);
if(res.status == 200) {
source = await res.text();
last_good = source;
try {
renderer = template(source);
// tag this template renderer so that we don't try to render it against the wrong one
renderer._template = template_name;
render_template();
} catch(error) {
log.error(error);
notice = `${error.message}`;
results = source;
}
} else {
notice = `Error loading ${template_name}: ${res.status}`;
}
}
const toggle_template = () => {
if(showing_rendered) {
render_template();
} else {
results = renderer.toString();
}
showing_rendered = !showing_rendered;
}
const copy_code = () => {
navigator.clipboard.writeText(results).then(() => {
notice = "Code copied to clipboard.";
}, () => {
notice = "Failed copying to clipboard.";
});
}
const canary = '3a025dba-1a62-4169-ae9f-f7cd4104c4f4';
log.debug("Canary is compiled into build as", canary);
$: if(variable_json) render_template();
// this reload the templates when you click on a new one
const re_render = async (what) => {
await load_variables(what);
await load_template(what);
}
$: if(selected_template) re_render(selected_template);
onMount(async () => await list_generators());
</script>
<style>
content {
display: flex;
flex-direction: row;
width: 100%;
}
template-editor {
display: flex;
flex-direction: column;
width: 100%;
align-items: center;
}
pre {
position: relative;
height: 90vh;
max-height: 90vh;
overflow-y: auto;
}
right pre {
display: flex;
font-size: 1em;
flex-basis: 100%;
margin: 0;
padding: 0;
}
right pre code {
padding-top: 2rem;
display: flex;
flex-basis: 100%;
border-radius: 0px 4px 4px 0px;
line-height: unset;
margin: 0;
}
left {
display: flex;
flex-grow: 1;
flex-basis: 70ch;
}
left textarea {
border-radius: 4px 0px 0px 4px;
margin: 0;
background-color: var(--color-secondary);
color: var(--color-bg);
height: 90vh;
max-height: 90vh;
}
right {
position: relative;
display: flex;
flex-direction: column;
flex-grow: 3;
flex-basis: 100ch;
align-items: stretch;
}
status {
position: absolute;
z-index: 100;
top: 0;
padding-right: 1rem;
padding-left: 1rem;
padding-top: 0.1rem;
width: 90%;
color: var(--color-bg);
display: flex;
box-sizing: border-box;
background-color: var(--color-secondary);
border-radius: 0px 4px 0px 0px;
justify-content: space-evenly;
}
status file {
flex-basis: 90%;
text-align: right;
}
status buttons {
display: flex;
justify-content: space-around;
flex-basis: 10%;
padding-top: 0.5rem;
}
pre notice {
position: absolute;
bottom: 0;
left: 0.3rem;
right: 0.3rem;
text-align: right;
padding: 0.5rem;
font-size: 1rem;
background-color: var(--color-bg-tertiary);
border-radius: 4px 4px 0px 0px;
}
</style>
<Layout fullscreen={ true } header={ false } testid="page-bando-djenterator">
<template-editor>
<select bind:value={ selected_template }>
{#each generators as template}
<option value={ template }>{ template }</option>
{/each}
</select>
<content>
<left>
<textarea class="editor" bind:value={ variable_json } rows="15"></textarea>
</left>
<right>
<status>
<buttons>
<span on:click={ copy_code }><Icon name="copy" size="24" color="var(--color-bg)" /></span>
<span on:click={ toggle_template }><Icon name="code" size="24" color="var(--color-bg)" /></span>
</buttons>
<file>static/djenterator/{ selected_template }</file>
</status>
<pre>
<code>
{results}
</code>
{#if notice}
<notice in:fade on:click={ () => notice = "" }>
<b>{ notice }</b>
</notice>
{/if}
</pre>
</right>
</content>
<template-editor>
</Layout>

@ -0,0 +1,194 @@
<script>
import Icon from "$/client/components/Icon.svelte";
import { onMount } from "svelte";
import { log } from "$/client/logging.js";
import api from "$/client/api.js";
import { defer } from "$/client/helpers.js";
import Toast from "$/client/components/Toasts.svelte";
let all_icons = [];
let icons_by_letters = {"a": []};
let selected_letter = "a";
let inactive = false;
let icons = [];
export let size=48;
let search = "";
export let labels=true;
export let tight=false;
let load_promise = defer();
let send_toast;
const order_pages = (in_icons) => {
const letters = {};
for(let icon_name of in_icons) {
const first = icon_name[0];
letters[first] = letters[first] || [];
letters[first].push(icon_name);
}
return letters;
}
const search_icons = async (pattern) => {
await load_promise; // lord I hate Svelte's lifecycle
if(pattern.trim() === "") {
icons_by_letters = order_pages(all_icons);
icons = all_icons;
selected_letter = Object.keys(icons_by_letters)[0];
} else {
icons = all_icons.filter(i => i.includes(pattern));
if(icons.length > 0) {
icons_by_letters = order_pages(icons);
selected_letter = Object.keys(icons_by_letters)[0];
}
}
}
$: search_icons(search);
const gen_code = (name) => {
let results = `<Icon name="${name}" size="${size}" />`;
navigator.clipboard.writeText(results).then(() => {
send_toast(`${name} copied to clipboard.`);
}, () => {
send_toast(`${name} copy FAILED.`);
});
}
onMount(async () => {
const [status, data] = await api.get("/icons/index.json");
if(status === 200) {
all_icons = data;
icons_by_letters = order_pages(all_icons);
load_promise.resolve();
} else {
log.error("Invalid response", status, data);
load_promise.reject();
}
});
</script>
<style>
icons {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: auto;
row-gap: 1rem;
}
icons.tight {
grid-template-columns: repeat(10, 1fr);
}
icons icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
search-bar {
display: flex;
justify-content: space-evenly;
flex-wrap: nowrap;
height: 5ch;
min-height: 5ch;
}
search-bar input#size {
width: 6ch;
}
search-bar input#search {
min-width: 30ch;
max-width: 30ch;
width: 30ch;
}
search-bar span {
padding-right: 1rem;
}
content {
display: flex;
flex-direction: column;
padding-top: 1rem;
}
display {
display: flex;
flex-direction: column;
align-items: stretch;
}
display letters {
display: grid;
grid-template-rows: 1fr 1fr;
grid-template-columns: repeat(13, 1fr);
margin-bottom: 1rem;
}
display letters letter {
font-size: 1.5em;
font-weight: 600;
text-align: center;
padding: 1rem;
border: 1px solid var(--value5);
}
display letters letter.selected {
background-color: var(--color-bg-secondary);
}
@media only screen and (max-width: 900px) {
icons {
grid-template-columns: repeat(4, 1fr);
}
}
@media only screen and (max-width: 600px) {
icons {
grid-template-columns: repeat(3, 1fr);
}
}
</style>
<content>
<search-bar>
<span on:click={ () => inactive = !inactive }>
<Icon tooltip="Toggle inactive look." name={ inactive ? 'eye' : 'eye-off'} size="24" />
</span>
<input placeholder="Search names..." bind:value={ search } id="search" >
<div>Pixel Size:</div>
<input bind:value={ size } id="size" >
</search-bar>
{#if icons.length > 0}
<display>
<letters>
{#each Object.keys(icons_by_letters) as letter}
<letter class:selected={ letter === selected_letter } on:click={ () => selected_letter = letter }>{ letter }</letter>
{/each}
</letters>
<icons class:tight={ tight }>
{#each icons_by_letters[selected_letter] as name}
<icon on:click={ () => gen_code(name) }>
<Icon name={ name } size={ size } inactive={inactive}/>
{#if labels}
<span>{ name }</span>
{/if}
</icon>
{/each}
</icons>
</display>
{:else}
<h1>No Icons match "{ search }"</h1>
{/if}
<Toast bind:send_toast orientation="bottom right" />
</content>

@ -0,0 +1,21 @@
<script>
import Tabs from "$/client/components/Tabs.svelte";
import Calendar from "./Calendar.svelte";
import Cards from "./Cards.svelte";
import Login from "./Login.svelte";
import { log } from "$/client/logging.js";
export let panels = [
{title: "Calendar", active: true, icon: "calendar", component: Calendar},
{title: "Cards", active: false, icon: "credit-card", component: Cards},
{title: "Login", active: false, icon: "log-in", component: Login},
];
let selected = panels[0];
const tab_select = (event) => {
log.debug("SELECTED TAB", event.detail);
}
</script>
<Tabs panels={ panels } on:select={ tab_select } bind:selected vertical={true} />

@ -0,0 +1,7 @@
An accordion is just the `Tabs` component with `vertical` set to true:
```
<Tabs panels={ panels } on:select={ tab_select } bind:selected vertical={true} />
```
See the documentation for `Tabs` for how to use it.

@ -0,0 +1,26 @@
<style>
aspect-test {
display: flex;
flex-direction: column;
}
.top-box p {
display: flex;
justify-content: center;
align-items: center;
background-color: var(--value5);
font-size: 2.5vw;
padding: 1rem;
}
</style>
<aspect-test>
<div class="top-box" style="--aspect-ratio: 16/9;">
<p>Every single visual experience that has a bounding plane has to deal with aspect ratios, whether that's a photo through a 1:1 ratio medium format camera or a 16:9 ratio 4k movie. If there's a visual bounding box around the scene then everything in the scene must deal with the ratio of the width to the height.</p>
</div>
<div style="--aspect-ratio: 10/2; background-color: var(--value7); font-size: 1.5vw;">
<p>CSS is one of the only visual systems that has no concept of aspect ratios despite the performance benefits of knowing the aspect of a block before the contents of the block are available. If you know the aspect ratio of a block, and you know the width of the page, then you can render the block immediately because you only need <b>one</b> dimension to render both.</p>
</div>
</aspect-test>

@ -0,0 +1,12 @@
Because CSS doesn't really support aspect ratios (despite a standard that says it does) you'll have a difficult time positioning and sizing many elements on the page. The `--aspect-ratio` hack I've put into `static/global.css` does a decent job of creating a fake aspect ratio system without too many changes to your HTML. You only need to add:
```
style="--aspect-ratio: 16/9"
```
And the magic CSS should do what you want. It's a trick I've taken from [an article on CSS tricks](https://css-tricks.com/aspect-ratio-boxes/#using-custom-properties) that gives an excellent solution that doesn't require crazy changed to your CSS or HTML. You can also use `16/9` or decimal notation `1.4`.
Limitations
===
As you change the size of this demo you'll see the limitation of this method. As the boxes shrink the text will explode out of the "box" that CSS has, or the boxes will cover other boxes. It's best to have only 1 or 2 elements that use `--aspect-ration` and test it at various sizes.

@ -0,0 +1,51 @@
<script>
import Icon from "$/client/components/Icon.svelte";
</script>
<style>
box {
width: 200px;
height: 100px;
background-color: var(--color-bg-inverted);
color: var(--color-text-inverted);
display: flex;
justify-content: center;
align-items: center;
position: relative;
padding: 0.5rem;
border-radius: var(--border-radius);
box-shadow: var(--box-shadow) var(--color-shadow);
}
</style>
<p>A simple badge with a number:</p>
<badge>1</badge>
<p>A badge with a tiny icon in it:</p>
<badge><Icon name="inbox" size="18px" width="1px" /></badge>
<p>Top left position with <b>class="top-left"</b>:</p>
<box>
<b>Top Left</b>
<badge class="top-left"><Icon name="inbox" size="14px" width="1px" /></badge>
</box>
<p>Bottom left position with <b>class="bottom-left"</b>:</p>
<box>
<b>Bottom Left</b>
<badge class="bottom-left"><Icon name="inbox" size="14px" width="1px" /></badge>
</box>
<p>Top right position with <b>class="top-right"</b>:</p>
<box>
<b>Top Right</b>
<badge class="top-right"><Icon name="inbox" size="14px" width="1px" /></badge>
</box>
<p>Bottom right position with <b>class="bottom-right"</b>:</p>
<box>
<b>Bottom Right</b>
<badge class="bottom-right"><Icon name="inbox" size="14px" width="1px" /></badge>
</box>

@ -0,0 +1,3 @@
Badges are simple little notification icons placed on elements. The ones implemented here are _very_ minimalist, with just enough space to work with the [feather icons](https://feathericons.com) included with the project.
You can place most single character elements inside a badge but test that your font will scale appropriately on different screen sizes.

@ -0,0 +1,25 @@
<script>
import Icon from "$/client/components/Icon.svelte";
</script>
<style>
button-group.vertical {
width: min-content;
}
</style>
<p>This is also shown at the bottom the Card demo. It's just a compact strip for buttons.</p>
<button-group>
<button id="ok"><Icon name="check-circle" color="var(--color-accent)" size="48" /></button>
<button><Icon name="x-circle" color="var(--color-bg)" size="48"/></button>
<button><Icon name="alert-circle" color="var(--color-bg)" size="48"/></button>
</button-group>
<h2>Vertical</h2>
<button-group class="vertical">
<button id="ok"><Icon name="check-circle" color="var(--color-accent)" size="48" /></button>
<button><Icon name="x-circle" color="var(--color-bg)" size="48"/></button>
<button><Icon name="alert-circle" color="var(--color-bg)" size="48"/></button>
</button-group>

@ -0,0 +1 @@
ButtonGroup will bundle a strip of buttons together with one or more marked "active". It will correctly curve the corners of the first and last button, and can be run horizontally or vertically.

@ -0,0 +1,27 @@
<script>
import Calendar from "$/client/components/Calendar.svelte";
let message = "";
const format_date = (d) => d.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
const date_selected = (event) => {
message = `Date is: ${format_date(event.detail)}`;
}
const next_month = (event) => {
message = `Next! First of Month: ${format_date(event.detail.fom)}`;
}
const prev_month = (event) => {
message = `Previous! First of Month: ${format_date(event.detail.fom)}`;
}
</script>
<Calendar on:select={ date_selected } on:next={ next_month } on:previous={ prev_month } />
{#if message}
<h4>{ message }</h4>
{/if}

@ -0,0 +1 @@
This is a _very_ basic implementation of a calendar that you might use in a fancy date picker. It does _not_ take into account the myriad of different date systems in the world, but it wouldn't be hard to change it to meet your needs. Consider it a starting point for your own Calendar if you need one.

@ -0,0 +1,37 @@
<script>
import Icon from "$/client/components/Icon.svelte";
</script>
<style>
callout {
margin: 1rem;
width: 50%;
}
</style>
<callout>
<span>This is a normal callout example.</span>
</callout>
<callout class="success">
<span>This is a success callout example.</span>
</callout>
<callout class="warning">
<span>This is a warning callout example.</span>
</callout>
<callout class="alert">
<span>This is an alert callout example.</span>
</callout>
<callout class="info">
<span>This is an info callout example.</span>
</callout>
<h2>Callout with Badges</h2>
<callout class="alert">
<span>This is an alert callout example with a badge.</span>
<badge class="top-right"><Icon name="alert-triangle" width="2px" size="18px" /></badge>
</callout>

@ -0,0 +1,8 @@
The Callout is simply a way to focus the user on a piece of text they should see. There's different levels of callout you can use:
* `class="alert"` -- An alert style you can use to tell of impending doom. Change the variable `--red` or `--color-error` in the CSS to change it's color from the default monochrome.
* `class="success"` -- For success messages. Change `--color-good` or `--green` to change it in the CSS.
* `class="warning"` -- For warning messages. Change `--color-warning` or `--orange`.
* `class="info"` -- For info messages. Change `--color-info` or `--yellow`.
When I say "change color" I mean go into the file `static/monochrome.css` or `static/color.css` to change how it looks.

@ -0,0 +1,47 @@
<script>
import Icon from '$/client/components/Icon.svelte';
import PlaceHolder from "$/client/components/PlaceHolder.svelte";
</script>
<style>
content {
display: flex;
flex-direction: column;
flex: flex-shrink;
flex-grow: 1;
justify-content: center;
}
button#ok {
background-color: var(--color-bg-secondary);
}
card {
width: 600px;
}
</style>
<content>
<card>
<top>
<PlaceHolder width={ 16 * 60 } height={ 9 * 60 } />
</top>
<middle>
<h4>
Card Example
</h4>
<p>Lorem ipsum dolor sit amet, consectetur
adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</middle>
<bottom>
<button-group>
<button id="ok"><Icon name="check-circle" color="var(--color-accent)" size="48" /></button>
<button><Icon name="x-circle" color="var(--color-bg)" size="48"/></button>
</button-group>
</bottom>
</card>
<p>This is another example of simply using CSS to implement basic components. Rather than implement Svelte compoentns with slots it is sometimes <b>much</b> easier to CSS and plain HTML. To see how to use cards look at <b>client/bando/demos/Cards.svelte</b>. I also show you how to restyle the buttons if you want them to be larger and square like you find in many panel examples.</p>
</content>

@ -0,0 +1 @@
Cards are used everywhere on the web, many times without you even knowing it. They allow you to show an image or similar media, some text, then some buttons. A variant of `Cards` is `Tiles` that are a horizontal layout of the same information. I generally use `Card` when I want a large display of the image or video, and `Tile` when I want a small display with a thumbnail or icon.

@ -0,0 +1,25 @@
<script>
import Carousel from "$/client/components/Carousel.svelte";
import IconImage from "$/client/components/IconImage.svelte";
import { log } from "$/client/logging.js";
export let panels = [
{caption: "Anchors Away", active: true, component: IconImage,
props: { name: "anchor", hue: "red", background_hue: "green" } },
{caption: "Money Money Money", active: false, component: IconImage,
props: { name: "dollar-sign", hue: "blue", background_hue: "purple", pattern: "triangles-sm" } },
{caption: "We Should Talk", active: false, component: IconImage,
props: { name: "message-circle", hue: "green", background_hue: "orange", pattern: "lines-sm" } },
];
let selected = panels[0];
const tab_select = (event) => {
// this is where you can grab selection information
log.debug("SELECTED TAB", event.detail);
}
</script>
<Carousel panels={ panels } on:select={ tab_select } bind:selected />
<h3>You're looking at the {selected.caption} panel which has a {selected.props.name} icon.</h3>

@ -0,0 +1 @@
The `Carousel` is found mostly on landing pages and even then it's considered not very useful. The problem with this UI component is it makes it difficult to navigate the images, and if it's automated then users will have a hard time accessing something you show them when it passes by. If you're using it I suggest using it only on the landing page, and then if it auto-rotates have it stop when users touch it for the first time.

@ -0,0 +1,14 @@
<script>
import Chat from "$/client/components/Chat.svelte";
import Markdown from "$/client/components/Markdown.svelte";
</script>
<style>
div {
max-width: 600px;
}
</style>
<div>
<Chat />
</div>

@ -0,0 +1 @@
The `Chat` is a small mini chat you might find on live streaming websites. It's not full featured but the client and backend are simple enough that you can change them to do what you want. To make it work you may need to look at `socket/chat.js` and make sure that's running correctly. If you don't use `Chat` on your site then consider removing `socket/chat.js` and edit `client/stores.js` to remove all [socket-io](https://socket.io) references. This will save you about 50k in the downloaded size of your app.

@ -0,0 +1,53 @@
<script>
import Code from "$/client/components/Code.svelte";
import Spinner from "$/client/components/Spinner.svelte";
import CodeFormatter from "$/client/components/CodeFormatter.svelte";
let code = "";
let test_code = "/test.js";
let message = "";
let timer;
const load_code = async () => {
let res = await fetch(test_code);
code = res.status == 200 ? await res.text() : `Error getting /test.js: ${res.status}`;
}
const code_copied = () => {
if(timer) clearTimeout(timer);
message = "You copied it!";
timer = setTimeout(() => message = "", 2000);
}
let code_promise = load_code();
</script>
{#await code_promise}
<Spinner />
{:then