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

Feature/changenote settings #901

Open
wants to merge 33 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4545b5c
WIP on change note user settings
lrosenstrom Oct 6, 2023
02516b1
Get change categories + labels from the data instead
lrosenstrom Oct 10, 2023
cae6e9a
Use flexbox instead of table
lrosenstrom Oct 11, 2023
f474a43
Simplify icon rotation css + make it work
lrosenstrom Oct 11, 2023
c16d9bc
*Naming things *Don't expand category list by default
lrosenstrom Oct 11, 2023
0dd1eb6
Sort list of sigels for changecategory view
lrosenstrom Oct 11, 2023
abcf5aa
Use sigel label instead of friendly name
lrosenstrom Oct 11, 2023
4a62eea
Cleanup
lrosenstrom Oct 11, 2023
830bacb
Write changenote notification email to user db
lrosenstrom Oct 12, 2023
e54dacb
Use library uri instead of library code in changesettings db
lrosenstrom Oct 16, 2023
e0bf139
Fix collection uri generation
lrosenstrom Oct 16, 2023
d036174
Fix sigelUri again
lrosenstrom Oct 16, 2023
fe76f94
Fix nullpointer on work extraction
lrosenstrom Oct 16, 2023
b23a37d
Cleanup
lrosenstrom Oct 16, 2023
9a2e23d
Let frontend facilitate a simple commit message in adminmetadata instead
lrosenstrom Oct 26, 2023
8db1bac
Alternative: overwrite changenote for each change.
lrosenstrom Oct 26, 2023
402a1cd
Label -> comment
lrosenstrom Oct 26, 2023
1c4fec4
Fix hightlight not being removed automatically
lrosenstrom Oct 26, 2023
2d8e587
Remove TODO
lrosenstrom Oct 26, 2023
0d8a1a1
Remove unused changenote code
lrosenstrom Oct 26, 2023
f6b6c83
WIP
lrosenstrom Oct 27, 2023
9a7484a
Avoid messing with the activeSearchType state when in the 'changes' view
lrosenstrom Oct 31, 2023
951d964
Make search tab menu look reasonable
lrosenstrom Oct 31, 2023
ab44db7
Restore file deleted by mistake
lrosenstrom Nov 1, 2023
da96898
Put change note search under directory care
lrosenstrom Nov 1, 2023
22d87de
Fix reverse relations spinner in change note search
lrosenstrom Nov 1, 2023
506d30d
Wrapper for change note view
lrosenstrom Nov 6, 2023
fd41d23
Search directly on change note view page load
lrosenstrom Nov 6, 2023
84e7d7a
Allow only one set of change categories for all library codes
lrosenstrom Nov 8, 2023
2ab0280
Aktivt sigel -> Aktiv sigel
lrosenstrom Nov 8, 2023
ee2bc16
WIP on facets
lrosenstrom Nov 9, 2023
77157eb
Checkboxes
lrosenstrom Nov 10, 2023
cf4d910
Checkboxes
lrosenstrom Nov 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions lxljs/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,16 @@ export function getFormattedEntries(list, vocab, language, context) {
remove(formatted, value => value === '' || value === null); // Remove empty strings
return formatted;
}

export function getSigelLabel(sigel, len) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It is mostly a hygiene detail. Is it possible to use a bit more generic or less libris-centric names excluding the word sigel in some of these function or var/const names? Since the sigel is by our definition the code on something and not the actual entity it describes (contrary to how we use it in natural language, the hard part here being it plays different roles and is not always a library). Are we meaning userLocation? Not a stopper by any means and should it turn out to be too timeconsuming to figure out I wouldn't argue it easier to revisit later when the model and definitions around this is clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can change it to 'collection', because that's what it is called in the user store?

if (!sigel.friendly_name) {
return sigel.code;
}

const sigelPart = ` (${sigel.code})`;
const fName = sigel.friendly_name.length + sigelPart.length > len
? `${sigel.friendly_name.substr(0, len - sigelPart.length - 3)}...`
: sigel.friendly_name;

return `${fName}${sigelPart}`;
}
108 changes: 108 additions & 0 deletions vue-client/src/components/usersettings/change-categories.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<script>
import { mapGetters } from 'vuex';
import * as StringUtil from 'lxljs/string';
import LensMixin from '@/components/mixins/lens-mixin';

