feat: remove lodash dependency (#743)

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/743
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
This commit is contained in:
dpschen 2021-10-06 20:25:06 +00:00 committed by konrad
parent a76d115baf
commit faa2daa876
20 changed files with 116 additions and 89 deletions

View file

@ -353,7 +353,7 @@ describe('Task', () => {
.first()
.click()
cy.get('.global-notification')
cy.get('.global-notification', { timeout: 4000 })
.should('contain', 'Success')
cy.get('.task-view .details.labels-list .multiselect .input-wrapper span.tag')
.should('exist')

View file

@ -1,5 +1,4 @@
import {seed} from './seed'
import merge from 'lodash/merge'
/**
* A factory makes it easy to seed the database with data.
@ -25,7 +24,10 @@ export class Factory {
const data = []
for (let i = 1; i <= count; i++) {
const entry = merge(this.factory(), override)
const entry = {
...this.factory(),
...override,
}
for (const e in entry) {
if(typeof entry[e] === 'function') {
entry[e] = entry[e](i)

View file

@ -22,7 +22,6 @@
"dompurify": "2.3.3",
"highlight.js": "11.2.0",
"is-touch-device": "1.0.1",
"lodash": "4.17.21",
"marked": "3.0.4",
"register-service-worker": "1.7.2",
"snake-case": "3.0.4",

View file

@ -178,9 +178,8 @@ import Fancycheckbox from '../../input/fancycheckbox'
import flatPickr from 'vue-flatpickr-component'
import 'flatpickr/dist/flatpickr.css'
import {includesById} from '@/helpers/utils'
import {formatISO} from 'date-fns'
import differenceWith from 'lodash/differenceWith'
import PrioritySelect from '@/components/tasks/partials/prioritySelect.vue'
import PercentDoneSelect from '@/components/tasks/partials/percentDoneSelect.vue'
import Multiselect from '@/components/input/multiselect.vue'
@ -269,13 +268,7 @@ export default {
},
computed: {
foundLabels() {
const labels = (Object.values(this.$store.state.labels.labels).filter(l => {
return l.title.toLowerCase().includes(this.labelQuery.toLowerCase())
}) ?? [])
return differenceWith(labels, this.labels, (first, second) => {
return first.id === second.id
})
return this.$store.getters['labels/filterLabelsByQuery'](this.labels, this.query)
},
flatPickerConfig() {
return {
@ -309,8 +302,13 @@ export default {
this.prepareRelatedObjectFilter('namespace')
this.prepareSingleValue('labels')
const labelIds = (typeof this.filters.labels === 'string' ? this.filters.labels : '').split(',').map(i => parseInt(i))
this.labels = (Object.values(this.$store.state.labels.labels).filter(l => labelIds.includes(l.id)) ?? [])
const labels = typeof this.filters.labels === 'string'
? this.filters.labels
: ''
const labelIds = labels.split(',').map(i => parseInt(i))
this.labels = this.$store.getters['labels/getLabelsByIds'](labelIds)
},
removePropertyFromFilter(propertyName) {
// Because of the way arrays work, we can only ever remove one element at once.
@ -533,10 +531,10 @@ export default {
this[`${kind}Service`].getAll({}, {s: query})
.then(response => {
// Filter the results to not include users who are already assigneid
this.$set(this, `found${kind}`, differenceWith(response, this[kind], (first, second) => {
return first.id === second.id
}))
// Filter users from the results who are already assigned
const unassignedUsers = response.filter(({id}) => !includesById(this[kind], id))
this.$set(this, `found${kind}`, unassignedUsers)
})
.catch(e => {
this.$message.error(e)

View file

@ -1,5 +1,4 @@
import TaskCollectionService from '@/services/taskCollection'
import cloneDeep from 'lodash/cloneDeep'
// FIXME: merge with DEFAULT_PARAMS in filters.vue
const DEFAULT_PARAMS = {
@ -83,7 +82,7 @@ export default {
this.tasks = r
this.currentPage = page
this.loadedList = cloneDeep(currentList)
this.loadedList = JSON.parse(JSON.stringify(currentList))
})
.catch(e => {
this.$message.error(e)

View file

@ -29,8 +29,7 @@
</template>
<script>
import differenceWith from 'lodash/differenceWith'
import {includesById} from '@/helpers/utils'
import UserModel from '../../../models/user'
import ListUserService from '../../../services/listUsers'
import TaskAssigneeService from '../../../services/taskAssignee'
@ -111,9 +110,9 @@ export default {
this.listUserService.getAll({listId: this.listId}, {s: query})
.then(response => {
// Filter the results to not include users who are already assigned
this.$set(this, 'foundUsers', differenceWith(response, this.assignees, (first, second) => {
return first.id === second.id
}))
const filteredResponse = response.filter(({id}) => !includesById(this.assignees, id))
this.$set(this, 'foundUsers', filteredResponse)
})
.catch(e => {
this.$message.error(e)

View file

@ -38,8 +38,6 @@
</template>
<script>
import differenceWith from 'lodash/differenceWith'
import LabelModel from '../../../models/label'
import LabelTaskService from '../../../services/labelTask'
@ -83,13 +81,7 @@ export default {
},
computed: {
foundLabels() {
const labels = (Object.values(this.$store.state.labels.labels).filter(l => {
return l.title.toLowerCase().includes(this.query.toLowerCase())
}) ?? [])
return differenceWith(labels, this.labels, (first, second) => {
return first.id === second.id
})
return this.$store.getters['labels/filterLabelsByQuery'](this.labels, this.query)
},
loading() {
return this.labelTaskService.loading || (this.$store.state[LOADING] && this.$store.state[LOADING_MODULE] === 'labels')

View file

@ -1,3 +0,0 @@
export function findIndexById(array : [], id : string | number) {
return array.findIndex(({id: currentId}) => currentId === id)
}

22
src/helpers/utils.ts Normal file
View file

@ -0,0 +1,22 @@
export function findIndexById(array : [], id : string | number) {
return array.findIndex(({id: currentId}) => currentId === id)
}
export function includesById(array: [], id: string | number) {
return array.some(({id: currentId}) => currentId === id)
}
// https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_isnil
export function isNil(value: any) {
return value == null
}
export function omitBy(obj: {}, check: (value: any) => Boolean): {} {
if (isNil(obj)) {
return {}
}
return Object.fromEntries(
Object.entries(obj).filter(([, value]) => !check(value)),
)
}

View file

@ -1,7 +1,5 @@
import defaults from 'lodash/defaults'
import isNil from 'lodash/isNil'
import omitBy from 'lodash/omitBy'
import {objectToCamelCase} from '@/helpers/case'
import {omitBy, isNil} from '@/helpers/utils'
export default class AbstractModel {
@ -16,11 +14,14 @@ export default class AbstractModel {
* @param data
*/
constructor(data) {
data = objectToCamelCase(data)
// Put all data in our model while overriding those with a value of null or undefined with their defaults
defaults(this, omitBy(data, isNil), this.defaults())
Object.assign(
this,
this.defaults(),
omitBy(data, isNil),
)
}
/**

View file

@ -1,13 +1,10 @@
import TeamShareBaseModel from './teamShareBase'
import merge from 'lodash/merge'
export default class TeamListModel extends TeamShareBaseModel {
defaults() {
return merge(
super.defaults(),
{
return {
...super.defaults(),
listId: 0,
},
)
}
}
}

View file

@ -1,14 +1,11 @@
import UserModel from './user'
import merge from 'lodash/merge'
export default class TeamMemberModel extends UserModel {
defaults() {
return merge(
super.defaults(),
{
return {
...super.defaults(),
admin: false,
teamId: 0,
},
)
}
}
}

View file

@ -1,13 +1,10 @@
import TeamShareBaseModel from './teamShareBase'
import merge from 'lodash/merge'
export default class TeamNamespaceModel extends TeamShareBaseModel {
defaults() {
return merge(
super.defaults(),
{
return {
...super.defaults(),
namespaceId: 0,
},
)
}
}
}

View file

@ -1,14 +1,11 @@
import UserShareBaseModel from './userShareBase'
import merge from 'lodash/merge'
// This class extends the user share model with a 'rights' parameter which is used in sharing
export default class UserListModel extends UserShareBaseModel {
defaults() {
return merge(
super.defaults(),
{
return {
...super.defaults(),
listId: 0,
},
)
}
}
}

View file

@ -1,14 +1,11 @@
import UserShareBaseModel from './userShareBase'
import merge from 'lodash/merge'
// This class extends the user share model with a 'rights' parameter which is used in sharing
export default class UserNamespaceModel extends UserShareBaseModel {
defaults() {
return merge(
super.defaults(),
{
return {
...super.defaults(),
namespaceId: 0,
},
)
}
}
}

View file

@ -1,6 +1,4 @@
import axios from 'axios'
import reduce from 'lodash/reduce'
import replace from 'lodash/replace'
import {objectToSnakeCase} from '@/helpers/case'
import {getToken} from '@/helpers/auth'
@ -157,9 +155,10 @@ export default class AbstractService {
*/
getReplacedRoute(path, pathparams) {
let replacements = this.getRouteReplacements(path, pathparams)
return reduce(replacements, function (result, value, parameter) {
return replace(result, parameter, value)
}, path)
return Object.entries(replacements).reduce(
(result, [parameter, value]) => result.replace(parameter, value),
path,
)
}
/**

View file

@ -1,6 +1,6 @@
import Vue from 'vue'
import {findIndexById} from '@/helpers/find'
import {findIndexById} from '@/helpers/utils'
export default {
namespaced: true,

View file

@ -1,5 +1,4 @@
import Vue from 'vue'
import cloneDeep from 'lodash/cloneDeep'
import BucketService from '../../services/bucket'
import {filterObject} from '@/helpers/filterObject'
@ -206,7 +205,7 @@ export default {
const cancel = setLoading(ctx, 'kanban')
ctx.commit('setBucketLoading', {bucketId: bucketId, loading: true})
const params = cloneDeep(ps)
const params = JSON.parse(JSON.stringify(ps))
params.sort_by = 'kanban_position'
params.order_by = 'asc'

View file

@ -2,10 +2,37 @@ import LabelService from '@/services/label'
import Vue from 'vue'
import {setLoading} from '@/store/helper'
/**
* Returns the labels by id if found
* @param {Object} state
* @param {Array} ids
* @returns {Array}
*/
function getLabelsByIds(state, ids) {
return Object.values(state.labels).filter(({id}) => ids.includes(id))
}
/**
* Checks if a list of labels is available in the store and filters them then query
* @param {Object} state
* @param {Array} labels
* @param {String} query
* @returns {Array}
*/
function filterLabelsByQuery(state, labels, query) {
const labelIds = labels.map(({id}) => id)
const foundLabels = getLabelsByIds(state, labelIds)
const labelQuery = query.toLowerCase()
return foundLabels.filter(({title}) => {
return !title.toLowerCase().includes(labelQuery)
})
}
export default {
namespaced: true,
// The state is an object which has the label ids as keys.
state: () => ({
// The labels are stored as an object which has the label ids as keys.
labels: {},
loaded: false,
}),
@ -25,6 +52,14 @@ export default {
state.loaded = loaded
},
},
getters: {
getLabelsByIds(state) {
return (ids) => getLabelsByIds(state, ids)
},
filterLabelsByQuery(state) {
return (...arr) => filterLabelsByQuery(state, ...arr)
},
},
actions: {
loadAllLabels(ctx, {forceLoad} = {}) {
if (ctx.state.loaded && !forceLoad) {

View file

@ -5511,7 +5511,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
lodash@4.17.21, lodash@4.x, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
lodash@4.x, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==