Compare commits

...

No commits in common. "1.0.0" and "main" have entirely different histories.
1.0.0 ... main

8 changed files with 487 additions and 15 deletions

213
package-lock.json generated
View File

@ -1,9 +1,37 @@
{
"name": "@jdbernard/vue-common",
"version": "1.0.0",
"version": "1.0.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/helper-string-parser": {
"version": "7.19.4",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
"integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.19.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
"integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
"dev": true
},
"@babel/parser": {
"version": "7.21.1",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.1.tgz",
"integrity": "sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg=="
},
"@babel/types": {
"version": "7.21.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.0.tgz",
"integrity": "sha512-uR7NWq2VNFnDi7EYqiRz2Jv/VQIu38tu64Zy8TX2nQFQ6etJ9V/Rr2msW8BS132mum2rL645qpDrLtAJtVpuow==",
"dev": true,
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
"to-fast-properties": "^2.0.0"
}
},
"@jdbernard/logging": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@jdbernard/logging/-/logging-1.1.3.tgz",
@ -12,6 +40,105 @@
"axios": "^0.19.2"
}
},
"@vue/compiler-core": {
"version": "3.2.47",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz",
"integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==",
"requires": {
"@babel/parser": "^7.16.4",
"@vue/shared": "3.2.47",
"estree-walker": "^2.0.2",
"source-map": "^0.6.1"
}
},
"@vue/compiler-dom": {
"version": "3.2.47",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz",
"integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==",
"requires": {
"@vue/compiler-core": "3.2.47",
"@vue/shared": "3.2.47"
}
},
"@vue/compiler-sfc": {
"version": "3.2.47",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz",
"integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==",
"requires": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.47",
"@vue/compiler-dom": "3.2.47",
"@vue/compiler-ssr": "3.2.47",
"@vue/reactivity-transform": "3.2.47",
"@vue/shared": "3.2.47",
"estree-walker": "^2.0.2",
"magic-string": "^0.25.7",
"postcss": "^8.1.10",
"source-map": "^0.6.1"
}
},
"@vue/compiler-ssr": {
"version": "3.2.47",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz",
"integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==",
"requires": {
"@vue/compiler-dom": "3.2.47",
"@vue/shared": "3.2.47"
}
},
"@vue/reactivity": {
"version": "3.2.47",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz",
"integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==",
"requires": {
"@vue/shared": "3.2.47"
}
},
"@vue/reactivity-transform": {
"version": "3.2.47",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz",
"integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==",
"requires": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.47",
"@vue/shared": "3.2.47",
"estree-walker": "^2.0.2",
"magic-string": "^0.25.7"
}
},
"@vue/runtime-core": {
"version": "3.2.47",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz",
"integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==",
"requires": {
"@vue/reactivity": "3.2.47",
"@vue/shared": "3.2.47"
}
},
"@vue/runtime-dom": {
"version": "3.2.47",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz",
"integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==",
"requires": {
"@vue/runtime-core": "3.2.47",
"@vue/shared": "3.2.47",
"csstype": "^2.6.8"
}
},
"@vue/server-renderer": {
"version": "3.2.47",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.47.tgz",
"integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==",
"requires": {
"@vue/compiler-ssr": "3.2.47",
"@vue/shared": "3.2.47"
}
},
"@vue/shared": {
"version": "3.2.47",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz",
"integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ=="
},
"axios": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
@ -20,6 +147,11 @@
"follow-redirects": "1.5.10"
}
},
"csstype": {
"version": "2.6.21",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
"integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
@ -28,6 +160,11 @@
"ms": "2.0.0"
}
},
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
@ -36,16 +173,82 @@
"debug": "=3.1.0"
}
},
"magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
"integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
"requires": {
"sourcemap-codec": "^1.4.8"
}
},
"mitt": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz",
"integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"typescript": {
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"postcss": {
"version": "8.4.21",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
},
"sourcemap-codec": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
},
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"dev": true
},
"typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true
},
"vue": {
"version": "3.2.47",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz",
"integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==",
"requires": {
"@vue/compiler-dom": "3.2.47",
"@vue/compiler-sfc": "3.2.47",
"@vue/runtime-dom": "3.2.47",
"@vue/server-renderer": "3.2.47",
"@vue/shared": "3.2.47"
}
}
}
}

View File

@ -1,15 +1,13 @@
{
"name": "@jdbernard/vue-common",
"version": "1.0.0",
"version": "1.0.7",
"description": "Extra stuff I always use when building Vue applications.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"/dist"
],
"main": "index.js",
"types": "index.d.ts",
"scripts": {
"build": "tsc --build tsconfig.json",
"test": "echo \"Error: no test specified\" && exit 1"
"build": "tsc --build tsconfig.json && cp -r src/components dist/.",
"test": "echo \"Error: no test specified\" && exit 1",
"do-publish": "npm run build && cp package.json dist/. && (cd dist && npm publish)"
},
"repository": {
"type": "git",
@ -24,9 +22,12 @@
"author": "Jonathan Bernard <jonathan@jdbernard.com>",
"license": "MIT",
"devDependencies": {
"typescript": "^4.5.5"
"@babel/types": "^7.21.0",
"typescript": "^4.9.5"
},
"dependencies": {
"@jdbernard/logging": "^1.1.3"
"@jdbernard/logging": "^1.1.3",
"mitt": "^3.0.0",
"vue": "^3.2.47"
}
}

21
src/components/FaIcon.vue Normal file
View File

@ -0,0 +1,21 @@
<template>
<span class="fa-icon"><FontAwesomeIcon :icon="icon" :spin="spin" /> </span>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
export default defineComponent({
name: 'FaIcon',
components: { FontAwesomeIcon },
props: { icon: String, spin: Boolean },
});
</script>
<style scoped lang="scss">
.fa-icon {
display: inline-block;
height: 1em;
text-align: center;
width: 1.2em;
}
</style>

