Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 4bc99591

Přidáno uživatelem Lukáš Vlček před asi 2 roky(ů)

Adding tag/subtag to selected text

Zobrazit rozdíly:

webapp/api/api.ts
76 76
     */
77 77
    'tagInstances'?: Array<TagInstanceInfo> | null;
78 78
}
79
/**
80
 * 
81
 * @export
82
 * @interface AnnotationInstanceAddRequest
83
 */
84
export interface AnnotationInstanceAddRequest {
85
    /**
86
     * 
87
     * @type {number}
88
     * @memberof AnnotationInstanceAddRequest
89
     */
90
    'position'?: number;
91
    /**
92
     * 
93
     * @type {number}
94
     * @memberof AnnotationInstanceAddRequest
95
     */
96
    'length'?: number;
97
    /**
98
     * 
99
     * @type {ETagType}
100
     * @memberof AnnotationInstanceAddRequest
101
     */
102
    'type'?: ETagType;
103
    /**
104
     * 
105
     * @type {string}
106
     * @memberof AnnotationInstanceAddRequest
107
     */
108
    'id'?: string;
109
    /**
110
     * 
111
     * @type {string}
112
     * @memberof AnnotationInstanceAddRequest
113
     */
114
    'instanceId'?: string | null;
115
}
79 116
/**
80 117
 * 
81 118
 * @export
......
321 358
export type EState = typeof EState[keyof typeof EState];
322 359

  
323 360

  
361
/**
362
 * 
363
 * @export
364
 * @enum {string}
365
 */
366

  
367
export const ETagType = {
368
    Tag: 'TAG',
369
    Subtag: 'SUBTAG'
370
} as const;
371

  
372
export type ETagType = typeof ETagType[keyof typeof ETagType];
373

  
374

  
324 375
/**
325 376
 * 
326 377
 * @export
......
553 604
    'subTagId'?: string | null;
554 605
    /**
555 606
     * 
556
     * @type {number}
607
     * @type {string}
557 608
     * @memberof TagInstanceInfo
558 609
     */
559
    'instance'?: number;
610
    'instance'?: string;
560 611
    /**
561 612
     * 
562 613
     * @type {number}
......
710 761
                options: localVarRequestOptions,
711 762
            };
712 763
        },
764
        /**
765
         * 
766
         * @param {string} annotationId 
767
         * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] 
768
         * @param {*} [options] Override http request option.
769
         * @throws {RequiredError}
770
         */
771
        annotationAnnotationIdPost: async (annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
772
            // verify required parameter 'annotationId' is not null or undefined
773
            assertParamExists('annotationAnnotationIdPost', 'annotationId', annotationId)
774
            const localVarPath = `/annotation/{annotationId}`
775
                .replace(`{${"annotationId"}}`, encodeURIComponent(String(annotationId)));
776
            // use dummy base URL string because the URL constructor only accepts absolute URLs.
777
            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
778
            let baseOptions;
779
            if (configuration) {
780
                baseOptions = configuration.baseOptions;
781
            }
782

  
783
            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
784
            const localVarHeaderParameter = {} as any;
785
            const localVarQueryParameter = {} as any;
786

  
787

  
788
    
789
            localVarHeaderParameter['Content-Type'] = 'application/json';
790

  
791
            setSearchParams(localVarUrlObj, localVarQueryParameter);
792
            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
793
            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
794
            localVarRequestOptions.data = serializeDataIfNeeded(annotationInstanceAddRequest, localVarRequestOptions, configuration)
795

  
796
            return {
797
                url: toPathString(localVarUrlObj),
798
                options: localVarRequestOptions,
799
            };
800
        },
713 801
        /**
714 802
         * 
715 803
         * @param {AnnotationsAddRequest} [annotationsAddRequest] 
......
763 851
            const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdGet(annotationId, options);
764 852
            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
765 853
        },
854
        /**
855
         * 
856
         * @param {string} annotationId 
857
         * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] 
858
         * @param {*} [options] Override http request option.
859
         * @throws {RequiredError}
860
         */
