Create icon library

This commit is contained in:
Kacper Donat 2020-02-09 21:56:21 +01:00
parent 4d98a8607b
commit ee3dc50914
19 changed files with 182 additions and 74 deletions

View File

@ -3,7 +3,7 @@
<departure :departure="departure" :key="departure.key" v-for="departure in departures"/>
</ul>
<div class="alert alert-info" v-if="stops.length === 0">
<fa :icon="['fal', 'info-circle']"/>
<ui-icon icon="info"/>
Wybierz przystanki korzystając z wyszukiwarki poniżej, aby zobaczyć listę odjazdów.
</div>
</div>

View File

@ -6,11 +6,10 @@
</div>
<div class="departure__time">
<fa-layers v-if="!departure.estimated" class="mr-1">
<template v-if="!departure.estimated">
<tooltip placement="top-end">Czas rozkładowy, nieuwzględniający aktualnej sytuacji komunikacyjnej.</tooltip>
<fa :icon="['far', 'clock']"/>
<fa :icon="['fas', 'exclamation-triangle']" transform="shrink-5 down-4 right-6"/>
</fa-layers>
<ui-icon icon="departure-warning"/>
</template>
<span :class="[ 'departure__time', 'departure__time--delayed']" v-if="timeDiffers">
{{ departure.scheduled.format('HH:mm') }}
@ -24,13 +23,13 @@
</div>
<div class="departure__stop">
<fa :icon="['fal', 'sign']" fixed-width class="mr-1 flex-shrink-0"/>
<ui-icon icon="stop" fixed-width class="mr-1 flex-shrink-0"/>
<stop :stop="departure.stop"/>
<div class="stop__actions flex-space-left">
<button class="btn btn-action" @click="showTrip = !showTrip">
<tooltip>pokaż/ukryj trasę</tooltip>
<fa :icon="['far', 'code-commit']" />
<ui-icon icon="track" />
</button>
</div>
</div>
@ -38,7 +37,7 @@
<fold :visible="showTrip">
<trip :schedule="trip.schedule" :current="departure.stop" v-if="trip" :class="[ `trip--${departure.line.type}` ]"/>
<div v-else class="text-center">
<fa icon="spinner-third" pulse></fa>
<ui-icon icon="spinner"/>
</div>
</fold>
</li>

View File

@ -3,7 +3,7 @@
<li v-for="favourite in favourites" class="favourite">
<button @click="choose(favourite)" class="favourite__entry">
<div class="icon">
<fa :icon="['fal', 'star']"/>
<ui-icon icon="favourite"/>
</div>
<div class="overflow-hidden">
<span class="text flex-grow-1">{{ favourite.name }}</span>
@ -14,12 +14,12 @@
</button>
<button class="btn btn-action" @click="remove(favourite)">
<tooltip placement="left">usuń</tooltip>
<fa :icon="['fal', 'trash-alt']"></fa>
<ui-icon icon="delete"/>
</button>
</li>
</ul>
<div class="alert alert-info" v-else>
<fa :icon="['fal', 'info-circle']"></fa>
<ui-icon icon="info"/>
Brak zapisanych zespołów przystanków
</div>
</div>

View File

@ -6,7 +6,7 @@
:class="{ 'is-invalid': errors.name.length > 0 }" id="favourite_add_name"
v-model="name" v-autofocus/>
<button class="btn btn-sm btn-dark" type="submit">
<fa :icon="['fal', 'check']"></fa>
<ui-icon class="add" />
</button>
<div v-if="errors.name.length > 0" class="invalid-feedback">
<p v-for="error in errors.name">{{ error }}</p>

View File

