Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 408ea567

Přidáno uživatelem stepanekp před asi 3 roky(ů)

#16 Rich text field for AP operationalization details implemented

Zobrazit rozdíly:

src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/controller/AppController.java
10 10
import org.slf4j.Logger;
11 11
import org.slf4j.LoggerFactory;
12 12
import org.springframework.beans.factory.annotation.Autowired;
13
import org.springframework.core.io.FileSystemResource;
14
import org.springframework.http.HttpStatus;
15
import org.springframework.http.ResponseEntity;
13 16
import org.springframework.stereotype.Controller;
14 17
import org.springframework.ui.Model;
15 18
import org.springframework.web.bind.annotation.*;
19
import org.springframework.web.multipart.MultipartFile;
16 20
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
17 21

  
22
import java.io.BufferedWriter;
23
import java.io.File;
24
import java.io.FileWriter;
25
import java.nio.file.Files;
18 26
import java.time.LocalTime;
19 27
import java.time.format.DateTimeFormatter;
20 28
import java.util.List;
......
255 263
        return "redirect:/anti-patterns/{id}";
256 264
    }
257 265

  
266
    /**
267
     * Method for storing operationalization detail for individual AP
268
     *
269
     * @param model object for passing data to the UI
270
     * @param id id of AP
271
     * @param innerText operationalization text (HTML)
272
     * @param redirectAttrs attributes for redirection
273
     * @return redirected html file name for thymeleaf template
274
     */
275
    @PostMapping("/anti-patterns/{id}/operationalization")
276
    public String antiPatternsPost(Model model,
277
                                   @PathVariable Long id,
278
                                   @RequestParam(value = "contentTextArea", required = false) String innerText,
279
                                   RedirectAttributes redirectAttrs) {
280

  
281
        AntiPattern antiPattern = antiPatternService.antiPatternToModel(antiPatternService.getAntiPatternById(id));
282

  
283
        String thePath = new FileSystemResource("").getFile().getAbsolutePath() + "\\src\\main\\webapp\\operationalizations\\" + antiPattern.getName() + ".html";
284

  
285
        try {
286
            BufferedWriter writer = new BufferedWriter(new FileWriter(thePath));
287
            writer.write(innerText);
288
            writer.close();
289
            redirectAttrs.addFlashAttribute("successMessage", "Operationalization detail has been successfully saved.");
290
        } catch (Exception e) {
291
        }
292
        return "redirect:/anti-patterns/{id}";
293
    }
294

  
295
    /**
296
     * Method for getting image from operationalization images folder
297
     *
298
     * @param imageName Name of the image
299
     * @return image as a byte array
300
     * @throws Exception If image is not in the folder
301
     */
302
    @GetMapping("/operationalizations/images/{imageName}")
303
    public @ResponseBody byte[] imageTestGet(@PathVariable String imageName) throws Exception {
304
        File f = new File(new FileSystemResource("").getFile().getAbsolutePath() + "\\src\\main\\webapp\\operationalizations\\images\\" + imageName);
305
        return Files.readAllBytes(f.toPath());
306
    }
307

  
308
    /**
309
     * Method for uploading an image to the operationalization images folder
310
     *
311
     * @param file image to upload
312
     * @return result
313
     */
314
    @PostMapping("/uploadImage")
315
    public ResponseEntity<?> handleFileUpload(@RequestParam("file") MultipartFile file ) {
316

  
317
        String fileName = file.getOriginalFilename();
318

  
319
        if(new File(new FileSystemResource("").getFile().getAbsolutePath() + "\\src\\main\\webapp\\operationalizations\\images\\" + fileName).isFile()){
320
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
321
        }
322

  
323
        try {
324
            file.transferTo( new File(new FileSystemResource("").getFile().getAbsolutePath() + "\\src\\main\\webapp\\operationalizations\\images\\" + fileName));
325
        } catch (Exception e) {
326
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
327
        }
328
        return ResponseEntity.ok("File uploaded successfully.");
329
    }
