Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add '@vue/composition-api' plugin; refactor SelectAddressForm with setup(); implement #7345 #8118

Merged
merged 14 commits into from
Jun 7, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions kolibri/core/assets/src/core-app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Vue from 'vue';
import VueMeta from 'vue-meta';
import VueRouter from 'vue-router';
import Vuex from 'vuex';
import VueCompositionApi from '@vue/composition-api';
import KThemePlugin from 'kolibri-design-system/lib/KThemePlugin';
import heartbeat from 'kolibri.heartbeat';
import KContentPlugin from 'kolibri-design-system/lib/content/KContentPlugin';
Expand Down Expand Up @@ -74,6 +75,7 @@ Vue.use(Vuex);
Vue.use(VueRouter);
Vue.use(VueMeta);
Vue.use(KThemePlugin);
Vue.use(VueCompositionApi);

Vue.use(KContentPlugin, {
languageDirection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@cancel="$emit('cancel')"
>
<template>
<p v-if="initialFetchingComplete && !addresses.length">
<p v-if="initialFetchingComplete && !combinedAddresses.length">
{{ $tr('noAddressText') }}
</p>
<UiAlert
Expand Down Expand Up @@ -116,38 +116,77 @@

<script>

import { computed } from '@vue/composition-api';
import { useLocalStorage } from '@vueuse/core';
import find from 'lodash/find';
import UiAlert from 'kolibri-design-system/lib/keen/UiAlert';
import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings';
import commonSyncElements from 'kolibri.coreVue.mixins.commonSyncElements';
import { deleteAddress, fetchStaticAddresses, fetchDynamicAddresses } from './api';

const Stages = Object.freeze({
FETCHING_ADDRESSES: 'FETCHING_ADDRESSES',
FETCHING_SUCCESSFUL: 'FETCHING_SUCCESSFUL',
FETCHING_FAILED: 'FETCHING_FAILED',
DELETING_ADDRESS: 'DELETING_ADDRESS',
DELETING_SUCCESSFUL: 'DELETING_SUCCESSFUL',
DELETING_FAILED: 'DELETING_FAILED',
PEER_DISCOVERY_STARTED: 'PEER_DISCOVERY_STARTED',
PEER_DISCOVERY_SUCCESSFUL: 'PEER_DISCOVERY_SUCCESSFUL',
PEER_DISCOVERY_FAILED: 'PEER_DISCOVERY_FAILED',
});
import useDynamicAddresses from './useDynamicAddresses.js';
import useSavedAddresses from './useSavedAddresses.js';

export default {
name: 'SelectAddressForm',
components: {
UiAlert,
},
mixins: [commonCoreStrings, commonSyncElements],
setup(props, context) {
const {
addresses: discoveredAddresses,
discoveringPeers,
discoveryFailed,
discoveredAddressesInitiallyFetched,
} = useDynamicAddresses(props);

const {
addresses: savedAddresses,
removeSavedAddress,
refreshSavedAddressList,
savedAddressesInitiallyFetched,
requestsFailed,
deletingAddress,
fetchingAddresses,
} = useSavedAddresses(props, context);

const combinedAddresses = computed(() => {
return [...savedAddresses.value, ...discoveredAddresses.value];
});

const initialFetchingComplete = computed(() => {
return savedAddressesInitiallyFetched.value && discoveredAddressesInitiallyFetched.value;
});

const storageAddressId = useLocalStorage('kolibri-lastSelectedNetworkLocationId', '');

return {
combinedAddresses,
initialFetchingComplete,
discoveredAddresses,
discoveringPeers,
discoveryFailed,
discoveredAddressesInitiallyFetched,
savedAddresses,
savedAddressesInitiallyFetched,
removeSavedAddress,
refreshSavedAddressList,
requestsFailed,
deletingAddress,
fetchingAddresses,
storageAddressId,
};
},
props: {
// eslint-disable-next-line kolibri/vue-no-unused-properties
discoverySpinnerTime: { type: Number, default: 2500 },
// Facility filter only needed on SyncFacilityModalGroup
// eslint-disable-next-line kolibri/vue-no-unused-properties
filterByFacilityId: {
type: String,
default: null,
},
// Channel filter only needed on ManageContentPage/SelectNetworkAddressModal
// eslint-disable-next-line kolibri/vue-no-unused-properties
filterByChannelId: {
type: String,
default: null,
Expand All @@ -171,175 +210,78 @@
data() {
return {
availableAddressIds: [],
savedAddresses: [],
savedAddressesInitiallyFetched: false,
discoveredAddresses: [],
discoveredAddressesInitiallyFetched: true,
selectedAddressId: '',
showUiAlerts: false,
stage: '',
discoveryStage: '',
Stages,
};
},
computed: {
fetchAddressArgs() {
if (this.filterByChannelId) {
return { channelId: this.filterByChannelId };
} else if (this.filterByFacilityId) {
return { facilityId: this.filterByFacilityId };
} else {
return {};
}
},
addresses() {
return this.savedAddresses.concat(this.discoveredAddresses);
},
isAddressAvailable() {
return function(addressId) {
return Boolean(this.availableAddressIds.find(id => id === addressId));
};
},
initialFetchingComplete() {
return this.savedAddressesInitiallyFetched && this.discoveredAddressesInitiallyFetched;
},
submitDisabled() {
return (
this.selectedAddressId === '' ||
this.stage === this.Stages.FETCHING_ADDRESSES ||
this.stage === this.Stages.DELETING_ADDRESS ||
this.discoveryStage === this.Stages.PEER_DISCOVERY_FAILED ||
this.fetchingAddresses ||
this.deletingAddress ||
this.discoveryFailed ||
this.availableAddressIds.length === 0
);
},
newAddressButtonDisabled() {
return this.hideSavedAddresses || this.stage === this.Stages.FETCHING_ADDRESSES;
},
requestsFailed() {
return (
this.stage === this.Stages.FETCHING_FAILED || this.stage === this.Stages.DELETING_FAILED
);
},
discoveringPeers() {
return this.discoveryStage === this.Stages.PEER_DISCOVERY_STARTED;
},
discoveryFailed() {
return this.discoveryStage === this.Stages.PEER_DISCOVERY_FAILED;
return this.hideSavedAddresses || this.fetchingAddresses;
},
uiAlertProps() {
if (this.stage === this.Stages.FETCHING_FAILED) {
return {
text: this.$tr('fetchingFailedText'),
type: 'error',
};
let text;
if (this.fetchingFailed) {
text = this.$tr('fetchingFailedText');
}
if (this.discoveryStage === this.Stages.PEER_DISCOVERY_FAILED) {
return {
text: this.$tr('fetchingFailedText'),
type: 'error',
};
if (this.discoveryFailed) {
text = this.$tr('fetchingFailedText');
}
if (this.stage === this.Stages.DELETING_FAILED) {
return {
text: this.$tr('deletingFailedText'),
type: 'error',
};
if (this.deletingFailed) {
text = this.$tr('deletingFailedText');
}
return null;
return text ? { text, type: 'error' } : null;
},
},
watch: {
addresses(addresses) {
this.availableAddressIds = addresses
selectedAddressId(newVal) {
this.storageAddressId = newVal;
},
combinedAddresses(addrs) {
this.availableAddressIds = addrs
.filter(address => address.available)
.map(address => address.id);
this.resetSelectedAddress();
if (!this.availableAddressIds.includes(this.selectedAddressId)) {
this.selectedAddressId = '';
}
if (!this.selectedAddressId) {
this.resetSelectedAddress();
}
},
},
beforeMount() {
this.startDiscoveryPolling();
return this.refreshSavedAddressList();
},
mounted() {
// Wait a little bit of time before showing UI alerts so there is no flash
// if data comes back quickly
setTimeout(() => {
this.showUiAlerts = true;
}, 100);
},
destroyed() {
this.stopDiscoveryPolling();
},
methods: {
refreshSavedAddressList() {
this.stage = this.Stages.FETCHING_ADDRESSES;
this.savedAddresses = [];
return fetchStaticAddresses(this.fetchAddressArgs)
.then(addresses => {
this.savedAddresses = addresses;
this.stage = this.Stages.FETCHING_SUCCESSFUL;
this.savedAddressesInitiallyFetched = true;
})
.catch(() => {
this.stage = this.Stages.FETCHING_FAILED;
});
},
resetSelectedAddress() {
if (this.availableAddressIds.length !== 0) {
const selectedId = this.selectedId ? this.selectedId : this.selectedAddressId;
const selectedId = this.selectedId || this.storageAddressId || this.selectedAddressId;
this.selectedAddressId =
this.availableAddressIds.find(id => selectedId === id) || this.availableAddressIds[0];
this.availableAddressIds.find(id => id === selectedId) || this.availableAddressIds[0];
} else {
this.selectedAddressId = '';
}
},
removeSavedAddress(id) {
this.stage = this.Stages.DELETING_ADDRESS;
return deleteAddress(id)
.then(() => {
this.savedAddresses = this.savedAddresses.filter(a => a.id !== id);
this.stage = this.Stages.DELETING_SUCCESSFUL;
this.$emit('removed_address');
})
.catch(() => {
this.stage = this.Stages.DELETING_FAILED;
});
},

discoverPeers() {
this.$parent.$emit('started_peer_discovery');
this.discoveryStage = this.Stages.PEER_DISCOVERY_STARTED;
return fetchDynamicAddresses(this.fetchAddressArgs)
.then(devices => {
this.discoveredAddresses = devices;
this.$parent.$emit('finished_peer_discovery');
setTimeout(() => {
this.discoveryStage = this.Stages.PEER_DISCOVERY_SUCCESSFUL;
}, this.discoverySpinnerTime);
this.discoveredAddressesInitiallyFetched = true;
})
.catch(() => {
this.$parent.$emit('peer_discovery_failed');
this.discoveryStage = this.Stages.PEER_DISCOVERY_FAILED;
});
},

startDiscoveryPolling() {
this.discoverPeers();
if (!this.intervalId) {
this.intervalId = setInterval(this.discoverPeers, 5000);
}
},

stopDiscoveryPolling() {
if (this.intervalId) {
this.intervalId = clearInterval(this.intervalId);
}
},

handleSubmit() {
if (this.selectedAddressId) {
const match = find(this.addresses, { id: this.selectedAddressId });
const match = find(this.combinedAddresses, { id: this.selectedAddressId });
match.isDynamic = Boolean(find(this.discoveredAddresses, { id: this.selectedAddressId }));
this.$emit('submit', match);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
ref,
computed,
onBeforeMount,
onBeforeUnmount,
getCurrentInstance,
} from '@vue/composition-api';
import { fetchDynamicAddresses } from './api';

const Stages = Object.freeze({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! you learn something new every day, and today I learned about this

PEER_DISCOVERY_STARTED: 'PEER_DISCOVERY_STARTED',
PEER_DISCOVERY_SUCCESSFUL: 'PEER_DISCOVERY_SUCCESSFUL',
PEER_DISCOVERY_FAILED: 'PEER_DISCOVERY_FAILED',
});

export default function useDynamicAddresses(props) {
const { fetchAddressArgs, discoverySpinnerTime } = props;
const addresses = ref([]);
const stage = ref('');
const intervalId = ref('');
const discoveredAddressesInitiallyFetched = ref(false);
const $parent = getCurrentInstance().proxy.$parent;

function parentEmit(event, ...args) {
$parent.$emit(event, ...args);
}

function discoverPeers() {
parentEmit('started_peer_discovery');
stage.value = Stages.PEER_DISCOVERY_STARTED;
return fetchDynamicAddresses(fetchAddressArgs)
.then(devices => {
addresses.value = devices;
parentEmit('finished_peer_discovery');
setTimeout(() => {
stage.value = Stages.PEER_DISCOVERY_SUCCESSFUL;
}, discoverySpinnerTime);
discoveredAddressesInitiallyFetched.value = true;
})
.catch(() => {
parentEmit('peer_discovery_failed');
stage.value = Stages.PEER_DISCOVERY_FAILED;
});
}

// Start polling
onBeforeMount(() => {
discoverPeers();
if (!intervalId.value) {
intervalId.value = setInterval(discoverPeers, 5000);
}
});

// Stop polling
onBeforeUnmount(() => {
if (intervalId.value) {
intervalId.value = clearInterval(intervalId.value);
}
});

const discoveringPeers = computed(() => {
return stage.value === Stages.PEER_DISCOVERY_STARTED;
});

const discoveryFailed = computed(() => {
return stage.value === Stages.PEER_DISCOVERY_FAILED;
});

return {
addresses,
discoveringPeers,
discoveryFailed,
discoveredAddressesInitiallyFetched,
};
}