@ -6,7 +6,7 @@
</div>
<div v-if="state === 'fetching'" class="text-center p-4">
<fa icon="spinner-third" pulse/>
<ui-icon icon="spinner"/>
</div>
<div class="finder__stops" v-else-if="filter.length > 2 && Object.keys(filtered).length > 0">
<div class="stop-group" v-for="(group, name) in filtered">
@ -16,7 +16,7 @@
<div class="actions flex-space-left">
<button class="btn btn-action" @click="select(group)">
<tooltip>wybierz wszystkie</tooltip>
<fa :icon="['fal', 'check-double']"></fa>
<ui-icon icon="add-all"/>
</button>
</div>
</div>
@ -26,7 +26,7 @@
<template v-slot:primary-action>
<button @click="select(stop, $event)" class="btn btn-action">
<tooltip>dodaj przystanek</tooltip>
<fa :icon="['fal', 'check']" />
<ui-icon icon="add" />
</button>
</template>
</picker-stop>
@ -35,7 +35,7 @@
</div>
</div>
<div class="alert alert-warning" v-else-if="filter.length > 2">
<fa :icon="['far', 'exclamation-triangle']"></fa>
<ui-icon icon="warning"/>
Nie znaleziono więcej przystanków, spełniających te kryteria.
</div>
</div>

View File

@ -2,14 +2,14 @@
<span class="flex align-items-stretch">
<slot name="icon" v-if="!simple">
<span class="icon">
<fa :icon="['fac', line.type]" fixed-width/>
<ui-icon :icon="`line-${line.type}`" fixed-width/>
</span>
</slot>
<slot name="badge">
<span class="badge badge-dark flex">
<fa :icon="['fal', 'moon']" fixed-width v-if="line.night && !simple"/>
<ui-icon icon="night" fixed-width v-if="line.night && !simple"/>
{{ line.symbol }}
<fa :icon="['fas', 'walking']" v-if="line.fast"/>
<ui-icon icon="fast" v-if="line.fast"/>
</span>
</slot>
</span>

View File

@ -1,6 +1,6 @@
<ul class="messages list-unstyled">
<li class="message alert" :class="`alert-${type(message)}`" v-for="message in messages">
<fa :icon="icon(message)" fixed-width></fa>
<ui-icon :icon="`message-${type(message)}`" fixed-width />
{{ message.message }}
<div class="message__info">
@ -12,4 +12,4 @@
</small>
</div>
</li>
</ul>
</ul>

View File

@ -21,11 +21,11 @@
<slot name="actions">
<button class="btn btn-action" ref="action-info" @click="details = !details">
<tooltip>dodatkowe informacje</tooltip>
<fa :icon="['fal', details ? 'chevron-circle-up' : 'info-circle']"/>
<ui-icon :icon="details ? 'info-hide' : 'info'"/>
</button>
<button class="btn btn-action" ref="action-map" v-hover:map>
<fa :icon="['fal', 'map-marker-alt']"/>
<ui-icon icon="map"/>
</button>
</slot>
</div>

View File

@ -32,5 +32,5 @@
</section>
</div>
<div v-else class="text-center">
<fa icon="spinner-third" pulse></fa>
</div>
<ui-icon icon="spinner"/>
</div>

View File

@ -0,0 +1,4 @@
<fa v-bind="definition" v-if="type === 'simple'"/>
<fa-layers v-else-if="type === 'stacked'">
<fa :icon="props.icon" v-bind="props" v-for="(props, index) in definition.icons" :key="index"/>
</fa-layers>

View File

@ -6,10 +6,6 @@ import "leaflet/dist/leaflet.css";
import Popper from 'popper.js';
import * as $ from "jquery";
window['$'] = window['jQuery'] = $;
window['Popper'] = Popper;
// dependencies
import Vue from "vue";
import Vuex from 'vuex';
@ -21,6 +17,9 @@ import { Workbox } from "workbox-window";
import { migrate } from "./store/migrations";
import { Component } from "vue-property-decorator";
window['$'] = window['jQuery'] = $;
window['Popper'] = Popper;
Vue.use(Vuex);
Vue.use(PortalVue);
Vue.use(VueDragscroll);
@ -43,7 +42,6 @@ Component.registerHooks(['removed']);
const [ components, { default: store } ] = await Promise.all([
import('./components'),
import('./store'),
import('./font-awesome'),
import('./filters'),
import('bootstrap'),
] as const);

View File