330

  
258 331

  
259 332
}
src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/model/AntiPattern.java
2 2

  
3 3
import cz.zcu.fav.kiv.antipatterndetectionapp.Constants;
4 4
import org.jsoup.Jsoup;
5
import org.springframework.core.io.FileSystemResource;
5 6

  
7
import java.io.IOException;
8
import java.nio.file.Files;
9
import java.nio.file.Paths;
6 10
import java.util.Map;
7 11

  
8 12
/**
......
133 137
        return resultDescription.trim();
134 138
    }
135 139

  
140
    public String getOperationalizationText() {
141
        String myPath = new FileSystemResource("").getFile().getAbsolutePath() + "\\src\\main\\webapp\\operationalizations\\" + this.getName() + ".html";
142
        String content = null;
143
        try {
144
            content = new String ( Files.readAllBytes(Paths.get(myPath)));
145
        } catch (IOException e) {
146
            e.printStackTrace();
147
        }
148

  
149
        return content;
150
    }
151

  
136 152
    @Override
137 153
    public String toString() {
138 154
        return "AntiPattern{" +
src/main/webapp/WEB-INF/templates/anti-pattern.html
7 7
    <meta name="viewport" content="width=device-width, initial-scale=1">
8 8
    <link rel="stylesheet" th:href="@{/resources/css/style.css}">
9 9
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
10
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
10 11
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
11 12
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
12 13
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
13 14
</head>
14
<body>
15
<body onload="makeEditorNotEditable()">
15 16
<!-- Navigation bar imported -->
16 17
<div th:replace="fragments/navbar :: navBar"></div>
17 18
<!-- ./Navigation bar imported -->
......
66 67

  
67 68
        <!-- Anti pattern description -->
68 69
        <div class="form-group">
69
            <label for="antiPatternDescription">Anti Pattern description:</label>
70
            <h5 for="antiPatternDescription">Anti Pattern Description</h5>
70 71
            <textarea disabled class="form-control" id="antiPatternDescription" rows="5"
71 72
                      th:text="${antiPattern.getDescriptionFromCatalogue()}"></textarea>
72 73
        </div>
73 74
        <!-- ./Anti pattern description -->
74 75

  
76
        <hr>
77

  
78
        <!-- Anti pattern operationalization -->
79
        <div class="operationalization-detail">
80
            <h5 style="display: inline-block">Anti Pattern Operationalization Detail</h5>
81
            <button type="button" class="btn edit-button" onclick="enableEdit()">
82
                <i class="fa fa-pen-to-square fa-lg"></i>
83
            </button>
84
            <div class="main-content" id="antiPatternOperationalization">
85
                <div class="text-editor-header" id="editorHeader">
86
                    <button type="button" class="btn rt" data-element="bold">
87
                        <i class="fa fa-bold"></i>
88
                    </button>
89
                    <button type="button" class="btn rt" data-element="italic">
90
                        <i class="fa fa-italic"></i>
91
                    </button>
92
                    <button type="button" class="btn rt" data-element="underline">
93
                        <i class="fa fa-underline"></i>
94
                    </button>
95
                    <button type="button" class="btn rt" data-element="justifyLeft">
96
                        <i class="fa fa-align-left"></i>
97
                    </button>
98
                    <button type="button" class="btn rt" data-element="justifyCenter">
99
                        <i class="fa fa-align-center"></i>
100
                    </button>
101
                    <button type="button" class="btn rt" data-element="justifyRight">
102
                        <i class="fa fa-align-right"></i>
103
                    </button>
104
                    <button type="button" class="btn rt" data-element="header1">
105
                        <i class="fa fa-heading fa-sm">1</i>
106
                    </button>
107
                    <button type="button" class="btn rt" data-element="header2">
108
                        <i class="fa fa-heading fa-sm">2</i>
109
                    </button>
110
                    <button type="button" class="btn rt" data-element="header3">
111
                        <i class="fa fa-heading fa-sm">3</i>
112
                    </button>
113
                    <button type="button" class="btn rt" data-element="insertUnorderedList">
114
                        <i class="fa fa-list-ul"></i>
115
                    </button>
116
                    <button type="button" class="btn rt" data-element="insertOrderedList">
117
                        <i class="fa fa-list-ol"></i>
118
                    </button>
119
                    <button type="button" class="btn rt" data-element="createLink">
120
                        <i class="fa fa-link"></i>
121
                    </button>
122
                    <button type="button" class="btn rt" data-element="insertImage">
123
                        <i class="fa fa-image"></i>
124
                    </button>
125
                    <input type="file" id="imageInput" name="fileupload" hidden>
126
                    <button id="upload-button" onclick="uploadFile()" hidden></button>
127
                </div>
128
                <div class="content form-control" id="content-area" onload="getContent()" th:utext="${antiPattern.getOperationalizationText()}"></div>
129
            </div>
130
            <form id="operationalization-form" action="#" th:action="@{/anti-patterns/} + ${antiPattern.id} + @{/operationalization}" th:object="${antiPattern}" method="post">
131
                <div class="save-operationalization-button-container">
132
                    <textarea style="display:none" id="contentTextArea" name="contentTextArea"></textarea>
133
                    <button type="submit" class="btn btn-primary custom-button" id="op_save_button">Save changes</button>
134
                </div>
135
            </form>
136

  
137
        </div>
138
        <!-- ./Anti pattern operationalization -->
139

  
140
        <hr>
141

  
75 142
        <!-- Anti pattern configuration form -->
76
        <h5>Anti Pattern configurations</h5>
143
        <h5>Anti Pattern Configurations</h5>
77 144

  
78 145
        <!-- Form for configuration values -->
79 146
        <form action="#" th:action="@{/anti-patterns/} + ${antiPattern.id}" th:object="${antiPattern}" method="post">
......
102 169
</div>
103 170
<!-- ./Card for anti pattern details-->
104 171

  
172
<!-- Scripts -->
173
<script th:src="@{/resources/js/rt-editor.js}"></script>
174
<!-- ./Scripts -->
175

  
105 176
</body>
106 177
</html>
src/main/webapp/operationalizations/BusinessAsUsual.html
1
test3
src/main/webapp/operationalizations/LongOrNonExistentFeedbackLoops.html
1
test6
src/main/webapp/operationalizations/NinetyNinetyRule.html
1
test7
src/main/webapp/operationalizations/RoadToNowhere.html
1
test5
src/main/webapp/operationalizations/SpecifyNothing.html
1
test4
src/main/webapp/operationalizations/TooLongSprint.html
1
<div><h1>This is h1</h1></div><div>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla est. </div><div>Etiam commodo dui eget wisi. <b>Fusce tellus </b>odio, dapibus id fermentum quis, suscipit id erat. </div><div>Fusce aliquam vestibulum ipsum. Nullam sit amet magna in <u>magna</u> gravida vehicula. </div><div><i>Ut tempus purus at lorem.</i> </div><div><br></div><div><ul><li>Lorem ipsum</li><li>Lorem ipsum</li><li>Lorem ipsum</li></ul><div><a href="https://www.fav.zcu.cz/cs/">https://www.fav.zcu.cz/cs/</a><br></div></div><div><br></div><div><img src="/operationalizations/images/test2.png"><br></div><div><br></div><h2>This is h2</h2><h3>This is h3</h3><div>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, </div><div>totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. </div><div>Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. </div><div><br></div><div><img src="/operationalizations/images/testImage.png"><br></div><div><br></div>
src/main/webapp/operationalizations/VaryingSprintLength.html
1
test2
src/main/webapp/resources/css/style.css
33 33
    width: 160px !important;
34 34
}
35 35

  
36
.text-editor-header{
37
    padding: 5px;
38
    background-color: rgba(0,0,0,.03);
39
    border-radius: 0.25rem 0.25rem 0rem 0rem;
40
    border-style: solid;
41
    border-width: 1px 1px 0 1px;
42
    border-color: rgba(0,0,0,.125);
43

  
44
}
45

  
46
.text-editor-header .rt{
47
    border: none;
48
    outline: none;
49
    background-color: transparent;
50
    cursor: pointer;
51
    font-size: 13px !important;
52
}
53

  
54
.edit-button{
55
    border: none;
56
    outline: none;
57
    background-color: #FFFFFF;
58
    cursor: pointer;
59
    font-size: 13px !important;
60
    padding: 3px 3px 3px 7px !important;
61
}
62

  
63
.main-content{
64
    margin-bottom: 15px;
65
}
66

  
67
.content{
68
    min-height: 150px;
69
    padding: 20px;
70
}
71

  
72
.operationalization-detail{
73
    margin-bottom: 20px;
74
}
75

  
76
#save-button{
77
    border: 1px solid rgba(0,0,0,.125);
78
    border-radius: 0.25rem;
79
}
80

  
81
.header-button{
82
    font-size: 1em;
83
}
84

  
85
#content-area h1{
86
    font-size: 1.6em;
87
}
88

  
89
#content-area h2{
90
    font-size: 1.4em;
91
}
92

  
93
#content-area h3{
94
    font-size: 1.2em;
95
}
96

  
36 97
/* ==========================================================================
37 98
   result.html
38 99
   ========================================================================== */