View File

@ -0,0 +1,33 @@
<template>
<button class="fa-icon"><FontAwesomeIcon :icon="icon" :spin="spin" /></button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
export default defineComponent({
name: 'FaIcon',
components: { FontAwesomeIcon },
props: { icon: String, spin: Boolean },
});
</script>
<style scoped lang="scss">
.fa-icon {
display: flex;
align-items: center;
justify-content: center;
border-radius: 1em;
height: 2em;
width: 2em;
}
.fa-icon.inline {
background: none;
margin: 0;
&:hover {
background: var(--color-accent-fg);
}
}
</style>

180
src/components/Toaster.vue Normal file
View File

@ -0,0 +1,180 @@
<template>
<transition-group name="toast-messages" id="toast-container" tag="ul">
<li v-for="msg in messages" :key="msg.id" :class="msg.type">
<div class="icon" @click="dismissMessage(msg.id)">
<fa-icon :icon="iconForMsg(msg)"></fa-icon>
</div>
<div class="message">
{{ msg.message }}
<div v-if="msg.detail" class="detail">{{ msg.detail }}</div>
</div>
</li>
</transition-group>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref, Ref } from 'vue';
import { ToastMessage, toastBus } from '../toast.service';
interface ToastMessageWithTypeAndId extends ToastMessage {
type: 'info' | 'error' | 'success';
id: number;
}
export default defineComponent({
name: 'ToasterComponent',
setup: function ToasterComponent() {
const lastId = ref(0);
const messages: Ref<ToastMessageWithTypeAndId[]> = ref([]);
function dismissMessage(msgId: number) {
messages.value = messages.value.filter((m) => m.id !== msgId);
}
function handleToast(
type: 'info' | 'error' | 'success',
m: ToastMessage
): void {
const msgWithId = { ...m, type, id: lastId.value++ };
messages.value.push(msgWithId);
if (m.duration != 'manual') {
setTimeout(() => {
dismissMessage(msgWithId.id);
}, m.duration ?? 10 * 1000); //default 10 seconds
}
}
onMounted(() => {
toastBus.on('info', (m) => handleToast('info', m));
toastBus.on('success', (m) => handleToast('success', m));
toastBus.on('error', (m) => handleToast('error', m));
});
onUnmounted(() => {
toastBus.all.clear();
});
function iconForMsg(m: ToastMessageWithTypeAndId): string {
if (m.icon) return m.icon;
switch (m.type) {
default:
case 'info':
return 'info-circle';
case 'error':
return 'exclamation-triangle';
case 'success':
return 'check-circle';
}
}
return { dismissMessage, iconForMsg, messages };
},
});
</script>
<style scoped lang="scss" src="./Toaster.scss">
#toast-container {
list-style: none;
display: flex;
flex-direction: column;
li {
transition: opacity 1s, transform 1s;
}
.toast-messages-enter, .toast-messages-leave-to {
opacity: 0;
transform: translateY(1em);
}
}
//@include forSize(notMobile) {
#toast-container {
position: fixed;
top: 2em;
right: 2em;
font-weight: 400;
li {
display: flex;
flex-direction: row;
align-items: stretch;
background-color: white;
background-color: var(--color-bg);
border-radius: 4px;
border: solid 2px;
box-shadow: 0 4px 4px rgba(0, 0, 0, 0.2);
box-shadow: 0 4px 4px var(--color-shadow);
margin-bottom: 1em;
opacity: 0.95;
width: 23em;
.icon {
cursor: pointer;
display: flex;
align-items: center;
font-size: 1.5em;
justify-content: center;
min-width: 2em;
min-height: 2em;
.fa-icon {
color: white;
color: var(--color-bg);
height: unset;
}
}
.message {
display: flex;
align-items: center;
flex-direction: column;
align-items: flex-start;
padding: 0.5em;
flex: 1 1;
.detail {
font-size: 16px;
font-size: var(--font-size-small);
}
}
&.info {
border-color: #787AC9;
border-color: var(--color-accent-fg);
color: #333;
color: var(--color-fg);
.icon {
background-color: #787AC9;
background-color: var(--color-accent-fg);
}
}
&.success {
border-color: #467A43;
border-color: var(--color-success-fg);
color: #333;
color: var(--color-fg);
.icon {
background-color: #467A43;
background-color: var(--color-success-fg);
}
}
&.error {
border-color: #AC190E;
border-color: var(--color-error-fg);
color: #333;
color: var(--color-fg);
.icon {
background-color: #AC190E;
background-color: var(--color-error-fg);
}
}
}
}
//}
</style>

View File

@ -1,3 +1,4 @@
export * from './state';
export * from './util';
export * from './sorting';
export { default as defaultDependencyInjector, DependencyInjector } from './injector';

17
src/injector.ts Normal file
View File

@ -0,0 +1,17 @@
import { InjectionKey } from 'vue';
export class DependencyInjector {
// TODO: can we type this better?
// eslint-disable-next-line
private deps = new Map<InjectionKey<any>, any>();
public provide<T>(key: InjectionKey<T>, dep: T): void {
this.deps.set(key, dep);
}
public inject<T>(key: InjectionKey<T>): T | undefined {
return this.deps.get(key) as T;
}
}
export default new DependencyInjector();

16
src/toast.service.ts Normal file
View File

@ -0,0 +1,16 @@
import mitt from 'mitt';
export interface ToastMessage {
detail?: string;
duration?: number | 'manual';
icon?: string;
message: string;
}
type ToastEvents = {
info: ToastMessage;
error: ToastMessage;
success: ToastMessage;
};
export const toastBus = mitt<ToastEvents>();