@ -1,7 +1,6 @@
import Vue from 'vue';
import { Component } from "vue-property-decorator";
import { Message } from "../model/message";
import { faInfoCircle, faExclamationTriangle, faQuestionCircle } from "@fortawesome/pro-light-svg-icons";
import { namespace } from 'vuex-class';
import store from '../store'
@ -13,9 +12,6 @@ export class MessagesComponent extends Vue {
public icon(message: Message) {
switch (message.type) {
case "breakdown": return faExclamationTriangle;
case "info": return faInfoCircle;
case "unknown": return faQuestionCircle;
}
}
@ -28,4 +24,4 @@ export class MessagesComponent extends Vue {
}
}
Vue.component('Messages', MessagesComponent);
Vue.component('Messages', MessagesComponent);

View File

@ -0,0 +1,127 @@
import Vue from 'vue'
import { Component, Prop } from 'vue-property-decorator'
import { IconDefinition, library } from "@fortawesome/fontawesome-svg-core"
import { Dictionary } from "../../utils";
import {
faBullhorn,
faCheck,
faCheckDouble,
faChevronCircleUp,
faChevronDown,
faChevronUp,
faClock,
faCog,
faExclamationTriangle,
faInfoCircle,
faMapMarkerAlt,
faMoon,
faQuestionCircle,
faSearch,
faSign,
faStar,
faSync,
faTimes,
faTrashAlt
} from "@fortawesome/pro-light-svg-icons";
import { faClock as faClockBold, faCodeCommit, faSpinnerThird } from "@fortawesome/pro-regular-svg-icons";
import { faExclamationTriangle as faSolidExclamationTriangle, faWalking } from "@fortawesome/pro-solid-svg-icons";
import { fac } from "../../icons";
import { FontAwesomeIcon, FontAwesomeLayers, FontAwesomeLayersText } from "@fortawesome/vue-fontawesome";
type IconDescription = { icon: IconDefinition, [prop: string]: any }
type SimpleIcon = {
type: 'simple',
} & IconDescription;
type StackedIcon = {
type: 'stacked',
icons: IconDescription[],
}
export type Icon = SimpleIcon | StackedIcon;
const simple = (icon: IconDefinition, props: any = {}): SimpleIcon => ({
icon, ...props, type: "simple"
});
const stack = (icons: IconDescription[]): StackedIcon => ({type: "stacked", icons});
const lineTypeIcons = Object
.values(fac)
.map<[string, Icon]>(icon => [ `line-${icon.iconName}`, simple(icon) ])
.reduce((acc, [icon, definition]) => ({ ...acc, [icon]: definition}), {})
const messageTypeIcons: Dictionary<Icon> = {
'message-breakdown': simple(faExclamationTriangle),
'message-info': simple(faInfoCircle),
'message-unknown': simple(faQuestionCircle),
};
const definitions: Dictionary<Icon> = {
'favourite': simple(faStar),
'add': simple(faCheck),
'add-all': simple(faCheckDouble),
'remove-stop': simple(faTimes),
'delete': simple(faTrashAlt),
'messages': simple(faBullhorn),
'timetable': simple(faClock),
'settings': simple(faCog),
'refresh': simple(faSync),
'chevron-down': simple(faChevronDown),
'chevron-up': simple(faChevronUp),
'search': simple(faSearch),
'info': simple(faInfoCircle),
'warning': simple(faExclamationTriangle),
'night': simple(faMoon),
'fast': simple(faWalking),
'track': simple(faCodeCommit),
'info-hide': simple(faChevronCircleUp),
'map': simple(faMapMarkerAlt),
'stop': simple(faSign),
'spinner': simple(faSpinnerThird, { spin: true }),
'departure-warning': stack([
{icon: faClockBold},
{icon: faSolidExclamationTriangle, transform: "shrink-5 down-4 right-6"}
]),
...lineTypeIcons,
...messageTypeIcons,
};
const extractAllIcons = (icons: Icon[]) => icons.map(icon => {
switch (icon.type) {
case "simple":
return [icon.icon];
case "stacked":
return icon.icons.map(stacked => stacked.icon);
}
}).reduce((acc, cur) => [...acc, ...cur]);
library.add(...extractAllIcons(Object.values(definitions)));
@Component({
template: require('../../../components/ui/icon.html'),
components: {
fa: FontAwesomeIcon,
faLayers: FontAwesomeLayers,
faText: FontAwesomeLayersText,
}
})
export class UiIcon extends Vue {
@Prop({
type: [ String, Object ],
validator: value => typeof value === "object" || Object.keys(definitions).includes(value),
required: true,
})
icon: keyof typeof definitions;
get definition() {
return {...(typeof this.icon === "string" ? definitions[this.icon] : { icon: this.icon }), ...this.$attrs};
}
get type() {
return definitions[this.icon].type;
}
}
Vue.component('UiIcon', UiIcon);

