source: main/trunk/model-interfaces-dev/atea/ocr/src/components/MainPage.vue@ 35759

Last change on this file since 35759 was 35759, checked in by cstephen, 2 years ago

Update packages

File size: 9.5 KB
Line 
1<template>
2<div class="main-page-root">
3 <modal-dialog v-if="modal.show" :title="modal.title" :description="modal.description" :buttons="modal.buttons" @close="onModalClose" />
4 <edit-page v-if="showEditor" class="image-editor" :src="imageUrl" :imageBuffer="imageBuffer"
5 @closeAndDiscard="onEditorCloseRequested" @closeAndSave="onEditorSave" />
6
7 <div class="paper root-container" :class="{ 'root-container-image-state': imageUrl !== null }">
8 <div v-if="imageUrl === null" class="upload-area" @click="uploadFile">
9 <span class="heading1">{{ translations.get("Title") }}</span>
10 <span class="material-icons mdi-xl">upload_file</span>
11 <span>{{ translations.get("MainPage_Upload") }}</span>
12 </div>
13
14 <div v-if="imageUrl !== null" class="image-area">
15 <div class="controls">
16 <button class="btn-primary" @click="doOcr" :disabled="ocrInProgress">
17 <span class="material-icons">play_arrow</span>
18 <span>{{ translations.get("MainPage_PerformOCR") }}</span>
19 </button>
20
21 <button class="btn-primary" @click="showEditor = true" :disabled="ocrInProgress">
22 <span class="material-icons">edit</span>
23 <span>{{ translations.get("MainPage_EditImage") }}</span>
24 </button>
25
26 <button class="btn-primary" @click="download" :disabled="!isValidOcrResult">
27 <span class="material-icons">download</span>
28 <span>{{ translations.get("MainPage_Download") }}</span>
29 </button>
30
31 <button class="btn-primary" @click="reset(false)" :disabled="ocrInProgress">
32 <span class="material-icons">restart_alt</span>
33 <span>{{ translations.get("MainPage_NewImage") }}</span>
34 </button>
35 </div>
36
37 <div class="progress-bar-container ocr-progress">
38 <div v-if="ocrInProgress" class="progress-bar-value progress-bar-indeterminate" />
39 </div>
40
41 <img class="image-display" :src="imageUrl" />
42
43 <textarea class="text-container" v-model="ocrResult" :placeholder="translations.get('MainPage_OCRHint')"
44 @input="modalChecks.ocrTextEdited = true" :disabled="!isValidOcrResult" />
45 </div>
46 </div>
47
48 <input ref="fileInput" type="file" @input="onFilesChanged" class="hidden"
49 accept="image/png,image/jpeg,image/gif,image/bmp,image/tiff,image/webp,application/pdf" />
50</div>
51
52</template>
53
54<style lang="scss" scoped>
55.main-page-root {
56 display: flex;
57 align-items: center;
58 justify-content: center;
59}
60
61.root-container {
62 padding: 1em;
63 transition-duration: var(--transition-duration);
64}
65
66.root-container-image-state {
67 width: calc(100% - 3em);
68 height: calc(100vh - 3em);
69 max-height: calc(100vh - 3em);
70}
71
72.upload-area {
73 display: grid;
74 grid-template-columns: auto auto;
75 grid-template-rows: auto 1fr;
76 align-items: center;
77 justify-items: center;
78
79 padding: 2em;
80 gap: 1em;
81
82 font-size: 2rem;
83 cursor: pointer;
84 border: 3px dashed var(--bg-color);
85 border-radius: var(--border-radius);
86
87 .heading1 {
88 grid-column-start: span 2;
89 color: #666;
90 }
91}
92
93.hidden {
94 display: none;
95}
96
97.image-editor {
98 position: absolute;
99 height: 100%;
100 width: 100%;
101 top: 0;
102 left: 0;
103 z-index: 9;
104}
105
106.image-area {
107 display: grid;
108 grid-template-columns: 1fr 1fr;
109 grid-template-rows: auto auto 1fr;
110 height: 100%;
111
112 gap: 1rem;
113 align-items: center;
114 justify-items: center;
115
116 position: relative;
117 overflow: hidden;
118
119 .text-container {
120 height: 100%;
121 width: 100%;
122 }
123}
124
125.image-display {
126 border: 1px solid #444;
127 max-width: 100%;
128 max-height: 80vh;
129 object-fit: contain;
130}
131
132.controls {
133 display: flex;
134 gap: 1em;
135 grid-column-start: span 2;
136}
137
138.ocr-progress {
139 grid-column-start: span 2;
140}
141</style>
142
143<script>
144import { mapState } from "vuex";
145import EditPage from "./EditPage.vue";
146import ModalDialog from "./ModalDialog.vue"
147import OcrService, { OcrOptions } from "../js/OcrService"
148import { log } from "../js/Util";
149
150const ocrService = new OcrService();
151
152export default {
153 name: "MainPage",
154 components: {
155 EditPage,
156 ModalDialog
157 },
158 data() {
159 return {
160 imageType: null,
161 /** @type {ArrayBuffer} */
162 imageBuffer: null,
163 imageUrl: null,
164 ocrInProgress: false,
165 ocrResult: null,
166 showEditor: false,
167 modal: {
168 show: false,
169 title: "",
170 description: "",
171 buttons: []
172 },
173 modalChecks: {
174 ocrTextEdited: false,
175 pendingReset: false
176 }
177 }
178 },
179 computed: {
180 ...mapState({
181 translations: state => state.translations,
182 isValidOcrResult() {
183 return this.ocrResult !== null && this.ocrResult.length !== 0;
184 }
185 })
186 },
187 watch: {
188 imageBuffer(newValue) {
189 if (this.imageUrl !== null) {
190 URL.revokeObjectURL(this.imageUrl);
191 }
192
193 if (newValue === null) {
194 return;
195 }
196
197 const arrayView = new Uint8Array(newValue);
198 const blob = new Blob([ arrayView ]);
199 this.imageUrl = URL.createObjectURL(blob);
200 }
201 },
202 methods: {
203 uploadFile() {
204 this.$refs.fileInput.click();
205 },
206 async onFilesChanged() {
207 /** @type {File[]} */
208 const files = this.$refs.fileInput.files;
209 if (files === null || files === undefined || files.length !== 1) {
210 return;
211 }
212
213 this.imageBuffer = await files[0].arrayBuffer();
214 this.imageType = files[0].type;
215 },
216
217 async doOcr() {
218 if (this.modalChecks.ocrTextEdited) {
219 this.modal.title = this.translations.get("OCREditedModal_Title");
220 this.modal.description = this.translations.get("OCREditedModal_Description");
221 this.modal.buttons = [
222 this.translations.get("OCREditedModal_ButtonContinue"),
223 this.translations.get("OCREditedModal_ButtonCancel")
224 ];
225 this.modal.show = true;
226
227 return;
228 }
229
230 try {
231 this.ocrInProgress = true;
232
233 const arrayView = new Uint8Array(this.imageBuffer);
234 const imageBlob = new Blob([ arrayView ], { type: this.imageType });
235
236 const result = await ocrService.run([
237 {
238 image: imageBlob,
239 fileName: "file.png",
240 options: new OcrOptions(false)
241 }
242 ]);
243
244 this.ocrResult = result[0].text;
245 this.modalChecks.ocrTextEdited = false;
246 }
247 catch (ex) {
248 // TODO: Display error
249 log("Failed to perform OCR", "error");
250 log(ex, "error");
251 }
252 finally {
253 this.ocrInProgress = false;
254 }
255 },
256
257 onEditorCloseRequested() {
258 this.showEditor = false;
259 },
260 /**
261 * Called when the editor is saved in order to update the stored image buffer.
262 * @param {Buffer} newBuffer The updated image buffer.
263 * @param {String} newType The updated MIME type of the image buffer.
264 */
265 onEditorSave(newBuffer, newType) {
266 this.imageBuffer = newBuffer.buffer;
267 this.imageType = newType;
268
269 this.onEditorCloseRequested();
270 },
271
272 reset(hard) {
273 if (!hard) {
274 this.modalChecks.pendingReset = true;
275
276 this.modal.title = this.translations.get("NewImageModal_Title");
277 this.modal.description = this.translations.get("NewImageModal_Description");
278 this.modal.buttons = [
279 this.translations.get("NewImageModal_ButtonContinue"),
280 this.translations.get("NewImageModal_ButtonCancel")
281 ];
282 this.modal.show = true;
283
284 return;
285 }
286
287 URL.revokeObjectURL(this.imageUrl);
288 this.imageUrl = null;
289 this.ocrInProgress = false;
290 this.ocrResult = null;
291 this.showEditor = false;
292 this.$refs.fileInput.value = "";
293 },
294
295 async onModalClose(button) {
296 this.modal.show = false;
297
298 if (this.modalChecks.pendingReset) {
299 console.log("reset is indeed pending");
300 if (button === "Continue") {
301 this.reset(true);
302 }
303
304 this.modalChecks.pendingReset = false;
305 }
306 else if (this.modalChecks.ocrTextEdited) {
307 if (button === "Continue") {
308 this.modalChecks.ocrTextEdited = false;
309 await this.doOcr();
310 }
311 }
312 },
313
314 async download() {
315 const blob = new Blob([ this.ocrResult ], { type: "text/plain;charset=utf-8" });
316 const date = new Date();
317 const fileName = `ocr-${date.getHours()}${date.getMinutes()}${date.getSeconds()}.txt`;
318
319 const { saveAs } = await import("file-saver");
320 saveAs(blob, fileName);
321 }
322 }
323}
324</script>
Note: See TracBrowser for help on using the repository browser.