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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store curation information and default nomenclatural code in cookies #260

Merged
merged 11 commits into from
Nov 30, 2022
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"newick-js": "^1.1.1",
"popper.js": "^1.16.1",
"vue": "^2.6.11",
"vue-cookies": "^1.8.1",
"vue-resize": "^0.4.5",
"vuex": "^3.5.1",
"x-hub-signature": "^1.2.0"
Expand Down
39 changes: 37 additions & 2 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ import PhyxView from './components/phyx/PhyxView.vue';
import AboutCurationToolModal from './components/modals/AboutCurationToolModal.vue';
import AdvancedOptionsModal from './components/modals/AdvancedOptionsModal.vue';

// Load some configuration options.
import {
COOKIE_ALLOWED,
COOKIE_CURATOR_NAME,
COOKIE_CURATOR_EMAIL,
COOKIE_CURATOR_ORCID,
} from './config';

export default {
name: 'App',
components: {
Expand Down Expand Up @@ -74,6 +82,28 @@ export default {
},
},
created() {
// We store some information as browser cookies so that users don't need to re-enter them
// every time. One of these (the default nomenclatural code) is entirely handled in
// modules/phyx.js.
//
// Three of them need to be set on the default empty Phyx file here:
if (this.$cookies.get(COOKIE_ALLOWED) === 'true') {
if (this.$cookies.get(COOKIE_CURATOR_NAME)) {
this.$store.commit('setCurator', {name: this.$cookies.get(COOKIE_CURATOR_NAME)});
}

if (this.$cookies.get(COOKIE_CURATOR_EMAIL)) {
this.$store.commit('setCurator', {email: this.$cookies.get(COOKIE_CURATOR_EMAIL)});
}

if (this.$cookies.get(COOKIE_CURATOR_ORCID)) {
this.$store.commit('setCurator', {orcid: this.$cookies.get(COOKIE_CURATOR_ORCID)});
}
}

// Reset the "changed" flags (in case the above code changed the Phyx file)
this.$store.commit('setLoadedPhyx');

// If someone tries to navigate away from the window while the
// PHYX has been modified, ask users to confirm before leaving.
// Confirmation message to display to the user. Note that modern
Expand All @@ -82,8 +112,13 @@ export default {
$(window).on('beforeunload', () => {
const confirmationMessage = 'Your modifications have not been saved and will be lost if you close Klados. Confirm to discard your changes, or cancel to return to Klados.';

if (!isEqual(this.loadedPhyx, this.currentPhyx)) return confirmationMessage;
return false;
console.info('beforeUnload() called!');

if (!isEqual(this.loadedPhyx, this.currentPhyx)) {
console.warn('Difference in loadedPhyx and currentPhyx detected, warning user before closing window:', this.loadedPhyx, this.currentPhyx);
return confirmationMessage;
}
return undefined;
});
},
};
Expand Down
21 changes: 19 additions & 2 deletions src/components/phyx/PhyxView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,28 @@
@change="$store.commit('setDefaultNomenCodeURI', { defaultNomenclaturalCodeURI: $event.target.value })"
>
<option
v-for="(nomenCode, nomenCodeIndex) of nomenCodes"
:value="nomenCode.uri"
v-for="nomenCode of nomenCodes"
:value="nomenCode.iri"
>
{{ nomenCode.label }}
</option>
</select>
</div>
</div>

<!-- Checkbox for permission to save this information to browser -->
<div class="form-group row">
<div class="col-md-2">&nbsp;</div>
<div class="col-md-10">
<input type="checkbox"
:checked="cookieCheckbox"
@click="$store.commit('toggleCookieAllowed')"
/>
Save curator information and
default nomenclatural code as a browser cookie (currently for thirty days). If changed to unchecked,
delete Klados cookies from your browser.
</div>
</div>
</form>
</div>
</div>
Expand Down Expand Up @@ -280,6 +294,9 @@ export default {
get() { return this.phyx.curatorORCID; },
set(orcid) { this.$store.commit('setCurator', { orcid }); },
},
cookieCheckbox() {
return this.$store.getters.isCookieAllowed;
},
...mapState({
phyx: state => state.phyx.currentPhyx,
phylorefs: state => state.phyx.currentPhyx.phylorefs,
Expand Down
16 changes: 16 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,20 @@ module.exports = {
// Open Tree Taxonomy IDs
// (https://github.com/OpenTreeOfLife/germinator/wiki/Synthetic-tree-API-v3#induced_subtree)
OPEN_TREE_INDUCED_SUBTREE_URL: 'https://api.opentreeoflife.org/v3/tree_of_life/induced_subtree',

// The default cookie expiry setting (see https://www.npmjs.com/package/vue-cookies for formats)
COOKIE_EXPIRY: '30d', // Expire cookies in 30 days

// Cookie names to use for:
// - the 'allowed' cookie -- if set to 'true', this means that the user has granted us permission
// to store their information in cookies.
COOKIE_ALLOWED: 'kladosCookieAllowed',
// - the default nomenclatural code
COOKIE_DEFAULT_NOMEN_CODE_URI: 'kladosDefaultNomenclaturalCodeURI',
// - curator name
COOKIE_CURATOR_NAME: 'kladosCuratorName',
// - curator e-mail address
COOKIE_CURATOR_EMAIL: 'kladosCuratorEmail',
// - curator ORCID
COOKIE_CURATOR_ORCID: 'kladosCuratorORCID',
};
1 change: 1 addition & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Vue.prototype.$config = require('./config.js');
// Add additional features to Vue.
Vue.use(BootstrapVue);
Vue.use(VueResize);
Vue.use(require('vue-cookies')); // Use https://www.npmjs.com/package/vue-cookies

// Turn off the Vue production tip on the console on Vue startup.
Vue.config.productionTip = false;
Expand Down
65 changes: 61 additions & 4 deletions src/store/modules/phyx.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,31 @@ import {
OPEN_TREE_ABOUT_URL,
OPEN_TREE_TNRS_MATCH_NAMES_URL,
OPEN_TREE_INDUCED_SUBTREE_URL,

COOKIE_EXPIRY,
COOKIE_ALLOWED, COOKIE_DEFAULT_NOMEN_CODE_URI, COOKIE_CURATOR_NAME, COOKIE_CURATOR_EMAIL, COOKIE_CURATOR_ORCID,
} from '../../config';

// Shared code for reading and writing cookies.

/** Check whether we are allowed to store cookies on this users' browser.
* We determine this based on whether the COOKIE_ALLOWED cookie is set. */
function checkCookieAllowed() {
return (Vue.$cookies.get(COOKIE_ALLOWED) === 'true');
}

/** Get a cookie from the browser (if we're allowed to). */
function getKladosCookie(keyName, valueIfNotSet = undefined) {
if (checkCookieAllowed()) return Vue.$cookies.get(keyName) || valueIfNotSet;
return valueIfNotSet;
}

/** Set a cookie on the browser (if we're allowed to). */
function setKladosCookie(keyName, value, expiry = COOKIE_EXPIRY) {
// Only set the cookie if we are allowed (the COOKIE_ALLOWED is set).
if (checkCookieAllowed()) Vue.$cookies.set(keyName, value, expiry);
}

export default {
state: {
// The currently loaded Phyx file. All methods modify and change this variable.
Expand All @@ -39,8 +62,10 @@ export default {
return !isEqual(state.currentPhyx, state.loadedPhyx);
},
getDefaultNomenCodeURI(state) {
// If no default nomenclatural code is set in the Phyx file, we will attempt to look up that information
// using a cookie.
return state.currentPhyx.defaultNomenclaturalCodeURI
|| TaxonNameWrapper.UNKNOWN_CODE;
|| getKladosCookie(COOKIE_DEFAULT_NOMEN_CODE_URI, TaxonNameWrapper.UNKNOWN_CODE);
},
getDownloadFilenameForPhyx(state) {
// Return a filename to be used to name downloads of this Phyx document.
Expand Down Expand Up @@ -69,8 +94,28 @@ export default {
if (phylorefLabels.length === 3) return `${phylorefLabels[0]}_${phylorefLabels[1]}_and_${phylorefLabels[2]}`;
return `${phylorefLabels[0]}_${phylorefLabels[1]}_and_${phylorefLabels.length - 2}_others`;
},
isCookieAllowed() {
// Checks to see if cookies are allowed. We can check this by seeing if a cookie named
// COOKIE_ALLOWED is set.
return checkCookieAllowed();
},
},
mutations: {
toggleCookieAllowed(state) {
if (checkCookieAllowed()) {
// Cookie allowed! Toggle it by deleting all Klados cookies.
Vue.$cookies.keys().forEach(key => Vue.$cookies.remove(key));
} else {
// Cookie not allowed! Toggle it to cookie allowed. We don't use setKladosCookie() because
// it includes a check for COOKIE_ALLOWED; instead, we set it directly.
Vue.$cookies.set(COOKIE_ALLOWED, 'true', COOKIE_EXPIRY);

// Then, save all current curator information.
setKladosCookie(COOKIE_CURATOR_NAME, state.currentPhyx.curator || '');
setKladosCookie(COOKIE_CURATOR_EMAIL, state.currentPhyx.curatorEmail || '');
setKladosCookie(COOKIE_CURATOR_ORCID, state.currentPhyx.curatorORCID || '');
}
},
setCurrentPhyx(state, phyx) {
// Replace the current Phyx file using an object. This method does NOT
// update the loaded Phyx file, so these changes are treated as changes
Expand Down Expand Up @@ -131,6 +176,9 @@ export default {
throw new Error('No default nomenclatural code URI provided to setDefaultNomenCodeURI');
}

// Overwrite the current default nomenclatural code cookie.
setKladosCookie(COOKIE_DEFAULT_NOMEN_CODE_URI, payload.defaultNomenclaturalCodeURI);

Vue.set(state.currentPhyx, 'defaultNomenclaturalCodeURI', payload.defaultNomenclaturalCodeURI);
},
duplicatePhyloref(state, payload) {
Expand All @@ -144,9 +192,18 @@ export default {
},
setCurator(state, payload) {
// Set the curator name, e-mail address or (eventually) ORCID.
if (has(payload, 'name')) Vue.set(state.currentPhyx, 'curator', payload.name);
if (has(payload, 'email')) Vue.set(state.currentPhyx, 'curatorEmail', payload.email);
if (has(payload, 'orcid')) Vue.set(state.currentPhyx, 'curatorORCID', payload.orcid);
if (has(payload, 'name')) {
setKladosCookie(COOKIE_CURATOR_NAME, payload.name);
Vue.set(state.currentPhyx, 'curator', payload.name);
}
if (has(payload, 'email')) {
setKladosCookie(COOKIE_CURATOR_EMAIL, payload.email);
Vue.set(state.currentPhyx, 'curatorEmail', payload.email);
}
if (has(payload, 'orcid')) {
setKladosCookie(COOKIE_CURATOR_ORCID, payload.orcid);
Vue.set(state.currentPhyx, 'curatorORCID', payload.orcid);
}
},
},
actions: {
Expand Down