861
        async annotationAnnotationIdPost(annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
862
            const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdPost(annotationId, annotationInstanceAddRequest, options);
863
            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
864
        },
766 865
        /**
767 866
         * 
768 867
         * @param {AnnotationsAddRequest} [annotationsAddRequest] 
......
792 891
        annotationAnnotationIdGet(annotationId: string, options?: any): AxiosPromise<AnnotationInfo> {
793 892
            return localVarFp.annotationAnnotationIdGet(annotationId, options).then((request) => request(axios, basePath));
794 893
        },
894
        /**
895
         * 
896
         * @param {string} annotationId 
897
         * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] 
898
         * @param {*} [options] Override http request option.
899
         * @throws {RequiredError}
900
         */
901
        annotationAnnotationIdPost(annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options?: any): AxiosPromise<void> {
902
            return localVarFp.annotationAnnotationIdPost(annotationId, annotationInstanceAddRequest, options).then((request) => request(axios, basePath));
903
        },
795 904
        /**
796 905
         * 
797 906
         * @param {AnnotationsAddRequest} [annotationsAddRequest] 
......
822 931
        return AnnotationApiFp(this.configuration).annotationAnnotationIdGet(annotationId, options).then((request) => request(this.axios, this.basePath));
823 932
    }
824 933

  
934
    /**
935
     * 
936
     * @param {string} annotationId 
937
     * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] 
938
     * @param {*} [options] Override http request option.
939
     * @throws {RequiredError}
940
     * @memberof AnnotationApi
941
     */
942
    public annotationAnnotationIdPost(annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options?: AxiosRequestConfig) {
943
        return AnnotationApiFp(this.configuration).annotationAnnotationIdPost(annotationId, annotationInstanceAddRequest, options).then((request) => request(this.axios, this.basePath));
944
    }
945

  
825 946
    /**
826 947
     * 
827 948
     * @param {AnnotationsAddRequest} [annotationsAddRequest] 
webapp/components/annotation/AnnotationItem.tsx
19 19
 * @returns The item that represents an annotation.
20 20
 */
