mirror of https://github.com/nocodb/nocodb
Pranav C
3 years ago
531 changed files with 158088 additions and 45061 deletions
@ -0,0 +1,42 @@
|
||||
# Setup |
||||
|
||||
#### Setting up dev environment |
||||
|
||||
- Clone `nocodb/nocodb` GitHub repo and checkout to `feat/v2` branch |
||||
```sh |
||||
git clone https://github.com/nocodb/nc |
||||
cd nocodb |
||||
``` |
||||
|
||||
- Navigate to `nocodb-sdk` package folder, install and build the package |
||||
```sh |
||||
cd packages/nocodb-sdk |
||||
npm install |
||||
npm run build |
||||
``` |
||||
|
||||
#### Running backend |
||||
|
||||
```sh |
||||
# Navigate to `nocodb` package and install dependencies |
||||
cd packages/nocodb |
||||
npm install |
||||
|
||||
# requires sqlite3 |
||||
|
||||
npm run watch:run |
||||
|
||||
# if you have mysql on localhost (set its password as password) |
||||
# npm run watch:run:mysql |
||||
``` |
||||
|
||||
#### Running frontend |
||||
|
||||
```sh |
||||
# Navigate to `nc-gui` package and install dependencies |
||||
cd packages/nc-gui |
||||
npm install |
||||
npm run dev |
||||
``` |
||||
|
||||
|
@ -1,34 +0,0 @@
|
||||
{ |
||||
"root": true, |
||||
"parser": "@typescript-eslint/parser", |
||||
"parserOptions": { "project": "./tsconfig.json" }, |
||||
"env": { "es6": true }, |
||||
"ignorePatterns": ["node_modules", "build", "coverage"], |
||||
"plugins": ["import", "eslint-comments", "functional"], |
||||
"extends": [ |
||||
"eslint:recommended", |
||||
"plugin:eslint-comments/recommended", |
||||
"plugin:@typescript-eslint/recommended", |
||||
"plugin:import/typescript", |
||||
"plugin:functional/lite", |
||||
"prettier", |
||||
"prettier/@typescript-eslint" |
||||
], |
||||
"globals": { "BigInt": true, "console": true, "WebAssembly": true }, |
||||
"rules": { |
||||
"@typescript-eslint/explicit-module-boundary-types": "off", |
||||
"eslint-comments/disable-enable-pair": [ |
||||
"error", |
||||
{ "allowWholeFile": true } |
||||
], |
||||
"eslint-comments/no-unused-disable": "error", |
||||
"import/order": [ |
||||
"error", |
||||
{ "newlines-between": "always", "alphabetize": { "order": "asc" } } |
||||
], |
||||
"sort-imports": [ |
||||
"error", |
||||
{ "ignoreDeclarationSort": true, "ignoreCase": true } |
||||
] |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -1,2 +0,0 @@
|
||||
export * from './lib/XcUIBuilder'; |
||||
export * from './lib/XcNotification'; |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,285 @@
|
||||
<template> |
||||
<v-dialog v-model="userEditDialog" :width="invite_token ? 700 :700" @close="invite_token = null"> |
||||
<v-card v-if="selectedUser" style="min-height: 100%" class="elevation-0"> |
||||
<v-card-title> |
||||
{{ $t('activity.share') }} : {{ $store.getters['project/GtrProjectName'] }} |
||||
|
||||
<div class="nc-header-border" /> |
||||
</v-card-title> |
||||
|
||||
<v-card-text> |
||||
<div> |
||||
<v-icon small> |
||||
mdi-account-outline |
||||
</v-icon> |
||||
<template v-if="invite_token"> |
||||
Copy Invite Token |
||||
</template> |
||||
<template v-else-if="selectedUser.id"> |
||||
Edit User |
||||
</template> |
||||
<template v-else> |
||||
<!-- Invite Team --> |
||||
{{ $t('activity.inviteTeam') }} |
||||
</template> |
||||
</div> |
||||
<div class="pa-4 nc-invite-container"> |
||||
<div v-if="invite_token" class="mt-6 align-center"> |
||||
<v-alert |
||||
v-ripple |
||||
type="success" |
||||
outlined |
||||
|
||||
class="pointer" |
||||
@click="clipboard(inviteUrl); $toast.success('Copied invite url to clipboard').goAway(3000)" |
||||
> |
||||
<template #append> |
||||
<v-icon color="green" class="ml-2"> |
||||
mdi-content-copy |
||||
</v-icon> |
||||
</template> |
||||
<div class="ellipsis d-100"> |
||||
{{ inviteUrl }} |
||||
</div> |
||||
</v-alert> |
||||
|
||||
<p class="caption grey--text mt-3"> |
||||
{{ $t('msg.info.userInviteNoSMTP') }} |
||||
<!-- Looks like you have not configured mailer yet! <br>Please copy above --> |
||||
<!-- invite --> |
||||
<!-- link and send it to --> |
||||
{{ invite_token && (invite_token.email || invite_token.emails && invite_token.emails.join(', ')) }}. |
||||
</p> |
||||
|
||||
<div class="text-right"> |
||||
<!--tooltip="Invite more users"--> |
||||
<x-btn |
||||
:tooltip="$t('tooltip.inviteMore')" |
||||
small |
||||
outlined |
||||
btn.class="grey--text" |
||||
@click="clickInviteMore" |
||||
> |
||||
<v-icon small color="grey" class="mr-1"> |
||||
mdi-account-multiple-plus-outline |
||||
</v-icon> |
||||
<!--Invite more--> |
||||
{{ $t('activity.inviteMore') }} |
||||
</x-btn> |
||||
</div> |
||||
|
||||
<!-- todo: show error message if failed--> |
||||
</div> |
||||
<template v-else> |
||||
<v-form ref="form" v-model="valid" @submit.prevent="saveUser"> |
||||
<v-row class="my-0"> |
||||
<v-col cols="8" class="py-0"> |
||||
<!--hint="You can add multiple comma(,) separated emails"--> |
||||
<v-text-field |
||||
ref="email" |
||||
v-model="selectedUser.email" |
||||
:disabled="!!selectedUser.id" |
||||
dense |
||||
validate-on-blur |
||||
outlined |
||||
:rules="emailRules" |
||||
class="caption" |
||||
:hint="$t('msg.info.addMultipleUsers')" |
||||
label="Email" |
||||
@input="edited=true" |
||||
> |
||||
<template #label> |
||||
<span class="caption"> |
||||
<!-- Email --> |
||||
{{ $t('labels.email') }} |
||||
</span> |
||||
</template> |
||||
</v-text-field> |
||||
</v-col> |
||||
<v-col cols="4" class="py-0"> |
||||
<!--label="Select User Role"--> |
||||
<v-combobox |
||||
v-model="selectedRoles" |
||||
outlined |
||||
:rules="roleRules" |
||||
class="role-select caption" |
||||
hide-details="auto" |
||||
:items="roles" |
||||
:label="$t('labels.selectUserRole')" |
||||
dense |
||||
deletable-chips |
||||
@change="edited = true" |
||||
> |
||||
<template #selection="{item}"> |
||||
<v-chip small :color="rolesColors[item]"> |
||||
{{ item }} |
||||
</v-chip> |
||||
</template> |
||||
<template #item="{item}"> |
||||
<div> |
||||
<div>{{ item }}</div> |
||||
<div class="mb-2 caption grey--text"> |
||||
{{ roleDescriptions[item] }} |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</v-combobox> |
||||
</v-col> |
||||
</v-row> |
||||
</v-form> |
||||
<div class="text-center mt-0"> |
||||
<x-btn |
||||
v-ge="['rows','save']" |
||||
:tooltip="$t('tooltip.saveChanges')" |
||||
color="primary" |
||||
|
||||
btn.class="nc-invite-or-save-btn" |
||||
@click="saveUser" |
||||
> |
||||
<v-icon small left> |
||||
{{ selectedUser.id ? 'save' : 'mdi-send' }} |
||||
</v-icon> |
||||
{{ selectedUser.id ? $t('general.save') : $t('activity.invite') }} |
||||
</x-btn> |
||||
</div> |
||||
</template> |
||||
<!-- </v-card-actions>--> |
||||
</div> |
||||
</v-card-text> |
||||
<v-card-text> |
||||
<share-base /> |
||||
</v-card-text> |
||||
</v-card> |
||||
</v-dialog> |
||||
</template> |
||||
|
||||
<script> |
||||
import { isEmail } from '~/helpers' |
||||
import { enumColor } from '~/components/project/spreadsheet/helpers/colors' |
||||
import ShareBase from '~/components/base/shareBase' |
||||
|
||||
export default { |
||||
name: 'ShareOrInviteModal', |
||||
components: { ShareBase }, |
||||
props: { |
||||
value: Boolean |
||||
}, |
||||
data: () => ({ |
||||
roles: ['creator', 'editor', 'commenter', 'viewer'], |
||||
selectedUser: {}, |
||||
invite_token: null, |
||||
valid: null, |
||||
emailRules: [ |
||||
v => !!v || 'E-mail is required', |
||||
(v) => { |
||||
const invalidEmails = (v || '').split(/\s*,\s*/).filter(e => !isEmail(e)) |
||||
return !invalidEmails.length || `"${invalidEmails.join(', ')}" - invalid email` |
||||
} |
||||
], |
||||
roleRules: [ |
||||
v => !!v || 'User Role is required', |
||||
v => ['creator', 'editor', 'commenter', 'viewer'].includes(v) || 'invalid user role' |
||||
], |
||||
userList: [], |
||||
roleDescriptions: {}, |
||||
deleteUserType: '' |
||||
}), |
||||
computed: { |
||||
userEditDialog: { |
||||
get() { |
||||
return this.value |
||||
}, |
||||
set(v) { |
||||
this.$emit('input', v) |
||||
} |
||||
}, |
||||
inviteUrl() { |
||||
return this.invite_token ? `${location.origin}${location.pathname}#/user/authentication/signup/${this.invite_token.invite_token}` : null |
||||
}, |
||||
rolesColors() { |
||||
const colors = this.$store.state.windows.darkTheme ? enumColor.dark : enumColor.light |
||||
return this.roles.reduce((o, r, i) => { |
||||
o[r] = colors[i % colors.length] |
||||
return o |
||||
}, {}) |
||||
}, |
||||
|
||||
selectedRoles: { |
||||
get() { |
||||
return (this.selectedUser && this.selectedUser.roles ? this.selectedUser.roles.split(',') : []).sort((a, b) => this.roleNames.indexOf(a) - this.roleNames.indexOf(a))[0] |
||||
}, |
||||
set(roles) { |
||||
if (this.selectedUser) { |
||||
this.selectedUser.roles = roles // .filter(Boolean).join(',') |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
methods: { |
||||
|
||||
async saveUser() { |
||||
this.validate = true |
||||
await this.$nextTick() |
||||
if (this.loading || !this.$refs.form.validate() || !this.selectedUser) { |
||||
return |
||||
} |
||||
this.$tele.emit(`user-mgmt:add:${this.selectedUser.roles}`) |
||||
|
||||
if (!this.edited) { |
||||
this.userEditDialog = false |
||||
} |
||||
|
||||
try { |
||||
let data |
||||
if (this.selectedUser.id) { |
||||
await this.$api.auth.projectUserUpdate(this.$route.params.project_id, this.selectedUser.id, { |
||||
roles: this.selectedUser.roles, |
||||
email: this.selectedUser.email, |
||||
project_id: this.$route.params.project_id, |
||||
projectName: this.$store.getters['project/GtrProjectName'] |
||||
}) |
||||
} else { |
||||
data = (await this.$api.auth.projectUserAdd(this.$route.params.project_id, { |
||||
...this.selectedUser, |
||||
project_id: this.$route.params.project_id, |
||||
projectName: this.$store.getters['project/GtrProjectName'] |
||||
})) |
||||
} |
||||
this.$toast.success('Successfully updated the user details').goAway(3000) |
||||
this.$emit('saved') |
||||
if (data && data.invite_token) { |
||||
this.invite_token = data |
||||
// todo: bring anim |
||||
// this.simpleAnim() |
||||
return |
||||
} |
||||
} catch (e) { |
||||
this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000) |
||||
} |
||||
|
||||
this.userEditDialog = false |
||||
}, |
||||
|
||||
clickInviteMore() { |
||||
this.$tele.emit('user-mgmt:invite-more') |
||||
this.invite_token = null |
||||
this.selectedUser = { roles: 'editor' } |
||||
}, |
||||
clipboard(str) { |
||||
const el = document.createElement('textarea') |
||||
el.addEventListener('focusin', e => e.stopPropagation()) |
||||
el.value = str |
||||
document.body.appendChild(el) |
||||
el.select() |
||||
document.execCommand('copy') |
||||
document.body.removeChild(el) |
||||
|
||||
this.$tele.emit('user-mgmt:copy-url') |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
@ -0,0 +1,46 @@
|
||||
<template> |
||||
<div> |
||||
<gh-btns-star |
||||
icon="mark-github" |
||||
slug="nocodb/nocodb" |
||||
show-count |
||||
:class="{'dark' : isDark}" |
||||
> |
||||
{{ ghStarText }} |
||||
</gh-btns-star> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'GithubStarBtn', |
||||
data: () => ({ ghStarText: 'Star' }), |
||||
mounted() { |
||||
setInterval(() => this.ghStarText = this.ghStarText === 'Star' ? 'Fork' : 'Star', 60000) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
/deep/ .gh-button-container{ |
||||
background: #fff2; |
||||
border-radius: 4px; |
||||
margin:0 |
||||
} |
||||
/deep/ .gh-button-container:not(.dark) > a { |
||||
background: transparent !important; |
||||
color: #cdcdcd !important; |
||||
} |
||||
/deep/ .gh-button-container > a:first-child{ |
||||
border-left-color: transparent; |
||||
border-top-color: transparent; |
||||
border-bottom-color: transparent; |
||||
} |
||||
/deep/ .gh-button-container > a:last-child{ |
||||
border-color: transparent; |
||||
} |
||||
|
||||
/deep/ .gh-button, /deep/ .social-count{ |
||||
padding:1px 5px; |
||||
} |
||||
</style> |
@ -0,0 +1,167 @@
|
||||
<template> |
||||
<div> |
||||
<v-menu offset-y> |
||||
<template #activator="{on}"> |
||||
<v-btn |
||||
v-show="isDashboard && _isUIAllowed('previewAs')" |
||||
small |
||||
light |
||||
color="#fff3" |
||||
class="white--text nc-btn-preview" |
||||
v-on="on" |
||||
> |
||||
<v-icon small class="mr-1"> |
||||
mdi-play-circle |
||||
</v-icon> |
||||
Preview |
||||
<v-icon small> |
||||
mdi-menu-down |
||||
</v-icon> |
||||
</v-btn> |
||||
</template> |
||||
<v-list dense> |
||||
<template v-for="(role) in rolesList"> |
||||
<v-list-item |
||||
:key="role.title" |
||||
:class="`pointer nc-preview-${role.title}`" |
||||
@click="setPreviewUser(role.title)" |
||||
> |
||||
<v-list-item-title> |
||||
<v-icon |
||||
small |
||||
class="mr-1" |
||||
:color="role.title === previewAs ? 'x-active' : ''" |
||||
> |
||||
{{ roleIcon[role.title] }} |
||||
</v-icon> |
||||
<span |
||||
class="caption text-capitalize" |
||||
:class="{ 'x-active--text': role.title === previewAs }" |
||||
>{{ role.title }}</span> |
||||
</v-list-item-title> |
||||
</v-list-item> |
||||
</template> |
||||
|
||||
<template v-if="previewAs"> |
||||
<!-- <v-divider></v-divider>--> |
||||
<v-list-item @click="setPreviewUser(null)"> |
||||
<v-icon small class="mr-1"> |
||||
mdi-close |
||||
</v-icon> |
||||
<!-- Reset Preview --> |
||||
<span class="caption nc-preview-reset">{{ $t('activity.resetReview') }}</span> |
||||
</v-list-item> |
||||
</template> |
||||
</v-list> |
||||
</v-menu> |
||||
|
||||
<v-menu |
||||
:position-x="position.x" |
||||
:position-y="position.y" |
||||
:value="previewAs" |
||||
activator="" |
||||
:close-on-click="false" |
||||
:close-on-content-click="false" |
||||
> |
||||
<div class="floating-reset-btn white py-1 pr-3 caption primary lighten-2 white--text font-weight-bold d-flex align-center nc-floating-preview-btn" style="overflow-y: hidden"> |
||||
<v-icon style="cursor: move" color="white" @mousedown="mouseDown"> |
||||
mdi-drag |
||||
</v-icon> |
||||
|
||||
<v-divider vertical class="mr-2" /> |
||||
|
||||
<div class="d-inline pointer d-flex align-center"> |
||||
<span>Preview as :</span> |
||||
<v-radio-group |
||||
:value="previewAs" |
||||
dense |
||||
row |
||||
class="mt-0 pt-0" |
||||
hide-details |
||||
@change="setPreviewUser($event)" |
||||
> |
||||
<v-radio |
||||
v-for="(role) in rolesList" |
||||
:key="role.title" |
||||
:value="role.title" |
||||
color="white" |
||||
dark |
||||
:class="`ml-1 nc-floating-preview-${role.title}`" |
||||
> |
||||
<template #label> |
||||
<span class="white--text caption text-capitalize">{{ role.title }}</span> |
||||
</template> |
||||
</v-radio> |
||||
</v-radio-group> |
||||
<v-divider vertical class="mr-2" /> |
||||
<span class="pointer" @click="setPreviewUser(null)"> <v-icon small color="white">mdi-exit-to-app</v-icon> Exit</span> |
||||
</div> |
||||
</div> |
||||
</v-menu> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'PreviewAs', |
||||
data: () => ({ |
||||
roleIcon: { |
||||
owner: 'mdi-account-star', |
||||
creator: 'mdi-account-hard-hat', |
||||
editor: 'mdi-account-edit', |
||||
viewer: 'mdi-eye-outline', |
||||
commenter: 'mdi-comment-account-outline' |
||||
}, |
||||
rolesList: [{ title: 'editor' }, { title: 'commenter' }, { title: 'viewer' }], |
||||
position: { |
||||
x: 9999, y: 9999 |
||||
} |
||||
}), |
||||
computed: { |
||||
previewAs: { |
||||
get() { |
||||
return this.$store.state.users.previewAs |
||||
}, |
||||
set(previewAs) { |
||||
this.$store.commit('users/MutPreviewAs', previewAs) |
||||
} |
||||
} |
||||
}, |
||||
mounted() { |
||||
this.position = { |
||||
y: window.innerHeight - 100, |
||||
x: window.innerWidth / 2 - 250 |
||||
} |
||||
|
||||
window.addEventListener('mouseup', this.mouseUp, false) |
||||
}, |
||||
beforeDestroy() { |
||||
window.removeEventListener('mousemove', this.divMove, true) |
||||
window.removeEventListener('mouseup', this.mouseUp, false) |
||||
}, |
||||
methods: { |
||||
setPreviewUser(previewAs) { |
||||
this.$tele.emit(`preview-as:${previewAs}`) |
||||
if (!process.env.EE) { |
||||
this.$toast.info('Available in Enterprise edition').goAway(3000) |
||||
} else { |
||||
this.previewAs = previewAs |
||||
window.location.reload() |
||||
} |
||||
}, |
||||
mouseUp() { |
||||
window.removeEventListener('mousemove', this.divMove, true) |
||||
}, |
||||
mouseDown(e) { |
||||
window.addEventListener('mousemove', this.divMove, true) |
||||
}, |
||||
|
||||
divMove(e) { |
||||
this.position = { y: e.clientY - 10, x: e.clientX - 18 } |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
</style> |
@ -1,43 +1,44 @@
|
||||
<template> |
||||
<v-tooltip |
||||
v-if="column.formula && column.formula.error && column.formula.error.length" |
||||
v-if="column && column.colOptions&& column.colOptions.error" |
||||
bottom |
||||
color="error" |
||||
> |
||||
<template #activator="{on}"> |
||||
<span class="caption" v-on="on">ERR<span class="error--text">!</span></span> |
||||
</template> |
||||
<span class=" font-weight-bold">{{ column.formula.error.join(', ') }}</span> |
||||
<span class=" font-weight-bold">{{ column.colOptions.error }}</span> |
||||
</v-tooltip> |
||||
<div v-else-if="urls" v-html="urls"></div> |
||||
<div v-else-if="urls" v-html="urls" /> |
||||
<div v-else> |
||||
{{ row[column._cn] }} |
||||
{{ row[column.title] }} |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'FormulaCell', |
||||
props: ['column', 'row'], |
||||
props: { column: Object, row: Object }, |
||||
computed: { |
||||
urls: function() { |
||||
if(this.row[this.column._cn] ) { |
||||
let rawText = this.row[this.column._cn].toString(); |
||||
let matchURLs = rawText.match(/URI::\((.*?)\)/g); |
||||
if(matchURLs) { |
||||
for(const url of matchURLs) { |
||||
let tmpuri = url.match(/URI::\((.*?)\)/)[1]; |
||||
rawText = rawText.replace(url, '<a href="' + tmpuri + '" target="_blank">' + tmpuri + '</a>'); |
||||
} |
||||
return rawText; |
||||
} |
||||
} |
||||
return false; |
||||
urls() { |
||||
if (!this.row[this.column.title]) { return } |
||||
|
||||
const rawText = this.row[this.column.title].toString() |
||||
let found = false |
||||
const out = rawText.match(/URI::\((.*?)\)/g, (_, url) => { |
||||
found = true |
||||
const a = document.createElement('a') |
||||
a.textContent = url |
||||
a.setAttribute('href', url) |
||||
return a.innerHTML |
||||
}) |
||||
|
||||
return found && out |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
<style scoped>/**/ |
||||
|
||||
</style> |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue