From 6ea340da7abcf9b291ffa090877c8881a9bff049 Mon Sep 17 00:00:00 2001 From: TchernyavskyDaniil Date: Thu, 4 Jun 2020 08:13:10 +0300 Subject: [PATCH] added csv converter --- backend/package-lock.json | 119 +++--------------- backend/package.json | 8 +- frontend/package-lock.json | 95 ++------------ frontend/package.json | 4 +- frontend/src/constants/blob_support.ts | 3 + frontend/src/features/csv_convert/index.tsx | 23 ++++ frontend/src/features/csv_convert/model.ts | 28 +++++ .../twitter/tabs/profile/likes/index.tsx | 11 +- .../twitter/tabs/profile/likes/model.ts | 8 ++ .../twitter/tabs/profile/media/index.tsx | 11 +- .../twitter/tabs/profile/media/model.ts | 8 ++ .../tabs/profile/profile_info/index.tsx | 92 ++++++++------ .../tabs/profile/profile_info/model.ts | 34 +++++ .../twitter/tabs/profile/tweets/index.tsx | 11 +- .../twitter/tabs/profile/tweets/model.ts | 8 ++ .../tabs/profile/tweets_and_replies/index.tsx | 13 +- .../tabs/profile/tweets_and_replies/model.ts | 8 ++ .../search_tweets/latest_tweets/index.tsx | 11 +- .../tabs/search_tweets/latest_tweets/model.ts | 62 ++++----- .../tabs/search_tweets/top_tweets/index.tsx | 11 +- .../tabs/search_tweets/top_tweets/model.ts | 60 +++++---- .../features/twitter/tweets_info/index.tsx | 17 ++- frontend/src/lib/blob_support/index.ts | 4 + frontend/src/lib/convert_tweets/index.ts | 29 +++++ frontend/src/lib/date/index.ts | 7 ++ frontend/src/lib/download_csv/index.ts | 10 ++ frontend/src/mocks/final_tweets.ts | 2 +- frontend/src/types/tweets.ts | 16 ++- 28 files changed, 404 insertions(+), 309 deletions(-) create mode 100644 frontend/src/constants/blob_support.ts create mode 100644 frontend/src/features/csv_convert/index.tsx create mode 100644 frontend/src/features/csv_convert/model.ts create mode 100644 frontend/src/lib/blob_support/index.ts create mode 100644 frontend/src/lib/convert_tweets/index.ts create mode 100644 frontend/src/lib/date/index.ts create mode 100644 frontend/src/lib/download_csv/index.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index 1df9133..49a2c15 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -2370,15 +2370,6 @@ "sumchecker": "^3.0.1" } }, - "@samverschueren/stream-to-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", - "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", - "dev": true, - "requires": { - "any-observable": "^0.3.0" - } - }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -2476,9 +2467,9 @@ } }, "@types/node": { - "version": "14.0.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.6.tgz", - "integrity": "sha512-FbNmu4F67d3oZMWBV6Y4MaPER+0EpE9eIYf2yaHhCWovc1dlXCZkqGX4NLHfVVr6umt20TNBdRzrNJIzIKfdbw==" + "version": "14.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.10.tgz", + "integrity": "sha512-Bz23oN/5bi0rniKT24ExLf4cK0JdvN3dH/3k0whYkdN4eI4vS2ZW/2ENNn2uxHCzWcbdHIa/GRuWQytfzCjRYw==" }, "@types/parse-json": { "version": "4.0.0", @@ -2523,9 +2514,9 @@ "dev": true }, "@types/ws": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.4.tgz", - "integrity": "sha512-9S6Ask71vujkVyeEXKxjBSUV8ZUB0mjL5la4IncBoheu04bDaYyUKErh1BQcY9+WzOUOiKqz/OnpJHYckbMfNg==", + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.5.tgz", + "integrity": "sha512-4UEih9BI1nBKii385G9id1oFrSkLcClbwtDfcYj8HJLQqZVAtb/42vXVrYvRWCcufNF/a+rZD3MxNwghA7UmCg==", "dev": true, "requires": { "@types/node": "*" @@ -2815,12 +2806,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==" }, - "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true - }, "ansi-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -2853,12 +2838,6 @@ "color-convert": "^1.9.0" } }, - "any-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", - "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", - "dev": true - }, "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -3637,12 +3616,6 @@ "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, "clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", @@ -4014,15 +3987,6 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "requires": { - "clone": "^1.0.2" - } - }, "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -4210,9 +4174,9 @@ } }, "electron": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.1.tgz", - "integrity": "sha512-PZsQ0juL5YyDfOKES3HWz7zbWidcRcmtTzFCHSNzeVMjlkWB+hQToWVczFuGEWzwbAM1rCFs9MT0V/zpYT3pqQ==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/electron/-/electron-9.0.2.tgz", + "integrity": "sha512-+a3KegLvQXVjC3b6yBWwZmtWp3tHf9ut27yORAWHO9JRFtKfNf88fi1UvTPJSW8R0sUH7ZEdzN6A95T22KGtlA==", "dev": true, "requires": { "@electron/get": "^1.0.1", @@ -4221,9 +4185,9 @@ }, "dependencies": { "@types/node": { - "version": "12.12.42", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.42.tgz", - "integrity": "sha512-R/9QdYFLL9dE9l5cWWzWIZByVGFd7lk7JVOJ7KD+E1SJ4gni7XJRLz9QTjyYQiHIqEAgku9VgxdLjMlhhUaAFg==", + "version": "12.12.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.43.tgz", + "integrity": "sha512-KUyZdkGCnVPuXfsKmDUu2XLui65LZIJ2s0M57noy5e+ixUT2oK33ep7zlvgzI8LElcWqbf8AR+o/3GqAPac2zA==", "dev": true }, "debug": { @@ -4255,12 +4219,6 @@ "integrity": "sha512-4lwnxp+ArqOX9hiLwLpwhfqvwzUHFuDgLz4NTiU3lhygUzWtocIJ/5Vix+mWVNE2HQ9aI1k2ncGe5H/0OktMvA==", "dev": true }, - "elegant-spinner": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-2.0.0.tgz", - "integrity": "sha512-5YRYHhvhYzV/FC4AiMdeSIg3jAYGq9xFvbhZMpPlJoBsfYgrw2DSCYeXfat6tYBu45PWiyRr3+flaCPPmviPaA==", - "dev": true - }, "elliptic": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", @@ -4329,15 +4287,6 @@ } } }, - "enquirer": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz", - "integrity": "sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==", - "dev": true, - "requires": { - "ansi-colors": "^3.2.1" - } - }, "entities": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", @@ -6958,9 +6907,9 @@ "dev": true }, "lint-staged": { - "version": "10.2.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.7.tgz", - "integrity": "sha512-srod2bTpF8riaLz+Bgr6v0mI/nSntE8M9jbh4WwAhoosx0G7RKEUIG7mI5Nu5SMbTF9o8GROPgK0Lhf5cDnUUw==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.8.tgz", + "integrity": "sha512-36VUVhZuTJUG0yuSv66o+/Cep9Uwp+od6VkjNxQjHKWvHClVD0SjAZx++4H3zgdr6DxPoUl1y/PVygfPhzAXQQ==", "dev": true, "requires": { "chalk": "^4.0.0", @@ -7081,25 +7030,19 @@ } }, "listr2": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.0.4.tgz", - "integrity": "sha512-oJaAcplPsa72rKW0eg4P4LbEJjhH+UO2I8uqR/I2wzHrVg16ohSfUy0SlcHS21zfYXxtsUpL8YXGHjyfWMR0cg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.1.0.tgz", + "integrity": "sha512-pWrbMLO+6jxGbgAasTLUzfRYdBaQvv6sNTWDfIX8ENBNmTwt/eafZ/LlJ66/dNaDnEhzCpWricLH4U9cjSAHYg==", "dev": true, "requires": { - "@samverschueren/stream-to-observable": "^0.3.0", "chalk": "^4.0.0", - "cli-cursor": "^3.1.0", "cli-truncate": "^2.1.0", - "elegant-spinner": "^2.0.0", - "enquirer": "^2.3.5", "figures": "^3.2.0", "indent-string": "^4.0.0", "log-update": "^4.0.0", "p-map": "^4.0.0", - "pad": "^3.2.0", "rxjs": "^6.5.5", - "through": "^2.3.8", - "uuid": "^7.0.2" + "through": "^2.3.8" }, "dependencies": { "ansi-styles": { @@ -8166,15 +8109,6 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, - "pad": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pad/-/pad-3.2.0.tgz", - "integrity": "sha512-2u0TrjcGbOjBTJpyewEl4hBO3OeX5wWue7eIFPzQTg6wFSvoaHcBTTUY5m+n0hd04gmTCPuY0kCpVIVuw5etwg==", - "dev": true, - "requires": { - "wcwidth": "^1.0.1" - } - }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -10094,12 +10028,6 @@ "object.getownpropertydescriptors": "^2.1.0" } }, - "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", - "dev": true - }, "v8-compile-cache": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", @@ -10145,15 +10073,6 @@ "neo-async": "^2.5.0" } }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, "webpack": { "version": "4.43.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", diff --git a/backend/package.json b/backend/package.json index 72ab178..ddc8f59 100644 --- a/backend/package.json +++ b/backend/package.json @@ -43,10 +43,10 @@ "@babel/preset-env": "^7.10.2", "@babel/preset-typescript": "^7.10.1", "@types/bull": "^3.13.0", - "@types/node": "^14.0.6", + "@types/node": "^14.0.10", "@types/redis": "^2.8.22", "@types/sequelize": "^4.28.9", - "@types/ws": "^7.2.4", + "@types/ws": "^7.2.5", "@typescript-eslint/parser": "^3.1.0", "babel-eslint": "^10.1.0", "eslint": "^7.1.0", @@ -56,14 +56,14 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.1.3", "husky": "^4.2.5", - "lint-staged": "^10.2.7", + "lint-staged": "^10.2.8", "prettier": "1.19.1", "ts-node": "^8.10.2", "typescript": "^3.9.3", "@types/cheerio": "^0.22.18", "@types/natural": "^0.6.3", "@types/ramda": "^0.27.6", - "electron": "^9.0.1" + "electron": "^9.0.2" }, "husky": { "hooks": { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bb65877..1640ee2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3224,15 +3224,6 @@ "react-is": "^16.8.0" } }, - "@samverschueren/stream-to-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", - "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", - "dev": true, - "requires": { - "any-observable": "^0.3.0" - } - }, "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", @@ -3848,12 +3839,6 @@ "color-convert": "^1.9.0" } }, - "any-observable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", - "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", - "dev": true - }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -5241,12 +5226,6 @@ } } }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, "clsx": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", @@ -6256,15 +6235,6 @@ } } }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, - "requires": { - "clone": "^1.0.2" - } - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -6657,9 +6627,9 @@ } }, "effector-react": { - "version": "20.7.2", - "resolved": "https://registry.npmjs.org/effector-react/-/effector-react-20.7.2.tgz", - "integrity": "sha512-oVGD6hwKeZtym0lQL7voB09Pb3lFGFJPkQ4TIj8LiGPDTesZvowr3PhnTRIOWFJ1XAjuM/kDSjVZnZikJYeRBg==" + "version": "20.7.3", + "resolved": "https://registry.npmjs.org/effector-react/-/effector-react-20.7.3.tgz", + "integrity": "sha512-bqSZDk2yw9pntaYQTMDLvke3pcuj2gi0+fKJEUZbAfV4j9E9DBMkVG+SggkPqq95R4ne4F8I8+RjNyC11K4iAQ==" }, "ejs": { "version": "2.7.4", @@ -6673,12 +6643,6 @@ "integrity": "sha512-uEb2zs6EJ6OZIqaMsCSliYVgzE/f7/s1fLWqtvRtHg/v5KBF2xds974fUnyatfxIDgkqzQVwFtam5KExqywx0Q==", "dev": true }, - "elegant-spinner": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-2.0.0.tgz", - "integrity": "sha512-5YRYHhvhYzV/FC4AiMdeSIg3jAYGq9xFvbhZMpPlJoBsfYgrw2DSCYeXfat6tYBu45PWiyRr3+flaCPPmviPaA==", - "dev": true - }, "elliptic": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", @@ -6751,15 +6715,6 @@ } } }, - "enquirer": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz", - "integrity": "sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA==", - "dev": true, - "requires": { - "ansi-colors": "^3.2.1" - } - }, "entities": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", @@ -10022,9 +9977,9 @@ "dev": true }, "lint-staged": { - "version": "10.2.7", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.7.tgz", - "integrity": "sha512-srod2bTpF8riaLz+Bgr6v0mI/nSntE8M9jbh4WwAhoosx0G7RKEUIG7mI5Nu5SMbTF9o8GROPgK0Lhf5cDnUUw==", + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.8.tgz", + "integrity": "sha512-36VUVhZuTJUG0yuSv66o+/Cep9Uwp+od6VkjNxQjHKWvHClVD0SjAZx++4H3zgdr6DxPoUl1y/PVygfPhzAXQQ==", "dev": true, "requires": { "chalk": "^4.0.0", @@ -10149,25 +10104,19 @@ } }, "listr2": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.0.4.tgz", - "integrity": "sha512-oJaAcplPsa72rKW0eg4P4LbEJjhH+UO2I8uqR/I2wzHrVg16ohSfUy0SlcHS21zfYXxtsUpL8YXGHjyfWMR0cg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-2.1.0.tgz", + "integrity": "sha512-pWrbMLO+6jxGbgAasTLUzfRYdBaQvv6sNTWDfIX8ENBNmTwt/eafZ/LlJ66/dNaDnEhzCpWricLH4U9cjSAHYg==", "dev": true, "requires": { - "@samverschueren/stream-to-observable": "^0.3.0", "chalk": "^4.0.0", - "cli-cursor": "^3.1.0", "cli-truncate": "^2.1.0", - "elegant-spinner": "^2.0.0", - "enquirer": "^2.3.5", "figures": "^3.2.0", "indent-string": "^4.0.0", "log-update": "^4.0.0", "p-map": "^4.0.0", - "pad": "^3.2.0", "rxjs": "^6.5.5", - "through": "^2.3.8", - "uuid": "^7.0.2" + "through": "^2.3.8" }, "dependencies": { "ansi-styles": { @@ -10219,12 +10168,6 @@ "requires": { "has-flag": "^4.0.0" } - }, - "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", - "dev": true } } }, @@ -11420,15 +11363,6 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "pad": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pad/-/pad-3.2.0.tgz", - "integrity": "sha512-2u0TrjcGbOjBTJpyewEl4hBO3OeX5wWue7eIFPzQTg6wFSvoaHcBTTUY5m+n0hd04gmTCPuY0kCpVIVuw5etwg==", - "dev": true, - "requires": { - "wcwidth": "^1.0.1" - } - }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -15140,15 +15074,6 @@ "minimalistic-assert": "^1.0.0" } }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, - "requires": { - "defaults": "^1.0.3" - } - }, "webidl-conversions": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 26005aa..e68776c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -67,7 +67,7 @@ "html-webpack-plugin": "^4.3.0", "husky": "^4.2.5", "ignore-loader": "^0.1.2", - "lint-staged": "^10.2.7", + "lint-staged": "^10.2.8", "mini-css-extract-plugin": "^0.9.0", "optimize-css-assets-webpack-plugin": "^5.0.3", "postcss-csso": "^4.0.0", @@ -90,7 +90,7 @@ "@material-ui/lab": "^4.0.0-alpha.55", "core-js": "3.6.5", "effector": "^20.15.8", - "effector-react": "^20.7.2", + "effector-react": "^20.7.3", "fast-async": "^6.3.8", "history": "4.10.1", "nanoid": "^3.1.9", diff --git a/frontend/src/constants/blob_support.ts b/frontend/src/constants/blob_support.ts new file mode 100644 index 0000000..2ef7148 --- /dev/null +++ b/frontend/src/constants/blob_support.ts @@ -0,0 +1,3 @@ +import { checkIsBrowserNotSupportBlob } from '../lib/blob_support'; + +export const isBlobNotSupported = checkIsBrowserNotSupportBlob(); diff --git a/frontend/src/features/csv_convert/index.tsx b/frontend/src/features/csv_convert/index.tsx new file mode 100644 index 0000000..cf695bd --- /dev/null +++ b/frontend/src/features/csv_convert/index.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import Button from '@material-ui/core/Button'; + +import { exportCsvFx } from './model'; + +export const CsvConvert: React.FC<{ + convertData: Array; + text: string; +}> = ({ convertData, text }) => { + const handleClickConvert = () => { + exportCsvFx(convertData); + }; + + return ( + + ); +}; diff --git a/frontend/src/features/csv_convert/model.ts b/frontend/src/features/csv_convert/model.ts new file mode 100644 index 0000000..791bd3b --- /dev/null +++ b/frontend/src/features/csv_convert/model.ts @@ -0,0 +1,28 @@ +import { createEffect } from 'effector'; + +import { isBlobNotSupported } from '../../constants/blob_support'; +import { downloadCsv } from '../../lib/download_csv'; +import { getCurrentDate } from '../../lib/date'; +import { speakMessage } from '../../lib/speech_synthesis'; + +export const exportCsvFx = createEffect, any>({ + handler: dataToConvert => { + if (isBlobNotSupported) { + speakMessage('Не сегодня, браузер обнови в начале'); + } + + const firstData = dataToConvert[0]; + const labels = Object.keys(firstData).join(','); + const fields = dataToConvert.map(data => { + const normalizedData = Object.values(data) + .map(value => `"${value}"`) + .join(','); + + return normalizedData; + }); + const normalizedDataToConvert = [labels, ...fields].join('\n'); + const fileName = `${getCurrentDate()}_parsed_tweets.csv`; + + downloadCsv(normalizedDataToConvert, fileName); + }, +}); diff --git a/frontend/src/features/twitter/tabs/profile/likes/index.tsx b/frontend/src/features/twitter/tabs/profile/likes/index.tsx index 18c77f6..779f100 100644 --- a/frontend/src/features/twitter/tabs/profile/likes/index.tsx +++ b/frontend/src/features/twitter/tabs/profile/likes/index.tsx @@ -6,11 +6,18 @@ import { $likesTweets } from './model'; import { TweetsInfo } from '../../../tweets_info'; export const LikesTweets: React.FC = () => { - const { tweets, isLoading } = useStore($likesTweets); + const { tweets, isLoading, tweetsToConvert } = useStore($likesTweets); if (isLoading === null) { return null; } - return ; + return ( + + ); }; diff --git a/frontend/src/features/twitter/tabs/profile/likes/model.ts b/frontend/src/features/twitter/tabs/profile/likes/model.ts index 3f1d6d7..3f1f77c 100644 --- a/frontend/src/features/twitter/tabs/profile/likes/model.ts +++ b/frontend/src/features/twitter/tabs/profile/likes/model.ts @@ -5,20 +5,27 @@ import { $tweetsMessage, sendFx } from '../../../../../socket'; import { LIKES } from '../../../../../constants/tweets_types'; import { initialStore } from '../../../../../constants/initial_tweets_store'; import { + FinalConvertedTweet, NormalizedTweetInfo, TakenTweetsInfo, } from '../../../../../types/tweets'; import { setNormalizedTweets } from '../../../../../lib/set_normalized_tweets'; import { speakMessage } from '../../../../../lib/speech_synthesis'; import { SUCCESS_LIKES_SPEECH } from '../../../../../constants/speech'; +import { getConvertedTweets } from '../../../../../lib/convert_tweets'; export const $normalizedLikesTweets = createStore( initialStore, ); +export const $likesTweetsForConvert = createStore>( + [], +); const likesChanged = createEvent(); $normalizedLikesTweets.on(likesChanged, setNormalizedTweets); +// @ts-ignore ¯\_(ツ)_/¯ +$likesTweetsForConvert.on(likesChanged, getConvertedTweets); guard({ source: $tweetsMessage, @@ -36,5 +43,6 @@ $isLoadingLikesTweets.on(likesChanged, () => { export const $likesTweets = combine({ tweets: $normalizedLikesTweets, + tweetsToConvert: $likesTweetsForConvert, isLoading: $isLoadingLikesTweets, }); diff --git a/frontend/src/features/twitter/tabs/profile/media/index.tsx b/frontend/src/features/twitter/tabs/profile/media/index.tsx index 92050e3..ccacb46 100644 --- a/frontend/src/features/twitter/tabs/profile/media/index.tsx +++ b/frontend/src/features/twitter/tabs/profile/media/index.tsx @@ -6,11 +6,18 @@ import { $mediaTweets } from './model'; import { TweetsInfo } from '../../../tweets_info'; export const MediaTweets: React.FC = () => { - const { tweets, isLoading } = useStore($mediaTweets); + const { tweets, isLoading, tweetsToConvert } = useStore($mediaTweets); if (isLoading === null) { return null; } - return ; + return ( + + ); }; diff --git a/frontend/src/features/twitter/tabs/profile/media/model.ts b/frontend/src/features/twitter/tabs/profile/media/model.ts index df6730c..7c5822b 100644 --- a/frontend/src/features/twitter/tabs/profile/media/model.ts +++ b/frontend/src/features/twitter/tabs/profile/media/model.ts @@ -5,20 +5,27 @@ import { $tweetsMessage, sendFx } from '../../../../../socket'; import { MEDIA } from '../../../../../constants/tweets_types'; import { initialStore } from '../../../../../constants/initial_tweets_store'; import { + FinalConvertedTweet, NormalizedTweetInfo, TakenTweetsInfo, } from '../../../../../types/tweets'; import { setNormalizedTweets } from '../../../../../lib/set_normalized_tweets'; import { speakMessage } from '../../../../../lib/speech_synthesis'; import { SUCCESS_MEDIA_SPEECH } from '../../../../../constants/speech'; +import { getConvertedTweets } from '../../../../../lib/convert_tweets'; export const $normalizedMediaTweets = createStore( initialStore, ); +export const $mediaTweetsForConvert = createStore>( + [], +); const mediaChanged = createEvent(); $normalizedMediaTweets.on(mediaChanged, setNormalizedTweets); +// @ts-ignore ¯\_(ツ)_/¯ +$mediaTweetsForConvert.on(mediaChanged, getConvertedTweets); guard({ source: $tweetsMessage, @@ -36,5 +43,6 @@ $isLoadingMediaTweets.on(mediaChanged, () => { export const $mediaTweets = combine({ tweets: $normalizedMediaTweets, + tweetsToConvert: $mediaTweetsForConvert, isLoading: $isLoadingMediaTweets, }); diff --git a/frontend/src/features/twitter/tabs/profile/profile_info/index.tsx b/frontend/src/features/twitter/tabs/profile/profile_info/index.tsx index 93e1e08..8b0b5d5 100644 --- a/frontend/src/features/twitter/tabs/profile/profile_info/index.tsx +++ b/frontend/src/features/twitter/tabs/profile/profile_info/index.tsx @@ -10,6 +10,7 @@ import Typography from '@material-ui/core/Typography'; import { $profileInfo } from './model'; import { checkIsNumberExist } from '../../../../../lib/is_number_exist'; import { ProfileSkeleton } from '../../../../../UI/skeleton/profile'; +import { CsvConvert } from '../../../../csv_convert'; const Info = styled(Typography)` padding-right: 10px; @@ -34,7 +35,9 @@ const DescriptionPart = styled.span` `; export const ProfileInfo: React.FC = () => { - const { profileInfo, isLoading } = useStore($profileInfo); + const { profileInfo, profileInfoForConvert, isLoading } = useStore( + $profileInfo, + ); if (isLoading === null) { return null; @@ -55,48 +58,55 @@ export const ProfileInfo: React.FC = () => { {isLoading && } {!isLoading && ( - - - - {name && {name}} - {tweetName && ( - - {tweetName} - + <> + + + + + + {name && {name}} + {tweetName && ( + + {tweetName} + + )} + + {description.length > 0 && ( + + {description.map(({ text, isUrl, url, id }) => ( + + {isUrl ? {text} : text} + + ))} + )} - - {description.length > 0 && ( - - {description.map(({ text, isUrl, url, id }) => ( - - {isUrl ? {text} : text} - + + {contactInfo.map(({ id, info }) => ( + + {info} + ))} - - )} - - {contactInfo.map(({ id, info }) => ( - - {info} - - ))} - - - {activityInfo.map(({ id, info }) => ( - - {info} - - ))} - - - {checkIsNumberExist(sentimentCoefficient) && ( - Sentiment: {sentimentCoefficient} - )} - {classifierData && ( - Naive Bayes: {classifierData} - )} - - + + + {activityInfo.map(({ id, info }) => ( + + {info} + + ))} + + + {checkIsNumberExist(sentimentCoefficient) && ( + Sentiment: {sentimentCoefficient} + )} + {classifierData && ( + Naive Bayes: {classifierData} + )} + + + )} ); diff --git a/frontend/src/features/twitter/tabs/profile/profile_info/model.ts b/frontend/src/features/twitter/tabs/profile/profile_info/model.ts index 198edd1..468b861 100644 --- a/frontend/src/features/twitter/tabs/profile/profile_info/model.ts +++ b/frontend/src/features/twitter/tabs/profile/profile_info/model.ts @@ -5,6 +5,7 @@ import { speakMessage } from '../../../../../lib/speech_synthesis'; import { SUCCESS_PROFILE_INFO_SPEECH } from '../../../../../constants/speech'; export const $isLoadingProfileInfo = createStore(null); +export const $profileInfoForConvert = createStore>([]); $isLoadingProfileInfo.on(sendFx, () => true); $isLoadingProfileInfo.on($profileMessage, () => { @@ -12,7 +13,40 @@ $isLoadingProfileInfo.on($profileMessage, () => { return false; }); +$profileInfoForConvert.on( + $profileMessage, + ( + _, + { + activityInfo, + classifierData, + contactInfo, + description, + name, + sentimentCoefficient, + tweetName, + avatarUrl, + }, + ) => { + const normalizedProfileInfoForConvert = [ + { + activityInfo: activityInfo.map(({ info }) => info).join(', '), + classifierData, + contactInfo: contactInfo.map(({ info }) => info).join(', '), + description: description.map(({ text }) => text).join(' '), + name, + sentimentCoefficient, + tweetName, + avatarUrl, + }, + ]; + + return normalizedProfileInfoForConvert; + }, +); + export const $profileInfo = combine({ profileInfo: $profileMessage, + profileInfoForConvert: $profileInfoForConvert, isLoading: $isLoadingProfileInfo, }); diff --git a/frontend/src/features/twitter/tabs/profile/tweets/index.tsx b/frontend/src/features/twitter/tabs/profile/tweets/index.tsx index 5cab259..49b0d4d 100644 --- a/frontend/src/features/twitter/tabs/profile/tweets/index.tsx +++ b/frontend/src/features/twitter/tabs/profile/tweets/index.tsx @@ -6,11 +6,18 @@ import { $profileTweets } from './model'; import { TweetsInfo } from '../../../tweets_info'; export const ProfileTweets: React.FC = () => { - const { tweets, isLoading } = useStore($profileTweets); + const { tweets, isLoading, tweetsToConvert } = useStore($profileTweets); if (isLoading === null) { return null; } - return ; + return ( + + ); }; diff --git a/frontend/src/features/twitter/tabs/profile/tweets/model.ts b/frontend/src/features/twitter/tabs/profile/tweets/model.ts index 58f4ffc..87a65fd 100644 --- a/frontend/src/features/twitter/tabs/profile/tweets/model.ts +++ b/frontend/src/features/twitter/tabs/profile/tweets/model.ts @@ -5,20 +5,27 @@ import { $tweetsMessage, sendFx } from '../../../../../socket'; import { TWEETS } from '../../../../../constants/tweets_types'; import { initialStore } from '../../../../../constants/initial_tweets_store'; import { + FinalConvertedTweet, NormalizedTweetInfo, TakenTweetsInfo, } from '../../../../../types/tweets'; import { setNormalizedTweets } from '../../../../../lib/set_normalized_tweets'; import { speakMessage } from '../../../../../lib/speech_synthesis'; import { SUCCESS_PROFILE_TWEETS_SPEECH } from '../../../../../constants/speech'; +import { getConvertedTweets } from '../../../../../lib/convert_tweets'; export const $profileNormalizedTweets = createStore( initialStore, ); +export const $profileTweetsForConvert = createStore>( + [], +); const profileChanged = createEvent(); $profileNormalizedTweets.on(profileChanged, setNormalizedTweets); +// @ts-ignore ¯\_(ツ)_/¯ +$profileTweetsForConvert.on(profileChanged, getConvertedTweets); guard({ source: $tweetsMessage, @@ -36,5 +43,6 @@ $isLoadingProfileTweets.on(profileChanged, () => { export const $profileTweets = combine({ tweets: $profileNormalizedTweets, + tweetsToConvert: $profileTweetsForConvert, isLoading: $isLoadingProfileTweets, }); diff --git a/frontend/src/features/twitter/tabs/profile/tweets_and_replies/index.tsx b/frontend/src/features/twitter/tabs/profile/tweets_and_replies/index.tsx index 1e9eae2..2e219e5 100644 --- a/frontend/src/features/twitter/tabs/profile/tweets_and_replies/index.tsx +++ b/frontend/src/features/twitter/tabs/profile/tweets_and_replies/index.tsx @@ -6,11 +6,20 @@ import { $profileTweetsAndReplies } from './model'; import { TweetsInfo } from '../../../tweets_info'; export const ProfileTweetsAndReplies: React.FC = () => { - const { tweets, isLoading } = useStore($profileTweetsAndReplies); + const { tweets, isLoading, tweetsToConvert } = useStore( + $profileTweetsAndReplies, + ); if (isLoading === null) { return null; } - return ; + return ( + + ); }; diff --git a/frontend/src/features/twitter/tabs/profile/tweets_and_replies/model.ts b/frontend/src/features/twitter/tabs/profile/tweets_and_replies/model.ts index 7ed5c74..4804e2f 100644 --- a/frontend/src/features/twitter/tabs/profile/tweets_and_replies/model.ts +++ b/frontend/src/features/twitter/tabs/profile/tweets_and_replies/model.ts @@ -5,16 +5,21 @@ import { $tweetsMessage, sendFx } from '../../../../../socket'; import { TWEETS_REPLIES } from '../../../../../constants/tweets_types'; import { initialStore } from '../../../../../constants/initial_tweets_store'; import { + FinalConvertedTweet, NormalizedTweetInfo, TakenTweetsInfo, } from '../../../../../types/tweets'; import { setNormalizedTweets } from '../../../../../lib/set_normalized_tweets'; import { speakMessage } from '../../../../../lib/speech_synthesis'; import { SUCCESS_TWEETS_AND_REPLIES_SPEECH } from '../../../../../constants/speech'; +import { getConvertedTweets } from '../../../../../lib/convert_tweets'; export const $profileNormalizedTweetsAndReplies = createStore< NormalizedTweetInfo >(initialStore); +export const $repliesTweetsForConvert = createStore>( + [], +); const profileTweetsAndRepliesChanged = createEvent(); @@ -22,6 +27,8 @@ $profileNormalizedTweetsAndReplies.on( profileTweetsAndRepliesChanged, setNormalizedTweets, ); +// @ts-ignore ¯\_(ツ)_/¯ +$repliesTweetsForConvert.on(profileTweetsAndRepliesChanged, getConvertedTweets); guard({ source: $tweetsMessage, @@ -39,5 +46,6 @@ $isLoadingTweetsAndReplies.on(profileTweetsAndRepliesChanged, () => { export const $profileTweetsAndReplies = combine({ tweets: $profileNormalizedTweetsAndReplies, + tweetsToConvert: $repliesTweetsForConvert, isLoading: $isLoadingTweetsAndReplies, }); diff --git a/frontend/src/features/twitter/tabs/search_tweets/latest_tweets/index.tsx b/frontend/src/features/twitter/tabs/search_tweets/latest_tweets/index.tsx index 7150454..7185d56 100644 --- a/frontend/src/features/twitter/tabs/search_tweets/latest_tweets/index.tsx +++ b/frontend/src/features/twitter/tabs/search_tweets/latest_tweets/index.tsx @@ -6,11 +6,18 @@ import { $latestTweets } from './model'; import { TweetsInfo } from '../../../tweets_info'; export const LatestTweets: React.FC = () => { - const { tweets, isLoading } = useStore($latestTweets); + const { tweets, isLoading, tweetsToConvert } = useStore($latestTweets); if (isLoading === null) { return null; } - return ; + return ( + + ); }; diff --git a/frontend/src/features/twitter/tabs/search_tweets/latest_tweets/model.ts b/frontend/src/features/twitter/tabs/search_tweets/latest_tweets/model.ts index cf1697f..bb89188 100644 --- a/frontend/src/features/twitter/tabs/search_tweets/latest_tweets/model.ts +++ b/frontend/src/features/twitter/tabs/search_tweets/latest_tweets/model.ts @@ -1,49 +1,49 @@ -import { Store, createStore, combine } from 'effector'; +import { createStore, combine, guard, createEvent } from 'effector'; import { $tweetsMessage, sendFx } from '../../../../../socket'; import { LATEST_TWEETS } from '../../../../../constants/tweets_types'; -import { getNormalizedTweetAnalyze } from '../../../lib/get_normalized_tweets'; -import { NormalizedTweetInfo } from '../../../../../types/tweets'; +import { + FinalConvertedTweet, + NormalizedTweetInfo, + TakenTweetsInfo, +} from '../../../../../types/tweets'; import { initialStore } from '../../../../../constants/initial_tweets_store'; import { speakMessage } from '../../../../../lib/speech_synthesis'; import { SUCCESS_LATEST_TWEETS_SPEECH } from '../../../../../constants/speech'; +import { setNormalizedTweets } from '../../../../../lib/set_normalized_tweets'; +import { getConvertedTweets } from '../../../../../lib/convert_tweets'; -const $normalizedTweets: Store = $tweetsMessage.map( - ( - { finalTweets, meanSentiment, minCoefficient, maxCoefficient, tweetsType }, - lastState = initialStore, - ) => { - if (tweetsType !== LATEST_TWEETS) { - return lastState; - } - - const normalizedTweets = { - finalTweets, - ...getNormalizedTweetAnalyze({ - maxCoefficient, - minCoefficient, - meanSentiment, - }), - }; - - return normalizedTweets; - }, +export const $normalizedLatestTweets = createStore( + initialStore, ); +export const $latestTweetsForConvert = createStore>( + [], +); + +const latestChanged = createEvent(); + +$normalizedLatestTweets.on(latestChanged, setNormalizedTweets); +// @ts-ignore ¯\_(ツ)_/¯ +$latestTweetsForConvert.on(latestChanged, getConvertedTweets); + +guard({ + source: $tweetsMessage, + filter: message => message.tweetsType === LATEST_TWEETS, + target: latestChanged, +}); + export const $isLoadingLatestTweets = createStore(null); $isLoadingLatestTweets.on(sendFx, () => true); -$isLoadingLatestTweets.on($tweetsMessage, (defaultState, { tweetsType }) => { - if (tweetsType === LATEST_TWEETS) { - speakMessage(SUCCESS_LATEST_TWEETS_SPEECH); - return false; - } - - return defaultState; +$isLoadingLatestTweets.on(latestChanged, () => { + speakMessage(SUCCESS_LATEST_TWEETS_SPEECH); + return false; }); export const $latestTweets = combine({ - tweets: $normalizedTweets, + tweets: $normalizedLatestTweets, + tweetsToConvert: $latestTweetsForConvert, isLoading: $isLoadingLatestTweets, }); diff --git a/frontend/src/features/twitter/tabs/search_tweets/top_tweets/index.tsx b/frontend/src/features/twitter/tabs/search_tweets/top_tweets/index.tsx index 913df7d..d7710a5 100644 --- a/frontend/src/features/twitter/tabs/search_tweets/top_tweets/index.tsx +++ b/frontend/src/features/twitter/tabs/search_tweets/top_tweets/index.tsx @@ -6,11 +6,18 @@ import { $topTweets } from './model'; import { TweetsInfo } from '../../../tweets_info'; export const TopTweets: React.FC = () => { - const { tweets, isLoading } = useStore($topTweets); + const { tweets, isLoading, tweetsToConvert } = useStore($topTweets); if (isLoading === null) { return null; } - return ; + return ( + + ); }; diff --git a/frontend/src/features/twitter/tabs/search_tweets/top_tweets/model.ts b/frontend/src/features/twitter/tabs/search_tweets/top_tweets/model.ts index c87257a..748649d 100644 --- a/frontend/src/features/twitter/tabs/search_tweets/top_tweets/model.ts +++ b/frontend/src/features/twitter/tabs/search_tweets/top_tweets/model.ts @@ -1,47 +1,45 @@ -import { Store, createStore, combine } from 'effector'; +import { createStore, combine, createEvent, guard } from 'effector'; import { $tweetsMessage, sendFx } from '../../../../../socket'; import { TOP_TWEETS } from '../../../../../constants/tweets_types'; -import { getNormalizedTweetAnalyze } from '../../../lib/get_normalized_tweets'; import { initialStore } from '../../../../../constants/initial_tweets_store'; -import { NormalizedTweetInfo } from '../../../../../types/tweets'; +import { + FinalConvertedTweet, + NormalizedTweetInfo, + TakenTweetsInfo, +} from '../../../../../types/tweets'; import { speakMessage } from '../../../../../lib/speech_synthesis'; import { SUCCESS_TOP_TWEETS_SPEECH } from '../../../../../constants/speech'; +import { setNormalizedTweets } from '../../../../../lib/set_normalized_tweets'; +import { getConvertedTweets } from '../../../../../lib/convert_tweets'; -const $normalizedTweets: Store = $tweetsMessage.map( - ( - { finalTweets, meanSentiment, minCoefficient, maxCoefficient, tweetsType }, - lastState = initialStore, - ) => { - if (tweetsType !== TOP_TWEETS) { - return lastState; - } - - const normalizedTweets = { - finalTweets, - ...getNormalizedTweetAnalyze({ - maxCoefficient, - minCoefficient, - meanSentiment, - }), - }; - - return normalizedTweets; - }, +export const $normalizedTopTweets = createStore( + initialStore, ); +export const $topTweetsForConvert = createStore>([]); + +const topChanged = createEvent(); + +$normalizedTopTweets.on(topChanged, setNormalizedTweets); +// @ts-ignore ¯\_(ツ)_/¯ +$topTweetsForConvert.on(topChanged, getConvertedTweets); + +guard({ + source: $tweetsMessage, + filter: message => message.tweetsType === TOP_TWEETS, + target: topChanged, +}); + export const $isLoadingTopTweets = createStore(null); $isLoadingTopTweets.on(sendFx, () => true); -$isLoadingTopTweets.on($tweetsMessage, (defaultState, { tweetsType }) => { - if (tweetsType === TOP_TWEETS) { - speakMessage(SUCCESS_TOP_TWEETS_SPEECH); - return false; - } - - return defaultState; +$isLoadingTopTweets.on(topChanged, () => { + speakMessage(SUCCESS_TOP_TWEETS_SPEECH); + return false; }); export const $topTweets = combine({ - tweets: $normalizedTweets, + tweets: $normalizedTopTweets, + tweetsToConvert: $topTweetsForConvert, isLoading: $isLoadingTopTweets, }); diff --git a/frontend/src/features/twitter/tweets_info/index.tsx b/frontend/src/features/twitter/tweets_info/index.tsx index aeff90e..f90e129 100644 --- a/frontend/src/features/twitter/tweets_info/index.tsx +++ b/frontend/src/features/twitter/tweets_info/index.tsx @@ -11,7 +11,11 @@ import { Tweet } from '../tweet'; import { TweetSkeleton } from '../../../UI/skeleton/tweet'; import { TweetsSkeleton } from '../../../UI/skeleton/tweets'; -import { NormalizedTweetInfo } from '../../../types/tweets'; +import { + FinalConvertedTweet, + NormalizedTweetInfo, +} from '../../../types/tweets'; +import { CsvConvert } from '../../csv_convert'; const ParseInfoContainer = styled.div` display: grid; @@ -30,9 +34,15 @@ const TweetsWrapper = styled(Wrapper)` padding: 0 16px; `; +const ConvertContainer = styled(Grid)` + margin-bottom: 10px; +`; + export const TweetsInfo: React.FC<{ infoOptions: NormalizedTweetInfo; + tweetsToConvert: Array; isLoading: boolean; + convertText: string; }> = ({ infoOptions: { finalTweets, @@ -44,6 +54,8 @@ export const TweetsInfo: React.FC<{ isMeanSentimentExist, }, isLoading, + convertText, + tweetsToConvert, }) => { return ( @@ -61,6 +73,9 @@ export const TweetsInfo: React.FC<{ )} {!isLoading && ( <> + + + {isMeanSentimentExist && ( diff --git a/frontend/src/lib/blob_support/index.ts b/frontend/src/lib/blob_support/index.ts new file mode 100644 index 0000000..1a2f26a --- /dev/null +++ b/frontend/src/lib/blob_support/index.ts @@ -0,0 +1,4 @@ +export const checkIsBrowserNotSupportBlob = () => + window.Blob === undefined || + window.URL === undefined || + window.URL.createObjectURL === undefined; diff --git a/frontend/src/lib/convert_tweets/index.ts b/frontend/src/lib/convert_tweets/index.ts new file mode 100644 index 0000000..f46a1a9 --- /dev/null +++ b/frontend/src/lib/convert_tweets/index.ts @@ -0,0 +1,29 @@ +import { + FinalConvertedTweet, + FinalTweet, + TakenTweetsInfo, +} from '../../types/tweets'; + +export const getConvertedTweets = ( + _: Array, + { finalTweets }: TakenTweetsInfo, +): Array => { + const normalizedTweets = finalTweets.map((tweet: FinalTweet) => { + const normalizedReplies = + tweet.replyingUsers.length > 0 + ? tweet.replyingUsers + // @ts-ignore ¯\_(ツ)_/¯ + .map( + ({ user, userLink }: { user: string; userLink: string }) => + `${user} ${userLink}`, + ) + .join(', ') + : ''; + return { + ...tweet, + replyingUsers: normalizedReplies, + }; + }); + + return normalizedTweets; +}; diff --git a/frontend/src/lib/date/index.ts b/frontend/src/lib/date/index.ts new file mode 100644 index 0000000..d3ef409 --- /dev/null +++ b/frontend/src/lib/date/index.ts @@ -0,0 +1,7 @@ +export const getCurrentDate = () => { + const date = new Date(); + const currentDate = `${date.getFullYear()}_${date.getMonth() + + 1}_${date.getDay()}`; + + return currentDate; +}; diff --git a/frontend/src/lib/download_csv/index.ts b/frontend/src/lib/download_csv/index.ts new file mode 100644 index 0000000..d057eac --- /dev/null +++ b/frontend/src/lib/download_csv/index.ts @@ -0,0 +1,10 @@ +export const downloadCsv = (blobParts: string, filename: string) => { + const csvFile = new Blob([blobParts], { type: 'text/csv' }); + const downloadLink = document.createElement('a'); + downloadLink.download = filename; + downloadLink.href = window.URL.createObjectURL(csvFile); + downloadLink.style.display = 'none'; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); +}; diff --git a/frontend/src/mocks/final_tweets.ts b/frontend/src/mocks/final_tweets.ts index 488c0ea..1d21941 100644 --- a/frontend/src/mocks/final_tweets.ts +++ b/frontend/src/mocks/final_tweets.ts @@ -1,4 +1,4 @@ -export const finalTweets = [ +export const finalTweetsMock = [ { userUrl: 'https://twitter.com/_harrisonJNR', name: 'HARRISON ❁', diff --git a/frontend/src/types/tweets.ts b/frontend/src/types/tweets.ts index 3ca647f..f65096b 100644 --- a/frontend/src/types/tweets.ts +++ b/frontend/src/types/tweets.ts @@ -26,7 +26,21 @@ export type FinalTweet = { likes: number; retweets: number; replies: number; - replyingUsers?: Array<{ user: string; userLink: string; id: string }>; + replyingUsers: Array<{ user: string; userLink: string; id: string }> | []; + tweetSentiment: number; + tweetBayes: string; +}; + +export type FinalConvertedTweet = { + id: string; + userUrl: string; + name: string; + tweetName: string; + tweetContent: string; + likes: number; + retweets: number; + replies: number; + replyingUsers: string; tweetSentiment: number; tweetBayes: string; };