@konfuzio/document-validation-ui 0.1.48-dev.2 → 0.1.48
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/dist/css/app.css +1 -1
- package/dist/index.html +1 -1
- package/dist/js/app.js +1 -1
- package/dist/js/app.js.map +1 -1
- package/dist/js/chunk-vendors.js +6 -6
- package/dist/js/chunk-vendors.js.map +1 -1
- package/jest.config.js +1 -1
- package/package.json +2 -1
- package/src/api.js +18 -8
- package/src/assets/scss/document_page.scss +10 -0
- package/src/assets/scss/theme.scss +9 -0
- package/src/components/App.vue +58 -3
- package/src/components/DocumentPage/DocumentPage.vue +42 -81
- package/src/components/DocumentPage/EditAnnotation.vue +8 -3
- package/src/components/DocumentPage/NewAnnotation.vue +12 -3
- package/src/store/document.js +5 -2
- package/src/utils/keycloak.js +38 -0
package/jest.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@konfuzio/document-validation-ui",
|
|
3
|
-
"version": "0.1.48
|
|
3
|
+
"version": "0.1.48",
|
|
4
4
|
"repository": "git://github.com:konfuzio-ai/document-validation-ui.git",
|
|
5
5
|
"main": "dist/app.js",
|
|
6
6
|
"scripts": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"axios": "^1.7.4",
|
|
33
33
|
"bignumber.js": "^9.1.0",
|
|
34
34
|
"buefy": "^0.9.22",
|
|
35
|
+
"keycloak-js": "^26.0.6",
|
|
35
36
|
"konva": "^8.3.13",
|
|
36
37
|
"sass": "^1.56.0",
|
|
37
38
|
"sass-loader": "^13.1.0",
|
package/src/api.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
|
+
import { updateKeycloakToken } from "./utils/keycloak";
|
|
2
3
|
|
|
3
|
-
let HTTP, FILE_REQUEST, authToken, appLocale;
|
|
4
|
+
let HTTP, FILE_REQUEST, authToken, appLocale, isKeycloakAuth;
|
|
4
5
|
const DEFAULT_URL = "https://app.konfuzio.com";
|
|
5
6
|
const FILE_URL = process.env.VUE_APP_IMAGE_URL;
|
|
6
7
|
|
|
7
|
-
axios.defaults.xsrfCookieName = "csrftoken";
|
|
8
|
-
axios.defaults.xsrfHeaderName = "X-CSRFToken";
|
|
9
|
-
|
|
10
8
|
HTTP = axios.create({
|
|
11
9
|
baseURL: process.env.VUE_APP_API_URL || `${DEFAULT_URL}/api/v3/`,
|
|
12
10
|
});
|
|
@@ -20,6 +18,10 @@ const setAuthToken = (token) => {
|
|
|
20
18
|
authToken = token;
|
|
21
19
|
};
|
|
22
20
|
|
|
21
|
+
const setIsKeycloakAuth = (result) => {
|
|
22
|
+
isKeycloakAuth = result;
|
|
23
|
+
};
|
|
24
|
+
|
|
23
25
|
const setApiUrl = (url) => {
|
|
24
26
|
HTTP.defaults.baseURL = url;
|
|
25
27
|
};
|
|
@@ -32,15 +34,22 @@ const setLocale = (locale) => {
|
|
|
32
34
|
appLocale = locale;
|
|
33
35
|
};
|
|
34
36
|
|
|
35
|
-
const getInterceptorConfig = (config) => {
|
|
37
|
+
const getInterceptorConfig = async (config) => {
|
|
36
38
|
if (authToken) {
|
|
37
|
-
config.headers["Authorization"] =
|
|
38
|
-
|
|
39
|
+
config.headers["Authorization"] = `${
|
|
40
|
+
isKeycloakAuth ? "Bearer" : "Token"
|
|
41
|
+
} ${authToken}`;
|
|
39
42
|
}
|
|
43
|
+
config.headers["Accept-Language"] = `${appLocale}-${appLocale}`;
|
|
44
|
+
|
|
45
|
+
if (isKeycloakAuth) {
|
|
46
|
+
await updateKeycloakToken();
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
return config;
|
|
41
50
|
};
|
|
42
51
|
|
|
43
|
-
HTTP.interceptors.request.use(getInterceptorConfig, (error) => {
|
|
52
|
+
HTTP.interceptors.request.use(getInterceptorConfig, async (error) => {
|
|
44
53
|
return Promise.reject(error);
|
|
45
54
|
});
|
|
46
55
|
|
|
@@ -107,6 +116,7 @@ export default {
|
|
|
107
116
|
makeFileRequest,
|
|
108
117
|
makeGetPaginatedRequest,
|
|
109
118
|
setAuthToken,
|
|
119
|
+
setIsKeycloakAuth,
|
|
110
120
|
setLocale,
|
|
111
121
|
FILE_REQUEST,
|
|
112
122
|
DEFAULT_URL,
|
package/src/components/App.vue
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "../utils/utils";
|
|
20
20
|
import { Integrations } from "@sentry/tracing";
|
|
21
21
|
import API from "../api";
|
|
22
|
+
import { initKeycloak } from "../utils/keycloak";
|
|
22
23
|
|
|
23
24
|
export default {
|
|
24
25
|
name: "App",
|
|
@@ -98,6 +99,24 @@ export default {
|
|
|
98
99
|
required: false,
|
|
99
100
|
default: "",
|
|
100
101
|
},
|
|
102
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
103
|
+
sso_url: {
|
|
104
|
+
type: String,
|
|
105
|
+
required: false,
|
|
106
|
+
default: "",
|
|
107
|
+
},
|
|
108
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
109
|
+
sso_realm: {
|
|
110
|
+
type: String,
|
|
111
|
+
required: false,
|
|
112
|
+
default: "",
|
|
113
|
+
},
|
|
114
|
+
// eslint-disable-next-line vue/prop-name-casing
|
|
115
|
+
sso_client_id: {
|
|
116
|
+
type: String,
|
|
117
|
+
required: false,
|
|
118
|
+
default: "",
|
|
119
|
+
},
|
|
101
120
|
},
|
|
102
121
|
computed: {
|
|
103
122
|
...mapState("display", ["pageError"]),
|
|
@@ -133,7 +152,11 @@ export default {
|
|
|
133
152
|
}
|
|
134
153
|
},
|
|
135
154
|
isPublicView() {
|
|
136
|
-
if (
|
|
155
|
+
if (
|
|
156
|
+
this.userToken ||
|
|
157
|
+
this.fullMode ||
|
|
158
|
+
(this.ssoUrl && this.ssoRealm && this.ssoClientId)
|
|
159
|
+
) {
|
|
137
160
|
return false;
|
|
138
161
|
} else {
|
|
139
162
|
return true;
|
|
@@ -160,6 +183,33 @@ export default {
|
|
|
160
183
|
return null;
|
|
161
184
|
}
|
|
162
185
|
},
|
|
186
|
+
ssoUrl() {
|
|
187
|
+
if (process.env.VUE_APP_SSO_URL) {
|
|
188
|
+
return process.env.VUE_APP_SSO_URL;
|
|
189
|
+
} else if (this.sso_url) {
|
|
190
|
+
return this.sso_url;
|
|
191
|
+
} else {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
ssoRealm() {
|
|
196
|
+
if (process.env.VUE_APP_SSO_REALM) {
|
|
197
|
+
return process.env.VUE_APP_SSO_REALM;
|
|
198
|
+
} else if (this.sso_realm) {
|
|
199
|
+
return this.sso_realm;
|
|
200
|
+
} else {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
ssoClientId() {
|
|
205
|
+
if (process.env.VUE_APP_SSO_CLIENT_ID) {
|
|
206
|
+
return process.env.VUE_APP_SSO_CLIENT_ID;
|
|
207
|
+
} else if (this.sso_client_id) {
|
|
208
|
+
return this.sso_client_id;
|
|
209
|
+
} else {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
163
213
|
annotationId() {
|
|
164
214
|
if (getURLValueFromHash("ann")) {
|
|
165
215
|
return getURLValueFromHash("ann");
|
|
@@ -183,7 +233,7 @@ export default {
|
|
|
183
233
|
}
|
|
184
234
|
},
|
|
185
235
|
},
|
|
186
|
-
created() {
|
|
236
|
+
async created() {
|
|
187
237
|
// Sentry config
|
|
188
238
|
if (process.env.NODE_ENV != "development") {
|
|
189
239
|
Sentry.init({
|
|
@@ -211,7 +261,12 @@ export default {
|
|
|
211
261
|
}
|
|
212
262
|
|
|
213
263
|
// api config
|
|
214
|
-
|
|
264
|
+
if (this.userToken) {
|
|
265
|
+
API.setAuthToken(this.userToken);
|
|
266
|
+
} else if (this.ssoUrl && this.ssoRealm && this.ssoClientId) {
|
|
267
|
+
await initKeycloak(this.ssoUrl, this.ssoRealm, this.ssoClientId);
|
|
268
|
+
}
|
|
269
|
+
|
|
215
270
|
API.setLocale(this.$i18n.locale);
|
|
216
271
|
|
|
217
272
|
if (this.api_url !== "") {
|
|
@@ -31,6 +31,14 @@
|
|
|
31
31
|
:container-height="scaledViewport.height"
|
|
32
32
|
/>
|
|
33
33
|
|
|
34
|
+
<div
|
|
35
|
+
v-if="showAnnotationLabel"
|
|
36
|
+
class="annotation-label"
|
|
37
|
+
:style="getAnnotationLabelPosition(showAnnotationLabel)"
|
|
38
|
+
>
|
|
39
|
+
{{ showAnnotationLabel.labelName }}
|
|
40
|
+
</div>
|
|
41
|
+
|
|
34
42
|
<AnnSetTableOptions v-if="showAnnSetTable" :page="page" />
|
|
35
43
|
|
|
36
44
|
<v-stage
|
|
@@ -78,35 +86,6 @@
|
|
|
78
86
|
)"
|
|
79
87
|
>
|
|
80
88
|
<v-group :key="'ann' + annotation.id + '-' + index">
|
|
81
|
-
<v-label
|
|
82
|
-
v-if="annotation.id == annotationId && !searchEnabled"
|
|
83
|
-
:key="`label${annotation.id}`"
|
|
84
|
-
:config="{
|
|
85
|
-
listening: false,
|
|
86
|
-
...annotationLabelRect(
|
|
87
|
-
bbox,
|
|
88
|
-
labelOfAnnotation(annotation).name
|
|
89
|
-
),
|
|
90
|
-
}"
|
|
91
|
-
>
|
|
92
|
-
<v-tag
|
|
93
|
-
:config="{
|
|
94
|
-
fill: '#1A1A1A',
|
|
95
|
-
lineJoin: 'round',
|
|
96
|
-
hitStrokeWidth: 0,
|
|
97
|
-
listening: false,
|
|
98
|
-
}"
|
|
99
|
-
/>
|
|
100
|
-
<v-text
|
|
101
|
-
:config="{
|
|
102
|
-
padding: 4,
|
|
103
|
-
text: labelOfAnnotation(annotation).name,
|
|
104
|
-
fill: 'white',
|
|
105
|
-
fontSize: 12,
|
|
106
|
-
listening: false,
|
|
107
|
-
}"
|
|
108
|
-
/>
|
|
109
|
-
</v-label>
|
|
110
89
|
<v-rect
|
|
111
90
|
v-if="!isAnnotationInEditMode(annotation.id)"
|
|
112
91
|
:config="annotationRect(bbox, annotation.id)"
|
|
@@ -142,42 +121,6 @@
|
|
|
142
121
|
</template>
|
|
143
122
|
</template>
|
|
144
123
|
</v-layer>
|
|
145
|
-
<v-layer
|
|
146
|
-
v-if="
|
|
147
|
-
showFocusedAnnotation &&
|
|
148
|
-
!isSelecting &&
|
|
149
|
-
documentAnnotationSelected.labelName !== ''
|
|
150
|
-
"
|
|
151
|
-
>
|
|
152
|
-
<v-label
|
|
153
|
-
:key="`label${documentAnnotationSelected.id}`"
|
|
154
|
-
:config="{
|
|
155
|
-
listening: false,
|
|
156
|
-
...annotationLabelRect(
|
|
157
|
-
documentAnnotationSelected.span,
|
|
158
|
-
documentAnnotationSelected.labelName
|
|
159
|
-
),
|
|
160
|
-
}"
|
|
161
|
-
>
|
|
162
|
-
<v-tag
|
|
163
|
-
:config="{
|
|
164
|
-
fill: '#1A1A1A',
|
|
165
|
-
lineJoin: 'round',
|
|
166
|
-
hitStrokeWidth: 0,
|
|
167
|
-
listening: false,
|
|
168
|
-
}"
|
|
169
|
-
/>
|
|
170
|
-
<v-text
|
|
171
|
-
:config="{
|
|
172
|
-
padding: 4,
|
|
173
|
-
text: documentAnnotationSelected.labelName,
|
|
174
|
-
fill: 'white',
|
|
175
|
-
fontSize: 12,
|
|
176
|
-
listening: false,
|
|
177
|
-
}"
|
|
178
|
-
/>
|
|
179
|
-
</v-label>
|
|
180
|
-
</v-layer>
|
|
181
124
|
<v-layer v-if="page.number === selectionPage">
|
|
182
125
|
<box-selection
|
|
183
126
|
:page="page"
|
|
@@ -337,6 +280,18 @@ export default {
|
|
|
337
280
|
this.page.number
|
|
338
281
|
);
|
|
339
282
|
},
|
|
283
|
+
showAnnotationLabel() {
|
|
284
|
+
if (
|
|
285
|
+
this.showFocusedAnnotation &&
|
|
286
|
+
!this.isSelecting &&
|
|
287
|
+
this.documentAnnotationSelected &&
|
|
288
|
+
this.documentAnnotationSelected.labelName !== ""
|
|
289
|
+
) {
|
|
290
|
+
return this.documentAnnotationSelected;
|
|
291
|
+
} else {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
},
|
|
340
295
|
},
|
|
341
296
|
watch: {
|
|
342
297
|
recalculatingAnnotations(newState) {
|
|
@@ -686,22 +641,28 @@ export default {
|
|
|
686
641
|
...this.bboxToRect(this.page, bbox),
|
|
687
642
|
};
|
|
688
643
|
},
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
644
|
+
getAnnotationLabelPosition(annotation) {
|
|
645
|
+
if (annotation && this.$refs.stage) {
|
|
646
|
+
const padding = 8;
|
|
647
|
+
const maxCharacters = 10;
|
|
648
|
+
const minimumSpaceTopY = 50;
|
|
649
|
+
const rect = this.bboxToRect(this.page, annotation.span, true);
|
|
650
|
+
|
|
651
|
+
if (
|
|
652
|
+
annotation.labelName.length > maxCharacters &&
|
|
653
|
+
rect.y < minimumSpaceTopY
|
|
654
|
+
) {
|
|
655
|
+
return `left: ${rect.x}px; top: ${
|
|
656
|
+
rect.y + rect.height * 3 + padding
|
|
657
|
+
}px`;
|
|
658
|
+
} else {
|
|
659
|
+
return `left: ${rect.x}px; bottom: ${
|
|
660
|
+
this.$refs.stage.$el.clientHeight - rect.y - rect.height - padding
|
|
661
|
+
}px`;
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
return "";
|
|
665
|
+
}
|
|
705
666
|
},
|
|
706
667
|
closePopups() {
|
|
707
668
|
this.newAnnotation = [];
|
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
:class="[
|
|
11
11
|
'annotation-dropdown',
|
|
12
12
|
'no-padding-bottom',
|
|
13
|
+
'dropdown-full-width',
|
|
13
14
|
setsList.length === 0 ? 'no-padding-top' : '',
|
|
14
15
|
]"
|
|
15
16
|
scrollable
|
|
16
17
|
>
|
|
17
18
|
<template #trigger>
|
|
18
19
|
<b-button
|
|
19
|
-
:class="[
|
|
20
|
+
:class="[
|
|
21
|
+
'popup-input',
|
|
22
|
+
selectedSet ? '' : 'not-selected',
|
|
23
|
+
'has-right-icon',
|
|
24
|
+
]"
|
|
20
25
|
type="is-text"
|
|
21
26
|
>
|
|
22
27
|
{{
|
|
@@ -72,11 +77,11 @@
|
|
|
72
77
|
aria-role="list"
|
|
73
78
|
:disabled="!labelsFiltered || labelsFiltered.length === 0"
|
|
74
79
|
scrollable
|
|
75
|
-
class="label-dropdown annotation-dropdown"
|
|
80
|
+
class="label-dropdown annotation-dropdown dropdown-full-width"
|
|
76
81
|
>
|
|
77
82
|
<template #trigger>
|
|
78
83
|
<b-button
|
|
79
|
-
class="popup-input"
|
|
84
|
+
class="popup-input has-right-icon"
|
|
80
85
|
:disabled="!labelsFiltered"
|
|
81
86
|
type="is-text"
|
|
82
87
|
>
|
|
@@ -12,13 +12,18 @@
|
|
|
12
12
|
'annotation-dropdown',
|
|
13
13
|
'no-padding-bottom',
|
|
14
14
|
'no-padding-top',
|
|
15
|
+
'dropdown-full-width',
|
|
15
16
|
setsList.length === 0 ? 'no-padding-top' : '',
|
|
16
17
|
]"
|
|
17
18
|
scrollable
|
|
18
19
|
>
|
|
19
20
|
<template #trigger>
|
|
20
21
|
<b-button
|
|
21
|
-
:class="[
|
|
22
|
+
:class="[
|
|
23
|
+
'popup-input',
|
|
24
|
+
selectedSet ? '' : 'not-selected',
|
|
25
|
+
'has-right-icon',
|
|
26
|
+
]"
|
|
22
27
|
type="is-text"
|
|
23
28
|
>
|
|
24
29
|
{{
|
|
@@ -76,11 +81,15 @@
|
|
|
76
81
|
aria-role="list"
|
|
77
82
|
:disabled="!textFromEntities || !labels || labels.length === 0"
|
|
78
83
|
scrollable
|
|
79
|
-
class="label-dropdown annotation-dropdown"
|
|
84
|
+
class="label-dropdown annotation-dropdown dropdown-full-width"
|
|
80
85
|
>
|
|
81
86
|
<template #trigger>
|
|
82
87
|
<b-button
|
|
83
|
-
:class="[
|
|
88
|
+
:class="[
|
|
89
|
+
'popup-input',
|
|
90
|
+
selectedLabel ? '' : 'not-selected',
|
|
91
|
+
'has-right-icon',
|
|
92
|
+
]"
|
|
84
93
|
type="is-text"
|
|
85
94
|
>
|
|
86
95
|
{{
|
package/src/store/document.js
CHANGED
|
@@ -706,7 +706,10 @@ const getters = {
|
|
|
706
706
|
},
|
|
707
707
|
|
|
708
708
|
annotationById: (state) => (annotationId) => {
|
|
709
|
-
|
|
709
|
+
if (state.annotations) {
|
|
710
|
+
return state.annotations.find((ann) => ann.id == annotationId);
|
|
711
|
+
}
|
|
712
|
+
return null;
|
|
710
713
|
},
|
|
711
714
|
|
|
712
715
|
// Check if document is ready to be finished
|
|
@@ -954,7 +957,7 @@ const actions = {
|
|
|
954
957
|
commit("SET_PAGES", []);
|
|
955
958
|
commit("SET_DOC_ID", id);
|
|
956
959
|
},
|
|
957
|
-
setAnnotationId: ({ commit }, id) => {
|
|
960
|
+
setAnnotationId: ({ commit, dispatch, getters }, id) => {
|
|
958
961
|
commit("SET_ANNOTATION_ID", id);
|
|
959
962
|
setURLAnnotationHash(id);
|
|
960
963
|
},
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Keycloak from "keycloak-js";
|
|
2
|
+
import API from "../api";
|
|
3
|
+
|
|
4
|
+
let keycloak;
|
|
5
|
+
|
|
6
|
+
export const initKeycloak = async (url, realm, clientId) => {
|
|
7
|
+
keycloak = new Keycloak({
|
|
8
|
+
url,
|
|
9
|
+
realm,
|
|
10
|
+
clientId,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const authenticated = await keycloak.init({
|
|
15
|
+
onLoad: "login-required",
|
|
16
|
+
enableLogging: true,
|
|
17
|
+
});
|
|
18
|
+
if (authenticated) {
|
|
19
|
+
API.setIsKeycloakAuth(true);
|
|
20
|
+
API.setAuthToken(keycloak.token);
|
|
21
|
+
} else {
|
|
22
|
+
console.error("User is not authenticated");
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error("Failed to initialize adapter:", error);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const updateKeycloakToken = () => {
|
|
30
|
+
return new Promise(async (resolve, reject) => {
|
|
31
|
+
if (keycloak) {
|
|
32
|
+
const update = await keycloak.updateToken(30);
|
|
33
|
+
resolve();
|
|
34
|
+
} else {
|
|
35
|
+
reject();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
};
|