mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-03-23 16:23:25 +00:00
Resolve "Web frontend refactor"
This commit is contained in:
parent
63aa85eaa3
commit
be2c5c4d4f
149 changed files with 1019 additions and 874 deletions
backend
src/baserow/contrib/database/fields
tests/baserow/contrib/database/api/v0/rows
web-frontend
README.md
components/sidebar
config
intellij-idea.webpack.config.jsmodules/core
applicationTypes.js
assets/scss
_vendors.scss
abstracts
base
components
_alert.scss_box.scss_box_page.scss_button.scss_context.scss_dropdown.scss_form.scss_header.scss_layout.scss_loading.scss_menu.scss_modal.scss_notifications.scss_scrollbar.scss_select.scss_sidebar.scss_style_guide.scss_tree.scss
default.scssfields
views
components
Checkbox.vueContext.vueDropdown.vueDropdownItem.vueEditable.vueError.vueModal.vueScrollbars.vue
config.jsapplication
group
notifications
sidebar
directives
filters
head.jslayouts
middleware.jsmiddleware
mixins
module.jspages
plugin.jsplugins
routes.jsservices
static/img
store
utils
|
@ -1,3 +1,5 @@
|
|||
from decimal import Decimal
|
||||
|
||||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
|
@ -35,6 +37,8 @@ class NumberFieldType(FieldType):
|
|||
serializer_field_names = ['number_type', 'number_decimal_places', 'number_negative']
|
||||
|
||||
def prepare_value_for_db(self, instance, value):
|
||||
if instance.number_type == NUMBER_TYPE_DECIMAL:
|
||||
value = Decimal(value)
|
||||
if not instance.number_negative and value < 0:
|
||||
raise ValidationError(f'The value for field {instance.id} cannot be '
|
||||
f'negative.')
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from django.shortcuts import reverse
|
||||
|
@ -241,6 +243,31 @@ def test_update_row(api_client, data_fixture):
|
|||
assert getattr(row_2, f'field_{number_field.id}') == 50
|
||||
assert not getattr(row_2, f'field_{boolean_field.id}')
|
||||
|
||||
table_3 = data_fixture.create_database_table(user=user)
|
||||
decimal_field = data_fixture.create_number_field(
|
||||
table=table_3, order=0, name='Price', number_type='DECIMAL',
|
||||
number_decimal_places=2
|
||||
)
|
||||
model_3 = table_3.get_model()
|
||||
row_3 = model_3.objects.create()
|
||||
|
||||
url = reverse('api_v0:database:rows:item', kwargs={
|
||||
'table_id': table_3.id,
|
||||
'row_id': row_3.id
|
||||
})
|
||||
response = api_client.patch(
|
||||
url,
|
||||
{f'field_{decimal_field.id}': 10.22},
|
||||
format='json',
|
||||
HTTP_AUTHORIZATION=f'JWT {token}'
|
||||
)
|
||||
response_json = response.json()
|
||||
assert response.status_code == 200
|
||||
assert response_json[f'field_{decimal_field.id}'] == '10.22'
|
||||
|
||||
row_3.refresh_from_db()
|
||||
assert getattr(row_3, f'field_{decimal_field.id}') == Decimal('10.22')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_row(api_client, data_fixture):
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# baserow-web-frontend
|
||||
|
||||
> Baserow web frontend
|
||||
|
||||
## Build Setup
|
||||
|
||||
``` bash
|
||||
# install dependencies
|
||||
$ yarn install
|
||||
|
||||
# serve with hot reload at localhost:3000
|
||||
$ yarn run dev
|
||||
|
||||
# build for production and launch server
|
||||
$ yarn run build
|
||||
$ yarn start
|
||||
|
||||
# generate static project
|
||||
$ yarn run generate
|
||||
|
||||
# lint
|
||||
|
||||
```
|
||||
|
||||
For detailed explanation on how things work, checkout [Nuxt.js docs](https://nuxtjs.org).
|
|
@ -1,81 +0,0 @@
|
|||
<template>
|
||||
<Modal>
|
||||
<h2 class="box-title">Create new {{ applicationType.name | lowercase }}</h2>
|
||||
<div v-if="error" class="alert alert-error alert-has-icon">
|
||||
<div class="alert-icon">
|
||||
<i class="fas fa-exclamation"></i>
|
||||
</div>
|
||||
<div class="alert-title">{{ errorTitle }}</div>
|
||||
<p class="alert-content">{{ errorMessage }}</p>
|
||||
</div>
|
||||
<component
|
||||
:is="applicationType.getApplicationFormComponent()"
|
||||
ref="applicationForm"
|
||||
@submitted="submitted"
|
||||
>
|
||||
<div class="actions">
|
||||
<div class="align-right">
|
||||
<button
|
||||
class="button button-large"
|
||||
:class="{ 'button-loading': loading }"
|
||||
:disabled="loading"
|
||||
>
|
||||
Add {{ applicationType.name | lowercase }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modal from '@/mixins/modal'
|
||||
|
||||
export default {
|
||||
name: 'CreateApplicationModal',
|
||||
mixins: [modal],
|
||||
props: {
|
||||
applicationType: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
error: false,
|
||||
errorTitle: '',
|
||||
errorMessage: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submitted(values) {
|
||||
this.loading = true
|
||||
this.error = false
|
||||
this.$store
|
||||
.dispatch('application/create', {
|
||||
type: this.applicationType.type,
|
||||
values: values
|
||||
})
|
||||
.then(() => {
|
||||
this.loading = false
|
||||
this.$emit('created')
|
||||
this.hide()
|
||||
})
|
||||
.catch(error => {
|
||||
this.loading = false
|
||||
|
||||
if (error.handler) {
|
||||
const message = error.handler.getMessage('group')
|
||||
this.error = true
|
||||
this.errorTitle = message.title
|
||||
this.errorMessage = message.message
|
||||
error.handler.handled()
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,77 +1,11 @@
|
|||
export default {
|
||||
mode: 'universal',
|
||||
head: {
|
||||
title: 'Baserow',
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
|
||||
],
|
||||
link: [
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/img/favicon_16.png',
|
||||
sizes: '16x16'
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/img/favicon_32.png',
|
||||
sizes: '32x32'
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/img/favicon_48.png',
|
||||
sizes: '64x64'
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/img/favicon_192.png',
|
||||
sizes: '192x192'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
/**
|
||||
* Customize the progress-bar color
|
||||
*/
|
||||
loading: { color: '#fff' },
|
||||
|
||||
/**
|
||||
* Global CSS
|
||||
*/
|
||||
css: ['@/assets/scss/default.scss'],
|
||||
|
||||
/**
|
||||
* Plugins to load before mounting the App
|
||||
*/
|
||||
plugins: [
|
||||
{ src: '@/plugins/global.js' },
|
||||
{ src: '@/plugins/client.js' },
|
||||
{ src: '@/plugins/auth.js' },
|
||||
{ src: '@/plugins/vuelidate.js' }
|
||||
],
|
||||
|
||||
/**
|
||||
* Nuxt.js modules
|
||||
*/
|
||||
modules: [
|
||||
'@nuxtjs/axios',
|
||||
'cookie-universal-nuxt',
|
||||
'@/modules/database/module.js'
|
||||
],
|
||||
|
||||
router: {
|
||||
middleware: ['authentication']
|
||||
},
|
||||
modules: ['@/modules/core/module.js', '@/modules/database/module.js'],
|
||||
|
||||
env: {
|
||||
// The API base url, this will be prepended to the urls of the remote calls.
|
||||
baseUrl: 'http://backend:8000/api/v0',
|
||||
|
||||
// If the API base url must different at the browser side it can be changed
|
||||
// If the API base url must different at the client side it can be changed
|
||||
// here.
|
||||
publicBaseUrl: 'http://localhost:8000/api/v0'
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
/** This file can be used in combination with intellij idea so the @ path resolves **/
|
||||
/**
|
||||
* This file can be used in combination with intellij idea so the @baserow path
|
||||
* resolves.
|
||||
*/
|
||||
|
||||
const path = require('path')
|
||||
|
||||
|
@ -7,10 +10,7 @@ module.exports = {
|
|||
extensions: ['.js', '.json', '.vue', '.ts'],
|
||||
root: path.resolve(__dirname),
|
||||
alias: {
|
||||
'@': path.resolve(__dirname),
|
||||
'@@': path.resolve(__dirname),
|
||||
'~': path.resolve(__dirname),
|
||||
'~~': path.resolve(__dirname)
|
||||
'@baserow': path.resolve(__dirname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import ApplicationForm from '@/components/sidebar/ApplicationForm'
|
||||
import ApplicationForm from '@baserow/modules/core/components/application/ApplicationForm'
|
||||
|
||||
/**
|
||||
* The application type base class that can be extended when creating a plugin
|
|
@ -5,8 +5,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { isElement } from '@/utils/dom'
|
||||
import MoveToBody from '@/mixins/moveToBody'
|
||||
import { isElement } from '@baserow/modules/core/utils/dom'
|
||||
import MoveToBody from '@baserow/modules/core/mixins/moveToBody'
|
||||
|
||||
export default {
|
||||
name: 'Context',
|
|
@ -34,7 +34,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { isElement } from '@/utils/dom'
|
||||
import { isElement } from '@baserow/modules/core/utils/dom'
|
||||
|
||||
// @TODO focus on tab
|
||||
export default {
|
|
@ -10,7 +10,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { focusEnd } from '@/utils/dom'
|
||||
import { focusEnd } from '@baserow/modules/core/utils/dom'
|
||||
|
||||
export default {
|
||||
name: 'Editable',
|
23
web-frontend/modules/core/components/Error.vue
Normal file
23
web-frontend/modules/core/components/Error.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div v-if="error.visible" class="alert alert-error alert-has-icon">
|
||||
<div class="alert-icon">
|
||||
<i class="fas fa-exclamation"></i>
|
||||
</div>
|
||||
<div class="alert-title">{{ error.title }}</div>
|
||||
<p class="alert-content">{{ error.message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* This component works the best if the parent has the error mixin.
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
error: {
|
||||
required: true,
|
||||
type: Object
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -15,7 +15,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import MoveToBody from '@/mixins/moveToBody'
|
||||
import MoveToBody from '@baserow/modules/core/mixins/moveToBody'
|
||||
|
||||
export default {
|
||||
name: 'Modal',
|
|
@ -20,7 +20,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { floor, ceil } from '@/utils/number'
|
||||
import { floor, ceil } from '@baserow/modules/core/utils/number'
|
||||
|
||||
/**
|
||||
* This component will render custom scrollbars to a scrollable div. They will
|
|
@ -26,7 +26,7 @@
|
|||
<script>
|
||||
import { required } from 'vuelidate/lib/validators'
|
||||
|
||||
import form from '@/mixins/form'
|
||||
import form from '@baserow/modules/core/mixins/form'
|
||||
|
||||
export default {
|
||||
name: 'ApplicationForm',
|
|
@ -25,8 +25,8 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import CreateApplicationModal from '@/components/sidebar/CreateApplicationModal'
|
||||
import context from '@/mixins/context'
|
||||
import CreateApplicationModal from '@baserow/modules/core/components/application/CreateApplicationModal'
|
||||
import context from '@baserow/modules/core/mixins/context'
|
||||
|
||||
export default {
|
||||
name: 'CreateApplicationContext',
|
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<Modal>
|
||||
<h2 class="box-title">Create new {{ applicationType.name | lowercase }}</h2>
|
||||
<Error :error="error"></Error>
|
||||
<component
|
||||
:is="applicationType.getApplicationFormComponent()"
|
||||
ref="applicationForm"
|
||||
@submitted="submitted"
|
||||
>
|
||||
<div class="actions">
|
||||
<div class="align-right">
|
||||
<button
|
||||
class="button button-large"
|
||||
:class="{ 'button-loading': loading }"
|
||||
:disabled="loading"
|
||||
>
|
||||
Add {{ applicationType.name | lowercase }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modal from '@baserow/modules/core/mixins/modal'
|
||||
import error from '@baserow/modules/core/mixins/error'
|
||||
|
||||
export default {
|
||||
name: 'CreateApplicationModal',
|
||||
mixins: [modal, error],
|
||||
props: {
|
||||
applicationType: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async submitted(values) {
|
||||
this.loading = true
|
||||
this.hideError()
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('application/create', {
|
||||
type: this.applicationType.type,
|
||||
values: values
|
||||
})
|
||||
this.loading = false
|
||||
this.$emit('created')
|
||||
this.hide()
|
||||
} catch (error) {
|
||||
this.loading = false
|
||||
this.handleError(error, 'application')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<Modal>
|
||||
<h2 class="box-title">Create new group</h2>
|
||||
<Error :error="error"></Error>
|
||||
<GroupForm ref="groupForm" @submitted="submitted">
|
||||
<div class="actions">
|
||||
<div class="align-right">
|
||||
|
@ -18,31 +19,33 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import GroupForm from './GroupForm'
|
||||
import modal from '@baserow/modules/core/mixins/modal'
|
||||
import error from '@baserow/modules/core/mixins/error'
|
||||
|
||||
import modal from '@/mixins/modal'
|
||||
import GroupForm from './GroupForm'
|
||||
|
||||
export default {
|
||||
name: 'CreateGroupModal',
|
||||
components: { GroupForm },
|
||||
mixins: [modal],
|
||||
mixins: [modal, error],
|
||||
data() {
|
||||
return {
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submitted(values) {
|
||||
async submitted(values) {
|
||||
this.loading = true
|
||||
this.$store
|
||||
.dispatch('group/create', values)
|
||||
.then(() => {
|
||||
this.loading = false
|
||||
this.hide()
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
this.hideError()
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('group/create', values)
|
||||
this.loading = false
|
||||
this.hide()
|
||||
} catch (error) {
|
||||
this.loading = false
|
||||
this.handleError(error, 'group')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@
|
|||
<script>
|
||||
import { required } from 'vuelidate/lib/validators'
|
||||
|
||||
import form from '@/mixins/form'
|
||||
import form from '@baserow/modules/core/mixins/form'
|
||||
|
||||
export default {
|
||||
name: 'GroupForm',
|
|
@ -39,9 +39,9 @@
|
|||
<script>
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
|
||||
import CreateGroupModal from '@/components/group/CreateGroupModal'
|
||||
import GroupsContextItem from '@/components/group/GroupsContextItem'
|
||||
import context from '@/mixins/context'
|
||||
import CreateGroupModal from '@baserow/modules/core/components/group/CreateGroupModal'
|
||||
import GroupsContextItem from '@baserow/modules/core/components/group/GroupsContextItem'
|
||||
import context from '@baserow/modules/core/mixins/context'
|
||||
|
||||
export default {
|
||||
name: 'GroupsContext',
|
|
@ -41,7 +41,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { notifyIf } from '@/utils/error'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
export default {
|
||||
name: 'GroupsContextItem',
|
||||
|
@ -59,51 +59,46 @@ export default {
|
|||
this.$refs.context.hide()
|
||||
this.$refs.rename.edit()
|
||||
},
|
||||
renameGroup(group, event) {
|
||||
async renameGroup(group, event) {
|
||||
this.setLoading(group, true)
|
||||
|
||||
this.$store
|
||||
.dispatch('group/update', {
|
||||
try {
|
||||
await this.$store.dispatch('group/update', {
|
||||
group,
|
||||
values: {
|
||||
name: event.value
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.$refs.rename.set(event.oldValue)
|
||||
notifyIf(error, 'group')
|
||||
})
|
||||
.then(() => {
|
||||
this.setLoading(group, false)
|
||||
})
|
||||
} catch (error) {
|
||||
this.$refs.rename.set(event.oldValue)
|
||||
notifyIf(error, 'group')
|
||||
}
|
||||
|
||||
this.setLoading(group, false)
|
||||
},
|
||||
selectGroup(group) {
|
||||
async selectGroup(group) {
|
||||
this.setLoading(group, true)
|
||||
|
||||
this.$store
|
||||
.dispatch('group/select', group)
|
||||
.then(() => {
|
||||
this.$emit('selected')
|
||||
})
|
||||
.catch(error => {
|
||||
notifyIf(error, 'group')
|
||||
})
|
||||
.then(() => {
|
||||
this.setLoading(group, false)
|
||||
})
|
||||
try {
|
||||
await this.$store.dispatch('group/select', group)
|
||||
this.$emit('selected')
|
||||
} catch (error) {
|
||||
notifyIf(error, 'group')
|
||||
}
|
||||
|
||||
this.setLoading(group, false)
|
||||
},
|
||||
deleteGroup(group) {
|
||||
async deleteGroup(group) {
|
||||
this.$refs.context.hide()
|
||||
this.setLoading(group, true)
|
||||
|
||||
this.$store
|
||||
.dispatch('group/delete', group)
|
||||
.catch(error => {
|
||||
notifyIf(error, 'group')
|
||||
})
|
||||
.then(() => {
|
||||
this.setLoading(group, false)
|
||||
})
|
||||
try {
|
||||
await this.$store.dispatch('group/delete', group)
|
||||
} catch (error) {
|
||||
notifyIf(error, 'group')
|
||||
}
|
||||
|
||||
this.setLoading(group, false)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
import Notification from '@/components/notifications/Notification'
|
||||
import Notification from '@baserow/modules/core/components/notifications/Notification'
|
||||
|
||||
export default {
|
||||
name: 'Notifications',
|
|
@ -31,8 +31,8 @@
|
|||
<script>
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
|
||||
import SidebarApplication from '@/components/sidebar/SidebarApplication'
|
||||
import CreateApplicationContext from '@/components/sidebar/CreateApplicationContext'
|
||||
import SidebarApplication from '@baserow/modules/core/components/sidebar/SidebarApplication'
|
||||
import CreateApplicationContext from '@baserow/modules/core/components/application/CreateApplicationContext'
|
||||
|
||||
export default {
|
||||
name: 'Sidebar',
|
|
@ -57,7 +57,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { notifyIf } from '@/utils/error'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
export default {
|
||||
name: 'SidebarApplication',
|
||||
|
@ -78,31 +78,32 @@ export default {
|
|||
this.$refs.context.hide()
|
||||
this.$refs.rename.edit()
|
||||
},
|
||||
renameApplication(application, event) {
|
||||
async renameApplication(application, event) {
|
||||
this.setLoading(application, true)
|
||||
|
||||
this.$store
|
||||
.dispatch('application/update', {
|
||||
try {
|
||||
await this.$store.dispatch('application/update', {
|
||||
application,
|
||||
values: {
|
||||
name: event.value
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
this.$refs.rename.set(event.oldValue)
|
||||
notifyIf(error, 'application')
|
||||
})
|
||||
.then(() => {
|
||||
this.setLoading(application, false)
|
||||
})
|
||||
} catch (error) {
|
||||
this.$refs.rename.set(event.oldValue)
|
||||
notifyIf(error, 'application')
|
||||
}
|
||||
|
||||
this.setLoading(application, false)
|
||||
},
|
||||
selectApplication(application) {
|
||||
async selectApplication(application) {
|
||||
// If there is no route associated with the application we just change the
|
||||
// selected state.
|
||||
if (application._.type.routeName === null) {
|
||||
this.$store.dispatch('application/select', application).catch(error => {
|
||||
try {
|
||||
await this.$store.dispatch('application/select', application)
|
||||
} catch (error) {
|
||||
notifyIf(error, 'group')
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -125,18 +126,17 @@ export default {
|
|||
}
|
||||
)
|
||||
},
|
||||
deleteApplication(application) {
|
||||
async deleteApplication(application) {
|
||||
this.$refs.context.hide()
|
||||
this.setLoading(application, true)
|
||||
|
||||
this.$store
|
||||
.dispatch('application/delete', application)
|
||||
.catch(error => {
|
||||
notifyIf(error, 'application')
|
||||
})
|
||||
.then(() => {
|
||||
this.setLoading(application, false)
|
||||
})
|
||||
try {
|
||||
await this.$store.dispatch('application/delete', application)
|
||||
} catch (error) {
|
||||
notifyIf(error, 'application')
|
||||
}
|
||||
|
||||
this.setLoading(application, false)
|
||||
},
|
||||
getSelectedApplicationComponent(application) {
|
||||
const type = this.$store.getters['application/getType'](application.type)
|
0
web-frontend/modules/core/config.js
Normal file
0
web-frontend/modules/core/config.js
Normal file
34
web-frontend/modules/core/head.js
Normal file
34
web-frontend/modules/core/head.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
export default {
|
||||
title: 'Baserow',
|
||||
titleTemplate: '%s // Baserow',
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
|
||||
],
|
||||
link: [
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/img/favicon_16.png',
|
||||
sizes: '16x16'
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/img/favicon_32.png',
|
||||
sizes: '32x32'
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/img/favicon_48.png',
|
||||
sizes: '64x64'
|
||||
},
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/img/favicon_192.png',
|
||||
sizes: '192x192'
|
||||
}
|
||||
]
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
<div class="layout-col-1 menu">
|
||||
<ul class="menu-items">
|
||||
<li class="menu-item">
|
||||
<nuxt-link :to="{ name: 'app' }" class="menu-link">
|
||||
<nuxt-link :to="{ name: 'dashboard' }" class="menu-link">
|
||||
<i class="fas fa-tachometer-alt"></i>
|
||||
<span class="menu-link-text">Dashboard</span>
|
||||
</nuxt-link>
|
||||
|
@ -55,7 +55,7 @@
|
|||
<div class="sidebar-content-wrapper">
|
||||
<nav class="sidebar-content">
|
||||
<div class="sidebar-title">
|
||||
<img src="@/static/img/logo.svg" alt="" />
|
||||
<img src="@baserow/modules/core/static/img/logo.svg" alt="" />
|
||||
</div>
|
||||
<Sidebar></Sidebar>
|
||||
</nav>
|
||||
|
@ -77,9 +77,9 @@
|
|||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
import Notifications from '@/components/notifications/Notifications'
|
||||
import GroupsContext from '@/components/group/GroupsContext'
|
||||
import Sidebar from '@/components/sidebar/Sidebar'
|
||||
import Notifications from '@baserow/modules/core/components/notifications/Notifications'
|
||||
import GroupsContext from '@baserow/modules/core/components/group/GroupsContext'
|
||||
import Sidebar from '@baserow/modules/core/components/sidebar/Sidebar'
|
||||
|
||||
export default {
|
||||
// Application pages are pages that have the edit sidebar on the left side which
|
|
@ -11,7 +11,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Notifications from '@/components/notifications/Notifications'
|
||||
import Notifications from '@baserow/modules/core/components/notifications/Notifications'
|
||||
|
||||
export default {
|
||||
components: { Notifications }
|
10
web-frontend/modules/core/middleware.js
Normal file
10
web-frontend/modules/core/middleware.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import authentication from '@baserow/modules/core/middleware/authentication'
|
||||
import authenticated from '@baserow/modules/core/middleware/authenticated'
|
||||
import groupsAndApplications from '@baserow/modules/core/middleware/groupsAndApplications'
|
||||
|
||||
/* eslint-disable-next-line */
|
||||
import Middleware from './middleware'
|
||||
|
||||
Middleware.authentication = authentication
|
||||
Middleware.authenticated = authenticated
|
||||
Middleware.groupsAndApplications = groupsAndApplications
|
|
@ -8,6 +8,6 @@ export default function({ req, store, redirect }) {
|
|||
|
||||
// If the user is not authenticated we will redirect him to the login page.
|
||||
if (!store.getters['auth/isAuthenticated']) {
|
||||
redirect('/login')
|
||||
redirect({ name: 'login' })
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { getToken } from '@/utils/auth'
|
||||
import { getToken } from '@baserow/modules/core/utils/auth'
|
||||
|
||||
export default function({ store, req, app }) {
|
||||
// If nuxt generate, pass this middleware
|
|
@ -1,4 +1,4 @@
|
|||
import { getGroupCookie } from '@/utils/group'
|
||||
import { getGroupCookie } from '@baserow/modules/core/utils/group'
|
||||
|
||||
/**
|
||||
* This middleware will make sure that all the groups and applications belonging to
|
47
web-frontend/modules/core/mixins/error.js
Normal file
47
web-frontend/modules/core/mixins/error.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* This mixin works the best in combination with the Error component.
|
||||
*/
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
error: {
|
||||
visible: false,
|
||||
title: '',
|
||||
message: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Can be called after catching an error. If an handler is available the error
|
||||
* data is populated with the correct error message.
|
||||
*/
|
||||
handleError(error, name, specificErrorMap = null) {
|
||||
if (error.handler) {
|
||||
const message = error.handler.getMessage(name, specificErrorMap)
|
||||
this.showError(message)
|
||||
error.handler.handled()
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Populates the error data with the provided message. Can be called with an
|
||||
* error message object or with a title and message.
|
||||
*/
|
||||
showError(title, message = null) {
|
||||
this.error.visible = true
|
||||
|
||||
if (message === null) {
|
||||
this.error.title = title.title
|
||||
this.error.message = title.message
|
||||
} else {
|
||||
this.error.title = title
|
||||
this.error.message = message
|
||||
}
|
||||
},
|
||||
hideError() {
|
||||
this.error.visible = false
|
||||
}
|
||||
}
|
||||
}
|
82
web-frontend/modules/core/module.js
Normal file
82
web-frontend/modules/core/module.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
import path from 'path'
|
||||
import _ from 'lodash'
|
||||
import serveStatic from 'serve-static'
|
||||
|
||||
import { routes } from './routes'
|
||||
import head from './head'
|
||||
|
||||
export default function DatabaseModule(options) {
|
||||
/**
|
||||
* This function adds a plugin, but rather then prepending it to the list it will
|
||||
* be appended.
|
||||
*/
|
||||
this.appendPlugin = template => {
|
||||
this.addPlugin(template)
|
||||
this.options.plugins.push(this.options.plugins.splice(0, 1)[0])
|
||||
}
|
||||
|
||||
// Baserow must be run in universal mode.
|
||||
this.options.mode = 'universal'
|
||||
|
||||
// Set the default head object, but override the configured head.
|
||||
// @TODO if a child is a list the new children must be appended instead of overriden.
|
||||
this.options.head = _.merge({}, head, this.options.head)
|
||||
|
||||
// Store must be true in order for the store to be injected into the context.
|
||||
this.options.store = true
|
||||
|
||||
// Register new alias to the web-frontend directory.
|
||||
this.options.alias['@baserow'] = path.resolve(__dirname, '../../')
|
||||
|
||||
// The core depends on these modules.
|
||||
this.requireModule('@nuxtjs/axios')
|
||||
this.requireModule('cookie-universal-nuxt')
|
||||
|
||||
// Serve the static directory
|
||||
// @TODO we might need to change some things here for production. (See:
|
||||
// https://github.com/nuxt/nuxt.js/blob/5a6cde3ebc23f04e89c30a4196a9b7d116b6d675/
|
||||
// packages/server/src/server.js)
|
||||
const staticMiddleware = serveStatic(
|
||||
path.resolve(__dirname, 'static'),
|
||||
this.options.render.static
|
||||
)
|
||||
this.addServerMiddleware(staticMiddleware)
|
||||
|
||||
// Add the layouts
|
||||
this.addLayout(path.resolve(__dirname, 'layouts/app.vue'), 'app')
|
||||
this.addLayout(path.resolve(__dirname, 'layouts/login.vue'), 'login')
|
||||
|
||||
const plugins = [
|
||||
'middleware.js',
|
||||
'plugin.js',
|
||||
'plugins/auth.js',
|
||||
'plugins/clientHandler.js',
|
||||
'plugins/global.js',
|
||||
'plugins/vuelidate.js'
|
||||
]
|
||||
plugins.forEach(plugin => {
|
||||
this.addPlugin({
|
||||
src: path.resolve(__dirname, plugin)
|
||||
})
|
||||
})
|
||||
|
||||
this.extendRoutes(configRoutes => {
|
||||
// Remove all the routes created by nuxt.
|
||||
let i = configRoutes.length
|
||||
while (i--) {
|
||||
if (configRoutes[i].component.indexOf('/@nuxt/') !== -1) {
|
||||
configRoutes.splice(i, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the routes from the ./routes.js.
|
||||
configRoutes.push(...routes)
|
||||
})
|
||||
|
||||
// Add a default authentication middleware. In order to add a new middleware the
|
||||
// middleware.js file has to be changed.
|
||||
this.options.router.middleware.push('authentication')
|
||||
|
||||
// Add the main scss file which contains all the generic scss code.
|
||||
this.options.css.push(path.resolve(__dirname, 'assets/scss/default.scss'))
|
||||
}
|
|
@ -35,6 +35,11 @@ import { mapState } from 'vuex'
|
|||
|
||||
export default {
|
||||
layout: 'app',
|
||||
head() {
|
||||
return {
|
||||
title: 'Dashboard'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: state => state.auth.user,
|
|
@ -5,7 +5,7 @@
|
|||
<script>
|
||||
export default {
|
||||
fetch({ store, redirect }) {
|
||||
const name = store.getters['auth/isAuthenticated'] ? 'app' : 'login'
|
||||
const name = store.getters['auth/isAuthenticated'] ? 'dashboard' : 'login'
|
||||
redirect({ name: name })
|
||||
}
|
||||
}
|
|
@ -1,15 +1,9 @@
|
|||
<template>
|
||||
<div>
|
||||
<h1 class="box-title">
|
||||
<img src="@/static/img/logo.svg" alt="" />
|
||||
<img src="@baserow/modules/core/static/img/logo.svg" alt="" />
|
||||
</h1>
|
||||
<div v-if="error" class="alert alert-error alert-has-icon">
|
||||
<div class="alert-icon">
|
||||
<i class="fas fa-exclamation"></i>
|
||||
</div>
|
||||
<div class="alert-title">{{ errorTitle }}</div>
|
||||
<p class="alert-content">{{ errorMessage }}</p>
|
||||
</div>
|
||||
<Error :error="error"></Error>
|
||||
<form @submit.prevent="login">
|
||||
<div class="control">
|
||||
<label class="control-label">E-mail address</label>
|
||||
|
@ -46,7 +40,7 @@
|
|||
<div class="actions">
|
||||
<ul class="action-links">
|
||||
<li>
|
||||
<nuxt-link :to="{ name: 'login-signup' }">
|
||||
<nuxt-link :to="{ name: 'signup' }">
|
||||
Sign up
|
||||
</nuxt-link>
|
||||
</li>
|
||||
|
@ -66,9 +60,11 @@
|
|||
|
||||
<script>
|
||||
import { required, email } from 'vuelidate/lib/validators'
|
||||
import error from '@baserow/modules/core/mixins/error'
|
||||
|
||||
export default {
|
||||
layout: 'login',
|
||||
mixins: [error],
|
||||
head() {
|
||||
return {
|
||||
title: 'Login'
|
||||
|
@ -80,10 +76,7 @@ export default {
|
|||
credentials: {
|
||||
email: '',
|
||||
password: ''
|
||||
},
|
||||
error: false,
|
||||
errorTitle: '',
|
||||
errorMessage: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
|
@ -100,33 +93,32 @@ export default {
|
|||
}
|
||||
|
||||
this.loading = true
|
||||
this.error = false
|
||||
this.hideError()
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('auth/login', {
|
||||
email: this.credentials.email,
|
||||
password: this.credentials.password
|
||||
})
|
||||
this.$nuxt.$router.push({ name: 'app' })
|
||||
this.$nuxt.$router.push({ name: 'dashboard' })
|
||||
} catch (error) {
|
||||
if (error.handler) {
|
||||
const response = error.handler.response
|
||||
// Because the API server does not yet respond with proper error codes we
|
||||
// manually have to add the error here.
|
||||
if (response && response.status === 400) {
|
||||
this.errorTitle = 'Incorrect credentials'
|
||||
this.errorMessage =
|
||||
this.showError(
|
||||
'Incorrect credentials',
|
||||
'The provided e-mail address or password is ' + 'incorrect.'
|
||||
)
|
||||
this.credentials.password = ''
|
||||
this.$v.$reset()
|
||||
this.$refs.password.focus()
|
||||
} else {
|
||||
const message = error.handler.getMessage('login')
|
||||
this.errorTitle = message.title
|
||||
this.errorMessage = message.message
|
||||
this.showError(message)
|
||||
}
|
||||
|
||||
this.error = true
|
||||
this.loading = false
|
||||
error.handler.handled()
|
||||
} else {
|
|
@ -1,13 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<h1 class="box-title">Sign up</h1>
|
||||
<div v-if="error" class="alert alert-error alert-has-icon">
|
||||
<div class="alert-icon">
|
||||
<i class="fas fa-exclamation"></i>
|
||||
</div>
|
||||
<div class="alert-title">{{ errorTitle }}</div>
|
||||
<p class="alert-content">{{ errorMessage }}</p>
|
||||
</div>
|
||||
<Error :error="error"></Error>
|
||||
<form @submit.prevent="register">
|
||||
<div class="control">
|
||||
<label class="control-label">E-mail address</label>
|
||||
|
@ -95,10 +89,12 @@
|
|||
<script>
|
||||
import { required, email, sameAs, minLength } from 'vuelidate/lib/validators'
|
||||
|
||||
import { ResponseErrorMessage } from '@/plugins/client'
|
||||
import { ResponseErrorMessage } from '@baserow/modules/core/plugins/clientHandler'
|
||||
import error from '@baserow/modules/core/mixins/error'
|
||||
|
||||
export default {
|
||||
layout: 'login',
|
||||
mixins: [error],
|
||||
head() {
|
||||
return {
|
||||
title: 'Create new account'
|
||||
|
@ -125,10 +121,7 @@ export default {
|
|||
name: '',
|
||||
password: '',
|
||||
passwordConfirm: ''
|
||||
},
|
||||
error: false,
|
||||
errorTitle: '',
|
||||
errorMessage: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -139,7 +132,7 @@ export default {
|
|||
}
|
||||
|
||||
this.loading = true
|
||||
this.error = false
|
||||
this.hideError()
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('auth/register', {
|
||||
|
@ -147,24 +140,15 @@ export default {
|
|||
email: this.account.email,
|
||||
password: this.account.password
|
||||
})
|
||||
this.$nuxt.$router.push({ name: 'app' })
|
||||
this.$nuxt.$router.push({ name: 'dashboard' })
|
||||
} catch (error) {
|
||||
this.loading = false
|
||||
|
||||
if (error.handler) {
|
||||
const message = error.handler.getMessage('signup', {
|
||||
ERROR_EMAIL_ALREADY_EXISTS: new ResponseErrorMessage(
|
||||
'User already exists.',
|
||||
'A user with the provided e-mail address already exists.'
|
||||
)
|
||||
})
|
||||
this.error = true
|
||||
this.errorTitle = message.title
|
||||
this.errorMessage = message.message
|
||||
error.handler.handled()
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
this.handleError(error, 'signup', {
|
||||
ERROR_EMAIL_ALREADY_EXISTS: new ResponseErrorMessage(
|
||||
'User already exists.',
|
||||
'A user with the provided e-mail address already exists.'
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -530,12 +530,17 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Notifications from '@/components/notifications/Notifications'
|
||||
import Notifications from '@baserow/modules/core/components/notifications/Notifications'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Notifications
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: 'Style guide'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
checkbox: false,
|
13
web-frontend/modules/core/plugin.js
Normal file
13
web-frontend/modules/core/plugin.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import applicationStore from '@baserow/modules/core/store/application'
|
||||
import authStore from '@baserow/modules/core/store/auth'
|
||||
import groupStore from '@baserow/modules/core/store/group'
|
||||
import notificationStore from '@baserow/modules/core/store/notification'
|
||||
import sidebarStore from '@baserow/modules/core/store/sidebar'
|
||||
|
||||
export default ({ store }) => {
|
||||
store.registerModule('application', applicationStore)
|
||||
store.registerModule('auth', authStore)
|
||||
store.registerModule('group', groupStore)
|
||||
store.registerModule('notification', notificationStore)
|
||||
store.registerModule('sidebar', sidebarStore)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { client } from '@/services/client'
|
||||
import { lowerCaseFirst } from '@/utils/string'
|
||||
import { client } from '@baserow/modules/core/services/client'
|
||||
import { lowerCaseFirst } from '@baserow/modules/core/utils/string'
|
||||
|
||||
export class ResponseErrorMessage {
|
||||
constructor(title, message) {
|
27
web-frontend/modules/core/plugins/global.js
Normal file
27
web-frontend/modules/core/plugins/global.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
import Context from '@baserow/modules/core/components/Context'
|
||||
import Modal from '@baserow/modules/core/components/Modal'
|
||||
import Editable from '@baserow/modules/core/components/Editable'
|
||||
import Dropdown from '@baserow/modules/core/components/Dropdown'
|
||||
import DropdownItem from '@baserow/modules/core/components/DropdownItem'
|
||||
import Checkbox from '@baserow/modules/core/components/Checkbox'
|
||||
import Scrollbars from '@baserow/modules/core/components/Scrollbars'
|
||||
import Error from '@baserow/modules/core/components/Error'
|
||||
|
||||
import lowercase from '@baserow/modules/core/filters/lowercase'
|
||||
|
||||
import scroll from '@baserow/modules/core/directives/scroll'
|
||||
|
||||
Vue.component('Context', Context)
|
||||
Vue.component('Modal', Modal)
|
||||
Vue.component('Editable', Editable)
|
||||
Vue.component('Dropdown', Dropdown)
|
||||
Vue.component('DropdownItem', DropdownItem)
|
||||
Vue.component('Checkbox', Checkbox)
|
||||
Vue.component('Scrollbars', Scrollbars)
|
||||
Vue.component('Error', Error)
|
||||
|
||||
Vue.filter('lowercase', lowercase)
|
||||
|
||||
Vue.directive('scroll', scroll)
|
29
web-frontend/modules/core/routes.js
Normal file
29
web-frontend/modules/core/routes.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import path from 'path'
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
name: 'index',
|
||||
path: '',
|
||||
component: path.resolve(__dirname, 'pages/index.vue')
|
||||
},
|
||||
{
|
||||
name: 'login',
|
||||
path: '/login',
|
||||
component: path.resolve(__dirname, 'pages/login.vue')
|
||||
},
|
||||
{
|
||||
name: 'signup',
|
||||
path: '/signup',
|
||||
component: path.resolve(__dirname, 'pages/signup.vue')
|
||||
},
|
||||
{
|
||||
name: 'dashboard',
|
||||
path: '/dashboard',
|
||||
component: path.resolve(__dirname, 'pages/dashboard.vue')
|
||||
},
|
||||
{
|
||||
name: 'style-guide',
|
||||
path: '/style-guide',
|
||||
component: path.resolve(__dirname, 'pages/style-guide.vue')
|
||||
}
|
||||
]
|
Before ![]() (image error) Size: 281 B After ![]() (image error) Size: 281 B ![]() ![]() |
Before ![]() (image error) Size: 742 B After ![]() (image error) Size: 742 B ![]() ![]() |
Before ![]() (image error) Size: 306 B After ![]() (image error) Size: 306 B ![]() ![]() |
Before ![]() (image error) Size: 347 B After ![]() (image error) Size: 347 B ![]() ![]() |
Before (image error) Size: 3.4 KiB After (image error) Size: 3.4 KiB |
|
@ -1,6 +1,6 @@
|
|||
import { ApplicationType } from '@/core/applicationTypes'
|
||||
import ApplicationService from '@/services/application'
|
||||
import { clone } from '@/utils/object'
|
||||
import { ApplicationType } from '@baserow/modules/core/applicationTypes'
|
||||
import ApplicationService from '@baserow/modules/core/services/application'
|
||||
import { clone } from '@baserow/modules/core/utils/object'
|
||||
|
||||
function populateApplication(application, getters) {
|
||||
const type = getters.getType(application.type)
|
||||
|
@ -87,24 +87,23 @@ export const actions = {
|
|||
/**
|
||||
* Fetches all the application of the authenticated user.
|
||||
*/
|
||||
fetchAll({ commit, getters }) {
|
||||
async fetchAll({ commit, getters }) {
|
||||
commit('SET_LOADING', true)
|
||||
|
||||
return ApplicationService.fetchAll()
|
||||
.then(({ data }) => {
|
||||
data.forEach((part, index, d) => {
|
||||
populateApplication(data[index], getters)
|
||||
})
|
||||
commit('SET_ITEMS', data)
|
||||
commit('SET_LOADING', false)
|
||||
commit('SET_LOADED', true)
|
||||
try {
|
||||
const { data } = await ApplicationService.fetchAll()
|
||||
data.forEach((part, index, d) => {
|
||||
populateApplication(data[index], getters)
|
||||
})
|
||||
.catch(error => {
|
||||
commit('SET_ITEMS', [])
|
||||
commit('SET_LOADING', false)
|
||||
commit('SET_ITEMS', data)
|
||||
commit('SET_LOADING', false)
|
||||
commit('SET_LOADED', true)
|
||||
} catch (error) {
|
||||
commit('SET_ITEMS', [])
|
||||
commit('SET_LOADING', false)
|
||||
|
||||
throw error
|
||||
})
|
||||
throw error
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Clears all the currently selected applications, this could be called when
|
||||
|
@ -129,7 +128,7 @@ export const actions = {
|
|||
* Creates a new application with the given type and values for the currently
|
||||
* selected group.
|
||||
*/
|
||||
create({ commit, getters, rootGetters, dispatch }, { type, values }) {
|
||||
async create({ commit, getters, rootGetters, dispatch }, { type, values }) {
|
||||
if (values.hasOwnProperty('type')) {
|
||||
throw new Error(
|
||||
'The key "type" is a reserved, but is already set on the ' +
|
||||
|
@ -141,48 +140,44 @@ export const actions = {
|
|||
throw new Error(`An application type with type "${type}" doesn't exist.`)
|
||||
}
|
||||
|
||||
const data = clone(values)
|
||||
data.type = type
|
||||
return ApplicationService.create(
|
||||
const postData = clone(values)
|
||||
postData.type = type
|
||||
|
||||
const { data } = await ApplicationService.create(
|
||||
rootGetters['group/selectedId'],
|
||||
data
|
||||
).then(({ data }) => {
|
||||
populateApplication(data, getters)
|
||||
commit('ADD_ITEM', data)
|
||||
})
|
||||
postData
|
||||
)
|
||||
populateApplication(data, getters)
|
||||
commit('ADD_ITEM', data)
|
||||
},
|
||||
/**
|
||||
* Updates the values of an existing application.
|
||||
*/
|
||||
update({ commit, dispatch, getters }, { application, values }) {
|
||||
return ApplicationService.update(application.id, values).then(
|
||||
({ data }) => {
|
||||
// Create a dict with only the values we want to update.
|
||||
const update = Object.keys(values).reduce((result, key) => {
|
||||
result[key] = data[key]
|
||||
return result
|
||||
}, {})
|
||||
commit('UPDATE_ITEM', { id: application.id, values: update })
|
||||
}
|
||||
)
|
||||
async update({ commit, dispatch, getters }, { application, values }) {
|
||||
const { data } = await ApplicationService.update(application.id, values)
|
||||
// Create a dict with only the values we want to update.
|
||||
const update = Object.keys(values).reduce((result, key) => {
|
||||
result[key] = data[key]
|
||||
return result
|
||||
}, {})
|
||||
commit('UPDATE_ITEM', { id: application.id, values: update })
|
||||
},
|
||||
/**
|
||||
* Deletes an existing application.
|
||||
*/
|
||||
delete({ commit, dispatch, getters }, application) {
|
||||
return ApplicationService.delete(application.id)
|
||||
.then(() => {
|
||||
const type = getters.getType(application.type)
|
||||
type.delete(application, this)
|
||||
async delete({ commit, dispatch, getters }, application) {
|
||||
try {
|
||||
await ApplicationService.delete(application.id)
|
||||
const type = getters.getType(application.type)
|
||||
type.delete(application, this)
|
||||
commit('DELETE_ITEM', application.id)
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
commit('DELETE_ITEM', application.id)
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response && error.response.status === 404) {
|
||||
commit('DELETE_ITEM', application.id)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Select an application.
|
||||
|
@ -232,3 +227,11 @@ export const getters = {
|
|||
return state.types[type]
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import jwtDecode from 'jwt-decode'
|
||||
|
||||
import AuthService from '@/services/auth'
|
||||
import { setToken, unsetToken } from '@/utils/auth'
|
||||
import { unsetGroupCookie } from '@/utils/group'
|
||||
import AuthService from '@baserow/modules/core/services/auth'
|
||||
import { setToken, unsetToken } from '@baserow/modules/core/utils/auth'
|
||||
import { unsetGroupCookie } from '@baserow/modules/core/utils/group'
|
||||
|
||||
export const state = () => ({
|
||||
refreshing: false,
|
||||
|
@ -30,58 +30,53 @@ export const actions = {
|
|||
* Authenticate a user by his email and password. If successful commit the
|
||||
* token to the state and start the refresh timeout to stay authenticated.
|
||||
*/
|
||||
login({ commit, dispatch }, { email, password }) {
|
||||
return AuthService.login(email, password).then(({ data }) => {
|
||||
setToken(data.token, this.app.$cookies)
|
||||
commit('SET_USER_DATA', data)
|
||||
dispatch('startRefreshTimeout')
|
||||
})
|
||||
async login({ commit, dispatch }, { email, password }) {
|
||||
const { data } = await AuthService.login(email, password)
|
||||
setToken(data.token, this.app.$cookies)
|
||||
commit('SET_USER_DATA', data)
|
||||
dispatch('startRefreshTimeout')
|
||||
},
|
||||
/**
|
||||
* Register a new user and immediately authenticate. If successful commit the
|
||||
* token to the state and start the refresh timeout to stay authenticated.
|
||||
*/
|
||||
register({ commit, dispatch }, { email, name, password }) {
|
||||
return AuthService.register(email, name, password, true).then(
|
||||
({ data }) => {
|
||||
setToken(data.token, this.app.$cookies)
|
||||
commit('SET_USER_DATA', data)
|
||||
dispatch('startRefreshTimeout')
|
||||
}
|
||||
)
|
||||
async register({ commit, dispatch }, { email, name, password }) {
|
||||
const { data } = await AuthService.register(email, name, password, true)
|
||||
setToken(data.token, this.app.$cookies)
|
||||
commit('SET_USER_DATA', data)
|
||||
dispatch('startRefreshTimeout')
|
||||
},
|
||||
/**
|
||||
* Logs off the user by removing the token as a cookie and clearing the user
|
||||
* data.
|
||||
*/
|
||||
logoff({ commit, dispatch }) {
|
||||
async logoff({ commit, dispatch }) {
|
||||
unsetToken(this.app.$cookies)
|
||||
unsetGroupCookie(this.app.$cookies)
|
||||
commit('CLEAR_USER_DATA')
|
||||
dispatch('group/clearAll', {}, { root: true })
|
||||
dispatch('group/unselect', {}, { root: true })
|
||||
await dispatch('group/clearAll', {}, { root: true })
|
||||
await dispatch('group/unselect', {}, { root: true })
|
||||
},
|
||||
/**
|
||||
* Refresh the existing token. If successful commit the new token and start a
|
||||
* new refresh timeout. If unsuccessful the existing cookie and user data is
|
||||
* cleared.
|
||||
*/
|
||||
refresh({ commit, state, dispatch }, token) {
|
||||
return AuthService.refresh(token)
|
||||
.then(({ data }) => {
|
||||
setToken(data.token, this.app.$cookies)
|
||||
commit('SET_USER_DATA', data)
|
||||
dispatch('startRefreshTimeout')
|
||||
})
|
||||
.catch(() => {
|
||||
// The token could not be refreshed, this means the token is no longer
|
||||
// valid and the user not logged in anymore.
|
||||
unsetToken(this.app.$cookies)
|
||||
commit('CLEAR_USER_DATA')
|
||||
async refresh({ commit, state, dispatch }, token) {
|
||||
try {
|
||||
const { data } = await AuthService.refresh(token)
|
||||
setToken(data.token, this.app.$cookies)
|
||||
commit('SET_USER_DATA', data)
|
||||
dispatch('startRefreshTimeout')
|
||||
} catch {
|
||||
// The token could not be refreshed, this means the token is no longer
|
||||
// valid and the user not logged in anymore.
|
||||
unsetToken(this.app.$cookies)
|
||||
commit('CLEAR_USER_DATA')
|
||||
|
||||
// @TODO we might want to do something here, trigger some event, show
|
||||
// show the user a login popup or redirect to the login page.
|
||||
})
|
||||
// @TODO we might want to do something here, trigger some event, show
|
||||
// show the user a login popup or redirect to the login page.
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Because the token expires within a configurable time, we need to keep
|
||||
|
@ -131,3 +126,11 @@ export const getters = {
|
|||
return state.token_data.exp - now
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
import GroupService from '@/services/group'
|
||||
import { setGroupCookie, unsetGroupCookie } from '@/utils/group'
|
||||
import GroupService from '@baserow/modules/core/services/group'
|
||||
import {
|
||||
setGroupCookie,
|
||||
unsetGroupCookie
|
||||
} from '@baserow/modules/core/utils/group'
|
||||
|
||||
function populateGroup(group) {
|
||||
group._ = { loading: false, selected: false }
|
||||
|
@ -88,59 +91,54 @@ export const actions = {
|
|||
/**
|
||||
* Fetches all the groups of an authenticated user.
|
||||
*/
|
||||
fetchAll({ commit }) {
|
||||
async fetchAll({ commit }) {
|
||||
commit('SET_LOADING', true)
|
||||
|
||||
return GroupService.fetchAll()
|
||||
.then(({ data }) => {
|
||||
commit('SET_LOADED', true)
|
||||
commit('SET_ITEMS', data)
|
||||
})
|
||||
.catch(() => {
|
||||
commit('SET_ITEMS', [])
|
||||
})
|
||||
.then(() => {
|
||||
commit('SET_LOADING', false)
|
||||
})
|
||||
try {
|
||||
const { data } = await GroupService.fetchAll()
|
||||
commit('SET_LOADED', true)
|
||||
commit('SET_ITEMS', data)
|
||||
} catch {
|
||||
commit('SET_ITEMS', [])
|
||||
}
|
||||
|
||||
commit('SET_LOADING', false)
|
||||
},
|
||||
/**
|
||||
* Creates a new group with the given values.
|
||||
*/
|
||||
create({ commit }, values) {
|
||||
return GroupService.create(values).then(({ data }) => {
|
||||
commit('ADD_ITEM', data)
|
||||
})
|
||||
async create({ commit }, values) {
|
||||
const { data } = await GroupService.create(values)
|
||||
commit('ADD_ITEM', data)
|
||||
},
|
||||
/**
|
||||
* Updates the values of the group with the provided id.
|
||||
*/
|
||||
update({ commit, dispatch }, { group, values }) {
|
||||
return GroupService.update(group.id, values).then(({ data }) => {
|
||||
// Create a dict with only the values we want to update.
|
||||
const update = Object.keys(values).reduce((result, key) => {
|
||||
result[key] = data[key]
|
||||
return result
|
||||
}, {})
|
||||
commit('UPDATE_ITEM', { id: group.id, values: update })
|
||||
})
|
||||
async update({ commit, dispatch }, { group, values }) {
|
||||
const { data } = await GroupService.update(group.id, values)
|
||||
// Create a dict with only the values we want to update.
|
||||
const update = Object.keys(values).reduce((result, key) => {
|
||||
result[key] = data[key]
|
||||
return result
|
||||
}, {})
|
||||
commit('UPDATE_ITEM', { id: group.id, values: update })
|
||||
},
|
||||
/**
|
||||
* Deletes an existing group with the provided id.
|
||||
*/
|
||||
delete({ commit, dispatch }, group) {
|
||||
return GroupService.delete(group.id)
|
||||
.then(() => {
|
||||
dispatch('forceDelete', group)
|
||||
})
|
||||
.catch(error => {
|
||||
// If the group to delete wasn't found we can just delete it from the
|
||||
// state.
|
||||
if (error.response && error.response.status === 404) {
|
||||
dispatch('forceDelete', group)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
})
|
||||
async delete({ commit, dispatch }, group) {
|
||||
try {
|
||||
await GroupService.delete(group.id)
|
||||
await dispatch('forceDelete', group)
|
||||
} catch (error) {
|
||||
// If the group to delete wasn't found we can just delete it from the
|
||||
// state.
|
||||
if (error.response && error.response.status === 404) {
|
||||
await dispatch('forceDelete', group)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Forcibly remove the group from the items without calling the server.
|
||||
|
@ -200,3 +198,11 @@ export const getters = {
|
|||
return state.selected.id
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { uuid } from '@/utils/string'
|
||||
import { uuid } from '@baserow/modules/core/utils/string'
|
||||
|
||||
export const state = () => ({
|
||||
items: []
|
||||
|
@ -44,3 +44,11 @@ export const actions = {
|
|||
}
|
||||
|
||||
export const getters = {}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
|
@ -22,3 +22,11 @@ export const getters = {
|
|||
return !!state.collapsed
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue