@inriver/inri 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +5 -0
- package/componentProperties.d.ts +8 -0
- package/configs/base-eslint-config.js +36 -0
- package/configs/base-tailwind-config.js +149 -0
- package/i18n.ts +11 -0
- package/index.ts +25 -0
- package/package.json +36 -0
- package/src/components/CAutoBreadcrumbs.vue +72 -0
- package/src/components/CBtn.vue +98 -0
- package/src/components/CColorPicker.vue +55 -0
- package/src/components/CContextMenu.vue +29 -0
- package/src/components/CDatePicker.vue +51 -0
- package/src/components/CDeleteTileBtn.vue +52 -0
- package/src/components/CDialog.vue +253 -0
- package/src/components/CDialogCloseBtn.vue +9 -0
- package/src/components/CDirtyCheck.vue +68 -0
- package/src/components/CDrawer.vue +44 -0
- package/src/components/CDrawerButton.vue +91 -0
- package/src/components/CExpansionItem.vue +57 -0
- package/src/components/CExpansionItemWithCheckbox.vue +41 -0
- package/src/components/CFileDropzone.vue +114 -0
- package/src/components/CGrid.vue +101 -0
- package/src/components/CHeader.vue +10 -0
- package/src/components/CHeaderLinks.vue +61 -0
- package/src/components/CHeaderMenuLink.vue +51 -0
- package/src/components/CHeaderSearch.vue +142 -0
- package/src/components/CIconBtn.vue +70 -0
- package/src/components/CMenuItem.vue +66 -0
- package/src/components/CPageContainer.vue +11 -0
- package/src/components/CPageError.vue +24 -0
- package/src/components/CPageSpinner.vue +18 -0
- package/src/components/CSearch.vue +45 -0
- package/src/components/CSection.vue +29 -0
- package/src/components/CSectionTitle.vue +8 -0
- package/src/components/CSelect.vue +208 -0
- package/src/components/CSlideEffect.vue +62 -0
- package/src/components/CSpinner.vue +107 -0
- package/src/components/CTableActions.vue +16 -0
- package/src/components/CTileBtn.vue +178 -0
- package/src/components/CTimePicker.vue +42 -0
- package/src/components/CToggleGridView.vue +26 -0
- package/src/components/CToggleSidebarBtn.vue +19 -0
- package/src/components/CTooltip.vue +44 -0
- package/src/components/CTreeNode.vue +39 -0
- package/src/components/cards/CBannerCard.vue +103 -0
- package/src/components/cards/CSmallCard.vue +149 -0
- package/src/components/internal-components/ConfirmDialog.vue +78 -0
- package/src/composables/useDialog.ts +47 -0
- package/src/composables/useDirtyCheck.ts +25 -0
- package/src/composables/useDoubleClickListener.ts +28 -0
- package/src/composables/useSelectable.ts +62 -0
- package/src/composables/useTruncateChips.ts +46 -0
- package/src/i18n/de/index.ts +17 -0
- package/src/i18n/en/index.ts +84 -0
- package/src/i18n/es/index.ts +7 -0
- package/src/i18n/fr/index.ts +7 -0
- package/src/init/initInriFramework.ts +25 -0
- package/src/init/useInriApp.ts +55 -0
- package/src/layouts/CLayoutBlank.vue +7 -0
- package/src/layouts/CLayoutWithBackButton.vue +50 -0
- package/src/layouts/CLayoutWithSidebars.vue +46 -0
- package/src/layouts/layout-fragments/CPageAutoBreadcrumbs.vue +6 -0
- package/src/layouts/layout-fragments/CPageBackButton.vue +18 -0
- package/src/layouts/layout-fragments/CPageBlank.vue +20 -0
- package/src/layouts/layout-fragments/CPageFlexCol.vue +5 -0
- package/src/layouts/layout-fragments/CPageLeftSidebar.vue +5 -0
- package/src/layouts/layout-fragments/CPageMainContent.vue +5 -0
- package/src/layouts/layout-fragments/CPageOuterWrapper.vue +5 -0
- package/src/layouts/layout-fragments/CPageRightSidebar.vue +5 -0
- package/src/layouts/layout-fragments/CPageTitle.vue +34 -0
- package/src/plugins/dialog.ts +45 -0
- package/src/plugins/errors/ErrorFactories.ts +59 -0
- package/src/plugins/errors/Errors.ts +156 -0
- package/src/plugins/errors/Exception.ts +6 -0
- package/src/plugins/errors/globalErrorHandler.ts +21 -0
- package/src/plugins/errors/handleError.ts +34 -0
- package/src/plugins/errors/logger.ts +16 -0
- package/src/plugins/errors/vueErrorHandler.ts +26 -0
- package/src/plugins/globalAppConfig.ts +25 -0
- package/src/plugins/i18n.ts +46 -0
- package/src/plugins/inriIcons.ts +58 -0
- package/src/plugins/inriverQuasarComponentConfigurations.ts +67 -0
- package/src/plugins/library.ts +119 -0
- package/src/plugins/notify.ts +153 -0
- package/src/plugins/quasar-user-options.ts +19 -0
- package/src/plugins/validationRules.ts +292 -0
- package/src/shims-vue.d.ts +6 -0
- package/src/stores/inriStore.ts +54 -0
- package/src/stores/interfaceLanguageStore.ts +27 -0
- package/src/styles/_css-variables.scss +101 -0
- package/src/styles/_reset.scss +67 -0
- package/src/styles/_transitions.scss +9 -0
- package/src/styles/_utils.scss +15 -0
- package/src/styles/components/_breadcrumbs.scss +3 -0
- package/src/styles/components/_checkboxes.scss +17 -0
- package/src/styles/components/_chips.scss +17 -0
- package/src/styles/components/_forms.scss +203 -0
- package/src/styles/components/_menu.scss +12 -0
- package/src/styles/components/_notifications.scss +24 -0
- package/src/styles/components/_q-editor.scss +14 -0
- package/src/styles/components/_radio-buttons.scss +17 -0
- package/src/styles/components/_tables.scss +92 -0
- package/src/styles/components/_tooltips.scss +5 -0
- package/src/styles/components/_tree.scss +86 -0
- package/src/styles/components/_truncated-chips.scss +39 -0
- package/src/styles/main.scss +45 -0
- package/src/styles/quasar.variables.scss +50 -0
- package/src/typings.d.ts +70 -0
- package/src/utils/colorUtils.ts +73 -0
- package/src/utils/errorUtils.ts +37 -0
- package/src/utils/fileUtils.ts +57 -0
- package/src/utils/helpers.ts +6 -0
- package/src/utils/routeUtils.ts +23 -0
- package/tsconfig.json +37 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="flex flex-1 no-wrap items-center"
|
|
4
|
+
:title="text"
|
|
5
|
+
>
|
|
6
|
+
<span class="truncate flex-1">{{ text }}</span>
|
|
7
|
+
<span
|
|
8
|
+
v-if="showCount"
|
|
9
|
+
class="text-body2 font-bold bg-white rounded-full border border-green ml-1 px-2 h-6 min-w-6 flex items-center justify-center"
|
|
10
|
+
>
|
|
11
|
+
{{ count }}
|
|
12
|
+
</span>
|
|
13
|
+
|
|
14
|
+
<!-- Used for context menus -->
|
|
15
|
+
<slot />
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { computed } from 'vue'
|
|
21
|
+
|
|
22
|
+
const props = defineProps({
|
|
23
|
+
text: {
|
|
24
|
+
type: String,
|
|
25
|
+
default: undefined,
|
|
26
|
+
},
|
|
27
|
+
count: {
|
|
28
|
+
type: [String, Number],
|
|
29
|
+
default: undefined,
|
|
30
|
+
},
|
|
31
|
+
hideZeroCount: {
|
|
32
|
+
type: Boolean,
|
|
33
|
+
default: false,
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// If we want to hide the chip when the count is 0 then hide if null/undefined/0. Else only hide if null/undefined.
|
|
38
|
+
const showCount = computed(() => props.hideZeroCount ? props.count : (props.count !== null && props.count !== undefined))
|
|
39
|
+
</script>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="to ? 'router-link' : 'div'"
|
|
4
|
+
:to="to"
|
|
5
|
+
class="block p-5 h-[220px] relative group cursor-pointer no-underline"
|
|
6
|
+
:class="[`bg-${color}`, specialModifierClasses.card]"
|
|
7
|
+
>
|
|
8
|
+
<c-slide-effect :parent-color="color" />
|
|
9
|
+
|
|
10
|
+
<c-context-menu
|
|
11
|
+
class="absolute right-2 top-3"
|
|
12
|
+
:class="specialModifierClasses.contextMenu"
|
|
13
|
+
>
|
|
14
|
+
<c-menu-item>
|
|
15
|
+
menu item
|
|
16
|
+
</c-menu-item>
|
|
17
|
+
</c-context-menu>
|
|
18
|
+
|
|
19
|
+
<div
|
|
20
|
+
class="relative"
|
|
21
|
+
:class="specialModifierClasses.text"
|
|
22
|
+
>
|
|
23
|
+
<div
|
|
24
|
+
class="mt-16 text-grey-darker"
|
|
25
|
+
:class="specialModifierClasses.text"
|
|
26
|
+
>
|
|
27
|
+
{{ title1 }}
|
|
28
|
+
</div>
|
|
29
|
+
<div class="text-h1 leading-none mb-4">
|
|
30
|
+
{{ title2 }}
|
|
31
|
+
</div>
|
|
32
|
+
<div class="line-clamp-2">
|
|
33
|
+
{{ description }}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</component>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup>
|
|
40
|
+
import { computed } from 'vue'
|
|
41
|
+
import { getRelatedColors } from '../../utils/colorUtils'
|
|
42
|
+
|
|
43
|
+
const props = defineProps({
|
|
44
|
+
color: {
|
|
45
|
+
type: String,
|
|
46
|
+
default: 'secondary',
|
|
47
|
+
},
|
|
48
|
+
title1: {
|
|
49
|
+
type: String,
|
|
50
|
+
default: '',
|
|
51
|
+
},
|
|
52
|
+
title2: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: '',
|
|
55
|
+
},
|
|
56
|
+
description: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: '',
|
|
59
|
+
},
|
|
60
|
+
to: {
|
|
61
|
+
type: [String, Object],
|
|
62
|
+
default: undefined,
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const specialModifierClasses = computed(() => {
|
|
67
|
+
const color = props.color
|
|
68
|
+
const relatedColors = getRelatedColors(color)
|
|
69
|
+
|
|
70
|
+
if (color === 'primary' || color === 'secondary' || color === 'accent') {
|
|
71
|
+
return {
|
|
72
|
+
card: '',
|
|
73
|
+
contextMenu: 'text-' + relatedColors.onAltColor,
|
|
74
|
+
text: 'text-' + relatedColors.onColor,
|
|
75
|
+
}
|
|
76
|
+
} else if (color === 'surface') {
|
|
77
|
+
return {
|
|
78
|
+
card: 'border border-surface-border',
|
|
79
|
+
contextMenu: 'text-' + relatedColors.onColor,
|
|
80
|
+
text: 'text-' + relatedColors.onColor,
|
|
81
|
+
}
|
|
82
|
+
} else if (color === 'white') {
|
|
83
|
+
return {
|
|
84
|
+
card: 'border border-grey',
|
|
85
|
+
contextMenu: 'text-grey-darkest',
|
|
86
|
+
text: 'text-grey-darkest',
|
|
87
|
+
}
|
|
88
|
+
} else if (color === 'black') {
|
|
89
|
+
return {
|
|
90
|
+
card: '',
|
|
91
|
+
contextMenu: 'text-white',
|
|
92
|
+
text: 'text-white',
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Default for the rest
|
|
97
|
+
return {
|
|
98
|
+
card: '',
|
|
99
|
+
contextMenu: 'text-white',
|
|
100
|
+
text: 'text-grey-darkest',
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
</script>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
ref="card"
|
|
4
|
+
class="c-small-card"
|
|
5
|
+
@mouseover="isHoveredOver = true"
|
|
6
|
+
@mouseleave="isHoveredOver = false"
|
|
7
|
+
@click="$emit('click', $event)"
|
|
8
|
+
>
|
|
9
|
+
<div
|
|
10
|
+
class="content flex items-center justify-center"
|
|
11
|
+
:class="[
|
|
12
|
+
`bg-${backgroundColor}`,
|
|
13
|
+
selected ? 'is-selected' : ''
|
|
14
|
+
]"
|
|
15
|
+
>
|
|
16
|
+
<img
|
|
17
|
+
v-if="imageSrc"
|
|
18
|
+
:src="imageSrc"
|
|
19
|
+
class="image"
|
|
20
|
+
>
|
|
21
|
+
|
|
22
|
+
<q-icon
|
|
23
|
+
v-else
|
|
24
|
+
:name="icon"
|
|
25
|
+
:style="`font-size: ${iconSize}px`"
|
|
26
|
+
:class="iconClass"
|
|
27
|
+
/>
|
|
28
|
+
|
|
29
|
+
<transition name="fade">
|
|
30
|
+
<c-context-menu
|
|
31
|
+
v-if="$slots['context-menu-items']"
|
|
32
|
+
v-show="isHoveredOver"
|
|
33
|
+
class="absolute right-0 top-0"
|
|
34
|
+
@click.stop
|
|
35
|
+
>
|
|
36
|
+
<slot name="context-menu-items" />
|
|
37
|
+
</c-context-menu>
|
|
38
|
+
</transition>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div
|
|
42
|
+
v-if="text"
|
|
43
|
+
class="text mt-8px line-clamp-2 break-words"
|
|
44
|
+
:class="{ 'font-semibold': selected }"
|
|
45
|
+
:title="text"
|
|
46
|
+
>
|
|
47
|
+
{{ text }}
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div
|
|
51
|
+
v-if="description"
|
|
52
|
+
class="description"
|
|
53
|
+
>
|
|
54
|
+
{{ description }}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script setup lang="ts">
|
|
60
|
+
import { ref } from 'vue'
|
|
61
|
+
import { useDoubleClickListener } from '../../composables/useDoubleClickListener'
|
|
62
|
+
|
|
63
|
+
const emit = defineEmits(['click', 'double-click'])
|
|
64
|
+
const card = ref<HTMLDivElement>()
|
|
65
|
+
|
|
66
|
+
const isHoveredOver = ref(false)
|
|
67
|
+
|
|
68
|
+
useDoubleClickListener(card, () => {
|
|
69
|
+
emit('double-click')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
defineProps({
|
|
73
|
+
imageSrc: {
|
|
74
|
+
type: String,
|
|
75
|
+
default: undefined,
|
|
76
|
+
},
|
|
77
|
+
text: {
|
|
78
|
+
type: String,
|
|
79
|
+
default: undefined,
|
|
80
|
+
},
|
|
81
|
+
description: {
|
|
82
|
+
type: String,
|
|
83
|
+
default: undefined,
|
|
84
|
+
},
|
|
85
|
+
selected: {
|
|
86
|
+
type: Boolean,
|
|
87
|
+
default: false,
|
|
88
|
+
},
|
|
89
|
+
icon: {
|
|
90
|
+
type: String,
|
|
91
|
+
default: undefined,
|
|
92
|
+
},
|
|
93
|
+
iconClass: {
|
|
94
|
+
type: String,
|
|
95
|
+
default: undefined,
|
|
96
|
+
},
|
|
97
|
+
iconSize: {
|
|
98
|
+
type: [String, Number],
|
|
99
|
+
default: 18,
|
|
100
|
+
},
|
|
101
|
+
backgroundColor: {
|
|
102
|
+
type: String,
|
|
103
|
+
default: 'grey-lighter',
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<style scoped lang="scss">
|
|
109
|
+
.c-small-card {
|
|
110
|
+
width: var(--small-card-image-size);
|
|
111
|
+
min-height: 100px;
|
|
112
|
+
|
|
113
|
+
&:hover {
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
|
|
116
|
+
.content {
|
|
117
|
+
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.content {
|
|
122
|
+
position: relative;
|
|
123
|
+
width: var(--small-card-image-size);
|
|
124
|
+
height: var(--small-card-image-size);
|
|
125
|
+
transition: box-shadow 0.2s ease-in-out;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.image {
|
|
129
|
+
width: var(--small-card-image-size);
|
|
130
|
+
height: var(--small-card-image-size);
|
|
131
|
+
object-fit: cover;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.is-selected {
|
|
136
|
+
outline: 2px solid var(--q-accent);
|
|
137
|
+
outline-offset: -2px;
|
|
138
|
+
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
|
139
|
+
|
|
140
|
+
&::before {
|
|
141
|
+
content: "";
|
|
142
|
+
width: 100%;
|
|
143
|
+
height: 100%;
|
|
144
|
+
background: var(--q-accent);
|
|
145
|
+
opacity: 0.1;
|
|
146
|
+
position: absolute;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
</style>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<q-dialog
|
|
3
|
+
ref="dialogRef"
|
|
4
|
+
class="c-dialog"
|
|
5
|
+
@hide="onDialogHide"
|
|
6
|
+
>
|
|
7
|
+
<q-card
|
|
8
|
+
data-id="confirm-dialog"
|
|
9
|
+
class="dialog-card"
|
|
10
|
+
>
|
|
11
|
+
<q-card-section class="px-5 pt-6 pb-4">
|
|
12
|
+
<h3
|
|
13
|
+
v-if="title"
|
|
14
|
+
data-id="confirm-dialog-title"
|
|
15
|
+
>
|
|
16
|
+
{{ title }}
|
|
17
|
+
</h3>
|
|
18
|
+
<div
|
|
19
|
+
data-id="confirm-dialog-description"
|
|
20
|
+
class="mt-2px"
|
|
21
|
+
>
|
|
22
|
+
{{ text }}
|
|
23
|
+
</div>
|
|
24
|
+
</q-card-section>
|
|
25
|
+
|
|
26
|
+
<div class="dialog-card-action-buttons px-5 pt-2 pb-10">
|
|
27
|
+
<c-btn
|
|
28
|
+
data-id="confirm-dialog-ok-button"
|
|
29
|
+
:label="confirmButtonText || $t('inri.common.confirm')"
|
|
30
|
+
:color="confirmButtonColor"
|
|
31
|
+
@click="onDialogOK"
|
|
32
|
+
/>
|
|
33
|
+
<c-btn
|
|
34
|
+
v-if="!hideCancelButton"
|
|
35
|
+
data-id="confirm-dialog-cancel-button"
|
|
36
|
+
:label="cancelButtonText || $t('inri.common.cancel')"
|
|
37
|
+
color="surface"
|
|
38
|
+
@click="onDialogCancel"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
</q-card>
|
|
42
|
+
</q-dialog>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
// For further documentation regarding custom programmatic dialogs:
|
|
47
|
+
// https://quasar.dev/quasar-plugins/dialog
|
|
48
|
+
|
|
49
|
+
import { useDialogPluginComponent } from 'quasar'
|
|
50
|
+
|
|
51
|
+
defineProps({
|
|
52
|
+
title: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: undefined,
|
|
55
|
+
},
|
|
56
|
+
text: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: undefined,
|
|
59
|
+
},
|
|
60
|
+
confirmButtonText: {
|
|
61
|
+
type: String,
|
|
62
|
+
default: undefined,
|
|
63
|
+
},
|
|
64
|
+
cancelButtonText: {
|
|
65
|
+
type: String,
|
|
66
|
+
default: undefined,
|
|
67
|
+
},
|
|
68
|
+
confirmButtonColor: {
|
|
69
|
+
type: String,
|
|
70
|
+
default: 'primary',
|
|
71
|
+
},
|
|
72
|
+
hideCancelButton: Boolean,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
defineEmits([...useDialogPluginComponent.emits])
|
|
76
|
+
|
|
77
|
+
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
|
|
78
|
+
</script>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getCurrentInstance, reactive, toRefs } from 'vue'
|
|
2
|
+
import notify from '../plugins/notify'
|
|
3
|
+
import { useDirtyCheck } from './useDirtyCheck'
|
|
4
|
+
|
|
5
|
+
export function useDialog (data?: object, immediate = true) {
|
|
6
|
+
const { isDirty, freezeCleanData } = useDirtyCheck(data, immediate)
|
|
7
|
+
const { emit } = getCurrentInstance() as any
|
|
8
|
+
|
|
9
|
+
const state = reactive({
|
|
10
|
+
isLoading: false,
|
|
11
|
+
showDialog: true,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
// Functions
|
|
15
|
+
function closeAfterTimeout () {
|
|
16
|
+
state.showDialog = false
|
|
17
|
+
|
|
18
|
+
// Let the q-dialog close transition finish before we tell our parent to close.
|
|
19
|
+
// This way we can use v-if on our parent to destroy the component but still have graceful transitions.
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
emit('update:show', false)
|
|
22
|
+
}, 500)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function cancel () {
|
|
26
|
+
closeAfterTimeout()
|
|
27
|
+
emit('after-cancel')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function confirmSuccess (msg: string | null | undefined, returnValue: any | undefined = undefined) {
|
|
31
|
+
closeAfterTimeout()
|
|
32
|
+
state.isLoading = false
|
|
33
|
+
emit('after-confirm', returnValue)
|
|
34
|
+
|
|
35
|
+
if (msg) {
|
|
36
|
+
notify.success(msg)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
...toRefs(state),
|
|
42
|
+
cancel,
|
|
43
|
+
confirmSuccess,
|
|
44
|
+
isDirty,
|
|
45
|
+
freezeCleanData,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { computed, reactive, ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function useDirtyCheck (data?: object, immediate = true) {
|
|
4
|
+
const isEnabled = ref(immediate)
|
|
5
|
+
const reactiveData = reactive(data || {})
|
|
6
|
+
const initialData = ref(JSON.stringify(reactiveData))
|
|
7
|
+
|
|
8
|
+
const isDirty = computed(() => {
|
|
9
|
+
if (!data || !isEnabled.value) {
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return JSON.stringify(reactiveData) !== initialData.value
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
function freezeCleanData () {
|
|
17
|
+
isEnabled.value = true
|
|
18
|
+
initialData.value = JSON.stringify(reactiveData)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
isDirty,
|
|
23
|
+
freezeCleanData,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Ref } from 'vue'
|
|
2
|
+
import { useEventListener } from '@vueuse/core'
|
|
3
|
+
|
|
4
|
+
export function useDoubleClickListener (
|
|
5
|
+
target: Ref<HTMLElement | undefined> | HTMLElement,
|
|
6
|
+
callback: (e: Event) => any,
|
|
7
|
+
) {
|
|
8
|
+
let clickCounter = 0
|
|
9
|
+
let clickTimer
|
|
10
|
+
|
|
11
|
+
function clickHandler (e) {
|
|
12
|
+
clickCounter++
|
|
13
|
+
|
|
14
|
+
if (clickCounter === 1) {
|
|
15
|
+
clickTimer = window.setTimeout(function () {
|
|
16
|
+
clickCounter = 0
|
|
17
|
+
}, 500)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (clickCounter > 1) {
|
|
21
|
+
clearTimeout(clickTimer)
|
|
22
|
+
clickCounter = 0
|
|
23
|
+
callback(e)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
useEventListener(target, 'click', clickHandler)
|
|
28
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { getCurrentInstance, computed, ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export default function useSelectable (allItems, selectedItems) {
|
|
4
|
+
const { emit } = getCurrentInstance() as any
|
|
5
|
+
|
|
6
|
+
// This will convert a "reactive" array to "ref". "ref"s will still be a ref.
|
|
7
|
+
// This lets us use a reactive -or- ref array as a parameter.
|
|
8
|
+
const _allItems = ref(allItems)
|
|
9
|
+
|
|
10
|
+
const allSelectedStatus = computed(() => {
|
|
11
|
+
if (!selectedItems.value || !selectedItems.value.length) {
|
|
12
|
+
return false
|
|
13
|
+
} else if (_allItems.value.length === selectedItems.value.length) {
|
|
14
|
+
return true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return undefined
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
function toggleItem (evt, item = undefined) {
|
|
21
|
+
// If someone passes just one parameter we assume it's the item that we want to toggle instead of the event.
|
|
22
|
+
// The evt parameter is not actually used here, but is automatically passed along on a Q-Table row click.
|
|
23
|
+
const _item = item || evt
|
|
24
|
+
const existingItemIndex = selectedItems.value.findIndex(i => JSON.stringify(i) === JSON.stringify(_item))
|
|
25
|
+
|
|
26
|
+
if (existingItemIndex >= 0) {
|
|
27
|
+
// Remove item
|
|
28
|
+
selectedItems.value.splice(existingItemIndex, 1)
|
|
29
|
+
} else {
|
|
30
|
+
// Add item
|
|
31
|
+
selectedItems.value.push(_item)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function toggleAllItems () {
|
|
36
|
+
if (allSelectedStatus.value === false) {
|
|
37
|
+
selectedItems.value = [..._allItems.value]
|
|
38
|
+
} else {
|
|
39
|
+
selectedItems.value = []
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function toggleAllItemsWithEmit (evt, emitString = 'update:modelValue') {
|
|
44
|
+
if (allSelectedStatus.value === false) {
|
|
45
|
+
emit(emitString, [..._allItems.value])
|
|
46
|
+
} else {
|
|
47
|
+
emit(emitString, [])
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isSelected (item) {
|
|
52
|
+
return selectedItems.value.includes(item)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
allSelectedStatus,
|
|
57
|
+
toggleItem,
|
|
58
|
+
toggleAllItems,
|
|
59
|
+
toggleAllItemsWithEmit,
|
|
60
|
+
isSelected,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { nextTick } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function useTruncateChips () {
|
|
4
|
+
const chipsColumnHeaderSelector = '.js-chips-column-header'
|
|
5
|
+
const chipsWrapperSelector = '.truncated-chips'
|
|
6
|
+
const chipSelector = '.q-chip'
|
|
7
|
+
const truncatedChipClass = 'truncated-chip'
|
|
8
|
+
const arrowBtnOffset = 32
|
|
9
|
+
|
|
10
|
+
function onResize () {
|
|
11
|
+
truncateChips()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function truncateChips () {
|
|
15
|
+
nextTick(() => {
|
|
16
|
+
const chipsHeaderRect = document.querySelector(chipsColumnHeaderSelector)?.getBoundingClientRect() as DOMRect
|
|
17
|
+
const chipsWrappers = document.querySelectorAll(chipsWrapperSelector)
|
|
18
|
+
|
|
19
|
+
// There is one chips wrapper for every row in a table
|
|
20
|
+
chipsWrappers.forEach(wrapper => {
|
|
21
|
+
const renderedChips = Array.from(wrapper.querySelectorAll(chipSelector))
|
|
22
|
+
|
|
23
|
+
// Reset hidden
|
|
24
|
+
renderedChips.forEach(chip => chip.classList.remove(truncatedChipClass))
|
|
25
|
+
|
|
26
|
+
// Check coordinates
|
|
27
|
+
renderedChips.every((chip) => {
|
|
28
|
+
const chipRect = chip.getBoundingClientRect()
|
|
29
|
+
|
|
30
|
+
// As soon as you find a chip that goes outside the boundaries then hide it (and all the following chips in the list)
|
|
31
|
+
if (chipRect.right > (chipsHeaderRect.right - arrowBtnOffset)) {
|
|
32
|
+
chip.classList.add(truncatedChipClass)
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return true
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
onResize,
|
|
44
|
+
truncateChips,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
inri: {
|
|
3
|
+
common: {
|
|
4
|
+
ok: 'OK',
|
|
5
|
+
save: 'Speichern',
|
|
6
|
+
cancel: 'Abbrechen',
|
|
7
|
+
confirm: 'Bestätigen',
|
|
8
|
+
yes: 'Ja',
|
|
9
|
+
},
|
|
10
|
+
languages: {
|
|
11
|
+
en: 'englisch',
|
|
12
|
+
de: 'deutsch',
|
|
13
|
+
fr: 'französisch',
|
|
14
|
+
es: 'spanisch',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
inri: {
|
|
3
|
+
validation: {
|
|
4
|
+
required: 'This field is required',
|
|
5
|
+
maxLength: 'This field can be no longer than {parsedLength} characters long',
|
|
6
|
+
minLength: 'This field must be at least {parsedLength} characters long',
|
|
7
|
+
email: 'This field must be a valid email address',
|
|
8
|
+
url: 'This field must be a valid URL',
|
|
9
|
+
https: 'This field must be a valid URL using https',
|
|
10
|
+
extension: 'Valid file extensions: {extensions}',
|
|
11
|
+
contains: 'This field is not valid. Must contain {match}',
|
|
12
|
+
notContains: 'This field is not valid. Must not contain {match}',
|
|
13
|
+
notBeginsWith: 'This field is not valid. Must not begin with {match}',
|
|
14
|
+
notEndsWith: 'This field is not valid. Must not end with {match}',
|
|
15
|
+
maxFileSize: 'Files can be no larger than {parsedFileSize}',
|
|
16
|
+
maxImageSize: 'Images can be no larger than {size}',
|
|
17
|
+
fileSizeTooBig: 'file size too big',
|
|
18
|
+
fileTypeInvalid: 'file type invalid',
|
|
19
|
+
},
|
|
20
|
+
error: {
|
|
21
|
+
unauthorized: 'Unauthorized',
|
|
22
|
+
session_expired: 'Your session has expired and you will be returned to login.',
|
|
23
|
+
internal_server_error: 'Internal server error',
|
|
24
|
+
unknown_error: 'Unknown error',
|
|
25
|
+
undefined_error: 'Undefined error',
|
|
26
|
+
file_error: 'Could not load file',
|
|
27
|
+
error: 'Error',
|
|
28
|
+
network_error: 'Network error',
|
|
29
|
+
error_occurred: 'An error occurred',
|
|
30
|
+
service_unavailable: 'Service unavailable',
|
|
31
|
+
service_unavailable_try_again: 'Request could not be handled. Please try again in a minute',
|
|
32
|
+
not_found: 'Not found',
|
|
33
|
+
requested_asset_not_found: 'The requested asset does not exist',
|
|
34
|
+
folder_does_not_exist: 'Folder does not exist',
|
|
35
|
+
could_not_retrieve_assets: 'Folder does not exist. Could not retrieve assets',
|
|
36
|
+
error_code: 'error code: {code}',
|
|
37
|
+
page_does_not_exist: 'Sorry! This page does not exist!',
|
|
38
|
+
},
|
|
39
|
+
common: {
|
|
40
|
+
ok: 'ok',
|
|
41
|
+
save: 'save',
|
|
42
|
+
cancel: 'cancel',
|
|
43
|
+
confirm: 'confirm',
|
|
44
|
+
yes: 'yes',
|
|
45
|
+
delete: 'delete',
|
|
46
|
+
close: 'close',
|
|
47
|
+
download: 'download',
|
|
48
|
+
more: 'more',
|
|
49
|
+
},
|
|
50
|
+
dialog: {
|
|
51
|
+
unsaved_changes_title: 'unsaved changes',
|
|
52
|
+
unsaved_changes_message: 'changes are not saved, do you want to continue?',
|
|
53
|
+
confirm_tooltip_invalid_fields: 'all required fields needs to have a valid value',
|
|
54
|
+
confirm_delete_title: 'are you sure you want to delete?',
|
|
55
|
+
},
|
|
56
|
+
toggle_actions: {
|
|
57
|
+
sidebar: 'toggle sidebar',
|
|
58
|
+
grid_view: 'grid view',
|
|
59
|
+
table_view: 'table view',
|
|
60
|
+
},
|
|
61
|
+
select: {
|
|
62
|
+
select_all: 'select all',
|
|
63
|
+
no_results: 'no results',
|
|
64
|
+
},
|
|
65
|
+
navigate_back: 'go back',
|
|
66
|
+
languages: {
|
|
67
|
+
en: 'english',
|
|
68
|
+
de: 'german',
|
|
69
|
+
fr: 'french',
|
|
70
|
+
es: 'spanish',
|
|
71
|
+
},
|
|
72
|
+
dropzone: {
|
|
73
|
+
hint: 'drop files to instantly add them to {0}',
|
|
74
|
+
hint_general: 'drop files to instantly add them here',
|
|
75
|
+
invalid_drop_items: 'Invalid item(s) dragged into the dropzone',
|
|
76
|
+
},
|
|
77
|
+
toggle_drawer: 'toggle drawer',
|
|
78
|
+
authentication: {
|
|
79
|
+
session_expired: 'your session has expired',
|
|
80
|
+
session_inactive: 'you have been inactive for too long, or you have been logged out of the main inriver portal.',
|
|
81
|
+
log_in: 'log in again',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
}
|