mirror of https://github.com/nocodb/nocodb
Browse Source
* wip: rating cell ui implementation Signed-off-by: Pranav C <pranavxc@gmail.com> * wip: migration for meta column data field Signed-off-by: Pranav C <pranavxc@gmail.com> * wip: rating column options Signed-off-by: Pranav C <pranavxc@gmail.com> * feat: rating cell creation and mapping metadata Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: change default type to int for rating Signed-off-by: Pranav C <pranavxc@gmail.com> * feat: add email cell Signed-off-by: Pranav C <pranavxc@gmail.com> * feat: accept invalid values for columns if validation disabled Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: remove additional options Signed-off-by: Pranav C <pranavxc@gmail.com> * feat: checkbox customization Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: move validate checkbox to advance option Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: add tick icon in checkbox Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: start rating max value from 1 Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: minor ui corrections Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: load rating component in gallery view Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: use default icon and color if meta missing(checkbox and rating) re #2036 Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: cache column with parsed metadata re #2038 Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: convert old rating column to number type Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: migration filename correction Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: component name Signed-off-by: Pranav C <pranavxc@gmail.com>pull/2071/head
22 changed files with 728 additions and 237 deletions
@ -0,0 +1,22 @@ |
|||||||
|
<template> |
||||||
|
<a v-if="isEmail" :href="`mailto:${value}`" target="_blank">{{ value }}</a> |
||||||
|
<span v-else>{{ value }}</span> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { isEmail } from '~/helpers' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'EmailCell', |
||||||
|
props: ['value'], |
||||||
|
computed: { |
||||||
|
isEmail() { |
||||||
|
return isEmail(this.value || '') |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,98 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<div class="nc-rating-wrapper ui-type"> |
||||||
|
<v-select |
||||||
|
v-model="colMeta.icon" |
||||||
|
label="Icon" |
||||||
|
:items="icons" |
||||||
|
dense |
||||||
|
outlined |
||||||
|
class="caption" |
||||||
|
:item-value="v=>v" |
||||||
|
:value-comparator="(a,b) => a && b && a.full === b.full && a.empty === b.empty" |
||||||
|
> |
||||||
|
<template #item="{ item }"> |
||||||
|
<v-icon small :color="colMeta.color"> |
||||||
|
{{ item.full }} |
||||||
|
</v-icon> |
||||||
|
<v-icon class="ml-2" small :color="colMeta.color"> |
||||||
|
{{ item.empty }} |
||||||
|
</v-icon> |
||||||
|
</template> |
||||||
|
<template #selection="{ item }"> |
||||||
|
<v-icon small :color="colMeta.color"> |
||||||
|
{{ item.full }} |
||||||
|
</v-icon> |
||||||
|
<v-icon class="ml-2" small :color="colMeta.color"> |
||||||
|
{{ item.empty }} |
||||||
|
</v-icon> |
||||||
|
</template> |
||||||
|
</v-select> |
||||||
|
<v-select |
||||||
|
v-model="colMeta.max" |
||||||
|
label="Max" |
||||||
|
:items="[1,2,3,4,5,6,7,8,9,10]" |
||||||
|
dense |
||||||
|
outlined |
||||||
|
class="caption" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<v-color-picker |
||||||
|
v-model="colMeta.color" |
||||||
|
class="mx-auto" |
||||||
|
hide-inputs |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'RatingOptions', |
||||||
|
props: ['column', 'meta', 'value'], |
||||||
|
data: () => ({ |
||||||
|
colMeta: { |
||||||
|
icon: { |
||||||
|
full: 'mdi-star', |
||||||
|
empty: 'mdi-star-outline' |
||||||
|
}, |
||||||
|
color: '#fcb401', |
||||||
|
max: 5 |
||||||
|
}, |
||||||
|
icons: [{ |
||||||
|
full: 'mdi-star', |
||||||
|
empty: 'mdi-star-outline' |
||||||
|
}, { |
||||||
|
full: 'mdi-heart', |
||||||
|
empty: 'mdi-heart-outline' |
||||||
|
}, { |
||||||
|
full: 'mdi-moon-full', |
||||||
|
empty: 'mdi-moon-new' |
||||||
|
}, { |
||||||
|
full: 'mdi-thumb-up', |
||||||
|
empty: 'mdi-thumb-up-outline' |
||||||
|
}, { |
||||||
|
full: 'mdi-flag', |
||||||
|
empty: 'mdi-flag-outline' |
||||||
|
}] |
||||||
|
}), |
||||||
|
watch: { |
||||||
|
value() { |
||||||
|
this.colMeta = this.value || {} |
||||||
|
}, |
||||||
|
colMeta(v) { |
||||||
|
this.$emit('input', v) |
||||||
|
} |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.colMeta = this.value || { ...this.colMeta } |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.nc-rating-wrapper { |
||||||
|
display: flex; |
||||||
|
gap: 16px |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,95 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<div class="nc-rating-wrapper ui-type"> |
||||||
|
<v-select |
||||||
|
v-model="colMeta.icon" |
||||||
|
label="Icon" |
||||||
|
:items="icons" |
||||||
|
dense |
||||||
|
outlined |
||||||
|
class="caption" |
||||||
|
:item-value="v=>v" |
||||||
|
:value-comparator="(a,b) => a && b && a.checked === b.checked && a.unchecked === b.unchecked" |
||||||
|
> |
||||||
|
<template #item="{ item }"> |
||||||
|
<v-icon small :color="colMeta.color"> |
||||||
|
{{ item.checked }} |
||||||
|
</v-icon> |
||||||
|
<v-icon class="ml-2" small :color="colMeta.color"> |
||||||
|
{{ item.unchecked }} |
||||||
|
</v-icon> |
||||||
|
</template> |
||||||
|
<template #selection="{ item }"> |
||||||
|
<v-icon small :color="colMeta.color"> |
||||||
|
{{ item.checked }} |
||||||
|
</v-icon> |
||||||
|
<v-icon class="ml-2" small :color="colMeta.color"> |
||||||
|
{{ item.unchecked }} |
||||||
|
</v-icon> |
||||||
|
</template> |
||||||
|
</v-select> |
||||||
|
</div> |
||||||
|
<v-color-picker |
||||||
|
v-model="colMeta.color" |
||||||
|
class="mx-auto" |
||||||
|
hide-inputs |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CheckboxOptions', |
||||||
|
props: ['column', 'meta', 'value'], |
||||||
|
data: () => ({ |
||||||
|
colMeta: { |
||||||
|
icon: { |
||||||
|
checked: 'mdi-check-bold', |
||||||
|
unchecked: 'mdi-crop-square' |
||||||
|
}, |
||||||
|
color: '#777' |
||||||
|
}, |
||||||
|
icons: [{ |
||||||
|
checked: 'mdi-check-bold', |
||||||
|
unchecked: 'mdi-crop-square' |
||||||
|
}, { |
||||||
|
checked: 'mdi-check-circle-outline', |
||||||
|
unchecked: 'mdi-checkbox-blank-circle-outline' |
||||||
|
}, { |
||||||
|
checked: 'mdi-star', |
||||||
|
unchecked: 'mdi-star-outline' |
||||||
|
}, { |
||||||
|
checked: 'mdi-heart', |
||||||
|
unchecked: 'mdi-heart-outline' |
||||||
|
}, { |
||||||
|
checked: 'mdi-moon-full', |
||||||
|
unchecked: 'mdi-moon-new' |
||||||
|
}, { |
||||||
|
checked: 'mdi-thumb-up', |
||||||
|
unchecked: 'mdi-thumb-up-outline' |
||||||
|
}, { |
||||||
|
checked: 'mdi-flag', |
||||||
|
unchecked: 'mdi-flag-outline' |
||||||
|
}] |
||||||
|
}), |
||||||
|
watch: { |
||||||
|
value() { |
||||||
|
this.colMeta = this.value || {} |
||||||
|
}, |
||||||
|
colMeta(v) { |
||||||
|
this.$emit('input', v) |
||||||
|
} |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.colMeta = this.value || { ...this.colMeta } |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.nc-rating-wrapper { |
||||||
|
display: flex; |
||||||
|
gap: 16px |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,71 @@ |
|||||||
|
<template> |
||||||
|
<div class="d-100 h-100" :class="{'nc-cell-hover-show': localState == 0 || !localState}"> |
||||||
|
<v-rating |
||||||
|
v-model="localState" |
||||||
|
:length="ratingMeta.max" |
||||||
|
dense |
||||||
|
x-small |
||||||
|
:disabled="readOnly" |
||||||
|
clearable |
||||||
|
> |
||||||
|
<template #item="{isFilled, click}"> |
||||||
|
<v-icon v-if="isFilled" :size="15" :color="ratingMeta.color" @click="click"> |
||||||
|
{{ fullIcon }} |
||||||
|
</v-icon> |
||||||
|
<v-icon |
||||||
|
v-else |
||||||
|
:color="ratingMeta.color" |
||||||
|
:size="15" |
||||||
|
class="nc-cell-hover-show" |
||||||
|
@click="click" |
||||||
|
> |
||||||
|
{{ emptyIcon }} |
||||||
|
</v-icon> |
||||||
|
</template> |
||||||
|
</v-rating> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: 'RatingCell', |
||||||
|
props: { |
||||||
|
column: Object, |
||||||
|
value: [String, Number], |
||||||
|
readOnly: Boolean |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
fullIcon() { |
||||||
|
return (this.ratingMeta && this.ratingMeta.icon && this.ratingMeta.icon.full) || 'mdi-star' |
||||||
|
}, |
||||||
|
emptyIcon() { |
||||||
|
return (this.ratingMeta && this.ratingMeta.icon && this.ratingMeta.icon.empty) || 'mdi-star-outline' |
||||||
|
}, |
||||||
|
localState: { |
||||||
|
get() { |
||||||
|
return this.value |
||||||
|
}, |
||||||
|
set(val) { |
||||||
|
this.$emit('input', val) |
||||||
|
} |
||||||
|
}, |
||||||
|
ratingMeta() { |
||||||
|
return { |
||||||
|
icon: { |
||||||
|
full: 'mdi-star', |
||||||
|
empty: 'mdi-star-outline' |
||||||
|
}, |
||||||
|
color: '#fcb401', |
||||||
|
max: 5, |
||||||
|
...(this.column && this.column.meta |
||||||
|
? this.column.meta |
||||||
|
: {}) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,38 @@ |
|||||||
|
import Knex from 'knex'; |
||||||
|
import { MetaTable } from '../../utils/globals'; |
||||||
|
|
||||||
|
const up = async (knex: Knex) => { |
||||||
|
await knex.schema.alterTable(MetaTable.COLUMNS, table => { |
||||||
|
table.text('meta'); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const down = async knex => { |
||||||
|
await knex.schema.alterTable(MetaTable.COLUMNS, table => { |
||||||
|
table.dropColumn('meta'); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
export { up, down }; |
||||||
|
|
||||||
|
/** |
||||||
|
* @copyright Copyright (c) 2022, Xgene Cloud Ltd |
||||||
|
* |
||||||
|
* @author Wing-Kam Wong <wingkwong.code@gmail.com> |
||||||
|
* |
||||||
|
* @license GNU AGPL version 3 or any later version |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as |
||||||
|
* published by the Free Software Foundation, either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
* |
||||||
|
*/ |
Loading…
Reference in new issue