View File

@ -1 +1,2 @@
export * from './switch';
export * from './icon';

View File

@ -3,7 +3,7 @@ import { Component, Prop } from 'vue-property-decorator'
import * as uuid from "uuid";
@Component({
template: require('@templates/ui/switch.html'),
template: require('../../../components/ui/switch.html'),
inheritAttrs: false
})
export class UiSwitch extends Vue {

View File

@ -1,16 +0,0 @@
import Vue from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { far } from "@fortawesome/pro-regular-svg-icons";
import { fas } from "@fortawesome/pro-solid-svg-icons";
import { fal } from "@fortawesome/pro-light-svg-icons";
import { fac } from "./icons";
import { FontAwesomeIcon, FontAwesomeLayers, FontAwesomeLayersText } from '@fortawesome/vue-fontawesome'
library.add(far, fas, fal, fac);
Vue.component('fa', FontAwesomeIcon);
Vue.component('fa-layers', FontAwesomeLayers);
Vue.component('fa-text', FontAwesomeLayersText);

View File

@ -8,30 +8,30 @@
<section class="section messages" v-show="messages.count > 0">
<header class="section__title flex">
<h2>
<fa :icon="['fal', 'bullhorn']" fixed-width class="mr-2"></fa>
<ui-icon icon="messages" fixed-width class="mr-2"></ui-icon>
Komunikaty <span class="ml-2 badge badge-pill badge-dark">{{ '{{ messages.count }}' }}</span>
</h2>
<button class="btn btn-action flex-space-left" ref="settings-messages" @click="visibility.messages = !visibility.messages">
<tooltip>ustawienia</tooltip>
<fa :icon="['fal', 'cog']" fixed-width></fa>
<ui-icon icon="settings" fixed-width></ui-icon>
</button>
<button class="btn btn-action" @click="updateMessages" ref="btn-messages-refresh">
<tooltip>odśwież</tooltip>
<fa :icon="['fal', 'sync']" :spin="messages.state === 'fetching'" fixed-width></fa>
<ui-icon icon="refresh" :spin="messages.state === 'fetching'" fixed-width></ui-icon>
</button>
<button class="btn btn-action" @click="sections.messages = !sections.messages">
<tooltip>
{{ '{{ ' }} sections.messages ? 'zwiń' : 'rozwiń' {{ '}}' }}
<span class="sr-only">sekcję komunikatów</span>
</tooltip>
<fa :icon="['fal', sections.messages ? 'chevron-up' : 'chevron-down']" fixed-width/>
<ui-icon :icon="sections.messages ? 'chevron-up' : 'chevron-down'" fixed-width></ui-icon>
</button>
<portal to="popups">
<popper reference="settings-messages" v-if="visibility.messages" arrow placement="left-start" @leave="visibility.messages = false">
<h3 class="popper__heading flex">
<label class="text" for="messages-auto-refresh">
<fa :icon="['far', 'sync']" fixed-width></fa>
<ui-icon icon="refresh" fixed-width></ui-icon>
autoodświeżanie
</label>
<ui-switch id="messages-auto-refresh" v-model="autorefresh.messages.active" class="flex-space-left"></ui-switch>
@ -59,23 +59,23 @@
<section class="section">
<header class="section__title flex">
<h2>
<fa :icon="['fal', 'clock']" fixed-width></fa>
<ui-icon icon="timetable" fixed-width></ui-icon>
<span class="text">Odjazdy</span>
</h2>
<button class="btn btn-action flex-space-left" ref="settings-departures" @click="visibility.departures = !visibility.departures">
<tooltip>ustawienia</tooltip>
<fa :icon="['fal', 'cog']" fixed-width></fa>
<ui-icon icon="settings" fixed-width></ui-icon>
</button>
<button class="btn btn-action" @click="updateDepartures({ stops })">
<tooltip>odśwież</tooltip>
<fa :icon="['fal', 'sync']" :spin="departures.state === 'fetching'" fixed-width></fa>
<ui-icon icon="refresh" :spin="departures.state === 'fetching'" fixed-width></ui-icon>
</button>
<portal to="popups">
<popper reference="settings-departures" v-if="visibility.departures" arrow placement="left-start" @leave="visibility.departures = false">
<h3 class="popper__heading flex">
<label class="text" for="departures-auto-refresh">
<fa :icon="['far', 'sync']" fixed-width></fa>
<ui-icon icon="refresh" fixed-width></ui-icon>
autoodświeżanie
</label>
<ui-switch id="departures-auto-refresh" v-model="autorefresh.departures.active" class="flex-space-left"></ui-switch>
@ -98,7 +98,7 @@
<departures :stops="stops"></departures>
{% if provider.attribution %}
<div class="attribution">
<fa :icon="['fal', 'info-circle']"></fa>
<ui-icon icon="info"></ui-icon>
Pochodzenie danych: {{ provider.attribution|raw }}
</div>
{% endif %}
@ -108,12 +108,12 @@
<section class="section picker" v-if="stops.length > 0">
<header class="section__title flex">
<h2>
<fa :icon="['fal', 'sign']" fixed-width></fa>
<ui-icon icon="stop" fixed-width></ui-icon>
<span class="text">Przystanki</span>
</h2>
<button class="btn btn-action flex-space-left" @click="clear">
<tooltip>usuń wszystkie</tooltip>
<fa :icon="['fal', 'trash-alt']" fixed-width></fa>
<ui-icon icon="delete" fixed-width></ui-icon>
</button>
</header>
@ -123,7 +123,7 @@
<template v-slot:primary-action>
<button @click="remove(stop)" class="btn btn-action">
<tooltip>usuń przystanek</tooltip>
<fa :icon="['fal', 'times']"></fa>
<ui-icon icon="remove-stop"></ui-icon>
</button>
</template>
</picker-stop>
@ -132,7 +132,7 @@
<div class="d-flex mt-2">
<button class="btn btn-action btn-sm flex-space-left" @click="visibility.save = true" ref="save">
<fa :icon="['fal', 'star']" fixed-width></fa>
<ui-icon icon="favourite" fixed-width></ui-icon>
zapisz jako...
</button>
</div>
@ -145,22 +145,22 @@
<header class="section__title flex">
<template v-if="visibility.picker === 'search'">
<h2 class="flex-grow-1">
<fa :icon="['fal', 'search']" fixed-width class="mr-1"></fa>
<ui-icon icon="search" fixed-width class="mr-1"></ui-icon>
Wybierz przystanki
</h2>
<button class="btn btn-action" @click="visibility.picker = 'favourites'">
<tooltip>Zapisane</tooltip>
<fa :icon="['fal', 'star']" fixed-witdth></fa>
<ui-icon icon="favourite" fixed-witdth></ui-icon>
</button>
</template>
<template v-else>
<h2 class="flex-grow-1">
<fa :icon="['fal', 'star']" fixed-width class="mr-1"></fa>
<ui-icon icon="favourite" fixed-width class="mr-1"></ui-icon>
Zapisane
</h2>
<button class="btn btn-action" @click="visibility.picker = 'search'">
<tooltip>Wybierz przystanki</tooltip>
<fa :icon="['fal', 'search']" fixed-witdth></fa>
<ui-icon icon="search" fixed-witdth></ui-icon>
</button>
</template>
</header>

View File

@ -2,7 +2,7 @@
{% block body %}
<div class="alert alert-primary">
<fa :icon="['fal', 'info-circle']"></fa>
<ui-icon icon="info-circle"></ui-icon>
Wybierz źródło danych
</div>
<ul class="list-underlined">
@ -14,4 +14,4 @@
</li>
{% endfor %}
</ul>
{% endblock %}
{% endblock %}

View File

@ -20,8 +20,7 @@ const config = {
extensions: ['.tsx', '.ts', '.js'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'mapbox-gl$': 'mapbox-gl/dist/mapbox-gl-unminified',
'@templates': path.resolve(__dirname, './resources/components/'),
'mapbox-gl$': 'mapbox-gl/dist/mapbox-gl-unminified'
}
},
module: {