Add Vuex projects module.

Not to be confused with the `project` module.

`projects`: lists all available projects
`project`: lists details for one project
This commit is contained in:
D. Berge
2023-10-29 19:27:26 +01:00
parent 31419e860e
commit 219425245f
6 changed files with 202 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ import Vuex from 'vuex'
import api from './modules/api'
import user from './modules/user'
import snack from './modules/snack'
import projects from './modules/projects'
import project from './modules/project'
import event from './modules/event'
import label from './modules/label'
@@ -19,6 +20,7 @@ export default new Vuex.Store({
api,
user,
snack,
projects,
project,
event,
label,

View File

@@ -0,0 +1,120 @@
/** Fetch projects from server
*/
async function refreshProjects ({commit, dispatch, state, rootState}) {
if (state.loading) {
commit('abortProjectsLoading');
}
commit('setProjectsLoading');
const pid = rootState.project.projectId;
const url = `/project`;
const init = {
signal: state.loading.signal
};
const res = await dispatch('api', [url, init]);
if (res) {
commit('setProjects', res);
commit('setProjectsTimestamp');
}
commit('clearProjectsLoading');
}
/** Return a subset of projects from state.projects
*/
async function getProjects ({commit, dispatch, state}, [{pid, name, schema, group, sortBy, sortDesc, itemsPerPage, page, text}] = [{}]) {
let filteredProjects = [...state.projects];
if (sortBy) {
sortBy.forEach( (key, idx) => {
filteredProjects.sort( (el0, el1) => {
const a = el0?.[key];
const b = el1?.[key];
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else if (a == b) {
return 0;
} else if (a && !b) {
return 1;
} else if (!a && b) {
return -1;
} else {
return 0;
}
});
if (sortDesc && sortDesc[idx] === true) {
filteredProjects.reverse();
}
});
}
if (pid) {
filteredProjects = filteredProjects.filter( project => project.pid == pid );
}
if (name) {
filteredProjects = filteredProjects.filter( project => project.name.toLowerCase().includes(name.toLowerCase()) );
}
if (schema) {
filteredProjects = filteredProjects.filter( project => project.schema.toLowerCase().includes(schema.toLowerCase()) );
}
if (group) {
filteredProjects = filteredProjects.filter( project => project.groups.find(g => g.toLowerCase() == group.toLowerCase()) );
}
if (text) {
const tstampFilter = (value, search, item) => {
return search?.length >= 5 && textFilter(value, search, item);
};
const numberFilter = (value, search, item) => {
return value == search;
};
const textFilter = (value, search, item) => {
return String(value).toLowerCase().includes(search.toLowerCase());
};
const arrayFilter = (elementFilter) => {
return (value, search, item) => value.some(element => elementFilter(element, search, item));
};
const searchFunctions = {
pid: textFilter,
name: textFilter,
group: arrayFilter(textFilter)
};
filteredProjects = filteredProjects.filter ( project => {
for (let key in searchFunctions) {
const fn = searchFunctions[key];
if (fn(project[key], text, project)) {
return true;
}
}
return false;
});
}
const count = filteredProjects.length;
if (itemsPerPage && itemsPerPage > 0) {
const offset = (page > 0)
? (page-1) * itemsPerPage
: 0;
filteredProjects = filteredProjects.slice(offset, offset+itemsPerPage);
}
return {projects: filteredProjects, count};
}
export default { refreshProjects, getProjects };

View File

@@ -0,0 +1,18 @@
function projects (state) {
return state.projects;
}
function projectGroups (state) {
return [...new Set(state.projects.map(i => i.groups).flat())].sort();
}
function projectCount (state) {
return state.projects?.length ?? 0;
}
function projectsLoading (state) {
return !!state.loading;
}
export default { projects, projectGroups, projectCount, projectsLoading };

View File

@@ -0,0 +1,6 @@
import state from './state'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'
export default { state, getters, actions, mutations };

View File

@@ -0,0 +1,48 @@
function setProjects (state, projects) {
// We don't need or want the array to be reactive
state.projects = Object.freeze(projects);
}
function setProjectsLoading (state, abortController = new AbortController()) {
state.loading = abortController;
}
// This assumes that we know any transactions have finished or we
// don't care about aborting.
function clearProjectsLoading (state) {
state.loading = null;
}
function setProjectsTimestamp (state, timestamp = new Date()) {
// NOTE: There is no `modified_on` property in the projects
// result or in the database schema, but we should probably add
// one.
if (timestamp === true) {
const tstamp = state.projects
.map( event => event.modified_on )
.reduce( (acc, cur) => acc > cur ? acc : cur );
state.timestamp = tstamp ? new Date(tstamp) : new Date();
} else {
state.timestamp = timestamp;
}
}
function setProjectsETag (state, etag) {
state.etag = etag;
}
function abortProjectsLoading (state) {
if (state.loading) {
state.loading.abort();
}
state.loading = null;
}
export default {
setProjects,
setProjectsLoading,
clearProjectsLoading,
setProjectsTimestamp,
setProjectsETag
};

View File

@@ -0,0 +1,8 @@
const state = () => ({
projects: Object.freeze([]),
loading: null,
timestamp: null,
etag: null,
});
export default state;