summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--css/Properties/Properties.scss99
-rw-r--r--css/Properties/PropertyTitle.scss33
-rw-r--r--css/contacts.scss19
-rw-r--r--css/details.scss78
-rw-r--r--src/components/ContactDetails.vue3
-rw-r--r--src/components/ContactDetails/ContactDetailsProperty.vue17
-rw-r--r--src/components/properties/PropertyDateTime.vue34
-rw-r--r--src/components/properties/PropertyMultipleText.vue33
-rw-r--r--src/components/properties/PropertyText.vue31
-rw-r--r--src/components/properties/PropertyTitle.vue46
10 files changed, 286 insertions, 107 deletions
diff --git a/css/Properties/Properties.scss b/css/Properties/Properties.scss
new file mode 100644
index 00000000..bddf8edf
--- /dev/null
+++ b/css/Properties/Properties.scss
@@ -0,0 +1,99 @@
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.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/>.
+ *
+ */
+
+.property {
+ @include generate-grid-span(1);
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: column;
+ position: relative;
+ width: $grid-column-width;
+ margin-bottom: $grid-height-unit;
+
+ &__delete {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: $grid-height-unit;
+ height: $grid-height-unit;
+ margin: 0;
+ border: 0;
+ background-color: transparent;
+ opacity: .5;
+ display: none;
+ &:hover,
+ a:active,
+ a:focus {
+ opacity: .7;
+ }
+ }
+
+ &:hover &__delete,
+ a:active &__delete,
+ a:focus &__delete {
+ display: block;
+ }
+
+ // property row
+ &__row {
+ display: flex;
+ align-items: center;
+ padding-right: 44px;
+ height: $grid-height-unit;
+ position: relative;
+
+ input {
+ flex: 1 1;
+ }
+
+ }
+
+ // label or multiselect
+ &__label,
+ &__label.multiselect {
+ margin: $grid-input-margin;
+ margin: $grid-input-margin 5px $grid-input-margin 0;
+ height: $grid-input-height-with-margin;
+ padding: $grid-input-padding 0;
+ width: 120px;
+ opacity: .7;
+
+ &,
+ .multiselect__input::placeholder {
+ text-align: right;
+ }
+
+ &:not(.multiselect) {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ overflow-x: hidden;
+ }
+
+ &.multiselect {
+ &:hover,
+ &:focus {
+ opacity: 1;
+ }
+ }
+ }
+}
diff --git a/css/Properties/PropertyTitle.scss b/css/Properties/PropertyTitle.scss
new file mode 100644
index 00000000..c70ba855
--- /dev/null
+++ b/css/Properties/PropertyTitle.scss
@@ -0,0 +1,33 @@
+/**
+ * @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
+ *
+ * @author John Molakvoæ <skjnldsv@protonmail.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/>.
+ *
+ */
+
+.property__title {
+ display: flex;
+ align-items: center;
+ margin: 0;
+ opacity: 0.6;
+ user-select: none;
+
+ .property__title--icon {
+ background-origin: center right;
+ }
+} \ No newline at end of file
diff --git a/css/contacts.scss b/css/contacts.scss
index d1b2da3e..0d8ed0e6 100644
--- a/css/contacts.scss
+++ b/css/contacts.scss
@@ -1,2 +1,21 @@
+$grid-height-unit: 40px;
+$grid-input-padding: 7px;
+$grid-input-margin: 3px;
+$grid-column-width: 380px;
+$grid-input-height-with-margin: #{$grid-height-unit - $grid-input-margin * 2};
+
+@mixin generate-grid-span($default-unit) {
+ // we only supports 10 props of the same type
+ @for $i from 1 through 10 {
+ &.grid-span-#{$i} {
+ // default unit + title + bottom padding
+ grid-row-start: span #{$i * $default-unit};
+ }
+ }
+}
+
+
@import 'settings';
@import 'details';
+@import './Properties/Properties';
+@import './Properties/PropertyTitle'; \ No newline at end of file
diff --git a/css/details.scss b/css/details.scss
index 09807a9b..c8c8a31e 100644
--- a/css/details.scss
+++ b/css/details.scss
@@ -113,21 +113,7 @@
}
}
- $grid-height-unit: 40px;
- $grid-input-padding: 7px;
- $grid-input-margin: 3px;
$grid-column-width: 380px;
- $grid-input-height-with-margin: #{$grid-height-unit - $grid-input-margin * 2};
-
- @mixin generate-grid-span($default-unit) {
- /* we only supports 10 props of the same type */
- @for $i from 1 through 10 {
- &.grid-span-#{$i} {
- /* default unit + title + bottom padding */
- grid-row-start: span #{$i * $default-unit};
- }
- }
- }
// contact details
section.contact-details {
@@ -135,69 +121,5 @@
/* unquote is a strange hack to avoid removal of the comma by the scss compiler */
grid-template-columns: repeat(auto-fit, minmax(unquote('#{$grid-column-width}'), 1fr));
grid-column-gap: 20px;
- .contact-details-property {
- @include generate-grid-span(1);
- display: flex;
- flex-wrap: wrap;
- flex-direction: column;
- position: relative;
- width: $grid-column-width;
- .icon-delete {
- position: absolute;
- top: 0;
- right: 0;
- width: 44px;
- height: 100%;
- margin: 0;
- border: 0;
- background-color: transparent;
- opacity: .5;
- display: none;
- &:hover,
- a:active,
- a:focus {
- opacity: .7;
- }
- }
- &:hover,
- a:active,
- a:focus {
- .icon-delete {
- display: block;
- }
- }
- }
- .contact-details-label {
- margin: $grid-input-margin;
- margin: $grid-input-margin 5px $grid-input-margin 0;
- width: 120px;
- height: $grid-input-height-with-margin;
- padding: $grid-input-padding 0;
- opacity: .7;
- &, .multiselect__input::placeholder {
- text-align: right;
- }
- &:not(.multiselect-vue) {
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow: hidden;
- overflow-x: hidden;
- }
- &.multiselect-vue {
- &:hover,
- &:focus {
- opacity: 1;
- }
- }
- }
- .contact-details-property-row {
- display: flex;
- align-items: center;
- margin-right: 44px;
- input {
- flex: 1 1;
- }
- }
-
}
} \ No newline at end of file
diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue
index 1ceda06c..46be417e 100644
--- a/src/components/ContactDetails.vue
+++ b/src/components/ContactDetails.vue
@@ -80,7 +80,8 @@
<!-- contact details -->
<section class="contact-details">
- <contact-details-property v-for="(property, index) in sortedProperties" :key="index" :property="property" />
+ <contact-details-property v-for="(property, index) in sortedProperties" :key="index" :index="index"
+ :sorted-properties="sortedProperties" :property="property" />
</section>
</template>
</div>
diff --git a/src/components/ContactDetails/ContactDetailsProperty.vue b/src/components/ContactDetails/ContactDetailsProperty.vue
index a9800fe0..ff973bdc 100644
--- a/src/components/ContactDetails/ContactDetailsProperty.vue
+++ b/src/components/ContactDetails/ContactDetailsProperty.vue
@@ -23,7 +23,7 @@
<template>
<!-- If not in the rfcProps then we don't want to display it -->
<component v-if="propModel" :is="componentInstance" :select-type="selectType"
- :prop-model="propModel" :value="value" />
+ :prop-model="propModel" :value="value" :is-first-property="isFirstProperty" />
</template>
<script>
@@ -41,6 +41,16 @@ export default {
property: {
type: Property,
default: true
+ },
+ sortedProperties: {
+ type: Array,
+ default() {
+ return []
+ }
+ },
+ index: {
+ type: Number,
+ default: 0
}
},
@@ -65,6 +75,11 @@ export default {
return rfcProps.fieldOrder
},
+ // is this the first property of its kind
+ isFirstProperty() {
+ return true
+ },
+
// the type of the prop e.g. FN
propName() {
return this.property.name
diff --git a/src/components/properties/PropertyDateTime.vue b/src/components/properties/PropertyDateTime.vue
index c7d79c25..53f50391 100644
--- a/src/components/properties/PropertyDateTime.vue
+++ b/src/components/properties/PropertyDateTime.vue
@@ -21,21 +21,24 @@
-->
<template>
- <div v-if="propModel" class="contact-details-property grid-span-1">
- <div class="contact-details-property-row">
+ <div v-if="propModel" :class="`grid-span-${gridLength}`" class="property">
+ <!-- title if first element -->
+ <property-title v-if="isFirstProperty && propModel.icon" :icon="propModel.icon" :readable-name="propModel.readableName" />
+
+ <div class="property__row">
<!-- type selector -->
<multiselect v-if="propModel.options" v-model="selectType"
:options="propModel.options" :searchable="false" :placeholder="t('contacts', 'Select type')"
- class="multiselect-vue contact-details-label" track-by="id" label="name" />
+ class="multiselect-vue property__label" track-by="id" label="name" />
<!-- if we do not support any type on our model but one is set anyway -->
- <div v-else-if="selectType" class="contact-details-label">{{ selectType.name }}</div>
+ <div v-else-if="selectType" class="property__label">{{ selectType.name }}</div>
<!-- no options, empty space -->
- <div v-else class="contact-details-label">{{ propModel.readableName }}</div>
+ <div v-else class="property__label">{{ propModel.readableName }}</div>
<!-- delete the prop -->
- <button :title="t('contacts', 'Delete')" class="icon-delete" @click="deleteProperty" />
+ <button :title="t('contacts', 'Delete')" class="property__delete icon-delete" @click="deleteProperty" />
<input v-model="value" type="text">
</div>
@@ -44,13 +47,14 @@
<script>
import Multiselect from 'vue-multiselect'
-import { VCardTime } from 'ical.js'
+import propertyTitle from './PropertyTitle'
export default {
name: 'PropertyDateTime',
components: {
- Multiselect
+ Multiselect,
+ propertyTitle
},
props: {
@@ -63,8 +67,20 @@ export default {
default: () => {}
},
value: {
- type: [VCardTime],
+ type: [String],
default: ''
+ },
+ isFirstProperty: {
+ type: Boolean,
+ default: true
+ }
+ },
+
+ computed: {
+ gridLength() {
+ let hasTitle = this.isFirstProperty && this.propModel.icon ? 1 : 0
+ // length is one & add one space at the end
+ return hasTitle + 1 + 1
}
},
diff --git a/src/components/properties/PropertyMultipleText.vue b/src/components/properties/PropertyMultipleText.vue
index 69975db9..3a52b843 100644
--- a/src/components/properties/PropertyMultipleText.vue
+++ b/src/components/properties/PropertyMultipleText.vue
@@ -21,25 +21,28 @@
-->
<template>
- <div v-if="propModel" :class="`grid-span-${gridLength}`" class="contact-details-property">
- <div class="contact-details-property-row">
+ <div v-if="propModel" :class="`grid-span-${gridLength}`" class="property">
+ <!-- title if first element -->
+ <property-title v-if="isFirstProperty && propModel.icon" :icon="propModel.icon" :readable-name="propModel.readableName" />
+
+ <div class="property__row">
<!-- type selector -->
<multiselect v-if="propModel.options" v-model="selectType"
:options="propModel.options" :searchable="false" :placeholder="t('contacts', 'Select type')"
- class="multiselect-vue contact-details-label" track-by="id" label="name" />
+ class="multiselect-vue property__label" track-by="id" label="name" />
<!-- if we do not support any type on our model but one is set anyway -->
- <div v-else-if="selectType" class="contact-details-label">{{ selectType.name }}</div>
+ <div v-else-if="selectType" class="property__label">{{ selectType.name }}</div>
<!-- no options, empty space -->
- <div v-else class="contact-details-label">{{ propModel.readableName }}</div>
+ <div v-else class="property__label">{{ propModel.readableName }}</div>
<!-- delete the prop -->
- <button :title="t('contacts', 'Delete')" class="icon-delete" @click="deleteProperty" />
+ <button :title="t('contacts', 'Delete')" class="property__delete icon-delete" @click="deleteProperty" />
</div>
- <div v-for="index in propModel.displayOrder" :key="index" class="contact-details-property-row">
- <div class="contact-details-label">{{ propModel.readableValues[index] }}</div>
+ <div v-for="index in propModel.displayOrder" :key="index" class="property__row">
+ <div class="property__label">{{ propModel.readableValues[index] }}</div>
<input v-model="value[index]" type="text">
</div>
</div>
@@ -47,12 +50,14 @@
<script>
import Multiselect from 'vue-multiselect'
+import propertyTitle from './PropertyTitle'
export default {
name: 'PropertyText',
components: {
- Multiselect
+ Multiselect,
+ propertyTitle
},
props: {
@@ -67,14 +72,20 @@ export default {
value: {
type: [Array, String, Object],
default: ''
+ },
+ isFirstProperty: {
+ type: Boolean,
+ default: true
}
},
computed: {
gridLength() {
- let hasType = this.propModel.options || this.selectType
+ let hasType = this.propModel.options || this.selectType ? 1 : 0
+ let hasTitle = this.isFirstProperty && this.propModel.icon ? 1 : 0
let length = this.propModel.displayOrder ? this.propModel.displayOrder.length : this.value.length
- return hasType ? length + 1 : length
+ // add one space at the end
+ return hasType + hasTitle + length + 1
}
},
diff --git a/src/components/properties/PropertyText.vue b/src/components/properties/PropertyText.vue
index 91019237..6dd72eda 100644
--- a/src/components/properties/PropertyText.vue
+++ b/src/components/properties/PropertyText.vue
@@ -21,21 +21,24 @@
-->
<template>
- <div v-if="propModel" class="contact-details-property grid-span-1">
- <div class="contact-details-property-row">
+ <div v-if="propModel" :class="`grid-span-${gridLength}`" class="property">
+ <!-- title if first element -->
+ <property-title v-if="isFirstProperty && propModel.icon" :icon="propModel.icon" :readable-name="propModel.readableName" />
+
+ <div class="property__row">
<!-- type selector -->
<multiselect v-if="propModel.options" v-model="selectType"
:options="propModel.options" :searchable="false" :placeholder="t('contacts', 'Select type')"
- class="multiselect-vue contact-details-label" track-by="id" label="name" />
+ class="multiselect-vue property__label" track-by="id" label="name" />
<!-- if we do not support any type on our model but one is set anyway -->
- <div v-else-if="selectType" class="contact-details-label">{{ selectType.name }}</div>
+ <div v-else-if="selectType" class="property__label">{{ selectType.name }}</div>
<!-- no options, empty space -->
- <div v-else class="contact-details-label">{{ propModel.readableName }}</div>
+ <div v-else class="property__label">{{ propModel.readableName }}</div>
<!-- delete the prop -->
- <button :title="t('contacts', 'Delete')" class="icon-delete" @click="deleteProperty" />
+ <button :title="t('contacts', 'Delete')" class="property__delete icon-delete" @click="deleteProperty" />
<input v-model="value" type="text">
</div>
@@ -44,12 +47,14 @@
<script>
import Multiselect from 'vue-multiselect'
+import propertyTitle from './PropertyTitle'
export default {
name: 'PropertyText',
components: {
- Multiselect
+ Multiselect,
+ propertyTitle
},
props: {
@@ -64,6 +69,18 @@ export default {
value: {
type: [String],
default: ''
+ },
+ isFirstProperty: {
+ type: Boolean,
+ default: true
+ }
+ },
+
+ computed: {
+ gridLength() {
+ let hasTitle = this.isFirstProperty && this.propModel.icon ? 1 : 0
+ // length is one & add one space at the end
+ return hasTitle + 1 + 1
}
},
diff --git a/src/components/properties/PropertyTitle.vue b/src/components/properties/PropertyTitle.vue
new file mode 100644
index 00000000..d857a647
--- /dev/null
+++ b/src/components/properties/PropertyTitle.vue
@@ -0,0 +1,46 @@
+<!--
+ - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
+ -
+ - @author John Molakvoæ <skjnldsv@protonmail.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/>.
+ -
+ -->
+
+<template>
+ <h3 class="property__title property__row">
+ <div :class="icon" class="property__label property__title--icon" />
+ {{ readableName }}
+ </h3>
+</template>
+
+<script>
+export default {
+ name: 'PropertyTitle',
+
+ props: {
+ icon: {
+ type: String,
+ default: ''
+ },
+ readableName: {
+ type: String,
+ default: ''
+ }
+ }
+
+}
+</script>