Edit number/text column inside item card [SCI-9600][SCI-9696] (#6546)

* Refactor Repository Text and Number value component [SCI-9600]

- Refactor text and number components.
- Add number validation.

* Create a Textarea standalone component [SCI-9600][SCI-9618]
This commit is contained in:
Soufiane 2023-11-07 12:30:49 +01:00 committed by GitHub
parent a0da544e27
commit dbff182314
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 209 additions and 124 deletions

View file

@ -0,0 +1,135 @@
<template>
<textarea v-if="editing"
ref="textareaRef"
class="leading-5 inline-block outline-none border-solid font-normal border-[1px] box-content
overflow-x-hidden overflow-y-auto resize-none rounded px-4 py-2 w-[calc(100%-2rem)]
border-sn-science-blue"
:class="{
'max-h-[4rem]': collapsed,
'max-h-[40rem]': !collapsed
}"
:placeholder="i18n.t('repositories.item_card.repository_number_value.placeholder')"
v-model="value"
@keydown="handleKeydown"
@blur="handleBlur" />
<div v-else
:ref="unEditableRef"
class="grid box-content sci-cursor-edit font-normal border-solid px-4 py-2 border-sn-light-grey rounded
leading-5 border outline-none hover:border-sn-sleepy-grey overflow-y-auto whitespace-pre-line
w-[calc(100%-2rem)]"
:class="{ 'max-h-[4rem]': collapsed,
'max-h-[40rem]': !collapsed, }"
@click="enableEdit">
<span v-html="value || noContentPlaceholder" ></span>
</div>
</template>
<script>
export default {
name: "Textarea",
data() {
return {
value: '',
editing: false,
};
},
props: {
expandable: { type: Boolean, required: true },
collapsed: { type: Boolean, required: true },
initialValue: String,
noContentPlaceholder: String,
decimals: { type: Number, default: null },
unEditableRef: { type: String, required: true },
},
mounted() {
this.value = this.initialValue;
this.$nextTick(() => {
this.toggleExpandableState();
});
},
beforeUpdate() {
if (!this.$refs.textareaRef) return;
if (this.decimals !== null) this.validateNumberInput();
},
watch: {
initialValue: {
handler() {
this.value = this.initialValue;
this.toggleExpandableState();
},
deep: true,
},
editing() {
this.$nextTick(() => {
if (this.editing) {
this.setCaretAtEnd();
this.refreshTextareaHeight();
return;
}
this.toggleExpandableState();
})
},
},
computed: {
canEdit() {
return this.permissions?.can_manage && !this.inArchivedRepositoryRow;
}
},
methods: {
handleKeydown(event) {
if (event.key === 'Enter') {
event.preventDefault();
this.$refs.textareaRef.blur();
}
},
handleBlur() {
this.editing = false;
this.toggleExpandableState();
this.$emit('update', this.value);
},
toggleExpandableState() {
this.$nextTick(() => {
if (!this.$refs[this.unEditableRef]) return;
const maxCollapsedHeight = '80';
const scrollHeight = this.$refs[this.unEditableRef].scrollHeight;
const expandable = scrollHeight > maxCollapsedHeight;
this.$emit('toggleExpandableState', expandable);
});
},
enableEdit(e) {
if (e && $(e.target).hasClass('atwho-user-popover')) return;
if (e && $(e.target).hasClass('sa-link')) return;
if (e && $(e.target).parent().hasClass('atwho-inserted')) return;
this.editing = true;
},
refreshTextareaHeight() {
this.$nextTick(() => {
if (!this.editing) return;
const textarea = this.$refs.textareaRef;
textarea.style.height = '0px';
// 16px is the height of the textarea's line
textarea.style.height = textarea.scrollHeight - 16 + 'px';
});
},
setCaretAtEnd() {
this.$nextTick(() => {
if (!this.editing) return;
this.$refs.textareaRef.focus();
});
},
validateNumberInput() {
const regexp = this.decimals === 0 ? /[^0-9]/g : /[^0-9.]/g;
const decimalsRegex = new RegExp(`^\\d*(\\.\\d{0,${this.decimals}})?`);
let value = this.value;
value = value.replace(regexp, '');
value = value.match(decimalsRegex)[0];
this.value = value;
}
},
};
</script>