export default {
name: 'change-categories',
mixins: [LensMixin],
data() {
return {
expanded: false,
};
},
props: {
sigel: Object,
availableCategories: [],
},
methods: {
updateChangeCategories(e, sigel, categoryId) {
this.$store.dispatch('updateSubscribedChangeCategories', { libraryId: sigel.code, categoryId: categoryId, checked: e.target.checked });
},
toggleExpanded() {
this.expanded = !this.expanded;
},
isActiveCategory(categoryId) {
const obj = this.userChangeCategories.find(c => c.heldBy === this.sigel.code);
return obj ? obj.triggers.includes(categoryId) : false;
},
label(obj) {
return this.getLabel(obj);
},
},
computed: {
...mapGetters([
'userChangeCategories',
]),
isExpanded() {
return this.expanded;
},
sigelLabel() {
return StringUtil.getSigelLabel(this.sigel);
},
},
mounted() {
this.$nextTick(() => {
});
},
};
</script>

<template>
<div class="Categories">
<div class="Categories-label" @click="toggleExpanded">
<i class="Categories-arrow fa fa-chevron-right"
:class="{'icon is-expanded' : isExpanded}"
></i>
{{ sigelLabel }}
</div>

<div v-if="isExpanded">
<div class="Categories-row" v-for="category in availableCategories" :key="category['@id']">
<div class="Categories-key">{{ label(category) }}</div>
<div class="Categories-value">
<input id="categoryCheckbox"
class="customCheckbox-input"
type="checkbox"
@change="updateChangeCategories(...arguments, sigel, category['@id'])" :checked="isActiveCategory(category['@id'])">
<div class="customCheckbox-icon"></div>
</div>
</div>
</div>
</div>
</template>

<style scoped lang="less">
.Categories {
&-arrow {
transition: all 0.2s ease;
padding: 0 2px;
font-size: 14px;
color: @grey-darker-transparent;

&.is-expanded {
transform: rotate(90deg);
}
.Categories-label:hover & {
color: @black
}
}
&-row {
display: flex;
border: solid @grey-lighter;
border-width: 0px 0px 1px 0px;
}
&-key {
padding: 0.5em;
width: 50%;
}
&-value {
padding: 0.5em;
width: 50%;
}
&-label {
padding: 0.5em;
cursor: pointer;
}
}
</style>
12 changes: 2 additions & 10 deletions vue-client/src/components/usersettings/select-sigel.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script>
import { mapGetters } from 'vuex';
import * as StringUtil from 'lxljs/string';

