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