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

Last change on this file since 35746 was 35746, checked in by cstephen, 13 months ago

Implement edit saving

File size: 6.4 KB
Line 
1<template>
2<div class="main-page-root">
3 <edit-page v-if="showEditor" class="image-editor" :src="imageUrl" :imageBuffer="imageBuffer"
4 @closeAndDiscard="onEditorCloseRequested" @closeAndSave="onEditorSave" />
5
6 <div class="paper root-container" :class="{ 'root-container-image-state': imageUrl !== null }">
7 <div v-if="imageUrl === null" class="upload-area" @click="uploadFile">
8 <span class="heading1">{{ translations.get("Title") }}</span>
9 <span class="material-icons mdi-xl">upload_file</span>
10 <span>Upload an image/PDF</span>
11 </div>
12
13 <div v-if="imageUrl !== null" class="image-area">
14 <div class="controls">
15 <button class="btn-primary" @click="doOcr" :disabled="ocrInProgress">
16 <span class="material-icons">play_arrow</span>
17 <span>{{ translations.get("Main_PerformOCR") }}</span>
18 </button>
19
20 <button class="btn-primary" @click="showEditor = true" :disabled="ocrInProgress">
21 <span class="material-icons">edit</span>
22 <span>Edit Image</span>
23 </button>
24
25 <button class="btn-primary" @click="reset" :disabled="ocrInProgress">
26 <span class="material-icons">restart_alt</span>
27 <span>New</span>
28 </button>
29
30 <div v-if="ocrInProgress" class="progress-bar-container">
31 <div class="progress-bar-value progress-bar-indeterminate" />
32 </div>
33 </div>
34
35 <img class="image-display" :src="imageUrl" />
36
37 <div class="text-container">
38 <pre>{{ ocrResult }}</pre>
39 </div>
40 </div>
41 </div>
42
43 <input ref="fileInput" type="file" @input="onFilesChanged" class="hidden"
44 accept="image/png,image/jpeg,image/gif,image/bmp,image/tiff,image/webp,application/pdf" />
45</div>
46
47</template>
48
49<style lang="scss" scoped>
50.main-page-root {
51 display: flex;
52 align-items: center;
53 justify-content: center;
54}
55
56.root-container {
57 padding: 1em;
58 transition-duration: var(--transition-duration);
59}
60
61.root-container-image-state {
62 width: calc(100% - 3em);
63 height: calc(100vh - 3em);
64 max-height: calc(100vh - 3em);
65}
66
67.upload-area {
68 display: grid;
69 grid-template-columns: auto auto;
70 grid-template-rows: auto 1fr;
71 align-items: center;
72 justify-items: center;
73
74 padding: 2em;
75 gap: 1em;
76
77 font-size: 2rem;
78 cursor: pointer;
79 border: 3px dashed var(--bg-color);
80 border-radius: var(--border-radius);
81
82 .heading1 {
83 grid-column-start: span 2;
84 color: #666;
85 }
86}
87
88.hidden {
89 display: none;
90}
91
92.image-editor {
93 position: absolute;
94 height: 100%;
95 width: 100%;
96 top: 0;
97 left: 0;
98 z-index: 9;
99}
100
101.image-area {
102 display: grid;
103 grid-template-columns: 1fr 1fr;
104 grid-template-rows: auto 1fr;
105 height: 100%;
106
107 gap: 1rem;
108 align-items: center;
109 justify-items: center;
110
111 position: relative;
112 overflow: hidden;
113
114 .text-container {
115 height: 100%;
116 width: 100%;
117 }
118}
119
120.image-display {
121 border: 1px solid #444;
122 max-width: 100%;
123 max-height: 80vh;
124 object-fit: contain;
125}
126
127.controls {
128 display: flex;
129 gap: 1em;
130 grid-column-start: span 2;
131}
132
133.text-container {
134 overflow: scroll;
135}
136</style>
137
138<script>
139import { mapState } from "vuex";
140import EditPage from "./EditPage.vue";
141import OcrService, { OcrOptions } from "../js/OcrService"
142import { log } from "../js/Util";
143
144const ocrService = new OcrService();
145
146export default {
147 name: "MainPage",
148 components: {
149 EditPage
150 },
151 data() {
152 return {
153 imageType: null,
154 /** @type {ArrayBuffer} */
155 imageBuffer: null,
156 imageUrl: null,
157 ocrInProgress: false,
158 ocrResult: null,
159 showEditor: false
160 }
161 },
162 computed: {
163 ...mapState({
164 translations: state => state.translations
165 })
166 },
167 watch: {
168 imageBuffer(newValue) {
169 if (this.imageUrl !== null) {
170 URL.revokeObjectURL(this.imageUrl);
171 }
172
173 if (newValue === null) {
174 return;
175 }
176
177 const arrayView = new Uint8Array(newValue);
178 const blob = new Blob([ arrayView ]);
179 this.imageUrl = URL.createObjectURL(blob);
180 }
181 },
182 methods: {
183 uploadFile() {
184 this.$refs.fileInput.click();
185 },
186 async onFilesChanged() {
187 /** @type {File[]} */
188 const files = this.$refs.fileInput.files;
189 if (files === null || files === undefined || files.length !== 1) {
190 return;
191 }
192
193 this.imageBuffer = await files[0].arrayBuffer();
194 this.imageType = files[0].type;
195 },
196
197 async doOcr() {
198 try {
199 this.ocrInProgress = true;
200
201 const arrayView = new Uint8Array(this.imageBuffer);
202 const imageBlob = new Blob([ arrayView ], { type: this.imageType });
203
204 const result = await ocrService.run([
205 {
206 image: imageBlob,
207 fileName: "file.png",
208 options: new OcrOptions(false)
209 }
210 ]);
211
212 this.ocrResult = result[0].text;
213 }
214 catch (ex) {
215 // TODO: Display error
216 log("Failed to perform OCR", "error");
217 log(ex, "error");
218 }
219 finally {
220 this.ocrInProgress = false;
221 }
222 },
223
224 onEditorCloseRequested() {
225 this.showEditor = false;
226 },
227 /**
228 * Called when the editor is saved in order to update the stored image buffer.
229 * @param {Buffer} newBuffer The updated image buffer.
230 * @param {String} newType The updated MIME type of the image buffer.
231 */
232 onEditorSave(newBuffer, newType) {
233 this.imageBuffer = newBuffer.buffer;
234 this.imageType = newType;
235
236 this.onEditorCloseRequested();
237 },
238
239 reset() {
240 URL.revokeObjectURL(this.imageUrl);
241 this.imageUrl = null;
242 this.ocrInProgress = false;
243 this.ocrResult = null;
244 this.showEditor = false;
245 this.$refs.fileInput.value = "";
246 }
247 }
248}
249</script>
Note: See TracBrowser for help on using the repository browser.