mirror of https://github.com/nocodb/nocodb
18 changed files with 487 additions and 70 deletions
@ -0,0 +1,31 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<TabMenu :model="tabItems" v-model:activeIndex="activeIndex"/> |
||||||
|
<template v-if="tabItems && tabItems[activeIndex]"> |
||||||
|
<TabsSmartsheet :tab-meta="tabs[activeIndex]" :key="tabs[activeIndex].id"/> |
||||||
|
</template> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
|
||||||
|
import {useTabs} from "~/composables/tabs"; |
||||||
|
|
||||||
|
const {tabs} = useTabs() |
||||||
|
const activeIndex = ref(0) |
||||||
|
|
||||||
|
const tabItems = computed(() => { |
||||||
|
return tabs.value.map(tab => { |
||||||
|
return { |
||||||
|
label: tab.title, |
||||||
|
// icon: tab.icon, |
||||||
|
closable: true |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,22 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
|
||||||
|
<div v-for="table in tables" class="p-2 text-sm" |
||||||
|
@click="addTab({type:'table',title:table.title, id:table.id})"> |
||||||
|
{{ table.title }} |
||||||
|
</div> |
||||||
|
|
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import {useProject} from "~/composables/project"; |
||||||
|
import {useTabs} from "~/composables/tabs"; |
||||||
|
|
||||||
|
const {tables} = useProject() |
||||||
|
const {addTab} = useTabs() |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,56 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
|
||||||
|
<div class="card"> |
||||||
|
<DataTable :value="rows" responsiveLayout="scroll"> |
||||||
|
|
||||||
|
<Column v-for="col in meta.columns" :key="col.id" :field="col.title" :header="col.title"> |
||||||
|
<template v-if="col.uidt === 'LinkToAnotherRecord'" #body="{data:{[col.title]:d}}"> |
||||||
|
{{ d&& (Array.isArray(d) ? d : [d]).map(c1 => c1[Object.keys(c1)[1]]).join(', ') }} |
||||||
|
</template> |
||||||
|
</Column> |
||||||
|
</DataTable> |
||||||
|
</div> |
||||||
|
|
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script lang="ts" setup> |
||||||
|
import {useNuxtApp} from "#app"; |
||||||
|
import {Api} from "nocodb-sdk"; |
||||||
|
import {useUser} from "~/composables/user"; |
||||||
|
|
||||||
|
const {tabMeta, meta} = defineProps({ |
||||||
|
tabMeta: Object, |
||||||
|
meta: Object |
||||||
|
}) |
||||||
|
|
||||||
|
const {project} = useProject() |
||||||
|
const {user} = useUser() |
||||||
|
const rows = ref() |
||||||
|
|
||||||
|
const {$api}: { $api: Api<any> } = useNuxtApp() as any |
||||||
|
|
||||||
|
|
||||||
|
const loadData = async () => { |
||||||
|
const response = await $api.dbTableRow.list('noco', |
||||||
|
project.value.id, |
||||||
|
meta.id, {}, { |
||||||
|
headers: { |
||||||
|
'xc-auth': user.token |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
rows.value = response.list |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(async () => { |
||||||
|
await loadData() |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,36 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<template v-if="meta && tabMeta"> |
||||||
|
<SmartsheetGrid :meta="meta" :tabMeta="tabMeta"></SmartsheetGrid> |
||||||
|
</template> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import {useMetas} from "~/composables/metas"; |
||||||
|
import {computed, onMounted, watch} from 'vue' |
||||||
|
|
||||||
|
const {tabMeta} = defineProps({ |
||||||
|
tabMeta: Object |
||||||
|
}) |
||||||
|
|
||||||
|
const {getMeta, metas} = useMetas() |
||||||
|
|
||||||
|
const meta = computed(() => { |
||||||
|
return metas.value?.[tabMeta?.id] |
||||||
|
}) |
||||||
|
|
||||||
|
onMounted(async () => { |
||||||
|
await getMeta(tabMeta?.id) |
||||||
|
}) |
||||||
|
|
||||||
|
watch(() => tabMeta && tabMeta?.id, async (newVal, oldVal) => { |
||||||
|
if (newVal !== oldVal) { |
||||||
|
await getMeta(newVal) |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,41 @@ |
|||||||
|
import {useNuxtApp, useState} from "#app"; |
||||||
|
import {Api, TableType} from "nocodb-sdk"; |
||||||
|
import {useUser} from "~/composables/user"; |
||||||
|
import {useProject} from "~/composables/project"; |
||||||
|
|
||||||
|
|
||||||
|
export const useMetas = () => { |
||||||
|
const {$api}: { $api: Api<any> } = useNuxtApp() as any |
||||||
|
const {user} = useUser() |
||||||
|
const {tables} = useProject() |
||||||
|
|
||||||
|
const metas = useState<{ [idOrTitle: string]: TableType | any }>('metas', () => ({})) |
||||||
|
|
||||||
|
const getMeta = async (tableIdOrTitle: string, force = false) => { |
||||||
|
if (!force && metas[tableIdOrTitle]) { |
||||||
|
return metas[tableIdOrTitle] |
||||||
|
} |
||||||
|
|
||||||
|
const modelId = (tables.value.find(t => t.title === tableIdOrTitle || t.id === tableIdOrTitle) || {}).id |
||||||
|
if (!modelId) { |
||||||
|
console.warn(`Table '${tableIdOrTitle}' is not found in the table list`) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
const model = await $api.dbTable.read(modelId, { |
||||||
|
headers: { |
||||||
|
'xc-auth': user.token |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
metas.value = { |
||||||
|
...metas.value, |
||||||
|
[model.id]: model, |
||||||
|
[model.title]: model |
||||||
|
} |
||||||
|
|
||||||
|
return model |
||||||
|
} |
||||||
|
|
||||||
|
return {getMeta, metas} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
import {useNuxtApp} from "#app"; |
||||||
|
import {Api, TableType} from "nocodb-sdk"; |
||||||
|
import {useUser} from "~/composables/user"; |
||||||
|
|
||||||
|
export const useProject = () => { |
||||||
|
const {$api}: { $api: Api<any> } = useNuxtApp() as any |
||||||
|
const {user} = useUser() |
||||||
|
|
||||||
|
const project = useState<{ id?: string, title?: string }>('project', null) |
||||||
|
const tables = useState<Array<TableType>>('tables', null) |
||||||
|
|
||||||
|
const loadTables = async () => { |
||||||
|
const tablesResponse = await $api.dbTable.list(project?.value?.id, {}, { |
||||||
|
headers: { |
||||||
|
'xc-auth': user.token |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
console.log(tablesResponse) |
||||||
|
tables.value = tablesResponse.list |
||||||
|
} |
||||||
|
|
||||||
|
const loadProject = async (projectId:string) => { |
||||||
|
const projectResponse = await $api.project.read(projectId, { |
||||||
|
headers: { |
||||||
|
'xc-auth': user.token |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
console.log(projectResponse) |
||||||
|
project.value = projectResponse |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return {project, tables, loadProject, loadTables} |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
import {useState} from "#app"; |
||||||
|
|
||||||
|
interface TabItem { |
||||||
|
type: 'table' | 'view', |
||||||
|
title: string, |
||||||
|
id:string |
||||||
|
} |
||||||
|
|
||||||
|
export const useTabs = () => { |
||||||
|
const tabs = useState<Array<TabItem>>('tabs', () => []) |
||||||
|
|
||||||
|
const addTab = (tabMeta: TabItem) => { |
||||||
|
tabs.value = [...(tabs.value || []), tabMeta] |
||||||
|
} |
||||||
|
|
||||||
|
return {tabs, addTab} |
||||||
|
} |
@ -1,9 +1,15 @@ |
|||||||
import {store} from 'nuxt3-store' |
import {store} from 'nuxt3-store' |
||||||
|
|
||||||
export const user = store({ |
export const useUser = () =>{ |
||||||
name: 'user', |
const user = store({ |
||||||
type: 'localstorage', |
name: 'user', |
||||||
value: {token: null}, |
type: 'localstorage', |
||||||
reactiveType: 'reactive', |
value: {token: null}, |
||||||
version: '1.0.0' |
reactiveType: 'reactive', |
||||||
}) |
version: '1.0.0' |
||||||
|
}) |
||||||
|
|
||||||
|
const setToken = (token) => { user.token = token } |
||||||
|
|
||||||
|
return {user,setToken} |
||||||
|
} |
||||||
|
@ -0,0 +1,14 @@ |
|||||||
|
export async function extractSdkResponseErrorMsg(e:Error & {response:any}) { |
||||||
|
if (!e || !e.response) { return e.message } |
||||||
|
let msg |
||||||
|
if (e.response.data instanceof Blob) { |
||||||
|
try { |
||||||
|
msg = JSON.parse(await e.response.data.text()).msg |
||||||
|
} catch { |
||||||
|
msg = 'Some internal error occurred' |
||||||
|
} |
||||||
|
} else { |
||||||
|
msg = e.response.data.msg || 'Some internal error occurred' |
||||||
|
} |
||||||
|
return msg || 'Some error occurred' |
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
<template> |
||||||
|
<div class="nc-container"> |
||||||
|
<div class="nc-topbar shadow-2"> |
||||||
|
</div> |
||||||
|
<div class="nc-sidebar shadow-2 p-4 overflow-y-auto"> |
||||||
|
<DashboardTreeView></DashboardTreeView> |
||||||
|
</div> |
||||||
|
<div class="nc-content p-4 overflow-auto"> |
||||||
|
<DashboardTabView></DashboardTabView> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import {useNuxtApp} from "#app"; |
||||||
|
import {useProject} from "~/composables/project"; |
||||||
|
|
||||||
|
const route = useRoute() |
||||||
|
const {loadProject, loadTables} = useProject() |
||||||
|
|
||||||
|
|
||||||
|
onMounted(async () => { |
||||||
|
await loadProject(route.params.projectId as string) |
||||||
|
await loadTables() |
||||||
|
}) |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.nc-container { |
||||||
|
.nc-topbar { |
||||||
|
position: fixed; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
height: 50px; |
||||||
|
width: 100%; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
.nc-sidebar { |
||||||
|
position: fixed; |
||||||
|
top: 50px; |
||||||
|
left: 0; |
||||||
|
height: calc(100% - 50px); |
||||||
|
width: 250px; |
||||||
|
} |
||||||
|
|
||||||
|
.nc-content { |
||||||
|
position: fixed; |
||||||
|
top: 50px; |
||||||
|
left: 250px; |
||||||
|
height: calc(100% - 50px); |
||||||
|
width: calc(100% - 250px); |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,51 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
|
||||||
|
<div class="grid"> |
||||||
|
<div class="col-3 p-3" v-for="project in projects" :key="project.id"> |
||||||
|
|
||||||
|
<Card @click="navigateToDashboard(project)"> |
||||||
|
<template #content> |
||||||
|
<div class="text-center"> |
||||||
|
<h3>{{ project.title }}</h3> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</Card> |
||||||
|
|
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
|
||||||
|
import {Api} from "nocodb-sdk"; |
||||||
|
|
||||||
|
const {$api, $router}= useNuxtApp() |
||||||
|
const projects = ref() |
||||||
|
const {user} = useUser() |
||||||
|
|
||||||
|
const loadProjects = async () => { |
||||||
|
const projectsResponse = await $api.project.list({}, { |
||||||
|
headers: { |
||||||
|
'xc-auth': user.token |
||||||
|
} |
||||||
|
}) |
||||||
|
projects.value = projectsResponse.list |
||||||
|
} |
||||||
|
|
||||||
|
const navigateToDashboard = async (project) => { |
||||||
|
await $router.push({ |
||||||
|
path: '/dashboard/' + project.id |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(async () => { |
||||||
|
await loadProjects() |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
</style> |
@ -1,72 +1,62 @@ |
|||||||
<template> |
<template> |
||||||
<v-cotainer> |
<div> |
||||||
<v-card max-width="500" class="pa-4 mx-auto mt-10"> |
<!-- Enter your work email --> |
||||||
{{userStore}} |
<Card class="p-4 mx-auto mt-5" style="max-width: 500px"> |
||||||
<v-form |
<template #content> |
||||||
ref="formType" |
|
||||||
v-model="valid" |
|
||||||
lazy-validation |
|
||||||
> |
|
||||||
<!-- Enter your work email --> |
|
||||||
<v-text-field |
|
||||||
v-model="form.email" |
|
||||||
label="Email" |
|
||||||
required |
|
||||||
/> |
|
||||||
|
|
||||||
<!-- Enter your password --> |
<Message class="" v-if="error" severity="error">{{ error }}</Message> |
||||||
<v-text-field |
|
||||||
label="Password" |
|
||||||
v-model="form.password" |
|
||||||
name="input-10-2" |
|
||||||
min="8" |
|
||||||
/> |
|
||||||
|
|
||||||
<v-btn |
<div class="p-float-label mt-5"> |
||||||
class="mx-auto" |
<InputText id="email" type="text" v-model="form.email" style="width:100%"/> |
||||||
large |
<label for="email">Email</label> |
||||||
elevation-10 |
</div> |
||||||
:disabled="false" |
|
||||||
@click="signIn" |
|
||||||
> |
|
||||||
<b>Sign In</b> |
|
||||||
</v-btn> |
|
||||||
|
|
||||||
</v-form> |
<!-- Enter your password --> |
||||||
</v-card> |
<div class="p-float-label mt-5"> |
||||||
</v-cotainer> |
<InputText id="password" type="password" v-model="form.password" style="width:100%"/> |
||||||
|
<label for="password">Password</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="text-center"> |
||||||
|
<Button |
||||||
|
class="mt-5" |
||||||
|
@click="signIn" |
||||||
|
> |
||||||
|
<b>Sign In</b> |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</Card> |
||||||
|
</div> |
||||||
|
|
||||||
</template> |
</template> |
||||||
|
|
||||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||||
import {ref, reactive} from 'vue' |
import {ref, reactive} from 'vue' |
||||||
|
import {useUser} from "~/composables/user"; |
||||||
|
import {extractSdkResponseErrorMsg} from "~/helpers/errorUtils"; |
||||||
|
import {useNuxtApp} from "#app"; |
||||||
|
|
||||||
|
const {$api} = useNuxtApp() |
||||||
const valid = ref() |
const valid = ref() |
||||||
|
const error = ref() |
||||||
const form = reactive({ |
const form = reactive({ |
||||||
email: '', |
email: '', |
||||||
password: '' |
password: '' |
||||||
}) |
}) |
||||||
const userStore = user |
const {user, setToken} = useUser() |
||||||
|
|
||||||
</script> |
const signIn = async () => { |
||||||
<script lang="ts"> |
error.value = null |
||||||
import {useNuxtApp} from "nuxt/app"; |
try { |
||||||
import {Api} from "nocodb-sdk"; |
const {token} = await $api.auth.signin(form) |
||||||
|
setToken(token) |
||||||
|
} catch (e) { |
||||||
// const {$api}: { $api: Api<any> } = useNuxtApp() as any |
error.value = await extractSdkResponseErrorMsg(e) |
||||||
export default { |
|
||||||
methods: { |
|
||||||
signIn() { |
|
||||||
this.$api.auth.signin(this.form).then((res) => { |
|
||||||
console.log(res) |
|
||||||
this.userStore.token = res |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
} |
||||||
} |
} |
||||||
</script> |
|
||||||
|
|
||||||
|
</script> |
||||||
<style scoped> |
<style scoped> |
||||||
|
|
||||||
</style> |
</style> |
||||||
|
@ -1,13 +1,62 @@ |
|||||||
<template> |
<template> |
||||||
|
<div> |
||||||
|
<!-- Enter your work email --> |
||||||
|
<Card class="p-4 mx-auto mt-5" style="max-width: 500px"> |
||||||
|
<template #content> |
||||||
|
|
||||||
|
<Message class="" v-if="error" severity="error">{{ error }}</Message> |
||||||
|
|
||||||
|
<div class="p-float-label mt-5"> |
||||||
|
<InputText id="email" type="text" v-model="form.email" style="width:100%"/> |
||||||
|
<label for="email">Email</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- Enter your password --> |
||||||
|
<div class="p-float-label mt-5"> |
||||||
|
<InputText id="password" type="password" v-model="form.password" style="width:100%"/> |
||||||
|
<label for="password">Password</label> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="text-center"> |
||||||
|
<Button |
||||||
|
class="mt-5" |
||||||
|
@click="signUp" |
||||||
|
> |
||||||
|
<b>Sign Up</b> |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</Card> |
||||||
|
</div> |
||||||
|
|
||||||
</template> |
</template> |
||||||
|
|
||||||
<script> |
<script setup lang="ts"> |
||||||
export default { |
import {ref, reactive} from 'vue' |
||||||
name: "signup" |
import {useUser} from "~/composables/user"; |
||||||
|
import {Api} from "nocodb-sdk"; |
||||||
|
import {extractSdkResponseErrorMsg} from "~/helpers/errorUtils"; |
||||||
|
|
||||||
|
const {$api}: { $api: Api<any> } = useNuxtApp() as any |
||||||
|
const valid = ref() |
||||||
|
const error = ref() |
||||||
|
const form = reactive({ |
||||||
|
email: '', |
||||||
|
password: '' |
||||||
|
}) |
||||||
|
const {user, setToken} = useUser() |
||||||
|
|
||||||
|
const signUp = async () => { |
||||||
|
error.value = null |
||||||
|
try { |
||||||
|
const {token} = await $api.auth.signup(form) |
||||||
|
setToken(token) |
||||||
|
} catch (e) { |
||||||
|
error.value = await extractSdkResponseErrorMsg(e) |
||||||
|
} |
||||||
} |
} |
||||||
</script> |
|
||||||
|
|
||||||
|
</script> |
||||||
<style scoped> |
<style scoped> |
||||||
|
|
||||||
</style> |
</style> |
||||||
|
Loading…
Reference in new issue