export default {
name: 'select-sigel',
Expand All @@ -13,16 +14,7 @@ export default {
},
methods: {
getSigelLabel(sigel, len) {
if (!sigel.friendly_name) {
return sigel.code;
}

const sigelPart = ` (${sigel.code})`;
const fName = sigel.friendly_name.length + sigelPart.length > len
? `${sigel.friendly_name.substr(0, len - sigelPart.length - 3)}...`
: sigel.friendly_name;

return `${fName}${sigelPart}`;
return StringUtil.getSigelLabel(sigel, len);
},
updateSigel(value) {
const doUpdate = () => {
Expand Down
36 changes: 34 additions & 2 deletions vue-client/src/components/usersettings/user-settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { mapGetters } from 'vuex';
import * as StringUtil from 'lxljs/string';
import UserAvatar from '@/components/shared/user-avatar';
import SelectSigel from './select-sigel';
import ChangeCategories from './change-categories.vue';

export default {
name: 'user-settings',
Expand All @@ -12,10 +13,21 @@ export default {
default: false,
},
},
data() {
return {
availableChangeCategories: [],
};
},
methods: {
setUser(userObj) {
this.$store.dispatch('setUser', userObj);
},
getAvailableChangeCategories() {
const fetchUrl = `${this.settings.apiPath}/find.jsonld?@type=ChangeCategory`;
fetch(fetchUrl).then(response => response.json()).then((result) => {
this.availableChangeCategories = result?.items;
});
},
updateLanguage(e) {
const userObj = this.user;
userObj.settings.language = e.target.value;
Expand All @@ -42,6 +54,9 @@ export default {
purgeBookmarks() {
this.$store.dispatch('purgeUserTagged', 'Bookmark');
},
purgeChangeNoteCategories() {
this.$store.dispatch('purgeChangeCategories');
},
},
computed: {
...mapGetters([
Expand All @@ -52,19 +67,26 @@ export default {
'resources',
'userFlagged',
'userBookmarks',
'userChangeCategories',
]),
userHasTaggedRecords() {
return Object.keys(this.userStorage.list).length > 0;
},
sortedSigels() {
return [...this.user.collections].sort((a, b) => StringUtil.getSigelLabel(a).localeCompare(StringUtil.getSigelLabel(b)));
},
},
components: {
'change-categories': ChangeCategories,
'user-avatar': UserAvatar,
'select-sigel': SelectSigel,
},
watch: {
},
ready() { // Ready method is deprecated in 2.0, switch to "mounted"
mounted() {
this.$nextTick(() => {
// TODO: don't do this every time we open user settings
this.getAvailableChangeCategories();
});
},
};
Expand Down Expand Up @@ -163,7 +185,13 @@ export default {
</td>
</tr>
</table>

<h5 class="uppercaseHeading--bold">{{ 'Subscribe to change notes' | translatePhrase }}</h5>
<div class="UserSettings-changeCategories">
<div v-for="sigel in sortedSigels" :key="sigel.code">
<change-categories :sigel="sigel" :userChangeCategories="userChangeCategories" :availableCategories="availableChangeCategories"/>
</div>
<!-- <button name="clearCategories" class="btn btn&#45;&#45;sm btn-danger" @click.prevent="purgeChangeNoteCategories" @keyup.enter.prevent="purgeChangeNoteCategories">{{ 'Rensa databasen' }}</button>-->
</div>
</form>
<button class="btn btn-primary btn--lg UserSettings-logout" @click="logout">{{"Log out" | translatePhrase}}</button>
</div>
Expand Down Expand Up @@ -242,6 +270,10 @@ export default {
}
}

&-changeCategories {
margin-bottom: 20px;
}

&-configTable {
td {
padding: 0.5em;
Expand Down
1 change: 1 addition & 0 deletions vue-client/src/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export class User {
sort: false,
facetSortings: {},
shelfMarkSearch: '',
changeCategoriesSubscribed: [],
};
this.uriMinter = null;
}
Expand Down
3 changes: 2 additions & 1 deletion vue-client/src/resources/json/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@
"Back" : "Tillbaka",
"Transliterate": "Translitterera",
"Romanize": "Romanisera",
"Unlinked entity": "Olänkad entitet"
"Unlinked entity": "Olänkad entitet",
"Subscribe to change notes": "Prenumerera på ändringsmeddelanden"
}
}
32 changes: 32 additions & 0 deletions vue-client/src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,13 @@ const store = new Vuex.Store({
}
return collection;
},
userChangeCategories: (state) => {
const collection = [];
if (state.userDatabase == null || state.userDatabase.requestedNotifications == null) {
return collection;
}
return state.userDatabase.requestedNotifications;
},
userDatabase: state => state.userDatabase,
status: state => state.status,
directoryCare: state => state.directoryCare,
Expand Down Expand Up @@ -534,6 +541,30 @@ const store = new Vuex.Store({
}
dispatch('modifyUserDatabase', { property: 'markedDocuments', value: newList });
},
setNotificationEmail({ dispatch, state }, { userEmail }) {
const notificationEmail = cloneDeep(state.userDatabase.notificationEmail);
if (userEmail !== notificationEmail) {
dispatch('modifyUserDatabase', { property: 'notificationEmail', value: userEmail });
}
},
updateSubscribedChangeCategories({ dispatch, state }, { libraryId, categoryId, checked }) {
const notifications = cloneDeep(state.userDatabase.requestedNotifications) || [];

const notification = notifications?.find(obj => obj.heldBy === libraryId);
if (checked) {
if (notification) {
notification.triggers.push(categoryId);
} else {
notifications.push({ heldBy: libraryId, triggers: [categoryId] });
}
} else { // Unchecked => remove from triggers
notification.triggers = notification.triggers.filter(id => id !== categoryId);
}
dispatch('modifyUserDatabase', { property: 'requestedNotifications', value: notifications });
},
purgeChangeCategories({ dispatch }) {
dispatch('modifyUserDatabase', { property: 'requestedNotifications', value: null });
},
loadUserDatabase({ commit, dispatch, state }) {
if (state.user.id.length === 0) {
throw new Error('loadUserDatabase was dispatched with no real user loaded.');
Expand All @@ -543,6 +574,7 @@ const store = new Vuex.Store({
httpUtil.get({ url: `${state.settings.apiPath}/_userdata/${digestHex}`, token: state.user.token, contentType: 'text/plain' }).then((result) => {
commit('setUserDatabase', result);
dispatch('checkForMigrationOfUserDatabase');
dispatch('setNotificationEmail', { userEmail: state.user.email });
}, (error) => {
console.error(error);
});
Expand Down