21 21
export function AnnotationItem(props: { tag: Tag }) {
22
    const { markSelectedText } = useContext(AnnotationContext);
23

  
22 24
    /**
23 25
     * Should properties of this annotation be visible?
24 26
     */
......
88 90
                <Col sm="auto" className="d-flex align-items-center">
89 91
                    <TagOutlined />
90 92
                </Col>
91
                <Col className="d-flex align-items-center">{props.tag.name}</Col>
93
                <Col className="d-flex align-items-center">
94
                    {props.tag.tagName}
95
                    {props.tag.subtagName ? ' (' + props.tag.subtagName + ')' : ''}
96
                </Col>
92 97
                <Col sm="auto">
93 98
                    <Button
94 99
                        type="text"
webapp/components/annotation/DocumentAnnotationView.tsx
15 15

  
16 16
    return (
17 17
        <div>
18
            <Button
19
                onClick={() => {
20
                    const selection = window.getSelection();
21
                    if (!selection) {
22
                        return;
23
                    }
24
                    if (!annotation?.tagStartPositions || !annotation.tagLengths) {
25
                        console.log('start or lengths not found');
26
                        return;
27
                    }
28

  
29
                    let startTag = selection.anchorNode;
30
                    let endTag = selection.focusNode;
31

  
32
                    if (!startTag || !endTag) {
33
                        console.log('Selection not found');
34
                        return;
35
                    }
36

  
37
                    if (startTag.nodeName.includes('#')) {
38
                        startTag = startTag.parentNode;
39
                    }
40
                    if (endTag.nodeName.includes('#')) {
41
                        endTag = endTag.parentNode;
42
                    }
43
                    if (!startTag || !endTag) {
44
                        console.log('Selection element not found');
45
                        return;
46
                    }
47

  
48
                    if (!(startTag instanceof Element)) {
49
                        console.log('StartTag is not instance of Element');
50
                        return;
51
                    }
52
                    if (!(endTag instanceof Element)) {
53
                        console.log('EndTag is not instance of Element');
54
                        return;
55
                    }
56

  
57
                    const startElement = startTag as Element;
58
                    const endElement = endTag as Element;
59

  
60
                    const startId: number =
61
                        Number(startElement.getAttribute('aswi-tag-id')) ?? -1;
62
                    const endId: number =
63
                        Number(endElement.getAttribute('aswi-tag-id')) ?? -1;
64

  
65
                    const startPosition =
66
                        annotation.tagStartPositions[startId] +
67
                        annotation.tagLengths[startId] +
68
                        selection.anchorOffset;
69
                    const endPosition =
70
                        annotation.tagStartPositions[endId] +
71
                        annotation.tagLengths[endId] +
72
                        selection.focusOffset -
73
                        1;
74
                }}
75
            >
76
                Test
77
            </Button>
78 18
            <div
79 19
                dangerouslySetInnerHTML={{ __html: annotation.documentToRender ?? '' }}
80 20
            />
webapp/components/types/tag.tsx
1
import { TagInstanceInfo } from '../../api';
1
import { SubTagInfo, TagInfo, TagInstanceInfo } from '../../api';
2 2

  
3 3
/**
4 4
 * Special tag used in annotation panel.
5 5
 */
6 6
export type Tag = {
7
    name: string;
7
    tagName: string;
8
    subtagName: string | null;
9

  
8 10
    category: string;
9 11
    visible: boolean;
10 12
    occurrences: TagInstanceInfo[];
13

  
14
    tagId: string;
15
    subtagId: string | null;
16
    instanceId: string;
11 17
};
webapp/contexts/AnnotationContext.tsx
1 1
import React, { createContext, useEffect, useState } from 'react';
2
import { AnnotationInfo, TagInstanceInfo } from '../api';
2
import { AnnotationInfo, ETagType, SubTagInfo, TagInfo, TagInstanceInfo } from '../api';
3 3
import { Tag } from '../components/types/tag';
4 4
import { annotationController } from '../controllers';
5
import { GetSelectionInfo } from '../utils/selectionUtils';
5 6

  
6 7
/**
7 8
 * Interface of an annotation context provider.
......
53 54
    annotation: AnnotationInfo | null;
54 55
    mappedTags: Tag[] | null;
55 56
    refreshAnnotation: () => void;
57

  
58
    markSelectedText: (
59
        tagId: string,
60
        subtagId: string | null,
61
        instanceID: string | null
62
    ) => void;
56 63
}
57 64

  
58 65
/**
......
119 126
    refreshAnnotation: () => {
120 127
        return;
121 128
    },
129

  
130
    markSelectedText: () => {
131
        return;
132
    },
122 133
});
123 134

  
124 135
/**
......
139 150

  
140 151
    const [mappedTags, setMappedTags] = useState<Tag[] | null>(null);
141 152

  
153
    async function markSelectedText(
154
        tagId: string,
155
        subtagId: string | null,
156
        instanceId: string | null
157
    ) {
158
        if (!annotation) {
159
            console.log('annotation not found');
160
            return;
161
        }
162

  
163
        const selectionInfo = GetSelectionInfo(annotation);
164
        if (!selectionInfo) {
165
            console.log(
166
                'not able to continue, selection processing not completed successfully'
167
            );
168
            return;
169
        }
170

  
171
        const id = subtagId ?? tagId;
172
        const type: ETagType = subtagId == null ? ETagType.Tag : ETagType.Subtag;
173

  
174
        const res = await annotationController.annotationAnnotationIdPost(
175
            props.annotationId,
176
            {
177
                id: id,
178
                instanceId,
179
                type,
180
                position: selectionInfo.startPositionOriginalDocument,
181
                length: selectionInfo.selectionLengthOriginalDocument,
182
            }
183
        );
184
        console.log('res');
185
        console.log(res);
186

  
187
        await refreshAnnotation();
188
    }
189

  
142 190
    /**
143 191
     * Default implementation of addOccurrence method.
144 192
     * @param tag The tag with new occurrence.
145 193
     */
146
    const addOccurrence = (tag: Tag) => {
147
        //TODO: Implement method (should use objects from server API)
194
    const addOccurrence = async (tag: Tag) => {
195
        await markSelectedText(tag.tagId, tag.subtagId ?? null, tag.instanceId);
148 196
    };
149 197

  
150 198
    /**
......
182 230
    };
183 231

  
184 232
    const remapAnnotations = (data: AnnotationInfo) => {
185
        let map = new Map<number, Tag>();
233
        let map = new Map<string, Tag>();
186 234
        data.tagInstances?.forEach((tagInstance) => {
187
            if (map.has(tagInstance.instance ?? 0)) {
188
                let tag = map.get(tagInstance.instance ?? 0);
235
            if (map.has(tagInstance.instance ?? '-')) {
236
                let tag = map.get(tagInstance.instance ?? '-');
189 237
                tag!.occurrences = [...tag!.occurrences, tagInstance];
190 238
            } else {
191
                map.set(tagInstance.position ?? 0, {
192
                    name: tagInstance.tagName ?? '',
239
                map.set(tagInstance.instance ?? '-', {
240
                    tagName: tagInstance.tagName ?? '',
241
                    subtagName: tagInstance.subTagName ?? null,
193 242
                    category: tagInstance.tagCategoryName ?? '',
194 243
                    visible: true,
195 244
                    occurrences: [tagInstance],
245
                    tagId: tagInstance.tagId ?? '',
246
                    instanceId: tagInstance.instance ?? '',
247
                    subtagId: tagInstance.subTagId ?? null,
196 248
                });
197 249
            }
198 250
        });
......
229 281
                refreshAnnotation,
230 282
                annotation,
231 283
                mappedTags,
284
                markSelectedText,
232 285
            }}
233 286
        >
234 287
            {props.children}
webapp/contexts/TagCategoryContext.tsx
1
import React, { createContext, useEffect, useState } from 'react';
1
import React, { createContext, useContext, useEffect, useState } from 'react';
2 2
import { SubTagInfo, TagCategoryInfo, TagInfo } from '../api';
3 3
import { tagController } from '../controllers';
4
import { LoggedUserContext } from './LoggedUserContext';
5
import Annotation from '../pages/annotation/[id]';
6
import { AnnotationContext } from './AnnotationContext';
4 7

  
5 8
/**
6 9
 * Interface of a tag context provider.
......
78 81
 * @returns Prepared html of the provider.
79 82
 */
80 83
const TagCategoryProvider = (props: { children: React.ReactNode }) => {
84
    const { markSelectedText } = useContext(AnnotationContext);
85

  
81 86
    /**
82 87
     * Tags managed by the context.
83 88
     */
......
99 104
    const selectTag = (tag: TagInfo, subTag: SubTagInfo | null) => {
100 105
        setSelectedTag(tag);
101 106
        setSelectedSubTag(subTag);
107

  
108
        if (!tag.id) {
109
            console.log('invalid selection');
110
            return;
111
        }
112

  
113
        markSelectedText(tag.id, subTag?.id ?? null, null);
102 114
    };
103 115

  
104 116
    /**
webapp/pages/annotation/[id].tsx
5 5
import 'antd/dist/antd.css';
6 6
import styles from '/styles/Annotation.module.scss';
7 7
import AnnotationProvider from '../../contexts/AnnotationContext';
8
import TagCategoryProvider from '../../contexts/TagCategoryContext';
9

  
8 10
import { useRouter } from 'next/router';
9 11

  
10 12
/**
......
27 29

  
28 30
    return (
29 31
        <AnnotationProvider annotationId={annotationId}>
30
            <MainLayout>
31
                <div className={styles.layoutWrapper}>
32
                    <div className={styles.tags}>
33
                        <TagPanel />
34
                    </div>
35
                    <div className={styles.document}>
36
                        <DocumentAnnotationView />
37
                    </div>
38
                    <div className={styles.annotations}>
39
                        <AnnotationPanel />
32
            <TagCategoryProvider>
33
                <MainLayout>
34
                    <div className={styles.layoutWrapper}>
35
                        <div className={styles.tags}>
36
                            <TagPanel />
37
                        </div>
38
                        <div className={styles.document}>
39
                            <DocumentAnnotationView />
40
                        </div>
41
                        <div className={styles.annotations}>
42
                            <AnnotationPanel />
43
                        </div>
40 44
                    </div>
41
                </div>
42
            </MainLayout>
45
                </MainLayout>
46
            </TagCategoryProvider>
43 47
        </AnnotationProvider>
44 48
    );
45 49
}
webapp/utils/selectionUtils.ts
1
import { AnnotationInfo } from '../api';
2

  
3
export interface SelectionInfo {
4
    startElementId: number;
5
    endElementId: number;
6
    startPositionOriginalDocument: number;
7
    endPositionOriginalDocument: number;
8
    selectionLengthOriginalDocument: number;
9
}
10
export function GetSelectionInfo(annotation: AnnotationInfo): SelectionInfo | null {
11
    const selection = window.getSelection();
12
    if (!selection) {
13
        return null;
14
    }
15
    if (!annotation?.tagStartPositions || !annotation.tagLengths) {
16
        console.log('start or lengths not found');
17
        return null;
18
    }
19

  
20
    let startTag = selection.anchorNode;
21
    let endTag = selection.focusNode;
22

  
23
    if (!startTag || !endTag) {
24
        console.log('Selection not found');
25
        return null;
26
    }
27

  
28
    if (startTag.nodeName.includes('#')) {
29
        startTag = startTag.parentNode;
30
    }
31
    if (endTag.nodeName.includes('#')) {
32
        endTag = endTag.parentNode;
33
    }
34
    if (!startTag || !endTag) {
35
        console.log('Selection element not found');
36
        return null;
37
    }
38

  
39
    if (!(startTag instanceof Element)) {
40
        console.log('StartTag is not instance of Element');
41
        return null;
42
    }
43
    if (!(endTag instanceof Element)) {
44
        console.log('EndTag is not instance of Element');
45
        return null;
46
    }
47

  
48
    let startElement = startTag as Element;
49
    let endElement = endTag as Element;
50

  
51
    let startId: number = Number(startElement.getAttribute('aswi-tag-id')) ?? -1;
52
    let endId: number = Number(endElement.getAttribute('aswi-tag-id')) ?? -1;
53

  
54
    let startPosition =
55
        annotation.tagStartPositions[startId] +
56
        annotation.tagLengths[startId] +
57
        selection.anchorOffset;
58
    let endPosition =
59
        annotation.tagStartPositions[endId] +
60
        annotation.tagLengths[endId] +
61
        selection.focusOffset -
62
        1;
63

  
64
    // need to switch start and end elements (selection was in the opposite way then expected)
65
    if (startPosition > endPosition) {
66
        let temp = startPosition;
67
        startPosition = endPosition;
68
        endPosition = temp;
69

  
70
        temp = startId;
71
        startId = endId;
72
        endId = temp;
73

  
74
        const tempElement = startElement;
75
        startElement = endElement;
76
        endElement = tempElement;
77
    }
78

  
79
    const length = endPosition - startPosition + 1;
80

  
81
    return {
82
        endElementId: endId,
83
        startElementId: startId,
84
        startPositionOriginalDocument: startPosition,
85
        endPositionOriginalDocument: endPosition,
86
        selectionLengthOriginalDocument: length,
87
    };
88
}

Také k dispozici: Unified diff