fix some memory leaks

This commit is contained in:
Kacper Donat 2018-10-03 21:10:11 +02:00
parent c3570cb38e
commit 12fb18d902
12 changed files with 260 additions and 71 deletions

View File

@ -20,7 +20,7 @@
<stop-details :stop="stop"></stop-details>
</fold>
<popper reference="action-map" :visible="map" arrow class="popper--no-padding" style="width: 500px;">
<popper reference="action-map" :visible="map" arrow class="popper--no-padding" style="width: 500px;" placement="right-start">
<div style="height: 300px">
<l-map :center="stop.location" :zoom=17 :options="{ zoomControl: false, dragging: false }">
<l-tile-layer url="//{s}.tile.osm.org/{z}/{x}/{y}.png" attribution='&copy; <a href="//osm.org/copyright">OpenStreetMap</a> contributors'></l-tile-layer>

View File

@ -11,7 +11,7 @@ window['Popper'] = Popper;
// dependencies
import Vue from "vue";
import Vuex, { mapActions, mapState, Store } from 'vuex';
import Vuex, { mapActions, mapMutations, mapState, Store } from 'vuex';
Vue.use(Vuex);
@ -25,12 +25,13 @@ Vue.use(Vuex);
import('bootstrap'),
]);
store.dispatch('messages/update');
// here goes "public" API
window['czydojade'] = {
window['czydojade'] = Object.assign({}, window['czydojade'], {
components
};
});
store.dispatch('messages/update');
store.dispatch('load', window['czydojade'].state);
let intervals = { messages: null, departures: null };
@ -38,7 +39,6 @@ Vue.use(Vuex);
el: '#app',
store: store,
data: {
stops: [],
sections: {
messages: true
},
@ -70,6 +70,14 @@ Vue.use(Vuex);
return {
state: this.$store.state.departures.state
};
},
stops: {
get(this: Vue) {
return this.$store.state.stops;
},
set(this: Vue, value) {
this.$store.commit('updateStops', value);
}
}
},
watch: {
@ -104,7 +112,13 @@ Vue.use(Vuex);
...mapActions({
updateMessages: 'messages/update',
updateDepartures: 'departures/update'
})
}),
...mapMutations({
updateStops: 'updateStops'
}),
save(this: Vue) {
this.$store.dispatch('save').then(x => console.log(x));
}
},
mounted() {
this.$el.classList.remove('not-ready');

View File

@ -68,7 +68,7 @@ export class PopperComponent extends Vue {
window.dispatchEvent(new Event('resize'));
}
destroyed() {
beforeDestroy() {
this._popper.destroy();
}
}
@ -94,7 +94,7 @@ export class FoldComponent extends Vue {
});
}
destroyed() {
beforeDestroy() {
this.observer.disconnect();
}

View File

@ -4,55 +4,74 @@ import { condition } from "./decorators";
Vue.filter('signed', signed);
Vue.directive('hover', (el, binding, node) => {
const update = (hovered: boolean, e: Event) => {
if (typeof binding.value === 'function') {
binding.value(hovered, e);
}
if (typeof binding.value === 'boolean') {
set(node.context, binding.expression, hovered);
}
if (typeof binding.arg !== 'undefined') {
set(node.context, binding.arg, hovered);
}
};
const activate = event => update(true, event);
const deactivate = event => update(false, event);
el.addEventListener('mouseenter', activate);
el.addEventListener('click', activate);
el.addEventListener('keydown', condition.decorate(deactivate, e => e.keyCode == 27));
el.addEventListener('mouseleave', deactivate);
el.addEventListener('focusout', deactivate);
});
Vue.directive('responsive', (el, binding) => {
const breakpoints = typeof binding.value === 'object' ? binding.value : {
'xs': 0,
'sm': 576,
'md': 768,
'lg': 1024,
'xl': 1200,
};
const resize = () => {
const width = el.scrollWidth;
el.classList.remove(...Object.keys(breakpoints).map(breakpoint => `size-${breakpoint}`));
for (let [ breakpoint, size ] of Object.entries(breakpoints)) {
if (width < size) {
break;
Vue.directive('hover', {
bind(el, binding, node) {
const update = (hovered: boolean, e: Event) => {
if (typeof binding.value === 'function') {
binding.value(hovered, e);
}
el.classList.add(`size-${breakpoint}`);
}
};
if (typeof binding.value === 'boolean') {
set(node.context, binding.expression, hovered);
}
resize();
if (!binding.modifiers['once']) {
window.addEventListener('resize', resize);
if (typeof binding.arg !== 'undefined') {
set(node.context, binding.arg, hovered);
}
};
const activate = event => update(true, event);
const deactivate = event => update(false, event);
const keyboard = condition.decorate(deactivate, e => e.keyCode == 27);
binding['events'] = { activate, deactivate, keyboard };
el.addEventListener('mouseenter', activate);
el.addEventListener('click', activate);
el.addEventListener('keydown', keyboard);
el.addEventListener('mouseleave', deactivate);
el.addEventListener('focusout', deactivate);
},
unbind(el, binding) {
const { activate, deactivate, keyboard } = binding['events'];
el.removeEventListener('mouseenter', activate);
el.removeEventListener('click', activate);
el.removeEventListener('keydown', keyboard);
el.removeEventListener('mouseleave', deactivate);
el.removeEventListener('focusout', deactivate);
}
});
Vue.directive('responsive', {
inserted(el, binding) {
const breakpoints = typeof binding.value === 'object' ? binding.value : {
'xs': 0,
'sm': 576,
'md': 768,
'lg': 1024,
'xl': 1200,
};
const resize = binding['resize'] = () => {
const width = el.scrollWidth;
el.classList.remove(...Object.keys(breakpoints).map(breakpoint => `size-${breakpoint}`));
for (let [ breakpoint, size ] of Object.entries(breakpoints)) {
if (width < size) {
break;
}
el.classList.add(`size-${breakpoint}`);
}
};
resize();
if (!binding.modifiers['once']) {
window.addEventListener('resize', resize);
}
},
unbind(el, binding) {
window.removeEventListener('resize', binding['resize']);
}
});

View File

@ -11,4 +11,4 @@ export type StopGroup = Stop[];
export type StopGroups = {
[name: string]: StopGroup;
}
}

View File

@ -2,7 +2,9 @@ import Vuex from 'vuex';
import messages from './messages';
import departures from './departures';
import { state, mutations, actions } from "./root";
export default new Vuex.Store({
state, mutations, actions,
modules: { messages, departures }
})

View File

@ -1,9 +1,36 @@
import { Module } from "vuex";
import { Stop } from "../model";
import { ActionTree, MutationTree } from "vuex";
import urls from "../urls";
const state = { };
export interface RootState {
stops: Stop[],
}
export type RootState = typeof state;
export interface SavedState {
version: 1,
stops: string[],
}
export default <Module<RootState, unknown>>{
state
}
export const state: RootState = {
stops: []
};
export const mutations: MutationTree<RootState> = {
updateStops: (state, stops) => state.stops = stops,
};
export const actions: ActionTree<RootState, undefined> = {
async load({ commit }, { stops }: SavedState) {
if (stops.length > 0) {
const response = await fetch(urls.prepare(urls.stops.all, { id: stops }));
if (response.ok) {
commit('updateStops', await response.json());
}
}
},
save: async ({ state }): Promise<SavedState> => ({
version: 1,
stops: state.stops.map(stop => stop.id)
})
};

View File

@ -0,0 +1,56 @@
<?php
namespace App\Provider\Dummy;
use App\Model\Departure;
use App\Model\Line;
use App\Model\Stop;
use App\Model\Vehicle;
use App\Provider\DepartureRepository;
use App\Service\Proxy\ReferenceFactory;
use Carbon\Carbon;
use Tightenco\Collect\Support\Collection;
class DummyDepartureRepository implements DepartureRepository
{
private $reference;
/**
* DummyDepartureProviderRepository constructor.
*
* @param $reference
*/
public function __construct(ReferenceFactory $reference)
{
$this->reference = $reference;
}
public function getForStop(Stop $stop): Collection
{
return collect([
[ 1, Line::TYPE_TRAM, 'lorem ipsum', 2137 ],
[ 1, Line::TYPE_TRAM, 'lorem ipsum', 2137 ],
[ 1, Line::TYPE_TRAM, 'lorem ipsum', 2137 ],
[ 1, Line::TYPE_TRAM, 'lorem ipsum', 2137 ],
[ 1, Line::TYPE_TRAM, 'lorem ipsum', 2137 ],
[ 1, Line::TYPE_TRAM, 'lorem ipsum', 2137 ],
[ 1, Line::TYPE_TRAM, 'lorem ipsum', 2137 ],
[ 1, Line::TYPE_TRAM, 'lorem ipsum', 2137 ],
[ 1, Line::TYPE_TRAM, 'lorem ipsum', 2137 ],
[ 1, Line::TYPE_TRAM, 'lorem ipsum', 2137 ],
])->map(function ($departure) use ($stop) {
list($symbol, $type, $display, $vehicle) = $departure;
$scheduled = new Carbon();
$estimated = (clone $scheduled)->addSeconds(40);
return Departure::createFromArray([
'scheduled' => $scheduled,
'estimated' => $estimated,
'stop' => $stop,
'display' => $display,
'vehicle' => $this->reference->get(Vehicle::class, $vehicle),
'line' => Line::createFromArray(['symbol' => $symbol, 'type' => $type]),
]);
});
}
}

View File

@ -12,9 +12,23 @@ use App\Provider\TrackRepository;
class DummyProvider implements Provider
{
private $departures;
private $stops;
/**
* DummyProvider constructor.
*
* @param $departures
*/
public function __construct(DummyDepartureRepository $departures, DummyStopRepository $stops)
{
$this->departures = $departures;
$this->stops = $stops;
}
public function getDepartureRepository(): DepartureRepository
{
throw new NotSupportedException();
return $this->departures;
}
public function getLineRepository(): LineRepository
@ -24,7 +38,7 @@ class DummyProvider implements Provider
public function getStopRepository(): StopRepository
{
throw new NotSupportedException();
return $this->stops;
}
public function getMessageRepository(): MessageRepository

View File

@ -0,0 +1,49 @@
<?php
namespace App\Provider\Dummy;
use App\Model\Stop;
use App\Provider\StopRepository;
use App\Service\Proxy\ReferenceFactory;
use Tightenco\Collect\Support\Collection;
use Kadet\Functional as f;
class DummyStopRepository implements StopRepository
{
private $reference;
/**
* DummyDepartureProviderRepository constructor.
*
* @param $reference
*/
public function __construct(ReferenceFactory $reference)
{
$this->reference = $reference;
}
public function getAll(): Collection
{
return collect();
}
public function getAllGroups(): Collection
{
return collect();
}
public function getById($id): ?Stop
{
return Stop::createFromArray(['id' => $id, 'name' => 'lorem']);
}
public function getManyById($ids): Collection
{
return collect($ids)->map(f\ref([ $this, 'getById' ]));
}
public function findGroupsByName(string $name): Collection
{
return collect();
}
}

View File

@ -12,12 +12,12 @@
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" v-hover="settings.messages">
<fa :icon="['fal', 'cog']"></fa>
<fa :icon="['fal', 'cog']" fixed-width></fa>
</button>
<button class="btn btn-action" @click="updateMessages" ref="btn-messages-refresh">
<fa :icon="['fal', 'sync']" :spin="messages.state === 'fetching'"></fa>
<fa :icon="['fal', 'sync']" :spin="messages.state === 'fetching'" fixed-width></fa>
</button>
<button class="btn btn-action" @click="sections.messages = !sections.messages">
<button class="btn btn-action" @click="sections.messages = !sections.messages" fixed-width>
<fa :icon="['fal', sections.messages ? 'chevron-up' : 'chevron-down']" fixed-width/>
</button>
@ -47,10 +47,10 @@
<span class="text">Odjazdy</span>
</h2>
<button class="btn btn-action flex-space-left" ref="settings-departures" v-hover="settings.departures">
<fa :icon="['fal', 'cog']"></fa>
<fa :icon="['fal', 'cog']" fixed-width></fa>
</button>
<button class="btn btn-action" @click="updateDepartures({ stops })">
<fa :icon="['fal', 'sync']" :spin="departures.state === 'fetching'"></fa>
<fa :icon="['fal', 'sync']" :spin="departures.state === 'fetching'" fixed-width></fa>
</button>
<popper reference="settings-departures" :visible="settings.departures" arrow placement="left-start">

View File

@ -35,6 +35,14 @@
</footer>
{% block javascripts %}{% endblock %}
<script>
window.czydojade = {
state: {{ {
version: 1,
stops: app.request.query.get('stop', [])
}|json_encode|raw }}
};
</script>
<script src="{{ asset('bundle.js') }}"></script>
</body>
</html>