Skip to content

Commit

Permalink
Merge pull request #8118 from jonboiser/save-net-addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
jonboiser committed Jun 7, 2021
2 parents 83efe23 + b53573f commit c3a6c46
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 138 deletions.
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,81 @@
import {
ref,
computed,
onBeforeMount,
onBeforeUnmount,
getCurrentInstance,
} from '@vue/composition-api';
import { get, set, useIntervalFn } from '@vueuse/core';
import { fetchDynamicAddresses } from './api';

const Stages = Object.freeze({
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 discoveredAddressesInitiallyFetched = ref(false);
const $parent = getCurrentInstance().proxy.$parent;

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

const setStage = newStage => set(stage, newStage);

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

// Start polling
const pollForPeers = useIntervalFn(
() => {
discoverPeers();
},
5000,
false
);

onBeforeMount(() => {
discoverPeers();
pollForPeers.resume();
});

// Stop polling
onBeforeUnmount(() => {
pollForPeers.pause();
});

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

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

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

0 comments on commit c3a6c46

Please sign in to comment.