src/main/webapp/resources/js/rt-editor.js
1
/**
2
 * Copying inner HTML from content editable div to textarea
3
 */
4
$('#operationalization-form').submit(function() {
5
    document.getElementById("contentTextArea").innerHTML = $("#content-area").html();
6
    return true;
7
});
8

  
9
/**
10
 * Set operationalization editor not editable
11
 */
12
function makeEditorNotEditable(){
13
    let header = document.getElementById('editorHeader');
14
    let save_button = document.getElementById('op_save_button');
15
    let content_area = document.getElementById('content-area');
16

  
17
    header.hidden = true;
18
    save_button.hidden = true;
19
    content_area.contentEditable = 'false';
20
    content_area.style.backgroundColor = '#e9ecef';
21
}
22

  
23
/**
24
 * Enable operationalization edit
25
 */
26
function enableEdit(){
27
    let header = document.getElementById('editorHeader');
28
    let save_button = document.getElementById('op_save_button');
29
    let content_area = document.getElementById('content-area');
30

  
31
    if(header.hidden === true){
32
        header.hidden = false;
33
        save_button.hidden = false;
34
        content_area.contentEditable = 'true';
35
        content_area.style = 'border-radius: 0rem 0rem 0.25rem 0.25rem; background-color: #FFFFFF';
36
    }
37
    else{
38
        header.hidden = true;
39
        save_button.hidden = true;
40
        content_area.contentEditable = 'false';
41
        content_area.style = 'border-radius: 0.25rem; background-color: #e9ecef';
42
    }
43

  
44
}
45

  
46
/**
47
 * Event listener to the editor buttons
48
 */
