Revize 408ea567
Přidáno uživatelem stepanekp před asi 3 roky(ů)
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
#16 Rich text field for AP operationalization details implemented