diff --git a/tts_frontend/package-lock.json b/tts_frontend/package-lock.json index d640621572..f8034a202a 100644 --- a/tts_frontend/package-lock.json +++ b/tts_frontend/package-lock.json @@ -1547,11 +1547,26 @@ "@types/react": "*" } }, + "@types/react-tag-autocomplete": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/@types/react-tag-autocomplete/-/react-tag-autocomplete-5.12.0.tgz", + "integrity": "sha512-QvPBUrXnkU5e3EaHiXQcI0oRsV1JwvL2vvgXtY5x7SkW19O3FRGFdcPg4+SA545+pcIsijf1O2wQ/sCCTeEe9w==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==" }, + "@types/tabulator-tables": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/@types/tabulator-tables/-/tabulator-tables-4.6.7.tgz", + "integrity": "sha512-mmPuILf3jdiutn1qbqSAZRVOUPUemsjeTClHC8iwEdi6wjIptz/t5e2xYO+EVlia7XvWAiD00Y+y1iF2cKkxbw==", + "dev": true + }, "@types/testing-library__dom": { "version": "6.11.1", "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-6.11.1.tgz", @@ -4829,6 +4844,12 @@ "es6-symbol": "^3.1.1" } }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, "es6-symbol": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", @@ -6366,6 +6387,12 @@ "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" }, + "html-attributes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/html-attributes/-/html-attributes-1.1.0.tgz", + "integrity": "sha1-ggJ6T6x6YHDqbBjMOIauoY1t6gk=", + "dev": true + }, "html-comment-regex": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", @@ -8320,6 +8347,12 @@ "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -9520,6 +9553,17 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "pick-react-known-prop": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/pick-react-known-prop/-/pick-react-known-prop-0.1.5.tgz", + "integrity": "sha512-SnDf64AVdvqoAFpHeZUKT9kdn40Ellj84CPALRxYWqNJ6r6f44eAAT+Jtkb0Suhiw7yg5BdOFAQ25OJnjG+afw==", + "dev": true, + "requires": { + "html-attributes": "^1.1.0", + "lodash.isplainobject": "^4.0.6", + "svg-attributes": "^1.0.0" + } + }, "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -11092,6 +11136,26 @@ "workbox-webpack-plugin": "4.3.1" } }, + "react-tabulator": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/react-tabulator/-/react-tabulator-0.12.7.tgz", + "integrity": "sha512-PQVdB20yMhtq7tt+tkToZKWEdqNPT3EshGl7E3TX222KKhvLk6hz8saJGvzAgCePYGyKkhpRh2dsbl6KQSj2eA==", + "dev": true, + "requires": { + "@types/react-tag-autocomplete": "^5.6.0", + "dotenv": "^8.2.0", + "es6-promise": "^4.2.8", + "pick-react-known-prop": "^0.1.5", + "react-tag-autocomplete": "^5.7.1", + "tabulator-tables": "^4.6.2" + } + }, + "react-tag-autocomplete": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/react-tag-autocomplete/-/react-tag-autocomplete-5.13.1.tgz", + "integrity": "sha512-ECcQnizAxw8VnEDUfCKuA2ZDQ0Fyxds3kVtE4NVAhJvBYOMMgkRNAM3UwyEXAQ0h7nnCwmIA+czJiwso07Mrqw==", + "dev": true + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -12545,6 +12609,12 @@ "has-flag": "^3.0.0" } }, + "svg-attributes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-attributes/-/svg-attributes-1.0.0.tgz", + "integrity": "sha1-tcWWjzYke32+OFMgfyqcaK2Aa/w=", + "dev": true + }, "svg-parser": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.2.tgz", @@ -12608,6 +12678,11 @@ } } }, + "tabulator-tables": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/tabulator-tables/-/tabulator-tables-4.6.3.tgz", + "integrity": "sha512-e/SeG1jU5ryg0OlBpKo89ZmKTYdgfRYLRT366Vu17jIrTFxtyaAhgg95N7L0vzvnvQHuSATUY3UynHzYh2iDYw==" + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", diff --git a/tts_frontend/package.json b/tts_frontend/package.json index f0b3e6ccc0..1779ac243e 100644 --- a/tts_frontend/package.json +++ b/tts_frontend/package.json @@ -13,6 +13,8 @@ "react": "^16.12.0", "react-dom": "^16.12.0", "react-scripts": "3.3.0", + "react-tabulator": "^0.12.7", + "tabulator-tables": "^4.6.3", "typescript": "^3.7.4" }, "scripts": { @@ -42,6 +44,7 @@ "devDependencies": { "@types/howler": "^2.1.2", "@types/react": "^16.9.17", - "@types/react-dom": "^16.9.4" + "@types/react-dom": "^16.9.4", + "@types/tabulator-tables": "^4.6.7" } } diff --git a/tts_frontend/src/ApiConfig.ts b/tts_frontend/src/ApiConfig.ts index d608a961a5..735cb03669 100644 --- a/tts_frontend/src/ApiConfig.ts +++ b/tts_frontend/src/ApiConfig.ts @@ -28,6 +28,7 @@ class ApiConfig { case "localhost": switch (url.port) { case "8080": + case "7000": console.log('environment', 'localhost_js'); environment = Environment.LOCALHOST_JS; break; diff --git a/tts_frontend/src/App.tsx b/tts_frontend/src/App.tsx index 0aca9233e3..9b9e9581b0 100644 --- a/tts_frontend/src/App.tsx +++ b/tts_frontend/src/App.tsx @@ -2,13 +2,18 @@ import React from 'react'; import './App.css'; import { MainComponent } from './components/MainComponent'; import ApiConfig from './ApiConfig'; +import { SentencesComponent } from './components/sentences/SentencesComponent'; const App: React.FC = () => { let apiConfig = new ApiConfig(); + // + //

Neural TTS

+ // + // return (
-

Neural TTS

- +
+
); } diff --git a/tts_service/src/database/model.rs b/tts_service/src/database/model.rs index 941f4567c1..b57202c735 100644 --- a/tts_service/src/database/model.rs +++ b/tts_service/src/database/model.rs @@ -4,6 +4,7 @@ use crate::database::connector::{DatabaseConnector, MysqlPooledConnection}; use crate::schema::sentences; //use crate::schema::sentences::dsl::*; use diesel::prelude::*; +use diesel::expression::dsl::count_star; #[derive(Queryable, Serialize, Debug)] pub struct Sentence { @@ -35,11 +36,22 @@ impl NewSentence { impl Sentence { - pub fn load(db_connector: &DatabaseConnector, limit: i64) -> AnyhowResult> { + /// Get a count of all sentence records. + pub fn count(db_connector: &DatabaseConnector) -> AnyhowResult { + let pooled_connection : MysqlPooledConnection = db_connector.get_pooled_connection()?; + let count : i64 = sentences::dsl::sentences + .select(count_star()) + .first(&*pooled_connection)?; + Ok(count) + } + + /// Load sentence records + pub fn load(db_connector: &DatabaseConnector, limit: i64, offset: i64) -> AnyhowResult> { let pooled_connection : MysqlPooledConnection = db_connector.get_pooled_connection()?; let results = sentences::dsl::sentences .limit(limit) - .order(sentences::dsl::id.desc()) + .offset(offset) + .order(sentences::dsl::id.asc()) .load::(&pooled_connection)?; Ok(results) } diff --git a/tts_service/src/endpoints/sentences.rs b/tts_service/src/endpoints/sentences.rs index b6765bc7c9..68fdde65a0 100644 --- a/tts_service/src/endpoints/sentences.rs +++ b/tts_service/src/endpoints/sentences.rs @@ -1,7 +1,4 @@ -use actix_web::web::{ - Data, - Json, -}; +use actix_web::web::{Data, Json, Query}; use actix_web::http::StatusCode; use actix_web::{HttpRequest, get, HttpResponse, Either}; @@ -9,20 +6,46 @@ use std::sync::Arc; use crate::AppState; use crate::database::model::Sentence; +/// Request with pagination from Tabulator.js +#[derive(Deserialize, Debug)] +pub struct SentencesRequest { + page: Option, + per_page: Option, +} + +/// Paginated responses in the format expected by Tabulator.js #[derive(Serialize, Debug)] pub struct SentencesResult { - sentences: Vec, + record_count: i64, // Added for my own purposes + last_page: i64, + data: Vec, } #[get("/sentences")] pub async fn get_sentences( _request: HttpRequest, + query: Query, app_state: Data> ) -> Either, std::io::Result>{ + + debug!("SentencesRequest: {:?}", query); + let app_state = app_state.into_inner(); - let limit = 1000; - let sentences = match Sentence::load(&app_state.database_connector, limit) { + let sentence_count = Sentence::count(&app_state.database_connector) + .ok() + .unwrap_or(0); + + debug!("Total Sentence Records: {:?}", sentence_count); + + let page = query.page.unwrap_or(1); + let limit = query.per_page.unwrap_or(100); + + let offset = limit * (page - 1); + + debug!("Sentence Query Limit: {}, Offset: {}", limit, offset); + + let sentences = match Sentence::load(&app_state.database_connector, limit, offset) { Err(e) => { error!("Couldn't query database for sentences: {:?}", e); return Either::B(Ok(HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR) @@ -32,7 +55,11 @@ pub async fn get_sentences( Ok(results) => results, }; + let last_page = sentence_count / limit; + Either::A(Json(SentencesResult { - sentences: sentences, + data: sentences, + last_page, + record_count: sentence_count, })) } diff --git a/tts_service/src/main.rs b/tts_service/src/main.rs index 22859eaac9..66ff03fd98 100644 --- a/tts_service/src/main.rs +++ b/tts_service/src/main.rs @@ -205,6 +205,7 @@ async fn run_server(app_state: AppState, server_args: ServerArgs) -> std::io::Re .allowed_methods(vec!["GET", "POST", "OPTIONS"]) .allowed_headers(vec![ http::header::ACCEPT, + http::header::ACCESS_CONTROL_ALLOW_ORIGIN, // Tabulator Ajax http::header::CONTENT_TYPE, http::header::HeaderName::from_static("x-requested-with") // Tabulator Ajax sends ])