49
const elements = document.querySelectorAll('.btn');
50
elements.forEach(element => {
51
    element.addEventListener('click', () => {
52
        let command = element.dataset['element'];
53

  
54
        if(command == 'createLink'){
55
            let url = prompt('Enter the link: ', '');
56
            document.execCommand(command, false, url);
57
        }
58
        else if(command == 'insertImage'){
59
            let input = document.getElementById('imageInput');
60
            input.click();
61
        }
62
        else if(command == 'header1'){
63
            headerH1();
64
        }
65
        else if(command == 'header2'){
66
            headerH2();
67
        }
68
        else if(command == 'header3'){
69
            headerH3();
70
        }
71
        else{
72
            document.execCommand(command, false, null);
73
        }
74

  
75
    });
76
});
77

  
78
/**
79
 * Event listener for input of the image in the editor
80
 * - clicking submit button (to save image on the server)
81
 * - inserting img tag with src to the content
82
 */
83
document.getElementById('imageInput').addEventListener('change', (event) => {
84
    if(document.getElementById('imageInput').files[0] == null)
85
        return;
86

  
87
    document.getElementById('upload-button').click();
88

  
89
    setTimeout(() => {
90
        let imageName = document.getElementById('imageInput').files[0].name;
91
        let imageString = "<img src=\"/operationalizations/images/" + imageName + "\">";
92
        document.execCommand('insertHTML', false, imageString.toString());
93
    }, 1000);
94
});
95

  
96
/**
97
 * Uploads file to the server by sending it in the POST
98
 */
99
async function uploadFile() {
100
    let formData = new FormData();
101
    formData.append("file", document.getElementById('imageInput').files[0]);
102
    let response = await fetch('/uploadImage', {
103
        method: "POST",
104
        body: formData
105
    });
106

  
107
    if(response.status == 200)
108
        return;
109
    else{
110
        alert("Image upload error.");
111
        document.getElementById('imageInput').files[0] = null;
112
        return;
113
    }
114
}
115

  
116
/**
117
 * Wraping selected text by h1 tag
118
 */
119
function headerH1() {
120
    let S=window.getSelection().toString();
121
    if(S.length == 0)
122
        return;
123
    if(window.getSelection().anchorNode.parentNode.nodeName.toLowerCase() ===  'h1'){
124
        document.execCommand('formatBlock', false, 'div');
125
        document.execCommand('insertHTML',false, S);
126
    }
127
    else{
128
        document.execCommand('delete',false,'');
129
        document.execCommand('insertHTML',false,'<h1>'+S+'</h1>');
130
    }
131
}
132

  
133
/**
134
 * Wraping selected text by h2 tag
135
 */
136
function headerH2() {
137
    let S=window.getSelection().toString();
138
    if(S.length == 0)
139
        return;
140
    if(window.getSelection().anchorNode.parentNode.nodeName.toLowerCase() ===  'h2'){
141
        document.execCommand('formatBlock', false, 'div');
142
        document.execCommand('insertHTML',false, S);
143
    }
144
    else{
145
        document.execCommand('delete',false,'');
146
        document.execCommand('insertHTML',false,'<h2>'+S+'</h2>');
147
    }
148
}
149

  
150
/**
151
 * Wraping selected text by h3 tag
152
 */
153
function headerH3() {
154
    let S=window.getSelection().toString();
155
    if(S.length == 0)
156
        return;
157
    if(window.getSelection().anchorNode.parentNode.nodeName.toLowerCase() ===  'h3'){
158
        document.execCommand('formatBlock', false, 'div');
159
        document.execCommand('insertHTML',false, S);
160
    }
161
    else{
162
        document.execCommand('delete',false,'');
163
        document.execCommand('insertHTML',false,'<h3>'+S+'</h3>');
164
    }
165
}

Také k dispozici: Unified diff