Ignore:
Timestamp:
2021-08-11T14:11:58+12:00 (3 years ago)
Author:
davidb
Message:

Start transition to Vue for layout composition

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

Legend:

Unmodified
Added
Removed
  • main/trunk/model-interfaces-dev/atea/js/asr/TranscribeService.js

    r35270 r35282  
    106106        try
    107107        {
    108             response = await fetch
    109             (
     108            response = await fetch(
    110109                that.queryUrl,
    111110                {
  • main/trunk/model-interfaces-dev/atea/js/asr/asr-controller.js

    r35276 r35282  
    44 */
    55
    6 /**
    7 * The name of the file input that audio files can be uploaded to.
    8 */
    9 const FILE_UPLOAD_INPUT_NAME = "#audioFileUpload";
    10 
    11 /**
    12 * The name of the container that holds the progress display for the audio upload.
    13 */
    14 const FILE_UPLOAD_PROGRESS_CONTAINER_NAME = "#prgFileUploadContainer";
    15 
    166/** @type {HTMLAudioElement} */
    177// @ts-ignore
     
    3222/** @type {HTMLTemplateElement} */
    3323// @ts-ignore
    34 const METADATA_TEMPLATE = document.getElementById("metadataTemplate");
    35 
    36 /** @type {HTMLTemplateElement} */
    37 // @ts-ignore
    38 const ERROR_TEMPLATE = document.querySelector("#errorTemplate");
     24const ERROR_TEMPLATE = document.getElementById("errorTemplate");
    3925
    4026let cachedAudioFileList = new Map();
     
    7864}
    7965
    80 var transcribeService;
     66var transcribeService;// = new TranscribeService();
    8167loadScript("./interfaces/atea/js/asr/TranscribeService.js", () => {
    8268    transcribeService = new TranscribeService();
    8369});
    8470
    85 async function doAudioUpload()
     71// @ts-ignore
     72const 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();
     86        },
     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});
     107const AudioUploadVM = AudioUploadComponent.mount("#audioUploadContainer");
     108
     109/**
     110 * When awaited, creates a delay for the given number of milliseconds.
     111 *
     112 * @param {Number} delayInms The number of milliseconds to delay for.
     113 * @returns A promise that will resolve when the delay has finished.
     114 */
     115function delay(delayInms)
     116{
     117    return new Promise(
     118        resolve =>
     119        {
     120            setTimeout(
     121                () => {
     122                    resolve(2);
     123                },
     124                delayInms
     125            );
     126        }
     127    );
     128}
     129
     130/**
     131 * Gets the transcription of each submitted audio file.
     132 */
     133async function getTranscriptions()
    86134{
    87135    /** @type {FileList} */
    88     // @ts-ignore
    89     const files = $(FILE_UPLOAD_INPUT_NAME)[0].files;
    90    
    91     // Disable the file input while transcribing, and show the progress bar
    92     $(FILE_UPLOAD_INPUT_NAME).prop("disabled", true);
    93     $(FILE_UPLOAD_PROGRESS_CONTAINER_NAME).removeClass("asr-hidden");
    94 
     136    const files = AudioUploadVM.files;
     137   
     138    AudioUploadVM.isTranscribing = true;
     139    await delay(1000); // TODO: Remove - UI testing purposes only
    95140    // Cache the file list so that we can playback audio in the future
    96141    for (const file of files) {
     
    121166    }
    122167   
    123     // Hide the progress bar and re-enable the file input.
    124     $(FILE_UPLOAD_PROGRESS_CONTAINER_NAME).addClass("asr-hidden");
    125     $(FILE_UPLOAD_INPUT_NAME).prop("disabled", false);
     168    AudioUploadVM.isTranscribing = false;
    126169}
    127170
     
    170213    audioSlider.step = "0.01";
    171214   
    172     // Get the metadata list
    173     /** @type {HTMLUListElement} */
    174     // @ts-ignore
    175     const metadataList = clone.querySelector(".metadata-list");
    176    
    177     // Insert metadata rows
    178     for (const metadata of transcription.metadata) {
    179         metadataList.appendChild(buildMetadataNode(metadata));
    180     }
    181    
    182215    // Set the filename data property on every element in the clone's DOM node
    183216    recurseAddData(clone, "file-name", transcription.file_name);
     
    187220    TRANSCRIPTIONS_LIST.appendChild(clone);
    188221
     222    const TranscriptionWordListComponent = Vue.createApp(
     223    {
     224        data()
     225        {
     226            return {
     227                lines: []
     228            }
     229        }
     230    });
     231    const TranscriptionWordListVM = TranscriptionWordListComponent.mount("#transcriptionWordList");
     232
     233    transcription.transcription.replace('', "\u00A0"); // This helps with formatting, as whitespace is trimmed.
     234
    189235    // Get the amount of space that we have to align words within
    190236    /** @type {HTMLDivElement} */
     
    193239    const charsPerLine = Math.floor(transcriptionTextList.clientWidth / MONOSPACE_CHAR_SIZE);
    194240   
    195     for (let i = 0; i < transcription.transcription.length; i += 0)
     241    // Insert words with correct line alignment
     242    const tText = transcription.transcription;
     243
     244    for (let i = 0; i < tText.length; i += 0)
    196245    {
    197246        /** @type {String} */
    198247        let slice;
    199248
    200         if (i + charsPerLine < transcription.transcription.length)
    201         {
    202             slice = transcription.transcription.slice(i, charsPerLine);
    203 
    204             if (transcription.transcription.charAt(i) != ' ' && transcription.transcription.charAt(i + 1) != ' ') {
     249        if (i + charsPerLine < tText.length)
     250        {
     251            slice = tText.slice(i, charsPerLine);
     252
     253            if (tText.charAt(i) != ' ' && tText.charAt(i + 1) != ' ') {
    205254                let lastSpacePos = slice.lastIndexOf(' ');
    206255                let decrement = slice.length - lastSpacePos;
     
    211260        else
    212261        {
    213             slice = transcription.transcription.slice(i);
    214         }
    215 
    216         slice.lastIndexOf(' ');
    217 
    218         let lineNode = document.createElement("p");
    219         lineNode.textContent = slice;
    220        
    221         transcriptionTextList.appendChild(lineNode);
     262            slice = tText.slice(i);
     263        }
     264
     265        TranscriptionWordListVM.lines.push({ words: slice.split(' ') });
    222266        i += charsPerLine;
    223267    }
    224    
    225 }
    226 
    227 /**
    228  * Builds a metadata node.
    229  *
    230  * @param {{char: string, confidence: number, start_time: number}} metadata The metadata used to create the node.
    231  * @returns {Node} The constructed DOM node.
    232  */
    233 function buildMetadataNode(metadata)
    234 {
    235     const metadataClone = METADATA_TEMPLATE.content.firstElementChild.cloneNode(true);
    236 
    237     /** @type {HTMLParagraphElement} */
    238     const p = metadataClone.querySelector("p");
    239    
    240     /** @type {HTMLButtonElement} */
    241     const button = metadataClone.querySelector("button");
    242 
    243     button.addEventListener("click", function(e)
    244     {
    245         // Have to traverse the event path, because the event target is the child element of the button.
    246         const requestedAudioFile = e.composedPath().find((t) => t instanceof HTMLButtonElement).dataset.fileName;
    247         loadAudioFile(requestedAudioFile);
    248 
    249         TRANSCRIPTION_AUDIO_ELEMENT.currentTime = metadata.start_time;
    250         TRANSCRIPTION_AUDIO_ELEMENT.play();
    251     });
    252    
    253     if (metadata.char == ' ') {
    254         p.textContent = "\u00A0 " // This helps with formatting, as elements that only contain whitespace are collapsed.
    255     }
    256     else {
    257         p.textContent = metadata.char;
    258     }
    259 
    260     return metadataClone;
    261268}
    262269
     
    337344    TRANSCRIPTIONS_LIST.appendChild(clone);
    338345}
    339 
    340 $(FILE_UPLOAD_INPUT_NAME).on("input", e =>
    341 {
    342     if (e.target.files.length <= 0) {
    343         $("#btnBeginTranscription").prop("disabled", true);
    344     }
    345     else {
    346         $("#btnBeginTranscription").prop("disabled", false);
    347     }
    348 });
  • main/trunk/model-interfaces-dev/atea/package-lock.json

    r35276 r35282  
    55  "requires": true,
    66  "dependencies": {
     7    "@babel/helper-validator-identifier": {
     8      "version": "7.14.9",
     9      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz",
     10      "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g=="
     11    },
     12    "@babel/parser": {
     13      "version": "7.15.2",
     14      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.2.tgz",
     15      "integrity": "sha512-bMJXql1Ss8lFnvr11TZDH4ArtwlAS5NG9qBmdiFW2UHHm6MVoR+GDc5XE2b9K938cyjc9O6/+vjjcffLDtfuDg=="
     16    },
     17    "@babel/types": {
     18      "version": "7.15.0",
     19      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz",
     20      "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==",
     21      "requires": {
     22        "@babel/helper-validator-identifier": "^7.14.9",
     23        "to-fast-properties": "^2.0.0"
     24      }
     25    },
    726    "@types/jquery": {
    827      "version": "3.5.6",
     
    2544      "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
    2645      "dev": true
     46    },
     47    "@vue/compiler-core": {
     48      "version": "3.2.1",
     49      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.1.tgz",
     50      "integrity": "sha512-UEJf2ZGww5wGVdrWIXIZo04KdJFGPmI2bHRUsBZ3AdyCAqJ5ykRXKOBn1OR1hvA2YzimudOEyHM+DpbBv91Kww==",
     51      "requires": {
     52        "@babel/parser": "^7.12.0",
     53        "@babel/types": "^7.12.0",
     54        "@vue/shared": "3.2.1",
     55        "estree-walker": "^2.0.1",
     56        "source-map": "^0.6.1"
     57      }
     58    },
     59    "@vue/compiler-dom": {
     60      "version": "3.2.1",
     61      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.1.tgz",
     62      "integrity": "sha512-tXg8tkPb3j54zNfWqoao9T1JI41yWPz8TROzmif/QNNA46eq8/SRuRsBd36i47GWaz7mh+yg3vOJ87/YBjcMyQ==",
     63      "requires": {
     64        "@vue/compiler-core": "3.2.1",
     65        "@vue/shared": "3.2.1"
     66      }
     67    },
     68    "@vue/reactivity": {
     69      "version": "3.2.1",
     70      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.1.tgz",
     71      "integrity": "sha512-4Lja2KmyiKvuraDed6dXK2A6+r/7x7xGDA7vVR2Aqc8hQVu0+FWeVX+IBfiVOSpbZXFlHLNmCBFkbuWLQSlgxg==",
     72      "requires": {
     73        "@vue/shared": "3.2.1"
     74      }
     75    },
     76    "@vue/runtime-core": {
     77      "version": "3.2.1",
     78      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.1.tgz",
     79      "integrity": "sha512-IsgelRM/5hYeRhz5+ECi66XvYDdjG2t4lARjHvCXw5s9Q4N6uIbjLMwtLzAWRxYf3/y258BrD+ehxAi943ScJg==",
     80      "requires": {
     81        "@vue/reactivity": "3.2.1",
     82        "@vue/shared": "3.2.1"
     83      }
     84    },
     85    "@vue/runtime-dom": {
     86      "version": "3.2.1",
     87      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.1.tgz",
     88      "integrity": "sha512-bUAHUSe49A5wYdHQ8wsLU1CMPXaG2fRuv2661mx/6Q9+20QxglT3ss8ZeL6AVRu16JNJMcdvTTsNpbnMbVc/lQ==",
     89      "requires": {
     90        "@vue/runtime-core": "3.2.1",
     91        "@vue/shared": "3.2.1",
     92        "csstype": "^2.6.8"
     93      }
     94    },
     95    "@vue/shared": {
     96      "version": "3.2.1",
     97      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.1.tgz",
     98      "integrity": "sha512-INN92dVBNgd0TW9BqfQQKx/HWGCHhUUbAV5EZ5FgSCiEdwuZsJbGt1mdnaD9IxGhpiyOjP2ClxGG8SFp7ELcWg=="
    2799    },
    28100    "abbrev": {
     
    222294      "dev": true
    223295    },
     296    "csstype": {
     297      "version": "2.6.17",
     298      "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz",
     299      "integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A=="
     300    },
    224301    "dateformat": {
    225302      "version": "3.0.3",
     
    263340      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
    264341      "dev": true
     342    },
     343    "estree-walker": {
     344      "version": "2.0.2",
     345      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
     346      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
    265347    },
    266348    "eventemitter2": {
     
    12301312      }
    12311313    },
     1314    "source-map": {
     1315      "version": "0.6.1",
     1316      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
     1317      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
     1318    },
    12321319    "sprintf-js": {
    12331320      "version": "1.1.2",
     
    12711358      }
    12721359    },
     1360    "to-fast-properties": {
     1361      "version": "2.0.0",
     1362      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
     1363      "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
     1364    },
    12731365    "to-regex-range": {
    12741366      "version": "5.0.1",
     
    13111403      }
    13121404    },
     1405    "vue": {
     1406      "version": "3.2.1",
     1407      "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.1.tgz",
     1408      "integrity": "sha512-0jhXluF5mzTAK5bXw/8yq4McvsI8HwEWI4cnQwJeN8NYGRbwh9wwuE4FNv1Kej9pxBB5ajTNsWr0M6DPs5EJZg==",
     1409      "requires": {
     1410        "@vue/compiler-dom": "3.2.1",
     1411        "@vue/runtime-dom": "3.2.1",
     1412        "@vue/shared": "3.2.1"
     1413      }
     1414    },
    13131415    "websocket-driver": {
    13141416      "version": "0.7.4",
  • main/trunk/model-interfaces-dev/atea/package.json

    r35276 r35282  
    1616    "load-grunt-tasks": "^5.1.0",
    1717    "sass": "^1.37.5"
     18  },
     19  "dependencies": {
     20    "vue": "^3.2.1"
    1821  }
    1922}
  • main/trunk/model-interfaces-dev/atea/style/asr.scss

    r35276 r35282  
    172172}
    173173
    174 .asr-hidden {
    175     display: none;
    176 }
    177 
    178174#btnBeginTranscription {
    179175    float: right;
     
    230226        margin-bottom: 0;
    231227    }   
    232 }
    233 
    234 .metadata-list {
    235     font-size: 16px;
    236     padding: 0;
    237     list-style-type: none;
    238     overflow-x: auto;
    239     overflow-y: hidden;
    240     display: flex;
    241 }
    242 
    243 .metadata-list-container {
    244     position: relative;
    245 }
    246 
    247 .metadata-list-item {
    248     z-index: 1;
    249     width: 1.5em;
    250     text-align: center;
    251     vertical-align: middle;
    252 
    253     p {
    254         margin: 0;
    255     }
    256 
    257     button {
    258         padding: 0;
    259         margin: 0;
    260     }
    261 }
    262 
    263 .metadata-list-item .spaced-block {
    264     width: 100%;
    265     border-right: 1px solid black;
    266 
    267     &:last-child {
    268         margin-top: -10%;
    269         margin-bottom: 20%;
    270         border-right: none;
    271     }
    272228}
    273229
  • main/trunk/model-interfaces-dev/atea/transform/pages/asr.xsl

    r35276 r35282  
    66    xmlns:gsf="http://www.greenstone.org/greenstone3/schema/ConfigFormat"
    77    xmlns:gslib="http://www.greenstone.org/skinning"
     8    xmlns:v-bind="http://vuejs.org"
     9    xmlns:v-on="http://vuejs.org"
    810    extension-element-prefixes="java util"
    911    exclude-result-prefixes="java util">
     
    3537        <xsl:call-template name="audio-transcription"/>
    3638    </xsl:template>
     39
     40    <!-- <xsl:attribute-set name="vue">
     41        <xsl:attribute name="v-on" />
     42        <xsl:attribute name="v-bind" />
     43    </xsl:attribute-set> -->
    3744   
    3845    <!-- Template for processing audio file uploads -->
    3946    <xsl:template name="audio-transcription">
    4047        <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
    41         <!-- <link rel="preconnect" href="https://fonts.googleapis.com" />
    42         <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin" />
    43         <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&amp;family=Roboto:wght@400;700&amp;display=swap" rel="stylesheet" /> -->
    4448        <link href="interfaces/{$interface_name}/style/asr.css" rel="stylesheet" type="text/css" />
    4549
     
    4751
    4852        <section id="audio-transcription-container" class="paper">
    49             <div>
    50                
    51                 <form onSubmit="doAudioUpload(); return false;" enctype="multipart/form-data">
    52                     <button class="btn-primary" type="button" onclick="document.getElementById('audioFileUpload').click()" title="test">
    53                         <span class="material-icons">&#xE2C6;</span> <!-- file_upload -->
    54                         <span>Upload Audio Files</span>
    55                     </button>
     53            <!-- Contains the file input, transcribe button and transcription progress indicator -->
     54            <div id="audioUploadContainer">
     55                <button class="btn-primary" type="button" v-on:click="openFilePicker">
     56                    <span class="material-icons">&#xE2C6;</span> <!-- file_upload -->
     57                    <span>Upload Audio Files</span>
     58                </button>
    5659
    57                     <input id="audioFileUpload" type="file" accept="audio/wav" multiple="multiple" />
    58                    
    59                     <button id="btnBeginTranscription" class="btn-primary" type="submit" disabled="disabled">
    60                         <span class="material-icons">&#xEA3E;</span> <!-- history_edu -->
    61                         <span>Transcribe</span>
    62                     </button>
    63                 </form>
     60                <input id="audioFileInput" type="file" v-on:input="onFilesChanged"
     61                       accept="audio/wav" multiple="multiple" v-bind:disabled="isTranscribing" />
    6462
    65                 <div id="prgFileUploadContainer" class="asr-hidden">
    66                     <label for="prgFileUpload">Processing:</label>
    67                     <progress id="prgFileUpload" />
    68                 </div>
     63                <button id="btnBeginTranscription" class="btn-primary" type="submit"
     64                        v-bind:disabled="!canTranscribe" v-on:click="doTranscription">
     65                    <span class="material-icons">&#xEA3E;</span> <!-- history_edu -->
     66                    <span>Transcribe</span>
     67                </button>
     68
     69                <progress v-if="isTranscribing" class="indeterminateLoadingBar" />
    6970            </div>
    7071   
     
    7778            </div> -->
    7879   
    79             <!-- The list of each transcription -->
     80            <!-- Contains the audio element and transcription list -->
    8081            <div id="transcriptionsDisplayContainer">
    8182
     
    100101                        </div>
    101102
    102                         <gsf:div class="transcription__word-list" />
    103 
    104                         <details>
    105                             <summary>Character Information</summary>
    106                             <div class="metadata-list-container">
    107                                 <ul class="metadata-list"></ul>
    108                             </div>
    109                             <div class="audio-slider-container">
    110                                 <input type="range" min="0" value="0" class="audio-slider" />
    111                             </div>
    112                         </details>
     103                        <gsf:div class="transcription__word-list" id="transcriptionWordList">
     104                            <ul class="transcription__list">
     105                                <li v-for="line in lines" style="display: flex">
     106                                    <span v-for="word in line.words" style="border: 1px solid blue">{{ word }}</span>
     107                                </li>
     108                            </ul>
     109                        </gsf:div>
     110                       
     111                        <div class="audio-slider-container">
     112                            <input type="range" min="0" value="0" class="audio-slider" />
     113                        </div>
    113114
    114115                        <hr class="divider" />
     
    129130                    </li>
    130131                </template>
    131    
    132                 <template id="metadataTemplate">
    133                     <!-- <li class="metadata-list-item tooltip-parent tooltip">
    134                         <div class="spaced-block">
    135                             <p></p>
    136                         </div>
    137                         <div class="spaced-block">
    138                             <button type="button"><img src="interfaces/{$interface_name}/assets/play_arrow_black_18dp.svg" /></button>
    139                         </div>
    140                         <div class="tooltip-wrapper">
    141                             <span class="tooltip">0.02</span>
    142                         </div>
    143                     </li> -->
    144                     <li class="metadata-list-item">
    145                         <div class="spaced-block">
    146                             <p></p>
    147                         </div>
    148                         <div class="spaced-block">
    149                             <button type="button"><img src="interfaces/{$interface_name}/assets/play_arrow_black_18dp.svg" /></button>
    150                         </div>
    151                     </li>
    152                 </template>
    153132            </div>
    154133        </section>
    155134       
     135        <!-- TODO: Switch to production version for release builds -->
     136        <gsf:script src="https://unpkg.com/vue@next"></gsf:script>
    156137        <gsf:script src="interfaces/{$interface_name}/js/asr/asr-controller.js"></gsf:script>
    157138    </xsl:template>
Note: See TracChangeset for help on using the changeset viewer.