Changeset 35284


Ignore:
Timestamp:
2021-08-11T16:24:17+12:00 (3 years ago)
Author:
davidb
Message:

Convert error templating to Vue

Location:
main/trunk/model-interfaces-dev/atea
Files:
1 added
1 deleted
4 edited

Legend:

Unmodified
Added
Removed
  • main/trunk/model-interfaces-dev/atea/README.md

    r35282 r35284  
    1919    npx grunt watch
    2020    ```
     21
     22# ASR Development TODO
     23
     24- Play audio at certain words, based on timestamps returned by tuhituhi API
     25- Corrections interface
     26- WebVTT download option
     27- Add transcription loading indicator
     28- Make transcriptions fully load in on a separate page, with the normal entry providing an arrow link to expand it on the new page?
     29- Ask user for confirmation before removing a transcription
  • main/trunk/model-interfaces-dev/atea/js/asr/asr-controller.js

    r35282 r35284  
    44 */
    55
     6import { TranscribeService, TranscriptionError, TranscriptionModel } from "./TranscribeModule.js";
     7
    68/** @type {HTMLAudioElement} */
    79// @ts-ignore
     
    2022const TRANSCRIPTION_TEMPLATE = document.getElementById("transcriptionTemplate");
    2123
    22 /** @type {HTMLTemplateElement} */
    23 // @ts-ignore
    24 const ERROR_TEMPLATE = document.getElementById("errorTemplate");
    25 
    2624let cachedAudioFileList = new Map();
    27 
    28 /**
    29 * @callback loadScriptCallback
    30 * @param {Event} ev The event
    31 * @returns {void}
    32 */
    3325
    3426// Get the size of each character in our monospace font
     
    3729{
    3830    const monoCharSizeTestElement = document.querySelector(".monospace-font-size");
    39     if (monoCharSizeTestElement == null || monoCharSizeTestElement.textContent == null) {
     31    if (monoCharSizeTestElement == null || monoCharSizeTestElement.textContent == null)
     32    {
    4033        MONOSPACE_CHAR_SIZE = 8; // Slightly over-estimated guess for size 16 font.
    4134        return;
     
    4538});
    4639
    47 /**
    48 * Loads a remote script.
    49 * Found at https://stackoverflow.com/questions/950087/how-do-i-include-a-javascript-file-in-another-javascript-file
    50 *
    51 * @param {string} url The URL from which to load the script.
    52 */
    53 function loadScript(url, callback = () => {})
    54 {
    55     var body = document.body;
    56 
    57     var script = document.createElement('script');
    58     script.type = 'text/javascript';
    59     script.src = url;
    60    
    61     script.addEventListener("load", callback);
    62 
    63     body.appendChild(script);
    64 }
    65 
    66 var transcribeService;// = new TranscribeService();
    67 loadScript("./interfaces/atea/js/asr/TranscribeService.js", () => {
    68     transcribeService = new TranscribeService();
    69 });
     40var transcribeService = new TranscribeService();
    7041
    7142// @ts-ignore
    7243const AudioUploadComponent = Vue.createApp(
    73 {
    74     data()
    75     {
    76         return {
    77             files: undefined,
    78             canTranscribe: false,
    79             isTranscribing: false
    80         }
    81     },
    82     methods:
    83     {
    84         openFilePicker() {
    85             document.getElementById("audioFileInput")?.click();
     44    {
     45        data()
     46        {
     47            return {
     48                files: undefined,
     49                canTranscribe: false,
     50                isTranscribing: false
     51            }
    8652        },
    87         onFilesChanged()
    88         {
    89             /** @type {HTMLInputElement} */
    90             // @ts-ignore | Object could be null
    91             const audioFileInput = document.getElementById("audioFileInput");
    92 
    93             if (audioFileInput.files?.length != undefined && audioFileInput.files?.length > 0) {
    94                 this.canTranscribe = true;
    95                 this.files = audioFileInput?.files
    96             }
    97             else {
    98                 this.canTranscribe = false;
    99                 this.files = undefined;
    100             }
    101         },
    102         async doTranscription() {
    103             await getTranscriptions();
    104         }
    105     }
    106 });
     53        methods:
     54        {
     55            openFilePicker() {
     56                document.getElementById("audioFileInput")?.click();
     57            },
     58            onFilesChanged()
     59            {
     60                /** @type {HTMLInputElement} */
     61                // @ts-ignore | Object could be null
     62                const audioFileInput = document.getElementById("audioFileInput");
     63
     64                if (audioFileInput.files?.length != undefined && audioFileInput.files?.length > 0) {
     65                    this.canTranscribe = true;
     66                    this.files = audioFileInput?.files
     67                }
     68                else {
     69                    this.canTranscribe = false;
     70                    this.files = undefined;
     71                }
     72            },
     73            async doTranscription() {
     74                // TODO: Push files to queue. If currently transcribing, good to go; it'll pull off it.
     75                // Else call getTranscriptions();
     76                // Then, we don't have to worry about preventing the user from transcribing multiple items.
     77                await getTranscriptions();
     78            }
     79        }
     80    }
     81);
    10782const AudioUploadVM = AudioUploadComponent.mount("#audioUploadContainer");
     83
     84const TranscriptionsListComponent = Vue.createApp(
     85    {
     86        data()
     87        {
     88            return {
     89                /** @type {TranscriptionModel[]} */
     90                transcriptions: [],
     91                /** @type {TranscriptionError[]} */
     92                failures: []
     93            }
     94        }
     95    }
     96)
     97const TranscriptionsListVM = TranscriptionsListComponent.mount("#transcriptionsDisplayContainer");
    10898
    10999/**
     
    133123async function getTranscriptions()
    134124{
     125    AudioUploadVM.isTranscribing = true;
     126
    135127    /** @type {FileList} */
    136128    const files = AudioUploadVM.files;
    137129   
    138     AudioUploadVM.isTranscribing = true;
    139     await delay(1000); // TODO: Remove - UI testing purposes only
     130    await delay(500); // TODO: Remove - UI testing purposes only
    140131    // Cache the file list so that we can playback audio in the future
    141132    for (const file of files) {
     
    151142            {
    152143                if (!t.success) {
    153                     insertError(t.file_name, t.log);
     144                    TranscriptionsListVM.failures.push(new TranscriptionError(t.log, t.file_name));
    154145                }
    155                 else { 
    156                     insertTranscription(t);
     146                else {
     147                    TranscriptionsListVM.transcriptions.push(t);
    157148                }
    158149            }
     
    163154        console.error("Failed to transcribe files");
    164155        console.error(e);
    165         insertError("all", e.statusMessage);
     156        TranscriptionsListVM.failures.push(e);
    166157    }
    167158   
    168159    AudioUploadVM.isTranscribing = false;
    169 }
    170 
    171 /**
    172  * Removes any transcriptions from the UI list.
    173  */
    174 function clearTranscriptions()
    175 {
    176     cachedAudioFileList = new Map();
    177     while (TRANSCRIPTIONS_LIST.lastChild) {
    178         TRANSCRIPTIONS_LIST.removeChild(TRANSCRIPTIONS_LIST.lastChild);
    179     }
    180160}
    181161
     
    324304    }
    325305}
    326 
    327 /**
    328 * Inserts a transcription error into the DOM.
    329 *
    330 * @param {String} fileName The file for which an error occured.
    331 * @param {String | null} statusMessage An informative error message to display.
    332 */
    333 function insertError(fileName, statusMessage)
    334 {
    335     /** @type {HTMLLIElement} */
    336     // @ts-ignore
    337     const clone = ERROR_TEMPLATE.content.firstElementChild.cloneNode(true);
    338 
    339     /** @type {NodeListOf<HTMLSpanElement>} */
    340     const spans = clone.querySelectorAll("span");
    341     spans[0].textContent = statusMessage;
    342     spans[1].textContent = fileName;
    343    
    344     TRANSCRIPTIONS_LIST.appendChild(clone);
    345 }
  • main/trunk/model-interfaces-dev/atea/style/asr.scss

    r35282 r35284  
    9292    margin: 0 1px 3px 1px; /* Keeps space around the box shadow */
    9393
    94     -webkit-transition-duration: 0.2s;
    95     transition-duration: 0.2s;
     94    -webkit-transition-duration: 0.15s;
     95    transition-duration: 0.15s;
    9696
    9797    span {
     
    144144
    145145/* === End theme definitions === */
     146
     147/* === Start theme application === */
     148
     149button {
     150    @extend .btn-primary;
     151}
     152
     153/* === End theme application === */
    146154
    147155body {
     
    172180}
    173181
    174 #btnBeginTranscription {
    175     float: right;
    176 }
    177182
    178183/* Transcriptions */
     
    184189
    185190.transcription__container {
    186     margin-bottom: 1em;
     191    padding: 0.5em;
     192
     193    -webkit-transition-duration: 0.15s;
     194    transition-duration: 0.15s;
    187195
    188196    &:last-child {
    189197        margin-bottom: 0;
     198    }
     199
     200    &:hover {
     201        background-color: #EEE;
    190202    }
    191203}
     
    211223}
    212224
    213 .error-list-item {
    214     list-style-type: none;
    215     background-color: rgba(255, 0, 0, 0.329);
    216     border: 1px solid rgba(255, 0, 0, 0.651);
    217     border-radius: 5px;
    218     margin-bottom: 5px;
    219     padding: 1em;
    220 }
    221 
    222 .error-list-item .spaced-block {
    223     margin-bottom: 8px;
    224 
    225     &:last-child {
    226         margin-bottom: 0;
    227     }   
     225.transcription__error-container {
     226    @extend .transcription__container;
     227
     228    background-color: rgba(255, 0, 0, 0.226);
     229   
     230    &:hover {
     231        background-color: rgba(255, 0, 0, 0.226);
     232    }
    228233}
    229234
  • main/trunk/model-interfaces-dev/atea/transform/pages/asr.xsl

    r35282 r35284  
    4848        <link href="interfaces/{$interface_name}/style/asr.css" rel="stylesheet" type="text/css" />
    4949
    50         <span class="monospace-font-size">ngā tama a rangi |</span>
     50        <section id="audio-transcription-container" class="paper">
     51            <!-- Used to calculate the character size of our monospace font -->
     52            <span class="monospace-font-size">ngā tama a rangi</span>
    5153
    52         <section id="audio-transcription-container" class="paper">
    5354            <!-- Contains the file input, transcribe button and transcription progress indicator -->
    5455            <div id="audioUploadContainer">
    55                 <button class="btn-primary" type="button" v-on:click="openFilePicker">
     56                <button type="button" v-on:click="openFilePicker"
     57                        v-bind:disabled="isTranscribing">
    5658                    <span class="material-icons">&#xE2C6;</span> <!-- file_upload -->
    5759                    <span>Upload Audio Files</span>
     
    6163                       accept="audio/wav" multiple="multiple" v-bind:disabled="isTranscribing" />
    6264
    63                 <button id="btnBeginTranscription" class="btn-primary" type="submit"
     65                <button style="float: right;" type="submit"
    6466                        v-bind:disabled="!canTranscribe" v-on:click="doTranscription">
    6567                    <span class="material-icons">&#xEA3E;</span> <!-- history_edu -->
     
    7072            </div>
    7173   
    72             <!-- <div class="audio-slider__container">
    73                 <button type="button" class="btn-fab">
    74                     <span class="material-icons mdi-xl">&#xE037;</span>
    75                 </button>
    76                 <input type="range" min="0" value="0" class="audio-slider__range" />
    77                 <span class="audio-slider__value">1:23</span>
    78             </div> -->
    79    
    8074            <!-- Contains the audio element and transcription list -->
    8175            <div id="transcriptionsDisplayContainer">
     
    8478                    <source id="transcriptionAudioSource" />
    8579                </audio>
     80
     81                <ul class="transcription__list">
     82                    <li v-for="transcription in transcriptions" class="transcription__container">
     83                        {{ transcription.transcription }}
     84                    </li>
     85                    <li v-for="failure in failures" class="transcription__error-container">
     86                        Failed to transcribe <i v-if="failure.file">{{ failure.file }}</i><br/>
     87                        <span v-if="failure.message">Reason:    {{ failure.message }}</span>
     88                    </li>
     89                </ul>
     90
     91                <ul class="transcription__list">
     92                   
     93                </ul>
    8694   
    87                 <ul id="transcriptionsList" class="transcription__list"></ul>
     95                <!-- <ul id="transcriptionsList" class="transcription__list"></ul> -->
    8896   
    8997                <template id="transcriptionTemplate">
     
    117125                    </li>
    118126                </template>
    119    
    120                 <template id="errorTemplate">
    121                     <li class="error-list-item">
    122                         <div>
    123                             <div class="spaced-block">
    124                                 <b>Transcription Failed: </b><span></span>
    125                             </div>
    126                             <div class="spaced-block">
    127                                 <b>File: </b><span></span>
    128                             </div>
    129                         </div>
    130                     </li>
    131                 </template>
    132127            </div>
     128
     129            <!-- <div class="audio-slider__container">
     130                <button type="button" class="btn-fab">
     131                    <span class="material-icons mdi-xl">&#xE037;</span>
     132                </button>
     133                <input type="range" min="0" value="0" class="audio-slider__range" />
     134                <span class="audio-slider__value">1:23</span>
     135            </div> -->
    133136        </section>
    134137       
    135138        <!-- TODO: Switch to production version for release builds -->
    136139        <gsf:script src="https://unpkg.com/vue@next"></gsf:script>
    137         <gsf:script src="interfaces/{$interface_name}/js/asr/asr-controller.js"></gsf:script>
     140        <gsf:script src="interfaces/{$interface_name}/js/asr/asr-controller.js" type="module"></gsf:script>
     141        <!-- <script type="module" src="interfaces/{$interface_name}/js/asr/asr-controller.js">
     142            <xsl:comment>Filler</xsl:comment>>
     143        </script> -->
    138144    </xsl:template>
    139145
Note: See TracChangeset for help on using the changeset viewer.