View file

@ -1,7 +1,6 @@
<template>
<div id="repository-number-value-wrapper"
class="flex flex-col min-h-[46px] h-auto gap-[6px]"
>
class="flex flex-col min-h-[46px] h-auto gap-[6px]">
<div class="font-inter text-sm font-semibold leading-5 flex justify-between">
<div class="truncate" :class="{ 'w-4/5': expandable }" :title="colName">{{ colName }}</div>
<div @click="toggleCollapse"
@ -14,90 +13,76 @@
}}
</div>
</div>
<div>
<inline-edit
v-if="permissions?.can_manage && !inArchivedRepositoryRow"
ref="numberRef"
:value="colVal"
:placeholder="i18n.t('repositories.item_card.repository_number_value.placeholder')"
:noContentPlaceholder="i18n.t('repositories.item_card.repository_number_value.no_number')"
:allowBlank="true"
:smartAnnotation="false"
:attributeName="`${colName} `"
:allowNewLine="false"
:singleLine="false"
:expandable="true"
:collapsed="collapsed"
@editingEnabled="editing = true"
@editingDisabled="editing = false"
@update="update"
></inline-edit>
<div v-if="canEdit">
<text-area :initialValue="colVal.toString()"
:noContentPlaceholder="noContentPlaceholder"
:decimals="decimals"
:unEditableRef="`numberRef`"
:expandable="expandable"
:collapsed="collapsed"
@toggleExpandableState="toggleExpandableState"
@update="update" />
</div>
<div v-else-if="colVal"
ref="numberRef"
class="text-sn-dark-grey box-content font-inter text-sm font-normal leading-5 min-h-[20px] overflow-y-auto"
:class="{
'max-h-[4rem]': collapsed,
'max-h-[40rem]': !collapsed
}"
>
}">
{{ colVal }}
</div>
<div v-else
class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
{{ i18n.t("repositories.item_card.repository_number_value.no_number") }}
</div>
</div>
</div>
</template>
<script>
import InlineEdit from "../../shared/inline_edit.vue";
import repositoryValueMixin from "./mixins/repository_value.js";
import Textarea from "../Textarea.vue";
export default {
name: "RepositoryNumberValue",
mixins: [repositoryValueMixin],
components: {
"inline-edit": InlineEdit
'text-area': Textarea,
},
data() {
return {
expandable: false,
collapsed: true,
numberValue: '',
};
},
props: {
data_type: String,
inArchivedRepository: Boolean,
colId: Number,
colName: String,
colVal: String,
permissions: null,
inArchivedRepositoryRow: Boolean,
},
data() {
return {
expandable: false,
collapsed: true
};
created() {
// constants
this.noContentPlaceholder = this.i18n.t("repositories.item_card.repository_number_value.no_number");
this.decimals = Number(document.getElementById(`${this.colId}`).dataset['metadataDecimals']) || 0;
},
mounted() {
this.$nextTick(() => {
this.toggleExpandableState()
});
},
updated() {
this.$nextTick(() => {
this.toggleExpandableState();
});
computed: {
canEdit() {
return this.permissions?.can_manage && !this.inArchivedRepositoryRow;
}
},
methods: {
toggleCollapse() {
if (this.expandable) {
this.collapsed = !this.collapsed;
}
},
toggleExpandableState() {
if (!this.$refs.numberRef) return;
if (!this.expandable) return;
const offsetHeight = this.$refs.numberRef.offsetHeight;
const scrollHeight = this.$refs.numberRef.scrollHeight;
this.expandable = scrollHeight > offsetHeight;
}
this.collapsed = !this.collapsed;
},
toggleExpandableState(expandable) {
this.expandable = expandable;
},
}
};
</script>

View file

@ -13,59 +13,49 @@
}}
</div>
</div>
<div>
<inline-edit
v-if="permissions?.can_manage && !inArchivedRepositoryRow"
ref="textRef"
:value="colVal?.edit"
:placeholder="i18n.t('repositories.item_card.repository_text_value.placeholder')"
:noContentPlaceholder="i18n.t('repositories.item_card.repository_text_value.no_text')"
:allowBlank="true"
:smartAnnotation="false"
:attributeName="`${colName} `"
:allowNewLine="false"
:singleLine="false"
:expandable="true"
:collapsed="collapsed"
@editingEnabled="editing = true"
@editingDisabled="editing = false"
@update="update"
></inline-edit>
<div v-else-if="colVal?.edit"
ref="textRef"
class="text-sn-dark-grey box-content font-inter text-sm font-normal leading-5 overflow-y-auto px-4 py-2 border-sn-light-grey border border-solid rounded"
:class="{
'max-h-[4rem]': collapsed,
'max-h-[40rem]': !collapsed
}"
>
{{ colVal?.edit }}
</div>
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5">
{{ i18n.t("repositories.item_card.repository_text_value.no_text") }}
</div>
<div v-if="canEdit">
<text-area :initialValue="colVal?.edit"
:noContentPlaceholder="noContentPlaceholder"
:unEditableRef="`textRef`"
:expandable="expandable"
:collapsed="collapsed"
@toggleExpandableState="toggleExpandableState"
@update="update" />
</div>
<div v-else-if="colVal?.edit"
ref="textRef"
class="text-sn-dark-grey box-content text-sm font-normal leading-5 overflow-y-auto px-4 py-2
border-sn-light-grey border border-solid rounded w-[calc(100%-2rem)]]"
:class="{
'max-h-[4rem]': collapsed,
'max-h-[40rem]': !collapsed
}"
>
{{ colVal?.edit }}
</div>
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5 px-4 py-2 w-[calc(100%-2rem)]]">
{{ i18n.t("repositories.item_card.repository_text_value.no_text") }}
</div>
</div>
</template>
<script>
import InlineEdit from "../../shared/inline_edit.vue";
import repositoryValueMixin from "./mixins/repository_value.js";
import Textarea from "../Textarea.vue";
export default {
name: "RepositoryTextValue",
mixins: [repositoryValueMixin],
components: {
"inline-edit": InlineEdit
'text-area': Textarea,
},
data() {
return {
edit: null,
view: null,
contentExpanded: false,
expandable: false,
collapsed: true
}
collapsed: true,
textValue: '',
};
},
props: {
data_type: String,
@ -75,48 +65,23 @@ export default {
permissions: null,
inArchivedRepositoryRow: Boolean,
},
mounted() {
this.$nextTick(() => {
if (this.$refs.textRef) {
const textHeight = this.$refs.textRef.scrollHeight
this.expandable = textHeight > 60 // 60px
}
this.toggleExpandableState();
});
created() {
// constants
this.noContentPlaceholder = this.i18n.t("repositories.item_card.repository_text_value.no_text");
},
watch: {
editing() {
if (this.editing) return;
this.toggleExpandableState();
computed: {
canEdit() {
return this.permissions?.can_manage && !this.inArchivedRepositoryRow;
}
},
methods: {
toggleCollapse() {
if (this.expandable) {
this.collapsed = !this.collapsed;
}
if (!this.expandable) return;
this.collapsed = !this.collapsed;
},
toggleExpandableState() {
if (!this.$refs.textRef) return;
let offsetHeight;
let scrollHeight;
if (Object.keys(this.$refs.textRef.$refs).length > 0) {
const keys = Object.keys(this.$refs.textRef.$refs)
keys.forEach((ref) => {
if (this.$refs.textRef.$refs[ref] !== undefined) {
offsetHeight = this.$refs.textRef.$refs[ref].offsetHeight;
scrollHeight = this.$refs.textRef.$refs[ref].scrollHeight;
this.expandable = scrollHeight > offsetHeight;
}
});
return;
}
offsetHeight = this.$refs.textRef.offsetHeight;
scrollHeight = this.$refs.textRef.scrollHeight;
this.expandable = scrollHeight > offsetHeight;
toggleExpandableState(expandable) {
this.expandable = expandable;
},
},
};