diff --git a/.vscode/settings.json b/.vscode/settings.json index 600b409..318cb70 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,5 @@ }, "go.testFlags": ["-v"], "go.testTimeout": "100s", - "debug.node.autoAttach": "off" + "debug.node.autoAttach": "on" } diff --git a/angular.json b/angular.json index 6fd5534..ab0f452 100644 --- a/angular.json +++ b/angular.json @@ -37,6 +37,10 @@ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" + }, + { + "replace": "src/environments/native/native.ts", + "with": "src/environments/native/native.browser.prod.ts" } ], "optimization": true, @@ -51,7 +55,7 @@ { "type": "initial", "maximumWarning": "2mb", - "maximumError": "5mb" + "maximumError": "6mb" }, { "type": "anyComponentStyle", @@ -60,16 +64,19 @@ } ] }, - "production-es5": { + "production-renderer": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" + }, + { + "replace": "src/environments/native/native.ts", + "with": "src/environments/native/native.renderer.prod.ts" } ], - "es5BrowserSupport": true, - "outputPath": "dist/ucap-lg-web-es5", - "tsConfig": "tsconfig.app.es5.json", + "outputPath": "dist/ucap-lg-renderer", + "tsConfig": "tsconfig.app.renderer.json", "optimization": true, "outputHashing": "all", "sourceMap": false, @@ -82,7 +89,7 @@ { "type": "initial", "maximumWarning": "2mb", - "maximumError": "5mb" + "maximumError": "6mb" }, { "type": "anyComponentStyle", @@ -96,18 +103,26 @@ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.hmr.ts" + }, + { + "replace": "src/environments/native/native.ts", + "with": "src/environments/native/native.browser.ts" } ] }, - "hmr-es5": { + "hmr-renderer": { "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.hmr.ts" + }, + { + "replace": "src/environments/native/native.ts", + "with": "src/environments/native/native.renderer.ts" } ], - "es5BrowserSupport": true, - "tsConfig": "tsconfig.app.es5.json" + "es5BrowserSupport": false, + "tsConfig": "tsconfig.app.renderer.json" } } }, @@ -121,16 +136,16 @@ "production": { "browserTarget": "ucap-lg-web:build:production" }, - "production-5": { - "browserTarget": "ucap-lg-web:build:production-es5" + "production-renderer": { + "browserTarget": "ucap-lg-web:build:production-renderer" }, "hmr": { "hmr": true, "browserTarget": "ucap-lg-web:build:hmr" }, - "hmr-es5": { + "hmr-renderer": { "hmr": true, - "browserTarget": "ucap-lg-web:build:hmr-es5" + "browserTarget": "ucap-lg-web:build:hmr-renderer" } } }, diff --git a/config/custom.webpack.config.js b/config/custom.webpack.config.js index e6d19be..78356a9 100644 --- a/config/custom.webpack.config.js +++ b/config/custom.webpack.config.js @@ -1,13 +1,20 @@ const path = require('path'); module.exports = (config, options) => { - const PRODUCTION = process.env.NODE_ENV === 'production'; + const PRODUCTION = + !!process.env.NODE_ENV && 'production' === process.env.NODE_ENV; + const RENDERER = + !!process.env.NATIVE_ENV && 'renderer' === process.env.NATIVE_ENV; - config.target = 'web'; - config.node = { - global: true, - fs: 'empty' - }; + if (RENDERER) { + config.target = 'electron-renderer'; + } else { + config.target = 'web'; + config.node = { + global: true, + fs: 'empty' + }; + } config.resolve.alias = { ...config.resolve.alias, diff --git a/package-lock.json b/package-lock.json index b161522..9d4c475 100644 --- a/package-lock.json +++ b/package-lock.json @@ -348,66 +348,33 @@ } }, "@angular-devkit/schematics": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/schematics/-/schematics-9.0.7.tgz", - "integrity": "sha512-ryPC+l24f3gX5DFMTLkDM/q2Kp6LPzBn6400k7j4qVdb1cIrZx+JUQd7F4iAksTTkX15EQPanptQXeztUrl9Ng==", + "version": "9.1.11", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/schematics/-/schematics-9.1.11.tgz", + "integrity": "sha512-1A3Oryhl8hpibJK2J5j2FYNzjfvBJcR4wuNRKzl27kBvVsdRXLQzMD3aAgqFvlMgUWhloQs4tZwuinu0E2VP1A==", "dev": true, "requires": { - "@angular-devkit/core": "9.0.7", - "ora": "4.0.2", - "rxjs": "6.5.3" + "@angular-devkit/core": "9.1.11", + "ora": "4.0.3", + "rxjs": "6.5.4" }, "dependencies": { "@angular-devkit/core": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/core/-/core-9.0.7.tgz", - "integrity": "sha512-tMrz36sM1xrwvFf9Qm59GwALscVlMP7rQBjtd0fIR/QbsiOAIX4AQbV+vN6Vtwnzo5NIRZY1IXJUhesWms+h5w==", + "version": "9.1.11", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/core/-/core-9.1.11.tgz", + "integrity": "sha512-uiEkDvWfMgPHuO4jVgBEr9Kl/LuxHaWYGD2ZtKsOnnHYZyRGp61ot7UcDF+KNdXTiq01JJH84VTd3IttEewmhQ==", "dev": true, "requires": { - "ajv": "6.10.2", - "fast-json-stable-stringify": "2.0.0", - "magic-string": "0.25.4", - "rxjs": "6.5.3", + "ajv": "6.12.0", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.5.4", "source-map": "0.7.3" } }, - "ajv": { - "version": "6.10.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://nexus.loafle.net/repository/npm-all/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "magic-string": { - "version": "0.25.4", - "resolved": "https://nexus.loafle.net/repository/npm-all/magic-string/-/magic-string-0.25.4.tgz", - "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, "rxjs": { - "version": "6.5.3", - "resolved": "https://nexus.loafle.net/repository/npm-all/rxjs/-/rxjs-6.5.3.tgz", - "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "version": "6.5.4", + "resolved": "https://nexus.loafle.net/repository/npm-all/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -416,9 +383,9 @@ } }, "@angular/animations": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/animations/-/animations-9.0.7.tgz", - "integrity": "sha512-74gY7onajmmnksy5E0/32bFv3B9NuWxV64kqD15YjGrh8AWe1BHt5enQI+rJ2tO8m2DKnwZsctis6k0Kcy+YKQ==" + "version": "9.1.12", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/animations/-/animations-9.1.12.tgz", + "integrity": "sha512-tphpf9QHnOPoL2Jl7KpR+R5aHNW3oifLEmRUTajJYJGvo1uzdUDE82+V9OGOinxJsYseCth9gYJhN24aYTB9NA==" }, "@angular/cdk": { "version": "9.2.4", @@ -429,68 +396,56 @@ } }, "@angular/cli": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/cli/-/cli-9.0.7.tgz", - "integrity": "sha512-/9CUNSSVyTtTNUADZ/VXJDEdhineMN/rfd35w6VsHiob49tKkeOTggaoiSne3RY4VCTqlo7GGf4KhhVXEMGnDQ==", + "version": "9.1.11", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/cli/-/cli-9.1.11.tgz", + "integrity": "sha512-EGDd9ZCGitn6F/x5sK7M1PmdfBg+8LoZ6nrfYEhWY8c2CMCVfVrLPLHq6r8uFiflcvd/nlcrVZQ52zt4a2vDaQ==", "dev": true, "requires": { - "@angular-devkit/architect": "0.900.7", - "@angular-devkit/core": "9.0.7", - "@angular-devkit/schematics": "9.0.7", - "@schematics/angular": "9.0.7", - "@schematics/update": "0.900.7", + "@angular-devkit/architect": "0.901.11", + "@angular-devkit/core": "9.1.11", + "@angular-devkit/schematics": "9.1.11", + "@schematics/angular": "9.1.11", + "@schematics/update": "0.901.11", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", - "debug": "^4.1.1", + "debug": "4.1.1", "ini": "1.3.5", - "inquirer": "7.0.0", - "npm-package-arg": "6.1.1", - "npm-pick-manifest": "3.0.2", - "open": "7.0.0", - "pacote": "9.5.8", + "inquirer": "7.1.0", + "npm-package-arg": "8.0.1", + "npm-pick-manifest": "6.0.0", + "open": "7.0.3", + "pacote": "9.5.12", "read-package-tree": "5.3.1", - "rimraf": "3.0.0", - "semver": "6.3.0", + "rimraf": "3.0.2", + "semver": "7.1.3", "symbol-observable": "1.2.0", - "universal-analytics": "^0.4.20", - "uuid": "^3.3.2" + "universal-analytics": "0.4.20", + "uuid": "7.0.2" }, "dependencies": { "@angular-devkit/architect": { - "version": "0.900.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/architect/-/architect-0.900.7.tgz", - "integrity": "sha512-hfiTVYc72kzbXrzK4tea6jnTDnSKpE1D+vEptBXN2tdXEVNEAQI5Qm5L1zVDtt16UdqoUTUypIgUc9jcNH1mUQ==", + "version": "0.901.11", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/architect/-/architect-0.901.11.tgz", + "integrity": "sha512-RmYOq1VEJdQLzwMno+C56WtgscAtoR/7i4tX5b5VxRa2RmQKTxowllYWwgrF5445VGUqzap9H6zJFXvlY2FA0w==", "dev": true, "requires": { - "@angular-devkit/core": "9.0.7", - "rxjs": "6.5.3" + "@angular-devkit/core": "9.1.11", + "rxjs": "6.5.4" } }, "@angular-devkit/core": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/core/-/core-9.0.7.tgz", - "integrity": "sha512-tMrz36sM1xrwvFf9Qm59GwALscVlMP7rQBjtd0fIR/QbsiOAIX4AQbV+vN6Vtwnzo5NIRZY1IXJUhesWms+h5w==", + "version": "9.1.11", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/core/-/core-9.1.11.tgz", + "integrity": "sha512-uiEkDvWfMgPHuO4jVgBEr9Kl/LuxHaWYGD2ZtKsOnnHYZyRGp61ot7UcDF+KNdXTiq01JJH84VTd3IttEewmhQ==", "dev": true, "requires": { - "ajv": "6.10.2", - "fast-json-stable-stringify": "2.0.0", - "magic-string": "0.25.4", - "rxjs": "6.5.3", + "ajv": "6.12.0", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.5.4", "source-map": "0.7.3" } }, - "ajv": { - "version": "6.10.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://nexus.loafle.net/repository/npm-all/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -506,58 +461,68 @@ "ms": "^2.1.1" } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://nexus.loafle.net/repository/npm-all/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "magic-string": { - "version": "0.25.4", - "resolved": "https://nexus.loafle.net/repository/npm-all/magic-string/-/magic-string-0.25.4.tgz", - "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, "ms": { "version": "2.1.2", "resolved": "https://nexus.loafle.net/repository/npm-all/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "open": { + "version": "7.0.3", + "resolved": "https://nexus.loafle.net/repository/npm-all/open/-/open-7.0.3.tgz", + "integrity": "sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA==", + "dev": true, + "requires": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://nexus.loafle.net/repository/npm-all/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "rxjs": { - "version": "6.5.3", - "resolved": "https://nexus.loafle.net/repository/npm-all/rxjs/-/rxjs-6.5.3.tgz", - "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "version": "6.5.4", + "resolved": "https://nexus.loafle.net/repository/npm-all/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", "dev": true, "requires": { "tslib": "^1.9.0" } + }, + "semver": { + "version": "7.1.3", + "resolved": "https://nexus.loafle.net/repository/npm-all/semver/-/semver-7.1.3.tgz", + "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "dev": true + }, + "uuid": { + "version": "7.0.2", + "resolved": "https://nexus.loafle.net/repository/npm-all/uuid/-/uuid-7.0.2.tgz", + "integrity": "sha512-vy9V/+pKG+5ZTYKf+VcphF5Oc6EFiu3W8Nv3P3zIh0EqVI80ZxOzuPfe9EHjkFNvf8+xuTHVeei4Drydlx4zjw==", + "dev": true } } }, "@angular/common": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/common/-/common-9.0.7.tgz", - "integrity": "sha512-B58YgxZva1DBaeayOBsaUOOkoyR+GRibuNC3gfOMm2vXeW9eCNX+jvDtw767GnKm2yGzIq8wB3x6GHojN00dPw==" + "version": "9.1.12", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/common/-/common-9.1.12.tgz", + "integrity": "sha512-XSIqkbM6VV1yixF9zuzeE5eqN1VsiXS517K2VU0XgCRSAzhVhLOeKsdYjeLf7PdSu/HgW/Tr81H+isi9A9I0YA==" }, "@angular/compiler": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/compiler/-/compiler-9.0.7.tgz", - "integrity": "sha512-hFpkuGpzxpK5h59LHHAjTFWsY6DCXZwgJFqvCuTPxWi/srvLGZRXrpC6Z1SlgHI9xxXaPfoa4uWw2VfA3BnqEg==" + "version": "9.1.12", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/compiler/-/compiler-9.1.12.tgz", + "integrity": "sha512-suefk0OFkaJpUUKnV+phbL4T8fmVGHvzkereY5eqybQlumOez8NPL1PJcygAylh/E6OIAYm8SWookYwM6ZY9dg==" }, "@angular/compiler-cli": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/compiler-cli/-/compiler-cli-9.0.7.tgz", - "integrity": "sha512-+RXghex63v0Vi8vpQtDpWiqpAAnrTaN3bHT5fntRenq5+Ok5vL1MJ1mzbTmBXs2tuwTqNlwMm2AlZB7G/xcDMQ==", + "version": "9.1.12", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/compiler-cli/-/compiler-cli-9.1.12.tgz", + "integrity": "sha512-bbqJ+fbY+aQejSYuHUjE1qYJCXkZBM5Hru9eN7m/j376u83MQ5jWdC290uYx+ipsXcPTa/YRZ44jpL+5cCzIrg==", "dev": true, "requires": { "canonical-path": "1.0.0", @@ -571,28 +536,59 @@ "semver": "^6.3.0", "source-map": "^0.6.1", "sourcemap-codec": "^1.4.8", - "yargs": "13.1.0" + "yargs": "15.3.0" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "5.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://nexus.loafle.net/repository/npm-all/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://nexus.loafle.net/repository/npm-all/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "find-up": { - "version": "3.0.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "version": "4.1.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "fs-extra": { @@ -612,14 +608,19 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "locate-path": { + "is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "resolved": "https://nexus.loafle.net/repository/npm-all/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, "p-limit": { @@ -632,12 +633,12 @@ } }, "p-locate": { - "version": "3.0.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "version": "4.1.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "p-limit": "^2.2.0" } }, "p-try": { @@ -646,6 +647,12 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://nexus.loafle.net/repository/npm-all/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -659,48 +666,59 @@ "dev": true }, "string-width": { - "version": "3.1.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, "yargs": { - "version": "13.1.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/yargs/-/yargs-13.1.0.tgz", - "integrity": "sha512-1UhJbXfzHiPqkfXNHYhiz79qM/kZqjTE8yGlEjZa85Q+3+OwcV6NRkV7XOV1W2Eom2bzILeUn55pQYffjVOLAg==", + "version": "15.3.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/yargs/-/yargs-15.3.0.tgz", + "integrity": "sha512-g/QCnmjgOl1YJjGsnUg2SatC7NUYEiLXJqxNOQU9qSpjzGtGXda9b+OKccr1kLTy8BN9yqEyqfq5lxlwdc13TA==", "dev": true, "requires": { - "cliui": "^4.0.0", - "find-up": "^3.0.0", + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^3.0.0", + "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" + "yargs-parser": "^18.1.0" } }, "yargs-parser": { - "version": "13.1.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "version": "18.1.3", + "resolved": "https://nexus.loafle.net/repository/npm-all/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -710,9 +728,9 @@ } }, "@angular/core": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/core/-/core-9.0.7.tgz", - "integrity": "sha512-E9XZH5Dl+9MWG3MDC6wrKllhA8Rljpz66HOIeqKv2fHPed8kzuJZU3WJWLtbhDAXFwtGTyTZ4c82ZLSmqwTorg==" + "version": "9.1.12", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/core/-/core-9.1.12.tgz", + "integrity": "sha512-WVA/eh3fzjx0apOzkKot4YRRUsGkHj50zFQWrAOMgivGaj1YVrvhf+m3hpglj5fn/BkLiFDl8RT0wAE8z9X+gQ==" }, "@angular/flex-layout": { "version": "9.0.0-beta.31", @@ -720,14 +738,14 @@ "integrity": "sha512-g94u2mecDl87ORvFRuOBshV/S/ETE4bybClU2e1xXKWNG+rhRHchChneHSonc29ZLyROTjHhmAtKOYojL92uLA==" }, "@angular/forms": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/forms/-/forms-9.0.7.tgz", - "integrity": "sha512-PaHAmjMJDtg/3aGCPuq5BCRC1eZ/DBCpva9f7NrA1kqk0LcLdebm0v2uHwTOBtiz/VEgPvxiS4tXC4rjvUtfEg==" + "version": "9.1.12", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/forms/-/forms-9.1.12.tgz", + "integrity": "sha512-LhjnZlC4WEsEsAJfOZLte+Lks3WBAFVeRv2lzoQNFVr/IMzBNDVfjEaaSqKF1cei3cjY39Df2nYDMJM7HfqbJA==" }, "@angular/language-service": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/language-service/-/language-service-9.0.7.tgz", - "integrity": "sha512-IZG1kvw48JyFRy7bfMHqBixWrEHZmXmkP5DWsi5Tw6KusaczkMghI20BevCkodPcajXWHAUHNKyp1tlE3OnH0w==", + "version": "9.1.12", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/language-service/-/language-service-9.1.12.tgz", + "integrity": "sha512-0qfIAn5fP5lD+JW6il5HBHGS89rv+idRv5aooDkHqBhuBo4V2VuB1wNy5eP49GZbHKMW1xPAzv1MqeMdk+zwQA==", "dev": true }, "@angular/material": { @@ -741,19 +759,19 @@ "integrity": "sha512-V5xkL+YUec3nDGRaJB72mJTUtdUvGaG9WCQEdr45viDWFGjQaEpS6msuScBLp0PwsN8Wt0n69eZg0ULgxPBa5g==" }, "@angular/platform-browser": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/platform-browser/-/platform-browser-9.0.7.tgz", - "integrity": "sha512-Por8omrEiSV2U/K2mm/Kuv+2R2rJkbAZ3ctEM6CWj9Y4Gz2akjOCxmEgWhhBeqdigcC3T1v707f52osf9jWBkg==" + "version": "9.1.12", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/platform-browser/-/platform-browser-9.1.12.tgz", + "integrity": "sha512-rPa/hJcLfdId6bYB0b6pFUo3QIgjZlvUlmtKMGdrLNLYR8XQxPa2Y/UdN/5YeZ12htGw6GXrX9U8U7nTbUSpkw==" }, "@angular/platform-browser-dynamic": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.0.7.tgz", - "integrity": "sha512-jwpyd93ofcRtchbayKD5v4GN4Lc7vbPe6dMUiwfnVnVAql0bOD/3YRI7w5qJ0Xx0sgQT+9Xo6jTXYnyUsZpEww==" + "version": "9.1.12", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.1.12.tgz", + "integrity": "sha512-NmwUZaQeMnA6f+vP9Fp9P+qjL72H8dKlxLS76ujlKHVf75pP5oahWS8wfl7KXel1tKW3FQWMMffmKf5/NHRiSw==" }, "@angular/router": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/router/-/router-9.0.7.tgz", - "integrity": "sha512-uKru9F/Zju//gg6INl54abnlpLdEUUO/GpCfMk4zqu8LCZGNFta6OY7VT+9DK9Vdrh/XUD70oE9WoelcRwwTYA==" + "version": "9.1.12", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular/router/-/router-9.1.12.tgz", + "integrity": "sha512-+qCaXa9y0nsRhzjAYBqmGoQ2YkrdXgftZwuFDf6t4qEi30EXa0oS97KrlFq0M5GKdLIDGrbUm9PcdHSTOI+ZhA==" }, "@angularclass/hmr": { "version": "2.1.3", @@ -1655,24 +1673,24 @@ "dev": true }, "@ngrx/effects": { - "version": "9.1.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ngrx/effects/-/effects-9.1.2.tgz", - "integrity": "sha512-H9jbGUzP5izk9Ap8BQJicO1+xheyDyHBbvv6b1NkaRHpDizhPOSBjoFWExFfsejXo0dafaIsu6aI+y+Fp+LSsg==" + "version": "9.2.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ngrx/effects/-/effects-9.2.0.tgz", + "integrity": "sha512-8V09zDIPehGpzgfcgyczelovsVYJvDQhN9wHt37K5A+YCG0CI8nj8FmKokHATwv/S62YqFrOVnr/TZacxpDhBw==" }, "@ngrx/entity": { - "version": "9.1.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ngrx/entity/-/entity-9.1.2.tgz", - "integrity": "sha512-V6sN/W2rLZFFMlzML3AcHYrA6h9mRMpSVqpsMF4ZLhubmQWP4BG24vyJX8ExtH46OU7ick8aiYqrNzF4rJMiag==" + "version": "9.2.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ngrx/entity/-/entity-9.2.0.tgz", + "integrity": "sha512-xSnS4EmksfvIobl2KMpljE1RMYuJGq7j5cCb9TnlsXkEc7cUa0TyGviSsxceSpk9WKtKARPR/AcVrVCESucF6Q==" }, "@ngrx/router-store": { - "version": "9.1.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ngrx/router-store/-/router-store-9.1.2.tgz", - "integrity": "sha512-zqAm7fOdJ34dY2Tlts2YV9MDcDP2CqB+hLEytjo/bOIkUo/lQA1R5VAUI+Z8+tUlmPqSVIJkzsGuCxO6XnLEjQ==" + "version": "9.2.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ngrx/router-store/-/router-store-9.2.0.tgz", + "integrity": "sha512-thu6aU9YWM64oNEk4Srx/mNSeQ2SPJKlTji8MSzfr06qgCMyPSXZBYlfs8HqY+af3eB7XBEhb/4ew4JJ6xC9zw==" }, "@ngrx/store": { - "version": "9.1.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ngrx/store/-/store-9.1.2.tgz", - "integrity": "sha512-FQXFonF8hSGJDqgLaoWHy2mkeJwVdoa3jLoT1YpkJWxsFMG4U0T6JYG4VrtuymDgo9XwWBBJqIiNpdTgHhofSQ==" + "version": "9.2.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ngrx/store/-/store-9.2.0.tgz", + "integrity": "sha512-V8AI3mxbMztVpbZpALkLZYlGkofKcu9GaOCY5e+sZ1VcJ90oxhFjBpnmd6MuVdmhep1XAHALb1B8ZbBFn+xsgQ==" }, "@ngrx/store-devtools": { "version": "9.1.2", @@ -1750,65 +1768,32 @@ } }, "@schematics/angular": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@schematics/angular/-/angular-9.0.7.tgz", - "integrity": "sha512-3UCeexYx/YVo3kboyPZ8KgqBTduMA18AAm3s2yrC0qj41fBFVVZAZLa74uouTf4RYVgy9kR7J3uv6VLxrJPOnQ==", + "version": "9.1.11", + "resolved": "https://nexus.loafle.net/repository/npm-all/@schematics/angular/-/angular-9.1.11.tgz", + "integrity": "sha512-nExcWmsQvcj9IrofXuHmXmvmehD36O93uFR/a2LisYxao7j6Pe31Qs4Xzk6K67Kxpbypicqr8wAN3LtCOuy20A==", "dev": true, "requires": { - "@angular-devkit/core": "9.0.7", - "@angular-devkit/schematics": "9.0.7" + "@angular-devkit/core": "9.1.11", + "@angular-devkit/schematics": "9.1.11" }, "dependencies": { "@angular-devkit/core": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/core/-/core-9.0.7.tgz", - "integrity": "sha512-tMrz36sM1xrwvFf9Qm59GwALscVlMP7rQBjtd0fIR/QbsiOAIX4AQbV+vN6Vtwnzo5NIRZY1IXJUhesWms+h5w==", + "version": "9.1.11", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/core/-/core-9.1.11.tgz", + "integrity": "sha512-uiEkDvWfMgPHuO4jVgBEr9Kl/LuxHaWYGD2ZtKsOnnHYZyRGp61ot7UcDF+KNdXTiq01JJH84VTd3IttEewmhQ==", "dev": true, "requires": { - "ajv": "6.10.2", - "fast-json-stable-stringify": "2.0.0", - "magic-string": "0.25.4", - "rxjs": "6.5.3", + "ajv": "6.12.0", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.5.4", "source-map": "0.7.3" } }, - "ajv": { - "version": "6.10.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://nexus.loafle.net/repository/npm-all/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "magic-string": { - "version": "0.25.4", - "resolved": "https://nexus.loafle.net/repository/npm-all/magic-string/-/magic-string-0.25.4.tgz", - "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, "rxjs": { - "version": "6.5.3", - "resolved": "https://nexus.loafle.net/repository/npm-all/rxjs/-/rxjs-6.5.3.tgz", - "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "version": "6.5.4", + "resolved": "https://nexus.loafle.net/repository/npm-all/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -1817,96 +1802,49 @@ } }, "@schematics/update": { - "version": "0.900.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@schematics/update/-/update-0.900.7.tgz", - "integrity": "sha512-e9tX2DGNYfj/k9mVICpQt2bWIYyD92dlsip7LzPeZGt+R9zCp5w19uBLa8Z00OgEGzFR1krhRvkQE5OxkkAnVw==", + "version": "0.901.11", + "resolved": "https://nexus.loafle.net/repository/npm-all/@schematics/update/-/update-0.901.11.tgz", + "integrity": "sha512-btqcX1dFL0R+KrTCDu/W+jF19gmoXEC73HWvbhJhU5CvpOF4RC51msjAs9u3J45KyWC2RoYdQCxuasKQZ962lw==", "dev": true, "requires": { - "@angular-devkit/core": "9.0.7", - "@angular-devkit/schematics": "9.0.7", + "@angular-devkit/core": "9.1.11", + "@angular-devkit/schematics": "9.1.11", "@yarnpkg/lockfile": "1.1.0", "ini": "1.3.5", - "npm-package-arg": "^7.0.0", - "pacote": "9.5.8", - "rxjs": "6.5.3", - "semver": "6.3.0", + "npm-package-arg": "^8.0.0", + "pacote": "9.5.12", + "rxjs": "6.5.4", + "semver": "7.1.3", "semver-intersect": "1.4.0" }, "dependencies": { "@angular-devkit/core": { - "version": "9.0.7", - "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/core/-/core-9.0.7.tgz", - "integrity": "sha512-tMrz36sM1xrwvFf9Qm59GwALscVlMP7rQBjtd0fIR/QbsiOAIX4AQbV+vN6Vtwnzo5NIRZY1IXJUhesWms+h5w==", + "version": "9.1.11", + "resolved": "https://nexus.loafle.net/repository/npm-all/@angular-devkit/core/-/core-9.1.11.tgz", + "integrity": "sha512-uiEkDvWfMgPHuO4jVgBEr9Kl/LuxHaWYGD2ZtKsOnnHYZyRGp61ot7UcDF+KNdXTiq01JJH84VTd3IttEewmhQ==", "dev": true, "requires": { - "ajv": "6.10.2", - "fast-json-stable-stringify": "2.0.0", - "magic-string": "0.25.4", - "rxjs": "6.5.3", + "ajv": "6.12.0", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.5.4", "source-map": "0.7.3" } }, - "ajv": { - "version": "6.10.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://nexus.loafle.net/repository/npm-all/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "magic-string": { - "version": "0.25.4", - "resolved": "https://nexus.loafle.net/repository/npm-all/magic-string/-/magic-string-0.25.4.tgz", - "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, - "npm-package-arg": { - "version": "7.0.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/npm-package-arg/-/npm-package-arg-7.0.0.tgz", - "integrity": "sha512-xXxr8y5U0kl8dVkz2oK7yZjPBvqM2fwaO5l3Yg13p03v8+E3qQcD0JNhHzjL1vyGgxcKkD0cco+NLR72iuPk3g==", - "dev": true, - "requires": { - "hosted-git-info": "^3.0.2", - "osenv": "^0.1.5", - "semver": "^5.6.0", - "validate-npm-package-name": "^3.0.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://nexus.loafle.net/repository/npm-all/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, "rxjs": { - "version": "6.5.3", - "resolved": "https://nexus.loafle.net/repository/npm-all/rxjs/-/rxjs-6.5.3.tgz", - "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "version": "6.5.4", + "resolved": "https://nexus.loafle.net/repository/npm-all/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", "dev": true, "requires": { "tslib": "^1.9.0" } + }, + "semver": { + "version": "7.1.3", + "resolved": "https://nexus.loafle.net/repository/npm-all/semver/-/semver-7.1.3.tgz", + "integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA==", + "dev": true } } }, @@ -1915,10 +1853,11 @@ "resolved": "https://nexus.loafle.net/repository/npm-all/@tokenizer/token/-/token-0.1.1.tgz", "integrity": "sha512-XO6INPbZCxdprl+9qa/AAbFFOMzzwqYxpjPgLICrMD6C2FCw6qfJOPcBk6JqqPLSaZ/Qx87qn4rpPmPMwaAK6w==" }, - "@tweenjs/tween.js": { - "version": "17.4.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/@tweenjs/tween.js/-/tween.js-17.4.0.tgz", - "integrity": "sha512-J3fzl1F6wvh8KXVVcIuHN12xi1ZDcPA/0Vix+ZcJYwZWVHUwfIqfvzYXXEw7ybeev6477KCTt9fKydU+ajUqcg==" + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true }, "@types/debug": { "version": "4.1.5", @@ -1948,15 +1887,6 @@ "@types/node": "*" } }, - "@types/i18next-node-fs-backend": { - "version": "2.1.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/@types/i18next-node-fs-backend/-/i18next-node-fs-backend-2.1.0.tgz", - "integrity": "sha512-bOOeT89UO/bYLJoQHdN5S3pggj7mMmFfQMBpDdUQOQIQkENGpnTwhNsIM/kjl1NE2HEihjlRZUNVV60Ze86UZA==", - "dev": true, - "requires": { - "i18next": ">=17.0.11" - } - }, "@types/jasmine": { "version": "3.5.10", "resolved": "https://nexus.loafle.net/repository/npm-all/@types/jasmine/-/jasmine-3.5.10.tgz", @@ -2002,11 +1932,6 @@ "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", "dev": true }, - "@types/tween.js": { - "version": "17.2.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/@types/tween.js/-/tween.js-17.2.0.tgz", - "integrity": "sha512-mOsqurEtFEzwgkVc/jDVE2XrjZBYTbrmDUyCr9GXmnfc6q5otokxFtKvSY/B21zgz9LVRIvRTawKczjKi57wrA==" - }, "@types/webpack-sources": { "version": "0.1.7", "resolved": "https://nexus.loafle.net/repository/npm-all/@types/webpack-sources/-/webpack-sources-0.1.7.tgz", @@ -2027,14 +1952,14 @@ } }, "@ucap/api": { - "version": "0.0.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/api/-/api-0.0.2.tgz", - "integrity": "sha512-rEV+wjlGI8FgJPCaVBp5cTu17tHliyrikLhsmyLrYOFeNvnn4GoljGeTyuItx1gJ1eIu65iGttL3dLtwfQ07Dg==" + "version": "0.0.4", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/api/-/api-0.0.4.tgz", + "integrity": "sha512-CYGdwVeLd6yIky2f+nsQAWfY22weSM0aLADeXRD1p/GhK4nw6UKTJX++J1qyRAfigp4Y9cO+6GJLc2zRMJ0oGQ==" }, "@ucap/api-common": { - "version": "0.0.3", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/api-common/-/api-common-0.0.3.tgz", - "integrity": "sha512-rifcCToIXdWZb9R3UXu2bXDqj/KBX3xfxHPFgx+Wp1TBUh6d+xszNYN7+mZbDKKSOYUgpVeuaF8leKkTwXUh9g==" + "version": "0.0.11", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/api-common/-/api-common-0.0.11.tgz", + "integrity": "sha512-QAuFb6APPtL3og5s4z9n5X0ubLxmO4QobbPg6NoFlGGA9LBMwxDrA/8mUe+VF8+ni2lv3JdiqvdxzlzFCphmew==" }, "@ucap/api-external": { "version": "0.0.5", @@ -2057,24 +1982,19 @@ "integrity": "sha512-/9w5uTgDkTV4nzMlyPzAyJCfR9E3VqnBQGYdVlyk8NFlVN3WOEzf8FdyRNfvUqyc4DAQFr3CDl06nL5pmua4hQ==" }, "@ucap/core": { - "version": "0.0.8", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/core/-/core-0.0.8.tgz", - "integrity": "sha512-e0BvZ4NHvqQhz0B4BKkUstFjvkvDkrKEcmAbSkDuzxS5KA1AfkXVYkn3eaIVQhYqWLXoyZwu3TSihS0scDitoA==" + "version": "0.0.14", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/core/-/core-0.0.14.tgz", + "integrity": "sha512-7JuyGBTRYjzu5fPp0v0MBAW6KwweMCfhoPiTPhGkSpwjJByCWaX1h2QQgn23s1DhKMAZrOiasAZozw72m9fYtg==" }, "@ucap/logger": { - "version": "0.0.12", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/logger/-/logger-0.0.12.tgz", - "integrity": "sha512-Egpsx50skGT+8ejalPzJRTZfNcgkrQ8RAueN+Oh2Y1bTkXqRkimrD0bHlOQzJrQ+86Ghi3YZZAseLf/38bCm1Q==" + "version": "0.0.13", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/logger/-/logger-0.0.13.tgz", + "integrity": "sha512-pNZAJmHJ1YBBCCYhpHcaSplAuBelPhgQqvva+0zRzsu3eSoBp+OlHrMgPL7Qu/dpqn3BSb6D2CyrhldwUBvn8Q==" }, "@ucap/native": { - "version": "0.0.6", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/native/-/native-0.0.6.tgz", - "integrity": "sha512-AsR3zwvZ45sah82IxNRip0YyelbJ7qc6vXdyh3qfhFcIFvADPd+THYN1xErDy6qx5mCpIvbUHRb7Ggz3qtUucw==" - }, - "@ucap/native-browser": { - "version": "0.0.5", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/native-browser/-/native-browser-0.0.5.tgz", - "integrity": "sha512-gmf17wyXWCmQQB3CoOpxL66u4XJaNfXb5behlVzJn2lZZjqFmaOi+VTOQ8h8FIK9bkdG1Cc0v9KBLAnOyniTZw==" + "version": "0.0.19", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/native/-/native-0.0.19.tgz", + "integrity": "sha512-IllRnQ4YDb1tfMJcu5W05CNz3tlp2IDrK3c1mL3aG5IF7BkbAaUQ2aens90uzBzJXngNLOIrtV5hMNzveLiWXQ==" }, "@ucap/ng-api-common": { "version": "0.0.1", @@ -2102,9 +2022,9 @@ "integrity": "sha512-GA9MDcwCvtxI0gOysgRm7DDHIfKfhCkDSa69QBFIEgWbob2OdGYKvxfymr6lGdl+vY7AqjlJXvSFkA1b0rTy1A==" }, "@ucap/ng-core": { - "version": "0.0.1", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-core/-/ng-core-0.0.1.tgz", - "integrity": "sha512-okuTcVh/9VkF9dA1d/nsPuLL8ft2+4E77mgqfuPI2mI6IA7udQpC+hdMtyqtAN4LmT39iF1VGFv7UJYRK7fN7w==" + "version": "0.0.7", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-core/-/ng-core-0.0.7.tgz", + "integrity": "sha512-ZC6LE3A0bg+REGbzDI/i1ad7mGpKsw6X0UtZ+Q8TUthHNv0DfWEieHFCgfYTRY1u022XyQ4ViOsrq9KunU1vfw==" }, "@ucap/ng-i18n": { "version": "0.0.6", @@ -2117,14 +2037,9 @@ "integrity": "sha512-mtPOig+wmsUhlBz2D16i4UX8Cy2D7y+Q42hVq9Ks5pvo3IKgZrC5AAwjfpWjgHRBPMXwbuaWlRxx2jFmjBqD5g==" }, "@ucap/ng-native": { - "version": "0.0.1", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-native/-/ng-native-0.0.1.tgz", - "integrity": "sha512-SE/jxzmBQ/DkAc7CVsA1HcMny6fSRadk65y8EMQDRiMiP5Wzd+n8uzdDlmQdK1zBPK3glYaOi7PP34TQpIkLig==" - }, - "@ucap/ng-native-browser": { - "version": "0.0.1", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-native-browser/-/ng-native-browser-0.0.1.tgz", - "integrity": "sha512-2qqmz8n5DfrHsmYW8OMRcFBRAG60OICIAay3B5KO4nEnZjShelKOZCL2nGAkX+/QSgjtHpgb0h8cj2g6nOZIVg==" + "version": "0.0.5", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-native/-/ng-native-0.0.5.tgz", + "integrity": "sha512-4HJ9AOTCRd7K1EtuvYhHBSB8a9HwjgYSLVxwEkh64AH8eFw4CRKnn4TWHkqX+q5uEuWRzKPBnH7mZBsM27m3IA==" }, "@ucap/ng-pi": { "version": "0.0.1", @@ -2212,44 +2127,44 @@ "integrity": "sha512-0s5OdVsZ5Vi2X/aN6lOubPp4qS+b1GKWyNQ8gJlct9+tdzE5SZXfdQygBH1txImDnRC9jxZx5chiCwGqcbk6YA==" }, "@ucap/ng-store-authentication": { - "version": "0.0.11", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-store-authentication/-/ng-store-authentication-0.0.11.tgz", - "integrity": "sha512-YwLMW+GIR3Rs7LaP+1xOH9KLI5jlpZx8oS7Zl32m6Wbym4ModIcGh21rRrqFhNXky4s9zl+ziaTRpFNCKJRgng==" + "version": "0.0.14", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-store-authentication/-/ng-store-authentication-0.0.14.tgz", + "integrity": "sha512-6GUU+lerlrwVB8mT3qDBO5rxo7S5OYaLAb2K5HLxx9T+ZfAC+3maTjc28xeJaVgPfoLapI0jFuGgTlmwlBL6dg==" }, "@ucap/ng-store-chat": { - "version": "0.0.13", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-store-chat/-/ng-store-chat-0.0.13.tgz", - "integrity": "sha512-o+BCCSMxneUenRHEW47sSY22+Zt3lyr202Lg4bub9OVRbW5CVohHez8H+JwK+w+Lf8KbqG32V1ZjKLGclTpboA==" + "version": "0.0.66", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-store-chat/-/ng-store-chat-0.0.66.tgz", + "integrity": "sha512-fSbIfu0fVcITs+crcKLjcivJSaW0KOqps1TQ5QawZekaQlJYw36rA8TbOF/qL9g1L0Vw9NphKe85Wwm5t2yJOQ==" }, "@ucap/ng-store-group": { - "version": "0.0.14", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-store-group/-/ng-store-group-0.0.14.tgz", - "integrity": "sha512-sUmdHO7TD5B33DMAoEnelvqbLXTsWPnK2HC8XQ0FdlfGyUtf3kGpwS4BxduUi5wiZckR3hfuBdpCShIhf/qmeQ==" + "version": "0.0.22", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-store-group/-/ng-store-group-0.0.22.tgz", + "integrity": "sha512-XRFBpmJ9SPPwvessI+fxyL4GdWF+p/TU7RSWm6abbBcA7YGO//2gH0ghY8iPwsNVh3UhECy5LxkJx3fOGsJdhA==" }, "@ucap/ng-store-organization": { - "version": "0.0.8", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-store-organization/-/ng-store-organization-0.0.8.tgz", - "integrity": "sha512-vWWyPukVWeUPOxXkdWPN9s36V4EuMVuR8vPfOfKwnh09TxV8Efl19jIYYtNo92L1H/lF0OKfaz5eR9Aw76v+FA==" + "version": "0.0.20", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-store-organization/-/ng-store-organization-0.0.20.tgz", + "integrity": "sha512-YpM0+IP6yvv/3B01YHQ/abwK2enamA4Gx1IN2fpD0NyOu7DfFt6mV6EEz32IN0DpY1gZ8jyU/ESchnDbRmPu5w==" }, "@ucap/ng-ui": { - "version": "0.0.19", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-ui/-/ng-ui-0.0.19.tgz", - "integrity": "sha512-UuZSzWM4tBR+e5Z/1PFdNenHS00Pn4K7dTafIicG29YjHE5sTXXRqjDCVrKNxJQoMSEPWjUj7qnTgYwP2U83Vg==" + "version": "0.0.97", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-ui/-/ng-ui-0.0.97.tgz", + "integrity": "sha512-bmrpdiA1qnDWviV91buXCTOLGb49zeyt3T7AnOrZ87/wuVZAWdGaNUbEJtwYjZxnO3w2I5WOsKFzttq2vYU2LQ==" }, "@ucap/ng-ui-authentication": { - "version": "0.0.24", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-ui-authentication/-/ng-ui-authentication-0.0.24.tgz", - "integrity": "sha512-6QMJ8dieTnbPANsBzg2Ll3HH5q6Bzl2iSM19yHq8Ct7XOmElrYqrEZmxbDyYO+aCXIAwd2t7vu+rTsHfz3XOQg==" + "version": "0.0.29", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-ui-authentication/-/ng-ui-authentication-0.0.29.tgz", + "integrity": "sha512-kSQFFqSmtf+8aOmMqL0U+aUzCSmnPaWcJcDQfxuYPdRvlAYd4kMOWRcd/klvUrCpSf7P8jPsI7557rwmYHt3jA==" }, "@ucap/ng-ui-chat": { - "version": "0.0.9", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-ui-chat/-/ng-ui-chat-0.0.9.tgz", - "integrity": "sha512-6qvzcTuylkxVjsqajsLW15laOyOskxVMy238/Ju1yYvwCRyHygwS1i67APoG5tv+SWu+l38f9uWIqzfy7WYHkQ==" + "version": "0.0.72", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-ui-chat/-/ng-ui-chat-0.0.72.tgz", + "integrity": "sha512-sA5+3/sD9EN2BB2D2HZ9qrpGbkssWN7GF/UAvd8x4ECnVZPXaadNgLsSfOuZkSOVuM+6nTSyuglmnkjrPQBb8w==" }, "@ucap/ng-ui-group": { - "version": "0.0.33", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-ui-group/-/ng-ui-group-0.0.33.tgz", - "integrity": "sha512-c//Jq00drbMGE3Cgwlh19ScXllGERX2eMVWkVjm311Y8HN9oBBT6Aq2uCM23/76P866oNrhecVDfDHgGYzPRjA==" + "version": "0.0.78", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-ui-group/-/ng-ui-group-0.0.78.tgz", + "integrity": "sha512-GhHevEcbJJEppdcLOPFSQiAhn0psqrbKHe1I/X+c2wRGeIaJ/+UfseJ5CEqif8IQzU5caigM+q8E0bEFXiSFgQ==" }, "@ucap/ng-ui-material": { "version": "0.0.4", @@ -2257,9 +2172,9 @@ "integrity": "sha512-ySPULAbP+nQ65hBG2VWZ2H5Hr7muuTGGNXs6A+S3lsxLaW452wM3GNyUBhvUopr8LaSsoOPpp4nK1JeC0fG6pA==" }, "@ucap/ng-ui-organization": { - "version": "0.0.55", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-ui-organization/-/ng-ui-organization-0.0.55.tgz", - "integrity": "sha512-vfpKd3fbd+I0Od8aB2nIFfjuI7wj3Ziu/uiTEmZxKwZy7uZrNYm59BPbctKW3AQsQ4UtnLofhlBbAA7e9pT80Q==" + "version": "0.0.202", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ng-ui-organization/-/ng-ui-organization-0.0.202.tgz", + "integrity": "sha512-ibK62UXrEFvz5oauxAqsJmaXmu0+iqIqY1W01RNHgl6TrzcWv0ytB54j20viAEhD8rclKfUD8Wa6GvzLolCa6w==" }, "@ucap/ng-ui-skin-default": { "version": "0.0.1", @@ -2277,9 +2192,9 @@ "integrity": "sha512-RKVPzRAKOByr3CZ+vX+ch9wy49m094Iw7lhy6b51qL+EOjNtf2a3bjJ1qP9Jtbhxy4ssbpbjdFsASbTD+gc2sQ==" }, "@ucap/pi": { - "version": "0.0.5", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/pi/-/pi-0.0.5.tgz", - "integrity": "sha512-9zWQzOgwn9Q1VFhNYgD4+hfl728gulSDKjIm/+jplp1zjr5qlmMVyUcywXNd3ClWvA0I5FR+6nwNFv2Q1u3QYg==" + "version": "0.0.8", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/pi/-/pi-0.0.8.tgz", + "integrity": "sha512-exfW6CRRN3XyMrnmoXX0e3lv0ICNHbLeYSZN5/BBYXp+Rilu9UMYlbveotlVZuh6RuDSIUOHZG4xElRApYzzBg==" }, "@ucap/protocol": { "version": "0.0.17", @@ -2297,14 +2212,14 @@ "integrity": "sha512-Q8ej93Zx0MDVh6fNrvoydOKAEDJyc4+6i6QqJh3PmZkaqTeQ8+C4kUX32N5tGSbsNZRVFNHp7yDWzKCnVRbidg==" }, "@ucap/protocol-event": { - "version": "0.0.5", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-event/-/protocol-event-0.0.5.tgz", - "integrity": "sha512-RR74BKQaMIVqPo6TUYxFTUrIVc2ZSrsZXtereqymk24UKfL3eJ/8Oa7Gi7CYqAUPUDV815g4iTilXWr+Ok201w==" + "version": "0.0.6", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-event/-/protocol-event-0.0.6.tgz", + "integrity": "sha512-nFpNd2jTgzGlrtLWhuWnaPfP3DxxSprHy516BPASlD/iRY2TNmNgFJmZvUY1y5pbEv99azEGjvP5rhy6IECk7w==" }, "@ucap/protocol-file": { - "version": "0.0.5", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-file/-/protocol-file-0.0.5.tgz", - "integrity": "sha512-3XRwtlpcrm2oZeckoOzzAUcqADPCGbgdEb4psfNnphTbGX9nYaBUTpWuLYwD3tVe7Wg4fytaHemcAu8yVZUANw==" + "version": "0.0.6", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-file/-/protocol-file-0.0.6.tgz", + "integrity": "sha512-iJk/Y5QmAm7GSD0lVjK0EWFhRWWgxE49dBtfyIa04N9NrOHLi0wNlxrwPmiuW1+b8r2wih5OJoJo6zaUZ+KyIw==" }, "@ucap/protocol-group": { "version": "0.0.5", @@ -2312,9 +2227,9 @@ "integrity": "sha512-3e35omfU61Q9dnbFPqbXIKkQ+2ph6CHD8oNFC0lH9a2rTS5G1EMeeFk/7f963+K3elWdj+OvWTdmbe2cd4PhRw==" }, "@ucap/protocol-info": { - "version": "0.0.6", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-info/-/protocol-info-0.0.6.tgz", - "integrity": "sha512-qpt0jfmHDyaMGyADzaDMKbbkfD04yEC0u4KDyoMdjnTi0RXA6cilDRGr9TW/bezB9OxS40yNLK6REfh7aSmcUA==" + "version": "0.0.9", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-info/-/protocol-info-0.0.9.tgz", + "integrity": "sha512-uRo+B5Xj9jsyVJ5w6Ec6L7a8HJ6PFc5DAzVPkgQEYeHbo6IGsom07udDFFS0/oV6bJNi3BjyL0eakKDTOMwTTA==" }, "@ucap/protocol-inner": { "version": "0.0.4", @@ -2337,9 +2252,9 @@ "integrity": "sha512-XvCeg9UG33NuBx8Uo4PegDWlTF30z8Hk3kkbDPtCTLwyVdlg3DSKs5YYd8UiKkUxhxWKukvd5SZtxBtcbup5yQ==" }, "@ucap/protocol-room": { - "version": "0.0.5", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-room/-/protocol-room-0.0.5.tgz", - "integrity": "sha512-RSzLtnz5JVeDz9Y8gP17+LO6OG2NtLIPpW+JuHnqhfJyB93v2QmqqK5T7NbGYok72jRp4m7cJZEp1tLNYCKcmA==" + "version": "0.0.7", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-room/-/protocol-room-0.0.7.tgz", + "integrity": "sha512-vGTwAK31CbV49bj6UMpJ2WfxLbVkPDvZV1aaFmwvB0HqqqXgyyaM9Cvlm5xrMguLDE//t9uu1jVi8vBYgALdMA==" }, "@ucap/protocol-service": { "version": "0.0.4", @@ -2352,9 +2267,9 @@ "integrity": "sha512-h1+HcR5bmwugclKinl4XVCYRPkLzxc3Rou593JtXWHESCvcDMm9bEEuv/E4E5T+UE7L1pF1chZY+du1/tOeU9g==" }, "@ucap/protocol-sync": { - "version": "0.0.4", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-sync/-/protocol-sync-0.0.4.tgz", - "integrity": "sha512-jcRb13WLSBa7s/yeAyRwUPfT+/AgRMTiISMdCZhs271gwoyvqS6I1jgsUxv6s52vqNroUEvX7nsPKX3Xxy6ajA==" + "version": "0.0.6", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/protocol-sync/-/protocol-sync-0.0.6.tgz", + "integrity": "sha512-gGraaSItVbJkgiIPMQ1RKQLVRdYD0B2h7Um00EWxHqHw9kXrHHmEGIGI7y+WnLjlF/ZaksdoWfOGPUmiJhaahQ==" }, "@ucap/protocol-umg": { "version": "0.0.5", @@ -2362,9 +2277,9 @@ "integrity": "sha512-qxBog4wSit25HEq5a2pBtVE4NPQoqwmTeFuCqZBERc1x6Bw1zoA2aCVQ6yRFCNBguTyH0NQMSBFq6rnXwNrgBg==" }, "@ucap/ui-scss": { - "version": "0.0.4", - "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ui-scss/-/ui-scss-0.0.4.tgz", - "integrity": "sha512-n7npexk0wkoLuPm2d4qaM8IgG3qskJVSLgWfr9UQ7sGjNOQp7UI8TP7NYmoPHObDB8i7Nzn2RP/CL0sawgvoLg==" + "version": "0.0.5", + "resolved": "https://nexus.loafle.net/repository/npm-all/@ucap/ui-scss/-/ui-scss-0.0.5.tgz", + "integrity": "sha512-V67uhXQ/FN27ISwHBy6w/os7fUqsCellgfLabw5aH4lWKpDqL9AX2eao6uhUNedCW9E6Kb/7cxXfv6sUyyLhxw==" }, "@ucap/web-socket": { "version": "0.0.10", @@ -2749,6 +2664,7 @@ "version": "1.0.10", "resolved": "https://nexus.loafle.net/repository/npm-all/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -4278,6 +4194,58 @@ "sha.js": "^2.4.8" } }, + "cross-env": { + "version": "7.0.2", + "resolved": "https://nexus.loafle.net/repository/npm-all/cross-env/-/cross-env-7.0.2.tgz", + "integrity": "sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://nexus.loafle.net/repository/npm-all/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://nexus.loafle.net/repository/npm-all/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://nexus.loafle.net/repository/npm-all/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -5027,12 +4995,23 @@ "dev": true }, "encoding": { - "version": "0.1.12", - "resolved": "https://nexus.loafle.net/repository/npm-all/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "version": "0.1.13", + "resolved": "https://nexus.loafle.net/repository/npm-all/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, "requires": { - "iconv-lite": "~0.4.13" + "iconv-lite": "^0.6.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://nexus.loafle.net/repository/npm-all/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } } }, "end-of-stream": { @@ -5245,7 +5224,8 @@ "esprima": { "version": "4.0.1", "resolved": "https://nexus.loafle.net/repository/npm-all/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true }, "esrecurse": { "version": "4.2.1", @@ -6218,12 +6198,23 @@ "dev": true }, "hosted-git-info": { - "version": "3.0.4", - "resolved": "https://nexus.loafle.net/repository/npm-all/hosted-git-info/-/hosted-git-info-3.0.4.tgz", - "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", + "version": "3.0.5", + "resolved": "https://nexus.loafle.net/repository/npm-all/hosted-git-info/-/hosted-git-info-3.0.5.tgz", + "integrity": "sha512-i4dpK6xj9BIpVOTboXIlKG9+8HMKggcrMX7WA24xZtKwX0TPelq/rbaS5rCKeNX8sJXZJGdSxpnEGtta+wismQ==", "dev": true, "requires": { - "lru-cache": "^5.1.1" + "lru-cache": "^6.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } } }, "hpack.js": { @@ -6392,14 +6383,10 @@ "@babel/runtime": "^7.5.5" } }, - "i18next-node-fs-backend": { - "version": "2.1.3", - "resolved": "https://nexus.loafle.net/repository/npm-all/i18next-node-fs-backend/-/i18next-node-fs-backend-2.1.3.tgz", - "integrity": "sha512-CreMFiVl3ChlMc5ys/e0QfuLFOZyFcL40Jj6jaKD6DxZ/GCUMxPI9BpU43QMWUgC7r+PClpxg2cGXAl0CjG04g==", - "requires": { - "js-yaml": "3.13.1", - "json5": "2.0.0" - } + "i18next-fs-backend": { + "version": "1.0.7", + "resolved": "https://nexus.loafle.net/repository/npm-all/i18next-fs-backend/-/i18next-fs-backend-1.0.7.tgz", + "integrity": "sha512-aAZ3rvshe1Zbl6JSCWrWWqbZS5JpmVNG+84YqLcgdYcm9uAxzw4xWxnA/a3044Nm2PKXE62CT+pIZjk7OEYtTw==" }, "i18next-xhr-backend": { "version": "3.2.2", @@ -6548,23 +6535,23 @@ "dev": true }, "inquirer": { - "version": "7.0.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/inquirer/-/inquirer-7.0.0.tgz", - "integrity": "sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ==", + "version": "7.1.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/inquirer/-/inquirer-7.1.0.tgz", + "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", - "chalk": "^2.4.2", + "chalk": "^3.0.0", "cli-cursor": "^3.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", "lodash": "^4.17.15", "mute-stream": "0.0.8", - "run-async": "^2.2.0", - "rxjs": "^6.4.0", + "run-async": "^2.4.0", + "rxjs": "^6.5.3", "string-width": "^4.1.0", - "strip-ansi": "^5.1.0", + "strip-ansi": "^6.0.0", "through": "^2.3.6" }, "dependencies": { @@ -6574,6 +6561,47 @@ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://nexus.loafle.net/repository/npm-all/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://nexus.loafle.net/repository/npm-all/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -6589,34 +6617,24 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - } + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } @@ -7206,6 +7224,7 @@ "version": "3.13.1", "resolved": "https://nexus.loafle.net/repository/npm-all/js-yaml/-/js-yaml-3.13.1.tgz", "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -7253,14 +7272,6 @@ "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", "dev": true }, - "json5": { - "version": "2.0.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/json5/-/json5-2.0.0.tgz", - "integrity": "sha512-0EdQvHuLm7yJ7lyG5dp7Q3X2ku++BG5ZHaJ5FTnaXpKqDrw4pMxel5Bt3oAYMthnrthFBdnZ1FcsXTPyrQlV0w==", - "requires": { - "minimist": "^1.2.0" - } - }, "jsonfile": { "version": "4.0.0", "resolved": "https://nexus.loafle.net/repository/npm-all/jsonfile/-/jsonfile-4.0.0.tgz", @@ -7779,6 +7790,11 @@ "p-is-promise": "^2.0.0" } }, + "memoize-one": { + "version": "5.1.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/memoize-one/-/memoize-one-5.1.1.tgz", + "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==" + }, "memory-fs": { "version": "0.5.0", "resolved": "https://nexus.loafle.net/repository/npm-all/memory-fs/-/memory-fs-0.5.0.tgz", @@ -8242,15 +8258,6 @@ "resize-observer-polyfill": "^1.5.0" } }, - "ngx-virtual-scroller": { - "version": "4.0.3", - "resolved": "https://nexus.loafle.net/repository/npm-all/ngx-virtual-scroller/-/ngx-virtual-scroller-4.0.3.tgz", - "integrity": "sha512-JBqUJ/f7GRCZDnI/JeiFoTmYR8rC/Hyv8L5I7ImePM6f/hwiFNRsrK8Abdd0E3TwklwgmZAK875te9XQJrgsyQ==", - "requires": { - "@tweenjs/tween.js": "17.4.0", - "@types/tween.js": "17.2.0" - } - }, "nice-try": { "version": "1.0.5", "resolved": "https://nexus.loafle.net/repository/npm-all/nice-try/-/nice-try-1.0.5.tgz", @@ -8372,6 +8379,23 @@ "npm-normalize-package-bin": "^1.0.1" } }, + "npm-install-checks": { + "version": "4.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/npm-install-checks/-/npm-install-checks-4.0.0.tgz", + "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==", + "dev": true, + "requires": { + "semver": "^7.1.1" + }, + "dependencies": { + "semver": { + "version": "7.3.2", + "resolved": "https://nexus.loafle.net/repository/npm-all/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } + } + }, "npm-normalize-package-bin": { "version": "1.0.1", "resolved": "https://nexus.loafle.net/repository/npm-all/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", @@ -8379,27 +8403,20 @@ "dev": true }, "npm-package-arg": { - "version": "6.1.1", - "resolved": "https://nexus.loafle.net/repository/npm-all/npm-package-arg/-/npm-package-arg-6.1.1.tgz", - "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "version": "8.0.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/npm-package-arg/-/npm-package-arg-8.0.1.tgz", + "integrity": "sha512-/h5Fm6a/exByzFSTm7jAyHbgOqErl9qSNJDQF32Si/ZzgwT2TERVxRxn3Jurw1wflgyVVAxnFR4fRHPM7y1ClQ==", "dev": true, "requires": { - "hosted-git-info": "^2.7.1", - "osenv": "^0.1.5", - "semver": "^5.6.0", + "hosted-git-info": "^3.0.2", + "semver": "^7.0.0", "validate-npm-package-name": "^3.0.0" }, "dependencies": { - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://nexus.loafle.net/repository/npm-all/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, "semver": { - "version": "5.7.1", - "resolved": "https://nexus.loafle.net/repository/npm-all/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "7.3.2", + "resolved": "https://nexus.loafle.net/repository/npm-all/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true } } @@ -8416,28 +8433,28 @@ } }, "npm-pick-manifest": { - "version": "3.0.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", - "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", + "version": "6.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/npm-pick-manifest/-/npm-pick-manifest-6.0.0.tgz", + "integrity": "sha512-PdJpXMvjqt4nftNEDpCgjBUF8yI3Q3MyuAmVB9nemnnCg32F4BPL/JFBfdj8DubgHCYUFQhtLWmBPvdsFtjWMg==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1", - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" + "npm-install-checks": "^4.0.0", + "npm-package-arg": "^8.0.0", + "semver": "^7.0.0" }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://nexus.loafle.net/repository/npm-all/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "7.3.2", + "resolved": "https://nexus.loafle.net/repository/npm-all/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true } } }, "npm-registry-fetch": { - "version": "4.0.4", - "resolved": "https://nexus.loafle.net/repository/npm-all/npm-registry-fetch/-/npm-registry-fetch-4.0.4.tgz", - "integrity": "sha512-6jb34hX/iYNQebqWUHtU8YF6Cjb1H6ouTFPClYsyiW6lpFkljTpdeftm53rRojtja1rKAvKNIIiTS5Sjpw4wsA==", + "version": "4.0.5", + "resolved": "https://nexus.loafle.net/repository/npm-all/npm-registry-fetch/-/npm-registry-fetch-4.0.5.tgz", + "integrity": "sha512-yQ0/U4fYpCCqmueB2g8sc+89ckQ3eXpmU4+Yi2j5o/r0WkKvE2+Y0tK3DEILAtn2UaQTkjTHxIXe2/CSdit+/Q==", "dev": true, "requires": { "JSONStream": "^1.3.4", @@ -8449,11 +8466,35 @@ "safe-buffer": "^5.2.0" }, "dependencies": { + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://nexus.loafle.net/repository/npm-all/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://nexus.loafle.net/repository/npm-all/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true } } }, @@ -8700,33 +8741,84 @@ } }, "ora": { - "version": "4.0.2", - "resolved": "https://nexus.loafle.net/repository/npm-all/ora/-/ora-4.0.2.tgz", - "integrity": "sha512-YUOZbamht5mfLxPmk4M35CD/5DuOkAacxlEUbStVXpBAt4fyhBf+vZHI/HRkI++QUp3sNoeA2Gw4C+hi4eGSig==", + "version": "4.0.3", + "resolved": "https://nexus.loafle.net/repository/npm-all/ora/-/ora-4.0.3.tgz", + "integrity": "sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg==", "dev": true, "requires": { - "chalk": "^2.4.2", + "chalk": "^3.0.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.2.0", "is-interactive": "^1.0.0", "log-symbols": "^3.0.0", - "strip-ansi": "^5.2.0", + "mute-stream": "0.0.8", + "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "5.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://nexus.loafle.net/repository/npm-all/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://nexus.loafle.net/repository/npm-all/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://nexus.loafle.net/repository/npm-all/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" } } } @@ -8840,9 +8932,9 @@ "dev": true }, "pacote": { - "version": "9.5.8", - "resolved": "https://nexus.loafle.net/repository/npm-all/pacote/-/pacote-9.5.8.tgz", - "integrity": "sha512-0Tl8Oi/K0Lo4MZmH0/6IsT3gpGf9eEAznLXEQPKgPq7FscnbUOyopnVpwXlnQdIbCUaojWy1Wd7VMyqfVsRrIw==", + "version": "9.5.12", + "resolved": "https://nexus.loafle.net/repository/npm-all/pacote/-/pacote-9.5.12.tgz", + "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", "dev": true, "requires": { "bluebird": "^3.5.3", @@ -8859,6 +8951,7 @@ "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", "npm-package-arg": "^6.1.0", "npm-packlist": "^1.1.12", "npm-pick-manifest": "^3.0.0", @@ -8899,6 +8992,12 @@ "y18n": "^4.0.0" } }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://nexus.loafle.net/repository/npm-all/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, "minipass": { "version": "2.9.0", "resolved": "https://nexus.loafle.net/repository/npm-all/minipass/-/minipass-2.9.0.tgz", @@ -8909,6 +9008,29 @@ "yallist": "^3.0.0" } }, + "npm-package-arg": { + "version": "6.1.1", + "resolved": "https://nexus.loafle.net/repository/npm-all/npm-package-arg/-/npm-package-arg-6.1.1.tgz", + "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-pick-manifest": { + "version": "3.0.2", + "resolved": "https://nexus.loafle.net/repository/npm-all/npm-pick-manifest/-/npm-pick-manifest-3.0.2.tgz", + "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, "rimraf": { "version": "2.7.1", "resolved": "https://nexus.loafle.net/repository/npm-all/rimraf/-/rimraf-2.7.1.tgz", @@ -11531,7 +11653,8 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://nexus.loafle.net/repository/npm-all/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "sshpk": { "version": "1.16.1", @@ -13342,6 +13465,12 @@ "lodash": "^4.17.15" } }, + "webpack-node-externals": { + "version": "1.7.2", + "resolved": "https://nexus.loafle.net/repository/npm-all/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", + "integrity": "sha512-ajerHZ+BJKeCLviLUUmnyd5B4RavLF76uv3cs6KNuO8W+HuQaEs0y0L7o40NQxdPy5w0pcv8Ew7yPUAQG0UdCg==", + "dev": true + }, "webpack-sources": { "version": "1.4.3", "resolved": "https://nexus.loafle.net/repository/npm-all/webpack-sources/-/webpack-sources-1.4.3.tgz", diff --git a/package.json b/package.json index 66e237c..c7e5b4c 100644 --- a/package.json +++ b/package.json @@ -5,42 +5,41 @@ "ng": "ng", "start": "ng serve", "start:hmr": "ng serve --configuration hmr", - "start:hmr-es5": "ng serve --configuration hmr-es5", + "start:hmr-renderer": "ng serve --configuration hmr-renderer", "build": "ng build", "build:production": "ng build --prod", - "build:production-es5": "ng build --configuration production-es5", + "build:production-renderer": "cross-env NATIVE_ENV=renderer ng build --prod --configuration production-renderer --base-href ./", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" }, "private": true, "dependencies": { - "@angular/animations": "~9.0.6", - "@angular/cdk": "^9.1.3", - "@angular/common": "~9.0.6", - "@angular/compiler": "~9.0.6", - "@angular/core": "~9.0.6", - "@angular/flex-layout": "^9.0.0-beta.29", - "@angular/forms": "~9.0.6", - "@angular/material": "^9.1.3", - "@angular/material-moment-adapter": "^9.1.3", - "@angular/platform-browser": "~9.0.6", - "@angular/platform-browser-dynamic": "~9.0.6", - "@angular/router": "~9.0.6", - "@ngrx/effects": "^9.0.0", - "@ngrx/entity": "^9.0.0", - "@ngrx/router-store": "^9.0.0", - "@ngrx/store": "^9.0.0", - "@ucap/api": "~0.0.2", - "@ucap/api-common": "~0.0.5", + "@angular/animations": "^9.1.11", + "@angular/cdk": "^9.2.4", + "@angular/common": "^9.1.11", + "@angular/compiler": "^9.1.11", + "@angular/core": "^9.1.11", + "@angular/flex-layout": "^9.0.0-beta.31", + "@angular/forms": "^9.1.11", + "@angular/material": "^9.2.4", + "@angular/material-moment-adapter": "^9.2.4", + "@angular/platform-browser": "^9.1.11", + "@angular/platform-browser-dynamic": "^9.1.11", + "@angular/router": "^9.1.11", + "@ngrx/effects": "^9.2.0", + "@ngrx/entity": "^9.2.0", + "@ngrx/router-store": "^9.2.0", + "@ngrx/store": "^9.2.0", + "@ucap/api": "~0.0.4", + "@ucap/api-common": "~0.0.11", "@ucap/api-external": "~0.0.5", "@ucap/api-message": "~0.0.3", "@ucap/api-prompt": "~0.0.3", "@ucap/api-public": "~0.0.4", - "@ucap/core": "~0.0.10", - "@ucap/logger": "~0.0.12", - "@ucap/native": "~0.0.6", - "@ucap/native-browser": "~0.0.5", + "@ucap/core": "~0.0.14", + "@ucap/logger": "~0.0.13", + "@ucap/native": "~0.0.19", "@ucap/ng-api-common": "~0.0.1", "@ucap/ng-api-external": "~0.0.1", "@ucap/ng-api-message": "~0.0.1", @@ -49,8 +48,7 @@ "@ucap/ng-core": "~0.0.7", "@ucap/ng-logger": "~0.0.2", "@ucap/ng-i18n": "~0.0.6", - "@ucap/ng-native": "~0.0.1", - "@ucap/ng-native-browser": "~0.0.1", + "@ucap/ng-native": "~0.0.5", "@ucap/ng-pi": "~0.0.1", "@ucap/ng-protocol": "~0.0.3", "@ucap/ng-protocol-authentication": "~0.0.3", @@ -68,35 +66,35 @@ "@ucap/ng-protocol-status": "~0.0.3", "@ucap/ng-protocol-sync": "~0.0.3", "@ucap/ng-protocol-umg": "~0.0.3", - "@ucap/ng-store-authentication": "~0.0.11", - "@ucap/ng-store-chat": "~0.0.16", - "@ucap/ng-store-group": "~0.0.14", - "@ucap/ng-store-organization": "~0.0.8", + "@ucap/ng-store-authentication": "~0.0.14", + "@ucap/ng-store-chat": "~0.0.66", + "@ucap/ng-store-group": "~0.0.22", + "@ucap/ng-store-organization": "~0.0.20", "@ucap/ng-web-socket": "~0.0.2", "@ucap/ng-web-storage": "~0.0.3", - "@ucap/ng-ui": "~0.0.19", - "@ucap/ng-ui-organization": "~0.0.83", - "@ucap/ng-ui-authentication": "~0.0.25", - "@ucap/ng-ui-group": "~0.0.33", - "@ucap/ng-ui-chat": "~0.0.12", + "@ucap/ng-ui": "0.0.97", + "@ucap/ng-ui-organization": "~0.0.202", + "@ucap/ng-ui-authentication": "~0.0.29", + "@ucap/ng-ui-group": "~0.0.78", + "@ucap/ng-ui-chat": "~0.0.72", "@ucap/ng-ui-material": "~0.0.4", "@ucap/ng-ui-skin-default": "~0.0.1", - "@ucap/pi": "~0.0.5", + "@ucap/pi": "~0.0.8", "@ucap/protocol": "~0.0.17", "@ucap/protocol-authentication": "~0.0.5", "@ucap/protocol-buddy": "~0.0.5", - "@ucap/protocol-event": "~0.0.5", - "@ucap/protocol-file": "~0.0.4", + "@ucap/protocol-event": "~0.0.6", + "@ucap/protocol-file": "~0.0.6", "@ucap/protocol-group": "~0.0.5", - "@ucap/protocol-info": "~0.0.6", + "@ucap/protocol-info": "~0.0.9", "@ucap/protocol-inner": "~0.0.4", "@ucap/protocol-option": "~0.0.7", "@ucap/protocol-ping": "~0.0.6", "@ucap/protocol-query": "~0.0.5", - "@ucap/protocol-room": "~0.0.6", + "@ucap/protocol-room": "~0.0.7", "@ucap/protocol-service": "~0.0.4", "@ucap/protocol-status": "~0.0.5", - "@ucap/protocol-sync": "~0.0.4", + "@ucap/protocol-sync": "~0.0.6", "@ucap/protocol-umg": "~0.0.5", "@ucap/ui-scss": "~0.0.5", "@ucap/web-socket": "~0.0.10", @@ -109,13 +107,13 @@ "file-type": "^14.1.4", "i18next": "^19.3.3", "i18next-browser-languagedetector": "^4.0.2", - "i18next-node-fs-backend": "^2.1.3", + "i18next-fs-backend": "^1.0.6", "i18next-xhr-backend": "^3.2.2", "libphonenumber-js": "^1.7.47", + "memoize-one": "^5.1.1", "moment": "^2.24.0", "moment-timezone": "^0.5.28", "ngx-perfect-scrollbar": "^9.0.0", - "ngx-virtual-scroller": "^4.0.3", "pino": "^6.0.0", "queueing-subject": "^0.3.4", "rxjs": "~6.5.4", @@ -126,16 +124,16 @@ "devDependencies": { "@angular-builders/custom-webpack": "^9.0.0", "@angular-devkit/build-angular": "~0.900.6", - "@angular/cli": "~9.0.6", - "@angular/compiler-cli": "~9.0.6", - "@angular/language-service": "~9.0.6", + "@angular/cli": "^9.1.9", + "@angular/compiler-cli": "^9.1.11", + "@angular/language-service": "^9.1.11", "@angularclass/hmr": "^2.1.3", "@ngrx/store-devtools": "^9.0.0", - "@types/i18next-node-fs-backend": "^2.1.0", "@types/jasmine": "~3.5.0", "@types/jasminewd2": "~2.0.3", "@types/node": "^12.11.1", "codelyzer": "^5.1.2", + "cross-env": "^7.0.2", "fs-extra": "^9.0.0", "jasmine-core": "~3.5.0", "jasmine-spec-reporter": "~4.2.1", @@ -148,6 +146,7 @@ "ts-node": "~8.3.0", "tslint": "~5.18.0", "typescript": "~3.7.5", - "webpack-bundle-analyzer": "^3.7.0" + "webpack-bundle-analyzer": "^3.7.0", + "webpack-node-externals": "^1.7.2" } } diff --git a/src/app/app-provider.module.ts b/src/app/app-provider.module.ts index 781e27e..625d68f 100644 --- a/src/app/app-provider.module.ts +++ b/src/app/app-provider.module.ts @@ -12,10 +12,13 @@ import { AppAuthenticationGuard } from './guards/app-authentication.guard'; import { AppSessionResolver } from './resolvers/app-session.resolver'; import { AppAuthenticationService } from './services/app-authentication.service'; +import { AppNotificationService } from './services/app-notification.service'; import { AppNativeService } from './services/app-native.service'; import { AppService } from './services/app.service'; import { AppChatService } from './services/app-chat.service'; import { AppFileService } from './services/app-file.service'; +import { AppGroupService } from './services/app-group.service'; +import { AppAccountService } from './services/app-account.service'; const GUARDS = [AppAuthenticationGuard]; const RESOLVERS = [AppSessionResolver]; @@ -24,7 +27,10 @@ const SERVICES = [ AppAuthenticationService, AppNativeService, AppFileService, - AppChatService + AppChatService, + AppNotificationService, + AppGroupService, + AppAccountService ]; const axiosFactory = () => { @@ -35,6 +41,9 @@ const axiosFactory = () => { return i; }; +const nativeServiceFactory = (nativeService: any) => + new environment.productConfig.nativeServiceClass(nativeService); + const appInit = (appService: AppService) => { return () => appService.initialize(); }; @@ -51,7 +60,7 @@ const appInit = (appService: AppService) => { }, { provide: UCAP_NATIVE_SERVICE, - useClass: environment.productConfig.nativeServiceClass, + useFactory: nativeServiceFactory, deps: [AXIOS_INSTANCE], multi: false }, diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index f0787e5..0833757 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -81,7 +81,9 @@ const routes: Routes = [ ]; @NgModule({ - imports: [RouterModule.forRoot(routes)], + imports: [ + RouterModule.forRoot(routes, { useHash: true, enableTracing: false }) + ], exports: [RouterModule] }) export class AppRoutingModule {} diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 4cd4364..24f685f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,28 +1,48 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { + Component, + OnDestroy, + OnInit, + AfterViewInit, + Renderer2 +} from '@angular/core'; import { Store } from '@ngrx/store'; import { AppActions } from '@app/store/actions'; -import { fromEvent, interval, Subscription } from 'rxjs'; -import { debounce } from 'rxjs/operators'; +import { fromEvent, interval, Subject } from 'rxjs'; +import { debounce, takeUntil } from 'rxjs/operators'; +import { AppAuthenticationService } from './services/app-authentication.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) -export class AppComponent implements OnInit, OnDestroy { - private resizeWindowSubscription: Subscription; +export class AppComponent implements OnInit, OnDestroy, AfterViewInit { + private ngOnDestroySubject: Subject = new Subject(); - constructor(private store: Store) { - this.resizeWindowSubscription = fromEvent(window, 'resize') - .pipe(debounce(() => interval(100))) + constructor( + private renderer2: Renderer2, + private store: Store, + private appAuthenticationService: AppAuthenticationService + ) { + fromEvent(window, 'resize') + .pipe( + takeUntil(this.ngOnDestroySubject), + debounce(() => interval(100)) + ) .subscribe((event: any) => { this.dispatchWindowSize({ width: event.target.innerWidth, height: event.target.innerHeight }); }); + + // fromEvent(window, 'beforeunload') + // .pipe(takeUntil(this.ngOnDestroySubject)) + // .subscribe((event: any) => { + // this.appAuthenticationService.logout(); + // }); } ngOnInit(): void { @@ -33,11 +53,19 @@ export class AppComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - if (!!this.resizeWindowSubscription) { - this.resizeWindowSubscription.unsubscribe(); + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); } } + ngAfterViewInit(): void { + const preloader = this.renderer2.selectRootElement( + '#ucap-lg-web-preloader' + ); + this.renderer2.setStyle(preloader, 'display', 'none'); + } + private dispatchWindowSize(size: { width: number; height: number }) { this.store.dispatch(AppActions.windowResized(size)); } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d3d55e2..02210bb 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -59,6 +59,8 @@ import { effects } from '@app/store/effects'; import { ROOT_REDUCERS } from '@app/store/reducers'; import { metaReducers } from '@app/store/state'; +import { AppAccountDialogModule } from '@app/dialogs/account/account.dialog.module'; + import { environment } from '@environments'; @NgModule({ @@ -69,6 +71,56 @@ import { environment } from '@environments'; FlexLayoutModule, + /** + * StoreModule.forRoot is imported once in the root module, accepting a reducer + * function or object map of reducer functions. If passed an object of + * reducers, combineReducers will be run creating your application + * meta-reducer. This returns all providers for an @ngrx/store + * based application. + */ + StoreModule.forRoot(ROOT_REDUCERS, { + metaReducers, + runtimeChecks: { + strictStateImmutability: true, + strictActionImmutability: true, + strictStateSerializability: true, + strictActionSerializability: true + } + }), + + /** + * @ngrx/router-store keeps router state up-to-date in the store. + */ + StoreRouterConnectingModule.forRoot({ + routerState: RouterState.Minimal + }), + + /** + * Store devtools instrument the store retaining past versions of state + * and recalculating new states. This enables powerful time-travel + * debugging. + * + * To use the debugger, install the Redux Devtools extension for either + * Chrome or Firefox + * + * See: https://github.com/zalmoxisus/redux-devtools-extension + */ + StoreDevtoolsModule.instrument({ + name: 'UCAP Store App' + + // In a production build you would want to disable the Store Devtools + // logOnly: environment.production, + }), + + /** + * EffectsModule.forRoot() is imported once in the root module and + * sets up the effects class to be initialized immediately when the + * application starts. + * + * See: https://ngrx.io/guide/effects#registering-root-effects + */ + EffectsModule.forRoot([...effects]), + LoggerModule.forRoot({}), CommonApiModule.forRoot(environment.commonApiModuleConfig), @@ -118,55 +170,7 @@ import { environment } from '@environments'; AppLayoutsModule, - /** - * StoreModule.forRoot is imported once in the root module, accepting a reducer - * function or object map of reducer functions. If passed an object of - * reducers, combineReducers will be run creating your application - * meta-reducer. This returns all providers for an @ngrx/store - * based application. - */ - StoreModule.forRoot(ROOT_REDUCERS, { - metaReducers, - runtimeChecks: { - strictStateImmutability: true, - strictActionImmutability: true, - strictStateSerializability: true, - strictActionSerializability: true - } - }), - - /** - * @ngrx/router-store keeps router state up-to-date in the store. - */ - StoreRouterConnectingModule.forRoot({ - routerState: RouterState.Minimal - }), - - /** - * Store devtools instrument the store retaining past versions of state - * and recalculating new states. This enables powerful time-travel - * debugging. - * - * To use the debugger, install the Redux Devtools extension for either - * Chrome or Firefox - * - * See: https://github.com/zalmoxisus/redux-devtools-extension - */ - StoreDevtoolsModule.instrument({ - name: 'UCAP Store App' - - // In a production build you would want to disable the Store Devtools - // logOnly: environment.production, - }), - - /** - * EffectsModule.forRoot() is imported once in the root module and - * sets up the effects class to be initialized immediately when the - * application starts. - * - * See: https://ngrx.io/guide/effects#registering-root-effects - */ - EffectsModule.forRoot([...effects]) + AppAccountDialogModule ], providers: [], bootstrap: [AppComponent] diff --git a/src/app/app.theme.scss b/src/app/app.theme.scss index 2dbb7d3..7d7c0c3 100644 --- a/src/app/app.theme.scss +++ b/src/app/app.theme.scss @@ -22,7 +22,7 @@ $typography: mat-typography-config( // Define the palettes for your theme using the Material Design palettes available in palette.scss // (imported above). For each palette, you can optionally specify a default, lighter, and darker // hue. Available color palettes: https://material.io/design/color/ -$lgRed-app-primary: mat-palette($ucap-color-primary); +$lgRed-app-primary: mat-palette($ucap-color-primary, 600); $lgRed-app-accent: mat-palette($ucap-color-accent, 700); // The warn palette is optional (defaults to red). diff --git a/src/app/dialogs/account/account.dialog.module.ts b/src/app/dialogs/account/account.dialog.module.ts new file mode 100644 index 0000000..c756cb2 --- /dev/null +++ b/src/app/dialogs/account/account.dialog.module.ts @@ -0,0 +1,48 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { FlexLayoutModule } from '@angular/flex-layout'; + +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; +import { MatTabsModule } from '@angular/material/tabs'; + +import { I18nModule } from '@ucap/ng-i18n'; +import { UiModule } from '@ucap/ng-ui'; + +import { AppLayoutsModule } from '@app/layouts/layouts.module'; +import { AppAccountSectionModule } from '@app/sections/account/account.section.module'; + +import { COMPONENTS } from './components'; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + FlexLayoutModule, + + MatButtonModule, + MatCheckboxModule, + MatIconModule, + MatInputModule, + MatRadioModule, + MatSelectModule, + MatTabsModule, + + I18nModule, + + UiModule, + + AppLayoutsModule, + AppAccountSectionModule + ], + exports: [...COMPONENTS], + declarations: [...COMPONENTS], + entryComponents: [...COMPONENTS] +}) +export class AppAccountDialogModule {} diff --git a/src/app/dialogs/account/components/index.ts b/src/app/dialogs/account/components/index.ts new file mode 100644 index 0000000..1ad8877 --- /dev/null +++ b/src/app/dialogs/account/components/index.ts @@ -0,0 +1,3 @@ +import { SettingsDialogComponent } from './settings.dialog.component'; + +export const COMPONENTS = [SettingsDialogComponent]; diff --git a/src/app/dialogs/account/components/settings.dialog.component.html b/src/app/dialogs/account/components/settings.dialog.component.html new file mode 100644 index 0000000..ed932b0 --- /dev/null +++ b/src/app/dialogs/account/components/settings.dialog.component.html @@ -0,0 +1,471 @@ +
+ +
+ {{ 'organization:settings.label' | ucapI18n }} +
+
+ + + +

{{ 'organization:settings.sub.general' | ucapI18n }}

+
+
+ + +
+
+ {{ + 'organization:settings.language.messenger' | ucapI18n + }} + +
+
+ + + + {{ 'locale:languages.' + languageCode | ucapI18n }} + + + +
+
+
+
+ {{ + 'organization:settings.language.hr' | ucapI18n + }} + +
+
+ + + + {{ 'locale:languages.' + languageCode | ucapI18n }} + + + +
+
+
+
+ {{ 'organization:settings.timezone' | ucapI18n }} + +
+
+ + + + + {{ timezone.displayName }} + + + + +
+
+
+
+ + +

{{ 'organization:settings.sub.notification' | ucapI18n }}

+
+
+ +
+
+ {{ 'organization:settings.notification.receival' | ucapI18n }} +
+
+ + + {{ + 'organization:settings.notification.receive' | ucapI18n + }} + + + {{ + 'organization:settings.notification.notReceive' | ucapI18n + }} + + +
+
+
+
+ {{ 'organization:settings.notification.method' | ucapI18n }} +
+
+ + + + {{ + 'organization:settings.notification.methodTypeSound' + | ucapI18n + }} + + + {{ + 'organization:settings.notification.methodTypeAlert' + | ucapI18n + }} + + + {{ + 'organization:settings.notification.methodTypeSoundAndAlert' + | ucapI18n + }} + + + +
+
+
+
+ {{ + 'organization:settings.notification.settingOfAlertWindow' + | ucapI18n + }} +
+
+ + + + 5{{ 'common:units.second' | ucapI18n }} + + + 10{{ 'common:units.second' | ucapI18n }} + + + 15{{ 'common:units.second' | ucapI18n }} + + + 20{{ 'common:units.second' | ucapI18n }} + + + +
+
+
+
+ {{ + 'organization:settings.notification.receiveForMobile' + | ucapI18n + }} +
+
+
    +
  • + + {{ + 'organization:settings.notification.receiveForMessageTypePopup' + | ucapI18n + }} + +
  • +
+
+
+
+
+ + +

{{ 'chat:settings.label' | ucapI18n }}

+
+
+ +
+
파일 전송
+
+
+ 다운로드 폴더 +
+
+ + + + +
+ +
+
+
+
+ + +

{{ 'call:settings.label' | ucapI18n }}

+
+
+ +
+
+ Click to Call + + | PC Messenger에서 Click to Call 기능을 사용할 기기 설정 +
+
+ + 휴대폰 + 사무실 + +
+
+
+
+ + +

{{ 'authentication:password.settings.label' | ucapI18n }}

+
+
+ +
+
+ {{ 'authentication:password.fields.changePassword' | ucapI18n }} +
+
+
+ {{ + 'authentication:password.fields.currentPassword' | ucapI18n + }} +
+
+ + + + Hint + +
+
+
+
+ {{ 'authentication:password.fields.newPassword' | ucapI18n }} +
+
+ + + + 반드시 영어 소문자, 숫자, 특수문자 중 2가지 이상 사용해야 + 합니다. + + + + + + Error + +
+
+ +
+
+
+ info_outline + {{ 'authentication:password.notice.condition' | ucapI18n }} +
+
+ {{ 'authentication:password.notice.condition1' | ucapI18n }} +
+
+ {{ 'authentication:password.notice.condition2' | ucapI18n }} +
+
+ {{ 'authentication:password.notice.condition3' | ucapI18n }} +
+
+ {{ 'authentication:password.notice.condition4' | ucapI18n }} +
+
+ {{ 'authentication:password.notice.condition5' | ucapI18n }} +
+
+
+
+
+
+
+
+
+ + +
+
+
diff --git a/src/app/dialogs/account/components/settings.dialog.component.scss b/src/app/dialogs/account/components/settings.dialog.component.scss new file mode 100644 index 0000000..fbd800c --- /dev/null +++ b/src/app/dialogs/account/components/settings.dialog.component.scss @@ -0,0 +1,224 @@ +@import '~@ucap/lg-scss/mixins'; +.dialog-container { + width: 100%; + height: 100%; + + .dialog-body { + width: 100%; + height: 100%; + .messenger-settings-area { + border-top: 10px solid #f1f2f6; + background-color: #fff; + display: flex; + flex-direction: column; + width: 100%; + padding: 10px 16px 9px; + &:first-of-type { + border-top: 0; + padding: 20px 16px 9px; + } + .title-settings-subject { + color: #5c444b; + font-size: 1.071em; + font-weight: 600; + line-height: 1.2; + padding: 6px 0 7px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + min-height: 30px; + span { + flex: 0 0 auto; + } + em { + flex: 0 1 auto; + justify-self: start; + font-style: normal; + font-size: 0.929em; + color: #999; + font-weight: 400; + @include wordBreak(); + @include ellipsis-column(1); + width: 100%; + } + .btn-subject-info { + width: 30px; + height: 30px; + line-height: 30px; + font-size: 18px; + } + } + .settings-contents { + ul { + li { + padding: 6px 0 7px; + } + } + .settings-radio-group { + padding: 6px 0 7px; + height: 42px; + display: flex; + flex-direction: row; + align-items: center; + .mat-radio-button { + margin-left: 20%; + &:first-child { + margin-left: 0; + } + } + } + } + .settings-contents02 { + display: flex; + flex-direction: row; + align-items: center; + margin-top: 25px; + @include screen(xs) { + flex-direction: column; + } + .subtitle-settings-info { + font-size: 0.929em; + color: #262626; + align-self: baseline; + @include screen(xs) { + flex: 1 0 auto; + } + } + .settings-sub-content { + display: flex; + flex-direction: column; + margin-left: 25px; + width: 60%; + @include screen(xs) { + flex: 0 1 auto; + width: 100% !important; + margin-left: 0; + } + .setting-input-obj { + @include ucapMatFormField(0, 0, 0%, 100%, 100%, 60px, 11px); + overflow: hidden; + margin-top: 15px; + &:first-of-type { + margin-top: 0; + @include screen(xs) { + margin-top: 15px; + } + } + .mat-hint { + line-height: 1.3; + } + } + &.sub-set-content { + width: auto; + flex: 1 1 auto; + .input-set-obj { + @include ucapMatFormField(0, 0, 0%, 100%, 100%, 55px, 11px); + } + } + } + } + } + + .default-settings-area { + display: flex; + flex-direction: column; + align-items: flex-start; + .login-setting-box { + } + .language-setting-box, + .time-setting-box { + .settings-contents { + width: 50%; + height: 50px; + @include screen(mid) { + width: 70%; + } + @include screen(xs) { + width: 100%; + } + .setting-select-obj { + @include ucapMatFormField(0, 0, 100%, 100%, 100%, 40px, 28px); + //@include ucapMatSelect(25px, 0 0); + + .general-timezone-viewport { + height: 250px; + width: 100%; + } + } + } + } + } + .allim-settings-area { + display: flex; + flex-direction: column; + align-items: flex-start; + .allim-way-box, + .allim-time-box { + .settings-contents { + width: 50%; + height: 50px; + @include screen(mid) { + width: 70%; + } + @include screen(xs) { + width: 100%; + } + .setting-select-obj { + @include ucapMatFormField(0, 0, 100%, 100%, 100%, 40px, 28px); + //@include ucapMatSelect(25px, 0 0); + } + } + } + } + .chat-settings-area { + } + .call-settings-area { + } + .secret-num-settings-area { + .pass-info-box { + dl { + border: 1px solid #dfe0e8; + background-color: #f7f8fa; + dt { + height: 30px; + background-color: #fff; + display: flex; + flex-direction: row; + align-items: center; + font-size: 0.857em; + font-weight: 600; + .bullet-ico-info { + flex: 0 0 36px; + text-align: center; + font-size: 16px; + width: 30px; + height: 30px; + line-height: 30px; + } + } + dd { + font-size: 0.857em; + color: #666; + line-height: 1.6; + margin: 2px 36px 3px; + &:first-of-type { + margin-top: 10px; + } + &:last-of-type { + margin-bottom: 10px; + } + } + } + } + } + } + .btn-box { + display: flex; + flex-direction: row; + justify-content: flex-end; + button { + @include ucap-button-flat-stroked(120px); + } + } +} diff --git a/src/app/dialogs/account/components/settings.dialog.component.spec.ts b/src/app/dialogs/account/components/settings.dialog.component.spec.ts new file mode 100644 index 0000000..692e93d --- /dev/null +++ b/src/app/dialogs/account/components/settings.dialog.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { SettingsDialogComponent } from './settings.dialog.component'; + +describe('app::account::SettingsDialogComponent', () => { + let component: SettingsDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SettingsDialogComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SettingsDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dialogs/account/components/settings.dialog.component.ts b/src/app/dialogs/account/components/settings.dialog.component.ts new file mode 100644 index 0000000..a2bf624 --- /dev/null +++ b/src/app/dialogs/account/components/settings.dialog.component.ts @@ -0,0 +1,189 @@ +import moment from 'moment'; +import 'moment-timezone'; + +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Inject, + ViewChild +} from '@angular/core'; + +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatDialog +} from '@angular/material/dialog'; +import { MatSelectChange, MatSelect } from '@angular/material/select'; + +import { NativeService, NativeType } from '@ucap/native'; + +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; + +import { environment } from '@environments'; + +import { + Settings, + GeneralSetting, + NotificationSetting, + ChatSetting, + PresenceSetting +} from '@app/models/settings'; +import { I18nService } from '@ucap/ng-i18n'; +import { VirtualScrollViewportComponent } from '@ucap/ng-ui'; +import { FormControl } from '@angular/forms'; +import { MatOptionSelectionChange } from '@angular/material/core'; + +export interface TimezoneData { + displayName: string; + name: string; +} + +export interface SettingsDialogData { + settings: Settings; +} +export interface SettingsDialogResult {} + +@Component({ + selector: 'app-sections-account-settings', + templateUrl: './settings.dialog.component.html', + styleUrls: ['./settings.dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SettingsDialogComponent implements OnInit, OnDestroy { + @ViewChild('vsTimezone', { static: false }) + vsTimezone: VirtualScrollViewportComponent; + + @ViewChild('selectForTimezone', { static: false }) + selectForTimezone: MatSelect; + + formControlForTimezone = new FormControl(); + + platform: 'browser' | 'electron' = 'electron'; + + generalSetting: GeneralSetting; + notificationSetting: NotificationSetting; + chatSetting: ChatSetting; + presenceSetting: PresenceSetting; + + timezoneList: TimezoneData[]; + timezonePlaceholder: string; + + supportedLanguages = environment.productConfig.supportedLanguages; + supportedHrLanguages = + environment.productConfig.organization.supportedLanguages; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: SettingsDialogData, + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, + private i18nService: I18nService, + private changeDetectorRef: ChangeDetectorRef, + public matDialog: MatDialog + ) { + this.nativeService.platform_nativeType().then((type) => { + switch (type) { + case NativeType.Browser: + this.platform = 'browser'; + break; + case NativeType.Electron: + this.platform = 'electron'; + break; + default: + break; + } + }); + + this.generalSetting = data.settings.general; + this.notificationSetting = data.settings.notification; + this.chatSetting = data.settings.chat; + this.presenceSetting = data.settings.presence; + } + + private ngOnDestroySubject: Subject = new Subject(); + + ngOnInit(): void { + this.generateTimezoneData(); + + this.i18nService.languageChanged$ + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe((lang) => { + this.generateTimezoneData(); + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onSelectionChangeLanguage(event: MatSelectChange) {} + + onSelectionChangeHrLanguage(event: MatSelectChange) {} + + onOpenedChangeTimezone(opened: boolean) { + if (opened) { + this.setTimezoneData(); + this.vsTimezone.checkViewportSize(); + } + } + + onSelectionChangeTimezone(event: MatOptionSelectionChange) { + if (!event.isUserInput) { + return; + } + } + + onClosed(event: MouseEvent): void { + this.dialogRef.close(); + } + + onCancel() {} + + onConfirm() {} + + private generateTimezoneData() { + const timezoneData = this.i18nService.t('locale:timezone', { + returnObjects: true + }); + + let timezoneList: TimezoneData[] = []; + for (const name of moment.tz.names()) { + const displayName = `(UTC${moment.tz(name).format('Z')}) ${ + timezoneData[name] + }`; + timezoneList.push({ + displayName, + name + }); + if (name === this.generalSetting.timezone) { + this.timezonePlaceholder = displayName; + } + } + timezoneList = timezoneList.sort((a: TimezoneData, b: TimezoneData) => { + return a.displayName.localeCompare(b.displayName); + }); + + this.timezoneList = timezoneList; + } + + private setTimezoneData() { + const timezoneIndex = this.timezoneList.findIndex( + (t) => t.name === this.generalSetting.timezone + ); + + if (-1 !== timezoneIndex) { + if (!!this.vsTimezone && !!this.selectForTimezone) { + this.vsTimezone.scrollToIndex(timezoneIndex); + this.selectForTimezone.value = this.timezoneList[timezoneIndex].name; + } + } + } +} diff --git a/src/app/layouts/components/default-dialog.layout.component.html b/src/app/layouts/components/default-dialog.layout.component.html index 21954be..20b6def 100644 --- a/src/app/layouts/components/default-dialog.layout.component.html +++ b/src/app/layouts/components/default-dialog.layout.component.html @@ -1,11 +1,17 @@
-
+
+
+ +
-
- - - +
+
+
+
+ +
+
+ +
+
+
+ + + +
+
+ +
+
diff --git a/src/app/layouts/components/default-drawer.layout.component.scss b/src/app/layouts/components/default-drawer.layout.component.scss new file mode 100644 index 0000000..b29a996 --- /dev/null +++ b/src/app/layouts/components/default-drawer.layout.component.scss @@ -0,0 +1,35 @@ +.layout-container { + width: 100%; + height: 100%; + + .layout-header { + align-items: center; + justify-content: space-between; + color: #333; + font-weight: 600; + font-size: 1.143em; + border-bottom: 1px solid #333; + margin: 0 16px; + .layout-header-content { + width: 100%; + height: 100%; + } + } + .layout-body { + overflow: auto; + + .layout-body-content { + width: 100%; + height: 100%; + } + } + .layout-action { + display: flex; + align-items: center; + justify-content: flex-end; + .layout-action-content { + width: 100%; + height: 100%; + } + } +} diff --git a/src/app/layouts/components/default-drawer.layout.component.spec.ts b/src/app/layouts/components/default-drawer.layout.component.spec.ts new file mode 100644 index 0000000..94bfc75 --- /dev/null +++ b/src/app/layouts/components/default-drawer.layout.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { DefaultDrawerLayoutComponent } from './default-drawer.layout.component'; + +describe('app::layouts::DefaultDrawerLayoutComponent', () => { + let component: DefaultDrawerLayoutComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [DefaultDrawerLayoutComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DefaultDrawerLayoutComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/layouts/components/default-drawer.layout.component.ts b/src/app/layouts/components/default-drawer.layout.component.ts new file mode 100644 index 0000000..bcb3ed9 --- /dev/null +++ b/src/app/layouts/components/default-drawer.layout.component.ts @@ -0,0 +1,45 @@ +import { Subject } from 'rxjs'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + EventEmitter, + Output +} from '@angular/core'; + +@Component({ + selector: 'app-layouts-default-drawer', + templateUrl: './default-drawer.layout.component.html', + styleUrls: ['./default-drawer.layout.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DefaultDrawerLayoutComponent implements OnInit, OnDestroy { + @Input() + disableClose = false; + + @Output() + closed = new EventEmitter(); + + constructor(private changeDetectorRef: ChangeDetectorRef) {} + + private ngOnDestroySubject: Subject; + + ngOnInit(): void { + this.ngOnDestroySubject = new Subject(); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onClickClose(event: MouseEvent): void { + this.closed.emit(event); + } +} diff --git a/src/app/layouts/components/default.layout.component.html b/src/app/layouts/components/default.layout.component.html index 1ae90fa..cd0f482 100644 --- a/src/app/layouts/components/default.layout.component.html +++ b/src/app/layouts/components/default.layout.component.html @@ -3,12 +3,13 @@
-
+
-
+
-
+
-
+
-
- -
- + + +
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+
+ + + + + + + + + + +
+
+ dialpad +
+
M-Messenger @@ -242,7 +398,41 @@
- + +
+
+ Today{{ moment().format('YYYY.MM.DD') }} +
+
+ + + + +
+ +
+
+
+
@@ -269,3 +459,16 @@
+ + + + + + diff --git a/src/app/layouts/components/default.layout.component.scss b/src/app/layouts/components/default.layout.component.scss index 2ee8d09..68102d8 100644 --- a/src/app/layouts/components/default.layout.component.scss +++ b/src/app/layouts/components/default.layout.component.scss @@ -12,18 +12,20 @@ .navitab-page { //GNB ///////////////////////////////////// .gnb { - background-color: $gray-ref0; + //background-color: $gray-ref0; + background-color: #f1f2f6; width: 60px; height: 100%; display: flex; flex-direction: column; justify-content: space-between; align-items: center; - border-right: 1px solid rgba(204, 204, 204, 0.8); + border-right: 1px solid rgba(0, 0, 0, 0.2); .mat-gnb-toolbar { - flex-basis: 64px; + flex-basis: 40px; + padding: 2px 12px 10px; .img-logo { - margin: 9px 0 5px; + margin: 6px 0 0 1px; } } .left-container { @@ -33,13 +35,14 @@ } .global-menu { width: 100%; - background-color: $gray-ref0; + //background-color: $gray-ref0; flex-grow: 1; } .btn-homepage-area { flex-flow: column-reverse; position: relative; button { + /* padding: 30px 0 12px; &::before { content: ''; @@ -51,6 +54,21 @@ top: 9px; left: calc(50% - 10px); } + */ + padding: 30px 0 12px; + border-radius: 21px; + min-width: 42px; + &::before { + content: ''; + width: 30px; + height: 30px; + background-image: url(../../../assets/images/ico/btn_gnb_hompage.svg); + background-size: 30px; + display: block; + position: absolute; + top: 5px; + left: calc(50% - 15px); + } em { font-style: normal; font-size: 8px; @@ -61,139 +79,6 @@ } } } - ::ng-deep .global-menu { - //display: flex; - //flex-direction: row; - .mat-tab-header { - border-bottom: none !important; - width: 100%; - } - .mat-tab-label-container { - .mat-tab-list { - .mat-tab-labels { - display: flex; - flex-flow: column; - justify-content: space-around; - height: 272px; - border-bottom: none; - - .mat-tab-label { - width: 100%; - height: 32px; - padding: 0; - min-width: 0 !important; - .mat-tab-label-content { - .icon-item { - display: inline-flex; - width: 32px; - height: 32px; - border-radius: 50%; - justify-content: center; - align-items: center; - //transform: scale(0.9); - transition: transform 0.3s cubic-bezier(0.4, 0, 0, 1); - - svg { - //width: 24px; - //height: 24px; - stroke: $gray-re9; - stroke-width: 2; - stroke-linecap: square; - stroke-linejoin: miter; - fill: none; - g { - &#icon_gnb_organiztion_g32 { - .prefix__cls-1, - .prefix__cls-4 { - fill: none; - } - .prefix__cls-1 { - stroke: #999; - stroke-width: 2px; - } - .prefix__cls-3 { - stroke: none; - } - } - &#icon_gnb_message_g32 { - .prefix__cls-1 { - fill: none; - stroke: #999; - stroke-width: 2px; - stroke-linejoin: round; - } - } - } - } - .mat-badge-content { - right: -9px !important; - border: 1px solid #ffbf2a; - //width: 24px; - //height: 24px; - box-sizing: content-box; - top: -10px !important; - } - } - } - &.mat-tab-label-active { - opacity: 0; - svg { - stroke: #fff !important; - g { - &#prefix_23, - &#icon_gnb_chat_g32, - &#icon_gnb_call_g32 { - path { - &:nth-child(2) { - fill: #fff !important; - } - } - } - &#icon_gnb_organiztion_g32 { - .prefix__cls-1 { - stroke: #fff !important; - } - path { - &:nth-last-of-type(2) { - stroke: #fff !important; - } - } - } - &#icon_gnb_message_g32 { - .prefix__cls-1 { - stroke: #fff !important; - } - path { - &:nth-child(3) { - stroke: #fff !important; - } - } - } - } - } - } - &[aria-selected='true'] { - opacity: 1; - .mat-tab-label-content { - .icon-item { - transform: scale(1); - } - } - } - } - } - .mat-ink-bar { - opacity: 0; - } - } - } - .mat-tab-body-wrapper { - .mat-tab-body { - height: 100%; - width: 100%; - } - } - } } /////////////////////////////////////GNB // } @@ -213,6 +98,7 @@ .left-sidenav { width: 370px; max-width: 90%; + border-right: 1px solid rgba(0, 0, 0, 0.2); .left-sidenav-container { width: 100%; @@ -222,8 +108,10 @@ size: 13px; color: $gray-re70; } - line-height: 15px; - padding: 25px 0 0 17px; + height: 40px; + display: flex; + align-items: center; + padding: 0 16px; } } } @@ -232,6 +120,96 @@ .content-sidenav-container { width: 100%; height: 100%; + overflow: hidden; //20200611 + .content-sidenav-top-bar { + .content-sidenav-top-bar-content { + height: 40px; + width: auto; + background-color: transparent; + display: flex; + flex-direction: row; + align-items: center; + flex-grow: 1; + font-size: 12px; + justify-content: space-between; + .toolbar-info-area { + display: flex; + flex-grow: 1; + align-items: center; + &.date-info { + @include font-family($font-light); + font-weight: 600; + font-size: 12px; + color: $gray-re70; + padding-left: 30px; + @include screen(mid) { + padding-left: 16px; + display: none; + } + span { + width: 54px; + height: 16px; + border-radius: 30px; + border: solid 1px $lipstick; + background-color: #ffffff; + font-size: 11px; + + display: flex; + align-items: center; + justify-content: center; + color: $lipstick; + margin-right: 8px; + } + } + &.toolbar-ctrl { + flex-flow: row-reverse; + .topbar-search { + order: 2; + margin-right: 8px; + .ico-search-icon { + width: 18px; + height: 18px; + font-size: 18px; + line-height: 18px; + color: #707070; + } + } + .my-profile { + height: 30px; + width: 30px; + margin-right: 20px; + order: 1; + //profile ///////////// + .user-profile-thumb { + @include profile-avatar-default( + 0, + 14, + $green, + 18px + ); //오른 아래 공간, 모바일 온라인 아이콘 크기, 모바일 아이콘 색, 모바일 아이콘 bg크기 + + .presence { + //PC 상태 + @include presence-state(10px); //원크기 + position: relative; + align-self: flex-end; + margin-left: -10px; + order: 2; + } + .profileImage { + @include avatar-img(30px, 0); //아바타 크기, 왼쪽공간 + order: 1; + } + } + ///// profile// + } + .app-layout-native-title-bar-actions { + order: 0; + } + } + } + } + } .content-sidenav-body { overflow: auto; @@ -250,7 +228,7 @@ justify-content: space-between; width: 100%; height: 38px; - border-top: 1px solid $line-color-gray01; + border-top: 1px solid rgba(0, 0, 0, 0.2); background-color: $white; .foot-info { display: flex; @@ -264,6 +242,13 @@ .var-txt { padding-left: 8px; color: $gray-re70; + text-align: center; + @include screen(custom, max, 414) { + padding-left: 22px; + flex-basis: 38%; + flex-grow: 0; + line-height: 1.2; + } &::before { content: ''; width: 1px; @@ -271,10 +256,19 @@ display: inline-block; background-color: #d4d4d4; margin-right: 8px; + @include screen(custom, max, 414) { + margin-left: -10px; + } } &:first-of-type { + @include screen(custom, max, 414) { + padding-left: 0; + } &::before { width: 0; + @include screen(custom, max, 414) { + margin-left: -4px; + } } } &.new-var { @@ -287,11 +281,13 @@ padding-right: 20px; p { margin: 0; + text-align: right; span { color: $lipstick; } em { margin-left: 10px; + white-space: nowrap; } } } @@ -301,3 +297,41 @@ } } } + +// Float action button +.ico-font-float { + svg { + .prefix_cls-2 { + fill: transparent; + stroke-width: 2px; + stroke: rgba(255, 255, 255, 1); + } + .prefix_cls-3 { + stroke: rgba(255, 255, 255, 1); + } + .prefix_cls-4 { + fill: rgba(255, 255, 255, 1); + } + } + &:hover { + svg { + .prefix_cls-2 { + stroke: rgba(255, 255, 255, 0.7); + } + .prefix_cls-3 { + stroke: rgba(255, 255, 255, 0.7); + } + .prefix_cls-4 { + fill: rgba(255, 255, 255, 0.7); + } + } + } +} + +.ico-font-dialpad { + font-size: 28px !important; + line-height: 44px; + &:hover { + color: rgba(255, 255, 255, 0.7); + } +} diff --git a/src/app/layouts/components/default.layout.component.ts b/src/app/layouts/components/default.layout.component.ts index de48dd1..f432c37 100644 --- a/src/app/layouts/components/default.layout.component.ts +++ b/src/app/layouts/components/default.layout.component.ts @@ -1,17 +1,40 @@ -import { Subscription } from 'rxjs'; +import moment from 'moment'; + +import { Subject, of } from 'rxjs'; +import { takeUntil, filter, take, map, catchError } from 'rxjs/operators'; import { Component, ViewChild, OnDestroy, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; +import { + Router, + RouterEvent, + NavigationEnd, + PRIMARY_OUTLET, + ActivatedRoute, + Params +} from '@angular/router'; import { Store, select } from '@ngrx/store'; +import { MatDialog } from '@angular/material/dialog'; +import { MatMenuTrigger } from '@angular/material/menu'; import { MatTabChangeEvent, MatTabGroup } from '@angular/material/tabs'; import { MatSidenav } from '@angular/material/sidenav'; +import { VersionInfo2Response } from '@ucap/api-public'; +import { UserInfoSS } from '@ucap/protocol-query'; + import { LogService } from '@ucap/ng-logger'; +import { UserSelector } from '@ucap/ng-store-organization'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; +import { RoomSelector } from '@ucap/ng-store-chat'; import { AppSelector } from '@app/store/state'; import { AppChatService } from '@app/services/app-chat.service'; +import { QueryParams as ChatQueryParams } from '@app/pages/chat/types/params.type'; +import { CreateDialogComponent } from '@app/sections/group/dialogs/create.dialog.component'; +import { AppAccountService } from '@app/services/app-account.service'; +import { QueryParams as OrganizationParams } from '@app/pages/organization/types/params.type'; +import { User } from '@ucap/protocol-info'; const NAVS = ['group', 'chat', 'organization', 'message']; @@ -27,79 +50,170 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy { @ViewChild('leftSidenav', { static: true }) leftSidenav: MatSidenav; + isShowLeftSideNav = false; + + @ViewChild('profileMenuTrigger', { static: true }) + profileMenuTrigger: MatMenuTrigger; + showStatusbar = true; + tabIndex: string; + queryParams: Params; + + unreadCountChat = 0; + /** FAB */ fabButtonShow = true; + fabUseCustomDefaultIcon = true; // default in this prj fabButtons: { icon: string; tooltip?: string; divisionType?: string }[]; + versionInfo2Res: VersionInfo2Response; + user: User; - private windowSizeSubscription: Subscription; + moment = moment; + + private ngOnDestroySubject: Subject = new Subject(); constructor( private router: Router, + private activatedRoute: ActivatedRoute, private store: Store, + private appAccountService: AppAccountService, private appChatService: AppChatService, - private logService: LogService - ) {} + private logService: LogService, + public dialog: MatDialog + ) { + this.setFabInitial(NAVS[0]); + this.router.events + .pipe( + takeUntil(this.ngOnDestroySubject), + filter((event) => event instanceof RouterEvent) + ) + .subscribe((event) => { + switch (event.constructor) { + case NavigationEnd: + { + const t = this.router.parseUrl((event as NavigationEnd).url); + const p = t.root.children[PRIMARY_OUTLET]; + if (!p || !p.segments || 0 === p.segments.length) { + break; + } + const index = p.segments[0].path; + this.setTabGroup(index); + this.setFabInitial(index); + } + break; + default: + break; + } + }); + + this.activatedRoute.queryParams + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe((params) => { + this.queryParams = params; + }); + + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + } ngOnInit(): void { - this.windowSizeSubscription = this.store - .pipe(select(AppSelector.windowSize)) + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(AppSelector.windowSize)) .subscribe((size) => { if (size.width < 780) { if (this.leftSidenav.opened) { this.leftSidenav.close(); } + this.isShowLeftSideNav = false; + this.leftSidenav.mode = 'over'; } else { if (!this.leftSidenav.opened) { this.leftSidenav.open(); } + this.isShowLeftSideNav = true; + this.leftSidenav.mode = 'side'; } }); - this.setTabGroup(this.router.url); + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; + }); - this.setFabInitial(NAVS[0]); + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(RoomSelector.unreadTotal) + ) + .subscribe((unreadTotal) => { + this.unreadCountChat = unreadTotal; + }); } ngOnDestroy(): void { - if (!this.windowSizeSubscription) { - this.windowSizeSubscription.unsubscribe(); + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); } } + onOpenProfile(userInfo: UserInfoSS) { + this.profileMenuTrigger.openMenu(); + } + onSelectedTabChange(event: MatTabChangeEvent) { - if (4 === event.index) { - this.router.navigate( - ['group', { outlets: { content: 'chat/index' } }], - {} - ); - return; - } - this.router.navigate([ + const commands: any = [ NAVS[event.index], { outlets: { content: 'index' } } - ]); + ]; + const orgInitialParams: Params = {}; + + if ( + event.index === 1 && // is chat. + !!this.queryParams && + !!this.queryParams[ChatQueryParams.ROOM_ID] + ) { + // 다른 화면에서 채팅으로 바로 유입할 경우에는 navigate 초기화를 무시한다. + this.queryParams = undefined; + return; + } + // if (!!this.tabIndex && this.tabIndex === 'chat') { + // if (!!this.queryParams && !!this.queryParams[ChatQueryParams.ROOM_ID]) { + // return; + // } else { + + // } + // } else { + // } + + if (event.index === 2 && !!this.user) { + orgInitialParams[OrganizationParams.DEPT_SEQ] = String( + this.user.departmentCode + ); + } + this.router.navigate(commands, { queryParams: orgInitialParams }); + if (!this.isShowLeftSideNav) { + this.leftSidenav.open(); + } this.setFabInitial(NAVS[event.index]); } onClickToggleLeftSidenav() { - if (this.leftSidenav.opened) { - this.leftSidenav.close(); - } else { - this.leftSidenav.open(); - } - } - - private setTabGroup(url: string) { - if (!!this.navTabGroup) { - this.navTabGroup.selectedIndex = NAVS.findIndex((v) => - url.startsWith(`/${v}`) - ); + if (!this.isShowLeftSideNav) { + this.leftSidenav.toggle(); } } setFabInitial(type: string) { + this.tabIndex = type; switch (type) { case 'group': { @@ -172,7 +286,20 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy { switch (btn.divisionType) { case 'GROUP_NEW_ADD': { - this.logService.debug('GROUP_NEW_ADD'); + const dialogRef = this.dialog.open(CreateDialogComponent, { + panelClass: 'max-create-dialog' + }); + + dialogRef + .afterClosed() + .pipe( + take(1), + map((result) => {}), + catchError((err) => { + return of(err); + }) + ) + .subscribe(); } break; case 'CAHT_NEW_ADD': @@ -195,4 +322,37 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy { break; } } + + onClickSearch(event: Event) { + event.stopPropagation(); + } + + onSelectedProfileManage() { + this.profileMenuTrigger.closeMenu(); + } + + onSelectedNotice() { + this.profileMenuTrigger.closeMenu(); + } + + onSelectedSettings() { + this.profileMenuTrigger.closeMenu(); + this.appAccountService.dialogForSettings(); + } + + onSelectedLogout() { + this.router.navigate(['/account/logout']); + } + + onSelectedExit() {} + + onDoneForProfileMenu() { + this.profileMenuTrigger.closeMenu(); + } + + private setTabGroup(url: string) { + if (!!this.navTabGroup) { + this.navTabGroup.selectedIndex = NAVS.findIndex((v) => url === v); + } + } } diff --git a/src/app/layouts/components/index.ts b/src/app/layouts/components/index.ts index 78d1e3a..2e67d24 100644 --- a/src/app/layouts/components/index.ts +++ b/src/app/layouts/components/index.ts @@ -4,6 +4,7 @@ import { DefaultLayoutComponent } from './default.layout.component'; import { NoNaviLayoutComponent } from './no-navi.layout.component'; import { DefaultDialogLayoutComponent } from './default-dialog.layout.component'; +import { DefaultDrawerLayoutComponent } from './default-drawer.layout.component'; import { SelectorLayoutComponent } from './selector.layout.component'; export const COMPONENTS = [ @@ -12,6 +13,7 @@ export const COMPONENTS = [ NoNaviLayoutComponent, DefaultDialogLayoutComponent, + DefaultDrawerLayoutComponent, SelectorLayoutComponent ]; diff --git a/src/app/layouts/components/no-navi.layout.component.scss b/src/app/layouts/components/no-navi.layout.component.scss index 7d4bc06..7285ecc 100644 --- a/src/app/layouts/components/no-navi.layout.component.scss +++ b/src/app/layouts/components/no-navi.layout.component.scss @@ -1,2 +1,3 @@ .layout-container { + background-color: #f3f4f5; } diff --git a/src/app/layouts/components/selector.layout.component.html b/src/app/layouts/components/selector.layout.component.html index 4651501..12b0a6a 100644 --- a/src/app/layouts/components/selector.layout.component.html +++ b/src/app/layouts/components/selector.layout.component.html @@ -9,9 +9,10 @@ matSuffix aria-label="Clear" class="btn-close" - color="accent" + color="primary" + (click)="onClickClose($event)" > - highlight_off + close
diff --git a/src/app/layouts/components/selector.layout.component.scss b/src/app/layouts/components/selector.layout.component.scss index e69de29..722b82c 100644 --- a/src/app/layouts/components/selector.layout.component.scss +++ b/src/app/layouts/components/selector.layout.component.scss @@ -0,0 +1,17 @@ +.selector { + border-bottom: 1px solid #ccc; + .selector-title { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding: 0 5px 0 16px; + //border-top: 1px solid #ccc; + } + .selector-contents { + background-color: #fff; + border-top: 1px solid #ccc; + } + .footer { + } +} diff --git a/src/app/layouts/components/selector.layout.component.ts b/src/app/layouts/components/selector.layout.component.ts index 5d3104a..9127fd5 100644 --- a/src/app/layouts/components/selector.layout.component.ts +++ b/src/app/layouts/components/selector.layout.component.ts @@ -2,9 +2,10 @@ import { Component, OnInit, OnDestroy, - Input, ChangeDetectionStrategy, - ChangeDetectorRef + ChangeDetectorRef, + EventEmitter, + Output } from '@angular/core'; @Component({ @@ -14,9 +15,16 @@ import { changeDetection: ChangeDetectionStrategy.OnPush }) export class SelectorLayoutComponent implements OnInit, OnDestroy { + @Output() + closed = new EventEmitter(); + constructor(private changeDetectorRef: ChangeDetectorRef) {} ngOnInit(): void {} ngOnDestroy(): void {} + + onClickClose(event: MouseEvent): void { + this.closed.emit(); + } } diff --git a/src/app/layouts/components/top-bar.component.html b/src/app/layouts/components/top-bar.component.html index 6521605..9770328 100644 --- a/src/app/layouts/components/top-bar.component.html +++ b/src/app/layouts/components/top-bar.component.html @@ -1,10 +1,10 @@
+
diff --git a/src/app/layouts/components/top-bar.component.scss b/src/app/layouts/components/top-bar.component.scss index 9ef7b36..34e8778 100644 --- a/src/app/layouts/components/top-bar.component.scss +++ b/src/app/layouts/components/top-bar.component.scss @@ -2,5 +2,5 @@ width: 100%; height: 100%; padding: 0; - background-color: #ffffff; + background-color: transparent; } diff --git a/src/app/layouts/components/top-bar.component.ts b/src/app/layouts/components/top-bar.component.ts index 92fd79e..fa193e8 100644 --- a/src/app/layouts/components/top-bar.component.ts +++ b/src/app/layouts/components/top-bar.component.ts @@ -1,7 +1,16 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; + +import * as detectBrowser from 'detect-browser'; import { Store } from '@ngrx/store'; +import { NativeService, WindowState, NativeType } from '@ucap/native'; + +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; + @Component({ selector: 'app-layouts-top-bar', templateUrl: './top-bar.component.html', @@ -9,13 +18,54 @@ import { Store } from '@ngrx/store'; }) export class TopBarComponent implements OnInit, OnDestroy { platform = 'win32'; - native = true; + windowState: WindowState; - constructor(private store: Store) {} + private ngOnDestroySubject: Subject = new Subject(); - ngOnInit() {} + constructor( + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, + private store: Store + ) { + this.nativeService.platform_nativeType().then((type) => { + switch (type) { + case NativeType.Browser: + this.platform = 'browser'; + break; + case NativeType.Electron: + { + const info = detectBrowser.detect(); + if (info.os.startsWith('Windows')) { + this.platform = 'win32'; + } else if (info.os.startsWith('Mac OS')) { + this.platform = 'darwin'; + } else if (info.os.startsWith('Linux')) { + this.platform = 'linux'; + } else { + } + } + break; - ngOnDestroy(): void {} + default: + break; + } + }); + } + + ngOnInit() { + this.nativeService + .window_onState$() + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe((windowState) => { + this.windowState = windowState; + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } onClosedTitleBar() {} diff --git a/src/app/layouts/layouts.module.ts b/src/app/layouts/layouts.module.ts index e958ce8..932e697 100644 --- a/src/app/layouts/layouts.module.ts +++ b/src/app/layouts/layouts.module.ts @@ -5,21 +5,25 @@ import { RouterModule } from '@angular/router'; import { FlexLayoutModule } from '@angular/flex-layout'; +import { MatBadgeModule } from '@angular/material/badge'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatSelectModule } from '@angular/material/select'; import { MatSidenavModule } from '@angular/material/sidenav'; import { MatTabsModule } from '@angular/material/tabs'; import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar'; +import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; import { UiModule } from '@ucap/ng-ui'; +import { AppOrganizationModule } from '@app/ucap/organization/organization.module'; + import { COMPONENTS } from './components'; import { DIALOGS } from './dialogs'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; -import { MatSelectModule } from '@angular/material/select'; @NgModule({ imports: [ @@ -28,17 +32,22 @@ import { MatSelectModule } from '@angular/material/select'; FlexLayoutModule, + MatBadgeModule, MatButtonModule, MatIconModule, + MatMenuModule, + MatSelectModule, MatSidenavModule, MatTabsModule, MatToolbarModule, - MatSelectModule, + MatTooltipModule, PerfectScrollbarModule, I18nModule, - UiModule + UiModule, + + AppOrganizationModule ], exports: [...COMPONENTS, ...DIALOGS], declarations: [...COMPONENTS, ...DIALOGS], @@ -46,7 +55,7 @@ import { MatSelectModule } from '@angular/material/select'; providers: [ { provide: UCAP_I18N_NAMESPACE, - useValue: ['chat', 'common'] + useValue: ['common'] } ] }) diff --git a/src/app/models/group-open-info.ts b/src/app/models/group-open-info.ts new file mode 100644 index 0000000..342c265 --- /dev/null +++ b/src/app/models/group-open-info.ts @@ -0,0 +1,4 @@ +export interface GroupOpenInfo { + lastGroupSeq: number; + groupSeqs: number[]; +} diff --git a/src/app/models/login-session.ts b/src/app/models/login-session.ts index edc64fb..2645a78 100644 --- a/src/app/models/login-session.ts +++ b/src/app/models/login-session.ts @@ -1,8 +1,10 @@ import { LoginSession as UCAPLoginSession } from '@ucap/core'; +import { GroupOpenInfo } from './group-open-info'; export interface LoginSession extends UCAPLoginSession { loginPw?: string; initPw?: boolean; encData?: string; alive?: boolean; + groupInfo?: GroupOpenInfo; } diff --git a/src/app/pages/account/account-routing.page.module.ts b/src/app/pages/account/account-routing.page.module.ts index 9edf13c..3abbe8e 100644 --- a/src/app/pages/account/account-routing.page.module.ts +++ b/src/app/pages/account/account-routing.page.module.ts @@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router'; import { ForgotPasswordPageComponent } from './components/forgot-password.page.component'; import { LoginPageComponent } from './components/login.page.component'; +import { LogoutPageComponent } from './components/logout.page.component'; import { ResetPasswordPageComponent } from './components/reset-password.page.component'; const routes: Routes = [ @@ -17,6 +18,10 @@ const routes: Routes = [ { path: 'reset_password', component: ResetPasswordPageComponent + }, + { + path: 'logout', + component: LogoutPageComponent } ]; diff --git a/src/app/pages/account/components/login.page.component.ts b/src/app/pages/account/components/login.page.component.ts index 6e3e0eb..2233f5f 100644 --- a/src/app/pages/account/components/login.page.component.ts +++ b/src/app/pages/account/components/login.page.component.ts @@ -25,13 +25,11 @@ export class LoginPageComponent implements OnInit, OnDestroy { readonly fixedCompanyCode = environment.companyConfig.fixedCompanyCode; - private ngOnDestroySubject = new Subject(); + private ngOnDestroySubject: Subject = new Subject(); constructor(private localStorageService: LocalStorageService) {} ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - this.localStorageService .encGet$( AppKey.UserStore, @@ -43,6 +41,7 @@ export class LoginPageComponent implements OnInit, OnDestroy { ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } diff --git a/src/app/pages/account/components/logout.page.component.html b/src/app/pages/account/components/logout.page.component.html new file mode 100644 index 0000000..d6bcfb8 --- /dev/null +++ b/src/app/pages/account/components/logout.page.component.html @@ -0,0 +1 @@ +
Logout
diff --git a/src/app/pages/account/components/logout.page.component.scss b/src/app/pages/account/components/logout.page.component.scss new file mode 100644 index 0000000..294bf9b --- /dev/null +++ b/src/app/pages/account/components/logout.page.component.scss @@ -0,0 +1,6 @@ +@import '~@ucap/lg-scss/mixins'; + +.logout-container { + width: 100%; + height: 100%; +} diff --git a/src/app/sections/account/components/login.section.component.spec.ts b/src/app/pages/account/components/logout.page.component.spec.ts similarity index 65% rename from src/app/sections/account/components/login.section.component.spec.ts rename to src/app/pages/account/components/logout.page.component.spec.ts index 15a95af..09cbb17 100644 --- a/src/app/sections/account/components/login.section.component.spec.ts +++ b/src/app/pages/account/components/logout.page.component.spec.ts @@ -1,28 +1,28 @@ import { TestBed, async } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { LoginSectionComponent } from './login.section.component'; +import { LogoutPageComponent } from './logout.page.component'; -describe('app::sections::account::LoginSectionComponent', () => { +describe('app::pages::account::LogoutPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule], - declarations: [LoginSectionComponent] + declarations: [LogoutPageComponent] }).compileComponents(); })); it('should create the app', () => { - const fixture = TestBed.createComponent(LoginSectionComponent); + const fixture = TestBed.createComponent(LogoutPageComponent); const app = fixture.componentInstance; expect(app).toBeTruthy(); }); it(`should have as title 'ucap-lg-web'`, () => { - const fixture = TestBed.createComponent(LoginSectionComponent); + const fixture = TestBed.createComponent(LogoutPageComponent); const app = fixture.componentInstance; }); it('should render title', () => { - const fixture = TestBed.createComponent(LoginSectionComponent); + const fixture = TestBed.createComponent(LogoutPageComponent); fixture.detectChanges(); const compiled = fixture.nativeElement; expect(compiled.querySelector('.content span').textContent).toContain( diff --git a/src/app/pages/account/components/logout.page.component.ts b/src/app/pages/account/components/logout.page.component.ts new file mode 100644 index 0000000..5db26a2 --- /dev/null +++ b/src/app/pages/account/components/logout.page.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; + +import { LocalStorageService } from '@ucap/ng-web-storage'; + +import { environment } from '@environments'; + +import { UserStore } from '@app/models/user-store'; +import { AppKey } from '@app/types/app-key.type'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'app-pages-account-logout', + templateUrl: './logout.page.component.html', + styleUrls: ['./logout.page.component.scss'] +}) +export class LogoutPageComponent implements OnInit, OnDestroy { + private ngOnDestroySubject: Subject = new Subject(); + + constructor(private localStorageService: LocalStorageService) {} + + ngOnInit(): void {} + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } +} diff --git a/src/app/pages/chat/chat.page.module.ts b/src/app/pages/chat/chat.page.module.ts index d49c340..60cff30 100644 --- a/src/app/pages/chat/chat.page.module.ts +++ b/src/app/pages/chat/chat.page.module.ts @@ -8,6 +8,7 @@ import { MatMenuModule } from '@angular/material/menu'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatButtonModule } from '@angular/material/button'; import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { AppChatSectionModule } from '@app/sections/chat/chat.section.module'; @@ -27,6 +28,7 @@ import { UCAP_I18N_NAMESPACE, I18nModule } from '@ucap/ng-i18n'; MatCheckboxModule, MatButtonModule, MatSidenavModule, + MatTooltipModule, AppChatSectionModule, AppChatRoutingPageModule, diff --git a/src/app/pages/chat/components/chat-room.page.component.html b/src/app/pages/chat/components/chat-room.page.component.html index ddde23d..e2f0dd5 100644 --- a/src/app/pages/chat/components/chat-room.page.component.html +++ b/src/app/pages/chat/components/chat-room.page.component.html @@ -3,22 +3,72 @@
-
- -
-
- +
+
+ +
+
+ +
- Right Sections. + + + + + + +
diff --git a/src/app/pages/chat/components/chat-room.page.component.scss b/src/app/pages/chat/components/chat-room.page.component.scss index e69de29..3e1d1a2 100644 --- a/src/app/pages/chat/components/chat-room.page.component.scss +++ b/src/app/pages/chat/components/chat-room.page.component.scss @@ -0,0 +1,33 @@ +@import '~@ucap/lg-scss/mixins'; + +.contents-main { + position: relative; + .subtitle { + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16); + position: relative; + z-index: 4; + } + .message-box-container { + overflow: hidden; + height: 100%; + .message-area { + position: relative; + height: 100%; + overflow: hidden; + } + .message-input { + max-height: 70%; + overflow-x: hidden; + overflow-y: auto; + padding: 1px 0; + background-color: $white; + } + } + .rightDrawer { + min-width: 360px; + max-width: 100%; + @include screen(xs) { + min-width: 100%; + } + } +} diff --git a/src/app/pages/chat/components/chat-room.page.component.ts b/src/app/pages/chat/components/chat-room.page.component.ts index 6205d58..8cf2b5b 100644 --- a/src/app/pages/chat/components/chat-room.page.component.ts +++ b/src/app/pages/chat/components/chat-room.page.component.ts @@ -3,8 +3,13 @@ import { ActivatedRoute, Params } from '@angular/router'; import { MatDrawer } from '@angular/material/sidenav'; -import { Subscription } from 'rxjs'; +import { Subscription, Subject, BehaviorSubject } from 'rxjs'; import { QueryParams } from '../types/params.type'; +import { ChatDrawType } from '../types/chat-draw.type'; +import { takeUntil } from 'rxjs/operators'; +import { DrawInfo } from '../models/draw-info'; +import { Store } from '@ngrx/store'; +import { RoomActions, ChattingActions } from '@ucap/ng-store-chat'; @Component({ selector: 'app-pages-chat-room', @@ -12,30 +17,65 @@ import { QueryParams } from '../types/params.type'; styleUrls: ['./chat-room.page.component.scss'] }) export class ChatRoomPageComponent implements OnInit, OnDestroy { - private paramsSubscription: Subscription; isChatSearch = false; roomId: string; + translationSimpleview = false; + + drawerType: ChatDrawType | null; + returnDrawerType: ChatDrawType | null; + eventSendTriggerSubject: BehaviorSubject = new BehaviorSubject(0); @ViewChild('chatRightDrawer', { static: false }) chatRightDrawer: MatDrawer; - constructor(private activatedRoute: ActivatedRoute) {} + ChatDrawType = ChatDrawType; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private store: Store, + private activatedRoute: ActivatedRoute + ) {} ngOnInit(): void { - this.paramsSubscription = this.activatedRoute.queryParams.subscribe( - (params: Params) => { + this.activatedRoute.queryParams + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe((params: Params) => { const seqParam = params[QueryParams.ROOM_ID]; + // initializing by roomId Change. + if (this.roomId !== seqParam) { + if (!!this.chatRightDrawer) { + this.chatRightDrawer.close(); + } + } + + // setting roomId. this.roomId = !!seqParam ? seqParam : undefined; - } - ); + }); } ngOnDestroy(): void { - if (!!this.paramsSubscription) { - this.paramsSubscription.unsubscribe(); + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); } + + if (!!this.eventSendTriggerSubject) { + this.eventSendTriggerSubject.complete(); + } + + // this.store.dispatch(RoomActions.clearSelectedRoom({ roomId: this.roomId })); + this.store.dispatch(ChattingActions.clearActiveRoomId({})); } - onRightDrawerToggle(): void { - this.chatRightDrawer.toggle(); + + onRightDrawerToggle(type: DrawInfo | null): void { + this.drawerType = type.chatDrawType; + this.returnDrawerType = !!type.returnDrawType ? type.returnDrawType : null; + this.chatRightDrawer.open(); + } + onRightDrawerClose(): void { + this.drawerType = null; + this.returnDrawerType = null; + this.chatRightDrawer.close(); } } diff --git a/src/app/pages/chat/components/index.page.component.html b/src/app/pages/chat/components/index.page.component.html index b5bbc78..7624710 100644 --- a/src/app/pages/chat/components/index.page.component.html +++ b/src/app/pages/chat/components/index.page.component.html @@ -1 +1,39 @@ -Index page of chat is works! +
+
+ + + + + + + + + +
+

+ {{ 'chat:room.noSelectRoom' | ucapI18n }} +

+
diff --git a/src/app/pages/chat/components/index.page.component.scss b/src/app/pages/chat/components/index.page.component.scss index e69de29..49addc5 100644 --- a/src/app/pages/chat/components/index.page.component.scss +++ b/src/app/pages/chat/components/index.page.component.scss @@ -0,0 +1,21 @@ +.index-page-chat-info { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .ico-page-chat { + width: 166px; + height: 142px; + margin-top: -80px; + } + .chat-index-copy { + font-size: 1.429em; + color: #666; + padding: 10px 20px; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + margin: 50px 0 0; + } +} diff --git a/src/app/pages/chat/components/sidenav.page.component.html b/src/app/pages/chat/components/sidenav.page.component.html index 6904461..09d3353 100644 --- a/src/app/pages/chat/components/sidenav.page.component.html +++ b/src/app/pages/chat/components/sidenav.page.component.html @@ -1,13 +1,41 @@
-

{{ 'label.chat' | ucapI18n }}

+

{{ 'chat:label.chat' | ucapI18n }}

- +
- + - - - diff --git a/src/app/pages/group/components/sidenav.page.component.scss b/src/app/pages/group/components/sidenav.page.component.scss index 5a24e7f..b09bab2 100644 --- a/src/app/pages/group/components/sidenav.page.component.scss +++ b/src/app/pages/group/components/sidenav.page.component.scss @@ -1,6 +1,6 @@ @import '~@ucap/lg-scss/mixins'; -.sidenav-container { +.sidenav-container.group { overflow: hidden; display: flex; flex-flow: column; @@ -24,9 +24,6 @@ align-items: center; font-weight: 600; } - .menu-btn { - justify-self: end; - } } } } diff --git a/src/app/pages/group/components/sidenav.page.component.ts b/src/app/pages/group/components/sidenav.page.component.ts index 9e0c0f8..a08f9a5 100644 --- a/src/app/pages/group/components/sidenav.page.component.ts +++ b/src/app/pages/group/components/sidenav.page.component.ts @@ -1,39 +1,46 @@ import { of, Subject } from 'rxjs'; -import { take, map, catchError, takeUntil } from 'rxjs/operators'; +import { take, map, catchError } from 'rxjs/operators'; import { Component, OnInit, OnDestroy, ChangeDetectorRef, - ViewChild + ViewChild, + ChangeDetectionStrategy } from '@angular/core'; -import { ActivatedRoute, Router, Params } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { MatDialog } from '@angular/material/dialog'; -import { ParamsUtil } from '@ucap/ng-core'; import { LogService } from '@ucap/ng-logger'; import { I18nService } from '@ucap/ng-i18n'; +import { SearchData } from '@app/ucap/organization/models/search-data'; + import { CreateDialogComponent } from '@app/sections/group/dialogs/create.dialog.component'; import { ListSectionComponent } from '@app/sections/group/components/list.section.component'; -import { SearchData } from '@app/ucap/organization/models/search-data'; -import { QueryParams } from '@app/pages/organization/types/params.type'; +import { UserInfo } from '@ucap/protocol-sync'; +import { SortViewType } from '../types/sort-view.type'; @Component({ selector: 'app-pages-group-sidenav', templateUrl: './sidenav.page.component.html', - styleUrls: ['./sidenav.page.component.scss'] + styleUrls: ['./sidenav.page.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class SidenavPageComponent implements OnInit, OnDestroy { @ViewChild('sectionGroupList', { static: false }) sectionGroupList: ListSectionComponent; set companySearchData(searchData: SearchData) { - this._companySearchData = searchData; + if (!!searchData && searchData.searchWord !== '') { + this._companySearchData = { ...searchData, bySearch: true }; + } else { + this._companySearchData = { ...searchData, bySearch: false }; + } } get companySearchData() { return this._companySearchData; @@ -41,9 +48,10 @@ export class SidenavPageComponent implements OnInit, OnDestroy { // tslint:disable-next-line: variable-name _companySearchData: SearchData; - showType: string; + showType: SortViewType; + sortViewType = SortViewType; - private ngOnDestroySubject: Subject; + private ngOnDestroySubject: Subject = new Subject(); constructor( private activatedRoute: ActivatedRoute, @@ -53,17 +61,19 @@ export class SidenavPageComponent implements OnInit, OnDestroy { private store: Store, private changeDetectorRef: ChangeDetectorRef, public dialog: MatDialog - ) { - this.i18nService.setDefaultNamespace('group'); - } + ) {} ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - - this.showType = 'ALL'; + this.showType = SortViewType.all; + this.showGroupMenuIcon(SortViewType.all); } - ngOnDestroy(): void {} + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } onClickFab(event: MouseEvent) {} @@ -72,8 +82,7 @@ export class SidenavPageComponent implements OnInit, OnDestroy { case 'GROUP_NEW': { const dialogRef = this.dialog.open(CreateDialogComponent, { - width: '100%', - height: '100%' + panelClass: 'max-create-dialog' }); dialogRef @@ -105,23 +114,48 @@ export class SidenavPageComponent implements OnInit, OnDestroy { } } - onClickShowGroupMenu(menuType: string) { + onClickShowGroupMenu(menuType: SortViewType) { switch (menuType) { - case 'ALL': + case SortViewType.all: { - this.showType = 'ALL'; + this.showType = SortViewType.all; } break; - case 'ONLINE_BUDDY': + case SortViewType.onlineBuddy: { - this.showType = 'ONLINE_BUDDY'; + this.showType = SortViewType.onlineBuddy; } break; - case 'ON_OFF': + case SortViewType.onOff: { - this.showType = 'ON_OFF'; + this.showType = SortViewType.onOff; } break; } } + onSearchCancel() { + this.companySearchData = { ...this.companySearchData, searchWord: '' }; + } + + showGroupMenuIcon(menuType: SortViewType): string { + if (this.showType === menuType) { + return 'check_circle'; + } + + return 'check_circle_outline'; + } + + onClickUser(userInfo: UserInfo) { + this.router.navigate( + [ + 'group', + { + outlets: { content: 'index' } + } + ], + { + queryParams: { id: Number(userInfo.seq) } + } + ); + } } diff --git a/src/app/pages/group/group.page.module.ts b/src/app/pages/group/group.page.module.ts index 0d6ae50..12cf04c 100644 --- a/src/app/pages/group/group.page.module.ts +++ b/src/app/pages/group/group.page.module.ts @@ -6,6 +6,7 @@ import { FlexLayoutModule } from '@angular/flex-layout'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; +import { MatTabsModule } from '@angular/material/tabs'; import { UiModule } from '@ucap/ng-ui'; @@ -26,6 +27,7 @@ import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; MatButtonModule, MatIconModule, MatMenuModule, + MatTabsModule, AppOrganizationModule, diff --git a/src/app/pages/group/types/sort-view.type.ts b/src/app/pages/group/types/sort-view.type.ts new file mode 100644 index 0000000..85d2309 --- /dev/null +++ b/src/app/pages/group/types/sort-view.type.ts @@ -0,0 +1,5 @@ +export enum SortViewType { + all = 'ALL', + onlineBuddy = 'ONLINE_BUDDY', + onOff = 'ON_OFF' +} diff --git a/src/app/pages/message/components/index.page.component.html b/src/app/pages/message/components/index.page.component.html index 3920a95..102cc60 100644 --- a/src/app/pages/message/components/index.page.component.html +++ b/src/app/pages/message/components/index.page.component.html @@ -1 +1,8 @@ -Index page of message is works! + +
+
+
+ Coming Soon + 곧 새로운 모습으로 찾아 뵙겠습니다. +
+
diff --git a/src/app/pages/message/components/index.page.component.scss b/src/app/pages/message/components/index.page.component.scss index e69de29..81699d2 100644 --- a/src/app/pages/message/components/index.page.component.scss +++ b/src/app/pages/message/components/index.page.component.scss @@ -0,0 +1,33 @@ +.index-page-empty { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .ico-coming-soon { + width: 200px; + height: 200px; + margin-top: -100px; + background-image: url(../../../../assets/images/ico/img_coming_soon.png); + background-size: 100% auto; + } + .coming-soon-index-copy { + text-align: center; + font-size: 2.4em; + color: #666; + font-weight: 600; + padding: 0 20px; + //border-top: 1px solid #ccc; + //border-bottom: 1px solid #ccc; + span { + display: block; + } + .guide-text { + padding-top: 10px; + font-size: 0.54em; + color: #999; + font-weight: normal; + } + } +} diff --git a/src/app/pages/organization/components/index.page.component.html b/src/app/pages/organization/components/index.page.component.html index aedd9e3..ef04481 100644 --- a/src/app/pages/organization/components/index.page.component.html +++ b/src/app/pages/organization/components/index.page.component.html @@ -1,7 +1,10 @@
- +
diff --git a/src/app/pages/organization/components/index.page.component.scss b/src/app/pages/organization/components/index.page.component.scss index 360f071..644d01c 100644 --- a/src/app/pages/organization/components/index.page.component.scss +++ b/src/app/pages/organization/components/index.page.component.scss @@ -1,4 +1,5 @@ .index-page-container { width: 100%; height: 100%; + overflow: hidden; } diff --git a/src/app/pages/organization/components/index.page.component.ts b/src/app/pages/organization/components/index.page.component.ts index 1393e9c..879f70f 100644 --- a/src/app/pages/organization/components/index.page.component.ts +++ b/src/app/pages/organization/components/index.page.component.ts @@ -1,21 +1,28 @@ import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core'; +import { + Component, + OnInit, + OnDestroy, + ChangeDetectorRef, + ChangeDetectionStrategy +} from '@angular/core'; import { ActivatedRoute, Router, Params } from '@angular/router'; -import { Store } from '@ngrx/store'; - import { ParamsUtil } from '@ucap/ng-core'; +import { AppAuthenticationService } from '@app/services/app-authentication.service'; import { SearchData } from '@app/ucap/organization/models/search-data'; import { QueryParams } from '../types/params.type'; +import { UserStore } from '@app/models/user-store'; @Component({ selector: 'app-pages-organization-index', templateUrl: './index.page.component.html', - styleUrls: ['./index.page.component.scss'] + styleUrls: ['./index.page.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class IndexPageComponent implements OnInit, OnDestroy { set companySearchData(searchData: SearchData) { @@ -32,18 +39,19 @@ export class IndexPageComponent implements OnInit, OnDestroy { deptSeq: string; - private ngOnDestroySubject: Subject; + private ngOnDestroySubject: Subject = new Subject(); + private userStore: UserStore; constructor( - private store: Store, + private appAuthenticationService: AppAuthenticationService, private router: Router, private activatedRoute: ActivatedRoute, private changeDetectorRef: ChangeDetectorRef - ) {} + ) { + this.userStore = this.appAuthenticationService.getUserStore(); + } ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - this.activatedRoute.queryParams .pipe(takeUntil(this.ngOnDestroySubject)) .subscribe((params) => { @@ -57,22 +65,31 @@ export class IndexPageComponent implements OnInit, OnDestroy { false ); + if (!!deptSeq && this.deptSeq !== deptSeq) { + this.deptSeq = deptSeq; + } + this.deptSearchData = { deptSeq: bySearch ? undefined : deptSeq, - companyCode: bySearch ? companyCode : undefined, - searchWord: bySearch ? searchWord : undefined, + companyCode: !!companyCode + ? companyCode + : this.userStore.companyCode, + searchWord: bySearch ? decodeURIComponent(searchWord) : undefined, bySearch }; this._companySearchData = { ...this.deptSearchData }; + + this.changeDetectorRef.markForCheck(); } }); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } @@ -80,9 +97,21 @@ export class IndexPageComponent implements OnInit, OnDestroy { onChangedCompanySearch() { const queryParams: Params = {}; queryParams[QueryParams.COMPANY_CODE] = this._companySearchData.companyCode; - queryParams[QueryParams.SEARCH_WORD] = this._companySearchData.searchWord; + queryParams[QueryParams.SEARCH_WORD] = encodeURIComponent( + this._companySearchData.searchWord + ); queryParams[QueryParams.BY_SEARCH] = String(true); + this._navigate(queryParams); + } + + onCanceledSearch() { + const queryParams: Params = {}; + queryParams[QueryParams.DEPT_SEQ] = String(this.deptSeq); + this._navigate(queryParams); + } + + private _navigate(queryParams: Params = {}) { this.router.navigate( [ 'organization', diff --git a/src/app/pages/organization/components/sidenav.page.component.html b/src/app/pages/organization/components/sidenav.page.component.html index abde0bc..e351468 100644 --- a/src/app/pages/organization/components/sidenav.page.component.html +++ b/src/app/pages/organization/components/sidenav.page.component.html @@ -1,22 +1,12 @@
-

조직도

-
- -
- -
- businessLG CNS +
+ business{{ displayRootDept | ucapOrganizationTranslate: 'name' }}
@@ -26,8 +16,3 @@ >
- - - - - diff --git a/src/app/pages/organization/components/sidenav.page.component.ts b/src/app/pages/organization/components/sidenav.page.component.ts index 7b496bb..67beca3 100644 --- a/src/app/pages/organization/components/sidenav.page.component.ts +++ b/src/app/pages/organization/components/sidenav.page.component.ts @@ -1,27 +1,44 @@ -import { Subject } from 'rxjs'; +import { Subject, combineLatest } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core'; +import { + Component, + OnInit, + OnDestroy, + ChangeDetectorRef, + ChangeDetectionStrategy +} from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; +import { Store, select } from '@ngrx/store'; + import { LogService } from '@ucap/ng-logger'; import { DeptInfo } from '@ucap/protocol-query'; +import { DepartmentSelector, UserSelector } from '@ucap/ng-store-organization'; +import { LoginSelector } from '@ucap/ng-store-authentication'; + +import { environment } from '@environments'; + import { QueryParams } from '../types/params.type'; @Component({ selector: 'app-pages-ogranization-sidenav', templateUrl: './sidenav.page.component.html', - styleUrls: ['./sidenav.page.component.scss'] + styleUrls: ['./sidenav.page.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush }) export class SidenavPageComponent implements OnInit, OnDestroy { initialExpanded: number; + displayRoot = false; + displayRootDept: DeptInfo; - private ngOnDestroySubject: Subject; + private ngOnDestroySubject: Subject = new Subject(); constructor( private router: Router, private activatedRoute: ActivatedRoute, + private store: Store, private changeDetectorRef: ChangeDetectorRef, private logService: LogService ) { @@ -29,22 +46,56 @@ export class SidenavPageComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); + // this.activatedRoute.queryParams + // .pipe(takeUntil(this.ngOnDestroySubject)) + // .subscribe((params) => { + // if (!!params) { + // const deptSeq = params[QueryParams.DEPT_SEQ]; + // if (!!deptSeq) { + // this.initialExpanded = Number(deptSeq); + // } + // } + // }); - this.activatedRoute.queryParams + combineLatest([ + this.activatedRoute.queryParams, + this.store.pipe(select(UserSelector.user)) + ]) .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe((params) => { + .subscribe(([params, user]) => { + let existParams = false; if (!!params) { const deptSeq = params[QueryParams.DEPT_SEQ]; if (!!deptSeq) { + existParams = true; this.initialExpanded = Number(deptSeq); } } + + if (!existParams) { + this.initialExpanded = user.departmentCode; + } + }); + + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(DepartmentSelector.departmentInfoList) + ) + .subscribe((deptInfoList) => { + if (!environment.productConfig.organization.displayRoot) { + if (!!deptInfoList && deptInfoList.length > 0) { + this.displayRootDept = deptInfoList.find( + (item) => item.type === 'R' + ); + } + } }); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } @@ -52,7 +103,6 @@ export class SidenavPageComponent implements OnInit, OnDestroy { onClickedTree(node: DeptInfo) { const queryParams: Params = {}; queryParams[QueryParams.DEPT_SEQ] = String(node.seq); - queryParams[QueryParams.BY_SEARCH] = String(false); this.router.navigate( [ diff --git a/src/app/pages/organization/organization.page.module.ts b/src/app/pages/organization/organization.page.module.ts index fc0ac19..333d8fa 100644 --- a/src/app/pages/organization/organization.page.module.ts +++ b/src/app/pages/organization/organization.page.module.ts @@ -12,6 +12,9 @@ import { AppOrganizationSectionModule } from '@app/sections/organization/organiz import { AppOrganizationRoutingPageModule } from './organization-routing.page.module'; import { COMPONENTS } from './components'; +import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; +import { UiModule } from '@ucap/ng-ui'; +import { OrganizationUiModule } from '@ucap/ng-ui-organization'; @NgModule({ imports: [ @@ -24,9 +27,19 @@ import { COMPONENTS } from './components'; AppOrganizationModule, AppOrganizationSectionModule, - AppOrganizationRoutingPageModule + AppOrganizationRoutingPageModule, + + I18nModule, + OrganizationUiModule, + UiModule ], declarations: [...COMPONENTS], - entryComponents: [] + entryComponents: [], + providers: [ + { + provide: UCAP_I18N_NAMESPACE, + useValue: ['organization', 'common'] + } + ] }) export class AppOrganizationPageModule {} diff --git a/src/app/sections/account/account.section.module.ts b/src/app/sections/account/account.section.module.ts index e4339cc..2c1baf9 100644 --- a/src/app/sections/account/account.section.module.ts +++ b/src/app/sections/account/account.section.module.ts @@ -4,67 +4,49 @@ import { ReactiveFormsModule } from '@angular/forms'; import { FlexLayoutModule } from '@angular/flex-layout'; -import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; -import { AuthenticationUiModule } from '@ucap/ng-ui-authentication'; - import { MatButtonModule } from '@angular/material/button'; -import { MatButtonToggleModule } from '@angular/material/button-toggle'; -import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatDatepickerModule } from '@angular/material/datepicker'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; -import { MatMenuModule } from '@angular/material/menu'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatRadioModule } from '@angular/material/radio'; import { MatSelectModule } from '@angular/material/select'; -import { MatSidenavModule } from '@angular/material/sidenav'; -import { MatSliderModule } from '@angular/material/slider'; import { MatTabsModule } from '@angular/material/tabs'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { MatToolbarModule } from '@angular/material/toolbar'; + +import { I18nModule } from '@ucap/ng-i18n'; +import { UiModule } from '@ucap/ng-ui'; +import { AuthenticationUiModule } from '@ucap/ng-ui-authentication'; +import { OrganizationUiModule } from '@ucap/ng-ui-organization'; + +import { AppLayoutsModule } from '@app/layouts/layouts.module'; +import { AppOrganizationModule } from '@app/ucap/organization/organization.module'; import { COMPONENTS } from './components'; @NgModule({ imports: [ CommonModule, - FlexLayoutModule, - MatCheckboxModule, - I18nModule, - AuthenticationUiModule, - ReactiveFormsModule, + FlexLayoutModule, + MatButtonModule, - MatButtonToggleModule, - MatCardModule, - MatDatepickerModule, - MatDialogModule, + MatCheckboxModule, MatIconModule, MatInputModule, - MatMenuModule, - MatProgressBarModule, - MatProgressSpinnerModule, - MatCheckboxModule, + MatRadioModule, MatSelectModule, - MatSidenavModule, - MatSliderModule, MatTabsModule, - MatTooltipModule, - MatToolbarModule, - MatFormFieldModule, - MatSelectModule + + I18nModule, + + UiModule, + AuthenticationUiModule, + OrganizationUiModule, + + AppLayoutsModule, + AppOrganizationModule ], exports: [...COMPONENTS], declarations: [...COMPONENTS], - entryComponents: [], - providers: [ - { - provide: UCAP_I18N_NAMESPACE, - useValue: ['authentication'] - } - ] + entryComponents: [] }) export class AppAccountSectionModule {} diff --git a/src/app/sections/account/components/component-ui/login.component.html b/src/app/sections/account/components/component-ui/login.component.html deleted file mode 100644 index 0a5d579..0000000 --- a/src/app/sections/account/components/component-ui/login.component.html +++ /dev/null @@ -1,127 +0,0 @@ - diff --git a/src/app/sections/account/components/component-ui/login.component.scss b/src/app/sections/account/components/component-ui/login.component.scss deleted file mode 100644 index c2b4910..0000000 --- a/src/app/sections/account/components/component-ui/login.component.scss +++ /dev/null @@ -1,160 +0,0 @@ -@import '../../../../../assets/scss/components'; - -.login-box { - @extend %clearfix; - padding: 0 0 45px; - width: 420px; - margin: auto; - text-align: center; - flex-basis: auto; - align-items: center; - .logo-img { - display: block; - text-align: center; - img { - margin-bottom: 7px; - vertical-align: top; - @include screen(mid) { - width: 120px; - } - @include screen(xs) { - width: 100px; - margin-bottom: 6px; - } - } - } - @extend %guideline; - - .login-content { - @extend %guideline2; //Guide Line2 - margin: 30px auto 0; - .login-input-area { - border: 1px solid #cccccc; - border-radius: 2px; - width: 100%; - max-width: 420px; - min-width: 150px; - height: 60px; - background-color: $white; - margin-top: 10px; - &.login-select-form { - height: 60px; - line-height: 60px; - padding: 0 16px; - @include screen(mid) { - height: 50px; - line-height: 50px; - } - @include screen(xs) { - height: 42px; - line-height: 42px; - } - } - &:first-of-type { - margin-top: 0px; - } - &.idpass-type { - padding-left: 50px; - position: relative; - &::before { - font-family: 'material Icons'; - font-size: 24px; - text-align: center; - line-height: 60px; - content: 'perm_identity'; - display: block; - position: absolute; - top: 0; - left: 16px; - @include screen(mid) { - line-height: 50px; - } - @include screen(xs) { - line-height: 42px; - } - } - &.pass-type { - &::before { - content: 'https'; - } - } - .login-idpass-txt { - width: 368px; - height: 60px; - line-height: 60px; - font-size: 14px; - @include screen(mid) { - width: 358 - 60 + px; - height: 50px; - line-height: 50px; - font-size: 14px; - } - @include screen(xs) { - width: 308 - 60 + px; - font-size: 14px; - height: 42px; - line-height: 42px; - } - input { - font-size: 18px; - line-height: 58px; - margin-top: 0; - vertical-align: top; - background-color: $white; - padding: 0 10px 0 5px; - @include screen(mid) { - font-size: 16px; - line-height: 48px; - } - @include screen(xs) { - font-size: 14px; - line-height: 40px; - } - } - } - } - @include screen(mid) { - margin-top: 8px; - } - } - .login-input-submit { - width: 100%; - height: 60px; - background-color: $black; - border-radius: 2px; - color: $white; - font-size: 20px; - @include font-family($font-semibold); - border: 0; - margin-top: 12px; - font-weight: 600; - cursor: pointer; - @include screen(mid) { - margin-top: 8px; - font-size: 16px; - height: 50px; - } - @include screen(xs) { - font-size: 14px; - height: 42px; - } - } - @include screen(mid) { - margin-top: 23px; - width: 350px; - .login-input-area { - height: 50px; - } - } - @include screen(xs) { - margin-top: 23px; - width: 300px; - .login-input-area { - height: 42px; - } - } - } -} -.login-company { - width: 100%; -} diff --git a/src/app/sections/account/components/component-ui/login.component.spec.ts b/src/app/sections/account/components/component-ui/login.component.spec.ts deleted file mode 100644 index a9ba1f3..0000000 --- a/src/app/sections/account/components/component-ui/login.component.spec.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { LoginComponent } from './login.component'; -import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; -import { CommonModule } from '@angular/common'; -import { ChangeDetectorRef } from '@angular/core'; -import { I18nService, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; -import { AuthenticationUiModule } from '../authentication-ui.module'; -import { MatSelectModule } from '@angular/material/select'; -import { Company } from '@ucap/api-external'; -import { LogService } from '@ucap/ng-logger'; -import { BrowserModule } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCheckboxModule } from '@angular/material/checkbox'; -import { MatIconModule } from '@angular/material/icon'; -import { MatInputModule } from '@angular/material/input'; -import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; - -describe('ui::authentication::LoginComponent', () => { - let component: LoginComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [ - BrowserModule, - BrowserAnimationsModule, - - CommonModule, - ReactiveFormsModule, - - MatButtonModule, - MatCheckboxModule, - MatFormFieldModule, - MatIconModule, - MatInputModule, - MatProgressSpinnerModule, - MatSelectModule - ], - providers: [ - AuthenticationUiModule, - // { provide: FormBuilder, useValue: new FormBuilder() }, - // { provide: ChangeDetectorRef, useValue: ChangeDetectorRef }, - { provide: I18nService, useValue: new I18nService(new LogService({})) }, - { - provide: UCAP_I18N_NAMESPACE, - useValue: 'authentication' - } - ] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(LoginComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - component.companyList = [ - { companyName: 'LG CNS', companyCode: 'GUC100' }, - { companyName: 'LG UCAP', companyCode: 'GUC101' } - ] as Company[]; - component.loginId = 'test'; - component.companyCode = 'GUC100'; - fixture.detectChanges(); - - component.ngOnInit(); - - expect(component).toBeTruthy(); - }); - - it('login', (done) => { - component.companyList = [ - { companyName: 'LG CNS', companyCode: 'GUC100' }, - { companyName: 'LG UCAP', companyCode: 'GUC101' } - ] as Company[]; - component.loginId = 'test'; - component.companyCode = 'GUC100'; - - component.ngOnInit(); - - component.login.subscribe((value) => { - console.log(value); - done(); - }); - - component.onClickLogin(); - }); -}); diff --git a/src/app/sections/account/components/component-ui/login.component.ts b/src/app/sections/account/components/component-ui/login.component.ts deleted file mode 100644 index 168da02..0000000 --- a/src/app/sections/account/components/component-ui/login.component.ts +++ /dev/null @@ -1,110 +0,0 @@ -import moment from 'moment'; - -import { - Component, - OnInit, - Input, - Output, - EventEmitter, - ViewChild, - ElementRef, - ChangeDetectorRef -} from '@angular/core'; -import { - FormGroup, - FormBuilder, - Validators, - FormControl, - ValidatorFn -} from '@angular/forms'; -import { Company } from '@ucap/api-external'; -import { LoginTry } from '@ucap/pi'; - -@Component({ - selector: 'ucap-authentication-login-local', - templateUrl: './login.component.html', - styleUrls: ['./login.component.scss'] -}) -export class LoginComponent implements OnInit { - @Input() - companyList: Company[]; - - @Input() - fixedCompanyCode: string; - - @Input() - companyCode: string; - - @Input() - loginId: string; - - @Input() - disable = false; - - @Input() - processing = false; - - @Input() - loginTry: LoginTry; - - @Output() - login = new EventEmitter<{ - companyCode: string; - loginId: string; - loginPw: string; - notValid: () => void; - }>(); - - @ViewChild('loginPw', { static: true }) loginPwElementRef: ElementRef; - - loginForm: FormGroup; - companyCodeFormControl = new FormControl(''); - loginIdFormControl = new FormControl(''); - loginPwFormControl = new FormControl(''); - loginFailed = false; - - moment = moment; - - constructor( - private formBuilder: FormBuilder, - private changeDetectorRef: ChangeDetectorRef - ) {} - - ngOnInit() { - const companyCodeValidators: ValidatorFn[] = [Validators.required]; - this.companyCodeFormControl.setValidators(companyCodeValidators); - if (!!this.fixedCompanyCode) { - this.companyCodeFormControl.setValue(this.fixedCompanyCode); - } - if (!!this.companyCode) { - this.companyCodeFormControl.setValue(this.companyCode); - } - const loginIdValidators: ValidatorFn[] = [Validators.required]; - this.loginIdFormControl.setValidators(loginIdValidators); - if (!!this.loginId) { - this.loginIdFormControl.setValue(this.loginId); - } - const loginPwValidators: ValidatorFn[] = [Validators.required]; - this.loginPwFormControl.setValidators(loginPwValidators); - - this.loginForm = this.formBuilder.group({ - companyCodeFormControl: this.companyCodeFormControl, - loginIdFormControl: this.loginIdFormControl, - loginPwFormControl: this.loginPwFormControl - }); - - this.changeDetectorRef.detectChanges(); - } - - onClickLogin() { - this.login.emit({ - companyCode: this.loginForm.get('companyCodeFormControl').value, - loginId: this.loginForm.get('loginIdFormControl').value, - loginPw: this.loginForm.get('loginPwFormControl').value, - notValid: () => { - this.loginFailed = true; - this.loginPwElementRef.nativeElement.focus(); - } - }); - } -} diff --git a/src/app/sections/account/components/login.section.component.html b/src/app/sections/account/components/login.section.component.html deleted file mode 100644 index f37b25c..0000000 --- a/src/app/sections/account/components/login.section.component.html +++ /dev/null @@ -1,67 +0,0 @@ - diff --git a/src/app/sections/account/components/login.section.component.scss b/src/app/sections/account/components/login.section.component.scss deleted file mode 100644 index a35480b..0000000 --- a/src/app/sections/account/components/login.section.component.scss +++ /dev/null @@ -1,117 +0,0 @@ -@import '../../../../assets/scss/components'; - -h1 { - @include font-family($font-light); - font-size: 24px; - text-align: center; - color: $txt-color01; - font-weight: 600; - line-height: 1.2; - @include screen(mid) { - font-size: 19px; - } - @include screen(xs) { - font-size: 14px; - } -} - -.login-section-container { - width: 100%; - height: 100%; - overflow: auto; -} - -.login-chk-area { - margin-top: 6px; - font-size: 13px; - text-align: left; - @include screen(xs) { - font-size: 12px; - } -} -.login-pass-info { - overflow: hidden; - margin-top: 83px; - ul { - display: flex; - justify-content: center; - li { - height: 24px; - position: relative; - display: inline-flex; - align-items: center; - padding: 0 12% 0 8%; - &::before { - content: ''; - height: 11px; - width: 1px; - display: flex; - background-color: $gray-re4a; - position: absolute; - top: 6.5px; - left: 0; - } - &:first-child { - padding-left: 0; - &::before { - display: none; - } - } - &:last-child { - padding-right: 0; - } - a { - line-height: 24px; - font-size: 12px; - color: $gray-re4a; - padding-left: 34px; - position: relative; - white-space: nowrap; - &::before { - font-family: 'material Icons'; - font-size: 18px; - text-align: center; - content: 'search'; - color: $white; - display: block; - width: 24px; - height: 24px; - border-radius: 50%; - background-color: $black; - position: absolute; - top: 0; - left: 0; - } - &.fir-pass { - &::before { - content: 'sync'; - } - } - } - } - } -} - -.login-button-area { - margin-top: 14px; - @include screen(xs) { - margin-top: 20px; - } - button { - border: 0; - margin: 0; - width: 100%; - height: 46px; - border-radius: 4px; - background-color: #e0e3e7; - font-size: 12px; - color: $gray-re4a; - cursor: pointer; - @include screen(mid) { - height: 38px; - } - @include screen(xs) { - height: 34px; - } - } -} diff --git a/src/app/sections/account/components/login.section.component.ts b/src/app/sections/account/components/login.section.component.ts deleted file mode 100644 index 3f2b6ae..0000000 --- a/src/app/sections/account/components/login.section.component.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { Subject } from 'rxjs'; -import { take, takeUntil } from 'rxjs/operators'; - -import { Component, OnInit, OnDestroy, Input, ViewChild } from '@angular/core'; - -import { MatCheckbox } from '@angular/material/checkbox'; - -import { Store, select } from '@ngrx/store'; - -import { Company } from '@ucap/api-external'; -import { LoginTry } from '@ucap/pi'; - -import { LogService } from '@ucap/ng-logger'; -import { I18nService } from '@ucap/ng-i18n'; -import { SessionStorageService } from '@ucap/ng-web-storage'; -import { PiService } from '@ucap/ng-pi'; -import { ProtocolService } from '@ucap/ng-protocol'; -import { CompanyActions, CompanySelector } from '@ucap/ng-store-organization'; -import { LoginActions } from '@ucap/ng-store-authentication'; - -import { UserStore } from '@app/models/user-store'; -import { LoginSession } from '@app/models/login-session'; -import { AppKey } from '@app/types/app-key.type'; -import { AppAuthenticationService } from '@app/services/app-authentication.service'; - -@Component({ - selector: 'app-sections-account-login', - templateUrl: './login.section.component.html', - styleUrls: ['./login.section.component.scss'] -}) -export class LoginSectionComponent implements OnInit, OnDestroy { - @Input() - companyGroupCode: string; - - @Input() - fixedCompanyCode: string; - - @Input() - userStore: UserStore; - - @Input() - useRememberMe: boolean; - - @Input() - useAutoLogin: boolean; - - @ViewChild('chkUseRememberMe', { static: false }) - chkUseRememberMe: MatCheckbox; - - @ViewChild('chkUseAutoLogin', { static: false }) - chkUseAutoLogin: MatCheckbox; - - loginSession: LoginSession; - companyList: Company[]; - disableLoginForm = false; - loginProcessing = false; - loginTry: LoginTry; - - private ngOnDestroySubject = new Subject(); - - constructor( - private piService: PiService, - private protocolService: ProtocolService, - private sessionStorageService: SessionStorageService, - private i18nService: I18nService, - private store: Store, - private appAuthenticationService: AppAuthenticationService, - private logService: LogService - ) {} - - ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - - this.appAuthenticationService - .getLoginSession$() - .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe((loginSession) => (this.loginSession = loginSession)); - - this.sessionStorageService - .get$(AppKey.LoginTry) - .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe((loginTry) => (this.loginTry = loginTry)); - - this.protocolService.disconnect(); - - this.store.dispatch( - CompanyActions.companies({ - req: { companyGroupCode: this.companyGroupCode } - }) - ); - - this.store - .pipe( - takeUntil(this.ngOnDestroySubject), - select(CompanySelector.companyList) - ) - .subscribe((companyList) => { - this.companyList = companyList; - }); - } - - ngOnDestroy(): void { - if (!!this.ngOnDestroySubject) { - this.ngOnDestroySubject.complete(); - } - } - - onLogin(event: { - companyCode: string; - loginId: string; - loginPw: string; - notValid: () => void; - }) { - const useRememberMe: boolean = this.chkUseRememberMe.checked; - const useAutoLogin: boolean = this.chkUseAutoLogin.checked; - - this.disableLoginForm = true; - this.loginProcessing = true; - - this.piService - .login2({ - companyCode: event.companyCode, - loginId: event.loginId, - loginPw: event.loginPw, - deviceType: this.loginSession.deviceType - }) - .pipe(take(1)) - .subscribe( - (res) => { - if ('success' !== res.status.toLowerCase()) { - this.onWebLoginFailure(event, res.status); - return; - } else { - this.store.dispatch( - LoginActions.webLoginSuccess({ - companyCode: event.companyCode, - loginId: event.loginId, - loginPw: event.loginPw, - autoLogin: useAutoLogin, - rememberMe: useRememberMe, - login2Response: res - }) - ); - return; - } - }, - (error) => { - this.onWebLoginFailure(event, error); - }, - () => { - this.disableLoginForm = false; - this.loginProcessing = false; - } - ); - } - - onClickForgotPassword(lng: string) { - this.i18nService.changeLanguage(lng); - } - - private onWebLoginFailure( - event: { - companyCode: string; - loginId: string; - loginPw: string; - notValid: () => void; - }, - error: any - ) { - this.store.dispatch(LoginActions.webLoginFailure({ error })); - - event.notValid(); - } -} diff --git a/src/app/sections/chat/chat.section.module.ts b/src/app/sections/chat/chat.section.module.ts index 88a834a..bf706f0 100644 --- a/src/app/sections/chat/chat.section.module.ts +++ b/src/app/sections/chat/chat.section.module.ts @@ -21,18 +21,28 @@ import { MatTreeModule } from '@angular/material/tree'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatStepperModule } from '@angular/material/stepper'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatTabsModule } from '@angular/material/tabs'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar'; import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; import { UiModule } from '@ucap/ng-ui'; import { ChatUiModule } from '@ucap/ng-ui-chat'; +import { OrganizationUiModule } from '@ucap/ng-ui-organization'; +import { AppOrganizationModule } from '@app/ucap/organization/organization.module'; + import { AppChatModule } from '@app/ucap/chat/chat.module'; import { AppLayoutsModule } from '@app/layouts/layouts.module'; import { AppGroupSectionModule } from '../group/group.section.module'; import { COMPONENTS } from './components'; import { DIALOGS } from './dialogs'; +import { DRAWERS } from './drawers'; +import { AppGroupModule } from '@app/ucap/group/group.module'; @NgModule({ imports: [ @@ -56,6 +66,11 @@ import { DIALOGS } from './dialogs'; MatTreeModule, MatTooltipModule, MatStepperModule, + MatDividerModule, + MatSlideToggleModule, + MatRadioModule, + MatTabsModule, + MatProgressBarModule, PerfectScrollbarModule, ScrollingModule, @@ -65,12 +80,16 @@ import { DIALOGS } from './dialogs'; AppLayoutsModule, AppGroupSectionModule, + AppOrganizationModule, ChatUiModule, - AppChatModule + OrganizationUiModule, + + AppChatModule, + AppGroupModule ], - exports: [...COMPONENTS, ...DIALOGS], - declarations: [...COMPONENTS, ...DIALOGS], - entryComponents: [...DIALOGS], + exports: [...COMPONENTS, ...DIALOGS, ...DRAWERS], + declarations: [...COMPONENTS, ...DIALOGS, ...DRAWERS], + entryComponents: [...DIALOGS, ...DRAWERS], providers: [ { provide: UCAP_I18N_NAMESPACE, diff --git a/src/app/sections/chat/components/chat-search.section.component.html b/src/app/sections/chat/components/chat-search.section.component.html index 02f1d49..1761c2b 100644 --- a/src/app/sections/chat/components/chat-search.section.component.html +++ b/src/app/sections/chat/components/chat-search.section.component.html @@ -1,13 +1,11 @@ -
+
- N건의 검색결과가 있습니다. - 1/N + N건의 검색결과가 있습니다. + 1/N
diff --git a/src/app/sections/chat/components/chat-search.section.component.scss b/src/app/sections/chat/components/chat-search.section.component.scss index e69de29..9df7e86 100644 --- a/src/app/sections/chat/components/chat-search.section.component.scss +++ b/src/app/sections/chat/components/chat-search.section.component.scss @@ -0,0 +1,78 @@ +@import '~@ucap/lg-scss/mixins'; + +.search-container { + padding: 0 16px; + background-color: $white; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16); + display: flex; + flex-direction: column; + align-items: center; + //position: relative; + //포지션 변경 + position: absolute; + z-index: 5; + top: -80px; + width: 100%; + visibility: hidden; + transition: all 0.2s linear; + .searchbox { + display: flex; + flex-flow: row nowrap; + width: 100%; + max-width: 900px; + border: 1px solid $lipstick; + background-color: $white; + .search-in-box { + @include ucapMatFormField(0, 0, 100%, auto, auto, 38px, 38px); + padding-left: 10px; + .btn-close { + margin-top: 2px; + color: #fd78a1 !important; + } + } + .btn-ico-search { + @include ucapMatButton(38px, 38px, 0, 20px); + } + } + .search-result { + width: 100%; + max-width: 900px; + display: flex; + flex-flow: row nowrap; + height: 40px; + justify-content: space-between; + align-items: center; + .text { + flex-grow: 1; + font-size: 0.857em; + color: $gray-re3; + strong { + font-size: 0.929em; + font-weight: 600; + } + } + .result-count { + justify-self: self-end; + font-size: 0.929em; + } + .btn-area { + margin-right: -5px; + margin-left: 12px; + display: flex; + align-items: center; + &:before { + content: ''; + width: 1px; + height: 10px; + display: inline-block; + background-color: $gray-rec; + } + } + } + //on + &.chat-search-show { + visibility: visible; + transition: all 0.2s linear; + top: 0; + } +} diff --git a/src/app/sections/chat/components/chat-search.section.component.ts b/src/app/sections/chat/components/chat-search.section.component.ts index cf2052e..3d08843 100644 --- a/src/app/sections/chat/components/chat-search.section.component.ts +++ b/src/app/sections/chat/components/chat-search.section.component.ts @@ -5,8 +5,10 @@ import { Output, EventEmitter, ChangeDetectorRef, - ChangeDetectionStrategy + ChangeDetectionStrategy, + Input } from '@angular/core'; +import { SearchInfo } from '@app/pages/chat/models/search-info'; @Component({ selector: 'app-sections-chat-chat-search', @@ -15,20 +17,19 @@ import { changeDetection: ChangeDetectionStrategy.OnPush }) export class ChatSearchSectionComponent implements OnInit, OnDestroy { - searchObj: any = { - isSearch: false, - searchWord: '' - }; + @Input() + isChatSearch = false; @Output() - chatSearch = new EventEmitter<{ - isSearch: false; - searchWord: ''; - }>(); + chatSearch = new EventEmitter(); @Output() closeChatSearch = new EventEmitter(); + searchObj: SearchInfo = { + isShowSearch: false, + searchWord: '' + }; constructor(private changeDetectorRef: ChangeDetectorRef) {} ngOnInit(): void {} @@ -39,9 +40,9 @@ export class ChatSearchSectionComponent implements OnInit, OnDestroy { alert(searchWord); this.searchObj = { - isSearch: true, + isShowSearch: true, searchWord - }; + } as SearchInfo; this.chatSearch.emit(this.searchObj); } diff --git a/src/app/sections/chat/components/component-ui/chat-list-item.component.html b/src/app/sections/chat/components/component-ui/chat-list-item.component.html deleted file mode 100644 index 12b95ab..0000000 --- a/src/app/sections/chat/components/component-ui/chat-list-item.component.html +++ /dev/null @@ -1,64 +0,0 @@ -
-
- -
-
- {{ roomName }} - ({{ roomInfo.joinUserCount }}) -
-
{{ roomInfo.finalEventMessage }}
-
{{ roomInfo.finalEventDate | ucapDate: 'LT' }}
- - - - - - -
- - - {{ roomName }} - - - - diff --git a/src/app/sections/chat/components/component-ui/chat-list-item.component.spec.ts b/src/app/sections/chat/components/component-ui/chat-list-item.component.spec.ts deleted file mode 100644 index 4eeee62..0000000 --- a/src/app/sections/chat/components/component-ui/chat-list-item.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ChatListItemComponent } from './chat-list-item.component'; - -describe('ChatListItemComponent', () => { - let component: ChatListItemComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ ChatListItemComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(ChatListItemComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/sections/chat/components/component-ui/chat-list-item.component.ts b/src/app/sections/chat/components/component-ui/chat-list-item.component.ts deleted file mode 100644 index ce68555..0000000 --- a/src/app/sections/chat/components/component-ui/chat-list-item.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - Component, - OnInit, - ChangeDetectionStrategy, - Input, - OnDestroy, - EventEmitter, - Output, - ChangeDetectorRef -} from '@angular/core'; -import { RoomInfo, RoomType } from '@ucap/protocol-room'; - -@Component({ - selector: 'app-chat-list-item', - templateUrl: './chat-list-item.component.html', - styleUrls: ['./chat-list-item.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ChatListItemComponent implements OnInit, OnDestroy { - @Input() - roomInfo: RoomInfo; - - @Input() - profileImageRoot: string; - - @Input() - defaultProfileImage: string; - - @Input() - profileImage: string; - - @Input() - roomName: string; - - @Input() - checkable = false; - - @Input() - checked = false; - - @Output() - toggleItem = new EventEmitter<{ - checked: boolean; - roomInfo: RoomInfo; - }>(); - - RoomType = RoomType; - - constructor(private changeDetectorRef: ChangeDetectorRef) {} - - ngOnInit(): void {} - - ngOnDestroy(): void {} - - onToggleItem(value: boolean): void { - this.toggleItem.emit({ - checked: value, - roomInfo: this.roomInfo - }); - - this.changeDetectorRef.detectChanges(); - } -} diff --git a/src/app/sections/chat/components/component-ui/expansion.component.html b/src/app/sections/chat/components/component-ui/expansion.component.html deleted file mode 100644 index 254d78c..0000000 --- a/src/app/sections/chat/components/component-ui/expansion.component.html +++ /dev/null @@ -1,63 +0,0 @@ -
- - - - - -
  • -
    - -
    -
  • -
    - - -
  • -
    - -
    - - -
    -
    -
      -
      -
      - -
      -
    -
  • -
    -
    -
    -
    diff --git a/src/app/sections/chat/components/component-ui/expansion.component.scss b/src/app/sections/chat/components/component-ui/expansion.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/sections/chat/components/component-ui/expansion.component.ts b/src/app/sections/chat/components/component-ui/expansion.component.ts deleted file mode 100644 index 8db4212..0000000 --- a/src/app/sections/chat/components/component-ui/expansion.component.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { - Component, - OnInit, - OnDestroy, - Input, - ViewChild, - ContentChild, - TemplateRef, - ChangeDetectionStrategy, - ChangeDetectorRef, - Directive -} from '@angular/core'; - -import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; -import { FlatTreeControl } from '@angular/cdk/tree'; - -import { MatTreeFlattener, MatTree } from '@angular/material/tree'; - -import { VirtualScrollTreeFlatDataSource } from '@ucap/ng-ui'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar'; -import { RoomInfo } from '@ucap/protocol-room'; - -export interface ChatGroupNode { - nodeType: string; - roomInfo?: RoomInfo; - children?: ChatGroupNode[]; -} - -export interface FlatNode { - expandable: boolean; - level: number; - node: ChatGroupNode; -} - -@Directive({ - selector: '[ucapChatExpansionNode]' -}) -export class ExpansionNodeDirective {} - -@Directive({ - selector: '[ucapChatExpansionHeader]' -}) -export class ExpansionHeaderDirective {} - -@Component({ - selector: 'ucap-chat-expansion', - templateUrl: './expansion.component.html', - styleUrls: ['./expansion.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ExpansionComponent implements OnInit, OnDestroy { - @Input() - set chatGroup(list: { division: string; roomList: RoomInfo[] }[]) { - if (!list || 0 === list.length) { - } else { - list.sort((a, b) => - a.division < b.division ? 1 : a.division > b.division ? -1 : 0 - ); - - for (const item of list) { - const nodeType = item.division; - const node: ChatGroupNode = { - nodeType, - children: [] - }; - - item.roomList.sort((a, b) => - a.finalEventDate < b.finalEventDate - ? 1 - : a.finalEventDate > b.finalEventDate - ? -1 - : 0 - ); - - item.roomList.forEach((roomInfo) => { - node.children.push({ - nodeType, - roomInfo - }); - }); - - if (!!this.nodeMap.get(item.division)) { - this.nodeMap[item.division].push(node); - } else { - this.nodeMap.set(item.division, [node]); - } - } - } - this.refreshNodes(); - } - - @ViewChild('treeList', { static: false }) - treeList: MatTree; - - @ViewChild('cvsvList', { static: false }) - cvsvList: CdkVirtualScrollViewport; - - @ViewChild(PerfectScrollbarDirective, { static: false }) - psDirectiveRef?: PerfectScrollbarDirective; - - @ContentChild(ExpansionNodeDirective, { - read: TemplateRef, - static: false - }) - nodeTemplate: TemplateRef; - - @ContentChild(ExpansionHeaderDirective, { - read: TemplateRef, - static: false - }) - headerTemplate: TemplateRef; - - treeControl: FlatTreeControl; - treeFlattener: MatTreeFlattener; - dataSource: VirtualScrollTreeFlatDataSource; - - private nodeMap: Map = new Map(); - // tslint:disable-next-line: variable-name - private _ngOnDestroySubject: Subject; - - constructor(private changeDetectorRef: ChangeDetectorRef) { - this.treeControl = new FlatTreeControl( - (node) => node.level, - (node) => node.expandable - ); - - this.treeFlattener = new MatTreeFlattener( - (node: ChatGroupNode, level: number) => { - return { - expandable: !!node.children && node.children.length > 0, - level, - nodeType: node.nodeType, - node - }; - }, - (node) => node.level, - (node) => node.expandable, - (node) => node.children - ); - - this.dataSource = new VirtualScrollTreeFlatDataSource< - ChatGroupNode, - FlatNode - >(this.treeControl, this.treeFlattener); - } - - ngOnInit(): void { - this._ngOnDestroySubject = new Subject(); - - this.dataSource.cdkVirtualScrollViewport = this.cvsvList; - this.treeControl.expansionModel.changed - .pipe(takeUntil(this._ngOnDestroySubject)) - .subscribe(() => { - this.cvsvList.checkViewportSize(); - this.psDirectiveRef.update(); - }); - } - - ngOnDestroy(): void { - if (!!this._ngOnDestroySubject) { - this._ngOnDestroySubject.next(); - this._ngOnDestroySubject.complete(); - } - } - - isHeader = (_: number, node: FlatNode) => 0 === node.level; - - private refreshNodes() { - const rootNode: ChatGroupNode[] = []; - this.nodeMap.forEach((node) => rootNode.push(...node)); - - this.dataSource.data = rootNode; - this.changeDetectorRef.detectChanges(); - } -} diff --git a/src/app/sections/chat/components/component-ui/index.ts b/src/app/sections/chat/components/component-ui/index.ts deleted file mode 100644 index 80b2d82..0000000 --- a/src/app/sections/chat/components/component-ui/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ChatListItemComponent } from './chat-list-item.component'; -import { - ExpansionComponent, - ExpansionNodeDirective, - ExpansionHeaderDirective -} from './expansion.component'; - -export const COMPONENTS = [ChatListItemComponent, ExpansionComponent]; - -export const DIRECTIVES = [ExpansionNodeDirective, ExpansionHeaderDirective]; diff --git a/src/app/sections/chat/components/form.section.component.html b/src/app/sections/chat/components/form.section.component.html index ede3691..2b1baf5 100644 --- a/src/app/sections/chat/components/form.section.component.html +++ b/src/app/sections/chat/components/form.section.component.html @@ -1,123 +1,159 @@ - - - +
    + + + - + - - + + - - + + -
    - - - +
    + + {{ 'chat:label.inputChatMessage' | ucapI18n }} + + + - -
    -
    - - - - - - - - + +
    + +
    + + + + + + + + +
    diff --git a/src/app/sections/chat/components/form.section.component.scss b/src/app/sections/chat/components/form.section.component.scss index e69de29..0017854 100644 --- a/src/app/sections/chat/components/form.section.component.scss +++ b/src/app/sections/chat/components/form.section.component.scss @@ -0,0 +1,60 @@ +@import '~@ucap/lg-scss/mixins'; + +.ucap-chat-input-container { + display: flex; + flex-direction: column; + justify-content: space-between; + border-top: 1px solid #ccc; + background-color: $white; + .chat-form-area { + max-height: 100%; + min-height: 20px; + background-color: $white; + padding-left: 30px; + font-size: 0.929em; + margin: 8px 0; + overflow-x: hidden; + overflow-y: auto; + flex: 1 1 auto; + @include screen(xs) { + padding-left: 16px; + } + textarea { + min-height: 22px; + overflow: hidden; + } + } + .button-area { + display: flex; + flex-direction: row; + align-items: center; + height: 34px; + background-color: rgba(0, 0, 0, 0.02); + border-top: 1px solid #eeeeee; + padding: 0 16px; + flex: 0 0 0; + @include screen(xs) { + padding: 0; + } + .btn-icon-chat { + @include ucapMatButton(34px, 34px, 0, 34px); + &.btn-icon-chat-gams { + width: 60px; + @include font-family($font-semibold); + font-weight: 600; + } + } + .btn-message-send { + width: 38px; + height: 38px; + box-shadow: 0px 2px 2px 0 rgba(0, 0, 0, 0.3); + border: solid 3px #ffffff; + background-image: linear-gradient(225deg, #fadfaa 5%, #f92465 95%); + font-size: 1.714em; + position: absolute; + z-index: 10; + bottom: 25px; + right: 20px; + } + } +} diff --git a/src/app/sections/chat/components/form.section.component.ts b/src/app/sections/chat/components/form.section.component.ts index f2f42b3..f187c3c 100644 --- a/src/app/sections/chat/components/form.section.component.ts +++ b/src/app/sections/chat/components/form.section.component.ts @@ -1,4 +1,4 @@ -import { Subject, of, Observable, forkJoin } from 'rxjs'; +import { Subject, of, merge } from 'rxjs'; import { takeUntil, map, catchError, take } from 'rxjs/operators'; import { @@ -9,51 +9,68 @@ import { ChangeDetectorRef, Input, ViewChild, - ElementRef + ElementRef, + EventEmitter, + Output } from '@angular/core'; -import { Store, select } from '@ngrx/store'; -import { Dictionary } from '@ngrx/entity'; +import { Store, select } from '@ngrx/store'; + +import { MatDialog } from '@angular/material/dialog'; + +import { StickerFilesInfo } from '@ucap/ng-core'; +import { StatusCode, FileUploadItem } from '@ucap/api'; +import { + TranslationSaveResponse, + TranslationSaveRequest +} from '@ucap/api-common'; +import { VersionInfo2Response } from '@ucap/api-public'; +import { + SendEventMailType, + SendEventEmailRequest, + SendEventEmailResponse, + StatusCode as PiStatusCode +} from '@ucap/pi'; import { RoomInfo } from '@ucap/protocol-room'; import { - SendRequest as SendEventRequest, - EventType + EventType, + MassTranslationEventJson, + TranslationEventJson } from '@ucap/protocol-event'; import { LoginResponse } from '@ucap/protocol-authentication'; +import { AuthResponse } from '@ucap/protocol-query'; +import { User } from '@ucap/protocol-info'; -import { ChattingActions } from '@ucap/ng-store-chat'; +import { I18nService } from '@ucap/ng-i18n'; +import { LogService } from '@ucap/ng-logger'; +import { CommonApiService } from '@ucap/ng-api-common'; +import { PiService } from '@ucap/ng-pi'; + +import { UserSelector } from '@ucap/ng-store-organization'; import { LoginSelector, - ConfigurationSelector + ConfigurationSelector, + AuthorizationSelector } from '@ucap/ng-store-authentication'; -import { StickerFilesInfo, KEY_STICKER_HISTORY } from '@ucap/ng-core'; +import { ChattingSelector, RoomSelector } from '@ucap/ng-store-chat'; + import { AlertDialogComponent, AlertDialogData, - AlertDialogResult + AlertDialogResult, + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult } from '@ucap/ng-ui'; -import { I18nService } from '@ucap/ng-i18n'; -import { LogService } from '@ucap/ng-logger'; -import { MatDialog } from '@angular/material/dialog'; -import { - TranslationSaveResponse, - MassTalkSaveRequest, - FileTalkSaveResponse, - FileTalkSaveRequest -} from '@ucap/api-common'; -import { environment } from '@environments'; -import { LocalStorageService } from '@ucap/ng-web-storage'; -import { CommonApiService } from '@ucap/ng-api-common'; import { LoginSession } from '@app/models/login-session'; import { AppAuthenticationService } from '@app/services/app-authentication.service'; -import { StatusCode, FileUploadItem } from '@ucap/api'; import { AppFileService } from '@app/services/app-file.service'; -import { VersionInfo2Response } from '@ucap/api-public'; import { FileUploadSelectorComponent } from '@app/ucap/chat/components/file-upload.selector.component'; -import { FileUtil } from '@ucap/core'; import { AppChatService } from '@app/services/app-chat.service'; +import { environment } from '@environments'; + export enum SelectorType { EMPTY = '', STICKER = 'STICKER', @@ -69,9 +86,17 @@ export enum SelectorType { changeDetection: ChangeDetectionStrategy.OnPush }) export class FormSectionComponent implements OnInit, OnDestroy { + private roomIdSubject = new Subject(); + private ngOnDestroySubject: Subject = new Subject(); @Input() set roomId(roomId: string) { - this._roomId = roomId; + if (!!roomId && this.roomId !== roomId) { + this._roomId = roomId; + + this.roomIdSubject.next(roomId); + + this.initializeRoomData(); + } } get roomId(): string { return this._roomId; @@ -79,9 +104,17 @@ export class FormSectionComponent implements OnInit, OnDestroy { // tslint:disable-next-line: variable-name _roomId: string; + @Output() + changeTranslationSimpleview = new EventEmitter(); + + @Output() + eventSendTrigger = new EventEmitter(); + versionInfo2Res: VersionInfo2Response; loginSession: LoginSession; loginRes: LoginResponse; + user: User; + authRes: AuthResponse; currentRoomInfo: RoomInfo; @@ -91,8 +124,9 @@ export class FormSectionComponent implements OnInit, OnDestroy { selectedSticker: StickerFilesInfo; /** About Translation */ - translationSimpleview = false; - translationPreview = false; + isTranslationProcess = false; + translationSimpleview = true; + translationPreview = true; destLocale = 'en'; // default English :: en translationPreviewInfo: { previewInfo: TranslationSaveResponse | null; @@ -108,14 +142,13 @@ export class FormSectionComponent implements OnInit, OnDestroy { SelectorType = SelectorType; - private ngOnDestroySubject: Subject; constructor( + private piService: PiService, private appFileService: AppFileService, private appChatService: AppChatService, private store: Store, private i18nService: I18nService, private dialog: MatDialog, - private localStorageService: LocalStorageService, private logService: LogService, private appAuthenticationService: AppAuthenticationService, private commonApiService: CommonApiService, @@ -123,8 +156,6 @@ export class FormSectionComponent implements OnInit, OnDestroy { ) {} ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - this.store .pipe( takeUntil(this.ngOnDestroySubject), @@ -133,56 +164,83 @@ export class FormSectionComponent implements OnInit, OnDestroy { .subscribe((versionInfo2Res) => { this.versionInfo2Res = versionInfo2Res; }); + + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; + }); + this.store .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) .subscribe((loginRes) => { this.loginRes = loginRes; }); - this.appAuthenticationService - .getLoginSession$() - .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe((loginSession) => (this.loginSession = loginSession)); this.store .pipe( takeUntil(this.ngOnDestroySubject), - select( - (state: any) => state.chat.room.rooms.entities as Dictionary - ) + select(AuthorizationSelector.authResponse) ) - .subscribe((rooms) => { - if (!!this.roomId) { - this.currentRoomInfo = rooms[this.roomId]; - - this.changeDetectorRef.detectChanges(); - } + .subscribe((authRes) => { + this.authRes = authRes; }); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } + + if (!!this.roomIdSubject) { + this.roomIdSubject.next(); + this.roomIdSubject.complete(); + } } + initializeRoomData() { + if (!!this.messageInput) { + this.messageInput.nativeElement.value = ''; + } + this.selectorType = SelectorType.EMPTY; + this.translationSimpleview = false; + this.changeTranslationSimpleview.emit(false); + this.translationPreview = true; + this.destLocale = 'en'; // default English :: en + this.translationPreviewInfo = null; + + this.store + .pipe( + takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)), + select(RoomSelector.room, this.roomId) + ) + .subscribe((room) => { + this.currentRoomInfo = room; + this.changeDetectorRef.markForCheck(); + }); + + this.loginSession = this.appAuthenticationService.getLoginSession(); + } /** About Selector */ onOpenSelector(type: SelectorType): void { this.selectorType = type; - this.changeDetectorRef.detectChanges(); + this.changeDetectorRef.markForCheck(); } clearSelector(): void { this.selectorType = SelectorType.EMPTY; this.selectedSticker = null; - this.changeDetectorRef.detectChanges(); + this.changeDetectorRef.markForCheck(); } /** Element Handling */ focus(clearField: boolean = true): void { if (!!this.messageInput) { if (!!clearField) { + if (this.selectorType !== SelectorType.TRANSLATION) { + this.clearSelector(); + } this.messageInput.nativeElement.value = ''; - - this.clearSelector(); } this.messageInput.nativeElement.focus(); } @@ -201,6 +259,7 @@ export class FormSectionComponent implements OnInit, OnDestroy { } else { // selector open self.onOpenSelector(SelectorType.FILEUPLOAD); + self.changeDetectorRef.detectChanges(); // FileuploadItem Init. & FileSelector Init. const fileUploadItems = FileUploadItem.fromFiles(fileList); @@ -213,6 +272,7 @@ export class FormSectionComponent implements OnInit, OnDestroy { self.appChatService .sendMessageOfAttachFile( self.loginRes, + self.user, self.loginSession.deviceType, self.currentRoomInfo.roomId, fileUploadItems @@ -226,10 +286,13 @@ export class FormSectionComponent implements OnInit, OnDestroy { } }) .catch((err) => { - alert(err); + self.clearSelector(); if (!!self.fileUploadSelector) { self.fileUploadSelector.onUploadComplete(); } + + const msg = this.i18nService.t('common:file.errors.failToUpload'); + alert(msg); }); } }) @@ -240,13 +303,16 @@ export class FormSectionComponent implements OnInit, OnDestroy { } onKeydown(event: KeyboardEvent) { - if (event.key === 'PageUp' || event.key === 'PageDown') { - event.preventDefault(); - return false; - } else if (event.key === 'Enter' && !event.shiftKey) { - event.preventDefault(); - this.send(); - } + // if (event.key === 'PageUp' || event.key === 'PageDown') { + // event.preventDefault(); + // return false; + // } else if (event.key === 'Enter' && !event.shiftKey) { + // event.preventDefault(); + // this.send(); + // } + event.preventDefault(); + event.stopPropagation(); + this.send(); } onSelectedSticker(stickerInfo: StickerFilesInfo) { @@ -254,9 +320,37 @@ export class FormSectionComponent implements OnInit, OnDestroy { this.focus(false); } + onPasteReply(event: ClipboardEvent) { + event.preventDefault(); + + // this.platform_readFromClipboard().then(async (data) => { + // console.log(data); + // console.log(data.html); + // console.log(data.image); + // console.log(data.imageDataUrl); + // console.log(data.text); + + // console.log(JSON.stringify(event.clipboardData.items)); + // if ((!!data.image && !!data.text) || !!data.image) { + // } + // }); + // for (const item of items) { + // if (item.type.indexOf('image') === 0) { + // blob = item.getAsFile(); + // } + // } + + // console.log(JSON.stringify(event.clipboardData.items)); + // this.nativeService.platform_readFromClipboard().then(async (data) => { + // console.log(data); + // console.log(JSON.stringify(event.clipboardData.items)); + // if ((!!data.image && !!data.text) || !!data.image) { + // } + // }); + } async send() { const roomId = this.currentRoomInfo.roomId; - const userSeq = this.loginRes.userSeq; + const userSeq = String(this.user.info.seq); let message = this.messageInput.nativeElement.value; if (!!message || message.trim().length > 0) { @@ -272,11 +366,11 @@ export class FormSectionComponent implements OnInit, OnDestroy { AlertDialogData, AlertDialogResult >(AlertDialogComponent, { + panelClass: 'min-create-dialog', data: { - title: this.i18nService.t('errors.label'), - message: this.i18nService.t('errors.inputChatMessage') - }, - panelClass: '' + title: this.i18nService.t('chat:errors.label'), + message: this.i18nService.t('chat:errors.inputChatMessage') + } }); return; } @@ -301,14 +395,7 @@ export class FormSectionComponent implements OnInit, OnDestroy { this.clearSelector(); } else { - this.appChatService.sendMessageOfTranslate( - this.loginRes, - this.loginSession.deviceType, - this.destLocale, - roomId, - message, - this.selectedSticker - ); + this.sendMessageOfTranslate(message); } } else if (!!this.selectedSticker) { /** CASE : Sticker */ @@ -326,6 +413,7 @@ export class FormSectionComponent implements OnInit, OnDestroy { /** CASE : MASS TEXT */ this.appChatService.sendMessageOfMassText( this.loginRes, + this.user, this.loginSession.deviceType, roomId, message @@ -335,6 +423,205 @@ export class FormSectionComponent implements OnInit, OnDestroy { this.appChatService.sendMessageOfNormal(userSeq, roomId, message); } + this.eventSendTrigger.emit(0); this.focus(); } + + sendMessageOfTranslate(message: string) { + if (!!this.isTranslationProcess) { + return; + } + + this.isTranslationProcess = true; + + this.commonApiService + .translationSave({ + userSeq: String(this.user.info.seq), + token: this.loginRes.tokenString, + deviceType: this.loginSession.deviceType, + original: message, + roomId: this.roomId, + srcLocale: '', + destLocale: this.destLocale + } as TranslationSaveRequest) + .pipe( + take(1), + map((res) => { + if (res.statusCode === StatusCode.Success) { + let sentMessage = ''; + let eventType = EventType.Translation; + let previewObject: TranslationEventJson | MassTranslationEventJson; + if (res.translationSeq > 0) { + // Mass Text Translation + previewObject = res; + sentMessage = res.returnJson; + eventType = EventType.MassTranslation; + } else { + // Normal Text Translation + previewObject = { + locale: this.destLocale, + original: message, + translation: res.translation, + stickername: '', + stickerfile: !!this.selectedSticker + ? this.selectedSticker.index + : '' + }; + sentMessage = JSON.stringify(previewObject); + eventType = EventType.Translation; + } + + if (!!this.translationPreview) { + this.translationPreviewInfo = { + previewInfo: res, + translationType: eventType + }; + this.changeDetectorRef.markForCheck(); + } else { + this._sendTranslationMessage(sentMessage, eventType); + } + } else { + // error + this._translationError(); + } + }), + catchError((error) => { + this._translationError(); + return of(this.logService.error('error', error)); + }) + ) + .subscribe(() => { + this.isTranslationProcess = false; + }); + } + + onChangeTranslationSimpleView(value: boolean) { + this.translationSimpleview = value; + this.changeTranslationSimpleview.emit(value); + } + onChangeTranslationPreView(value: boolean) { + this.translationPreview = value; + } + onChangeDestLocale(destLocale: string) { + this.destLocale = destLocale; + } + onSendTranslationMessage(params: { + previewInfo: TranslationSaveResponse | null; + translationType: EventType.Translation | EventType.MassTranslation; + }) { + let sentMessage = ''; + if (params.translationType === EventType.MassTranslation) { + // Mass Text Translation + sentMessage = params.previewInfo.returnJson; + } else { + sentMessage = JSON.stringify({ + locale: params.previewInfo.destLocale, + original: params.previewInfo.original, + translation: params.previewInfo.translation, + stickername: '', + stickerfile: !!this.selectedSticker ? this.selectedSticker.index : '' + }); + } + + this._sendTranslationMessage(sentMessage, params.translationType); + } + + private _translationError() { + this.isTranslationProcess = false; + this.dialog.open( + AlertDialogComponent, + { + data: { + title: this.i18nService.t('chat:errors.label'), + message: this.i18nService.t('chat:errors.translateServerError') + }, + panelClass: 'min-create-dialog' + } + ); + } + private _sendTranslationMessage(sentMessage: string, eventType: EventType) { + this.appChatService.sendMessageOfTranslate( + String(this.user.info.seq), + this.roomId, + eventType, + sentMessage + ); + this.translationPreviewInfo = undefined; + this.focus(); + } + + onSendEventEmail(type: SendEventMailType): void { + this.store + .pipe(take(1), select(ChattingSelector.eventList, this._roomId)) + .subscribe((eventList) => { + if (!!eventList && eventList.length > 0) { + const self = this; + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:label.emailSend'), + html: this.i18nService.t( + type === SendEventMailType.ALL + ? 'chat:dialog.confirmSendEventEmailAll' + : 'chat:dialog.confirmSendEventEmailMe' + ) + } + }); + + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && !!result.choice) { + const req: SendEventEmailRequest = { + userSeq: String(this.user.info.seq), + deviceType: this.loginSession.deviceType, + tokenKey: this.loginRes.tokenString, + roomSeq: this._roomId, + eventSeq: String(eventList[0].seq), + sendType: type + }; + this.piService + .sendEventEmail(req) + .pipe(take(1)) + .subscribe( + (res: SendEventEmailResponse) => { + if (res.intStatusCode === PiStatusCode.Success) { + this.dialog.open< + AlertDialogComponent, + AlertDialogData, + AlertDialogResult + >(AlertDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:label.emailSend'), + message: this.i18nService.t( + 'chat:dialog.sendEventEmailSuccess' + ) + } + }); + + self.selectorType = SelectorType.EMPTY; + } else { + this.logService.error(res.strErrorMessage); + } + }, + (error) => { + this.logService.error(error); + }, + () => {} + ); + + this.logService.debug( + this.i18nService.t('chat:dialog.sendEventEmailSuccess') + ); + } + }); + } + }); + } } diff --git a/src/app/sections/chat/components/info.section.component.html b/src/app/sections/chat/components/info.section.component.html index 28ffc53..57ead93 100644 --- a/src/app/sections/chat/components/info.section.component.html +++ b/src/app/sections/chat/components/info.section.component.html @@ -1,74 +1,163 @@ - -
    - + +
    +
    + +
    - {{ roomName }} - timer{{ roomName }} + - ({{ currentRoomInfo?.joinUserCount }}) - + {{ currentRoomInfo?.joinUserCount }} + -
    +
    - - - --> +
    + + + + +
    +
    +
    + inbox{{ 'chat:label.data' | ucapI18n }} + +
    +
    + + + +
    +
    + + + + + + +
    +
    diff --git a/src/app/sections/chat/components/info.section.component.scss b/src/app/sections/chat/components/info.section.component.scss index 98fcdb2..bc0e029 100644 --- a/src/app/sections/chat/components/info.section.component.scss +++ b/src/app/sections/chat/components/info.section.component.scss @@ -4,12 +4,73 @@ height: 50px; min-height: 50px; background-color: $white; - display: flex; - flex-direction: row; - justify-content: space-between; - align-content: center; - .mat-toolbar-row { + .info-chat-toolbar-content { height: 50px; min-height: 50px; + display: flex; + flex-direction: row; + align-content: center; + justify-content: space-between; + @include screen(xs) { + padding-right: 0; + } + .chat-room-profile { + align-self: center; + margin-right: 15px; + height: 38px; + flex: 0 0 0; + @include profile-avatar-default( + 0, + 14, + $warm-pink, + 18px + ); //오른 아래 공간, 모바일 온라인 아이콘 크기, 모바일 아이콘 색, 모바일 아이콘 bg크기 + .profile-image { + @include avatar-img(36px, 0); //아바타 크기, 왼쪽공간 + background-color: #d1f6ff; + } + } + .chat-room-subject { + flex: 1 1 auto; + color: $gray-re3; + font-size: 14px; + font-weight: 600; + display: flex !important; + flex-direction: row; + align-items: center; + @include ellipsis-column(1); + .ico-timer-unit { + max-width: 18px !important; + height: 18px !important; + line-height: 18px; + font-size: 12px; + text-align: center; + border-radius: 50%; + margin-right: 6px; + flex: 0 0 24px; + } + span { + flex: 0 1 auto; + @include ellipsis(1); + //width: ; + } + strong { + flex: 1 1 auto; + display: flex; + flex-direction: row; + padding-left: 4px; + } + } + .btn-chat-room-top { + flex: 1 0 160px; + align-items: center; + display: flex; + flex-direction: inline-flex; + justify-content: flex-end; + //대화방 즐겨찾기 - 개발보류 + .btn-icon-favorite { + display: none; + } + } } } diff --git a/src/app/sections/chat/components/info.section.component.ts b/src/app/sections/chat/components/info.section.component.ts index 566b122..40541e8 100644 --- a/src/app/sections/chat/components/info.section.component.ts +++ b/src/app/sections/chat/components/info.section.component.ts @@ -1,3 +1,6 @@ +import { combineLatest, Subject, merge } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + import { Component, OnInit, @@ -8,22 +11,26 @@ import { EventEmitter, ChangeDetectorRef } from '@angular/core'; -import { Dictionary } from '@ngrx/entity'; -import { Store, select } from '@ngrx/store'; -import { combineLatest, Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; -import { RoomSelector, RoomActions } from '@ucap/ng-store-chat'; -import { - LoginSelector, - ConfigurationSelector -} from '@ucap/ng-store-authentication'; +import { Store, select } from '@ngrx/store'; +import { Dictionary } from '@ngrx/entity'; + +import { MatDialog } from '@angular/material/dialog'; + +import { LocaleCode } from '@ucap/core'; +import { I18nService } from '@ucap/ng-i18n'; +import { VersionInfo2Response } from '@ucap/api-public'; +import { RoomInfo, RoomType, UpdateRequest } from '@ucap/protocol-room'; +import { User } from '@ucap/protocol-info'; + +import { UserSelector } from '@ucap/ng-store-organization'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; import { + RoomSelector, + RoomActions, RoomUserMap, RoomUserShortMap -} from '@ucap/ng-store-chat/lib/store/room/state'; -import { LoginResponse } from '@ucap/protocol-authentication'; -import { RoomInfo, RoomType, UpdateRequest } from '@ucap/protocol-room'; +} from '@ucap/ng-store-chat'; import { TranslatePipe as OrganizationTranslate, @@ -31,9 +38,8 @@ import { } from '@ucap/ng-ui-organization'; import { AppChatService } from '@app/services/app-chat.service'; -import { I18nService } from '@ucap/ng-i18n'; -import { VersionInfo2Response } from '@ucap/api-public'; -import { LocaleCode } from '@ucap/core'; +import { ChatDrawType } from '@app/pages/chat/types/chat-draw.type'; +import { DrawInfo } from '@app/pages/chat/models/draw-info'; @Component({ selector: 'app-sections-chat-info', @@ -42,10 +48,17 @@ import { LocaleCode } from '@ucap/core'; changeDetection: ChangeDetectionStrategy.OnPush }) export class InfoSectionComponent implements OnInit, OnDestroy { + private roomIdSubject = new Subject(); + private ngOnDestroySubject: Subject = new Subject(); + @Input() set roomId(roomId: string) { this._roomId = roomId; + this.roomIdSubject.next(roomId); + + this.initializeRoomData(); + // request selected room if (!!this.roomId) { this.store.dispatch( @@ -55,8 +68,6 @@ export class InfoSectionComponent implements OnInit, OnDestroy { }) ); } - - this.getRoomInfo(); } get roomId(): string { return this._roomId; @@ -68,31 +79,31 @@ export class InfoSectionComponent implements OnInit, OnDestroy { openChatSearch = new EventEmitter(); @Output() - rightDrawerToggle = new EventEmitter(); + rightDrawerToggle = new EventEmitter(); versionInfo2Res: VersionInfo2Response; - loginRes: LoginResponse; + user: User; defaultProfileImage: string; defaultProfileImageMulti: string; currentRoomInfo: RoomInfo; roomList: RoomInfo[]; - roomUsersDictionary: Dictionary; - roomUsersShortDictionary: Dictionary; + roomUsersMap: RoomUserMap; + roomUsersShortMap: RoomUserShortMap; roomName: string; roomImage: string; RoomType = RoomType; organizationTranslate: OrganizationTranslate; - private ngOnDestroySubject: Subject; constructor( private store: Store, private appChatService: AppChatService, private i18nService: I18nService, private translateService: TranslateService, + private dialog: MatDialog, private changeDetectorRef: ChangeDetectorRef ) { // language setting @@ -105,12 +116,10 @@ export class InfoSectionComponent implements OnInit, OnDestroy { // default image setting this.defaultProfileImage = this.appChatService.defaultProfileImage; - this.defaultProfileImageMulti = this.appChatService.defaultProfileImage; + this.defaultProfileImageMulti = this.appChatService.defaultProfileImageMulti; } ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - this.store .pipe( takeUntil(this.ngOnDestroySubject), @@ -119,76 +128,58 @@ export class InfoSectionComponent implements OnInit, OnDestroy { .subscribe((versionInfo2Res) => { this.versionInfo2Res = versionInfo2Res; }); - - combineLatest([ - this.store.pipe(select(LoginSelector.loginRes)), - this.store.pipe(select(RoomSelector.rooms)), - this.store.pipe( - select( - (state: any) => - state.chat.room.roomUsers.entities as Dictionary - ) - ), - this.store.pipe( - select( - (state: any) => - state.chat.room.roomUsersShort.entities as Dictionary< - RoomUserShortMap - > - ) - ) - ]) - .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe(([loginRes, rooms, roomUsers, roomUsersShort]) => { - this.loginRes = loginRes; - if (!!loginRes && !!this.roomId) { - this.roomList = rooms; - this.roomUsersDictionary = roomUsers; - this.roomUsersShortDictionary = roomUsersShort; - - this.getRoomInfo(); - } - }); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } + + if (!!this.roomIdSubject) { + this.roomIdSubject.next(); + this.roomIdSubject.complete(); + } + } + + initializeRoomData() { + combineLatest([ + this.store.pipe(select(UserSelector.user)), + this.store.pipe(select(RoomSelector.room, this.roomId)), + this.store.pipe(select(RoomSelector.roomUser, this.roomId)), + this.store.pipe(select(RoomSelector.roomUserShort, this.roomId)) + ]) + .pipe(takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject))) + .subscribe(([user, room, roomUsers, roomUsersShort]) => { + this.user = user; + this.currentRoomInfo = room; + this.roomUsersMap = roomUsers; + this.roomUsersShortMap = roomUsersShort; + + this.getRoomInfo(); + }); } getRoomInfo(): void { - // room render. - if (!this.roomList || this.roomList.length === 0) { - return; - } - this.roomName = '...'; - const idx = this.roomList.findIndex( - (roomInfo) => roomInfo.roomId === this.roomId - ); - if (idx > -1 && !!this.roomList && this.roomList.length > 0) { - this.currentRoomInfo = this.roomList[idx]; - } - if (!!this.currentRoomInfo) { // Setting RoomName. this.roomName = this.appChatService.getRoomName( this.organizationTranslate, - this.loginRes, + this.user, this.currentRoomInfo, - this.roomUsersDictionary, - this.roomUsersShortDictionary + this.roomUsersMap, + this.roomUsersShortMap ); this.roomImage = this.appChatService.getRoomProfileImage( + this.user, this.currentRoomInfo, - this.loginRes, - this.roomUsersDictionary, - this.roomUsersShortDictionary + this.roomUsersMap, + this.roomUsersShortMap ); } - this.changeDetectorRef.detectChanges(); + this.changeDetectorRef.markForCheck(); } onToggleAlarm(roomInfo: RoomInfo): void { @@ -209,11 +200,9 @@ export class InfoSectionComponent implements OnInit, OnDestroy { } as UpdateRequest }) ); - this.changeDetectorRef.detectChanges(); + this.changeDetectorRef.markForCheck(); } - onOpenRoomUserList(roomInfo: RoomInfo): void {} - onOpenChatSearch(): void { this.openChatSearch.emit(); } @@ -221,4 +210,64 @@ export class InfoSectionComponent implements OnInit, OnDestroy { onRightDrawerToggle(): void { this.rightDrawerToggle.emit(); } + + onClickRoomMenu(type: string): void { + switch (type) { + case 'ATTACH_IMAGE': + this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.AttachImage }); + break; + case 'ATTACH_VIDEO': + this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.AttachVideo }); + break; + case 'ATTACH_FILE': + this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.AttachFile }); + break; + case 'ADD_GROUP': + this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.AddGroup }); + break; + case 'ROOM_USERS': + this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.RoomUsers }); + break; + case 'CHANGE_ROOM_USERS': + this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.Invite }); + break; + case 'SETTING': + this.rightDrawerToggle.emit({ chatDrawType: ChatDrawType.Setting }); + break; + case 'EXIT': + this.appChatService.exitRoomDialog(this.currentRoomInfo); + break; + } + } + + getShowContextMenu(menuType: string) { + if ( + ['EVENT', 'ROOM_USERS', 'CHANGE_ROOM_USERS', 'ADD_GROUP', 'SETTING'].some( + (v) => v === menuType + ) + ) { + if ( + !this.currentRoomInfo || + !this.currentRoomInfo.roomType || + [RoomType.Mytalk, RoomType.Allim, RoomType.Bot, RoomType.Link].some( + (v) => v === this.currentRoomInfo.roomType + ) + ) { + return false; + } + } + // else if (['CHAT_EXPORT'].some((v) => v === menuType)) { + // if ( + // !this.currentRoomInfo || + // !this.currentRoomInfo.roomType || + // [RoomType.Allim, RoomType.Bot, RoomType.Link].some( + // (v) => v === this.currentRoomInfo.roomType + // ) + // ) { + // return false; + // } + // } + + return true; + } } diff --git a/src/app/sections/chat/components/list.section.component.html b/src/app/sections/chat/components/list.section.component.html index 8df303e..a24f6ce 100644 --- a/src/app/sections/chat/components/list.section.component.html +++ b/src/app/sections/chat/components/list.section.component.html @@ -1,30 +1,12 @@ -
    +
    -
    - -
    diff --git a/src/app/sections/chat/components/list.section.component.scss b/src/app/sections/chat/components/list.section.component.scss index 356b3ad..063068d 100644 --- a/src/app/sections/chat/components/list.section.component.scss +++ b/src/app/sections/chat/components/list.section.component.scss @@ -1,2 +1,4 @@ .list-container { + height: calc(100% - 90px) !important; + min-height: auto !important; } diff --git a/src/app/sections/chat/components/list.section.component.ts b/src/app/sections/chat/components/list.section.component.ts index b6d33ee..cf1387f 100644 --- a/src/app/sections/chat/components/list.section.component.ts +++ b/src/app/sections/chat/components/list.section.component.ts @@ -1,5 +1,5 @@ -import { Subject, combineLatest } from 'rxjs'; -import { takeUntil, take } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; import { Component, @@ -9,91 +9,24 @@ import { ChangeDetectorRef, Input, EventEmitter, - Output, - NgZone + Output } from '@angular/core'; -import { Store, select } from '@ngrx/store'; - -import { - FixedSizeVirtualScrollStrategy, - VIRTUAL_SCROLL_STRATEGY -} from '@angular/cdk/scrolling'; - -import { LoginResponse } from '@ucap/protocol-authentication'; - import { LogService } from '@ucap/ng-logger'; -import { SessionStorageService } from '@ucap/ng-web-storage'; -import { - LoginSelector, - ConfigurationSelector -} from '@ucap/ng-store-authentication'; -import { RoomSelector, RoomActions } from '@ucap/ng-store-chat'; -import { - RoomInfo, - RoomType, - ExitAllRequest, - UpdateRequest, - ExitRequest -} from '@ucap/protocol-room'; -import { - RoomUserMap, - RoomUserShortMap -} from '@ucap/ng-store-chat/lib/store/room/state'; -import { Dictionary } from '@ngrx/entity'; -import { - TranslatePipe as OrganizationTranslate, - TranslateService -} from '@ucap/ng-ui-organization'; -import { I18nService } from '@ucap/ng-i18n'; -import { AppChatService } from '@app/services/app-chat.service'; -import { - DateService, - ConfirmDialogComponent, - ConfirmDialogData, - ConfirmDialogResult -} from '@ucap/ng-ui'; -import { VersionInfo2Response } from '@ucap/api-public'; -import { MatDialog } from '@angular/material/dialog'; -import { Router } from '@angular/router'; - -export class ChatVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy { - constructor() { - super(60, 150, 200); // (itemSize, minBufferPx, maxBufferPx) - } -} +import { RoomInfo } from '@ucap/protocol-room'; +import { Router, ActivatedRoute, Params } from '@angular/router'; +import { QueryParams } from '@app/pages/chat/types/params.type'; +import { SearchInfo } from '@app/pages/chat/models/search-info'; @Component({ selector: 'app-sections-chat-list', templateUrl: './list.section.component.html', styleUrls: ['./list.section.component.scss'], - providers: [ - { - provide: VIRTUAL_SCROLL_STRATEGY, - useClass: ChatVirtualScrollStrategy - } - ], changeDetection: ChangeDetectionStrategy.OnPush }) export class ListSectionComponent implements OnInit, OnDestroy { @Input() - set searchObj(obj: { isShowSearch: boolean; searchWord: string }) { - this._searchObj = obj; - - if (obj.isShowSearch && obj.searchWord.localeCompare('') !== 0) { - this.onRoomSearch(obj); - } else { - this._searchObj.isShowSearch = false; - this.searchRoomList = []; - } - this.searchResultList.emit(this.searchRoomList); - } - - get searchObj() { - return this._searchObj; - } - // tslint:disable-next-line: variable-name - _searchObj: any; + searchObj: SearchInfo; @Input() checkable = false; @@ -110,277 +43,53 @@ export class ListSectionComponent implements OnInit, OnDestroy { roomInfo: RoomInfo; }>(); - @Output() - delRoom = new EventEmitter(); - - versionInfo2Res: VersionInfo2Response; - loginRes: LoginResponse; - - defaultProfileImage: string; - defaultProfileImageMulti: string; - + currentRoomId: string; roomList: RoomInfo[]; - roomUsersDictionary: Dictionary; - roomUsersShortDictionary: Dictionary; - searchRoomList: RoomInfo[]; - - roomGroup: { division: string; roomList: RoomInfo[] }[]; - - organizationTranslate: OrganizationTranslate; - - private ngOnDestroySubject: Subject; + private ngOnDestroySubject: Subject = new Subject(); constructor( private router: Router, - private appChatService: AppChatService, - private dateService: DateService, - private sessionStorageService: SessionStorageService, - private i18nService: I18nService, - private translateService: TranslateService, - private store: Store, - private dialog: MatDialog, + private activatedRoute: ActivatedRoute, private changeDetectorRef: ChangeDetectorRef, - private ngZone: NgZone, private logService: LogService - ) { - // default image setting - this.defaultProfileImage = this.appChatService.defaultProfileImage; - this.defaultProfileImageMulti = this.appChatService.defaultProfileImage; - } + ) {} ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - - // language setting - this.translateService.setDefaultLang(this.i18nService.currentLng); - this.translateService.use(this.i18nService.currentLng); - this.organizationTranslate = new OrganizationTranslate( - this.translateService, - this.changeDetectorRef - ); - this.i18nService.setDefaultNamespace('chat'); - - this.store - .pipe( - takeUntil(this.ngOnDestroySubject), - select(ConfigurationSelector.versionInfo2Response) - ) - .subscribe((versionInfo2Res) => { - this.versionInfo2Res = versionInfo2Res; - }); - - this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; - }); - - combineLatest([ - this.store.pipe(select(RoomSelector.rooms)), - this.store.pipe(select(RoomSelector.standbyRooms)) - ]) + this.activatedRoute.queryParams .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe(([rooms, standbyRooms]) => { - rooms = (rooms || []).filter((info) => { - return ( - info.isJoinRoom && - !standbyRooms.find((standbyRoom) => standbyRoom === info.roomId) - ); - }); - this.roomList = rooms; - - this.changeDetectorRef.detectChanges(); - }); - - combineLatest([ - this.store.pipe( - select( - (state: any) => - state.chat.room.roomUsers.entities as Dictionary - ) - ), - this.store.pipe( - select( - (state: any) => - state.chat.room.roomUsersShort.entities as Dictionary< - RoomUserShortMap - > - ) - ) - ]) - .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe(([roomUsers, roomUsersShort]) => { - this.roomUsersDictionary = roomUsers; - this.roomUsersShortDictionary = roomUsersShort; - this.changeDetectorRef.detectChanges(); + .subscribe((params: Params) => { + const seqParam = params[QueryParams.ROOM_ID]; + this.currentRoomId = !!seqParam ? seqParam : undefined; }); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } - getRoomName(roomInfo: RoomInfo): string { - if (!roomInfo) { - return ''; - } - - const roomName = this.appChatService.getRoomName( - this.organizationTranslate, - this.loginRes, - roomInfo, - this.roomUsersDictionary, - this.roomUsersShortDictionary - ); - - return roomName; - } - - getRoomProfileImage(roomInfo: RoomInfo): string { - let roomImage = ''; - if (!!roomInfo) { - roomImage = this.appChatService.getRoomProfileImage( - roomInfo, - this.loginRes, - this.roomUsersDictionary, - this.roomUsersShortDictionary - ); - } - - return roomImage; - } - - isToday(date: any) { - return this.dateService.isToday(date); - } - - onRoomSearch(obj: { isShowSearch: boolean; searchWord: string }) { - const searchRoomList: RoomInfo[] = []; - - this.roomList.forEach((roomInfo) => { - if (roomInfo.roomName.indexOf(obj.searchWord) > -1) { - searchRoomList.push(roomInfo); - } else { - const roomUsers = this.appChatService.getRoomUserList( - this.loginRes, - roomInfo.roomId, - this.roomUsersDictionary, - this.roomUsersShortDictionary - ); - - if ( - roomUsers.existUsers && - roomUsers.users.filter( - (userInfo) => - userInfo.name.indexOf(obj.searchWord) > -1 || - userInfo.nameEn.indexOf(obj.searchWord) > -1 || - userInfo.nameCn.indexOf(obj.searchWord) > -1 - ).length > 0 - ) { - searchRoomList.push(roomInfo); - } - } - }); - - this.searchRoomList = searchRoomList; - } - - getChecked(roomInfo: RoomInfo): boolean { - if (this.selectedRoomList.some((info) => info.roomId === roomInfo.roomId)) { - return true; - } else { - return false; - } - } - onToggleItem(event: { checked: boolean; roomInfo: RoomInfo }): void { this.toggleItem.emit(event); } - onClickRoomItem(event: MouseEvent, roomInfo: RoomInfo): void { - event.preventDefault(); - event.stopPropagation(); - - if (!!this.checkable) { - this.onToggleItem({ - checked: !this.getChecked(roomInfo), - roomInfo - }); - } else { - this.onOpenChatRoom(roomInfo); - } - } - onOpenChatRoom(roomInfo: RoomInfo): void { - this.ngZone.run(() => { - this.router.navigate( - [ - 'chat', - { - outlets: { content: 'chatroom' } - } - ], + this.router.navigate( + [ + 'chat', { - queryParams: { roomId: roomInfo.roomId } + outlets: { content: 'chatroom' } } - ); - }); - } - - onToggleAlarm(roomInfo: RoomInfo): void { - if (!roomInfo) { - return; - } - - this.store.dispatch( - RoomActions.update({ - req: { - roomId: roomInfo.roomId, - roomName: - roomInfo.roomName.trim().length === 0 - ? ' ' - : roomInfo.roomName.trim(), - receiveAlarm: !roomInfo.receiveAlarm, - syncAll: false - } as UpdateRequest - }) - ); - this.changeDetectorRef.detectChanges(); - } - - onDelRoom(roomInfo: RoomInfo): void { - if (!roomInfo) { - return; - } - - const dialogRef = this.dialog.open< - ConfirmDialogComponent, - ConfirmDialogData, - ConfirmDialogResult - >(ConfirmDialogComponent, { - data: { - title: this.i18nService.t('dialog.title.exitFromRoom'), - html: this.i18nService.t('dialog.confirmExitFromRoom') + ], + { + queryParams: { roomId: roomInfo.roomId } } - }); + ); + } - dialogRef - .afterClosed() - .pipe(take(1)) - .subscribe((result) => { - if (!!result && !!result.choice) { - this.store.dispatch( - RoomActions.del({ - req: { - roomId: roomInfo.roomId - } as ExitRequest - }) - ); - } - }); - this.changeDetectorRef.detectChanges(); + onSearchResultList(searchResultList: RoomInfo[]) { + this.searchResultList.emit(searchResultList); } } diff --git a/src/app/sections/chat/components/list.section.strategy.ts b/src/app/sections/chat/components/list.section.strategy.ts deleted file mode 100644 index 54b08bc..0000000 --- a/src/app/sections/chat/components/list.section.strategy.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Observable, Subject } from 'rxjs'; - -import { - VirtualScrollStrategy, - CdkVirtualScrollViewport -} from '@angular/cdk/scrolling'; -import { distinctUntilChanged } from 'rxjs/operators'; - -export class ChatGroupVirtualScrollStrategy implements VirtualScrollStrategy { - scrolledIndexChange: Observable; - - private indexSubject = new Subject(); - private viewport: CdkVirtualScrollViewport | null = null; - - constructor() { - this.scrolledIndexChange = this.indexSubject.pipe(distinctUntilChanged()); - } - - attach(viewport: CdkVirtualScrollViewport): void { - this.viewport = viewport; - } - detach(): void { - this.indexSubject.complete(); - this.viewport = null; - } - onContentScrolled(): void {} - onDataLengthChanged(): void {} - onContentRendered(): void {} - onRenderedOffsetChanged(): void {} - scrollToIndex(index: number, behavior: ScrollBehavior): void {} -} diff --git a/src/app/sections/chat/components/message.section.component.html b/src/app/sections/chat/components/message.section.component.html index e4001a5..cd8b349 100644 --- a/src/app/sections/chat/components/message.section.component.html +++ b/src/app/sections/chat/components/message.section.component.html @@ -1,33 +1,58 @@ - - +
    + + -
    -
    - -
    +
    +
    + +
    - + - - -
    - 스크롤 상단일때 도착한 최근 메시지 표시 영역 +
    + + +
    diff --git a/src/app/sections/chat/components/message.section.component.scss b/src/app/sections/chat/components/message.section.component.scss index e69de29..876f9f5 100644 --- a/src/app/sections/chat/components/message.section.component.scss +++ b/src/app/sections/chat/components/message.section.component.scss @@ -0,0 +1,64 @@ +@import '~@ucap/lg-scss/mixins'; + +.ucap-message-section { + display: flex; + flex-direction: column; + height: 100%; + padding: 0 30px; + overflow: auto; + min-width: 450px; + @include screen(xs) { + padding: 0 16px; + min-width: 100%; + } + .icon-button-arrow { + @include ucapMatButton(36px, 36px, 6px, 36px); + border-color: $lipstick; + background-color: rgba(255, 255, 255, 0.5); + box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.16); + position: sticky; + z-index: 5; + width: 36px; + align-self: flex-end; + margin-right: -10px; + &.top-position { + bottom: 90%; + order: 2; + } + &.bottom-position { + bottom: 30px; + order: 3; + } + &:hover { + background-color: rgba(255, 255, 255, 0.8); + } + } + + .chat-area { + flex-grow: 1; + order: 1; + padding-top: 30px; + display: flex; + flex-direction: column; + .ucap-chat-more-event { + padding: 20px 0 0; + .btn-more-pre { + @include ucapMatButton(100%, 30px, 2px, 30px); + font-size: 0.857em; + } + } + .recent-receive-message { + transition: all 0.4s; + position: sticky; + bottom: 0; + left: 0; + z-index: 5; + margin: 0 -30px; + background-color: rgba(0, 0, 0, 0.6); + color: #fff; + @include screen(xs) { + margin: 0 -15px; + } + } + } +} diff --git a/src/app/sections/chat/components/message.section.component.ts b/src/app/sections/chat/components/message.section.component.ts index f53e53b..d838862 100644 --- a/src/app/sections/chat/components/message.section.component.ts +++ b/src/app/sections/chat/components/message.section.component.ts @@ -1,11 +1,7 @@ -import { - Subject, - Observable, - BehaviorSubject, - Subscription, - merge -} from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import moment from 'moment'; + +import { Subject, merge, combineLatest, Observable } from 'rxjs'; +import { takeUntil, take } from 'rxjs/operators'; import { Component, @@ -13,26 +9,85 @@ import { OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, - Input + Input, + AfterViewInit, + ViewChild, + ElementRef } from '@angular/core'; -import { Info, EventJson, EventType, FileType } from '@ucap/protocol-event'; + import { Store, select } from '@ngrx/store'; -import { Chatting } from '@ucap/ng-store-chat/lib/store/chatting/state'; -import { ChattingSelector, RoomSelector } from '@ucap/ng-store-chat'; +import { Dictionary } from '@ngrx/entity'; + +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; + +import { TransMassTalkDownloadRequest } from '@ucap/api-common'; +import { VersionInfo2Response } from '@ucap/api-public'; +import { + Info, + EventJson, + EventType, + FileType, + MassTranslationEventJson +} from '@ucap/protocol-event'; import { UserInfo as RoomUserInfo, UserInfoShort as RoomUserInfoShort, - RoomInfo + RoomInfo, + UpdateTimerSetRequest, + UpdateRequest, + RoomType } from '@ucap/protocol-room'; import { LoginResponse } from '@ucap/protocol-authentication'; +import { FileInfo } from '@ucap/protocol-file'; +import { User } from '@ucap/protocol-info'; + +import { I18nService } from '@ucap/ng-i18n'; + +import { UserSelector } from '@ucap/ng-store-organization'; import { LoginSelector, ConfigurationSelector } from '@ucap/ng-store-authentication'; +import { + ChattingSelector, + RoomSelector, + ChattingActions, + RoomActions, + Chatting +} from '@ucap/ng-store-chat'; + +import { + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult, + ClipboardService, + SelectFileInfo +} from '@ucap/ng-ui'; + +import { LoginSession } from '@app/models/login-session'; import { AppChatService } from '@app/services/app-chat.service'; -import { VersionInfo2Response } from '@ucap/api-public'; -import moment from 'moment'; -import { Dictionary } from '@ngrx/entity'; +import { AppAuthenticationService } from '@app/services/app-authentication.service'; +import { + ProfileDialogComponent, + ProfileDialogData, + ProfileDialogResult +} from '@app/sections/organization/dialogs/profile.dialog.component'; + +import { + ForwardDialogComponent, + ForwardDialogData, + ForwardDialogResult +} from '../dialogs/forward.dialog.component'; +import { + FileViewerDialogComponent, + FileViewerDialogData, + FileViewerDialogResult +} from '../dialogs/file-viewer.dialog.component'; +import { + SettingDialogComponent, + SettingDialogData, + SettingDialogResult +} from '../dialogs/setting.dialog.component'; @Component({ selector: 'app-sections-chat-message', @@ -41,7 +96,11 @@ import { Dictionary } from '@ngrx/entity'; changeDetection: ChangeDetectionStrategy.OnPush }) -export class MessageSectionComponent implements OnInit, OnDestroy { +export class MessageSectionComponent + implements OnInit, OnDestroy, AfterViewInit { + private roomIdSubject = new Subject(); + private ngOnDestroySubject: Subject = new Subject(); + @Input() set roomId(roomId: string) { this._roomId = roomId; @@ -56,13 +115,20 @@ export class MessageSectionComponent implements OnInit, OnDestroy { // tslint:disable-next-line: variable-name _roomId: string; + @Input() + translationSimpleview: boolean; + + @Input() + eventSendTrigger$: Observable; + versionInfo2Res: VersionInfo2Response; loginRes: LoginResponse; - - roomIdSubject = new Subject(); + user: User; + loginSession: LoginSession; currentRoomInfo: RoomInfo; - chatting$: Observable; + currentChatting: Chatting; + currentFileInfoList: FileInfo[] = []; roomUsers: RoomUserInfoShort[] = []; // eventList$: Observable[]>; eventList: Info[]; @@ -71,11 +137,26 @@ export class MessageSectionComponent implements OnInit, OnDestroy { EventType = EventType; FileType = FileType; - private ngOnDestroySubject = new Subject(); + /** Timer 대화방의 대화 삭제를 위한 interval */ + interval: any; + + /** new Chat room setting dialog */ + settingDialogRef: MatDialogRef; + + /** About Scroll */ + isInitScrollbottom = true; + @ViewChild('chatMessagesContainer', { static: false }) + chatMessagesContainer: ElementRef; + + String = String; + constructor( private appChatService: AppChatService, private store: Store, - private changeDetectorRef: ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef, + private dialog: MatDialog, + private i18nService: I18nService, + private appAuthenticationService: AppAuthenticationService ) { this.defaultProfileImage = this.appChatService.defaultProfileImage; } @@ -86,44 +167,69 @@ export class MessageSectionComponent implements OnInit, OnDestroy { takeUntil(this.ngOnDestroySubject), select(ConfigurationSelector.versionInfo2Response) ) - .subscribe((versionInfo2Res) => { - this.versionInfo2Res = versionInfo2Res; - }); - this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; - }); + .subscribe((versionInfo2Res) => (this.versionInfo2Res = versionInfo2Res)); this.store - .pipe( - takeUntil(this.ngOnDestroySubject), - select( - (state: any) => state.chat.room.rooms.entities as Dictionary - ) - ) - .subscribe((rooms) => { - if (!!this.roomId) { - this.currentRoomInfo = rooms[this.roomId]; - } + .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) + .subscribe((loginRes) => (this.loginRes = loginRes)); + + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => (this.user = user)); + + this.eventSendTrigger$ + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe((_) => { + // Fires when I enter the event + this.gotoScrollToBottom(); }); } + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + + if (!!this.roomIdSubject) { + this.roomIdSubject.next(); + this.roomIdSubject.complete(); + } + + if (!!this.interval) { + clearInterval(this.interval); + this.interval = undefined; + } + } + + ngAfterViewInit() {} + initializeRoomData() { - this.chatting$ = this.store.pipe( - takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)), - select(ChattingSelector.chatting, this.roomId) - ); + /** About initialize roomId */ + this.isInitScrollbottom = true; + + this.loginSession = this.appAuthenticationService.getLoginSession(); this.store .pipe( takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)), - select(ChattingSelector.eventList, this.roomId) + select(RoomSelector.room, this.roomId) ) - .subscribe((eventList) => { - this.eventList = eventList; + .subscribe((room) => { + this.currentRoomInfo = room; - this.changeDetectorRef.detectChanges(); + // About Interval + if (!!this.interval) { + clearInterval(this.interval); + this.interval = undefined; + } + if (!!this.currentRoomInfo && !!this.currentRoomInfo.isTimeRoom) { + this.interval = setInterval(() => { + this.store.dispatch( + ChattingActions.intervalClearEvent({ roomId: this.roomId }) + ); + }, 1000); + } }); this.store @@ -134,13 +240,110 @@ export class MessageSectionComponent implements OnInit, OnDestroy { .subscribe((roomUserShort) => { if (!!roomUserShort) { this.roomUsers = roomUserShort.userInfos; + this.changeDetectorRef.markForCheck(); + } + }); + + this.store + .pipe( + takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)), + select(ChattingSelector.chatting, this.roomId) + ) + .subscribe((chatting) => { + this.currentChatting = chatting; + if ( + !!chatting && + !!chatting.fileInfoList && + chatting.fileInfoList.ids.length > 0 + ) { + const tempFileInfoList: FileInfo[] = []; + const fileInfoDic = chatting.fileInfoList.entities as Dictionary< + FileInfo + >; + + for (const key in fileInfoDic) { + if (key === undefined) { + continue; + } + + if (fileInfoDic.hasOwnProperty(key)) { + const fileInfo = fileInfoDic[key]; + tempFileInfoList.push(fileInfo); + } + } + + this.currentFileInfoList = tempFileInfoList; + } + }); + + this.eventList = []; + this.store + .pipe( + takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject)), + select(ChattingSelector.eventList, this.roomId) + ) + .subscribe((eventList) => { + if (!!eventList && eventList.length > 0) { + this.eventList = eventList; + this.changeDetectorRef.markForCheck(); + + if (!!this.isInitScrollbottom) { + this.gotoScrollToBottom(); + this.isInitScrollbottom = false; + } + } + }); + + /** Settings popup that opens when you first open a chat room */ + combineLatest([ + this.store.pipe(select(RoomSelector.room, this.roomId)), + this.store.pipe(select(RoomSelector.standbyRooms)) + ]) + .pipe(takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject))) + .subscribe(([roomInfo, standbyRooms]) => { + if ( + !!roomInfo && + roomInfo.roomId === this.roomId && + roomInfo.roomType !== RoomType.Mytalk && + roomInfo.roomType !== RoomType.Single && + !!standbyRooms && + standbyRooms.length > 0 && + standbyRooms.findIndex((roomId) => roomId === this.roomId) > -1 + ) { + if (!this.settingDialogRef) { + this.settingDialogRef = this.dialog.open< + SettingDialogComponent, + SettingDialogData, + SettingDialogResult + >(SettingDialogComponent, { + panelClass: 'min-create-dialog', + data: { + roomId: this.roomId + } + }); + + this.settingDialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result: SettingDialogResult) => { + if (!!result && !!result.choice) { + this._standByRoomSetting(result); + } + }); + + this.changeDetectorRef.markForCheck(); + } } }); } - ngOnDestroy(): void { - if (!!this.ngOnDestroySubject) { - this.ngOnDestroySubject.complete(); + gotoScrollToBottom() { + if (!!this.chatMessagesContainer) { + const self = this; + setTimeout(() => { + self.chatMessagesContainer.nativeElement.scrollTop = + self.chatMessagesContainer.nativeElement.scrollHeight; + }, 500); } } @@ -185,4 +388,256 @@ export class MessageSectionComponent implements OnInit, OnDestroy { .filter((user) => user.lastReadEventSeq < message.seq).length; return unreadCnt === 0 ? '' : unreadCnt; } + + onClickShowPreviousEvents(event: MouseEvent) { + event.preventDefault(); + event.stopPropagation(); + + if (!!this.currentChatting.remainEvent) { + this.store.dispatch( + ChattingActions.moreEvents({ + roomId: this.roomId + }) + ); + } + } + + async onClickMessageContextMenu(params: { + menuType: string; + message: Info; + }) { + switch (params.menuType) { + case 'COPY': + { + switch (params.message.type) { + case EventType.Character: + { + this.appChatService.copyFromContentText( + (params.message as Info).sentMessage + ); + } + break; + case EventType.MassText: + { + this.appChatService.massTextDownload({ + userSeq: String(this.user.info.seq), + deviceType: this.loginSession.deviceType, + token: this.loginRes.tokenString, + eventMassSeq: params.message.seq + }); + } + break; + case EventType.Translation: + { + } + break; + case EventType.MassTranslation: + { + } + break; + default: + break; + } + } + break; + case 'RECALL': + { + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:label.recallEvent'), + html: this.i18nService.t('chat:dialog.confirmRecallEvent') + } + }); + + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && !!result.choice) { + this.store.dispatch( + ChattingActions.cancel({ + roomId: this.currentRoomInfo.roomId, + eventSeq: params.message.seq, + deviceType: this.loginSession.deviceType + }) + ); + } + }); + } + break; + case 'FORWARD': + { + const dialogRef = this.dialog.open< + ForwardDialogComponent, + ForwardDialogData, + ForwardDialogResult + >(ForwardDialogComponent, { + panelClass: 'max-create-dialog' + }); + dialogRef + .afterOpened() + .pipe(take(1)) + .subscribe(() => { + dialogRef.componentInstance.psUpdate(); + }); + + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && !!result.choice) { + const userSeqs: string[] = []; + let roomSeq = ''; + if ( + !!result.selelctUserList && + result.selelctUserList.length > 0 + ) { + result.selelctUserList.map((user) => + userSeqs.push(String(user.seq)) + ); + } + if (!!result.selectRoom) { + roomSeq = result.selectRoom.roomId; + } + if (userSeqs.length > 0 || roomSeq.trim().length > 0) { + this.store.dispatch( + ChattingActions.forward({ + senderSeq: String(this.user.info.seq), + deviceType: this.loginSession.deviceType, + req: { + roomId: '-999', + eventType: params.message.type, + sentMessage: params.message.sentMessage + }, + trgtUserSeqs: userSeqs, + trgtRoomId: roomSeq + }) + ); + } + } + }); + } + break; + case 'FORWARD_TO_ME': + { + if (this.loginRes.talkWithMeBotSeq > -1) { + const seqs = this.user.talkWithMeBotSeq as any; + this.store.dispatch( + ChattingActions.forward({ + senderSeq: String(this.user.info.seq), + deviceType: this.loginSession.deviceType, + req: { + roomId: '-999', + eventType: params.message.type, + sentMessage: params.message.sentMessage + }, + trgtUserSeqs: [seqs] + }) + ); + } + } + break; + case 'DELETE': + { + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:dialog.title.removeChat'), + html: this.i18nService.t('chat:dialog.confirmRemoveChat') + } + }); + + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && !!result.choice) { + this.store.dispatch( + ChattingActions.del({ + roomId: this.currentRoomInfo.roomId, + eventSeq: params.message.seq + }) + ); + } + }); + } + break; + } + } + + onMassTranslationDetail(params: { + message: Info; + contentsType: string; + }) { + const req = { + userSeq: String(this.user.info.seq), + token: this.loginRes.tokenString, + deviceType: this.loginSession.deviceType, + eventTransSeq: params.message.sentMessageJson.translationSeq.toString() + } as TransMassTalkDownloadRequest; + this.appChatService.massTranslationMessageDetail( + req, + params.message, + params.contentsType, + this.roomId + ); + } + + onFileViewer(selectFileInfo: SelectFileInfo) { + const data: FileViewerDialogData = { + fileInfos: this.currentFileInfoList, + selectFileInfo, + downloadUrl: this.versionInfo2Res.downloadUrl, + deviceType: this.loginSession.deviceType, + token: this.loginRes.tokenString, + userSeq: String(this.user.info.seq) + }; + this.appChatService.openFileviwer(data); + } + + private _standByRoomSetting(result: SettingDialogResult) { + if (result.timerInterval > 0 && !!result.roomInfo.isTimeRoom) { + this.store.dispatch( + RoomActions.updateTimeRoomInterval({ + req: { + roomId: result.roomInfo.roomId, + timerInterval: result.timerInterval + } as UpdateTimerSetRequest + }) + ); + } else { + this.store.dispatch( + RoomActions.update({ + req: { + roomId: result.roomInfo.roomId, + roomName: result.roomName, + receiveAlarm: result.roomInfo.receiveAlarm, + syncAll: result.syncAll + } as UpdateRequest + }) + ); + } + } + + onOpenProfile(userSeq: string): void { + const result = this.dialog.open< + ProfileDialogComponent, + ProfileDialogData, + ProfileDialogResult + >(ProfileDialogComponent, { + panelClass: 'mid-create-dialog', + data: { + userSeq + } + }); + } } diff --git a/src/app/sections/chat/components/search.section.component.html b/src/app/sections/chat/components/search.section.component.html index b25f174..292fe4d 100644 --- a/src/app/sections/chat/components/search.section.component.html +++ b/src/app/sections/chat/components/search.section.component.html @@ -1,14 +1,16 @@
    - +
    diff --git a/src/app/sections/chat/components/search.section.component.scss b/src/app/sections/chat/components/search.section.component.scss index e69de29..863ac06 100644 --- a/src/app/sections/chat/components/search.section.component.scss +++ b/src/app/sections/chat/components/search.section.component.scss @@ -0,0 +1,33 @@ +@import '~@ucap/lg-scss/mixins'; + +.search-container { + padding: 0 16px 10px; + background-color: $white; + .searchbox { + display: flex; + flex-flow: row nowrap; + width: 100%; + border: 1px solid $lipstick; + background-color: $white; + .search-in-box { + flex-grow: 1; + @include ucapMatFormField( + 0, + 0, + auto, + auto, + auto, + 38px, + 38px + ); //$border: 1px solid #cccccc, $border-radius: 0, $width: 100%, $max-width, $min-width, $height, $line-height, $background-color: white + padding-left: 10px; + .btn-close { + margin-top: 2px; + color: #fd78a1 !important; + } + } + .btn-ico-search { + @include ucapMatButton(38px, 38px, 0, 20px); + } + } +} diff --git a/src/app/sections/chat/components/search.section.component.ts b/src/app/sections/chat/components/search.section.component.ts index 4651d79..6ae71b5 100644 --- a/src/app/sections/chat/components/search.section.component.ts +++ b/src/app/sections/chat/components/search.section.component.ts @@ -1,3 +1,6 @@ +import { Subject, combineLatest } from 'rxjs'; +import { takeUntil, debounceTime } from 'rxjs/operators'; + import { Component, OnInit, @@ -12,15 +15,14 @@ import { FormGroup, FormBuilder } from '@angular/forms'; import { Store, select } from '@ngrx/store'; +import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; + +import { User } from '@ucap/protocol-info'; + import { LogService } from '@ucap/ng-logger'; -import { takeUntil, debounceTime } from 'rxjs/operators'; -import { Subject, combineLatest } from 'rxjs'; import { RoomSelector } from '@ucap/ng-store-chat'; -import { LoginSelector } from '@ucap/ng-store-authentication'; - -import { LoginResponse } from '@ucap/protocol-authentication'; -import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; +import { UserSelector } from '@ucap/ng-store-organization'; @Component({ selector: 'app-sections-chat-search', @@ -37,7 +39,7 @@ export class SearchSectionComponent implements OnInit, OnDestroy { @Output() searchCancel = new EventEmitter(); - loginRes: LoginResponse; + user: User; fgSearch: FormGroup; recommendedWordList: string[]; @@ -45,7 +47,7 @@ export class SearchSectionComponent implements OnInit, OnDestroy { @ViewChild(MatAutocompleteTrigger) autocomplete: MatAutocompleteTrigger; - private ngOnDestroySubject: Subject; + private ngOnDestroySubject: Subject = new Subject(); constructor( private store: Store, @@ -55,8 +57,6 @@ export class SearchSectionComponent implements OnInit, OnDestroy { ) {} ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - this.fgSearch = this.formBuilder.group({ searchInput: null }); @@ -75,25 +75,39 @@ export class SearchSectionComponent implements OnInit, OnDestroy { this.filteredRecommendedWordList = []; } - this.changeDetectorRef.detectChanges(); + this.changeDetectorRef.markForCheck(); }); this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; }); combineLatest([ this.store.pipe(select(RoomSelector.rooms)), this.store.pipe(select(RoomSelector.roomUsers)), - this.store.pipe(select(RoomSelector.roomUsersShort)) + this.store.pipe(select(RoomSelector.roomUsersShort)), + this.store.pipe(select(RoomSelector.standbyRooms)) ]) .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe(([rooms, roomUsers, roomUsersShort]) => { - rooms = rooms || []; - roomUsers = roomUsers || []; - roomUsersShort = roomUsersShort || []; + .subscribe(([rooms, roomUsers, roomUsersShort, standbyRooms]) => { + rooms = (rooms || []).filter((info) => { + return ( + info.isJoinRoom && + !standbyRooms.find((standbyRoom) => standbyRoom === info.roomId) + ); + }); + roomUsers = (roomUsers || []).filter( + (userMap) => + rooms.findIndex((roomInfo) => roomInfo.roomId === userMap.roomId) > + -1 + ); + roomUsersShort = (roomUsersShort || []).filter( + (userMap) => + rooms.findIndex((roomInfo) => roomInfo.roomId === userMap.roomId) > + -1 + ); const recommendedWordList = []; for (const r of rooms) { @@ -103,7 +117,7 @@ export class SearchSectionComponent implements OnInit, OnDestroy { } for (const ru of roomUsers) { for (const u of ru.userInfos) { - if (!!this.loginRes && u.seq !== Number(this.loginRes.userSeq)) { + if (!!this.user && String(u.seq) !== String(this.user.info.seq)) { if (!!u.name && '' !== u.name.trim() && u.isJoinRoom) { recommendedWordList.push(u.name); } @@ -112,7 +126,7 @@ export class SearchSectionComponent implements OnInit, OnDestroy { } for (const ru of roomUsersShort) { for (const u of ru.userInfos) { - if (!!this.loginRes && u.seq !== Number(this.loginRes.userSeq)) { + if (!!this.user && String(u.seq) !== String(this.user.info.seq)) { if (!!u.name && '' !== u.name.trim() && u.isJoinRoom) { recommendedWordList.push(u.name); } @@ -129,6 +143,7 @@ export class SearchSectionComponent implements OnInit, OnDestroy { ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } diff --git a/src/app/sections/chat/dialogs/clipboard.dialog.component.html b/src/app/sections/chat/dialogs/clipboard.dialog.component.html new file mode 100644 index 0000000..3edaaeb --- /dev/null +++ b/src/app/sections/chat/dialogs/clipboard.dialog.component.html @@ -0,0 +1,26 @@ +
    + +
    +
    클립보드
    +
    {{ data.content.imageDataUrl }}
    +
    +
    + +
    + +
    +
    +
    +
    + + +
    +
    +
    diff --git a/src/app/sections/chat/dialogs/clipboard.dialog.component.scss b/src/app/sections/chat/dialogs/clipboard.dialog.component.scss new file mode 100644 index 0000000..c274a9d --- /dev/null +++ b/src/app/sections/chat/dialogs/clipboard.dialog.component.scss @@ -0,0 +1,59 @@ +.forwrad-dialog-container { + width: 100%; + height: 100%; + + overflow: hidden; + .select-user-section-search { + padding: 0 0 10px; + } + + .forwrad-dialog-content { + height: calc(100% - 50px); + .tap-container { + height: 100%; + } + } + .organization-tree-simple { + height: 50px; + display: flex; + flex-direction: row; + align-items: center; + background-color: #f1f2f6; + padding: 0 16px; + overflow: hidden; + .organization-dept { + display: flex; + align-content: center; + &:first-child { + .btn-navigate-next { + display: none; + } + } + &:last-child { + .dept-name { + background-color: #fd578a; + color: #ffffff; + border: none; + } + } + .dept-name { + border-radius: 100px; + height: 30px; + box-shadow: none; + border: 1px solid #999999; + color: #666666; + } + .btn-navigate-next { + width: 30px; + height: 30px; + color: #cccccc; + } + } + } + .organization-tree { + width: 100%; + height: calc(100% - 30px); + padding-bottom: 10px; + display: none; + } +} diff --git a/src/app/sections/chat/components/component-ui/expansion.component.spec.ts b/src/app/sections/chat/dialogs/clipboard.dialog.component.spec.ts similarity index 57% rename from src/app/sections/chat/components/component-ui/expansion.component.spec.ts rename to src/app/sections/chat/dialogs/clipboard.dialog.component.spec.ts index 31dc1f4..831cf64 100644 --- a/src/app/sections/chat/components/component-ui/expansion.component.spec.ts +++ b/src/app/sections/chat/dialogs/clipboard.dialog.component.spec.ts @@ -2,20 +2,20 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; -import { ExpansionComponent } from './expansion.component'; +import { ForwardDialogComponent } from './forward.dialog.component'; -describe('ucap::ui-group::ExpansionComponent', () => { - let component: ExpansionComponent; - let fixture: ComponentFixture; +describe('app::ui-chat::ForwardDialogComponent', () => { + let component: ForwardDialogComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ExpansionComponent] + declarations: [ForwardDialogComponent] }).compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(ExpansionComponent); + fixture = TestBed.createComponent(ForwardDialogComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/sections/chat/dialogs/clipboard.dialog.component.ts b/src/app/sections/chat/dialogs/clipboard.dialog.component.ts new file mode 100644 index 0000000..c2fdf30 --- /dev/null +++ b/src/app/sections/chat/dialogs/clipboard.dialog.component.ts @@ -0,0 +1,80 @@ +import { Subject, combineLatest } from 'rxjs'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Inject +} from '@angular/core'; + +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatDialog +} from '@angular/material/dialog'; + +import { I18nService } from '@ucap/ng-i18n'; + +import { Store, select } from '@ngrx/store'; +import { takeUntil } from 'rxjs/operators'; +import { AppChatService } from '@app/services/app-chat.service'; +import { + TranslatePipe as OrganizationTranslate, + TranslateService +} from '@ucap/ng-ui-organization'; + +export interface ClipboardDialogData { + content: { + text?: string; + rtf?: string; + html?: string; + image?: Buffer; + imageDataUrl?: string; + }; +} +export interface ClipboardDialogResult {} + +@Component({ + selector: 'app-dialog-chat-clipboard', + templateUrl: './clipboard.dialog.component.html', + styleUrls: ['./clipboard.dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ClipboardDialogComponent implements OnInit, OnDestroy { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: ClipboardDialogData, + private store: Store, + private i18nService: I18nService, + public dialog: MatDialog, + private changeDetectorRef: ChangeDetectorRef, + private appChatService: AppChatService, + private translateService: TranslateService + ) { + // language setting + this.translateService.setDefaultLang(this.i18nService.currentLng); + this.translateService.use(this.i18nService.currentLng); + } + + private ngOnDestroySubject: Subject = new Subject(); + + ngOnInit(): void {} + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onClosed(event: MouseEvent): void { + this.dialogRef.close({ choice: false }); + } + + onCancel() { + this.dialogRef.close({ choice: false }); + } + onConfirm() {} +} diff --git a/src/app/sections/chat/dialogs/create.dialog.component.html b/src/app/sections/chat/dialogs/create.dialog.component.html index 433b7a2..d8dd678 100644 --- a/src/app/sections/chat/dialogs/create.dialog.component.html +++ b/src/app/sections/chat/dialogs/create.dialog.component.html @@ -2,81 +2,164 @@
    - {{ 'dialog.title.newChatRoom' | ucapI18n }} + + {{ 'dialog.title.newChatRoom' | ucapI18n }} + + + {{ (!!isTimer ? 'dialog.timerRoom' : 'dialog.normalRoom') | ucapI18n }} + +
    + + {{ 'dialog.title.subSelectRoomType' | ucapI18n }} + + + {{ 'dialog.title.subSelectUser' | ucapI18n }} + +
    +
    - -
    - {{ 'dialog.normalRoom' | ucapI18n }} - -
    -
    + +
    +
    + {{ 'dialog.normalRoom' | ucapI18n }} +
    +
    +
    + + + +
    +
    -
    - {{ 'dialog.timerRoom' | ucapI18n }} - -
    +
    + {{ 'dialog.timerRoom' | ucapI18n }} +
    +
    +
    + + + +
    +
    - +
    + +
    +
    + + +

    + {{ 'dialog.selectedUserList' | ucapI18n }} +

    + ({{ selectedUserList?.length }}/{{ maxChatRoomUser - 1 }}) +
    +
    +
    -
    - + +
    + +
    diff --git a/src/app/sections/chat/dialogs/forward.dialog.component.scss b/src/app/sections/chat/dialogs/forward.dialog.component.scss new file mode 100644 index 0000000..0d19337 --- /dev/null +++ b/src/app/sections/chat/dialogs/forward.dialog.component.scss @@ -0,0 +1,74 @@ +@import '~@ucap/lg-scss/mixins'; +.forwrad-dialog-container { + width: 100%; + height: 100%; + + .dialog-body { + width: 100%; + height: 100%; + display: flex; + flex-direction: row; + @include screen(custom, max, 768) { + flex-direction: column; + } + } + overflow: hidden; + .select-user-section-search { + padding: 0 0 10px; + } + + .forwrad-dialog-content { + height: calc(100% - 50px); + .tap-container { + height: 100%; + } + } + + .ucap-dialog-app-group-select-user { + flex: 1 0 auto; + //width: 60%; + height: 100%; + border-bottom: 1px solid #cccccc; + background-color: #ffffff; + @include screen(custom, max, 768) { + width: 100%; + height: 78%; + margin-bottom: 2%; + } + } + .ucap-dialog-organization-profile-selection { + position: relative; + //width: 40%; + flex: 0 1 40%; + padding-left: $default-space; + border-bottom: 1px solid #cccccc; + background-color: #ffffff; + @include screen(custom, max, 768) { + width: 100%; + height: 20%; + padding: 0; + flex: 0 1 auto; + } + .ucap-organization-selected-list { + width: 100%; + height: 100%; + display: flex; + border: 1px solid #cccccc; + border-bottom: none; + overflow: auto; + background-color: #ffffff; + .ucap-organization-selected-header { + display: flex; + align-items: center; + padding: 0 10px; + height: 40px; + //background-color: #eeeeee; + .description { + strong { + color: #e42f66; + } + } + } + } + } +} diff --git a/src/app/sections/chat/dialogs/forward.dialog.component.spec.ts b/src/app/sections/chat/dialogs/forward.dialog.component.spec.ts new file mode 100644 index 0000000..831cf64 --- /dev/null +++ b/src/app/sections/chat/dialogs/forward.dialog.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { ForwardDialogComponent } from './forward.dialog.component'; + +describe('app::ui-chat::ForwardDialogComponent', () => { + let component: ForwardDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ForwardDialogComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ForwardDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/sections/chat/dialogs/forward.dialog.component.ts b/src/app/sections/chat/dialogs/forward.dialog.component.ts new file mode 100644 index 0000000..cb12cfb --- /dev/null +++ b/src/app/sections/chat/dialogs/forward.dialog.component.ts @@ -0,0 +1,356 @@ +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Inject, + ViewChild +} from '@angular/core'; + +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatDialog +} from '@angular/material/dialog'; + +import { Store, select } from '@ngrx/store'; + +import { VersionInfo2Response } from '@ucap/api-public'; +import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; +import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; +import { UserInfo as RoomUserInfo, RoomInfo } from '@ucap/protocol-room'; + +import { I18nService } from '@ucap/ng-i18n'; + +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; +import { GroupSelector } from '@ucap/ng-store-group'; + +import { + AlertDialogComponent, + AlertDialogData, + AlertDialogResult +} from '@ucap/ng-ui'; + +import { SearchData } from '@app/ucap/organization/models/search-data'; +import { Expansion02Component as AppExpansion02Component } from '@app/ucap/group/components/expansion-02.component'; +import { environment } from '@environments'; + +export type UserInfoTypes = + | UserInfo + | UserInfoSS + | UserInfoF + | UserInfoDN + | RoomUserInfo; + +export interface ForwardDialogData {} +export interface ForwardDialogResult { + choice: boolean; + selelctUserList?: UserInfoTypes[]; + selectRoom?: RoomInfo; +} + +@Component({ + selector: 'app-dialog-chat-forward', + templateUrl: './forward.dialog.component.html', + styleUrls: ['./forward.dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ForwardDialogComponent implements OnInit, OnDestroy { + @ViewChild('appGroupExpansion', { static: false }) + appGroupExpansion: AppExpansion02Component; + + set companySearchData(searchData: SearchData) { + if (!!searchData && searchData.searchWord !== '') { + this._companySearchData = { ...searchData, bySearch: true }; + } else { + this._companySearchData = { ...searchData, bySearch: false }; + } + + this.onChangedCompanySearch(); + } + get companySearchData() { + return this._companySearchData; + } + // tslint:disable-next-line: variable-name + _companySearchData: SearchData; + + private ngOnDestroySubject: Subject = new Subject(); + + maxChatRoomUser: number; + + selectedRoom: RoomInfo; + selectedUserList: UserInfoTypes[] = []; + + versionInfo2Res: VersionInfo2Response; + + groupList: GroupDetailData[]; + + isSearch = false; + searchedList: UserInfoSS[] = []; + currentTabIndex: number; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: ForwardDialogData, + private store: Store, + private i18nService: I18nService, + public dialog: MatDialog, + private changeDetectorRef: ChangeDetectorRef + ) { + this.maxChatRoomUser = environment.productConfig.chat.maxChatRoomUser; + } + + ngOnInit(): void { + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(GroupSelector.groups)) + .subscribe((groups) => { + this.groupList = groups; + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + psUpdate() { + this.appGroupExpansion.psUpdate(); + } + onClosed(event: MouseEvent): void { + this.dialogRef.close({ choice: false }); + } + + onSelectedIndexChange(value: number): void { + this.currentTabIndex = value; + this._resetSelectedObject(); + } + + private _resetSelectedObject(): void { + this.selectedRoom = undefined; + this.selectedUserList = []; + } + + onSearched(searchedUserInfos: UserInfoSS[]): void { + this.searchedList = searchedUserInfos; + } + + onToggleCheckUser(datas: { checked: boolean; userInfo: UserInfoSS }[]) { + if (!datas || 0 === datas.length) { + return; + } + + const pushs: UserInfoSS[] = []; + const pops: UserInfoSS[] = []; + + datas.forEach((d) => { + const i = this.selectedUserList.findIndex( + (u) => u.seq === d.userInfo.seq + ); + if (d.checked) { + if (-1 === i) { + pushs.push(d.userInfo); + } + } else { + if (-1 < i) { + pops.push(d.userInfo); + } + } + }); + + if (0 < pushs.length) { + this.selectedUserList = [...this.selectedUserList, ...pushs]; + } + + if (0 < pops.length) { + this.selectedUserList = this.selectedUserList.filter( + (u) => -1 === pops.findIndex((p) => p.seq === u.seq) + ); + } + } + + onToggleCheckGroup(params: { + isChecked: boolean; + groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; + }) { + if (params.isChecked) { + params.groupBuddyList.buddyList.forEach((item) => { + if ( + this.selectedUserList.filter((user) => user.seq === item.seq) + .length === 0 + ) { + this.selectedUserList = [...this.selectedUserList, item]; + } + }); + } else { + this.selectedUserList = this.selectedUserList.filter( + (item) => + params.groupBuddyList.buddyList.filter((del) => del.seq === item.seq) + .length === 0 + ); + } + } + + onCancel() { + this.dialogRef.close({ choice: false }); + } + onConfirm() { + if ( + !this.selectedRoom && + !!this.selectedUserList && + this.selectedUserList.length === 0 + ) { + const result = this.dialog.open< + AlertDialogComponent, + AlertDialogData, + AlertDialogResult + >(AlertDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:errors.label'), + message: this.i18nService.t('chat:errors.inputChatMessage') + } + }); + return; + } + + this.dialogRef.close({ + choice: true, + selectRoom: this.selectedRoom, + selelctUserList: this.selectedUserList + }); + } + + onToggleRoom(roomInfo: RoomInfo): void { + if (!!roomInfo) { + this.selectedRoom = roomInfo; + } + } + + getCheckedByRoomInfo(roomInfo: RoomInfo): boolean { + if ( + !!this.selectedRoom && + this.selectedRoom.roomId.localeCompare(roomInfo.roomId) === 0 + ) { + return true; + } + + return false; + } + onChangeUserList(data: { checked: boolean; userInfo: UserInfoSS }) { + const i = this.selectedUserList.findIndex( + (u) => String(u.seq) === String(data.userInfo.seq) + ); + + if (data.checked) { + if (-1 === i) { + this.selectedUserList = [...this.selectedUserList, data.userInfo]; + } + } else { + if (-1 < i) { + this.selectedUserList = this.selectedUserList.filter( + (u) => String(u.seq) !== String(data.userInfo.seq) + ); + } + } + } + + onChangeGroupList(params: { + isChecked: boolean; + groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; + }) { + if (params.isChecked) { + params.groupBuddyList.buddyList.forEach((item) => { + if ( + this.selectedUserList.filter( + (user) => String(user.seq) === String(item.seq) + ).length === 0 + ) { + this.selectedUserList = [...this.selectedUserList, item]; + } + }); + } else { + this.selectedUserList = this.selectedUserList.filter( + (item) => + params.groupBuddyList.buddyList.filter( + (del) => String(del.seq) === String(item.seq) + ).length === 0 + ); + } + } + + onChangedCompanySearch() { + if (!!this.companySearchData && this.companySearchData.bySearch) { + this.isSearch = true; + } + } + + getCheckedSearchAllItem(): boolean { + const targetRoomList = this.searchedList; + + if ( + !targetRoomList || + targetRoomList.length === 0 || + targetRoomList.filter( + (item) => + !( + this.selectedUserList.filter( + (info) => String(info.seq) === String(item.seq) + ).length > 0 + ) + ).length > 0 + ) { + return false; + } else { + return true; + } + } + + onToggleSearchAllItem(value: boolean): void { + if (!!value) { + const targetRoomList = this.searchedList; + this.selectedUserList = targetRoomList.slice(); + } else { + this.selectedUserList = []; + } + this.changeDetectorRef.markForCheck(); + } + + onCanceled() { + this.isSearch = false; + this.searchedList = []; + this.companySearchData = { ...this.companySearchData, searchWord: '' }; + } + + onRemovedProfileSelection(userInfo: UserInfo) { + const i = this.selectedUserList.findIndex( + (u) => String(u.seq) === String(userInfo.seq) + ); + + if (-1 < i) { + this.selectedUserList = this.selectedUserList.filter( + (u) => String(u.seq) !== String(userInfo.seq) + ); + } + } + removableForSelection = (userInfo: UserInfo) => { + return true; + }; + colorForSelection = (userInfo: UserInfo) => { + return 'accent'; + }; +} diff --git a/src/app/sections/chat/dialogs/index.ts b/src/app/sections/chat/dialogs/index.ts index 5a5d084..c7cf92f 100644 --- a/src/app/sections/chat/dialogs/index.ts +++ b/src/app/sections/chat/dialogs/index.ts @@ -1,3 +1,15 @@ import { CreateDialogComponent } from './create.dialog.component'; +import { ForwardDialogComponent } from './forward.dialog.component'; +import { TextDetailDialogComponent } from './text-detail.dialog.component'; +import { FileViewerDialogComponent } from './file-viewer.dialog.component'; +import { TransDetailDialogComponent } from './trans-detail.dialog.component'; +import { SettingDialogComponent } from './setting.dialog.component'; -export const DIALOGS = [CreateDialogComponent]; +export const DIALOGS = [ + CreateDialogComponent, + ForwardDialogComponent, + TextDetailDialogComponent, + FileViewerDialogComponent, + TransDetailDialogComponent, + SettingDialogComponent +]; diff --git a/src/app/sections/chat/dialogs/setting.dialog.component.html b/src/app/sections/chat/dialogs/setting.dialog.component.html new file mode 100644 index 0000000..92f55be --- /dev/null +++ b/src/app/sections/chat/dialogs/setting.dialog.component.html @@ -0,0 +1,51 @@ +
    + +
    +
    + {{ 'chat:dialog.title.roomNameGuide' | ucapI18n }} +
    +
    + {{ 'chat:dialog.title.roomTimerGuide' | ucapI18n }} +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + timer +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    diff --git a/src/app/sections/chat/dialogs/setting.dialog.component.scss b/src/app/sections/chat/dialogs/setting.dialog.component.scss new file mode 100644 index 0000000..7f7c53d --- /dev/null +++ b/src/app/sections/chat/dialogs/setting.dialog.component.scss @@ -0,0 +1,42 @@ +.room-setting-dialog-container { + width: 100%; + height: 100%; + .dialog-body { + flex-direction: column; + .guide-info { + display: flex; + flex-direction: row; + padding: 10px; + &-img { + justify-content: center; + align-items: center; + display: flex; + flex: 0 0 60px; + width: 60px; + height: 60px; + border-radius: 50%; + overflow: hidden; + margin-right: 24px; + background-color: #f1f2f6; + img { + width: 60px; + height: 60px; + } + .mat-icon { + height: 40px; + width: 40px; + font-size: 40px; + color: #999999; + } + } + &-text { + flex: 1 1 auto; + font-size: 0.94em; + } + } + .setting-info { + padding: 10px; + margin-top: 10px; + } + } +} diff --git a/src/app/sections/chat/dialogs/setting.dialog.component.spec.ts b/src/app/sections/chat/dialogs/setting.dialog.component.spec.ts new file mode 100644 index 0000000..831cf64 --- /dev/null +++ b/src/app/sections/chat/dialogs/setting.dialog.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { ForwardDialogComponent } from './forward.dialog.component'; + +describe('app::ui-chat::ForwardDialogComponent', () => { + let component: ForwardDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ForwardDialogComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ForwardDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/sections/chat/dialogs/setting.dialog.component.ts b/src/app/sections/chat/dialogs/setting.dialog.component.ts new file mode 100644 index 0000000..b89e969 --- /dev/null +++ b/src/app/sections/chat/dialogs/setting.dialog.component.ts @@ -0,0 +1,140 @@ +import { Subject } from 'rxjs'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Inject +} from '@angular/core'; + +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatDialog, + MatDialogConfig +} from '@angular/material/dialog'; + +import { I18nService } from '@ucap/ng-i18n'; + +import { Store, select } from '@ngrx/store'; +import { takeUntil } from 'rxjs/operators'; +import { RoomInfo } from '@ucap/protocol-room'; +import { RoomSelector } from '@ucap/ng-store-chat'; + +export interface SettingDialogData { + roomId: string; + rect?: any; +} +export interface SettingDialogResult { + choice: boolean; + roomInfo?: RoomInfo; + roomName?: string; + syncAll?: boolean; + timerInterval?: number; +} + +@Component({ + selector: 'app-dialog-chat-setting', + templateUrl: './setting.dialog.component.html', + styleUrls: ['./setting.dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SettingDialogComponent implements OnInit, OnDestroy { + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: SettingDialogData, + private store: Store, + private i18nService: I18nService, + private dialog: MatDialog, + private changeDetectorRef: ChangeDetectorRef + ) {} + + currentRoomInfo: RoomInfo; + currentParams: { + invalid: boolean; + roomName: string; + checkedMe: string; + timerInterval: number; + }; + + ngOnInit(): void { + if (!!this.data.rect) { + const matDialogConfig: MatDialogConfig = new MatDialogConfig(); + matDialogConfig.position = { + left: `${this.data.rect.left}px`, + top: '90px' + }; + + this.dialogRef.updatePosition(matDialogConfig.position); + } + + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(RoomSelector.room, this.data.roomId) + ) + .subscribe((room) => { + if (!!room) { + this.currentRoomInfo = room; + this.changeDetectorRef.markForCheck(); + } + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onClosed(event: MouseEvent): void { + this.dialogRef.close({ choice: false }); + } + + onChangedRoomSetting(param: { + invalid: boolean; + roomName: string; + checkedMe: string; + timerInterval: number; + }) { + this.currentParams = param; + } + + onCancel() { + this.dialogRef.close({ choice: false }); + } + onConfirm() { + if ( + !!this.currentRoomInfo && + !!this.currentRoomInfo.isTimeRoom && + this.currentParams.timerInterval > 0 + ) { + this.dialogRef.close({ + choice: true, + roomInfo: this.currentRoomInfo, + timerInterval: this.currentParams.timerInterval + }); + } else if (this.currentParams.invalid) { + console.debug( + '유효하지 않은 이름: ', + this.currentParams.invalid, + this.currentParams.roomName + ); + } else { + this.dialogRef.close({ + choice: true, + roomInfo: this.currentRoomInfo, + roomName: this.currentParams.roomName, + syncAll: + !!this.currentParams && this.currentParams.checkedMe === 'all' + ? true + : false + }); + } + } +} diff --git a/src/app/sections/chat/dialogs/text-detail.dialog.component.html b/src/app/sections/chat/dialogs/text-detail.dialog.component.html new file mode 100644 index 0000000..8789f53 --- /dev/null +++ b/src/app/sections/chat/dialogs/text-detail.dialog.component.html @@ -0,0 +1,45 @@ +
    + +
    + {{ 'dialog.title.detail' | ucapI18n }} +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    diff --git a/src/app/sections/chat/dialogs/text-detail.dialog.component.scss b/src/app/sections/chat/dialogs/text-detail.dialog.component.scss new file mode 100644 index 0000000..c9e145d --- /dev/null +++ b/src/app/sections/chat/dialogs/text-detail.dialog.component.scss @@ -0,0 +1,75 @@ +@import '~@ucap/lg-scss/mixins'; +.dialog-container { + width: 100%; + height: 100%; + + .dialog-body { + width: 100%; + height: 100%; + .profile { + display: flex; + flex-direction: row; + padding-bottom: 10px; + margin-bottom: 10px; + border-bottom: 1px solid #ccc; + .profile-image { + border-radius: 50%; + overflow: hidden; + width: 36px; + height: 36px; + margin-left: 0; + background-color: #ffe8cb; + img { + max-width: 100%; + height: auto; + vertical-align: top; + border: none; + } + } + .user-info { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + padding-left: 16px; + .user-n-g { + display: flex; + flex-flow: row-reverse nowrap; + align-items: flex-end; + height: 22px; + .user-name { + @include ellipsis-column(1); + height: 22px; + font-size: 14px; + font-weight: 600; + order: 1; + -ms-flex-order: 1; + } + .user-grade { + @include ellipsis(1); + align-self: stretch; + font: { + size: 13px; + } + margin-left: 4px; + order: 0; + -ms-flex-order: 0; + } + .write-date { + font-size: 12px; + } + } + } + } + .contents { + width: 100%; + height: calc(100% - 60px); + overflow: hidden; + perfect-scrollbar { + width: 100%; + height: 100%; + padding-right: 10px; + } + } + } +} diff --git a/src/app/sections/group/components/component-ui/dialogs/create-chat.dialog.component.spec.ts b/src/app/sections/chat/dialogs/text-detail.dialog.component.spec.ts similarity index 65% rename from src/app/sections/group/components/component-ui/dialogs/create-chat.dialog.component.spec.ts rename to src/app/sections/chat/dialogs/text-detail.dialog.component.spec.ts index 46e44a0..550915f 100644 --- a/src/app/sections/group/components/component-ui/dialogs/create-chat.dialog.component.spec.ts +++ b/src/app/sections/chat/dialogs/text-detail.dialog.component.spec.ts @@ -2,20 +2,20 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; -import { CreateChatDialogComponent } from './create-chat.dialog.component'; +import { TextDetailDialogComponent } from './text-detail.dialog.component'; describe('ucap::ui-organization::CreateChatDialogComponent', () => { - let component: CreateChatDialogComponent; - let fixture: ComponentFixture; + let component: TextDetailDialogComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [CreateChatDialogComponent] + declarations: [TextDetailDialogComponent] }).compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(CreateChatDialogComponent); + fixture = TestBed.createComponent(TextDetailDialogComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/sections/chat/dialogs/text-detail.dialog.component.ts b/src/app/sections/chat/dialogs/text-detail.dialog.component.ts new file mode 100644 index 0000000..c62546f --- /dev/null +++ b/src/app/sections/chat/dialogs/text-detail.dialog.component.ts @@ -0,0 +1,166 @@ +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Inject, + ElementRef +} from '@angular/core'; + +import { Subject, of, combineLatest } from 'rxjs'; +import { take, map, catchError, takeUntil } from 'rxjs/operators'; +import { select, Store } from '@ngrx/store'; + +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatDialog +} from '@angular/material/dialog'; + +import { DeviceType } from '@ucap/core'; +import { StatusCode } from '@ucap/api'; +import { NativeService } from '@ucap/native'; +import { VersionInfo2Response } from '@ucap/api-public'; +import { MassTalkDownloadRequest } from '@ucap/api-common'; +import { + UserInfo as RoomUserInfo, + UserInfoShort as RoomUserInfoShort +} from '@ucap/protocol-room'; +import { Info, EventJson } from '@ucap/protocol-event'; + +import { CommonApiService } from '@ucap/ng-api-common'; +import { LogService } from '@ucap/ng-logger'; +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; +import { RoomSelector } from '@ucap/ng-store-chat'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; + +import { AppChatService } from '@app/services/app-chat.service'; + +export type UserInfoTypes = RoomUserInfo | RoomUserInfoShort; + +export interface TextDetailDialogData { + message: Info; + roomId: string; + userSeq: string; + deviceType: DeviceType; + token: string; + eventMassSeq?: number; +} +export interface TextDetailDialogResult {} + +@Component({ + selector: 'app-dialog-chat-text-detail', + templateUrl: './text-detail.dialog.component.html', + styleUrls: ['./text-detail.dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TextDetailDialogComponent implements OnInit, OnDestroy { + userInfo: UserInfoTypes; + contents: string; + + defaultProfileImage: string; + versionInfo2Res: VersionInfo2Response; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + public dialogRef: MatDialogRef< + TextDetailDialogData, + TextDetailDialogResult + >, + @Inject(MAT_DIALOG_DATA) public data: TextDetailDialogData, + public dialog: MatDialog, + private store: Store, + private elementRef: ElementRef, + private commonApiService: CommonApiService, + private appChatService: AppChatService, + private logService: LogService, + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit(): void { + this.defaultProfileImage = this.appChatService.defaultProfileImage; + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + + this.commonApiService + .massTalkDownload({ + userSeq: this.data.userSeq, + deviceType: this.data.deviceType, + eventMassSeq: this.data.eventMassSeq, + token: this.data.token + } as MassTalkDownloadRequest) + .pipe( + take(1), + map((res) => { + if (res.statusCode === StatusCode.Success) { + this.contents = res.content; + + this.changeDetectorRef.markForCheck(); + + setTimeout(() => { + if ( + !!this.elementRef.nativeElement && + !!this.elementRef.nativeElement.querySelector('a') + ) { + const elements = this.elementRef.nativeElement.querySelectorAll( + 'a' + ); + elements.forEach((element) => { + element.addEventListener( + 'click', + this.onClickEvent.bind(this) + ); + }); + } + }, 500); + } else { + this.logService.error( + `commonApiService] massTalkDownload ${res?.errorMessage}` + ); + } + }), + catchError((error) => of({ error })) + ) + .subscribe(); + + combineLatest([ + this.store.pipe(select(RoomSelector.roomUser, this.data.roomId)), + this.store.pipe(select(RoomSelector.roomUserShort, this.data.roomId)) + ]) + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe(([roomUsers, roomUsersShort]) => { + const userList: UserInfoTypes[] = + roomUsers?.userInfos || roomUsersShort?.userInfos; + this.userInfo = userList.find( + (item) => item.seq + '' === this.data.message.senderSeq + '' + ); + this.changeDetectorRef.markForCheck(); + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onClickEvent(event: MouseEvent) { + this.nativeService.platform_openDefaultBrowser( + (event.target as HTMLAnchorElement).text + ); + } + + onClosed(event: MouseEvent): void { + this.dialogRef.close(); + } +} diff --git a/src/app/sections/chat/dialogs/trans-detail.dialog.component.html b/src/app/sections/chat/dialogs/trans-detail.dialog.component.html new file mode 100644 index 0000000..8dccc5a --- /dev/null +++ b/src/app/sections/chat/dialogs/trans-detail.dialog.component.html @@ -0,0 +1,45 @@ +
    + +
    + {{ 'dialog.title.detail' | ucapI18n }} +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    diff --git a/src/app/sections/chat/dialogs/trans-detail.dialog.component.scss b/src/app/sections/chat/dialogs/trans-detail.dialog.component.scss new file mode 100644 index 0000000..c9e145d --- /dev/null +++ b/src/app/sections/chat/dialogs/trans-detail.dialog.component.scss @@ -0,0 +1,75 @@ +@import '~@ucap/lg-scss/mixins'; +.dialog-container { + width: 100%; + height: 100%; + + .dialog-body { + width: 100%; + height: 100%; + .profile { + display: flex; + flex-direction: row; + padding-bottom: 10px; + margin-bottom: 10px; + border-bottom: 1px solid #ccc; + .profile-image { + border-radius: 50%; + overflow: hidden; + width: 36px; + height: 36px; + margin-left: 0; + background-color: #ffe8cb; + img { + max-width: 100%; + height: auto; + vertical-align: top; + border: none; + } + } + .user-info { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; + padding-left: 16px; + .user-n-g { + display: flex; + flex-flow: row-reverse nowrap; + align-items: flex-end; + height: 22px; + .user-name { + @include ellipsis-column(1); + height: 22px; + font-size: 14px; + font-weight: 600; + order: 1; + -ms-flex-order: 1; + } + .user-grade { + @include ellipsis(1); + align-self: stretch; + font: { + size: 13px; + } + margin-left: 4px; + order: 0; + -ms-flex-order: 0; + } + .write-date { + font-size: 12px; + } + } + } + } + .contents { + width: 100%; + height: calc(100% - 60px); + overflow: hidden; + perfect-scrollbar { + width: 100%; + height: 100%; + padding-right: 10px; + } + } + } +} diff --git a/src/app/sections/chat/dialogs/trans-detail.dialog.component.spec.ts b/src/app/sections/chat/dialogs/trans-detail.dialog.component.spec.ts new file mode 100644 index 0000000..550915f --- /dev/null +++ b/src/app/sections/chat/dialogs/trans-detail.dialog.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { TextDetailDialogComponent } from './text-detail.dialog.component'; + +describe('ucap::ui-organization::CreateChatDialogComponent', () => { + let component: TextDetailDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [TextDetailDialogComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TextDetailDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/sections/chat/dialogs/trans-detail.dialog.component.ts b/src/app/sections/chat/dialogs/trans-detail.dialog.component.ts new file mode 100644 index 0000000..a53cf82 --- /dev/null +++ b/src/app/sections/chat/dialogs/trans-detail.dialog.component.ts @@ -0,0 +1,129 @@ +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Inject, + ElementRef, + AfterViewInit +} from '@angular/core'; + +import { Subject, of, combineLatest } from 'rxjs'; + +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatDialog +} from '@angular/material/dialog'; + +import { NativeService } from '@ucap/native'; +import { VersionInfo2Response } from '@ucap/api-public'; + +import { + UserInfo as RoomUserInfo, + UserInfoShort as RoomUserInfoShort +} from '@ucap/protocol-room'; + +import { LogService } from '@ucap/ng-logger'; +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; +import { Store, select } from '@ngrx/store'; +import { takeUntil } from 'rxjs/operators'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; +import { RoomSelector } from '@ucap/ng-store-chat'; +import { Info, MassTranslationEventJson } from '@ucap/protocol-event'; + +export type UserInfoTypes = RoomUserInfo | RoomUserInfoShort; + +export interface TransDetailDialogData { + contents: string; + message: Info; + roomId: string; + defaultProfileImage: string; +} +export interface TransDetailDialogResult {} + +@Component({ + selector: 'app-dialog-chat-trans-detail', + templateUrl: './trans-detail.dialog.component.html', + styleUrls: ['./trans-detail.dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class TransDetailDialogComponent + implements OnInit, OnDestroy, AfterViewInit { + userInfo: UserInfoTypes; + contents: string; + + defaultProfileImage: string; + versionInfo2Res: VersionInfo2Response; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + public dialogRef: MatDialogRef< + TransDetailDialogData, + TransDetailDialogResult + >, + @Inject(MAT_DIALOG_DATA) public data: TransDetailDialogData, + public dialog: MatDialog, + private store: Store, + private elementRef: ElementRef, + private logService: LogService, + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit(): void { + this.defaultProfileImage = this.data.defaultProfileImage; + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + combineLatest([ + this.store.pipe(select(RoomSelector.roomUser, this.data.roomId)), + this.store.pipe(select(RoomSelector.roomUserShort, this.data.roomId)) + ]) + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe(([roomUsers, roomUsersShort]) => { + const userList: UserInfoTypes[] = + roomUsers?.userInfos || roomUsersShort?.userInfos; + this.userInfo = userList.find( + (item) => item.seq + '' === this.data.message.senderSeq + '' + ); + this.changeDetectorRef.markForCheck(); + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + ngAfterViewInit(): void { + if ( + !!this.elementRef.nativeElement && + !!this.elementRef.nativeElement.querySelector('a') + ) { + const elements = this.elementRef.nativeElement.querySelectorAll('a'); + elements.forEach((element) => { + element.addEventListener('click', this.onClickEvent.bind(this)); + }); + } + } + + onClickEvent(event: MouseEvent) { + this.nativeService.platform_openDefaultBrowser( + (event.target as HTMLAnchorElement).text + ); + } + + onClosed(event: MouseEvent): void { + this.dialogRef.close(); + } +} diff --git a/src/app/sections/chat/drawers/add-group.drawer.component.html b/src/app/sections/chat/drawers/add-group.drawer.component.html new file mode 100644 index 0000000..e8a9c92 --- /dev/null +++ b/src/app/sections/chat/drawers/add-group.drawer.component.html @@ -0,0 +1,45 @@ +
    + +
    + + {{ 'chat:label.addGroup' | ucapI18n }} +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    diff --git a/src/app/sections/chat/drawers/add-group.drawer.component.scss b/src/app/sections/chat/drawers/add-group.drawer.component.scss new file mode 100644 index 0000000..dd6c36f --- /dev/null +++ b/src/app/sections/chat/drawers/add-group.drawer.component.scss @@ -0,0 +1,31 @@ +@import '~@ucap/lg-scss/mixins'; + +.drawer-container { + width: 100%; + height: 100%; + .drawer-body { + height: 100%; + .ucap-chat-room-add-group-body { + padding: 0 16px; + min-width: 336px; + height: 100%; + @include screen(xs) { + min-width: 100%; + } + } + } +} +.ucap-virtual-scroll-viewport { + height: auto !important; +} +.btn-bottom-area { + display: flex; + flex-direction: row; + background-color: $white; + padding: 14px 16px 10px; + justify-content: flex-end; + width: 100%; + button { + @include ucap-button-flat-stroked(50%); + } +} diff --git a/src/app/sections/chat/drawers/add-group.drawer.component.spec.ts b/src/app/sections/chat/drawers/add-group.drawer.component.spec.ts new file mode 100644 index 0000000..1fa4f7f --- /dev/null +++ b/src/app/sections/chat/drawers/add-group.drawer.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { AddGroupDrawerComponent } from './add-group.drawer.component'; + +describe('ucap::ui-organization::CreateChatDialogComponent', () => { + let component: AddGroupDrawerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [AddGroupDrawerComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AddGroupDrawerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/sections/chat/drawers/add-group.drawer.component.ts b/src/app/sections/chat/drawers/add-group.drawer.component.ts new file mode 100644 index 0000000..fcd7d39 --- /dev/null +++ b/src/app/sections/chat/drawers/add-group.drawer.component.ts @@ -0,0 +1,268 @@ +import { Subject, combineLatest, merge } from 'rxjs'; +import { takeUntil, take } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + EventEmitter, + Output, + Input +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { MatDialog } from '@angular/material/dialog'; + +import { GroupDetailData } from '@ucap/protocol-sync'; +import { + UserInfo as RoomUserInfo, + UserInfoShort as RoomUserInfoShort +} from '@ucap/protocol-room'; +import { User } from '@ucap/protocol-info'; + +import { LogService } from '@ucap/ng-logger'; +import { I18nService } from '@ucap/ng-i18n'; + +import { UserSelector } from '@ucap/ng-store-organization'; +import { RoomSelector } from '@ucap/ng-store-chat'; + +import { + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult, + AlertDialogComponent, + AlertDialogData, + AlertDialogResult +} from '@ucap/ng-ui'; + +import { ChatDrawType } from '@app/pages/chat/types/chat-draw.type'; +import { DrawInfo } from '@app/pages/chat/models/draw-info'; +import { AppGroupService } from '@app/services/app-group.service'; + +export type UserInfoTypes = RoomUserInfo | RoomUserInfoShort; + +@Component({ + selector: 'app-drawer-chat-add-group', + templateUrl: './add-group.drawer.component.html', + styleUrls: ['./add-group.drawer.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AddGroupDrawerComponent implements OnInit, OnDestroy { + private roomIdSubject = new Subject(); + private ngOnDestroySubject = new Subject(); + + @Input() + set roomId(value: string) { + this._roomId = value; + this.roomIdSubject.next(value); + this._initializeData(); + } + get roomId(): string { + return this._roomId; + } + // tslint:disable-next-line: variable-name + _roomId: string; + + @Input() + returnChatDrawerType?: ChatDrawType; + + @Output() + rightDrawerToggle = new EventEmitter(); + + @Output() + closed = new EventEmitter(); + + user: User; + roomUsers: RoomUserInfo[] = []; + roomUsersShort: RoomUserInfoShort[] = []; + isAddBtnInvalid = true; + + groupName = ''; + selectedGroupList: GroupDetailData[] = []; + + constructor( + private i18nService: I18nService, + public dialog: MatDialog, + private store: Store, + private logService: LogService, + private changeDetectorRef: ChangeDetectorRef, + private appGroupService: AppGroupService + ) {} + + ngOnInit(): void { + this._initializeData(); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + + if (!!this.roomIdSubject) { + this.roomIdSubject.next(); + this.roomIdSubject.complete(); + } + } + + private _initializeData() { + combineLatest([ + this.store.pipe(select(UserSelector.user)), + this.store.pipe(select(RoomSelector.roomUser, this.roomId)), + this.store.pipe(select(RoomSelector.roomUserShort, this.roomId)) + ]) + .pipe(takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject))) + .subscribe(([user, roomUser, roomUserShort]) => { + this.user = user; + if ( + !!roomUser && + !!roomUser.userInfos && + roomUser.userInfos.length > 0 + ) { + this.roomUsers = roomUser.userInfos.filter( + (item) => + !!item.isJoinRoom && String(item.seq) !== String(user.info.seq) + ); + } + if ( + !!roomUserShort && + !!roomUserShort.userInfos && + roomUserShort.userInfos.length > 0 + ) { + this.roomUsersShort = roomUserShort.userInfos.filter( + (item) => + !!item.isJoinRoom && String(item.seq) !== String(user.info.seq) + ); + } + }); + } + + onChangeSelectedGroupList(data: { + checked: boolean; + group: GroupDetailData; + }) { + const i = this.selectedGroupList.findIndex((u) => u.seq === data.group.seq); + + if (data.checked) { + if (-1 === i) { + this.selectedGroupList = [...this.selectedGroupList, data.group]; + } + } else { + if (-1 < i) { + this.selectedGroupList = this.selectedGroupList.filter( + (u) => u.seq !== data.group.seq + ); + } + } + } + + onChangeGroupName(data: { invalid: boolean; groupName: string }) { + this.isAddBtnInvalid = data.invalid; + this.groupName = data.groupName; + } + + onClosed(event: MouseEvent): void { + this.closed.emit(); + } + + onClickCancel(): void { + if (!!this.returnChatDrawerType) { + this.rightDrawerToggle.emit({ + chatDrawType: this.returnChatDrawerType + }); + } else { + this.closed.emit(); + } + } + + onClickAddGroupMember(): void { + const targetUserInfos: UserInfoTypes[] = []; + + if (!!this.roomUsers && this.roomUsers.length > 0) { + this.roomUsers.map((item) => targetUserInfos.push(item)); + } else if (!!this.roomUsersShort && this.roomUsersShort.length > 0) { + this.roomUsersShort.map((item) => targetUserInfos.push(item)); + } else { + this.logService.error('chat', 'not exist selected users.'); + return; + } + + // validations + if ( + (!this.groupName && !this.selectedGroupList) || + (this.groupName.trim().length === 0 && + this.selectedGroupList.length === 0) + ) { + const result = this.dialog.open< + AlertDialogComponent, + AlertDialogData, + AlertDialogResult + >(AlertDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:label.addGroup'), + message: this.i18nService.t('chat:errors.addBuddyForGroup') + } + }); + return; + } + + // Do Action. + if (!!this.groupName && this.groupName.trim().length > 0) { + // create group and update room. + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:label.addGroup'), + html: this.i18nService.t('chat:dialog.confirmAddBuddyForNewGroup', { + targetGroups: this.groupName + }) + } + }); + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && result.choice) { + this.appGroupService.createGroup(this.groupName, targetUserInfos); + this.closed.emit(); + } + }); + } else if (!!this.selectedGroupList && this.selectedGroupList.length > 0) { + // existed group update room. + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:label.addGroup'), + html: this.i18nService.t('chat:dialog.confirmAddBuddyForGroup', { + targetGroups: this.selectedGroupList + .map((group) => group.name) + .join(',') + }) + } + }); + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && result.choice) { + this.selectedGroupList.forEach((group) => { + this.appGroupService.addMemberToGroup(group, targetUserInfos); + this.closed.emit(); + }); + } + }); + } + } +} diff --git a/src/app/sections/chat/drawers/add-users.drawer.component.html b/src/app/sections/chat/drawers/add-users.drawer.component.html new file mode 100644 index 0000000..411fb32 --- /dev/null +++ b/src/app/sections/chat/drawers/add-users.drawer.component.html @@ -0,0 +1,56 @@ +
    + +
    + + {{ 'chat:label.addRoomUsers' | ucapI18n }} +
    +
    +
    + + + +

    + {{ 'dialog.selectedUserList' | ucapI18n }} +

    + ({{ selectedUserList?.length }}/{{ maxChatRoomUser - 1 }}) +
    +
    +
    +
    +
    + + +
    +
    +
    diff --git a/src/app/sections/chat/drawers/add-users.drawer.component.scss b/src/app/sections/chat/drawers/add-users.drawer.component.scss new file mode 100644 index 0000000..5efc869 --- /dev/null +++ b/src/app/sections/chat/drawers/add-users.drawer.component.scss @@ -0,0 +1,24 @@ +@import '~@ucap/lg-scss/mixins'; + +.drawer-container { + width: 100%; + height: 100%; + overflow: hidden; + .drawer-body { + width: 100%; + height: 100%; + padding-top: 8px; + } + .btn-bottom-area { + display: flex; + flex-direction: row; + background-color: $white; + padding: 14px 16px 10px; + justify-content: flex-end; + border-top: 1px solid #d4d4d4; + width: 100%; + button { + @include ucap-button-flat-stroked(50%); + } + } +} diff --git a/src/app/sections/chat/drawers/add-users.drawer.component.spec.ts b/src/app/sections/chat/drawers/add-users.drawer.component.spec.ts new file mode 100644 index 0000000..5b1e780 --- /dev/null +++ b/src/app/sections/chat/drawers/add-users.drawer.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { AddUsersDrawerComponent } from './add-users.drawer.component'; + +describe('ucap::ui-organization::CreateChatDialogComponent', () => { + let component: AddUsersDrawerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [AddUsersDrawerComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AddUsersDrawerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/sections/chat/drawers/add-users.drawer.component.ts b/src/app/sections/chat/drawers/add-users.drawer.component.ts new file mode 100644 index 0000000..8b0e5f6 --- /dev/null +++ b/src/app/sections/chat/drawers/add-users.drawer.component.ts @@ -0,0 +1,312 @@ +import { Subject, combineLatest, merge } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + EventEmitter, + Output, + Input +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { MatDialog } from '@angular/material/dialog'; + +import { LocaleCode } from '@ucap/core'; +import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; +import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; +import { + UserInfo as RoomUserInfo, + UserInfoShort as RoomUserInfoShort, + RoomInfo, + RoomType, + InviteRequest +} from '@ucap/protocol-room'; + +import { I18nService } from '@ucap/ng-i18n'; + +import { UserSelector } from '@ucap/ng-store-organization'; +import { RoomSelector, RoomActions } from '@ucap/ng-store-chat'; + +import { + AlertDialogComponent, + AlertDialogData, + AlertDialogResult +} from '@ucap/ng-ui'; + +import { environment } from '@environments'; +import { AppChatService } from '@app/services/app-chat.service'; +import { ChatDrawType } from '@app/pages/chat/types/chat-draw.type'; +import { DrawInfo } from '@app/pages/chat/models/draw-info'; +import { User } from '@ucap/protocol-info'; + +export type UserInfoTypes = + | UserInfo + | UserInfoSS + | UserInfoF + | UserInfoDN + | RoomUserInfo + | RoomUserInfoShort; + +@Component({ + selector: 'app-drawer-chat-add-users', + templateUrl: './add-users.drawer.component.html', + styleUrls: ['./add-users.drawer.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AddUsersDrawerComponent implements OnInit, OnDestroy { + private roomIdSubject = new Subject(); + private ngOnDestroySubject = new Subject(); + + @Input() + set roomId(value: string) { + this._roomId = value; + this.roomIdSubject.next(value); + this._initializeData(); + } + get roomId(): string { + return this._roomId; + } + // tslint:disable-next-line: variable-name + _roomId: string; + + @Input() + returnChatDrawerType?: ChatDrawType; + + @Output() + rightDrawerToggle = new EventEmitter(); + + @Output() + closed = new EventEmitter(); + + maxChatRoomUser: number; + + isTimer: boolean | undefined; + selectedUserList: UserInfoTypes[] = []; + + roomInfo: RoomInfo; + fixedUserList: (RoomUserInfo | RoomUserInfoShort)[] = []; + + user: User; + + constructor( + private i18nService: I18nService, + public dialog: MatDialog, + private store: Store, + private appChatService: AppChatService, + private changeDetectorRef: ChangeDetectorRef + ) { + this.maxChatRoomUser = environment.productConfig.chat.maxChatRoomUser; + } + + ngOnInit(): void { + this._initializeData(); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + + if (!!this.roomIdSubject) { + this.roomIdSubject.next(); + this.roomIdSubject.complete(); + } + } + + private _initializeData() { + combineLatest([ + this.store.pipe(select(UserSelector.user)), + this.store.pipe(select(RoomSelector.room, this.roomId)), + this.store.pipe(select(RoomSelector.roomUser, this.roomId)), + this.store.pipe(select(RoomSelector.roomUserShort, this.roomId)) + ]) + .pipe(takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject))) + .subscribe(([user, roomInfo, roomUser, roomUserShort]) => { + this.user = user; + this.roomInfo = roomInfo; + + this.fixedUserList = roomUserShort?.userInfos || roomUser?.userInfos; + if (!!this.fixedUserList) { + this.fixedUserList = this.fixedUserList.filter( + (info) => + !!info.isJoinRoom && String(info.seq) !== String(user.info.seq) + ); + } + this.selectedUserList = this.fixedUserList.slice(); + + this.changeDetectorRef.markForCheck(); + }); + } + + onClosed(event: MouseEvent): void { + this.closed.emit(); + } + onClickCancel(): void { + if (!!this.returnChatDrawerType) { + this.rightDrawerToggle.emit({ + chatDrawType: this.returnChatDrawerType + }); + } else { + this.closed.emit(); + } + } + onConfirm() { + if (this.selectedUserList.length >= this.maxChatRoomUser) { + this.dialog.open< + AlertDialogComponent, + AlertDialogData, + AlertDialogResult + >(AlertDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:errors.label'), + html: this.i18nService.t('chat:errors.maxCountOfRoomMemberWith', { + maxCount: this.maxChatRoomUser + }) + } + }); + return; + } + + // No Change.. + if ( + !!this.selectedUserList && + this.selectedUserList.length > 0 && + this.fixedUserList + .map((user) => user.seq) + .sort() + .join('|') === + this.selectedUserList + .map((user) => user.seq) + .sort() + .join('|') + ) { + this.closed.emit(); + } + + // Invite or Open room. + let userSeqs = this.selectedUserList.map((userInfo) => userInfo.seq + ''); + + if (this.roomInfo.roomType === RoomType.Single) { + this.appChatService.newOpenRoom(userSeqs, false); + } else if (this.roomInfo.roomType === RoomType.Multi) { + // only target in invite users. + userSeqs = userSeqs.filter((seq) => { + return !this.fixedUserList.some((info) => info.seq + '' === seq + ''); + }); + + this.store.dispatch( + RoomActions.invite({ + req: { + roomId: this.roomId, + inviteUserSeqs: userSeqs + } as InviteRequest, + localeCode: LocaleCode.Korean + }) + ); + } + + this.closed.emit(); + } + + onChangeUserList(datas: { checked: boolean; userInfo: UserInfoSS }[]) { + if (!datas || 0 === datas.length) { + return; + } + + const pushs: UserInfoSS[] = []; + const pops: UserInfoSS[] = []; + + datas.forEach((d) => { + const i = this.selectedUserList.findIndex( + (u) => String(u.seq) === String(d.userInfo.seq) + ); + if (d.checked) { + if (-1 === i) { + pushs.push(d.userInfo); + } + } else { + if (-1 < i) { + pops.push(d.userInfo); + } + } + }); + + if (0 < pushs.length) { + this.selectedUserList = [...this.selectedUserList, ...pushs]; + } + + if (0 < pops.length) { + this.selectedUserList = this.selectedUserList.filter((u) => { + let result = false; + if (-1 === pops.findIndex((p) => String(p.seq) === String(u.seq))) { + result = true; + } + if ( + !!this.fixedUserList && + this.fixedUserList.length > 0 && + this.fixedUserList.findIndex((p) => String(p.seq) === String(u.seq)) > + -1 + ) { + result = true; + } + + return result; + }); + } + } + onChangeGroupList(params: { + isChecked: boolean; + groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; + }) { + if (params.isChecked) { + params.groupBuddyList.buddyList.forEach((item) => { + if ( + this.selectedUserList.filter( + (user) => String(user.seq) === String(item.seq) + ).length === 0 + ) { + this.selectedUserList = [...this.selectedUserList, item]; + } + }); + } else { + this.selectedUserList = this.selectedUserList.filter( + (item) => + params.groupBuddyList.buddyList.filter( + (del) => String(del.seq) === String(item.seq) + ).length === 0 + ); + } + } + onRemovedProfileSelection(userInfo: UserInfo) { + const i = this.selectedUserList.findIndex( + (u) => String(u.seq) === String(userInfo.seq) + ); + + if (-1 < i) { + this.selectedUserList = this.selectedUserList.filter( + (u) => String(u.seq) !== String(userInfo.seq) + ); + } + } + + removableForSelection = (userInfo: UserInfo) => { + return !this.fixedUserList.some( + (info) => String(info.seq) === String(userInfo.seq) + ); + }; + + colorForSelection = (userInfo: UserInfo) => { + return this.fixedUserList.some( + (info) => String(info.seq) === String(userInfo.seq) + ) + ? 'primary' + : 'accent'; + }; +} diff --git a/src/app/sections/chat/drawers/attach-data.drawer.component.html b/src/app/sections/chat/drawers/attach-data.drawer.component.html new file mode 100644 index 0000000..384067e --- /dev/null +++ b/src/app/sections/chat/drawers/attach-data.drawer.component.html @@ -0,0 +1,208 @@ +
    + +
    + {{ 'label.data' | ucapI18n }} +
    + + + +
    +
    + +
    + +
    +
    +
    +
    +
    + + {{ + (drawerType === ChatDrawType.AttachImage + ? 'label.image' + : drawerType === ChatDrawType.AttachVideo + ? 'label.video' + : 'label.file' + ) | ucapI18n + }} + + {{ + filteredFileInfoList?.length + }} +
    +
    + + + +
    +
    + +
    +
    +
    + +
    + +
    + +
    +
    +
    + +
    + + + + +
    +
    +
    diff --git a/src/app/sections/chat/drawers/attach-data.drawer.component.scss b/src/app/sections/chat/drawers/attach-data.drawer.component.scss new file mode 100644 index 0000000..be53605 --- /dev/null +++ b/src/app/sections/chat/drawers/attach-data.drawer.component.scss @@ -0,0 +1,146 @@ +@import '~@ucap/lg-scss/mixins'; + +.drawer-container { + width: 100%; + height: 100%; + .tit-head-area { + display: flex; + flex-direction: row; + align-items: center; + .title-text { + font-size: 1.143em; + color: #333; + @include screen(xs) { + font-size: 0.929em; + } + } + .btns { + display: inline-flex; + flex-direction: row; + button { + font-size: 0.929em; + font-weight: 600; + color: #999; + position: relative; + &.cdk-focused, + &.cdk-active { + color: $lipstick; + } + &:before { + content: ''; + width: 1px; + background-color: #ccc; + height: 12px; + position: absolute; + left: 0; + top: 12px; + } + &:first-of-type { + &:before { + display: none; + } + } + @include screen(xs) { + min-width: auto; + padding: 0 10px; + font-size: 0.857em; + &:first-of-type { + margin-left: 5px; + } + } + } + } + } + .drawer-body { + width: 100%; + height: 100%; + padding: 0 16px; + display: flex; + flex-direction: column; + .sub-header { + flex: 0 0 80px; + display: flex; + flex-direction: column; + .title { + flex: 0 0 40px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + .tit-text { + flex: 0 0 auto; + span { + color: #333; + font-size: 1em; + font-weight: 600; + } + .amount-text { + color: $lipstick; + font-weight: 600; + padding-left: 5px; + } + } + .btn-chk-area { + display: inline-flex; + flex-direction: row; + align-items: center; + .btn-re { + flex: 0 0 40px; + min-width: 40px; + height: 40px; + border-radius: 20px; + padding: 0; + } + .mat-checkbox { + flex: 1 0 20px; + margin: 0 4px 0 12px; + } + } + } + .sub-navi-btns { + flex: 0 0 40px; + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + background-color: #f1f2f6; + border-top: 1px solid #ccc; + padding: 0 10px 2px; + .btn-sub-navi { + height: 26px; + line-height: 26px; + border-radius: 13px; + background-color: $white; + color: #707070; + &.cdk-focused, + &.cdk-active { + background-color: $lipstick; + color: $white; + border-color: $lipstick; + } + } + button { + & + button { + margin-left: 6px; + } + } + } + } + .dataroom-contents { + flex: 1 0 auto; + overflow-x: hidden; + overflow-y: auto; + } + } + .btn-bottom-area { + display: flex; + flex-direction: row; + background-color: $white; + padding: 14px 16px 10px; + justify-content: flex-end; + width: 100%; + button { + @include ucap-button-flat-stroked(100%); + } + } +} diff --git a/src/app/sections/chat/drawers/attach-data.drawer.component.spec.ts b/src/app/sections/chat/drawers/attach-data.drawer.component.spec.ts new file mode 100644 index 0000000..8e9d29b --- /dev/null +++ b/src/app/sections/chat/drawers/attach-data.drawer.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { AttachDataDrawerComponent } from './attach-data.drawer.component'; + +describe('ucap::ui-organization::CreateChatDialogComponent', () => { + let component: AttachDataDrawerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [AttachDataDrawerComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AttachDataDrawerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/sections/chat/drawers/attach-data.drawer.component.ts b/src/app/sections/chat/drawers/attach-data.drawer.component.ts new file mode 100644 index 0000000..795c3b6 --- /dev/null +++ b/src/app/sections/chat/drawers/attach-data.drawer.component.ts @@ -0,0 +1,485 @@ +import { Subject, combineLatest, merge } from 'rxjs'; +import { takeUntil, take } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + EventEmitter, + Output, + Inject, + NgZone +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { MatDialog } from '@angular/material/dialog'; + +import { LoginSession, DeviceType } from '@ucap/core'; +import { NativeService } from '@ucap/native'; +import { FileDownloadItem } from '@ucap/api'; +import { VersionInfo2Response } from '@ucap/api-public'; +import { LoginResponse } from '@ucap/protocol-authentication'; +import { RoomInfo } from '@ucap/protocol-room'; +import { FileInfo, InfoRequest } from '@ucap/protocol-file'; +import { FileType, FileEventJson } from '@ucap/protocol-event'; +import { User } from '@ucap/protocol-info'; + +import { I18nService } from '@ucap/ng-i18n'; +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; + +import { UserSelector } from '@ucap/ng-store-organization'; +import { + LoginSelector, + ConfigurationSelector +} from '@ucap/ng-store-authentication'; +import { + RoomSelector, + ChattingSelector, + ChattingActions +} from '@ucap/ng-store-chat'; + +import { + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult +} from '@ucap/ng-ui'; + +import { ChatDrawType } from '@app/pages/chat/types/chat-draw.type'; +import { AppAuthenticationService } from '@app/services/app-authentication.service'; +import { AppFileService } from '@app/services/app-file.service'; +import { AppChatService } from '@app/services/app-chat.service'; + +import { FileViewerDialogData } from '../dialogs/file-viewer.dialog.component'; + +export type SendRecvType = 'ALL' | 'SEND' | 'RECV'; + +@Component({ + selector: 'app-drawer-chat-attach-data', + templateUrl: './attach-data.drawer.component.html', + styleUrls: ['./attach-data.drawer.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AttachDataDrawerComponent implements OnInit, OnDestroy { + private roomIdSubject = new Subject(); + private ngOnDestroySubject = new Subject(); + + @Input() + set roomId(value: string) { + this._roomId = value; + this.roomIdSubject.next(value); + this._initializeData(); + } + get roomId(): string { + return this._roomId; + } + // tslint:disable-next-line: variable-name + _roomId: string; + + @Input() + set drawerType(type: ChatDrawType) { + this._drawerType = type; + this.filterSendRecvType = 'ALL'; + + this.getFiltered(); + } + get drawerType(): ChatDrawType { + return this._drawerType; + } + // tslint:disable-next-line: variable-name + _drawerType: ChatDrawType; + + @Output() + closed = new EventEmitter(); + + originalFileInfoList: { + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }[] = []; + filteredFileInfoList: { + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }[] = []; + selectedFileInfoList: { + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }[] = []; + + filterSendRecvType: SendRecvType = 'ALL'; + + versionInfo2Res: VersionInfo2Response; + loginRes: LoginResponse; + user: User; + loginSession: LoginSession; + + roomInfo: RoomInfo; + showDownCheckSeq: number | undefined; + + ChatDrawType = ChatDrawType; + DeviceType = DeviceType; + + constructor( + private store: Store, + private dialog: MatDialog, + private i18nService: I18nService, + private appChatService: AppChatService, + private appFileService: AppFileService, + private appAuthenticationService: AppAuthenticationService, + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, + private ngZone: NgZone, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit(): void { + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => (this.versionInfo2Res = versionInfo2Res)); + + this.loginSession = this.appAuthenticationService.getLoginSession(); + + this._initializeData(); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + + if (!!this.roomIdSubject) { + this.roomIdSubject.next(); + this.roomIdSubject.complete(); + } + } + + private _initializeData() { + this.originalFileInfoList = []; + this.filteredFileInfoList = []; + this.selectedFileInfoList = []; + this.showDownCheckSeq = undefined; + + combineLatest([ + this.store.pipe(select(LoginSelector.loginRes)), + this.store.pipe(select(UserSelector.user)), + this.store.pipe(select(RoomSelector.room, this.roomId)), + this.store.pipe(select(ChattingSelector.fileInfoList, this.roomId)) + ]) + .pipe(takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject))) + .subscribe(([loginRes, user, roomInfo, fileInfoList]) => { + this.loginRes = loginRes; + this.user = user; + this.roomInfo = roomInfo; + + if (!!fileInfoList && fileInfoList.length > 0) { + this.originalFileInfoList = fileInfoList + .map((item) => { + return { + fileInfo: item, + fileDownloadItem: new FileDownloadItem() + }; + }) + .sort((a, b) => + a.fileInfo.eventSeq < b.fileInfo.eventSeq + ? 1 + : a.fileInfo.eventSeq > b.fileInfo.eventSeq + ? -1 + : 0 + ); + this.getFiltered(); + } + }); + } + + onChangeDrawerType(type: ChatDrawType) { + this.selectedFileInfoList = []; + this.showDownCheckSeq = undefined; + this.drawerType = type; + } + + onChangeFilterSendRecv(type: SendRecvType) { + this.selectedFileInfoList = []; + this.filterSendRecvType = type; + this.getFiltered(); + } + + getFiltered() { + if ( + !!this.drawerType && + !!this.originalFileInfoList && + this.originalFileInfoList.length > 0 + ) { + this.filteredFileInfoList = this.originalFileInfoList + .filter((item) => { + if (this.drawerType === ChatDrawType.AttachImage) { + return item.fileInfo.type === FileType.Image; + } else if (this.drawerType === ChatDrawType.AttachVideo) { + return item.fileInfo.type === FileType.Video; + } else { + return ( + item.fileInfo.type !== FileType.Image && + item.fileInfo.type !== FileType.Video + ); + } + }) + .filter((item) => { + if (this.filterSendRecvType === 'RECV') { + return ( + String(item.fileInfo.senderSeq) !== String(this.user.info.seq) + ); + } else if (this.filterSendRecvType === 'SEND') { + return ( + String(item.fileInfo.senderSeq) === String(this.user.info.seq) + ); + } else { + return true; + } + }); + } + + this.changeDetectorRef.detectChanges(); + } + + refreshList(): void { + let searchType: FileType = FileType.All; + switch (this.drawerType) { + case ChatDrawType.AttachImage: + searchType = FileType.Image; + break; + case ChatDrawType.AttachVideo: + searchType = FileType.Video; + break; + case ChatDrawType.AttachFile: + searchType = FileType.File; + break; + } + this.store.dispatch( + ChattingActions.fileInfos({ + req: { roomId: this.roomId, type: searchType } as InfoRequest + }) + ); + } + + onClosed(event: MouseEvent): void { + this.closed.emit(); + } + + getCheckedAllItem(): boolean { + const targetList = this.filteredFileInfoList; + + if ( + !targetList || + targetList.length === 0 || + targetList.filter( + (item) => + !( + this.selectedFileInfoList.filter( + (info) => info.fileInfo.seq === item.fileInfo.seq + ).length > 0 + ) + ).length > 0 + ) { + return false; + } else { + return true; + } + } + onToggleAllItem(value: boolean): void { + if (!!value) { + const targetList = this.filteredFileInfoList; + + this.selectedFileInfoList = this.filteredFileInfoList; + } else { + this.selectedFileInfoList = []; + } + this.changeDetectorRef.markForCheck(); + } + onToggleItem(params: { + checked: boolean; + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }) { + if (!!params.checked) { + if ( + !this.selectedFileInfoList.some( + (info) => info.fileInfo.seq === params.fileInfo.seq + ) + ) { + this.selectedFileInfoList.push({ + fileInfo: params.fileInfo, + fileDownloadItem: params.fileDownloadItem + }); + } + } else { + if (!!this.selectedFileInfoList && this.selectedFileInfoList.length > 0) { + const index = this.selectedFileInfoList.findIndex( + (info) => info.fileInfo.seq === params.fileInfo.seq + ); + if (index > -1) { + this.selectedFileInfoList.splice(index, 1); + } + } + } + } + + onShowDownCheck(fileInfo: FileInfo): void { + this.showDownCheckSeq = fileInfo.seq; + } + + onClickItemDelete(fileInfo: FileInfo): void { + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:dialog.title.removeChat'), + html: this.i18nService.t('chat:dialog.confirmRemoveChat') + } + }); + + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && !!result.choice) { + this.store.dispatch( + ChattingActions.del({ + roomId: fileInfo.roomId, + eventSeq: fileInfo.eventSeq + }) + ); + } + }); + } + + onClickItemOpenViewer(fileInfo: FileInfo): void { + const data: FileViewerDialogData = { + fileInfos: this.filteredFileInfoList.map((item) => item.fileInfo), + selectFileInfo: { attachmentSeq: fileInfo.seq }, + downloadUrl: this.versionInfo2Res.downloadUrl, + deviceType: this.loginSession.deviceType, + token: this.loginRes.tokenString, + userSeq: String(this.user.info.seq) + }; + this.appChatService.openFileviwer(data); + } + onClickItemSave(params: { + fileInfo: FileEventJson; + fileDownloadItem: FileDownloadItem; + type: string; + }): void { + if ( + params.type === 'saveAs' && + this.loginSession.deviceType === DeviceType.PC + ) { + this.nativeService + .file_selectForSave({ defaultPath: params.fileInfo.fileName }) + .then((result) => { + if (!!result) { + if (!!result.canceled) { + // 취소함. + } else { + this.appFileService.saveFile( + { + fileInfo: params.fileInfo, + fileDownloadItem: params.fileDownloadItem, + type: params.type, + fileName: params.fileInfo.fileName, + fileDownloadUrl: undefined, + savePath: result.filePath + }, + this.loginRes, + this.user, + this.loginSession + ); + } + } + }) + .catch((reason) => { + // this.snackBarService.open( + // this.translateService.instant( + // 'common:file.errors.failToSpecifyPath' + // ), + // this.translateService.instant('common:file.errors.label') + // ); + }); + } else { + this.appFileService.saveFile( + { + fileInfo: params.fileInfo, + fileDownloadItem: params.fileDownloadItem, + type: params.type, + fileName: params.fileInfo.fileName, + fileDownloadUrl: undefined, + savePath: undefined + }, + this.loginRes, + this.user, + this.loginSession + ); + } + } + + onClickDelete(): void { + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:dialog.title.removeChat'), + html: this.i18nService.t('chat:dialog.confirmRemoveChat') + } + }); + + const self = this; + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && !!result.choice) { + self.selectedFileInfoList.forEach((item, i) => { + setTimeout(() => { + this.store.dispatch( + ChattingActions.del({ + roomId: item.fileInfo.roomId, + eventSeq: item.fileInfo.eventSeq + }) + ); + }, i * 300); + }); + } + }); + } + onClickChangeFolder(): void { + alert('changeFolder'); + } + onClickOpenViewer(): void { + if (!!this.filteredFileInfoList && this.filteredFileInfoList.length > 0) { + this.onClickItemOpenViewer(this.filteredFileInfoList[0].fileInfo); + } + } + onClickDownload(): void { + this.ngZone.run(() => { + this.selectedFileInfoList.forEach((item) => { + this.onClickItemSave({ + fileInfo: { + // require parameters. + fileName: item.fileInfo.name, + attachmentSeq: item.fileInfo.seq + } as FileEventJson, + fileDownloadItem: item.fileDownloadItem, + type: 'save' + }); + }); + }); + } +} diff --git a/src/app/sections/chat/drawers/index.ts b/src/app/sections/chat/drawers/index.ts new file mode 100644 index 0000000..893cd45 --- /dev/null +++ b/src/app/sections/chat/drawers/index.ts @@ -0,0 +1,13 @@ +import { AddGroupDrawerComponent } from './add-group.drawer.component'; +import { AddUsersDrawerComponent } from './add-users.drawer.component'; +import { AttachDataDrawerComponent } from './attach-data.drawer.component'; +import { SettingDrawerComponent } from './setting.drawer.component'; +import { UsersDrawerComponent } from './users.drawer.component'; + +export const DRAWERS = [ + AddGroupDrawerComponent, + AddUsersDrawerComponent, + UsersDrawerComponent, + SettingDrawerComponent, + AttachDataDrawerComponent +]; diff --git a/src/app/sections/chat/drawers/setting.drawer.component.html b/src/app/sections/chat/drawers/setting.drawer.component.html new file mode 100644 index 0000000..b689bfc --- /dev/null +++ b/src/app/sections/chat/drawers/setting.drawer.component.html @@ -0,0 +1,92 @@ +
    + +
    + {{ 'chat:label.roomSetting' | ucapI18n }} +
    +
    +
    +
    +
    + {{ + 'chat:dialog.roomName' | ucapI18n + }} + + + {{ input.value?.length || 0 }}/20 + +
    +
    + {{ + 'chat:dialog.roomNameChangeTarget' | ucapI18n + }} + + {{ + 'chat:dialog.me' | ucapI18n + }} + {{ + 'chat:dialog.all' | ucapI18n + }} + + +
    +
    + {{ + 'chat:dialog.settingTimer' | ucapI18n + }} + + + + {{ timer.text }} + + + {{ + 'chat:dialog:settingTimerHint' | ucapI18n + }} + +
    +
    +
    +
    +
    + + +
    +
    +
    diff --git a/src/app/sections/chat/drawers/setting.drawer.component.scss b/src/app/sections/chat/drawers/setting.drawer.component.scss new file mode 100644 index 0000000..6255a4b --- /dev/null +++ b/src/app/sections/chat/drawers/setting.drawer.component.scss @@ -0,0 +1,70 @@ +@import '~@ucap/lg-scss/mixins'; + +.drawer-container { + width: 100%; + height: 100%; + &.ucap-chat-room-setting-drawer { + .drawer-body { + width: 100%; + height: 100%; + padding: 0 16px; + .ucap-chat-room-setting-body { + display: flex; + flex-direction: column; + min-width: 327px; + @include screen(xs) { + min-width: 100%; + } + .setting-content-box { + border-top: 10px solid #f7f8fa; + padding: 20px 10px; + &:first-of-type { + border-top: none; + } + .tit-setting-content { + color: #584f52; + font-size: 1em; + font-weight: 600; + margin-bottom: 10px; + display: block; + } + } + .roomName { + } + .roomName-target { + display: flex; + flex-direction: column; + .mat-radio-group { + .mat-radio-button { + & + .mat-radio-button { + margin-left: 28px; + } + } + } + .radio-hint-copy { + color: #f69532; + font-size: 0.786em; + margin-top: 13px; + } + } + .timer { + .form-select-timer { + width: 100%; + } + } + } + } + } + + .btn-bottom-area { + display: flex; + flex-direction: row; + background-color: $white; + padding: 14px 16px 10px; + justify-content: flex-end; + width: 100%; + button { + @include ucap-button-flat-stroked(50%); + } + } +} diff --git a/src/app/sections/chat/drawers/setting.drawer.component.spec.ts b/src/app/sections/chat/drawers/setting.drawer.component.spec.ts new file mode 100644 index 0000000..e596b9e --- /dev/null +++ b/src/app/sections/chat/drawers/setting.drawer.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { SettingDrawerComponent } from './setting.drawer.component'; + +describe('ucap::ui-organization::CreateChatDialogComponent', () => { + let component: SettingDrawerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SettingDrawerComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SettingDrawerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/sections/chat/drawers/setting.drawer.component.ts b/src/app/sections/chat/drawers/setting.drawer.component.ts new file mode 100644 index 0000000..4fdb3be --- /dev/null +++ b/src/app/sections/chat/drawers/setting.drawer.component.ts @@ -0,0 +1,178 @@ +import { Subject, combineLatest, merge } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + EventEmitter, + Output +} from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +import { Store, select } from '@ngrx/store'; + +import { I18nService } from '@ucap/ng-i18n'; +import { + RoomInfo, + UpdateRequest, + UpdateTimerSetRequest +} from '@ucap/protocol-room'; + +import { RoomSelector, RoomActions } from '@ucap/ng-store-chat'; + +@Component({ + selector: 'app-drawer-chat-setting', + templateUrl: './setting.drawer.component.html', + styleUrls: ['./setting.drawer.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class SettingDrawerComponent implements OnInit, OnDestroy { + private roomIdSubject = new Subject(); + private ngOnDestroySubject = new Subject(); + + @Input() + set roomId(value: string) { + this._roomId = value; + this.roomIdSubject.next(value); + this._initializeData(); + } + get roomId(): string { + return this._roomId; + } + // tslint:disable-next-line: variable-name + _roomId: string; + + @Output() + closed = new EventEmitter(); + + roomInfo: RoomInfo; + + roomName: string; + timerArray: { value: number; text: string }[]; + + chatSettingForm: FormGroup; + + constructor( + private i18nService: I18nService, + private store: Store, + private formBuilder: FormBuilder, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit(): void { + this._initializeData(); + + this.i18nService.languageChanged$ + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe((_) => { + this.setTimerArray(); + }); + this.setTimerArray(); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + + if (!!this.roomIdSubject) { + this.roomIdSubject.next(); + this.roomIdSubject.complete(); + } + } + + private _initializeData() { + combineLatest([this.store.pipe(select(RoomSelector.room, this.roomId))]) + .pipe(takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject))) + .subscribe(([roomInfo]) => { + this.roomInfo = roomInfo; + + this.chatSettingForm = this.formBuilder.group({ + roomName: [ + this.roomInfo?.roomName, + !this.roomInfo?.isTimeRoom ? [Validators.required] : [] + ], + changeTarget: ['me'], + timerInterval: [this.roomInfo?.timeRoomInterval] + }); + + this.changeDetectorRef.markForCheck(); + }); + } + + onChange() { + const checkInvalid = this.chatSettingForm.invalid; + let roomName: string; + if (checkInvalid) { + roomName = ''; + } else { + roomName = this.chatSettingForm.get('roomName').value; + } + } + + onKeyupName() { + this.chatSettingForm.get('roomName').markAsTouched(); + } + + setTimerArray() { + const hourFrom = this.i18nService.t('common:units.hourFrom'); + const minute = this.i18nService.t('common:units.minute'); + const second = this.i18nService.t('common:units.second'); + + this.timerArray = [ + { value: 5, text: `5 ${second}` }, + { value: 10, text: `10 ${second}` }, + { value: 30, text: `30 ${second}` }, + { value: 60, text: `1 ${minute}` }, + { value: 300, text: `5 ${minute}` }, + { value: 600, text: `10 ${minute}` }, + { value: 1800, text: `30 ${minute}` }, + { value: 3600, text: `1 ${hourFrom}` }, + { value: 21600, text: `6 ${hourFrom}` }, + { value: 43200, text: `12 ${hourFrom}` }, + { value: 86400, text: `24 ${hourFrom}` } + ]; + } + + onChangeGroupName(name: string) { + this.roomName = name; + } + + onClosed(event: MouseEvent): void { + this.closed.emit(); + } + onConfirm(): void { + const roomName = this.chatSettingForm.get('roomName').value; + const roomNameChangeTarget = this.chatSettingForm.get('changeTarget').value; + const timerInterval = this.chatSettingForm.get('timerInterval').value; + + this.store.dispatch( + RoomActions.update({ + req: { + roomId: this.roomInfo.roomId, + roomName, + receiveAlarm: this.roomInfo.receiveAlarm, + syncAll: roomNameChangeTarget.toUpperCase() === 'ALL' ? true : false + } as UpdateRequest + }) + ); + + if (!!this.roomInfo?.isTimeRoom) { + this.store.dispatch( + RoomActions.updateTimeRoomInterval({ + req: { + roomId: this.roomInfo.roomId, + timerInterval + } as UpdateTimerSetRequest + }) + ); + } + + this.closed.emit(); + } +} diff --git a/src/app/sections/chat/drawers/users.drawer.component.html b/src/app/sections/chat/drawers/users.drawer.component.html new file mode 100644 index 0000000..ceebd01 --- /dev/null +++ b/src/app/sections/chat/drawers/users.drawer.component.html @@ -0,0 +1,52 @@ +
    + +
    + {{ 'chat:label.showRoomUsers' | ucapI18n }} +
    +
    + + + + + + + +
    +
    + + +
    +
    +
    diff --git a/src/app/sections/chat/drawers/users.drawer.component.scss b/src/app/sections/chat/drawers/users.drawer.component.scss new file mode 100644 index 0000000..d9b951d --- /dev/null +++ b/src/app/sections/chat/drawers/users.drawer.component.scss @@ -0,0 +1,23 @@ +@import '~@ucap/lg-scss/mixins'; + +.drawer-container { + width: 100%; + height: 100%; + .drawer-body { + height: 100%; + } +} +.ucap-virtual-scroll-viewport { + height: auto !important; +} +.btn-bottom-area { + display: flex; + flex-direction: row; + background-color: $white; + padding: 14px 16px 10px; + justify-content: flex-end; + width: 100%; + button { + @include ucap-button-flat-stroked(50%); + } +} diff --git a/src/app/sections/group/components/component-ui/tenant-search.component.spec.ts b/src/app/sections/chat/drawers/users.drawer.component.spec.ts similarity index 57% rename from src/app/sections/group/components/component-ui/tenant-search.component.spec.ts rename to src/app/sections/chat/drawers/users.drawer.component.spec.ts index f4b9b1c..c4b6385 100644 --- a/src/app/sections/group/components/component-ui/tenant-search.component.spec.ts +++ b/src/app/sections/chat/drawers/users.drawer.component.spec.ts @@ -2,20 +2,20 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; -import { TenantSearchComponent } from './tenant-search.component'; +import { UsersDrawerComponent } from './users.drawer.component'; -describe('ucap::ui-organization::TenantSearchComponent', () => { - let component: TenantSearchComponent; - let fixture: ComponentFixture; +describe('ucap::ui-organization::CreateChatDialogComponent', () => { + let component: UsersDrawerComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [TenantSearchComponent] + declarations: [UsersDrawerComponent] }).compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(TenantSearchComponent); + fixture = TestBed.createComponent(UsersDrawerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/sections/chat/drawers/users.drawer.component.ts b/src/app/sections/chat/drawers/users.drawer.component.ts new file mode 100644 index 0000000..576d189 --- /dev/null +++ b/src/app/sections/chat/drawers/users.drawer.component.ts @@ -0,0 +1,226 @@ +import { Subject, combineLatest, merge } from 'rxjs'; +import { takeUntil, take } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + EventEmitter, + Output, + Input +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { MatDialog } from '@angular/material/dialog'; + +import { VersionInfo2Response } from '@ucap/api-public'; +import { + UserInfo as RoomUserInfo, + UserInfoShort as RoomUserInfoShort, + ExitForcingRequest +} from '@ucap/protocol-room'; +import { User } from '@ucap/protocol-info'; + +import { I18nService } from '@ucap/ng-i18n'; + +import { UserSelector } from '@ucap/ng-store-organization'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; +import { RoomSelector, RoomActions } from '@ucap/ng-store-chat'; + +import { + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult +} from '@ucap/ng-ui'; + +import { ChatDrawType } from '@app/pages/chat/types/chat-draw.type'; +import { DrawInfo } from '@app/pages/chat/models/draw-info'; +import { + ProfileDialogComponent, + ProfileDialogData, + ProfileDialogResult +} from '@app/sections/organization/dialogs/profile.dialog.component'; +import { AppChatService } from '@app/services/app-chat.service'; + +export type UserInfoTypes = RoomUserInfo | RoomUserInfoShort; + +@Component({ + selector: 'app-drawer-chat-users', + templateUrl: './users.drawer.component.html', + styleUrls: ['./users.drawer.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class UsersDrawerComponent implements OnInit, OnDestroy { + private roomIdSubject = new Subject(); + private ngOnDestroySubject = new Subject(); + + @Input() + set roomId(value: string) { + this._roomId = value; + if (!!this.roomIdSubject) { + this.roomIdSubject.next(value); + } + this._initializeData(); + } + get roomId(): string { + return this._roomId; + } + // tslint:disable-next-line: variable-name + _roomId: string; + + @Output() + rightDrawerToggle = new EventEmitter(); + + @Output() + closed = new EventEmitter(); + + user: User; + versionInfo2Res: VersionInfo2Response; + + roomUsers: RoomUserInfo[] = []; + roomUsersShort: RoomUserInfoShort[] = []; + + constructor( + private i18nService: I18nService, + public dialog: MatDialog, + private store: Store, + private appChatService: AppChatService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit(): void { + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + + this._initializeData(); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + + if (!!this.roomIdSubject) { + this.roomIdSubject.next(); + this.roomIdSubject.complete(); + } + } + + private _initializeData() { + combineLatest([ + this.store.pipe(select(UserSelector.user)), + this.store.pipe(select(RoomSelector.roomUser, this.roomId)), + this.store.pipe(select(RoomSelector.roomUserShort, this.roomId)) + ]) + .pipe(takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject))) + .subscribe(([user, roomUser, roomUserShort]) => { + this.user = user; + if ( + !!roomUser && + !!roomUser.userInfos && + roomUser.userInfos.length > 0 + ) { + this.roomUsers = roomUser.userInfos.filter( + (item) => !!item.isJoinRoom + ); + } + if ( + !!roomUserShort && + !!roomUserShort.userInfos && + roomUserShort.userInfos.length > 0 + ) { + this.roomUsersShort = roomUserShort.userInfos.filter( + (item) => !!item.isJoinRoom + ); + } + + this.changeDetectorRef.markForCheck(); + }); + } + + onClosed(event: MouseEvent): void { + this.closed.emit(); + } + onClickAddUserChatRoom(): void { + this.rightDrawerToggle.emit({ + chatDrawType: ChatDrawType.Invite, + returnDrawType: ChatDrawType.RoomUsers + }); + } + onClickAddGroupMember(): void { + this.rightDrawerToggle.emit({ + chatDrawType: ChatDrawType.AddGroup, + returnDrawType: ChatDrawType.RoomUsers + }); + } + + onClickUser(userInfo: UserInfoTypes): void {} + + onClickOpenProfile(userSeq: string): void { + const result = this.dialog.open< + ProfileDialogComponent, + ProfileDialogData, + ProfileDialogResult + >(ProfileDialogComponent, { + panelClass: 'mid-create-dialog', + data: { + userSeq + } + }); + } + onClickOpenChatRoom(userSeq: string): void { + this.appChatService.newOpenRoom([userSeq], false); + } + onClickSendMessage(userSeq: string): void {} + + onClickExitForcing(userInfo: UserInfoTypes): void { + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + data: { + title: this.i18nService.t('chat:dialog.title.ejectFromRoom'), + html: this.i18nService.t('chat:dialog.confirmEjectFromRoom', { + targetMember: userInfo.name + }) + } + }); + + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && !!result.choice) { + this.store.dispatch( + RoomActions.expel({ + req: { + roomId: this.roomId, + type: 'A', + senderSeq: String(this.user.info.seq), + userSeqs: [userInfo.seq + ''] + } as ExitForcingRequest + }) + ); + } + }); + } + + isMe(userInfo: UserInfoTypes) { + if (!!this.user) { + return String(this.user.info.seq) === String(userInfo.seq); + } else { + return false; + } + } +} diff --git a/src/app/sections/group/components/component-ui/dialogs/create-chat.dialog.component.html b/src/app/sections/group/components/component-ui/dialogs/create-chat.dialog.component.html deleted file mode 100644 index a026d34..0000000 --- a/src/app/sections/group/components/component-ui/dialogs/create-chat.dialog.component.html +++ /dev/null @@ -1,131 +0,0 @@ - - - {{ data.title }} - - - -
    - - - - -
    - - -
    -
    - -
    - 새 그룹 추가 - - -
    - -
    - - -
    -
    -
    -
    - - -
    -
    - - -
    - - - - {{ userInfo.name }} - clear - - -
    - - - - {{ selectedUserList.length }} / 300 - - - - - - - - - - {{ selectedUserList.length }} - - - -
    diff --git a/src/app/sections/group/components/component-ui/dialogs/create-chat.dialog.component.scss b/src/app/sections/group/components/component-ui/dialogs/create-chat.dialog.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/sections/group/components/component-ui/dialogs/create-chat.dialog.component.ts b/src/app/sections/group/components/component-ui/dialogs/create-chat.dialog.component.ts deleted file mode 100644 index a65f04a..0000000 --- a/src/app/sections/group/components/component-ui/dialogs/create-chat.dialog.component.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { - Component, - OnInit, - OnDestroy, - ChangeDetectionStrategy, - ChangeDetectorRef, - Inject, - ViewChild -} from '@angular/core'; -import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; -import { - UserInfoSS, - UserInfoF, - UserInfoDN, - DeptInfo -} from '@ucap/protocol-query'; - -import { Store, select } from '@ngrx/store'; -import { takeUntil } from 'rxjs/operators'; -import { - CompanySelector, - DepartmentSelector -} from '@ucap/ng-store-organization'; -import { Subject, combineLatest } from 'rxjs'; -import { AppAuthenticationService } from '@app/services/app-authentication.service'; -import { SelectUserDialogType } from '@app/types'; -import { RoomInfo, UserInfo as RoomUserInfo } from '@ucap/protocol-room'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { SelectUserSectionComponent } from '../../select-user.section.component'; -import { GroupActions } from '@ucap/ng-store-group'; -import { UserInfoTypes } from '../profile-list-item.component'; - -export interface CreateChatDialogData { - type?: SelectUserDialogType; - title: string; - /** CASE :: EditMember */ - group?: GroupDetailData; - /** CASE :: EventForward */ - ignoreRoom?: RoomInfo[]; - /** CASE :: EditChatMember */ - curRoomUser?: ( - | UserInfo - | UserInfoSS - | UserInfoF - | UserInfoDN - | RoomUserInfo - )[]; -} -export interface CreateChatDialogResult {} - -@Component({ - selector: 'ucap-local-organization-create-chat.dialog', - templateUrl: './create-chat.dialog.component.html', - styleUrls: ['./create-chat.dialog.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class CreateChatDialogComponent implements OnInit, OnDestroy { - private ngOnDestroySubject = new Subject(); - isLinear = false; - firstFormGroup: FormGroup; - secondFormGroup: FormGroup; - selectedUserList: UserInfoTypes[] = []; - SelectUserDialogType = SelectUserDialogType; - - constructor( - public dialogRef: MatDialogRef< - CreateChatDialogData, - CreateChatDialogResult - >, - @Inject(MAT_DIALOG_DATA) public data: CreateChatDialogData, - private changeDetectorRef: ChangeDetectorRef, - private store: Store, - private appAuthenticationService: AppAuthenticationService - ) {} - - @ViewChild('selectBoxUserComponent', { static: false }) - selectBoxUserComponent: SelectUserSectionComponent; - - ngOnInit(): void {} - - ngOnDestroy(): void { - if (!!this.ngOnDestroySubject) { - this.ngOnDestroySubject.complete(); - } - } - - onClickCancel() {} - onClickChoice(s: boolean) {} - getBtnValid() {} - getChipsRemoveYn(userInfo: UserInfo) {} - onClickDeleteUser(userInfo: UserInfo) {} - - onChangeSelectedUserList(userList: UserInfoTypes[]) { - this.selectedUserList = userList; - this.changeDetectorRef.markForCheck(); - } - onClickComplete(groupName: string) { - switch (this.data.type) { - case SelectUserDialogType.NewGroup: - { - const userSeqs: string[] = []; - this.selectedUserList.map((user) => - userSeqs.push(user.seq.toString()) - ); - this.store.dispatch( - GroupActions.create({ - groupName, - targetUserSeqs: userSeqs - }) - ); - } - break; - case SelectUserDialogType.NewChat: - { - } - break; - case SelectUserDialogType.EditChatMember: - { - } - break; - case SelectUserDialogType.EditMember: - { - } - break; - case SelectUserDialogType.MessageForward: - { - } - break; - } - - this.dialogRef.close(); - } -} diff --git a/src/app/sections/group/components/component-ui/dialogs/index.ts b/src/app/sections/group/components/component-ui/dialogs/index.ts deleted file mode 100644 index a2b25b0..0000000 --- a/src/app/sections/group/components/component-ui/dialogs/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { CreateChatDialogComponent } from './create-chat.dialog.component'; - -export const DIALOGS = [CreateChatDialogComponent]; diff --git a/src/app/sections/group/components/component-ui/index.ts b/src/app/sections/group/components/component-ui/index.ts deleted file mode 100644 index 8f2e212..0000000 --- a/src/app/sections/group/components/component-ui/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ProfileListItemComponent } from './profile-list-item.component'; -import { ProfileComponent } from './profile.component'; -import { TenantSearchComponent } from './tenant-search.component'; - -export const COMPONENTS = [ - ProfileListItemComponent, - ProfileComponent, - TenantSearchComponent -]; diff --git a/src/app/sections/group/components/component-ui/profile-list-item.component.html b/src/app/sections/group/components/component-ui/profile-list-item.component.html deleted file mode 100644 index d222f84..0000000 --- a/src/app/sections/group/components/component-ui/profile-list-item.component.html +++ /dev/null @@ -1,42 +0,0 @@ -
    - -
    - {{ userInfo.intro }} -
    -
    - -
    -
    diff --git a/src/app/sections/group/components/component-ui/profile-list-item.component.scss b/src/app/sections/group/components/component-ui/profile-list-item.component.scss deleted file mode 100644 index 57f284c..0000000 --- a/src/app/sections/group/components/component-ui/profile-list-item.component.scss +++ /dev/null @@ -1,101 +0,0 @@ -@charset 'UTF-8'; - -@import '../../../../../assets/scss/components.scss'; - -.user-list { - display: flex; - flex-flow: row nowrap; - justify-content: space-between; - padding: 0 16px; - height: 70px; - align-items: center; - &.line-top { - border-top: 1px solid $gray-rec; - } - .user-profile-info { - display: inline-flex; - flex-direction: row; - flex-grow: 2.3; - .user-profile-thumb { - @include profile-avatar-default( - 0 5px 5px 0, - 8, - $green, - 18px - ); //오른 아래 공간, 모바일 온라인 아이콘 크기, 모바일 아이콘 색, 모바일 아이콘 bg크기 - .presence { - //PC 상태 - @include presence-state(8px); //원크기 - } - .profile-image { - @include avatar-img(36px, 2px); //아바타 크기, 왼쪽공간 - } - } - .user-info { - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: flex-start; - padding-left: 16px; - .user-n-g { - display: flex; - flex-flow: row-reverse nowrap; - align-items: flex-end; - height: 22px; - .user-name { - font: { - size: 16px; - weight: 600; - } - color: $gray-re21; - order: 1; - -ms-flex-order: 1; - } - .user-grade { - font: { - size: 13px; - } - color: $gray-re70; - margin-left: 4px; - order: 0; - -ms-flex-order: 0; - } - } - .dept-name { - font-size: 12px; - color: $gray-re6; - line-height: 16px; - } - } - } - .intro { - display: inline-flex; - flex-flow: row nowrap; - flex-basis: 35%; - flex-grow: 0; - align-items: baseline; - p { - font-size: 11px; - line-height: 1.4; - display: block; - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - height: 30px; - } - &:before { - content: 'chat'; - @include font-family-ico(12, center, $lipstick); - flex-direction: row; - align-items: flex-start; - width: 12px; - height: 12px; - line-height: 12px; - margin-right: 4.8px; - position: relative; - top: 2px; - } - } -} diff --git a/src/app/sections/group/components/component-ui/profile-list-item.component.ts b/src/app/sections/group/components/component-ui/profile-list-item.component.ts deleted file mode 100644 index f106b4e..0000000 --- a/src/app/sections/group/components/component-ui/profile-list-item.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { - Component, - OnInit, - OnDestroy, - ChangeDetectionStrategy, - ChangeDetectorRef, - Input, - EventEmitter, - Output -} from '@angular/core'; -import { UserInfo } from '@ucap/protocol-sync'; -import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; - -export type UserInfoTypes = UserInfo | UserInfoSS | UserInfoF | UserInfoDN; - -@Component({ - selector: 'ucap-local-organization-profile-list-item', - templateUrl: './profile-list-item.component.html', - styleUrls: ['./profile-list-item.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProfileListItemComponent implements OnInit, OnDestroy { - @Input() - set userInfo(user: UserInfoTypes) { - this._userInfo = user; - } - get userInfo(): UserInfoTypes { - return this._userInfo; - } - _userInfo: UserInfoTypes; - - @Input() - defaultProfileImage: string; - - @Input() - profileImageRoot: string; - - @Input() - checkable = false; - - @Output() - checkUser = new EventEmitter<{ - isChecked: boolean; - userInfo: UserInfoTypes; - }>(); - - constructor(private changeDetectorRef: ChangeDetectorRef) {} - - ngOnInit(): void {} - - ngOnDestroy(): void {} - - onClickProfileImage(event: Event, userInfo: UserInfoTypes): void {} - - onChangeCheck( - value: boolean, - userInfo: UserInfo | UserInfoSS | UserInfoF | UserInfoDN - ) { - this.checkUser.emit({ - isChecked: value, - userInfo - }); - } -} diff --git a/src/app/sections/group/components/component-ui/profile.component.html b/src/app/sections/group/components/component-ui/profile.component.html deleted file mode 100644 index 1c58bdf..0000000 --- a/src/app/sections/group/components/component-ui/profile.component.html +++ /dev/null @@ -1,73 +0,0 @@ -
    - - -
    - -
    - {{ userInfo.name }} - {{ userInfo.grade }} - ({{ userInfo.nameEn }}) - O온라인 -
    - - - - -
    - - 이름 부서명, 전화번호, 이메일 - - search - -
    -
    - - - - - - - - - - - -
    - - - - - - -
    -
    diff --git a/src/app/sections/group/components/component-ui/profile.component.scss b/src/app/sections/group/components/component-ui/profile.component.scss deleted file mode 100644 index 873d432..0000000 --- a/src/app/sections/group/components/component-ui/profile.component.scss +++ /dev/null @@ -1,77 +0,0 @@ -@charset 'UTF-8'; - -@import '../../../../../assets/scss/components.scss'; - -.main-profile { - display: flex; - flex: 0 0 650px; - min-height: 100%; - min-width: 450px; - @include screen(mid) { - min-height: auto; - } - // background-image: url(../../../assets/images/bg/bg_profile1.svg), - // url(../../../assets/images/bg/bg_profile2.svg), - // url(../../../assets/images/bg/bg_profile3.svg), - // url(../../../assets/images/bg/bg_profile4.svg), - // url(../../../assets/images/bg/bg_profile5.svg), $bg-linear-gradient; - background-repeat: no-repeat; - background-position: -213px -223px, 433px 95px, 489px 72px, 433px 517px, - 335px 634px, 0 0; - - .example-card { - display: flex; - flex-direction: column; - padding: 60px 8.7%; - width: 100%; - .my-input { - display: flex; - flex-flow: row nowrap; - justify-content: space-between; - margin: 0; - width: 100%; - border-bottom: 1px solid $white; - margin-top: 78px; - .my-in-input { - font-size: 16px; - color: $gray-re3; - flex-grow: 1; - height: 24px; - line-height: 24px; - margin-top: 8px; - } - button { - margin-bottom: 5px; - } - } - } - - .user-profile-info-list { - margin-top: 60px; - ul { - display: flex; - flex-direction: column; - justify-content: space-between; - height: 250px; - li { - font-size: 16px; - font-weight: 600; - color: $brown; - span { - width: 100px; - height: 34px; - border-radius: 18px; - border: solid 1px #f8f9fd; - background-color: #aaa0a5; - font-size: 14px; - font-weight: 600; - display: inline-flex; - align-items: center; - justify-content: center; - color: $white; - margin-right: 40px; - } - } - } - } -} diff --git a/src/app/sections/group/components/component-ui/profile.component.ts b/src/app/sections/group/components/component-ui/profile.component.ts deleted file mode 100644 index 2a86ac7..0000000 --- a/src/app/sections/group/components/component-ui/profile.component.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { - Component, - OnInit, - OnDestroy, - ChangeDetectionStrategy, - ChangeDetectorRef, - Input, - Output, - EventEmitter, - ViewChild, - ElementRef -} from '@angular/core'; - -import { AppKey } from '@app/types/app-key.type'; -import { LoginSession } from '@app/models/login-session'; -import { Subject } from 'rxjs'; -import { UserInfoSS, AuthResponse } from '@ucap/protocol-query'; -import { OpenProfileOptions } from '@ucap/protocol-buddy'; -import { FileUploadItem } from '@ucap/api'; -import { FormControl } from '@angular/forms'; -import { WorkStatusType } from '@ucap/protocol'; -import { I18nService } from '@ucap/ng-i18n'; - -@Component({ - selector: 'app-group-profile', - templateUrl: './profile.component.html', - styleUrls: ['./profile.component.scss'], - - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProfileComponent implements OnInit, OnDestroy { - private ngOnDestroySubject = new Subject(); - - @Input() - profileImageRoot: string; - - @Input() - isMe: boolean; - @Input() - isBuddy: boolean; - @Input() - isFavorit: boolean; - - @Input() - set userInfo(u: UserInfoSS) { - this._userInfo = u; - } - get userInfo(): UserInfoSS { - return this._userInfo; - } - _userInfo: UserInfoSS; - @Input() - myMadn?: string; - @Input() - openProfileOptions?: OpenProfileOptions; - @Input() - useBuddyToggleButton: boolean; - @Input() - authInfo: AuthResponse; - - @Output() - profileImageView = new EventEmitter(); - @Output() - openChat = new EventEmitter(); - @Output() - sendMessage = new EventEmitter(); - @Output() - sendCall = new EventEmitter(); - @Output() - sendSms = new EventEmitter(); - @Output() - createConference = new EventEmitter(); - @Output() - toggleFavorit = new EventEmitter<{ - userInfo: UserInfoSS; - isFavorit: boolean; - }>(); - @Output() - toggleBuddy = new EventEmitter<{ - userInfo: UserInfoSS; - isBuddy: boolean; - }>(); - @Output() - uploadProfileImage = new EventEmitter(); - @Output() - updateIntro = new EventEmitter(); - - @ViewChild('profileImageFileInput', { static: false }) - profileImageFileInput: ElementRef; - - userIntroFormControl = new FormControl(''); - profileImageFileUploadItem: FileUploadItem; - - constructor(private i18nService: I18nService) {} - - ngOnInit(): void {} - - ngOnDestroy(): void { - if (!!this.ngOnDestroySubject) { - this.ngOnDestroySubject.complete(); - } - } - - onClickProfileImageView() { - this.profileImageView.emit(); - } - - onClickOpenChat() { - this.openChat.emit(this.userInfo); - } - - onClickCall(type: string) { - let calleeNumber = ''; - - if (type === 'LINE') { - calleeNumber = this.userInfo.lineNumber; - } else { - calleeNumber = this.userInfo.hpNumber; - } - this.sendCall.emit(calleeNumber); - } - - onClickSMS() { - this.sendSms.emit(this.userInfo.employeeNum); - } - - onClickVideoConference() { - this.createConference.emit(Number(this.userInfo.seq)); - } - - onClickMessage() { - this.sendMessage.emit(this.userInfo); - } - - onToggleFavorit() { - this.isFavorit = !this.isFavorit; - - this.toggleFavorit.emit({ - userInfo: this.userInfo, - isFavorit: this.isFavorit - }); - } - - onClickAddBuddy() { - this.toggleBuddy.emit({ - userInfo: this.userInfo, - isBuddy: true - }); - } - - onClickDelBuddy() { - this.toggleBuddy.emit({ - userInfo: this.userInfo, - isBuddy: false - }); - } - onApplyIntroMessage(intro: string) { - if (intro.trim().length < 1) { - this.updateIntro.emit(' '); - } else { - this.updateIntro.emit(intro); - } - } - - onChangeFileInput() { - this.profileImageFileUploadItem = FileUploadItem.fromFiles( - this.profileImageFileInput.nativeElement.files - )[0]; - - this.uploadProfileImage.emit(this.profileImageFileUploadItem); - - this.profileImageFileInput.nativeElement.value = ''; - } - - getWorkstatus(userInfo: UserInfoSS): string { - let workstatus = ''; - if (!!userInfo && !!userInfo.workstatus) { - switch (userInfo.workstatus) { - case WorkStatusType.VacationAM: - workstatus = '오전'; - break; - case WorkStatusType.VacationPM: - workstatus = '오후'; - break; - case WorkStatusType.VacationAll: - workstatus = '휴가'; - break; - case WorkStatusType.LeaveOfAbsence: - workstatus = '휴직'; - break; - case WorkStatusType.LongtermRefresh: - workstatus = '장기'; - break; - } - } - - return workstatus; - } - getWorkstatusStyle(userInfo: UserInfoSS): string { - // morning-off: 오전 afternoon-off: 오후 day-off: 휴가 long-time: 장기 leave-of-absence: 휴직 - let style = ''; - if (!!userInfo && !!userInfo.workstatus) { - switch (userInfo.workstatus) { - case WorkStatusType.VacationAM: - style = 'morning-off'; - break; - case WorkStatusType.VacationPM: - style = 'afternoon-off'; - break; - case WorkStatusType.VacationAll: - style = 'day-off'; - break; - case WorkStatusType.LeaveOfAbsence: - style = 'leave-of-absence'; - break; - case WorkStatusType.LongtermRefresh: - style = 'long-time'; - break; - } - } - - return style; - } - - getDisabledBtn(type: string): boolean { - if (!this.myMadn || this.myMadn.trim().length === 0) { - if (type === 'LINE' || type === 'MOBILE') { - return true; - } - } - - if (type === 'LINE') { - if ( - !!this.userInfo && - !!this.userInfo.lineNumber && - this.userInfo.lineNumber.trim().length > 0 - ) { - return false; - } else { - return true; - } - } else if (type === 'MOBILE') { - if ( - !!this.userInfo && - !!this.userInfo.hpNumber && - this.userInfo.hpNumber.trim().length > 0 - ) { - return false; - } else { - return true; - } - } else if (type === 'SMS') { - // const smsUtils = new SmsUtils( - // this.sessionStorageService, - // this.nativeService - // ); - // return !smsUtils.getAuthSms(); - } - - return true; - } - - getShowBuddyToggleBtn(type: 'DEL' | 'ADD'): boolean { - let rtn = false; - if (!this.useBuddyToggleButton) { - return false; - } - - if (type === 'ADD') { - if (!this.isBuddy) { - rtn = true; - } - } else if (type === 'DEL') { - if (!!this.isBuddy) { - rtn = true; - } - } - return rtn; - } -} diff --git a/src/app/sections/group/components/component-ui/tenant-search.component.html b/src/app/sections/group/components/component-ui/tenant-search.component.html deleted file mode 100644 index 15b5712..0000000 --- a/src/app/sections/group/components/component-ui/tenant-search.component.html +++ /dev/null @@ -1,38 +0,0 @@ -
    -
    - - {{ company.companyName }} - - -
    - -
    diff --git a/src/app/sections/group/components/component-ui/tenant-search.component.scss b/src/app/sections/group/components/component-ui/tenant-search.component.scss deleted file mode 100644 index bfc8e47..0000000 --- a/src/app/sections/group/components/component-ui/tenant-search.component.scss +++ /dev/null @@ -1,28 +0,0 @@ -@charset 'UTF-8'; - -@import '../../../../../assets//scss/components.scss'; - -.search-container { - display: flex; - flex-flow: row nowrap; - margin: 0 15px 0 17px; - width: calc(100% - 32px); - border: 1px solid $lipstick; - background-color: $white; - .search-selec-box { - @include selecCtrl(40px, 12px); - flex-basis: 150px; - } - .search-in-box { - @include matinputCtrl1( - 0, - 0, - auto, - auto, - 40px - ); //$bdw: 1px, $bdr: 0, $maxw, $minw, $inputH - } - .btn-ico-search { - @include matbtnCtrl(40px, 40px, 0, 20px); - } -} diff --git a/src/app/sections/group/components/component-ui/tenant-search.component.ts b/src/app/sections/group/components/component-ui/tenant-search.component.ts deleted file mode 100644 index 8535aa4..0000000 --- a/src/app/sections/group/components/component-ui/tenant-search.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - Component, - OnInit, - OnDestroy, - ChangeDetectionStrategy, - ChangeDetectorRef, - Input, - EventEmitter, - Output -} from '@angular/core'; -import { UserInfo } from '@ucap/protocol-sync'; -import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; - -import { Store, select } from '@ngrx/store'; -import { takeUntil } from 'rxjs/operators'; -import { CompanySelector } from '@ucap/ng-store-organization'; -import { Subject } from 'rxjs'; -import { Company } from '@ucap/api-external'; -import { AppAuthenticationService } from '@app/services/app-authentication.service'; - -@Component({ - selector: 'ucap-local-organization-tenant-search', - templateUrl: './tenant-search.component.html', - styleUrls: ['./tenant-search.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TenantSearchComponent implements OnInit, OnDestroy { - companyList: Company[]; - companyCode: string; - - @Output() - keyDownEnter = new EventEmitter<{ - companyCode: string; - searchWord: string; - }>(); - - @Output() - searchCancel = new EventEmitter(); - - private ngOnDestroySubject = new Subject(); - constructor( - private changeDetectorRef: ChangeDetectorRef, - private store: Store, - private appAuthenticationService: AppAuthenticationService - ) {} - - ngOnInit(): void { - const userStore = this.appAuthenticationService.getUserStore(); - this.companyCode = userStore.companyCode; - - this.store - .pipe( - takeUntil(this.ngOnDestroySubject), - select(CompanySelector.companyList) - ) - .subscribe((companyList) => { - this.companyList = companyList; - }); - } - - ngOnDestroy(): void { - if (!!this.ngOnDestroySubject) { - this.ngOnDestroySubject.complete(); - } - } - - onKeyDownEnter(searchWord: string) { - this.keyDownEnter.emit({ companyCode: this.companyCode, searchWord }); - } - onClickCancel() { - this.searchCancel.emit(); - } -} diff --git a/src/app/sections/group/components/index.ts b/src/app/sections/group/components/index.ts index 01841b9..53c238e 100644 --- a/src/app/sections/group/components/index.ts +++ b/src/app/sections/group/components/index.ts @@ -1,12 +1,10 @@ import { ListSectionComponent } from './list.section.component'; -import { SearchSectionComponent } from './search.section.component'; import { ProfileSectionComponent } from './profile.section.component'; import { InfoSectionComponent } from './info.section.component'; import { SelectUserSectionComponent } from './select-user.section.component'; import { SelectGroupSectionComponent } from './select-group.section.component'; export const COMPONENTS = [ ListSectionComponent, - SearchSectionComponent, ProfileSectionComponent, InfoSectionComponent, SelectUserSectionComponent, diff --git a/src/app/sections/group/components/info.section.component.html b/src/app/sections/group/components/info.section.component.html index f4f7355..6df7521 100644 --- a/src/app/sections/group/components/info.section.component.html +++ b/src/app/sections/group/components/info.section.component.html @@ -1,22 +1,127 @@
    -
    Bookmark
    +
    + {{ 'organization:profile.unreadChat' | ucapI18n }} +
    +
    + {{ 'organization:profile.chatWithUs' | ucapI18n }} +
    - + > -
    Empty List
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ + 'organization:profile.noConversation' | ucapI18n + }} + +
    -
    알림봇
    +
    + {{ 'organization:profile.notificationBot' | ucapI18n }} +
    @@ -48,8 +153,71 @@ -
    Empty List
    +
    + + + + + + + + + + + + + NO + + + + 알림 내용이 없습니다. + +
    - +
    diff --git a/src/app/sections/group/components/info.section.component.scss b/src/app/sections/group/components/info.section.component.scss index e089dd5..7c5a8de 100644 --- a/src/app/sections/group/components/info.section.component.scss +++ b/src/app/sections/group/components/info.section.component.scss @@ -7,19 +7,79 @@ margin-left: 30px; overflow: hidden; justify-content: space-between; - min-width: 450px; + //min-width: 450px; + height: 100%; + @include screen(lg) { + overflow: visible; + margin-left: 0; + padding-top: 30px; + } @include screen(mid) { overflow: visible; margin-left: 0; - padding: 30px 0; + padding-top: 30px; + } + @include screen(xs) { + //overflow: visible; + //margin-left: 0; + padding: 0; } .bookmark { display: flex; flex-direction: column; + flex-grow: 1; + @include screen(xs) { + height: 100%; + } + .subtitle2 { + @include screen(xs) { + visibility: hidden; + } + } .chatlist { display: flex; flex-direction: column; margin-top: 15px; + @include screen(xs) { + margin-top: 16px; + height: calc(100% - 50px); + overflow: auto; + padding: 0 16px; + } + .empty-case { + width: 100%; + height: 100%; + span { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + svg { + width: 112px; + .svg-empty-cls01 { + fill: #bababa; + } + .svg-empty-cls02 { + fill: none; + stroke-width: 4px; + stroke: #fff; + stroke-linecap: round; + stroke-linejoin: round; + } + } + strong { + font-size: 1em; + color: #666; + padding: 5px 10px; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + margin: 10px 0 0; + } + } + } + /* .chatlist-con { display: flex; flex-direction: row; @@ -104,14 +164,23 @@ } } } + */ } } .allim { + flex-grow: 1; overflow-x: auto; @include no-scrollbar(); padding-bottom: 10px; margin-bottom: 20px; position: relative; + @include screen(custom, min, 1500) { + padding-bottom: 0; + margin-bottom: 0; + } + @include screen(xs) { + display: none; + } .btn-scroll-ctrl { position: absolute; z-index: 10; @@ -192,12 +261,51 @@ border-radius: 0; } } + .empty-case { + width: 100%; + height: 100%; + span { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; + svg { + width: 112px; + .svg-allim-empty-cls01 { + fill: #fff; + stroke: #bababa; + stroke-width: 4px; + } + .svg-allim-empty-cls02 { + fill: #bababa; + } + } + strong { + font-size: 1em; + color: #666; + padding: 5px 10px; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + margin: 10px 0 0; + } + } + } } } .banner-box { width: 100%; - height: 74px; - @extend %guideline; + min-height: 74px; align-self: flex-end; + margin-top: 20px; + flex-direction: column-reverse; + img { + width: 100%; + } + display: none; + @include screen(custom, min, 1500) { + display: flex; + } } } diff --git a/src/app/sections/group/components/info.section.component.ts b/src/app/sections/group/components/info.section.component.ts index 61c0fd6..21b7c3f 100644 --- a/src/app/sections/group/components/info.section.component.ts +++ b/src/app/sections/group/components/info.section.component.ts @@ -1,3 +1,6 @@ +import { Subject, combineLatest } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + import { Component, OnInit, @@ -7,31 +10,29 @@ import { Input } from '@angular/core'; -import { AppKey } from '@app/types/app-key.type'; -import { LoginSession } from '@app/models/login-session'; -import { Subject, combineLatest } from 'rxjs'; import { Store, select } from '@ngrx/store'; -import { takeUntil } from 'rxjs/operators'; -import { - LoginSelector, - ConfigurationSelector -} from '@ucap/ng-store-authentication'; -import { LoginResponse } from '@ucap/protocol-authentication'; -import { RoomInfo, RoomType } from '@ucap/protocol-room'; import { Dictionary } from '@ngrx/entity'; + +import { RoomInfo, RoomType } from '@ucap/protocol-room'; +import { User } from '@ucap/protocol-info'; + +import { I18nService } from '@ucap/ng-i18n'; + +import { UserSelector } from '@ucap/ng-store-organization'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; import { + RoomSelector, RoomUserMap, RoomUserShortMap -} from '@ucap/ng-store-chat/lib/store/room/state'; -import { RoomSelector } from '@ucap/ng-store-chat'; -import { VersionInfo2Response } from '@ucap/api-public'; -import { AppChatService } from '@app/services/app-chat.service'; -import { I18nService } from '@ucap/ng-i18n'; +} from '@ucap/ng-store-chat'; + import { TranslatePipe as OrganizationTranslate, TranslateService } from '@ucap/ng-ui-organization'; +import { AppChatService } from '@app/services/app-chat.service'; + @Component({ selector: 'app-sections-group-info', templateUrl: './info.section.component.html', @@ -51,8 +52,7 @@ export class InfoSectionComponent implements OnInit, OnDestroy { // tslint:disable-next-line: variable-name _userSeq: string; - versionInfo2Res: VersionInfo2Response; - loginRes: LoginResponse; + user: User; isMe = false; defaultProfileImage: string; @@ -66,7 +66,7 @@ export class InfoSectionComponent implements OnInit, OnDestroy { organizationTranslate: OrganizationTranslate; - private ngOnDestroySubject = new Subject(); + private ngOnDestroySubject: Subject = new Subject(); constructor( private appChatService: AppChatService, @@ -85,26 +85,17 @@ export class InfoSectionComponent implements OnInit, OnDestroy { // default image setting this.defaultProfileImage = this.appChatService.defaultProfileImage; - this.defaultProfileImageMulti = this.appChatService.defaultProfileImage; + this.defaultProfileImageMulti = this.appChatService.defaultProfileImageMulti; } ngOnInit(): void { this.store - .pipe( - takeUntil(this.ngOnDestroySubject), - select(ConfigurationSelector.versionInfo2Response) - ) - .subscribe((versionInfo2Res) => { - this.versionInfo2Res = versionInfo2Res; - }); - - this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; if ( - (!!this.userSeq && this.userSeq === loginRes.userSeq) || + (!!this.userSeq && this.userSeq === String(user.info.seq)) || !this.userSeq ) { this.isMe = true; @@ -154,14 +145,15 @@ export class InfoSectionComponent implements OnInit, OnDestroy { ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } getInfoByUser(userSeq: string | number): void { // is Me check - if (!!userSeq && !!this.loginRes && !!this.loginRes.userInfo) { - if (this.loginRes.userSeq + '' === userSeq + '') { + if (!!userSeq && !!this.user && !!this.user.info.seq) { + if (String(this.user.info.seq) === String(userSeq)) { this.isMe = true; // Allim room list setting @@ -212,40 +204,10 @@ export class InfoSectionComponent implements OnInit, OnDestroy { this.changeDetectorRef.detectChanges(); } - getRoomName(roomInfo: RoomInfo): string { - if (!roomInfo) { - return ''; - } - - const roomName = this.appChatService.getRoomName( - this.organizationTranslate, - this.loginRes, - roomInfo, - this.roomUsersDictionary, - this.roomUsersShortDictionary - ); - - return roomName; - } - - getRoomProfileImage(roomInfo: RoomInfo): string { - let roomImage = ''; - if (!!roomInfo) { - roomImage = this.appChatService.getRoomProfileImage( - roomInfo, - this.loginRes, - this.roomUsersDictionary, - this.roomUsersShortDictionary - ); - } - - return roomImage; - } - onClickRoomItem(event: MouseEvent, roomInfo: RoomInfo): void { event.preventDefault(); event.stopPropagation(); - console.log('OPEN CHAT ROOM / Group / Main'); + this.appChatService.openRoombyRoomId(roomInfo.roomId); } } diff --git a/src/app/sections/group/components/list.section.component.html b/src/app/sections/group/components/list.section.component.html index 3e6f0c1..81ad874 100644 --- a/src/app/sections/group/components/list.section.component.html +++ b/src/app/sections/group/components/list.section.component.html @@ -1,20 +1,28 @@
    - + (groupDestroy)="onGroupDestroy($event)" + >
    -
    +
    diff --git a/src/app/sections/group/components/list.section.component.scss b/src/app/sections/group/components/list.section.component.scss index 63a93c1..a9686d2 100644 --- a/src/app/sections/group/components/list.section.component.scss +++ b/src/app/sections/group/components/list.section.component.scss @@ -1,7 +1,11 @@ .list-container { + width: 100%; + height: 100%; } -.search-wrpper { - overflow: auto; - position: relative; - height: 350px; -} +// .search-wrpper { +// overflow-y: auto; +// overflow-x: hidden; +// position: relative; +// height: 100%; +// background-color: #fff; +// } diff --git a/src/app/sections/group/components/list.section.component.ts b/src/app/sections/group/components/list.section.component.ts index f966ed0..7dd5e56 100644 --- a/src/app/sections/group/components/list.section.component.ts +++ b/src/app/sections/group/components/list.section.component.ts @@ -10,30 +10,18 @@ import { Input, ViewChild, EventEmitter, - Output + Output, + ElementRef } from '@angular/core'; +import { Router } from '@angular/router'; + +import { MatDialog } from '@angular/material/dialog'; + import { Store, select } from '@ngrx/store'; -import { - VirtualScrollStrategy, - FixedSizeVirtualScrollStrategy, - VIRTUAL_SCROLL_STRATEGY, - CdkVirtualScrollViewport -} from '@angular/cdk/scrolling'; - -import { LoginResponse } from '@ucap/protocol-authentication'; import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; - -import { LogService } from '@ucap/ng-logger'; -import { ExpansionComponent as AppExpansionComponent } from '@app/ucap/group/components/expansion.component'; -import { SessionStorageService } from '@ucap/ng-web-storage'; -import { LoginSelector } from '@ucap/ng-store-authentication'; -import { GroupActions, BuddyActions } from '@ucap/ng-store-group'; -import { I18nService } from '@ucap/ng-i18n'; - -import { AppAuthenticationService } from '@app/services/app-authentication.service'; -import { QueryProtocolService } from '@ucap/ng-protocol-query'; +import { User } from '@ucap/protocol-info'; import { UserInfoSS, UserInfoF, @@ -42,32 +30,45 @@ import { } from '@ucap/protocol-query'; import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; -import { Router } from '@angular/router'; -import { MatDialog } from '@angular/material/dialog'; +import { LogService } from '@ucap/ng-logger'; +import { I18nService } from '@ucap/ng-i18n'; +import { SessionStorageService } from '@ucap/ng-web-storage'; +import { QueryProtocolService } from '@ucap/ng-protocol-query'; + +import { + PresenceActions, + PresenceSelector, + UserSelector +} from '@ucap/ng-store-organization'; + import { - ConfirmDialogComponent, - ConfirmDialogData, - ConfirmDialogResult, AlertDialogComponent, AlertDialogData, AlertDialogResult } from '@ucap/ng-ui'; + +import { Expansion01Component as AppExpansion01Component } from '@app/ucap/group/components/expansion-01.component'; +import { AppAuthenticationService } from '@app/services/app-authentication.service'; +import { SearchData } from '@app/ucap/organization/models/search-data'; +import { UserStore } from '@app/models/user-store'; +import { AppChatService } from '@app/services/app-chat.service'; +import { GroupManageType } from '@app/types'; +import { AppGroupService } from '@app/services/app-group.service'; +import { LoginSession } from '@app/models/login-session'; +import { GroupOpenInfo } from '@app/models/group-open-info'; + import { ManageDialogComponent, ManageDialogData, ManageDialogResult } from '../dialogs/manage.dialog.component'; -import { PresenceActions, PresenceSelector } from '@ucap/ng-store-organization'; -import { GroupUserDialaogType } from '@app/types'; -import { EditInlineInputDialogComponent } from '../dialogs/edit-inline-input.dialog.component'; import { EditUserDialogComponent, EditUserDialogData, EditUserDialogResult } from '../dialogs/edit-user.dialog.component'; -import { SearchData } from '@app/ucap/organization/models/search-data'; -import { UserStore } from '@app/models/user-store'; +import { SortViewType } from '@app/pages/group/types/sort-view.type'; export type UserInfoTypes = | UserInfo @@ -76,22 +77,10 @@ export type UserInfoTypes = | UserInfoDN | RoomUserInfo; -export class GroupVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy { - constructor() { - super(60, 150, 200); // (itemSize, minBufferPx, maxBufferPx) - } -} - @Component({ selector: 'app-sections-group-list', templateUrl: './list.section.component.html', styleUrls: ['./list.section.component.scss'], - providers: [ - { - provide: VIRTUAL_SCROLL_STRATEGY, - useClass: GroupVirtualScrollStrategy - } - ], changeDetection: ChangeDetectionStrategy.OnPush }) export class ListSectionComponent implements OnInit, OnDestroy { @@ -129,7 +118,7 @@ export class ListSectionComponent implements OnInit, OnDestroy { checkable = false; @Input() - showType: string; + showType: SortViewType; @Output() checkUser = new EventEmitter<{ @@ -137,14 +126,21 @@ export class ListSectionComponent implements OnInit, OnDestroy { userInfo: any; }>(); - @ViewChild('appGroupExpansion', { static: false }) - appGroupExpansion: AppExpansionComponent; + @Output() + clickUser = new EventEmitter(); - loginRes: LoginResponse; + @ViewChild('appGroupExpansion01', { static: false }) + appGroupExpansion: AppExpansion01Component; + + @ViewChild('nicknameInput', { static: false }) + nicknameInput: ElementRef; + + loginSession: LoginSession; + user: User; searchUserInfos: UserInfoSS[] = []; - private ngOnDestroySubject = new Subject(); + private ngOnDestroySubject: Subject = new Subject(); private userStore: UserStore; constructor( @@ -156,24 +152,30 @@ export class ListSectionComponent implements OnInit, OnDestroy { private i18nService: I18nService, private logService: LogService, private queryProtocolService: QueryProtocolService, - public dialog: MatDialog + private dialog: MatDialog, + private appChatService: AppChatService, + private appGroupService: AppGroupService ) { this.userStore = this.appAuthenticationService.getUserStore(); - this.i18nService.setDefaultNamespace('group'); } ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; + }); + this.appAuthenticationService + .getLoginSession$() + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe((loginSession) => { + this.loginSession = loginSession; }); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } @@ -187,10 +189,11 @@ export class ListSectionComponent implements OnInit, OnDestroy { companyCode: searchData.companyCode, searchRange: DeptSearchType.All, search: searchData.searchWord, - senderCompanyCode: this.loginRes.userInfo.companyCode, - senderEmployeeType: this.loginRes.userInfo.employeeType + senderCompanyCode: this.user.info.companyCode, + senderEmployeeType: this.user.info.employeeType }) .pipe( + take(1), map((resObj) => { const userInfos = resObj.userInfos; @@ -228,25 +231,23 @@ export class ListSectionComponent implements OnInit, OnDestroy { } onClickUser(params: { event: MouseEvent; userInfo: UserInfo }) { - params.event.preventDefault(); - params.event.stopPropagation(); + if (!!params.event) { + params.event.preventDefault(); + params.event.stopPropagation(); + } - this.router.navigate( - [ - 'group', - { - outlets: { content: 'index' } - } - ], - { - queryParams: { id: params.userInfo.seq } - } - ); + this.clickUser.emit(params.userInfo); } onClickSearchUser(event: MouseEvent, userInfo: UserInfo) { this.onClickUser({ event, userInfo }); } + onGroupDestroy(groupOpenInfo: GroupOpenInfo) { + this.appAuthenticationService.setLoginSession({ + ...this.loginSession, + groupInfo: groupOpenInfo + }); + } onExpandMore() { this.appGroupExpansion.onExpandMore(); } @@ -265,28 +266,25 @@ export class ListSectionComponent implements OnInit, OnDestroy { switch (params.menuType) { case 'CHAT': { - this.dialog.open< - AlertDialogComponent, - AlertDialogData, - AlertDialogResult - >(AlertDialogComponent, { - data: { - title: '그룹대화', - html: '그룹대화' - } - }); + this.appChatService.newOpenRoom( + params.groupBuddyList.group.userSeqs, + false, + this.user + ); } break; case 'SEND_MESSAGE': { + return; + this.dialog.open< AlertDialogComponent, AlertDialogData, AlertDialogResult >(AlertDialogComponent, { data: { - title: '그룹쪽지', - html: '그룹쪽지' + title: this.i18nService.t('group:dialog.title.messageGroup'), + html: this.i18nService.t('group:dialog.title.messageGroup') } }); } @@ -303,10 +301,9 @@ export class ListSectionComponent implements OnInit, OnDestroy { ManageDialogData, ManageDialogResult >(ManageDialogComponent, { - width: '100%', - height: '100%', + panelClass: 'max-create-dialog', data: { - title: '그룹 멤버 관리', + title: this.i18nService.t('group:dialog.title.managementGroup'), groupBuddyList: params.groupBuddyList } }); @@ -314,32 +311,42 @@ export class ListSectionComponent implements OnInit, OnDestroy { .afterClosed() .pipe(take(1)) .subscribe((result) => { - if (!!result && !!result.type) { - this.manageGroup(result); + if (!!result && !!result.choice) { + this.appGroupService.addMemberToGroup( + result.group, + result.selelctUserList + ); } }); + dialogRef + .afterOpened() + .pipe(take(1)) + .subscribe(() => { + dialogRef.componentInstance.psUpdate(); + }); + } + break; + case 'COPY_MEMBER': + { + this.editUserDialog( + GroupManageType.Copy, + params.groupBuddyList.group, + params.groupBuddyList.buddyList + ); + } + break; + case 'MOVE_MEMBER': + { + this.editUserDialog( + GroupManageType.Move, + params.groupBuddyList.group, + params.groupBuddyList.buddyList + ); } break; case 'DELETE': { - const dialogRef = this.dialog.open< - ConfirmDialogComponent, - ConfirmDialogData, - ConfirmDialogResult - >(ConfirmDialogComponent, { - data: { - title: this.i18nService.t('moreMenu.group.removeGroup'), - html: this.i18nService.t('moreMenu.confirm.removeGroup') - } - }); - dialogRef - .afterClosed() - .pipe(take(1)) - .subscribe((result) => { - if (!!result && !!result.choice) { - GroupActions.del({ group: params.groupBuddyList.group }); - } - }); + this.appGroupService.removeGroup(params.groupBuddyList); } break; default: @@ -355,25 +362,21 @@ export class ListSectionComponent implements OnInit, OnDestroy { }) { switch (params.menuType) { case 'REGISTER_FAVORITE': - this.store.dispatch( - BuddyActions.update({ - req: { - seq: Number(params.userInfo.seq), - isFavorit: !params.userInfo.isFavorit - } - }) + this.appGroupService.updateBuddy( + params.userInfo, + !params.userInfo.isFavorit ); break; - case 'NICKNAME': + case 'REGISTER_NICKNAME': { this.editNickname(params.userInfo, params.rect); } break; case 'COPY_BUDDY': - this.editUserDialog('COPY_BUDDY', params.group, params.userInfo); + this.editUserDialog('COPY_BUDDY', params.group, [params.userInfo]); break; case 'MOVE_BUDDY': - this.editUserDialog('MOVE_BUDDY', params.group, params.userInfo); + this.editUserDialog('MOVE_BUDDY', params.group, [params.userInfo]); break; case 'REMOVE_BUDDY': { @@ -383,167 +386,116 @@ export class ListSectionComponent implements OnInit, OnDestroy { } } + onFloatingProfileMenu(params: { menuType: string; userInfo: UserInfoF }) { + switch (params.menuType) { + case 'CHAT': + this.appChatService.newOpenRoom( + [params.userInfo.seq as any], + false, + this.user + ); + break; + case 'MESSAGE': + break; + case 'MOBILE': + break; + case 'OFFICE': + break; + case 'VIDEO_CONFERENCE': + break; + } + } + private renameGroup(params: { menuType: string; groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; rect: any; }) { - const paramGroup = params.groupBuddyList.group; - - const dialogRef = this.dialog.open(EditInlineInputDialogComponent, { - width: params.rect.width, - height: params.rect.height, - panelClass: 'ucap-edit-group-name-dialog', - data: { - curValue: paramGroup.name, - placeholder: '그룹명을 입력하세요.', - left: params.rect.left, - top: params.rect.top - } - }); - - dialogRef - .afterClosed() - .pipe( - take(1), - map((result) => { - if ( - !!result && - result.choice && - result.curValue.localeCompare(paramGroup.name) !== 0 - ) { - this.store.dispatch( - GroupActions.update({ - req: { - groupSeq: paramGroup.seq, - groupName: result.curValue, - userSeqs: paramGroup.userSeqs - } - }) - ); - } - }), - catchError((err) => { - return of(err); - }) - ) - .subscribe(); + this.appGroupService.updateGroupName( + params.groupBuddyList.group, + params.rect + ); } - private manageGroup(result: ManageDialogResult) { + + private updateMemberByResult(result: EditUserDialogResult) { let targetGroup: GroupDetailData; let targetUserSeqs: string[]; - if (result.type === GroupUserDialaogType.Add) { - targetGroup = result.group; - targetUserSeqs = []; - result.selelctUserList.forEach((userInfo) => { - targetUserSeqs.push(userInfo.seq + ''); - }); - - this.store.dispatch( - GroupActions.updateMember({ targetGroup, targetUserSeqs }) + if (result.type === GroupManageType.Add) { + this.appGroupService.addMemberToGroup( + result.group, + result.selelctUserList ); - } else if (result.type === GroupUserDialaogType.Copy) { + } else if (result.type === GroupManageType.Copy) { if (!!result.selectGroupList && result.selectGroupList.length > 0) { - result.selectGroupList.forEach((g) => { - targetGroup = g; - targetUserSeqs = []; - - g.userSeqs.map((seq) => { - targetUserSeqs.push(seq); - }); - - if (targetUserSeqs.length === 0) { - result.selelctUserList.forEach((user) => { - targetUserSeqs.push(user.seq as any); - }); - } else { - result.selelctUserList.forEach((user) => { - const find = targetUserSeqs.indexOf(user.seq as any); - if (find < 0) { - targetUserSeqs.push(user.seq as any); - } - }); - } - - this.store.dispatch( - GroupActions.updateMember({ targetGroup, targetUserSeqs }) - ); + result.selectGroupList.map((g) => { + this.appGroupService.copyMemberToGroup(g, result.selelctUserList); }); } - } else if (result.type === GroupUserDialaogType.Move) { + } else if (result.type === GroupManageType.Move) { const fromGroup = result.group; let toGroup: GroupDetailData; - targetUserSeqs = []; if (!!result.selectGroupList && result.selectGroupList.length > 0) { - result.selectGroupList.forEach((g) => { + result.selectGroupList.forEach((g, idx) => { toGroup = g; targetUserSeqs = []; - result.selelctUserList.forEach((user) => { - targetUserSeqs.push(user.seq as any); - }); - - this.store.dispatch( - GroupActions.moveMember({ - fromGroup, - toGroup, - targetUserSeq: targetUserSeqs - }) - ); + if (!!result.selelctUserList && result.selelctUserList.length > 0) { + if (idx === 0) { + this.appGroupService.moveMemberToGroup( + fromGroup, + toGroup, + result.selelctUserList + ); + } else { + this.appGroupService.copyMemberToGroup( + toGroup, + result.selelctUserList + ); + } + } }); } - } else if (result.type === GroupUserDialaogType.Create) { - targetUserSeqs = []; - result.selelctUserList.forEach((userInfo) => { - targetUserSeqs.push(userInfo.seq + ''); - }); - - this.store.dispatch( - GroupActions.create({ - groupName: result.groupName, - targetUserSeqs - }) + } else if (result.type === GroupManageType.Create) { + this.appGroupService.createGroup( + result.groupName, + result.selelctUserList ); } } - getStatusBulkInfo(buddy: UserInfoTypes) { - return this.store.pipe( - select(PresenceSelector.selectEntitiesStatusBulkInfo), - map((statusBulkInfo) => - !!statusBulkInfo ? statusBulkInfo[buddy.seq] : undefined - ) - ); - } - private editUserDialog( type: string, group: GroupDetailData, - userInfo: UserInfoTypes + userInfos: UserInfoTypes[] ) { let title = ''; - let dialogType: GroupUserDialaogType; + let dialogType: GroupManageType; if (type === 'COPY_BUDDY') { - title = '멤버 복사'; - dialogType = GroupUserDialaogType.Copy; - } else { - title = '멤버 이동'; - dialogType = GroupUserDialaogType.Move; + dialogType = GroupManageType.Copy; + title = this.i18nService.t('group:contextMenu.copyBuddy'); + } else if (type === 'MOVE_BUDDY') { + dialogType = GroupManageType.Move; + title = this.i18nService.t('group:contextMenu.moveBuddy'); + } else if (type === GroupManageType.Copy) { + dialogType = GroupManageType.Copy; + title = this.i18nService.t('group:dialog.title.copyGroup'); + } else if (type === GroupManageType.Move) { + dialogType = GroupManageType.Move; + title = this.i18nService.t('group:dialog.title.moveGroup'); } const dialogRef = this.dialog.open< EditUserDialogComponent, EditUserDialogData, EditUserDialogResult >(EditUserDialogComponent, { - width: '100%', - height: '100%', + panelClass: 'max-create-dialog', data: { title, type: dialogType, group, - userInfo + userInfos } }); dialogRef @@ -551,74 +503,7 @@ export class ListSectionComponent implements OnInit, OnDestroy { .pipe( take(1), map((result: EditUserDialogResult) => { - let targetGroup: GroupDetailData; - let targetUserSeqs: string[]; - if (result.type === GroupUserDialaogType.Add) { - targetGroup = result.group; - targetUserSeqs = []; - result.selelctUserList.forEach((u) => { - targetUserSeqs.push(u.seq + ''); - }); - this.store.dispatch( - GroupActions.updateMember({ targetGroup, targetUserSeqs }) - ); - } else if (result.type === GroupUserDialaogType.Copy) { - if (!!result.selectGroupList && result.selectGroupList.length > 0) { - result.selectGroupList.forEach((g) => { - targetGroup = g; - targetUserSeqs = []; - g.userSeqs.map((seq) => { - targetUserSeqs.push(seq); - }); - if (targetUserSeqs.length === 0) { - result.selelctUserList.forEach((user) => { - targetUserSeqs.push(user.seq as any); - }); - } else { - result.selelctUserList.forEach((user) => { - const find = targetUserSeqs.indexOf(user.seq as any); - if (find < 0) { - targetUserSeqs.push(user.seq as any); - } - }); - } - this.store.dispatch( - GroupActions.updateMember({ targetGroup, targetUserSeqs }) - ); - }); - } - } else if (result.type === GroupUserDialaogType.Move) { - const fromGroup = result.group; - let toGroup: GroupDetailData; - targetUserSeqs = []; - if (!!result.selectGroupList && result.selectGroupList.length > 0) { - result.selectGroupList.forEach((g) => { - toGroup = g; - targetUserSeqs = []; - result.selelctUserList.forEach((user) => { - targetUserSeqs.push(user.seq as any); - }); - this.store.dispatch( - GroupActions.moveMember({ - fromGroup, - toGroup, - targetUserSeq: targetUserSeqs - }) - ); - }); - } - } else if (result.type === GroupUserDialaogType.Create) { - targetUserSeqs = []; - result.selelctUserList.forEach((u) => { - targetUserSeqs.push(u.seq + ''); - }); - this.store.dispatch( - GroupActions.create({ - groupName: result.groupName, - targetUserSeqs - }) - ); - } + this.updateMemberByResult(result); }), catchError((err) => { return of(err); @@ -628,77 +513,17 @@ export class ListSectionComponent implements OnInit, OnDestroy { } private removeBuddy(userInfo: UserInfoF, group: GroupDetailData) { - const dialogRef = this.dialog.open< - ConfirmDialogComponent, - ConfirmDialogData, - ConfirmDialogResult - >(ConfirmDialogComponent, { - data: { - title: '', - html: this.i18nService.t('label.confirmRemoveBuddy') - } - }); - dialogRef - .afterClosed() - .pipe( - take(1), - map((result) => { - if (!!result && result.choice) { - const trgtUserSeq = group.userSeqs.filter( - (user) => user + '' !== userInfo.seq + '' - ); - - this.store.dispatch( - GroupActions.updateMember({ - targetGroup: group, - targetUserSeqs: trgtUserSeq - }) - ); - } - }), - catchError((err) => { - return of(err); - }) - ) - .subscribe(); + this.appGroupService.removeMemberToGroup( + this.i18nService.t('group:dialog.removeBuddyConfirm', { + targetMember: `${userInfo.name}` + }), + [userInfo], + group + ); } private editNickname(userInfo: UserInfoF, rect: any) { - const dialogRef = this.dialog.open(EditInlineInputDialogComponent, { - width: rect.width - 30 + '', - height: rect.height, - panelClass: 'ucap-edit-group-name-dialog', - data: { - curValue: userInfo.nickName, - placeholder: '닉네임을 설정하세요.', - left: rect.left + 70, - top: rect.top - } - }); - - dialogRef - .afterClosed() - .pipe( - take(1), - map((result) => { - if ( - !!result && - result.choice && - result.curValue.localeCompare(userInfo.nickName) !== 0 - ) { - this.store.dispatch( - BuddyActions.nickname({ - req: { - userSeq: Number(userInfo.seq), - nickname: result.curValue - } - }) - ); - } - }), - catchError((err) => { - return of(err); - }) - ) - .subscribe(); + this.appGroupService.updateNickname(userInfo, undefined, rect); } + + onChangeNickname(nick: string) {} } diff --git a/src/app/sections/group/components/list.section.strategy.ts b/src/app/sections/group/components/list.section.strategy.ts deleted file mode 100644 index cb565a2..0000000 --- a/src/app/sections/group/components/list.section.strategy.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Observable, Subject } from 'rxjs'; - -import { - VirtualScrollStrategy, - CdkVirtualScrollViewport -} from '@angular/cdk/scrolling'; -import { distinctUntilChanged } from 'rxjs/operators'; - -export class GroupVirtualScrollStrategy implements VirtualScrollStrategy { - scrolledIndexChange: Observable; - - private indexSubject = new Subject(); - private viewport: CdkVirtualScrollViewport | null = null; - - constructor() { - this.scrolledIndexChange = this.indexSubject.pipe(distinctUntilChanged()); - } - - attach(viewport: CdkVirtualScrollViewport): void { - this.viewport = viewport; - } - detach(): void { - this.indexSubject.complete(); - this.viewport = null; - } - onContentScrolled(): void {} - onDataLengthChanged(): void {} - onContentRendered(): void {} - onRenderedOffsetChanged(): void {} - scrollToIndex(index: number, behavior: ScrollBehavior): void {} -} diff --git a/src/app/sections/group/components/profile.section.component.ts b/src/app/sections/group/components/profile.section.component.ts index ec3bd62..e63b0e9 100644 --- a/src/app/sections/group/components/profile.section.component.ts +++ b/src/app/sections/group/components/profile.section.component.ts @@ -1,3 +1,6 @@ +import { Subject } from 'rxjs'; +import { takeUntil, take, map } from 'rxjs/operators'; + import { Component, OnInit, @@ -7,18 +10,15 @@ import { Input } from '@angular/core'; -import { AppKey } from '@app/types/app-key.type'; -import { LoginSession } from '@app/models/login-session'; -import { Subject } from 'rxjs'; import { Store, select } from '@ngrx/store'; -import { takeUntil, take, map } from 'rxjs/operators'; -import { - LoginSelector, - AuthorizationSelector -} from '@ucap/ng-store-authentication'; -import { LoginResponse } from '@ucap/protocol-authentication'; -import { QueryProtocolService } from '@ucap/ng-protocol-query'; + import { UserInfoSS, AuthResponse } from '@ucap/protocol-query'; +import { User } from '@ucap/protocol-info'; + +import { QueryProtocolService } from '@ucap/ng-protocol-query'; + +import { UserSelector } from '@ucap/ng-store-organization'; +import { AuthorizationSelector } from '@ucap/ng-store-authentication'; @Component({ selector: 'app-sections-group-profile', @@ -39,13 +39,13 @@ export class ProfileSectionComponent implements OnInit, OnDestroy { // tslint:disable-next-line: variable-name _userSeq: string; - loginRes: LoginResponse; + user: User; authRes: AuthResponse; isMe = false; profileUserInfo: UserInfoSS; - private ngOnDestroySubject = new Subject(); + private ngOnDestroySubject: Subject = new Subject(); constructor( private store: Store, @@ -55,16 +55,16 @@ export class ProfileSectionComponent implements OnInit, OnDestroy { ngOnInit(): void { this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; - if (!!loginRes) { - const seq = !!this.userSeq ? this.userSeq : loginRes.userSeq; + if (!!user) { + const seq = !!this.userSeq ? this.userSeq : String(user.info.seq); this.getUserInfo(seq); } - if (!!this.userSeq && this.userSeq === loginRes.userSeq) { + if (!!this.userSeq && this.userSeq === String(user.info.seq)) { this.isMe = true; } else { this.isMe = false; @@ -83,23 +83,24 @@ export class ProfileSectionComponent implements OnInit, OnDestroy { ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } getUserInfo(userSeq: string | number): void { - if (!!userSeq && !!this.loginRes && !!this.loginRes.userInfo) { + if (!!userSeq && !!this.user && !!this.user.info) { this.queryProtocolService .dataUser({ divCd: 'OPENPROF', seq: Number(userSeq + ''), - senderCompanyCode: this.loginRes.userInfo.companyCode, - senderEmployeeType: this.loginRes.userInfo.employeeType + senderCompanyCode: this.user.info.companyCode, + senderEmployeeType: this.user.info.employeeType }) .pipe( take(1), map((res) => { - if (this.loginRes.userSeq + '' === userSeq + '') { + if (String(this.user.info.seq) === String(userSeq)) { this.isMe = true; } else { this.isMe = false; diff --git a/src/app/sections/group/components/search.section.component.html b/src/app/sections/group/components/search.section.component.html deleted file mode 100644 index bf0c3f5..0000000 --- a/src/app/sections/group/components/search.section.component.html +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/src/app/sections/group/components/search.section.component.scss b/src/app/sections/group/components/search.section.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/sections/group/components/search.section.component.ts b/src/app/sections/group/components/search.section.component.ts deleted file mode 100644 index fe5dca7..0000000 --- a/src/app/sections/group/components/search.section.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - Component, - OnInit, - OnDestroy, - Output, - EventEmitter, - ChangeDetectionStrategy -} from '@angular/core'; - -import { LogService } from '@ucap/ng-logger'; -import { I18nService } from '@ucap/ng-i18n'; - -@Component({ - selector: 'app-sections-group-search', - templateUrl: './search.section.component.html', - styleUrls: ['./search.section.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class SearchSectionComponent implements OnInit, OnDestroy { - @Output() - keyDownEnter = new EventEmitter<{ - companyCode: string; - searchWord: string; - }>(); - - @Output() - searchCancel = new EventEmitter(); - - constructor( - private logService: LogService, - private i18nService: I18nService - ) {} - - ngOnInit(): void {} - - ngOnDestroy(): void {} - - onKeyDownEnter(params: { companyCode: string; searchWord: string }) { - this.keyDownEnter.emit(params); - } - onClickCancel() { - this.searchCancel.emit(); - } -} diff --git a/src/app/sections/group/components/select-group.section.component.html b/src/app/sections/group/components/select-group.section.component.html index 69d8753..fa0eb47 100644 --- a/src/app/sections/group/components/select-group.section.component.html +++ b/src/app/sections/group/components/select-group.section.component.html @@ -1,70 +1,85 @@ -
    -
    - -
    - - +
    + + + + +
    +
    +
    +
    + + {{ + 'group:label.addNewGroup' | ucapI18n + }} +
    +
    + + +
    +
    - - -
    -
    - +
    + {{ 'group:label.existingGroup' | ucapI18n }} + - - {{ input.value?.length || 0 }}/20 - - - - - - - - - - - -
    - 기존 그룹 지정 -
    -
    -
    - {{ group.name }} -
    - -
    + +
    +
    +
    +
    + {{ group.name }} +
    +
    -
    - +
    + +
    +
    + {{ 'group:label.searchResult' | ucapI18n }} + {{ searchUserInfos.length }} + + +
    +
    + + +
    +
    diff --git a/src/app/sections/group/components/select-group.section.component.scss b/src/app/sections/group/components/select-group.section.component.scss index 8a387b9..7135881 100644 --- a/src/app/sections/group/components/select-group.section.component.scss +++ b/src/app/sections/group/components/select-group.section.component.scss @@ -1,2 +1,79 @@ -.profile-container { +.ucap-dialog-select-group-container { + .ucap-dialog-select-group-contnet { + display: flex; + flex-direction: column; + height: calc(100% - 50px); + .ucap-dialog-app-group-name-input { + display: flex; + flex-direction: row; + width: 100%; + height: 60px; + margin-bottom: 20px; + .new-group-add { + display: inline-flex; + align-items: center; + width: 100%; + .sub-title { + padding: 0 10px 0 0; + display: inline-flex; + align-items: center; + mat-icon { + margin-right: 4px; + } + } + } + .group-name-input { + flex: 1 1 auto; + } + } + .ucap-dialog-group-list-container { + overflow: hidden; + .sub-title { + height: 40px; + display: flex; + flex-direction: row; + align-items: center; + border-bottom: 1px solid #999999; + mat-checkbox { + margin-left: auto; + padding: 0 16px 0 10px; + } + } + .group-list-container { + height: calc(100% - 40px); + overflow-y: auto; + &-scrollbar { + width: 100%; + height: 100%; + } + .group-list-item { + width: 100%; + height: 50px; + display: flex; + flex-direction: row; + align-items: center; + border-bottom: 1px solid #ccc; + .group-name { + padding-left: 16px; + display: block; + overflow: hidden; + text-overflow: ellipsis; + -webkit-box-orient: vertical; + white-space: nowrap; + width: calc(100% - 44px); + } + .checkbox-area { + display: inline-flex; + margin-left: auto; + padding: 0 16px 0 10px; + } + } + } + } + .sub-title { + font-size: 14px; + font-weight: bold; + color: #474244; + } + } } diff --git a/src/app/sections/group/components/select-group.section.component.ts b/src/app/sections/group/components/select-group.section.component.ts index c85021e..3f15902 100644 --- a/src/app/sections/group/components/select-group.section.component.ts +++ b/src/app/sections/group/components/select-group.section.component.ts @@ -1,43 +1,38 @@ +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + import { Component, OnInit, OnDestroy, + AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Input, Output, - EventEmitter + EventEmitter, + ViewChild } from '@angular/core'; -import { Subject, of } from 'rxjs'; -import { Store, select } from '@ngrx/store'; -import { takeUntil, take, map, catchError } from 'rxjs/operators'; -import { - LoginSelector, - AuthorizationSelector -} from '@ucap/ng-store-authentication'; -import { LoginResponse } from '@ucap/protocol-authentication'; -import { QueryProtocolService } from '@ucap/ng-protocol-query'; -import { - UserInfoSS, - AuthResponse, - DeptSearchType, - UserInfoF, - UserInfoDN -} from '@ucap/protocol-query'; -import { GroupSelector } from '@ucap/ng-store-group'; -import { GroupDetailData, UserInfo } from '@ucap/protocol-sync'; -import { PresenceActions } from '@ucap/ng-store-organization'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; -import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; import { MatDialog } from '@angular/material/dialog'; -import { - AlertDialogComponent, - AlertDialogData, - AlertDialogResult -} from '@ucap/ng-ui'; -import { MatCheckbox } from '@angular/material/checkbox'; +import { MatCheckbox, MatCheckboxChange } from '@angular/material/checkbox'; + +import { Store, select } from '@ngrx/store'; + +import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; +import { GroupDetailData, UserInfo } from '@ucap/protocol-sync'; +import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; + +import { I18nService } from '@ucap/ng-i18n'; + +import { GroupSelector } from '@ucap/ng-store-group'; + import { SearchData } from '@app/ucap/organization/models/search-data'; +import { ProfileListComponent as AppGroupProfileListComponent } from '@app/ucap/group/components/profile-list.component'; +import { + PerfectScrollbarDirective, + PerfectScrollbarComponent +} from 'ngx-perfect-scrollbar'; export type UserInfoTypes = | UserInfo @@ -53,7 +48,8 @@ export type UserInfoTypes = changeDetection: ChangeDetectionStrategy.OnPush }) -export class SelectGroupSectionComponent implements OnInit, OnDestroy { +export class SelectGroupSectionComponent + implements OnInit, OnDestroy, AfterViewInit { @Input() isMemberMove: boolean; @@ -63,23 +59,69 @@ export class SelectGroupSectionComponent implements OnInit, OnDestroy { @Input() checkable = false; + @Input() + searchable = true; + @Input() curGroup: GroupDetailData; + @Input() + set selectedUserList(userList: UserInfoTypes[]) { + this._selectedUserList = userList; + + this._allCheckedBySearchUser(); + } + get selectedUserList() { + return this._selectedUserList; + } + // tslint:disable-next-line: variable-name + _selectedUserList: UserInfoTypes[]; + + @Input() + selectedGroupList: GroupDetailData[] = []; + + @Input() + groupName: string; + @Output() - changeUserList: EventEmitter<{ + changeUserList: EventEmitter< + { + checked: boolean; + userInfo: UserInfoSS; + }[] + > = new EventEmitter(); + + @Output() + changeGroupList: EventEmitter<{ checked: boolean; - userInfo: UserInfoSS; + group: GroupDetailData; }> = new EventEmitter(); @Output() - changeGroupList: EventEmitter = new EventEmitter(); + changeGroupName: EventEmitter<{ + invalid: boolean; + groupName: string; + }> = new EventEmitter(); - @Output() - changeGroupName: EventEmitter = new EventEmitter(); + @ViewChild('groupProfileList', { static: false }) + groupProfileList: AppGroupProfileListComponent; + + @ViewChild('checkboxAllGroup', { static: false }) + checkboxAllGroup: MatCheckbox; + + @ViewChild('checkboxAllSearch', { static: false }) + checkboxAllSearch: MatCheckbox; + + @ViewChild(PerfectScrollbarDirective, { static: false }) + psDirectiveRef: PerfectScrollbarDirective; set companySearchData(searchData: SearchData) { - this._companySearchData = searchData; + if (!!searchData && searchData.searchWord !== '') { + this._companySearchData = { ...searchData, bySearch: true }; + } else { + this._companySearchData = { ...searchData, bySearch: false }; + } + this.onChangedCompanySearch(); } get companySearchData() { @@ -88,139 +130,182 @@ export class SelectGroupSectionComponent implements OnInit, OnDestroy { // tslint:disable-next-line: variable-name _companySearchData: SearchData; - private ngOnDestroySubject = new Subject(); + private ngOnDestroySubject: Subject = new Subject(); constructor( private store: Store, - private queryProtocolService: QueryProtocolService, - private changeDetectorRef: ChangeDetectorRef, - private formBuilder: FormBuilder, - public dialog: MatDialog + private dialog: MatDialog, + private i18nService: I18nService ) {} - loginRes: LoginResponse; isSearch = false; searchWord: string; searchUserInfos: UserInfoSS[] = []; groupList: GroupDetailData[]; - selectedUserList: UserInfoTypes[] = []; - selectedGroupList: GroupDetailData[] = []; - inputForm: FormGroup; - groupChecked = false; - groupName: string; + groupChecked = false; ngOnInit(): void { - this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; - }); - this.store .pipe(takeUntil(this.ngOnDestroySubject), select(GroupSelector.groups)) .subscribe((groups) => { - this.groupList = groups; + let defaultGroup: GroupDetailData; + const buddyGroup: GroupDetailData[] = []; + const tempOrder: GroupDetailData[] = []; + + groups.forEach((group) => { + if (0 === group.seq) { + defaultGroup = group; + } else { + if (!!this.curGroup && this.curGroup.seq === group.seq) { + // ignore.. + } else { + buddyGroup.push(group); + } + } + }); + + tempOrder.push( + ...buddyGroup.sort((a, b) => + a.name < b.name ? -1 : a.name > b.name ? 1 : 0 + ) + ); + + if (!!defaultGroup) { + tempOrder.push(defaultGroup); + } + + this.groupList = tempOrder; }); - this.inputForm = this.formBuilder.group({ - groupName: [ - this.groupName, - [ - // Validators.required - // StringUtil.includes(, CharactorType.Special), - // this.checkBanWords(), - // this.checkSameName() - ] - ] - }); + this._allCheckedBySearchUser(); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } - onKeyupGroupName() { - this.inputForm.get('groupName').markAsTouched(); - this.changeGroupName.emit(this.inputForm.get('groupName').value); + ngAfterViewInit() { + if (!!this.psDirectiveRef) { + this.psDirectiveRef.update(); + } } - onCheckForGroup(checbox: MatCheckbox, group: GroupDetailData) { - if ( - this.isMemberMove && - !!this.selectedGroupList && - this.selectedGroupList.length > 0 && - this.selectedGroupList[0].seq !== group.seq - ) { - this.dialog.open< - AlertDialogComponent, - AlertDialogData, - AlertDialogResult - >(AlertDialogComponent, { - data: { - title: '멤버이동', - html: '멤버이동은 그룹 여러개를 선택할 수 없습니다.' - } - }); + onCheckForGroup(checkbox: MatCheckbox, group: GroupDetailData) { + const findGroup = this.selectedGroupList.filter( + (selectGroup) => selectGroup.seq === group.seq + ); - checbox.checked = false; - return; - } - if ( - this.selectedGroupList.filter((g) => g.seq === group.seq).length === 0 - ) { - this.selectedGroupList = [...this.selectedGroupList, group]; - } else { + if (!!findGroup && findGroup.length > 0 && !checkbox.checked) { this.selectedGroupList = this.selectedGroupList.filter( - (g) => g.seq !== group.seq + (u) => u.seq !== findGroup[0].seq ); + } else if (!!findGroup && findGroup.length === 0 && checkbox.checked) { + this.selectedGroupList = [...this.selectedGroupList, group]; } - this.changeGroupList.emit(this.selectedGroupList); + if (this.selectedGroupList.length === this.groupList.length) { + this.checkboxAllGroup.checked = true; + } else { + this.checkboxAllGroup.checked = false; + } + this.changeGroupList.emit({ checked: checkbox.checked, group }); } - // onCheckForUser(params: { isChecked: boolean; userInfo: UserInfoTypes }) { - // if ( - // this.selectedUserList.filter((user) => user.seq === params.userInfo.seq) - // .length === 0 - // ) { - // this.selectedUserList = [...this.selectedUserList, params.userInfo]; - // } else { - // this.selectedUserList = this.selectedUserList.filter( - // (item) => item.seq !== params.userInfo.seq - // ); - // } + onToggleCheck(datas: { checked: boolean; userInfo: UserInfoSS }[]) { + this.changeUserList.emit(datas); + } - // this.changeUserList.emit(this.selectedUserList); - // } - - onToggleCheck(data: { checked: boolean; userInfo: UserInfoSS }) { - this.changeUserList.emit(data); + onSearched(users: UserInfoSS[]) { + this.searchUserInfos = users; + this._allCheckedBySearchUser(); } onChangedCompanySearch() { - this.isSearch = true; + if (!!this.companySearchData && this.companySearchData.bySearch) { + this.isSearch = true; + } } onCanceled() { this.isSearch = false; + this.companySearchData = { ...this.companySearchData, searchWord: '' }; } - getCheckedUser(userInfo: UserInfoTypes) { - if (!!this.selectedUserList && this.selectedUserList.length > 0) { + /** 개별 그룹 체크여부 */ + getCheckedGroup(group: GroupDetailData) { + if (!!this.selectedGroupList && this.selectedGroupList.length > 0) { return ( - this.selectedUserList.filter((item) => item.seq === userInfo.seq) - .length > 0 + this.selectedGroupList.filter((item) => item.seq === group.seq).length > + 0 ); } + return false; } + checkVisible(group: GroupDetailData): boolean { if (!!this.curGroup && this.curGroup.seq === group.seq) { return false; } return true; } + + onChangeGroupName(data: { invalid: boolean; groupName: string }) { + this.groupName = data.groupName; + this.changeGroupName.emit(data); + } + + onAllCheckedGroup(event: MatCheckboxChange) { + if (event.checked) { + this.groupList.map((group) => { + this.selectedGroupList.push(group); + this.changeGroupList.emit({ checked: true, group }); + }); + } else { + this.selectedGroupList = []; + this.groupList.map((group) => + this.changeGroupList.emit({ checked: false, group }) + ); + } + } + onAllCheckedSearch(event: MatCheckboxChange) { + if (event.checked) { + this.groupProfileList.checkAll(); + } else { + this.groupProfileList.uncheckAll(); + } + } + + private _allCheckedBySearchUser() { + if ( + !!this.searchUserInfos && + this.searchUserInfos.length > 0 && + !!this.selectedUserList && + this.selectedUserList.length > 0 + ) { + const tempList = []; + + this.searchUserInfos.map((searchUser) => { + this.selectedUserList.every((selectUser) => { + if (searchUser.seq === selectUser.seq) { + tempList.push(searchUser); + return false; + } + return true; + }); + }); + + if (tempList.length === this.searchUserInfos.length) { + this.checkboxAllSearch.checked = true; + } else { + this.checkboxAllSearch.checked = false; + } + } else if (!!this.checkboxAllSearch) { + this.checkboxAllSearch.checked = false; + } + } } diff --git a/src/app/sections/group/components/select-user.section.component.html b/src/app/sections/group/components/select-user.section.component.html index 0cf34fb..3a95b9e 100644 --- a/src/app/sections/group/components/select-user.section.component.html +++ b/src/app/sections/group/components/select-user.section.component.html @@ -1,73 +1,99 @@
    -
    +
    -
    + -
    - +
    + - -

    그룹

    +

    + {{ 'group:label.group' | ucapI18n }} +

    -
    -
    - +
    + + class="select-user-tap-group-expansion" + >
    -
    + - -

    조직도

    +

    + {{ 'group:label.organization' | ucapI18n }} +

    -
    - -
    - - - > - + +
    + +
    -
    - -
    -
    +
    -
    - + +
    +
    + {{ 'group:label.searchResult' | ucapI18n }} + {{ searchedUserInfosLength }} + + +
    +
    + + + +
    +
    diff --git a/src/app/sections/group/components/select-user.section.component.scss b/src/app/sections/group/components/select-user.section.component.scss index 3d4537a..37ef5b2 100644 --- a/src/app/sections/group/components/select-user.section.component.scss +++ b/src/app/sections/group/components/select-user.section.component.scss @@ -1,12 +1,22 @@ -.container { +.select-user-section-container { overflow: hidden; - .tap-container { - height: 300px; + + .select-user-section-search { + padding: 0 0 10px; + } + .select-user-section-content { + width: 100%; + height: calc(100% - 50px); + + .tap-container { + height: 100%; + } } .organization-tree { width: 100%; height: calc(100% - 30px); padding-bottom: 10px; + display: none; } } diff --git a/src/app/sections/group/components/select-user.section.component.ts b/src/app/sections/group/components/select-user.section.component.ts index 877c80c..f0fb0fc 100644 --- a/src/app/sections/group/components/select-user.section.component.ts +++ b/src/app/sections/group/components/select-user.section.component.ts @@ -1,40 +1,30 @@ +import { Subject } from 'rxjs'; + import { Component, OnInit, OnDestroy, + AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Output, EventEmitter, - Input + Input, + ViewChild } from '@angular/core'; -import { Store, select } from '@ngrx/store'; -import { takeUntil, map, catchError } from 'rxjs/operators'; +import { MatCheckboxChange } from '@angular/material/checkbox'; -import { Subject, combineLatest, of } from 'rxjs'; -import { AppAuthenticationService } from '@app/services/app-authentication.service'; -import { SelectUserDialogType } from '@app/types'; -import { QueryProtocolService } from '@ucap/ng-protocol-query'; +import { Store } from '@ngrx/store'; import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; -import { - UserInfoSS, - UserInfoF, - UserInfoDN, - DeptSearchType -} from '@ucap/protocol-query'; -import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; -import { LoginResponse } from '@ucap/protocol-authentication'; -import { LoginSelector } from '@ucap/ng-store-authentication'; -import { SearchData } from '@app/ucap/organization/models/search-data'; +import { DeptInfo, UserInfoSS } from '@ucap/protocol-query'; -export type UserInfoTypes = - | UserInfo - | UserInfoSS - | UserInfoF - | UserInfoDN - | RoomUserInfo; +import { SearchData } from '@app/ucap/organization/models/search-data'; +import { ProfileListComponent as AppGroupProfileListComponent } from '@app/ucap/group/components/profile-list.component'; +import { SelectUserDialogType, UserInfoTypes } from '@app/types'; +import { ProfileNavigationListComponent } from '@app/ucap/organization/components/profile-navigation-list.component'; +import { Expansion02Component as AppExpansion02Component } from '@app/ucap/group/components/expansion-02.component'; @Component({ selector: 'app-group-select-user', @@ -42,36 +32,33 @@ export type UserInfoTypes = styleUrls: ['./select-user.section.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class SelectUserSectionComponent implements OnInit, OnDestroy { - breadcrumbs: any = [ - { - label: 'LGCNS' - }, - { - label: 'IT Helpdesk' - }, - { - label: 'Issue Log' - } - ]; - +export class SelectUserSectionComponent + implements OnInit, OnDestroy, AfterViewInit { @Input() isDialog = false; - @Input() - existUsers: UserInfoTypes[]; - - @Input() - isSelectionOff = true; - @Input() checkable = false; + @Input() + selectedUserList: UserInfoTypes[]; + + @Input() + fixedUserList: UserInfoTypes[]; + + @Input() + displayRootDepartment = false; + + @Input() + selectedGroupHeader: GroupDetailData; + @Output() - toggleCheckUser: EventEmitter<{ - checked: boolean; - userInfo: UserInfoSS; - }> = new EventEmitter(); + toggleCheckUser: EventEmitter< + { + checked: boolean; + userInfo: UserInfoTypes; + }[] + > = new EventEmitter(); @Output() toggleCheckGroup: EventEmitter<{ @@ -79,14 +66,21 @@ export class SelectUserSectionComponent implements OnInit, OnDestroy { groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; }> = new EventEmitter(); - @Output() - cancel = new EventEmitter(); + @ViewChild('groupDialogExpasion', { static: false }) + groupDialogExpasion: AppExpansion02Component; - @Output() - confirm = new EventEmitter(); + @ViewChild('groupProfileList', { static: false }) + groupProfileList: AppGroupProfileListComponent; + + @ViewChild('profileNavigationList', { static: false }) + profileNavigationList: ProfileNavigationListComponent; set companySearchData(searchData: SearchData) { - this._companySearchData = searchData; + if (!!searchData && searchData.searchWord !== '') { + this._companySearchData = { ...searchData, bySearch: true }; + } else { + this._companySearchData = { ...searchData, bySearch: false }; + } this.onChangedCompanySearch(); } @@ -97,41 +91,47 @@ export class SelectUserSectionComponent implements OnInit, OnDestroy { _companySearchData: SearchData; isSearch = false; - searchWord: string; - private ngOnDestroySubject = new Subject(); + isAllCheck: boolean; - @Input() - selectedUserList: UserInfoTypes[] = []; - searchUserInfos: UserInfoSS[] = []; + searchedUserInfos: UserInfoTypes[] = []; + searchedUserInfosLength: number; - loginRes: LoginResponse; + breadcrumbs: DeptInfo[]; + departmentInfoList: DeptInfo[] = []; + sameLevelDeptInfoList: DeptInfo[] = []; + memberSearchData: SearchData; SelectUserDialogType = SelectUserDialogType; + private ngOnDestroySubject: Subject = new Subject(); + constructor( private changeDetectorRef: ChangeDetectorRef, - private store: Store, - private appAuthenticationService: AppAuthenticationService, - private queryProtocolService: QueryProtocolService + private store: Store ) {} ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - - this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; - }); + if (!!this.selectedUserList && this.selectedUserList.length > 0) { + this.selectedUserList.map((user: UserInfoTypes) => + this.onToggleCheckUser([{ checked: true, userInfo: user }]) + ); + } } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } + ngAfterViewInit(): void {} + + psUpdate() { + this.groupDialogExpasion.psUpdate(); + } + onToggleCheckGroup(params: { isChecked: boolean; groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; @@ -139,25 +139,66 @@ export class SelectUserSectionComponent implements OnInit, OnDestroy { this.toggleCheckGroup.emit(params); } - onToggleCheckUser(data: { checked: boolean; userInfo: UserInfoSS }) { - this.toggleCheckUser.emit(data); - } - - getCheckedUser(userInfo: UserInfoTypes) { - if (!!this.selectedUserList && this.selectedUserList.length > 0) { - return ( - this.selectedUserList.filter((item) => item.seq === userInfo.seq) - .length > 0 - ); - } - return false; + onToggleCheckUser(datas: { checked: boolean; userInfo: UserInfoTypes }[]) { + this.toggleCheckUser.emit(datas); } onChangedCompanySearch() { - this.isSearch = true; + if (!!this.companySearchData && this.companySearchData.bySearch) { + this.isSearch = true; + } + this.changeDetectorRef.markForCheck(); } onCanceled() { this.isSearch = false; + this.companySearchData = { ...this.companySearchData, searchWord: '' }; + } + + onIsAllCheck(checked: boolean): void { + this.isAllCheck = checked; + this.changeDetectorRef.detectChanges(); + } + + onSearched(userInfo: UserInfoSS[]) { + this.searchedUserInfosLength = userInfo.length; + } + getAllCheckedItem(): boolean { + if (!!this.groupProfileList) { + const targetList = this.groupProfileList.userInfos; + if ( + !targetList || + targetList.length === 0 || + targetList.filter( + (item) => + !( + this.selectedUserList.filter((info) => info.seq === item.seq) + .length > 0 + ) + ).length > 0 + ) { + return false; + } else { + return true; + } + } + + return false; + } + + onAllChecked(event: MatCheckboxChange): void { + if (event.checked) { + this.groupProfileList.checkAll(); + } else { + this.groupProfileList.uncheckAll(); + } + } + + onChangedCheckProfileList( + datas: { checked: boolean; userInfo: UserInfoTypes }[] + ) { + if (!datas || 0 === datas.length) { + return; + } } } diff --git a/src/app/sections/group/dialogs/create.dialog.component.html b/src/app/sections/group/dialogs/create.dialog.component.html index f3b7f4f..d78d2ac 100644 --- a/src/app/sections/group/dialogs/create.dialog.component.html +++ b/src/app/sections/group/dialogs/create.dialog.component.html @@ -2,78 +2,95 @@
    - 그룹 생성 + {{ 'group:dialog.title.createGroup' | ucapI18n }} +
    +
    + {{ 'group:dialog.title.subTitleGroupInfo' | ucapI18n }} +
    +
    + {{ 'group:dialog.title.subTitleSelectMember' | ucapI18n }} +
    +
    +
    -
    - - - {{ input.value?.length || 0 }}/20 - - - - - - - - - -
    + +
    - - + +
    + +
    +
    + + +

    + {{ 'group:dialog.selectedMember' | ucapI18n }} +

    + ({{ selectedUserList.length }}) +
    +
    +
    - -
    - - -
    -
    - - +
    diff --git a/src/app/sections/group/dialogs/create.dialog.component.scss b/src/app/sections/group/dialogs/create.dialog.component.scss index cccbc78..b783a8a 100644 --- a/src/app/sections/group/dialogs/create.dialog.component.scss +++ b/src/app/sections/group/dialogs/create.dialog.component.scss @@ -1,3 +1,4 @@ +@import '~@ucap/lg-scss/mixins'; .dialog-container { width: 100%; height: 100%; @@ -5,5 +6,44 @@ .dialog-body { width: 100%; height: 100%; + .ucap-dialog-app-group-select-user { + width: 60%; + border-bottom: 1px solid #cccccc; + background-color: #ffffff; + @include screen(xs) { + width: 100%; + height: 78%; + margin-bottom: 2%; + } + } + .ucap-dialog-organization-profile-selection { + position: relative; + width: 40%; + padding-left: $default-space; + border-bottom: 1px solid #cccccc; + background-color: #ffffff; + @include screen(xs) { + width: 100%; + height: 20%; + padding: 0; + } + .ucap-organization-selected-list { + width: 100%; + height: 100%; + display: flex; + border: 1px solid #cccccc; + border-bottom: none; + overflow: auto; + } + } + } + .btn-box { + display: flex; + flex-direction: row; + justify-content: flex-end; + button { + margin-left: 4px; + border-radius: 3px; + } } } diff --git a/src/app/sections/group/dialogs/create.dialog.component.ts b/src/app/sections/group/dialogs/create.dialog.component.ts index bbcce68..bbd3ebf 100644 --- a/src/app/sections/group/dialogs/create.dialog.component.ts +++ b/src/app/sections/group/dialogs/create.dialog.component.ts @@ -7,10 +7,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Inject, - Input + Input, + ViewChild } from '@angular/core'; -import { Store } from '@ngrx/store'; +import { Store, select } from '@ngrx/store'; import { MatDialogRef, @@ -24,12 +25,16 @@ import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; import { MatStepper } from '@angular/material/stepper'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { I18nService } from '@ucap/ng-i18n'; -import { GroupActions } from '@ucap/ng-store-group'; +import { GroupActions, GroupSelector } from '@ucap/ng-store-group'; import { AlertDialogComponent, AlertDialogData, AlertDialogResult } from '@ucap/ng-ui'; +import { takeUntil } from 'rxjs/operators'; +import { AppGroupService } from '@app/services/app-group.service'; + +import { SelectUserSectionComponent } from '../components/select-user.section.component'; export type UserInfoTypes = | UserInfo @@ -50,14 +55,8 @@ export interface CreateDialogResult {} changeDetection: ChangeDetectionStrategy.OnPush }) export class CreateDialogComponent implements OnInit, OnDestroy { - @Input() - set groupName(g: string) { - this._groupName = g; - } - get groupName(): string { - return this._groupName; - } - _groupName: string; + @ViewChild('groupSelectUser', { static: false }) + groupSelectUser: SelectUserSectionComponent; constructor( public dialogRef: MatDialogRef, @@ -66,33 +65,31 @@ export class CreateDialogComponent implements OnInit, OnDestroy { private store: Store, private formBuilder: FormBuilder, private i18nService: I18nService, - public dialog: MatDialog + public dialog: MatDialog, + private appGroupService: AppGroupService ) {} private ngOnDestroySubject: Subject; + private groupName: string; + private defaultGroup: GroupDetailData; + currentStep = 0; - inputForm: FormGroup; + isDisable = true; selectedUserList: UserInfoTypes[] = []; ngOnInit(): void { this.ngOnDestroySubject = new Subject(); - - this.inputForm = this.formBuilder.group({ - groupName: [ - this.groupName, - [ - Validators.required - // StringUtil.includes(, CharactorType.Special), - // this.checkBanWords(), - // this.checkSameName() - ] - ] - }); + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(GroupSelector.groups)) + .subscribe((groups) => { + this.defaultGroup = groups.filter((group) => group.seq === 0)[0]; + }); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } @@ -110,61 +107,136 @@ export class CreateDialogComponent implements OnInit, OnDestroy { this.dialogRef.close(); } onConfirm(stepper: MatStepper) { - const userSeqs: string[] = []; + let contents: string; + let isDefaultGroup = false; - this.selectedUserList.map((user) => userSeqs.push(user.seq.toString())); - const groupName = this.inputForm.get('groupName').value as string; + if (this.currentStep === 0) { + if ( + !this.groupName || + this.groupName.localeCompare('') === 0 || + this.groupName.trim().localeCompare('') === 0 + ) { + contents = this.i18nService.t('group:error.requireGroupName'); + } else if (this.isDisable) { + contents = this.i18nService.t('group:error.invalidGroupName'); + } + } else { + if ( + !this.groupName || + this.groupName.localeCompare('') === 0 || + this.groupName.trim().localeCompare('') === 0 + ) { + isDefaultGroup = true; + } + if (!!this.selectedUserList && this.selectedUserList.length === 0) { + contents = this.i18nService.t('group:error.notSelectedUser'); + } + } - if (!groupName || groupName.localeCompare('') === 0) { + if (!!contents && contents.localeCompare('') !== 0) { this.dialog.open< AlertDialogComponent, AlertDialogData, AlertDialogResult >(AlertDialogComponent, { + panelClass: 'min-create-dialog', data: { - title: this.i18nService.t('moreMenu.error.label'), - html: this.i18nService.t('moreMenu.error.requireName') + title: this.i18nService.t('group:error.title.default'), + html: contents } }); return; } - this.store.dispatch( - GroupActions.create({ - groupName, - targetUserSeqs: userSeqs - }) - ); + const userSeqs: string[] = []; + this.selectedUserList.map((user) => userSeqs.push(String(user.seq))); + + if (!isDefaultGroup) { + this.appGroupService.createGroup(this.groupName, this.selectedUserList); + } else { + this.appGroupService.addMemberToGroup( + this.defaultGroup, + this.selectedUserList + ); + } this.dialogRef.close(); } onCompleteConfirm(stepper: MatStepper) { this.currentStep++; stepper.next(); + this.groupSelectUser.psUpdate(); } - onKeyupGroupName() { - this.inputForm.get('groupName').markAsTouched(); + checkDisableConfirmBtn() { + if (!!this.groupName && this.groupName.trim().localeCompare('') === 0) { + return true; + } else if ( + this.isDisable && + !!this.groupName && + this.groupName.trim().localeCompare('') !== 0 + ) { + return true; + } + + return false; } + onChangeUserList(datas: { checked: boolean; userInfo: UserInfoSS }[]) { + if (!datas || 0 === datas.length) { + return; + } - onChangeUserList(data: { checked: boolean; userInfo: UserInfoSS }) { - const i = this.selectedUserList.findIndex( - (u) => u.seq === data.userInfo.seq - ); + const pushs: UserInfoSS[] = []; + const pops: UserInfoSS[] = []; - if (data.checked) { - if (-1 === i) { - this.selectedUserList = [...this.selectedUserList, data.userInfo]; - } - } else { - if (-1 < i) { - this.selectedUserList = this.selectedUserList.filter( - (u) => u.seq !== data.userInfo.seq - ); + datas.forEach((d) => { + const i = this.selectedUserList.findIndex( + (u) => String(u.seq) === String(d.userInfo.seq) + ); + if (d.checked) { + if (-1 === i) { + pushs.push(d.userInfo); + } + } else { + if (-1 < i) { + pops.push(d.userInfo); + } } + }); + + if (0 < pushs.length) { + this.selectedUserList = [...this.selectedUserList, ...pushs]; + } + + if (0 < pops.length) { + this.selectedUserList = this.selectedUserList.filter( + (u) => -1 === pops.findIndex((p) => String(p.seq) === String(u.seq)) + ); } } + // onChangeUserList(data: { checked: boolean; userInfo: UserInfoSS }) { + // const i = this.selectedUserList.findIndex( + // (u) => u.seq === data.userInfo.seq + // ); + + // if (data.checked) { + // if (-1 === i) { + // this.selectedUserList = [...this.selectedUserList, data.userInfo]; + // } + // } else { + // if (-1 < i) { + // this.selectedUserList = this.selectedUserList.filter( + // (u) => u.seq !== data.userInfo.seq + // ); + // } + // } + // } + + onChangeGroupName(data: { invalid: boolean; groupName: string }) { + this.isDisable = data.invalid; + this.groupName = data.groupName; + } onChangeGroupList(params: { isChecked: boolean; groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; @@ -172,8 +244,9 @@ export class CreateDialogComponent implements OnInit, OnDestroy { if (params.isChecked) { params.groupBuddyList.buddyList.forEach((item) => { if ( - this.selectedUserList.filter((user) => user.seq === item.seq) - .length === 0 + this.selectedUserList.filter( + (user) => String(user.seq) === String(item.seq) + ).length === 0 ) { this.selectedUserList = [...this.selectedUserList, item]; } @@ -181,20 +254,21 @@ export class CreateDialogComponent implements OnInit, OnDestroy { } else { this.selectedUserList = this.selectedUserList.filter( (item) => - params.groupBuddyList.buddyList.filter((del) => del.seq === item.seq) - .length === 0 + params.groupBuddyList.buddyList.filter( + (del) => String(del.seq) === String(item.seq) + ).length === 0 ); } } onRemovedProfileSelection(userInfo: UserInfo) { const i = this.selectedUserList.findIndex( - (u) => (u.seq as any) === (userInfo.seq as any) + (u) => String(u.seq) === String(userInfo.seq) ); if (-1 < i) { this.selectedUserList = this.selectedUserList.filter( - (u) => (u.seq as any) !== (userInfo.seq as any) + (u) => String(u.seq) !== String(userInfo.seq) ); } } diff --git a/src/app/sections/group/dialogs/edit-inline-input.dialog.component.html b/src/app/sections/group/dialogs/edit-inline-input.dialog.component.html index e657d53..bbfab18 100644 --- a/src/app/sections/group/dialogs/edit-inline-input.dialog.component.html +++ b/src/app/sections/group/dialogs/edit-inline-input.dialog.component.html @@ -13,8 +13,14 @@ #inlineInput type="text" [value]="data.curValue" + [maxLength]="data.maxLength" [placeholder]="data.placeholder" (click)="$event.stopPropagation()" - /> + (keydown.enter)="$event.stopPropagation(); onApply(inlineInput.value)" + /> + {{ inlineInput.value?.length || 0 }}/{{ data.maxLength }} +
    diff --git a/src/app/sections/group/dialogs/edit-inline-input.dialog.component.scss b/src/app/sections/group/dialogs/edit-inline-input.dialog.component.scss index 42e0cbf..d31c34a 100644 --- a/src/app/sections/group/dialogs/edit-inline-input.dialog.component.scss +++ b/src/app/sections/group/dialogs/edit-inline-input.dialog.component.scss @@ -2,13 +2,19 @@ width: 100%; height: 100%; overflow: hidden; + border-bottom: 1px solid #999; + padding-left: 10px; .ng-star-inserted { - float: left; + flex-grow: 1; } mat-dialog-container { padding: 0px !important; background: transparent !important; } - .ucap-edit-group-name-dialog { + .form-eidt { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; } } diff --git a/src/app/sections/group/dialogs/edit-inline-input.dialog.component.ts b/src/app/sections/group/dialogs/edit-inline-input.dialog.component.ts index 0581b16..63308f7 100644 --- a/src/app/sections/group/dialogs/edit-inline-input.dialog.component.ts +++ b/src/app/sections/group/dialogs/edit-inline-input.dialog.component.ts @@ -8,7 +8,7 @@ import { Inject } from '@angular/core'; -import { Store } from '@ngrx/store'; +import { Store, select } from '@ngrx/store'; import { MatDialogRef, @@ -24,12 +24,16 @@ import { AlertDialogData, AlertDialogResult } from '@ucap/ng-ui'; -import { take, map, catchError } from 'rxjs/operators'; +import { take, map, catchError, takeUntil } from 'rxjs/operators'; +import { AppService } from '@app/services/app.service'; +import { GroupSelector } from '@ucap/ng-store-group'; +import { GroupDetailData } from '@ucap/protocol-sync'; export interface EditInlineInputDialogData { - title: string; + type: string; curValue?: string; placeholder: string; + maxLength: number; left?: number; top?: number; } @@ -54,10 +58,12 @@ export class EditInlineInputDialogComponent implements OnInit, OnDestroy { private store: Store, private i18nService: I18nService, - public dialog: MatDialog + public dialog: MatDialog, + private appService: AppService ) {} private ngOnDestroySubject: Subject; + groupList: GroupDetailData[] = []; ngOnInit(): void { const matDialogConfig: MatDialogConfig = new MatDialogConfig(); @@ -70,10 +76,25 @@ export class EditInlineInputDialogComponent implements OnInit, OnDestroy { this.dialogRef.updatePosition(matDialogConfig.position); this.ngOnDestroySubject = new Subject(); + + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(GroupSelector.groups)) + .subscribe((groups) => { + this.groupList = groups; + }); + + if ( + !!this.data && + !!this.data.curValue && + this.data.curValue.trim() === '' + ) { + this.data.curValue = this.data.curValue.trim(); + } } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } @@ -83,23 +104,39 @@ export class EditInlineInputDialogComponent implements OnInit, OnDestroy { } onApply(inputValue: string) { + if (!!this.data && this.data.type.localeCompare('GROUP_NAME') === 0) { + const errMsg = this.appService.validateGroupName( + inputValue, + this.groupList + ); + + if (!!errMsg && errMsg !== '') { + return this._alertErr(errMsg); + } + } + this.doAction(true, inputValue); } doAction(choice: boolean, curValue?: string): void { if (!choice) { this.dialogRef.close({ choice: false }); + return; } - if (curValue.localeCompare('') === 0) { + this.dialogRef.close({ choice, curValue }); + } + + private _alertErr(msg: string) { + if (msg.localeCompare('') !== 0) { const dialogRef = this.dialog.open< AlertDialogComponent, AlertDialogData, AlertDialogResult >(AlertDialogComponent, { data: { - title: this.i18nService.t('moreMenu.error.label'), - html: this.i18nService.t('moreMenu.error.requireName') + title: this.i18nService.t('group:error.title.default'), + html: msg } }); dialogRef @@ -114,7 +151,5 @@ export class EditInlineInputDialogComponent implements OnInit, OnDestroy { .subscribe(); return; } - - this.dialogRef.close({ choice, curValue }); } } diff --git a/src/app/sections/group/dialogs/edit-user.dialog.component.html b/src/app/sections/group/dialogs/edit-user.dialog.component.html index bf356da..339ac64 100644 --- a/src/app/sections/group/dialogs/edit-user.dialog.component.html +++ b/src/app/sections/group/dialogs/edit-user.dialog.component.html @@ -2,34 +2,61 @@
    {{ data.title }}
    - -
    - - +
    +
    + +
    +
    + + +

    + {{ 'group:dialog.selectedMember' | ucapI18n }} +

    + ({{ selectedUserList.length }}) +
    +
    +
    -
    - - +
    diff --git a/src/app/sections/group/dialogs/edit-user.dialog.component.scss b/src/app/sections/group/dialogs/edit-user.dialog.component.scss index cccbc78..ce8549c 100644 --- a/src/app/sections/group/dialogs/edit-user.dialog.component.scss +++ b/src/app/sections/group/dialogs/edit-user.dialog.component.scss @@ -1,9 +1,59 @@ +@import '~@ucap/lg-scss/mixins'; .dialog-container { width: 100%; height: 100%; - .dialog-body { + .edit-user-dialog-container { width: 100%; height: 100%; + + .dialog-body { + width: 100%; + height: 100%; + + .edit-user-dialog-content { + display: flex; + flex-direction: row; + height: 100%; + @include screen(xs) { + flex-direction: column; + } + + .ucap-dialog-app-group-select-user { + width: 60%; + border-bottom: 1px solid #cccccc; + background-color: #ffffff; + @include screen(xs) { + width: 100%; + height: 78%; + margin-bottom: 2%; + } + .ucap-dialog-app-group-select-user-list { + width: 100%; + height: 100%; + } + } + .ucap-dialog-organization-profile-selection { + position: relative; + width: 40%; + padding-left: $default-space; + border-bottom: 1px solid #cccccc; + background-color: #ffffff; + @include screen(xs) { + width: 100%; + height: 20%; + padding: 0; + } + .ucap-organization-selected-list { + width: 100%; + height: 100%; + display: flex; + border: 1px solid #cccccc; + border-bottom: none; + overflow: auto; + } + } + } + } } } diff --git a/src/app/sections/group/dialogs/edit-user.dialog.component.ts b/src/app/sections/group/dialogs/edit-user.dialog.component.ts index cdfa0bb..e370a97 100644 --- a/src/app/sections/group/dialogs/edit-user.dialog.component.ts +++ b/src/app/sections/group/dialogs/edit-user.dialog.component.ts @@ -10,7 +10,7 @@ import { Input } from '@angular/core'; -import { Store } from '@ngrx/store'; +import { Store, select } from '@ngrx/store'; import { MatDialogRef, @@ -23,23 +23,20 @@ import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; import { I18nService } from '@ucap/ng-i18n'; -import { SelectUserDialogType, GroupUserDialaogType } from '@app/types'; - -export type UserInfoTypes = - | UserInfo - | UserInfoSS - | UserInfoF - | UserInfoDN - | RoomUserInfo; +import { SelectUserDialogType, GroupManageType } from '@app/types'; +import { UserInfoTypes } from '@app/types'; +import { AppService } from '@app/services/app.service'; +import { takeUntil } from 'rxjs/operators'; +import { GroupSelector } from '@ucap/ng-store-group'; export interface EditUserDialogData { title: string; - type: GroupUserDialaogType; - group: GroupDetailData; - userInfo: UserInfoTypes; + type: GroupManageType; + group?: GroupDetailData; + userInfos: UserInfoTypes[]; } export interface EditUserDialogResult { - type: GroupUserDialaogType; + type: GroupManageType; groupName: string; group: GroupDetailData; selelctUserList?: UserInfoTypes[]; @@ -56,28 +53,36 @@ export class EditUserDialogComponent implements OnInit, OnDestroy { constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: EditUserDialogData, + private appService: AppService, private changeDetectorRef: ChangeDetectorRef, private store: Store, private i18nService: I18nService, public dialog: MatDialog ) {} - private ngOnDestroySubject: Subject; + private ngOnDestroySubject: Subject = new Subject(); - currentType: GroupUserDialaogType; + currentManageType: GroupManageType; selectedUserList: UserInfoTypes[] = []; selectedGroupList: GroupDetailData[] = []; groupName = ''; SelectUserDialogType = SelectUserDialogType; - ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); + groupList: GroupDetailData[] = []; - this.selectedUserList.push(this.data.userInfo); - this.currentType = this.data.type; + ngOnInit(): void { + this.selectedUserList = [...this.data.userInfos]; + this.currentManageType = this.data.type; + + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(GroupSelector.groups)) + .subscribe((groups) => { + this.groupList = groups; + }); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } @@ -88,62 +93,133 @@ export class EditUserDialogComponent implements OnInit, OnDestroy { onConfirm() { if (!!this.groupName && this.groupName.trim().localeCompare('') !== 0) { - this.currentType = GroupUserDialaogType.Create; + this.currentManageType = GroupManageType.Create; } else { this.groupName = undefined; } this.dialogRef.close({ - type: this.currentType, + type: this.currentManageType, groupName: this.groupName, group: this.data.group, selelctUserList: this.selectedUserList, selectGroupList: this.selectedGroupList }); } - onChangeUserList(data: { checked: boolean; userInfo: UserInfoSS }) { - const i = this.selectedUserList.findIndex( - (u) => u.seq === data.userInfo.seq + + onChangeSelectedUserList( + datas: { + checked: boolean; + userInfo: UserInfoSS; + }[] + ) { + if (!datas || 0 === datas.length) { + return; + } + + const pushs: UserInfoSS[] = []; + const pops: UserInfoSS[] = []; + + datas.forEach((d) => { + const i = this.selectedUserList.findIndex( + (u) => u.seq === d.userInfo.seq + ); + if (d.checked) { + if (-1 === i) { + pushs.push(d.userInfo); + } + } else { + if (-1 < i) { + pops.push(d.userInfo); + } + } + }); + + if (0 < pushs.length) { + this.selectedUserList = [...this.selectedUserList, ...pushs]; + } + + if (0 < pops.length) { + this.selectedUserList = this.selectedUserList.filter( + (u) => -1 === pops.findIndex((p) => p.seq === u.seq) + ); + } + } + + onChangeGroupList(data: { checked: boolean; group: GroupDetailData }) { + const i = this.selectedGroupList.findIndex( + (u) => String(u.seq) === String(data.group.seq) ); if (data.checked) { if (-1 === i) { - this.selectedUserList = [...this.selectedUserList, data.userInfo]; + this.selectedGroupList = [...this.selectedGroupList, data.group]; } } else { if (-1 < i) { - this.selectedUserList = this.selectedUserList.filter( - (u) => u.seq !== data.userInfo.seq + this.selectedGroupList = this.selectedGroupList.filter( + (u) => String(u.seq) !== String(data.group.seq) ); } } } - onChangeGroupList(selectedGroupList: GroupDetailData[]) { - this.selectedGroupList = selectedGroupList; + onChangeGroupName(data: { invalid: boolean; groupName: string }) { + this.groupName = data.groupName; } - onChangeGroupName(name: string) { - this.groupName = name; + checkDisableBtn(): boolean { + const errMsg = this.appService.validateGroupName( + this.groupName, + this.groupList + ); + + if ( + !!errMsg && + this.groupName === '' && + !!this.selectedGroupList && + this.selectedGroupList.length > 0 + ) { + return false; + } else if ( + !!errMsg && + !!this.selectedGroupList && + this.selectedGroupList.length === 0 + ) { + return true; + } else if (!!errMsg && this.groupName !== '') { + return true; + } + + return false; } onRemovedProfileSelection(userInfo: UserInfo) { + // if (userInfo.seq === this.data.userInfo.seq) { + // return; + // } const i = this.selectedUserList.findIndex( - (u) => (u.seq as any) === (userInfo.seq as any) + (u) => String(u.seq) === String(userInfo.seq) ); if (-1 < i) { this.selectedUserList = this.selectedUserList.filter( - (u) => (u.seq as any) !== (userInfo.seq as any) + (u) => String(u.seq) !== String(userInfo.seq) ); } } removableForSelection = (userInfo: UserInfo) => { + // if (String(userInfo.seq) === String(this.data.userInfo.seq)) { + // return false; + // } return true; }; colorForSelection = (userInfo: UserInfo) => { + // if (String(userInfo.seq) === String(this.data.userInfo.seq)) { + // return 'primary'; + // } return 'accent'; }; } diff --git a/src/app/sections/group/dialogs/manage.dialog.component.html b/src/app/sections/group/dialogs/manage.dialog.component.html index f356a73..33a28ff 100644 --- a/src/app/sections/group/dialogs/manage.dialog.component.html +++ b/src/app/sections/group/dialogs/manage.dialog.component.html @@ -1,77 +1,61 @@
    - {{ data.title }} + {{ 'group:dialog.title.managementGroup' | ucapI18n }}
    - - -
    -
    - {{ data.groupBuddyList.group.name }} -
    -
    - -
    -
    -
    - -
    -
    - - - -
    -
    +
    + +
    +
    + +

    + {{ data.groupBuddyList.group.name }} + {{ 'group:label.member' | ucapI18n }} +

    + ({{ selectedUserList.length }}) +
    -
    -
    - - -
    -
    - -
    diff --git a/src/app/sections/group/dialogs/manage.dialog.component.scss b/src/app/sections/group/dialogs/manage.dialog.component.scss index cccbc78..0f0d088 100644 --- a/src/app/sections/group/dialogs/manage.dialog.component.scss +++ b/src/app/sections/group/dialogs/manage.dialog.component.scss @@ -1,3 +1,4 @@ +@import '~@ucap/lg-scss/mixins'; .dialog-container { width: 100%; height: 100%; @@ -5,5 +6,84 @@ .dialog-body { width: 100%; height: 100%; + display: flex; + flex-direction: row; + @include screen(custom, max, 768) { + flex-direction: column; + } + .ucap-dialog-group-manage-container { + display: flex; + flex-flow: column; + width: 100%; + height: 100%; + .group-name { + display: flex; + flex-flow: row; + height: 40px; + border-bottom: 1px solid #999999; + .sub-title { + display: inline-flex; + height: 100%; + align-items: center; + } + button { + display: flex; + margin-left: auto; + padding: 0 10px; + } + } + + .ucap-dialog-app-group-profile-list { + height: calc(100% - 40px); + // overflow-y: hidden; + overflow: hidden; + .profile-list-01-item-container { + .ucap-virtual-scroll-viewport { + .ps__rail-x { + display: none; + } + } + } + } + } + .ucap-dialog-app-group-select-user { + width: 60%; + border-bottom: 1px solid #cccccc; + background-color: #ffffff; + @include screen(custom, max, 768) { + width: 100%; + height: 78%; + margin-bottom: 2%; + } + } + + .ucap-dialog-organization-profile-selection { + position: relative; + width: 40%; + padding-left: $default-space; + border-bottom: 1px solid #cccccc; + background-color: #ffffff; + @include screen(custom, max, 768) { + width: 100%; + height: 20%; + padding: 0; + } + .ucap-organization-selected-list { + width: 100%; + height: 100%; + display: flex; + border: 1px solid #cccccc; + border-bottom: none; + overflow: auto; + } + } + } + .btn-box { + display: flex; + flex-direction: row; + button { + margin-left: 4px; + border-radius: 3px; + } } } diff --git a/src/app/sections/group/dialogs/manage.dialog.component.spec.ts b/src/app/sections/group/dialogs/manage.dialog.component.spec.ts index d20bf16..b541ade 100644 --- a/src/app/sections/group/dialogs/manage.dialog.component.spec.ts +++ b/src/app/sections/group/dialogs/manage.dialog.component.spec.ts @@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; -import { CreateDialogComponent } from './create.dialog.component'; +import { ManageDialogComponent } from './manage.dialog.component'; describe('ucap::ui-organization::ManageDialogComponent', () => { let component: ManageDialogComponent; diff --git a/src/app/sections/group/dialogs/manage.dialog.component.ts b/src/app/sections/group/dialogs/manage.dialog.component.ts index b09f804..53e7dbd 100644 --- a/src/app/sections/group/dialogs/manage.dialog.component.ts +++ b/src/app/sections/group/dialogs/manage.dialog.component.ts @@ -4,14 +4,12 @@ import { Component, OnInit, OnDestroy, + AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Inject, - Input, - ComponentFactoryResolver, ViewChild, - ViewContainerRef, - ComponentRef + ViewContainerRef } from '@angular/core'; import { Store } from '@ngrx/store'; @@ -25,22 +23,13 @@ import { import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; -import { MatStepper } from '@angular/material/stepper'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + import { I18nService } from '@ucap/ng-i18n'; -import { GroupActions } from '@ucap/ng-store-group'; -import { - AlertDialogComponent, - AlertDialogData, - AlertDialogResult, - ConfirmDialogComponent, - ConfirmDialogResult, - ConfirmDialogData -} from '@ucap/ng-ui'; + +import { GroupManageType } from '@app/types'; +import { LogService } from '@ucap/ng-logger'; import { SelectUserSectionComponent } from '../components/select-user.section.component'; -import { take, map, catchError } from 'rxjs/operators'; -import { SelectGroupSectionComponent } from '../components/select-group.section.component'; -import { SelectUserDialogType, GroupUserDialaogType } from '@app/types'; +import { DefaultDialogLayoutComponent } from '@app/layouts/components/default-dialog.layout.component'; export type UserInfoTypes = | UserInfo @@ -54,11 +43,9 @@ export interface ManageDialogData { groupBuddyList?: { group: GroupDetailData; buddyList: UserInfo[] }; } export interface ManageDialogResult { - type: GroupUserDialaogType; - groupName: string; + choice: boolean; group?: GroupDetailData; selelctUserList?: UserInfoTypes[]; - selectGroupList?: GroupDetailData[]; } @Component({ @@ -67,217 +54,138 @@ export interface ManageDialogResult { styleUrls: ['./manage.dialog.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ManageDialogComponent implements OnInit, OnDestroy { +export class ManageDialogComponent implements OnInit, OnDestroy, AfterViewInit { constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: ManageDialogData, private changeDetectorRef: ChangeDetectorRef, - private store: Store, - private formBuilder: FormBuilder, private i18nService: I18nService, public dialog: MatDialog, - private cfResolver: ComponentFactoryResolver + private logService: LogService ) {} @ViewChild('dialogContainer', { static: true, read: ViewContainerRef }) dialogContainer: ViewContainerRef; - componentRef: ComponentRef; - private ngOnDestroySubject: Subject; - currentType: GroupUserDialaogType; - SelectUserDialogType = SelectUserDialogType; - GroupUserDialaogType = GroupUserDialaogType; + @ViewChild('appGroupSelectUser', { static: false }) + appGroupSelectUser: SelectUserSectionComponent; - currentStep = 0; - groupName = ''; + private ngOnDestroySubject: Subject = new Subject(); + + GroupManageType = GroupManageType; - delteUserList: UserInfoTypes[] = []; selectedUserList: UserInfoTypes[] = []; selectedGroupList: GroupDetailData[] = []; ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - // this.selectedUserList = this.data.groupBuddyList.buddyList; + this.selectedUserList = this.data.groupBuddyList.buddyList; } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } + ngAfterViewInit() { + this.psUpdate(); + } + onClosed(event: MouseEvent): void { this.dialogRef.close(); } - onDelete(stepper: MatStepper) { - if ( - !!this.delteUserList && - this.delteUserList.length > 0 && - this.currentStep === 0 - ) { - let titleStr = ''; - this.delteUserList.forEach((user, idx) => { - let userTitle = user.name + ' ' + user.grade; - if (idx < this.delteUserList.length) { - userTitle = userTitle + ', '; - } - titleStr = titleStr.concat('', userTitle); - }); - const dialogRef = this.dialog.open< - ConfirmDialogComponent, - ConfirmDialogData, - ConfirmDialogResult - >(ConfirmDialogComponent, { - data: { - title: '동료 삭제', - html: titleStr + '을 삭제하시겠습니까?' - } - }); - dialogRef - .afterClosed() - .pipe( - take(1), - map((result) => { - if (!!result && result.choice) { - const trgtUserSeq: string[] = []; - - this.delteUserList.forEach((userIfno) => { - const tempSeq = this.data.groupBuddyList.group.userSeqs.filter( - (seq) => seq === userIfno.seq - ); - - trgtUserSeq.push(tempSeq[0]); - }); - - console.log(trgtUserSeq); - - this.store.dispatch( - GroupActions.updateMember({ - targetGroup: this.data.groupBuddyList.group, - targetUserSeqs: trgtUserSeq - }) - ); - this.dialogRef.close(); - } - }), - catchError((err) => { - return of(err); - }) - ) - .subscribe(); - } + psUpdate() { + this.appGroupSelectUser.psUpdate(); } - onUpdateMember(stepper: MatStepper, type: GroupUserDialaogType) { - this.dialogContainer.clear(); - this.currentType = type; - const isMemberMove = type === GroupUserDialaogType.Copy ? false : true; - // const title = type === GroupUserDialaogType.Copy ? '멤버 복사' : '멤버 이동'; + onSelectBySectionGroup(data: { checked: boolean; group: GroupDetailData }) { + const i = this.selectedGroupList.findIndex((u) => u.seq === data.group.seq); - const factory = this.cfResolver.resolveComponentFactory( - SelectGroupSectionComponent - ); - - this.componentRef = this.dialogContainer.createComponent(factory); - const cpInstance = this.componentRef.instance; - // cpInstance.title = title; - cpInstance.isMemberMove = isMemberMove; - cpInstance.isDialog = true; - cpInstance.checkable = true; - cpInstance.curGroup = this.data.groupBuddyList.group; - cpInstance.changeUserList.subscribe((val) => { - this.selectedUserList = val; - }); - cpInstance.changeGroupList.subscribe((groupList) => { - this.selectedGroupList = groupList; - }); - cpInstance.changeGroupName.subscribe((groupName) => { - this.groupName = groupName; - }); - this.currentStep++; - stepper.next(); - } - - onAdd(stepper: MatStepper) { - this.dialogContainer.clear(); - this.currentType = GroupUserDialaogType.Add; - // this.selectedUserList = this.data.groupBuddyList.buddyList; - - const factory = this.cfResolver.resolveComponentFactory( - SelectUserSectionComponent - ); - - this.componentRef = this.dialogContainer.createComponent(factory); - const cpInstance = this.componentRef.instance; - cpInstance.isDialog = true; - cpInstance.checkable = true; - cpInstance.selectedUserList = this.data.groupBuddyList.buddyList; - cpInstance.isSelectionOff = false; - cpInstance.changeUserList.subscribe((val) => { - this.selectedUserList = val; - }); - this.currentStep++; - stepper.next(); - } - - onChangeUserList(selectedUserList: UserInfoTypes[]) { - this.selectedUserList = selectedUserList; - } - - onCnacel(stepper: MatStepper) { - if (!!this.selectedUserList && this.selectedUserList.length > 0) { - this.selectedUserList = []; - } - this.currentStep--; - stepper.previous(); - } - onConfirm(stepper: MatStepper) { - switch (this.currentType) { - case GroupUserDialaogType.Add: - { - if (!!this.selectedUserList && this.selectedUserList.length > 0) { - this.doAction(); - } - } - break; - case GroupUserDialaogType.Copy: - case GroupUserDialaogType.Move: - { - if (!!this.selectedUserList && this.selectedUserList.length === 0) { - this.dialog.open< - AlertDialogComponent, - AlertDialogData, - AlertDialogResult - >(AlertDialogComponent, { - data: { - title: 'Error', - html: '선택된 유저가 없습니다.' - } - }); - - return; - } - - this.doAction(); - } - break; - } - } - - doAction() { - this.dialogContainer.clear(); - if (!!this.groupName && this.groupName.trim().localeCompare('') !== 0) { - this.currentType = GroupUserDialaogType.Create; + if (data.checked) { + if (-1 === i) { + this.selectedGroupList = [...this.selectedGroupList, data.group]; + } } else { - this.groupName = undefined; + if (-1 < i) { + this.selectedGroupList = this.selectedGroupList.filter( + (u) => u.seq !== data.group.seq + ); + } + } + } + + onSelectBySectionUserGroup(params: { + isChecked: boolean; + groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; + }) { + if (params.isChecked) { + params.groupBuddyList.buddyList.forEach((item) => { + if ( + this.selectedUserList.filter((user) => user.seq === item.seq) + .length === 0 + ) { + this.selectedUserList = [...this.selectedUserList, item]; + } + }); + } else { + this.selectedUserList = this.selectedUserList.filter( + (item) => + params.groupBuddyList.buddyList.filter((del) => del.seq === item.seq) + .length === 0 + ); + } + } + + onChangeSelectedUserList( + datas: { + checked: boolean; + userInfo: UserInfoSS; + }[] + ) { + if (!datas || 0 === datas.length) { + return; } + const pushs: UserInfoSS[] = []; + const pops: UserInfoSS[] = []; + + datas.forEach((d) => { + const i = this.selectedUserList.findIndex( + (u) => u.seq === d.userInfo.seq + ); + if (d.checked) { + if (-1 === i) { + pushs.push(d.userInfo); + } + } else { + if (-1 < i) { + pops.push(d.userInfo); + } + } + }); + + if (0 < pushs.length) { + this.selectedUserList = [...this.selectedUserList, ...pushs]; + } + + if (0 < pops.length) { + this.selectedUserList = this.selectedUserList.filter( + (u) => -1 === pops.findIndex((p) => p.seq === u.seq) + ); + } + } + onCancel(event: MouseEvent) { + this.dialogRef.close({ choice: false }); + } + onConfirm(event: MouseEvent) { this.dialogRef.close({ - type: this.currentType, - groupName: this.groupName, - group: this.data.groupBuddyList.group, - selelctUserList: this.selectedUserList, - selectGroupList: this.selectedGroupList + choice: true, + group: { + ...this.data.groupBuddyList.group, + userSeqs: [] + }, + selelctUserList: this.selectedUserList }); } @@ -286,47 +194,43 @@ export class ManageDialogComponent implements OnInit, OnDestroy { if (!!this.selectedUserList && this.selectedUserList.length > 0) { return ( this.selectedUserList.filter( - (item) => (item.seq as any) === (userInfo.seq as any) + (item) => String(item.seq) === String(userInfo.seq) ).length > 0 ); } + return false; } - onToggleCheckForDelete(data: { checked: boolean; userInfo: UserInfoSS }) { - if (data.checked) { - this.delteUserList.push(data.userInfo); - } else { - const index = this.delteUserList.findIndex( - (userInfo) => userInfo.seq === data.userInfo.seq - ); - if (index > -1) { - this.delteUserList.splice(index, 1); - } + checkDisableBtn(): boolean { + let findUser: UserInfoTypes[]; + if (!!this.selectedUserList && this.selectedUserList.length > 0) { + findUser = this.selectedUserList.filter((user) => { + return this.data.groupBuddyList.group.userSeqs.includes( + String(user.seq) + ); + }); + } + + if (!!findUser && findUser.length > 0) { + return false; + } else { + return true; } - this.onToggleCheck(data); } + onToggleCheck(data: { checked: boolean; userInfo: UserInfoSS }) { - if (data.checked) { - this.selectedUserList.push(data.userInfo); - } else { - const index = this.selectedUserList.findIndex( - (userInfo) => userInfo.seq === data.userInfo.seq - ); - if (index > -1) { - this.selectedUserList.splice(index, 1); - } - } + this.onChangeSelectedUserList([data]); } onRemovedProfileSelection(userInfo: UserInfo) { const i = this.selectedUserList.findIndex( - (u) => (u.seq as any) === (userInfo.seq as any) + (u) => String(u.seq) === String(userInfo.seq) ); if (-1 < i) { this.selectedUserList = this.selectedUserList.filter( - (u) => (u.seq as any) !== (userInfo.seq as any) + (u) => String(u.seq) !== String(userInfo.seq) ); } } diff --git a/src/app/sections/group/group.section.module.ts b/src/app/sections/group/group.section.module.ts index 9e10705..7767d8b 100644 --- a/src/app/sections/group/group.section.module.ts +++ b/src/app/sections/group/group.section.module.ts @@ -21,7 +21,6 @@ import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar'; import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; import { UiModule } from '@ucap/ng-ui'; - import { OrganizationUiModule } from '@ucap/ng-ui-organization'; import { ChatUiModule } from '@ucap/ng-ui-chat'; @@ -31,6 +30,7 @@ import { AppGroupModule } from '@app/ucap/group/group.module'; import { COMPONENTS } from './components'; import { DIALOGS } from './dialogs'; +import { AppChatModule } from '@app/ucap/chat/chat.module'; @NgModule({ imports: [ @@ -57,6 +57,7 @@ import { DIALOGS } from './dialogs'; AppLayoutsModule, AppOrganizationModule, + AppChatModule, AppGroupModule ], exports: [...COMPONENTS, ...DIALOGS], @@ -65,7 +66,7 @@ import { DIALOGS } from './dialogs'; providers: [ { provide: UCAP_I18N_NAMESPACE, - useValue: ['group', 'common'] + useValue: ['group', 'organization', 'common'] } ] }) diff --git a/src/app/sections/organization/components/component-ui/index.ts b/src/app/sections/organization/components/component-ui/index.ts deleted file mode 100644 index 98bcc9b..0000000 --- a/src/app/sections/organization/components/component-ui/index.ts +++ /dev/null @@ -1 +0,0 @@ -export const COMPONENTS = []; diff --git a/src/app/sections/organization/components/detail-table.component.html b/src/app/sections/organization/components/detail-table.component.html deleted file mode 100644 index 92947f4..0000000 --- a/src/app/sections/organization/components/detail-table.component.html +++ /dev/null @@ -1,169 +0,0 @@ -
    -
    - {{ selectedDeptInfo | ucapOrganizationTranslate: 'name' - }}{{ - !!searchObj.isShowSearch - ? searchUserList.length - : departmentUserList.length - }}{{ 'common.units.persons' | ucapI18n }} -
    -
    - - - - 전체 - - - 전체1 - - - -
    - - -
    -
    - - - - - - -
    -
    - - - - - {{ 'label.selectedUsers' | ucapI18n }} - {{ selectedUserList.length }} - - - - - - - - - -
    - - - - {{ userInfo.name }} - clear - - - -
    -
    -
      -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    -
    -
    -
    -
    diff --git a/src/app/sections/organization/components/detail-table.component.scss b/src/app/sections/organization/components/detail-table.component.scss deleted file mode 100644 index 38e013d..0000000 --- a/src/app/sections/organization/components/detail-table.component.scss +++ /dev/null @@ -1,235 +0,0 @@ -// @charset 'utf-8'; -// @mixin ellipsis($row) { -// overflow: hidden; -// text-overflow: ellipsis; -// @if $row == 1 { -// display: block; -// white-space: nowrap; -// word-wrap: normal; -// } @else if $row >= 2 { -// display: -webkit-box; -// -webkit-line-clamp: $row; -// -webkit-box-orient: vertical; -// word-wrap: break-word; -// } -// } -// @mixin disable-selection { -// -webkit-touch-callout: none; /* iOS Safari */ -// -webkit-user-select: none; /* Safari */ -// -khtml-user-select: none; /* Konqueror HTML */ -// -moz-user-select: none; /* Firefox */ -// -ms-user-select: none; /* Internet Explorer/Edge */ -// user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ -// } - -// /*.scrollbar { -// height: 550px; -// }*/ - -// .wrapper { -// height: 300px; -// } - -// mat-form-field { -// font-size: 14px; -// width: 100%; -// } -// .list-chip { -// height: 100px; -// width: 100%; -// padding: 10px; -// border: 1px solid #dddddd; -// overflow: auto; -// background-color: #f9f9f9; -// margin-top: 10px; -// } - -// table { -// width: 100%; -// min-width: 600px; -// table-layout: fixed; -// } - -// th.mat-header-cell { -// @include disable-selection; -// position: relative; -// padding: 0 10px; - -// span.ui-column-divider { -// display: block; -// position: absolute; -// top: 10px; -// right: 0; -// margin: 0; -// width: 2px; -// height: 40px; -// padding: 0; -// cursor: initial; -// border: none; -// background-color: #d4d4d4; - -// &.resizable { -// cursor: col-resize; -// } -// } -// span { -// &[mat-sort-header='name'], -// &[mat-sort-header='grade'] { -// display: inline-flex; -// padding-right: 10px; -// } -// } -// &.profileImage { -// width: 90px; -// } -// &.mat-column-checkable { -// width: 50px; -// } -// } - -// td.mat-cell { -// padding: 6px; -// position: relative; - -// div { -// @include ellipsis(1); -// } -// div:nth-chlid(2) { -// padding-top: 4px; -// } - -// &.profileImage { -// width: 90px; -// text-overflow: unset; -// flex: 0 0 90px; - -// .table-item { -// display: flex; -// width: 70px; -// min-width: 70px; -// font-size: 1em; - -// .presence { -// transform: translateY(6px); -// } -// .thumbnail { -// cursor: pointer; -// &-mask { -// display: inline-block; -// width: 40px; -// height: 40px; -// border-radius: 50%; -// overflow: hidden; -// margin-right: 0; -// position: relative; -// img { -// width: 40px; -// height: auto; -// background-color: #efefef; -// } -// } -// } -// .marker-mobile-state { -// position: absolute; -// background-color: #ffffff; -// width: 20px; -// height: 20px; -// border-radius: 50%; -// bottom: 0; -// left: 64px; -// display: flex; -// align-items: center; -// align-content: center; -// justify-content: center; -// .mat-icon { -// font-size: 0.9em; -// width: 18px; -// height: 18px; -// line-height: 18px; -// min-width: 18px; -// min-height: 18px; -// } -// } -// } -// } - -// &.mat-column-checkable { -// padding-left: 10px; -// flex: 0 0 50px; -// } - -// &.profileInfo { -// cursor: pointer; -// flex: 0 0 200px; -// .baseInfo { -// display: flex; -// font-size: 1em; -// @include ellipsis(1); -// .name { -// font-size: 1em; -// font-weight: 600; -// } -// .grade { -// font-size: 0.86em; -// color: #666666; -// } -// } -// .deptName { -// font-size: 0.9em; -// color: #666666; -// } -// } - -// .companyName, -// .workplace, -// .responsibilities { -// font-size: 0.86em; -// font-weight: 600; -// } - -// .hpNumber { -// cursor: pointer; -// } -// .lineNumber { -// cursor: pointer; -// } -// } - -// .work-status { -// display: inline-block; -// justify-content: center; -// justify-items: center; -// color: #ffffff; -// height: 100%; -// min-width: 32px; -// margin-right: 4px; -// border-radius: 24px; -// flex: 0 0 auto; -// font-size: 0.8em; -// text-align: center; -// } - -// .no-search-result { -// display: flex; -// width: 100%; -// margin-top: 40px; -// justify-content: center; -// justify-items: center; -// font-size: 1.1em; -// } - -// ::ng-deep .integrate-search-org { -// td.mat-cell { -// &.profileInfo { -// cursor: initial !important; -// } -// } -// } - -// ::ng-deep .ps__rail-y { -// z-index: 101; -// } - -// .example-chip-list { -// width: 100%; -// } diff --git a/src/app/sections/organization/components/detail-table.component.ts b/src/app/sections/organization/components/detail-table.component.ts deleted file mode 100644 index c147aaf..0000000 --- a/src/app/sections/organization/components/detail-table.component.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { - Component, - ViewChild, - OnInit, - ChangeDetectorRef, - Input, - OnDestroy, - ChangeDetectionStrategy -} from '@angular/core'; -import { MatTableDataSource } from '@angular/material/table'; -import { SelectionModel } from '@angular/cdk/collections'; -import { MatSort } from '@angular/material/sort'; -import { Subject, of } from 'rxjs'; -import { map, takeUntil, take, catchError } from 'rxjs/operators'; -import { - DeptInfo, - DeptSearchType, - DeptUserRequest, - UserInfoSS, - AuthResponse -} from '@ucap/protocol-query'; -import { - DepartmentSelector, - PresenceActions -} from '@ucap/ng-store-organization'; -import { select, Store } from '@ngrx/store'; -import { QueryProtocolService } from '@ucap/ng-protocol-query'; -import { - LoginSelector, - AuthorizationSelector, - ConfigurationSelector -} from '@ucap/ng-store-authentication'; -import { LoginResponse } from '@ucap/protocol-authentication'; -import { - FixedSizeVirtualScrollStrategy, - VIRTUAL_SCROLL_STRATEGY, - CdkVirtualScrollViewport -} from '@angular/cdk/scrolling'; -import { VersionInfo2Response } from '@ucap/api-public'; -import { PerfectScrollbarDirective } from 'ngx-perfect-scrollbar'; - -export class DepartmentUserVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy { - constructor() { - super(184, 150, 200); - } -} - -@Component({ - selector: 'app-organization-detail-table', - templateUrl: './detail-table.component.html', - styleUrls: ['./detail-table.component.scss'], - providers: [ - { - provide: VIRTUAL_SCROLL_STRATEGY, - useClass: DepartmentUserVirtualScrollStrategy - } - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class DetailTableComponent implements OnInit, OnDestroy { - @Input() - set searchObj(obj: { - isShowSearch: boolean; - companyCode: string; - searchWord: string; - }) { - this._searchObj = obj; - if (obj.isShowSearch && obj.searchWord.localeCompare('') !== 0) { - this.onOrganizationTenantSearch(obj); - } else { - this._searchObj.isShowSearch = false; - this.searchUserList = []; - this.changeDetectorRef.detectChanges(); - } - } - - get searchObj() { - return this._searchObj; - } - // tslint:disable-next-line: variable-name - _searchObj: any; - - @Input() - set deptSeq(deptSeq: string) { - this._deptSeq = deptSeq; - this.getSelectedDeptInfo(); - } - get deptSeq(): string { - return this._deptSeq; - } - // tslint:disable-next-line: variable-name - _deptSeq: string; - - versionInfo2Res: VersionInfo2Response; - loginRes: LoginResponse; - authRes: AuthResponse; - - defaultProfileImage = 'assets/images/ico/img_nophoto.svg'; - - departmentInfoList: DeptInfo[]; - selectedDeptInfo: DeptInfo; - - departmentUserListProcessing = false; - departmentUserList: UserInfoSS[] = []; - searchUserList: UserInfoSS[] = []; - - selectedUserList: UserInfoSS[] = []; - - @ViewChild('cvsvList', { static: false }) - cvsvList: CdkVirtualScrollViewport; - - @ViewChild(PerfectScrollbarDirective, { static: false }) - psDirectiveRef?: PerfectScrollbarDirective; - - private ngOnDestroySubject: Subject; - - constructor( - private store: Store, - private queryProtocolService: QueryProtocolService, - private changeDetectorRef: ChangeDetectorRef - ) {} - - ngOnInit() { - this.ngOnDestroySubject = new Subject(); - - this.store - .pipe( - takeUntil(this.ngOnDestroySubject), - select(ConfigurationSelector.versionInfo2Response) - ) - .subscribe((versionInfo2Res) => { - this.versionInfo2Res = versionInfo2Res; - }); - - this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; - }); - - this.store - .pipe( - takeUntil(this.ngOnDestroySubject), - select(AuthorizationSelector.authResponse) - ) - .subscribe((authRes) => { - this.authRes = authRes; - }); - - this.store - .pipe( - takeUntil(this.ngOnDestroySubject), - select(DepartmentSelector.departmentInfoList) - ) - .subscribe((departmentInfoList) => { - this.departmentInfoList = departmentInfoList; - this.getSelectedDeptInfo(); - }); - } - - ngOnDestroy(): void { - if (!!this.ngOnDestroySubject) { - this.ngOnDestroySubject.complete(); - } - } - - getSelectedDeptInfo() { - if ( - !!this.deptSeq && - !!this.departmentInfoList && - this.departmentInfoList.length > 0 - ) { - const existDept = this.departmentInfoList.some((deptInfo) => { - if (Number(deptInfo.seq + '') === Number(this.deptSeq + '')) { - this.selectedDeptInfo = deptInfo; - return true; - } - }); - - if (!!existDept && !!this.loginRes) { - this.departmentUserListProcessing = true; - const req: DeptUserRequest = { - divCd: 'ORG', - companyCode: this.loginRes.companyCode, - seq: Number(this.deptSeq), - search: '', - searchRange: DeptSearchType.All, - senderCompanyCode: this.loginRes.companyCode, - senderEmployeeType: this.loginRes.userInfo.employeeType - }; - this.queryProtocolService - .deptUser(req) - .pipe( - take(1), - map((res) => { - this.departmentUserList = res.userInfos; - this.departmentUserListProcessing = false; - - if (!!this.cvsvList) { - this.cvsvList.checkViewportSize(); - } - if (!!this.psDirectiveRef) { - this.psDirectiveRef.update(); - } - - this.changeDetectorRef.detectChanges(); - }), - catchError((error) => { - this.departmentUserListProcessing = false; - return of(error); - }) - ) - .subscribe(); - } - - this.changeDetectorRef.detectChanges(); - } - } - - onOrganizationTenantSearch(obj: { - isShowSearch: boolean; - companyCode: string; - searchWord: string; - }) { - this.departmentUserListProcessing = true; - - this.queryProtocolService - .deptUser({ - divCd: 'ORGS', - companyCode: this._searchObj.companyCode, - searchRange: DeptSearchType.All, - search: this._searchObj.searchWord, - senderCompanyCode: this.loginRes.userInfo.companyCode, - senderEmployeeType: this.loginRes.userInfo.employeeType - }) - .pipe( - map((resObj) => { - // 검색 결과 처리. - this.searchUserList = resObj.userInfos.sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0 - ); - this.changeDetectorRef.detectChanges(); - this.departmentUserListProcessing = false; - - // 검색 결과에 따른 프레즌스 조회. - const userSeqList: string[] = []; - this.searchUserList.map((user) => userSeqList.push(user.seq)); - - if (userSeqList.length > 0) { - this.store.dispatch( - PresenceActions.bulkInfo({ - divCd: 'orgSrch', - userSeqs: userSeqList - }) - ); - } - }), - catchError((error) => { - this.departmentUserListProcessing = false; - return of(error); - }) - ) - .subscribe(); - } - - /** 전체선택 체크여부 */ - getCheckedAllUser() { - if (!this.loginRes) { - return false; - } - - const compareList: UserInfoSS[] = !!this.searchObj.isShowSearch - ? this.searchUserList - : this.departmentUserList; - if ( - !compareList || - compareList.length === 0 || - compareList.filter((item) => item.seq !== this.loginRes.userSeq) - .length === 0 || - compareList - .filter((item) => item.seq !== this.loginRes.userSeq) - .filter( - (item) => - !( - this.selectedUserList.filter((user) => user.seq === item.seq) - .length > 0 - ) - ).length > 0 - ) { - return false; - } else { - return true; - } - } - /** 전체선택 이벤트 */ - onCheckAllUser(value: boolean) { - if (!this.loginRes) { - return false; - } - - const trgtUserList = !!this.searchObj.isShowSearch - ? this.searchUserList - : this.departmentUserList; - - if (!!value) { - this.selectedUserList = trgtUserList.slice(); - } else { - this.selectedUserList = []; - } - - this.changeDetectorRef.detectChanges(); - } - /** 개별 체크여부 */ - getCheckedUser(userInfo: UserInfoSS) { - if (!!this.selectedUserList && this.selectedUserList.length > 0) { - return ( - this.selectedUserList.filter((item) => item.seq === userInfo.seq) - .length > 0 - ); - } - return false; - } - /** 개별선택(토글) 이벤트 */ - onToggleUser(param: { isChecked: boolean; userInfo: UserInfoSS }) { - if (!this.loginRes || param.userInfo.seq === this.loginRes.userSeq) { - return; - } - - if ( - !this.selectedUserList.some((user) => user.seq === param.userInfo.seq) - ) { - this.selectedUserList = [...this.selectedUserList, param.userInfo]; - } else { - this.selectedUserList = this.selectedUserList.filter( - (item) => item.seq !== param.userInfo.seq - ); - } - this.changeDetectorRef.detectChanges(); - } - - onClickDeleteUserChips(userInfo: UserInfoSS) { - this.selectedUserList = this.selectedUserList.filter( - (item) => item.seq !== userInfo.seq - ); - this.changeDetectorRef.detectChanges(); - } - - onOpenProfile(userInfo: UserInfoSS): void { - alert('Open Profile'); - } - - onClickAddGroup(): void { - alert('onClickAddGroup'); - } - onClickChatOpen(): void { - alert('onClickChatOpen'); - } - onClickMessage(): void { - alert('onClickMessage'); - } - onClickCall(): void { - alert('onClickCall'); - } - onClickConference(): void { - alert('onClickConference'); - } -} diff --git a/src/app/sections/organization/components/index.ts b/src/app/sections/organization/components/index.ts index 6855536..3af76e3 100644 --- a/src/app/sections/organization/components/index.ts +++ b/src/app/sections/organization/components/index.ts @@ -1,4 +1,3 @@ -import { DetailTableComponent } from './detail-table.component'; import { MemberListComponent } from './member-list.component'; -export const COMPONENTS = [DetailTableComponent, MemberListComponent]; +export const COMPONENTS = [MemberListComponent]; diff --git a/src/app/sections/organization/components/member-list.component.html b/src/app/sections/organization/components/member-list.component.html index 79324c4..b4b2907 100644 --- a/src/app/sections/organization/components/member-list.component.html +++ b/src/app/sections/organization/components/member-list.component.html @@ -1,99 +1,173 @@ -
    +
    -
    -
    -
    - - {{ selectedDeptInfo | ucapOrganizationTranslate: 'name' }} - - - {{ selectedCompanyInfo.companyName }} - - {{ searchedProfileLength }}명 -
    +
    +
    +
    +
    + + {{ 'organization:label.searchResult' | ucapI18n }} + + + {{ selectedDeptInfo | ucapOrganizationTranslate: 'name' }} + + + {{ searchedProfileLength }}{{ 'common:units.persons' | ucapI18n }} +
    +
    +
    + {{ 'organization:label.sortName' | ucapI18n }} + +
    +
    + + +
    -
    - 이름 - - - - -
    -
    -
    -
    +
    + - - + - - - 선택한 대화상대 - {{ - !!selectedUserInfos ? selectedUserInfos.length : 0 - }} - - - - -
    - + {{ 'organization:label.selectedUsers' | ucapI18n }} + {{ + !!selectedUserInfos ? selectedUserInfos.length : 0 + }} + + +
    + + + +
    + +
    + + +
    +
    - - - - -
    -
    -
    -
    + + +
    diff --git a/src/app/sections/organization/components/member-list.component.scss b/src/app/sections/organization/components/member-list.component.scss index b3c8e10..82c939c 100644 --- a/src/app/sections/organization/components/member-list.component.scss +++ b/src/app/sections/organization/components/member-list.component.scss @@ -8,15 +8,21 @@ padding: 0 30px; background-color: white; + @include screen(xs) { + padding: 0; + } .list-header { justify-content: space-between; align-items: center; border-bottom: 2px solid #999999; - padding: 12px 0 13px; + padding: 12px 16px 13px 0; .list-header-title { + @include screen(xs) { + padding-left: 16px; + } h5 { - font-size: 13px; + font-size: 1.1em; align-items: center; font-weight: 600; color: #333333; @@ -27,33 +33,116 @@ } } .list-header-toolbox { - right: 0px; - } - } - - .selected-users { - flex-grow: 0.8; - .organization-accordion-head { - background-color: #f1f2f6; - } - .select-user-title { - strong { - color: $lipstick; - margin-left: 8px; + display: flex; + margin-left: 0; + flex: 0 0 auto; + flex-direction: row; + align-items: center; + padding-right: 16px; + .mat-icon-button { + .mat-icon { + font-size: 20px; + width: 20px; + height: 20px; + line-height: 20px; + } } } - - .selected-user-list { - width: 150px; - } - - .btn-box { - margin-top: 10px; - padding-right: 8px; + .subtitle-chk-box { + border-left: 1px solid #ccc; + flex: 0 0 4% !important; + position: relative; + padding-left: 20px; display: flex; - flex-direction: row; - align-content: center; - justify-content: space-between; + justify-content: center; + } + } + .list-body-container { + } + .list-selection-container { + @include screen(custom, min, 1280) { + margin: 0 0 20px 30px; + min-width: 450px !important; + } + @include screen(custom, max, 1280) { + min-height: 40px !important; + max-height: 200px !important; + max-width: 100% !important; + min-width: auto !important; + } + .selection-expansion { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + border-radius: 0; + @include screen(custom, max, 1280) { + border-bottom: none; + } + @include screen(xs) { + border-left: none; + } + & > .selection-expansion { + border: 1px solid #ccc; + box-shadow: none; + } + .selection-header { + background-color: #f1f2f6; + .selection-header-title { + strong { + color: $lipstick; + margin-left: 8px; + } + } + } + .selected-user-list { + // overflow-y: auto; + //overflow-x: hidden; + overflow: hidden; + /* + &[layout-type='min'] { + width: 100%; + height: 150px; + } + &[layout-type='mid'] { + width: 100%; + height: 150px; + } + &[layout-type='max'] { + width: 100%; + //height: 580px; + } + */ + } + .mat-action-row { + padding: 15px 16px 10px 16px; + @include screen(xs) { + padding: 15px 4px 10px; + } + .btn-box { + display: flex; + flex-direction: row; + align-content: center; + justify-content: space-between; + width: 100%; + @include screen(xs) { + width: calc(100% - 2px); + } + button { + @include ucap-button-flat-stroked(18%) { + @include screen(xs) { + padding: 0 5px; + min-width: auto; + font-size: 0.857em; + } + } + margin-left: 0; + &:first-of-type { + width: 28%; + } + } + } + } } } } diff --git a/src/app/sections/organization/components/member-list.component.ts b/src/app/sections/organization/components/member-list.component.ts index d5b9128..acfb740 100644 --- a/src/app/sections/organization/components/member-list.component.ts +++ b/src/app/sections/organization/components/member-list.component.ts @@ -1,5 +1,5 @@ import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { takeUntil, take } from 'rxjs/operators'; import { Component, @@ -11,23 +11,51 @@ import { ViewChild } from '@angular/core'; +import { MediaObserver } from '@angular/flex-layout'; + +import { MatCheckboxChange, MatCheckbox } from '@angular/material/checkbox'; +import { MatDialog } from '@angular/material/dialog'; + import { select, Store } from '@ngrx/store'; import { SortOrder } from '@ucap/core'; -import { LoginResponse } from '@ucap/protocol-authentication'; -import { DeptInfo, UserInfoSS } from '@ucap/protocol-query'; +import { Company } from '@ucap/api-external'; +import { DeptInfo, UserInfoSS, AuthResponse } from '@ucap/protocol-query'; import { UserInfo } from '@ucap/protocol-sync'; +import { User } from '@ucap/protocol-info'; + +import { LogService } from '@ucap/ng-logger'; +import { I18nService } from '@ucap/ng-i18n'; -import { LoginSelector } from '@ucap/ng-store-authentication'; import { DepartmentSelector, - CompanySelector + CompanySelector, + UserSelector } from '@ucap/ng-store-organization'; +import { AuthorizationSelector } from '@ucap/ng-store-authentication'; + +import { + AlertDialogComponent, + AlertDialogData, + AlertDialogResult +} from '@ucap/ng-ui'; import { SearchData } from '@app/ucap/organization/models/search-data'; -import { Company } from '@ucap/api-external'; -import { MatCheckboxChange } from '@angular/material/checkbox'; import { ProfileListComponent as AppProfileListComponent } from '@app/ucap/organization/components/profile-list.component'; +import { AppChatService } from '@app/services/app-chat.service'; + +import { + AddGroupDialogComponent, + AddGroupDialogData, + AddGroupDialogResult +} from '../dialogs/add-group.dialog.component'; +import { + ProfileDialogComponent, + ProfileDialogData, + ProfileDialogResult +} from '../dialogs/profile.dialog.component'; + +import { environment } from '@environments'; @Component({ selector: 'app-sections-organization-member-list', @@ -40,48 +68,123 @@ export class MemberListComponent implements OnInit, OnDestroy { set searchData(searchData: SearchData) { this._searchData = searchData; - if (searchData.bySearch) { - this.setCompanyInfo(searchData.companyCode); - } else { + this.selectedDeptInfo = undefined; + this.selectedCompanyInfo = undefined; + + this.sortOrderForProfileList = { + ...this.sortOrderForProfileList, + ascending: undefined + }; + + if (!!searchData && !searchData.bySearch && !!searchData.deptSeq) { this.setDeptInfo(searchData.deptSeq); + } else { + // if (!!this.loginRes) { + // this.setDeptInfo(this.loginRes.departmentCode + ''); + // } + // this.setCompanyInfo(searchData.companyCode); } } + @Input() + cacheSize = 0; + @ViewChild('profileList', { static: false }) profileList: AppProfileListComponent; + @ViewChild('checkboxAll', { static: false }) + checkboxAll: MatCheckbox; + // tslint:disable-next-line: variable-name _searchData: SearchData; - loginRes: LoginResponse; + user: User; selectedDeptInfo: DeptInfo; selectedCompanyInfo: Company; searchedProfileLength: number; + searchedUserInfos: UserInfoSS[] = []; selectedUserInfos: UserInfoSS[] = []; isExpanded = false; + isAllCheck = false; + authRes: AuthResponse; + sortOrderForProfileList: SortOrder = { property: 'name', - ascending: true + ascending: undefined }; - private ngOnDestroySubject: Subject; + layoutMode: 'min' | 'mid' | 'max'; + + private ngOnDestroySubject: Subject = new Subject(); constructor( private store: Store, - private changeDetectorRef: ChangeDetectorRef + private appChatService: AppChatService, + public mediaObserver: MediaObserver, + private changeDetectorRef: ChangeDetectorRef, + private logService: LogService, + private dialog: MatDialog, + private i18nService: I18nService ) {} ngOnInit() { - this.ngOnDestroySubject = new Subject(); + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; + if ( + !!this._searchData && + !this._searchData.bySearch && + !!this._searchData.deptSeq + ) { + // ignore.. + } else { + if (!!user) { + this.setDeptInfo(user.departmentCode + ''); + } + } + }); + + this.mediaObserver + .asObservable() + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe((changes) => { + if (!changes || 0 === changes.length) { + return; + } + for (const change of changes) { + switch (change.mqAlias) { + case 'lt-sm': + this.layoutMode = 'min'; + this.changeDetectorRef.detectChanges(); + return; + case 'sm': + case 'md': + this.layoutMode = 'mid'; + this.changeDetectorRef.detectChanges(); + return; + case 'gt-md': + this.layoutMode = 'max'; + this.changeDetectorRef.detectChanges(); + return; + default: + break; + } + } + }); this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; + .pipe( + takeUntil(this.ngOnDestroySubject), + select(AuthorizationSelector.authResponse) + ) + .subscribe((authRes) => { + this.authRes = authRes; }); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } @@ -91,11 +194,19 @@ export class MemberListComponent implements OnInit, OnDestroy { bySearch: true, ...data }; + this.setDeptInfo(data.deptSeq); } onSearchedProfileList(userInfos: UserInfoSS[]) { + if (!!this.checkboxAll) { + this.checkboxAll.checked = false; + } + + this.searchedUserInfos = userInfos; this.searchedProfileLength = !!userInfos ? userInfos.length : 0; + + this._changeAllCheck(); } onChangedCheckProfileList( @@ -132,6 +243,40 @@ export class MemberListComponent implements OnInit, OnDestroy { (u) => -1 === pops.findIndex((p) => p.seq === u.seq) ); } + this._changeAllCheck(); + this.changeDetectorRef.markForCheck(); + } + + private _changeAllCheck() { + const findUser: UserInfoSS[] = []; + let isChecked = false; + + if ( + !!this.searchedUserInfos && + !!this.selectedUserInfos && + this.searchedUserInfos.length > 0 && + this.selectedUserInfos.length > 0 + ) { + this.selectedUserInfos.forEach((selUser) => { + this.searchedUserInfos.every((searchUser) => { + if (selUser.seq === searchUser.seq) { + findUser.push(selUser); + return false; + } + return true; + }); + }); + } + + if ( + findUser.length > 0 && + findUser.length === this.searchedUserInfos.length + ) { + isChecked = true; + } + if (!!this.checkboxAll) { + this.checkboxAll.checked = isChecked; + } } onRemovedProfileSelection(userInfo: UserInfo) { @@ -144,8 +289,22 @@ export class MemberListComponent implements OnInit, OnDestroy { (u) => u.seq !== String(userInfo.seq) ); } + this._changeAllCheck(); + this.changeDetectorRef.markForCheck(); } + onOpenProfile(userInfo: UserInfoSS) { + const result = this.dialog.open< + ProfileDialogComponent, + ProfileDialogData, + ProfileDialogResult + >(ProfileDialogComponent, { + panelClass: 'mid-create-dialog', + data: { + userSeq: userInfo.seq + } + }); + } removableForSelection = (userInfo: UserInfo) => { return true; }; @@ -154,18 +313,15 @@ export class MemberListComponent implements OnInit, OnDestroy { return 'accent'; }; - onOpenedSelection() { - this.isExpanded = true; - } - - onClosedSelection() { - this.isExpanded = false; - } - onClickToggleSort() { this.sortOrderForProfileList = { ...this.sortOrderForProfileList, - ascending: !this.sortOrderForProfileList.ascending + ascending: + undefined === this.sortOrderForProfileList.ascending + ? true + : true === this.sortOrderForProfileList.ascending + ? false + : undefined }; } @@ -173,10 +329,33 @@ export class MemberListComponent implements OnInit, OnDestroy { if (event.checked) { this.profileList.checkAll(); } else { - this.selectedUserInfos = []; + this.profileList.uncheckAll(); } } + getContainerLayout(type: 'max' | 'mid'): string { + switch (type) { + case 'max': + return 'row'; + case 'mid': + return 'column'; + default: + break; + } + } + + onAfterExpandForSelection() { + this.isExpanded = true; + } + + onAfterCollapseForSelection() { + this.isExpanded = false; + } + + onClearSelected(): void { + this.profileList.uncheckAll(); + } + private setCompanyInfo(companyCode: string) { const destroySubject: Subject = new Subject(); this.store @@ -208,9 +387,82 @@ export class MemberListComponent implements OnInit, OnDestroy { this.selectedDeptInfo = departmentInfoList.find( (d) => String(d.seq) === seq ); + this.changeDetectorRef.markForCheck(); destroySubject.next(); destroySubject.complete(); }); } + + openChatRoom() { + if ( + !!this.selectedUserInfos && + this.selectedUserInfos.length > + environment.productConfig.chat.maxChatRoomUser + ) { + const result = this.dialog.open< + AlertDialogComponent, + AlertDialogData, + AlertDialogResult + >(AlertDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:errors.label'), + message: this.i18nService.t('chat:errors.maxCountOfRoomMemberWith', { + maxCount: environment.productConfig.chat.maxChatRoomUser + }) + } + }); + return; + } + + if (!!this.selectedUserInfos && this.selectedUserInfos.length > 0) { + this.appChatService.newOpenRoom( + this.selectedUserInfos.map((item) => item.seq), + false, + this.user + ); + } + } + + addGroup(): void { + const dialogRef = this.dialog.open< + AddGroupDialogComponent, + AddGroupDialogData, + AddGroupDialogResult + >(AddGroupDialogComponent, { + panelClass: 'mid-create-dialog', + data: { + selectedUserList: this.selectedUserInfos + } + }); + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && !!result.choice) { + this.selectedUserInfos = []; + } + }); + } + + onFloatingProfileMenu(params: { menuType: string; userInfo: UserInfoSS }) { + switch (params.menuType) { + case 'CHAT': + this.appChatService.newOpenRoom( + [String(params.userInfo.seq)], + false, + this.user + ); + break; + case 'MESSAGE': + break; + case 'MOBILE': + break; + case 'OFFICE': + break; + case 'VIDEO_CONFERENCE': + break; + } + } } diff --git a/src/app/sections/organization/components/tree.section.component.html b/src/app/sections/organization/components/tree.section.component.html deleted file mode 100644 index baa861c..0000000 --- a/src/app/sections/organization/components/tree.section.component.html +++ /dev/null @@ -1,4 +0,0 @@ -
    - - -
    diff --git a/src/app/sections/organization/components/tree.section.component.scss b/src/app/sections/organization/components/tree.section.component.scss deleted file mode 100644 index 8e9eeba..0000000 --- a/src/app/sections/organization/components/tree.section.component.scss +++ /dev/null @@ -1,2 +0,0 @@ -.tree-container { -} diff --git a/src/app/sections/organization/components/tree.section.component.ts b/src/app/sections/organization/components/tree.section.component.ts deleted file mode 100644 index 5950bc7..0000000 --- a/src/app/sections/organization/components/tree.section.component.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Observable, Subject, combineLatest } from 'rxjs'; -import { filter, takeUntil } from 'rxjs/operators'; - -import { - Component, - OnInit, - OnDestroy, - ChangeDetectionStrategy, - ChangeDetectorRef -} from '@angular/core'; - -import { Store, select } from '@ngrx/store'; - -import { - VirtualScrollStrategy, - FixedSizeVirtualScrollStrategy, - VIRTUAL_SCROLL_STRATEGY, - CdkVirtualScrollViewport -} from '@angular/cdk/scrolling'; - -import { VersionInfo2Response } from '@ucap/api-public'; -import { Company } from '@ucap/api-external'; -import { LoginResponse } from '@ucap/protocol-authentication'; -import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; -import { DeptInfo } from '@ucap/protocol-query'; - -import { LogService } from '@ucap/ng-logger'; -import { NodeType } from '@ucap/ng-ui-group'; -import { SessionStorageService } from '@ucap/ng-web-storage'; -import { - LoginSelector, - ConfigurationSelector -} from '@ucap/ng-store-authentication'; -import { DepartmentSelector } from '@ucap/ng-store-organization'; -import { BuddySelector, GroupSelector } from '@ucap/ng-store-group'; - -import { AppAuthenticationService } from '@app/services/app-authentication.service'; -import { AppKey } from '@app/types/app-key.type'; -import { LoginSession } from '@app/models/login-session'; -import { environment } from '@environments'; - -export class TreeVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy { - constructor() { - super(48, 250, 500); // (itemSize, minBufferPx, maxBufferPx) - } -} - -@Component({ - selector: 'app-sections-organization-tree', - templateUrl: './tree.section.component.html', - styleUrls: ['./tree.section.component.scss'], - providers: [ - { - provide: VIRTUAL_SCROLL_STRATEGY, - useClass: TreeVirtualScrollStrategy - } - ], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class TreeSectionComponent implements OnInit, OnDestroy { - treeData: { - deptInfoList: DeptInfo[]; - displayRoot?: boolean; - }; - - loginRes: LoginResponse; - - private ngOnDestroySubject = new Subject(); - - constructor( - private appAuthenticationService: AppAuthenticationService, - private sessionStorageService: SessionStorageService, - private store: Store, - private changeDetectorRef: ChangeDetectorRef, - private logService: LogService - ) {} - - ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - - combineLatest([ - this.store.pipe(select(LoginSelector.loginRes)), - this.store.pipe(select(DepartmentSelector.departmentInfoList)) - ]) - .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe(([loginRes, deptInfoList]) => { - this.loginRes = loginRes; - this.treeData = { - deptInfoList, - displayRoot: environment.productConfig.organization.displayRoot - }; - this.changeDetectorRef.markForCheck(); - }); - } - - ngOnDestroy(): void { - if (!!this.ngOnDestroySubject) { - this.ngOnDestroySubject.complete(); - } - } -} diff --git a/src/app/sections/organization/components/tree.section.strategy.ts b/src/app/sections/organization/components/tree.section.strategy.ts deleted file mode 100644 index 564488d..0000000 --- a/src/app/sections/organization/components/tree.section.strategy.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Observable, Subject } from 'rxjs'; - -import { - VirtualScrollStrategy, - CdkVirtualScrollViewport -} from '@angular/cdk/scrolling'; -import { distinctUntilChanged } from 'rxjs/operators'; - -export class OrganizationTreeVirtualScrollStrategy - implements VirtualScrollStrategy { - scrolledIndexChange: Observable; - - private indexSubject = new Subject(); - private viewport: CdkVirtualScrollViewport | null = null; - - constructor() { - this.scrolledIndexChange = this.indexSubject.pipe(distinctUntilChanged()); - } - - attach(viewport: CdkVirtualScrollViewport): void { - this.viewport = viewport; - } - detach(): void { - this.indexSubject.complete(); - this.viewport = null; - } - onContentScrolled(): void {} - onDataLengthChanged(): void {} - onContentRendered(): void {} - onRenderedOffsetChanged(): void {} - scrollToIndex(index: number, behavior: ScrollBehavior): void {} -} diff --git a/src/app/sections/organization/dialogs/add-group.dialog.component.html b/src/app/sections/organization/dialogs/add-group.dialog.component.html new file mode 100644 index 0000000..53d3cda --- /dev/null +++ b/src/app/sections/organization/dialogs/add-group.dialog.component.html @@ -0,0 +1,33 @@ +
    + +
    + {{ 'organization:dialog.title.addGroup' | ucapI18n }} +
    +
    +
    + +
    +
    + +
    + + +
    +
    +
    diff --git a/src/app/sections/organization/dialogs/add-group.dialog.component.scss b/src/app/sections/organization/dialogs/add-group.dialog.component.scss new file mode 100644 index 0000000..1dc77ad --- /dev/null +++ b/src/app/sections/organization/dialogs/add-group.dialog.component.scss @@ -0,0 +1,18 @@ +@import '~@ucap/lg-scss/mixins'; +.dialog-container { + width: 100%; + height: 100%; + + .dialog-body { + width: 100%; + height: 100%; + } + .btn-box { + display: flex; + flex-direction: row; + button { + margin-left: 4px; + border-radius: 3px; + } + } +} diff --git a/src/app/sections/organization/dialogs/add-group.dialog.component.spec.ts b/src/app/sections/organization/dialogs/add-group.dialog.component.spec.ts new file mode 100644 index 0000000..4943a35 --- /dev/null +++ b/src/app/sections/organization/dialogs/add-group.dialog.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { AddGroupDialogComponent } from './add-group.dialog.component'; + +describe('ucap::ui-organization::AddGroupDialogComponent', () => { + let component: AddGroupDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [AddGroupDialogComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AddGroupDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/sections/organization/dialogs/add-group.dialog.component.ts b/src/app/sections/organization/dialogs/add-group.dialog.component.ts new file mode 100644 index 0000000..b9131da --- /dev/null +++ b/src/app/sections/organization/dialogs/add-group.dialog.component.ts @@ -0,0 +1,234 @@ +import { Subject, of } from 'rxjs'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Inject, + ViewChild, + ViewContainerRef +} from '@angular/core'; + +import { Store } from '@ngrx/store'; + +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatDialog +} from '@angular/material/dialog'; + +import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; +import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; +import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; +import { MatStepper } from '@angular/material/stepper'; +import { I18nService } from '@ucap/ng-i18n'; +import { GroupActions } from '@ucap/ng-store-group'; +import { + AlertDialogComponent, + AlertDialogData, + AlertDialogResult, + ConfirmDialogComponent, + ConfirmDialogResult, + ConfirmDialogData +} from '@ucap/ng-ui'; + +import { take, map, catchError } from 'rxjs/operators'; + +import { SelectUserDialogType, GroupManageType } from '@app/types'; +import { LogService } from '@ucap/ng-logger'; +import { AppGroupService } from '@app/services/app-group.service'; + +export type UserInfoTypes = + | UserInfo + | UserInfoSS + | UserInfoF + | UserInfoDN + | RoomUserInfo; + +export interface AddGroupDialogData { + selectedUserList: UserInfoTypes[]; +} +export interface AddGroupDialogResult { + choice: boolean; +} + +@Component({ + selector: 'app-dialog-organization-add-group', + templateUrl: './add-group.dialog.component.html', + styleUrls: ['./add-group.dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AddGroupDialogComponent implements OnInit, OnDestroy { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: AddGroupDialogData, + private changeDetectorRef: ChangeDetectorRef, + private store: Store, + private i18nService: I18nService, + private logService: LogService, + public dialog: MatDialog, + private appGroupService: AppGroupService + ) {} + + private ngOnDestroySubject: Subject; + + groupName = ''; + selectedUserList: UserInfoTypes[] = []; + selectedGroupList: GroupDetailData[] = []; + isDisableBtn = false; + + ngOnInit(): void { + this.ngOnDestroySubject = new Subject(); + this.selectedUserList = this.data.selectedUserList; + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onClosed(event: MouseEvent): void { + this.dialogRef.close({ choice: false }); + } + + checkDisableBtnConfirm(): boolean { + return false; + } + + onChangeGroupName(data: { invalid: boolean; groupName: string }) { + this.groupName = data.groupName; + + if ( + !!this.groupName && + this.groupName.trim().localeCompare('') !== 0 && + !!data.invalid + ) { + this.isDisableBtn = data.invalid; + } else { + this.isDisableBtn = false; + } + } + + onChangeSelectedGroupList(data: { + checked: boolean; + group: GroupDetailData; + }) { + const i = this.selectedGroupList.findIndex((u) => u.seq === data.group.seq); + + if (data.checked) { + if (-1 === i) { + this.selectedGroupList = [...this.selectedGroupList, data.group]; + } + } else { + if (-1 < i) { + this.selectedGroupList = this.selectedGroupList.filter( + (u) => u.seq !== data.group.seq + ); + } + } + } + + onCancel(): void { + this.dialogRef.close({ choice: false }); + } + onConfirm(): void { + const targetUserSeqs = this.selectedUserList.map((item) => item.seq + ''); + if (!targetUserSeqs || targetUserSeqs.length === 0) { + this.logService.error('organization', 'not exist selected users.'); + return; + } + + // validations + if ( + (!this.groupName && !this.selectedGroupList) || + (this.groupName.trim().length === 0 && + this.selectedGroupList.length === 0) + ) { + const result = this.dialog.open< + AlertDialogComponent, + AlertDialogData, + AlertDialogResult + >(AlertDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('organization:dialog.title.addGroup'), + message: this.i18nService.t( + 'organization:dialog.errorAddBuddyForGroup' + ) + } + }); + return; + } + + // Do Action. + if (!!this.groupName && this.groupName.trim().length > 0) { + // create group and update room. + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('organization:label.addGroup'), + html: this.i18nService.t( + 'organization:dialog.confirmAddBuddyForNewGroup', + { + targetGroups: this.groupName + } + ) + } + }); + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && result.choice) { + this.appGroupService.createGroup( + this.groupName, + this.selectedUserList + ); + this.dialogRef.close({ choice: true }); + } + }); + } else if (!!this.selectedGroupList && this.selectedGroupList.length > 0) { + // existed group update room. + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('organization:label.addGroup'), + html: this.i18nService.t( + 'organization:dialog.confirmAddBuddyForGroup', + { + targetGroups: this.selectedGroupList + .map((group) => group.name) + .join(',') + } + ) + } + }); + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && result.choice) { + this.selectedGroupList.forEach((group) => { + this.appGroupService.addMemberToGroup( + group, + this.selectedUserList + ); + this.dialogRef.close({ choice: true }); + }); + } + }); + } + } +} diff --git a/src/app/sections/organization/dialogs/index.ts b/src/app/sections/organization/dialogs/index.ts new file mode 100644 index 0000000..2f6412c --- /dev/null +++ b/src/app/sections/organization/dialogs/index.ts @@ -0,0 +1,4 @@ +import { AddGroupDialogComponent } from './add-group.dialog.component'; +import { ProfileDialogComponent } from './profile.dialog.component'; + +export const DIALOGS = [AddGroupDialogComponent, ProfileDialogComponent]; diff --git a/src/app/sections/organization/dialogs/profile.dialog.component.html b/src/app/sections/organization/dialogs/profile.dialog.component.html new file mode 100644 index 0000000..726c44f --- /dev/null +++ b/src/app/sections/organization/dialogs/profile.dialog.component.html @@ -0,0 +1,27 @@ +
    + +
    + {{ 'organization:profile.other' | ucapI18n }} +
    +
    + +
    + +
    +
    diff --git a/src/app/sections/organization/dialogs/profile.dialog.component.scss b/src/app/sections/organization/dialogs/profile.dialog.component.scss new file mode 100644 index 0000000..694699c --- /dev/null +++ b/src/app/sections/organization/dialogs/profile.dialog.component.scss @@ -0,0 +1,19 @@ +@import '~@ucap/lg-scss/mixins'; +.dialog-container { + width: 100%; + height: 100%; + + .dialog-body { + width: 100%; + height: 100%; + } + .btn-box { + display: flex; + flex-direction: row; + justify-content: flex-end; + button { + margin-left: 4px; + border-radius: 3px; + } + } +} diff --git a/src/app/sections/organization/dialogs/profile.dialog.component.spec.ts b/src/app/sections/organization/dialogs/profile.dialog.component.spec.ts new file mode 100644 index 0000000..8a04a19 --- /dev/null +++ b/src/app/sections/organization/dialogs/profile.dialog.component.spec.ts @@ -0,0 +1,26 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { ProfileDialogComponent } from './profile.dialog.component'; + +describe('app::account::ProfileDialogComponent', () => { + let component: ProfileDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ProfileDialogComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ProfileDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/sections/organization/dialogs/profile.dialog.component.ts b/src/app/sections/organization/dialogs/profile.dialog.component.ts new file mode 100644 index 0000000..9e79fc5 --- /dev/null +++ b/src/app/sections/organization/dialogs/profile.dialog.component.ts @@ -0,0 +1,153 @@ +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Inject +} from '@angular/core'; + +import { + MatDialogRef, + MAT_DIALOG_DATA, + MatDialog +} from '@angular/material/dialog'; + +import { select, Store } from '@ngrx/store'; + +import { FileUploadItem } from '@ucap/api'; +import { FileProfileSaveRequest } from '@ucap/api-common'; +import { VersionInfo2Response } from '@ucap/api-public'; +import { UserInfoSS, UserInfoF } from '@ucap/protocol-query'; +import { LoginResponse } from '@ucap/protocol-authentication'; +import { UserInfoUpdateType, User } from '@ucap/protocol-info'; + +import { LogService } from '@ucap/ng-logger'; + +import { UserSelector } from '@ucap/ng-store-organization'; +import { + LoginSelector, + ConfigurationSelector +} from '@ucap/ng-store-authentication'; + +import { UserInfoTypes } from '@app/types'; +import { AppGroupService } from '@app/services/app-group.service'; +import { AppFileService } from '@app/services/app-file.service'; +import { AppAuthenticationService } from '@app/services/app-authentication.service'; +import { AppChatService } from '@app/services/app-chat.service'; + +export interface ProfileDialogData { + userSeq: string; +} +export interface ProfileDialogResult {} + +@Component({ + selector: 'app-dialog-organization-profile', + templateUrl: './profile.dialog.component.html', + styleUrls: ['./profile.dialog.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ProfileDialogComponent implements OnInit, OnDestroy { + constructor( + private store: Store, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: ProfileDialogData, + private logService: LogService, + private changeDetectorRef: ChangeDetectorRef, + public matDialog: MatDialog, + private appAuthenticationService: AppAuthenticationService, + private appGroupService: AppGroupService, + private appChatService: AppChatService, + private appFileServie: AppFileService + ) {} + + private ngOnDestroySubject: Subject = new Subject(); + private loginRes: LoginResponse; + private user: User; + private versionInfo2Res: VersionInfo2Response; + + ngOnInit(): void { + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) + .subscribe((loginRes) => { + this.loginRes = loginRes; + }); + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; + }); + + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onClosed(event: MouseEvent): void { + this.dialogRef.close(); + } + + onOpenCaht(userInfo: UserInfoSS) { + this.appChatService.newOpenRoom( + [Number(userInfo.seq) as any], + false, + this.user + ); + this.dialogRef.close(); + } + onSendMessage(userInfo: UserInfoSS) {} + onSendCall(call: string) {} + onSendSms(employeeNum: string) {} + onCreateConference(userSeq: number) {} + onToggleFavorit(params: { userInfo: UserInfoSS; isFavorite: boolean }) { + this.appGroupService.updateBuddy(params.userInfo, params.isFavorite); + } + onToggleBuddy(params: { userInfo: UserInfoSS; isBuddy: boolean }) { + this.appGroupService + .updateBuddyByToggle(params) + .then((isRemoveBuddy) => { + this.changeDetectorRef.markForCheck(); + }) + .catch((reson) => this.logService.error(reson)); + } + onUploadProfileImage(profileImageFileUploadItem: FileUploadItem) { + const loginSession = this.appAuthenticationService.getLoginSession(); + + const profile = { + userSeq: String(this.user.info.seq), + deviceType: loginSession.deviceType, + token: this.loginRes.tokenString, + file: profileImageFileUploadItem.file, + fileUploadItem: profileImageFileUploadItem + } as FileProfileSaveRequest; + + this.appFileServie.fileProfileSave( + profile, + this.versionInfo2Res.profileUploadUrl + ); + } + onUpdateIntro(intro: string) { + this.appGroupService.updateIntro(intro, UserInfoUpdateType.Intro); + } + onUpdateNickname(params: { userInfo: UserInfoTypes; nickname: string }) { + this.appGroupService.updateNickname( + params.userInfo as UserInfoF, + params.nickname + ); + } +} diff --git a/src/app/sections/organization/organization.section.module.ts b/src/app/sections/organization/organization.section.module.ts index 4261d8e..f1eb1ee 100644 --- a/src/app/sections/organization/organization.section.module.ts +++ b/src/app/sections/organization/organization.section.module.ts @@ -22,9 +22,12 @@ import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar'; import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; import { UiModule } from '@ucap/ng-ui'; import { OrganizationUiModule } from '@ucap/ng-ui-organization'; +import { AppLayoutsModule } from '@app/layouts/layouts.module'; import { AppOrganizationModule } from '@app/ucap/organization/organization.module'; +import { AppGroupSectionModule } from '../group/group.section.module'; import { COMPONENTS } from './components'; +import { DIALOGS } from './dialogs'; @NgModule({ imports: [ @@ -51,10 +54,12 @@ import { COMPONENTS } from './components'; UiModule, OrganizationUiModule, + AppLayoutsModule, + AppGroupSectionModule, AppOrganizationModule ], - exports: [...COMPONENTS], - declarations: [...COMPONENTS], + exports: [...COMPONENTS, ...DIALOGS], + declarations: [...COMPONENTS, ...DIALOGS], entryComponents: [], providers: [ { diff --git a/src/app/services/app-account.service.ts b/src/app/services/app-account.service.ts new file mode 100644 index 0000000..bdd9d34 --- /dev/null +++ b/src/app/services/app-account.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { + SettingsDialogComponent, + SettingsDialogData, + SettingsDialogResult +} from '@app/dialogs/account/components/settings.dialog.component'; + +import { AppAuthenticationService } from './app-authentication.service'; + +@Injectable({ + providedIn: 'root' +}) +export class AppAccountService { + constructor( + private appAuthenticationService: AppAuthenticationService, + private matDialog: MatDialog + ) {} + + async dialogForSettings() { + const userStore = this.appAuthenticationService.getUserStore(); + + const result = await this.matDialog.open< + SettingsDialogComponent, + SettingsDialogData, + SettingsDialogResult + >(SettingsDialogComponent, { + panelClass: 'settings-dialog-panel', + data: { + settings: userStore.settings + } + }); + } +} diff --git a/src/app/services/app-authentication.service.ts b/src/app/services/app-authentication.service.ts index 29b42d8..7ad483f 100644 --- a/src/app/services/app-authentication.service.ts +++ b/src/app/services/app-authentication.service.ts @@ -27,6 +27,7 @@ import { AppKey } from '@app/types/app-key.type'; import { environment } from '@environments'; import { PresenceActions } from '@ucap/ng-store-organization'; +import { StatusBulkInfo } from '@ucap/protocol-status'; @Injectable({ providedIn: 'root' @@ -147,7 +148,7 @@ export class AppAuthenticationService { ...environment.productConfig.defaultSettings, chat: { ...environment.productConfig.defaultSettings.chat, - downloadPath: await this.nativeService.getPath( + downloadPath: await this.nativeService.file_path( 'documents', environment.productConfig.file.defaultDownloadFolder ) @@ -156,9 +157,9 @@ export class AppAuthenticationService { }; if (!!environment.productConfig.defaultSettings.general.autoLaunch) { - this.nativeService.changeAutoLaunch( - environment.productConfig.defaultSettings.general.autoLaunch - ); + // this.nativeService.changeAutoLaunch( + // environment.productConfig.defaultSettings.general.autoLaunch + // ); } } @@ -193,7 +194,7 @@ export class AppAuthenticationService { }>(async (resolve, reject) => { const loginSession = this.getLoginSession(); - const networkInfo = await this.nativeService.getNetworkInfo(); + const networkInfo = await this.nativeService.platform_networkInfo(); const localIp = !!networkInfo && 0 < networkInfo.length && !!networkInfo[0].ip ? networkInfo[0].ip @@ -239,10 +240,29 @@ export class AppAuthenticationService { loginSession }) ); + // this.store.dispatch( + // PresenceActions.bulkInfo({ + // divCd: 'myBulk', + // userSeqs: [loginRes.userSeq] + // }) + // ); + // 임시 코드 this.store.dispatch( - PresenceActions.bulkInfo({ - divCd: 'myBulk', - userSeqs: [loginRes.userSeq] + PresenceActions.bulkInfoSuccess({ + statusBulkInfoList: [ + { + conferenceStatus: 'X', + imessengerStatus: 'X', + mobileConferenceStatus: 'X', + mobileStatus: 'X', + pcStatus: 'O', + phoneStatus: 'X', + statusMessage: '.', + terminalStatus: 'TERMINAL_STATE_UNKNOWN', + terminalStatusNumber: 0, + userSeq: String(loginRes.userSeq) + } as StatusBulkInfo + ] }) ); resolve({ diff --git a/src/app/services/app-chat.service.ts b/src/app/services/app-chat.service.ts index 44970ba..9282e6d 100644 --- a/src/app/services/app-chat.service.ts +++ b/src/app/services/app-chat.service.ts @@ -1,36 +1,23 @@ import { Observable, of, forkJoin } from 'rxjs'; -import { take, concatMap, map, catchError } from 'rxjs/operators'; +import { take, map, catchError } from 'rxjs/operators'; -import { Injectable, Inject, ChangeDetectorRef } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { Store } from '@ngrx/store'; -import { LocaleCode, DeviceType, FileUtil } from '@ucap/core'; +import { DeviceType, FileUtil } from '@ucap/core'; -import { PasswordUtil } from '@ucap/pi'; import { LoginResponse, SSOMode } from '@ucap/protocol-authentication'; -import { NativeService } from '@ucap/native'; -import { - SessionStorageService, - LocalStorageService -} from '@ucap/ng-web-storage'; -import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; -import { InnerProtocolService } from '@ucap/ng-protocol-inner'; -import { AuthenticationProtocolService } from '@ucap/ng-protocol-authentication'; -import { LoginActions } from '@ucap/ng-store-authentication'; - -import { LoginSession } from '@app/models/login-session'; -import { UserStore } from '@app/models/user-store'; - -import { AppKey } from '@app/types/app-key.type'; +import { LocalStorageService } from '@ucap/ng-web-storage'; import { environment } from '@environments'; import { RoomInfo, RoomType, OpenRequest, - Open3Request + Open3Request, + ExitRequest } from '@ucap/protocol-room'; import { Dictionary } from '@ngrx/entity'; import { @@ -46,12 +33,17 @@ import { I18nService } from '@ucap/ng-i18n'; import { ChattingActions, RoomActions } from '@ucap/ng-store-chat'; import { SendRequest as SendEventRequest, - EventType + EventType, + Info, + MassTranslationEventJson } from '@ucap/protocol-event'; import { MassTalkSaveRequest, FileTalkSaveResponse, - FileTalkSaveRequest + FileTalkSaveRequest, + MassTalkDownloadRequest, + TransMassTalkDownloadRequest, + TransMassTalkDownloadResponse } from '@ucap/api-common'; import { CommonApiService } from '@ucap/ng-api-common'; import { StatusCode, FileUploadItem } from '@ucap/api'; @@ -60,7 +52,11 @@ import { MatDialog } from '@angular/material/dialog'; import { AlertDialogComponent, AlertDialogData, - AlertDialogResult + AlertDialogResult, + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult, + ClipboardService } from '@ucap/ng-ui'; import { StickerFilesInfo, KEY_STICKER_HISTORY } from '@ucap/ng-core'; import { @@ -68,13 +64,25 @@ import { CreateDialogData, CreateDialogResult } from '@app/sections/chat/dialogs/create.dialog.component'; +import { Router } from '@angular/router'; +import { + TransDetailDialogComponent, + TransDetailDialogData, + TransDetailDialogResult +} from '@app/sections/chat/dialogs/trans-detail.dialog.component'; +import { + FileViewerDialogComponent, + FileViewerDialogData, + FileViewerDialogResult +} from '@app/sections/chat/dialogs/file-viewer.dialog.component'; +import { User } from '@ucap/protocol-info'; @Injectable({ providedIn: 'root' }) export class AppChatService { defaultProfileImage = 'assets/images/ico/img_nophoto.svg'; - defaultProfileImageMulti = 'assets/images/ico/img_nophoto.svg'; + defaultProfileImageMulti = 'assets/images/ico/img_nophoto_multiple.svg'; constructor( private i18nService: I18nService, @@ -82,18 +90,19 @@ export class AppChatService { private localStorageService: LocalStorageService, private store: Store, private commonApiService: CommonApiService, - private logService: LogService - ) { - this.i18nService.setDefaultNamespace('chat'); - } + private clipboardService: ClipboardService, + private router: Router, + private logService: LogService, + private ngZone: NgZone + ) {} /** * 방이름 생성. * cf) 방이름이 지정되어 있다면 방이름 리턴, 지정되어 있지 않으면 방참여인원의 이름 조합. */ - getRoomName( + getRoomNameByDic( organizationTranslate: OrganizationTranslate, - loginRes: LoginResponse, + user: User, roomInfo: RoomInfo, roomUsersDictionary?: Dictionary, roomUsersShortDictionary?: Dictionary @@ -108,29 +117,37 @@ export class AppChatService { case RoomType.Mytalk: roomName = 'MyTalk'; break; + case RoomType.Single: + { + const roomUsers = this.getRoomUserList( + user, + roomInfo.roomId, + roomUsersDictionary, + roomUsersShortDictionary + ); + + if (roomUsers.users.length > 0) { + roomName = roomUsers.users + .map((item) => organizationTranslate.transform(item, 'name')) + .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)) + .join(','); + } else { + roomName = this.i18nService.t('chat:room.noRoomUser'); + } + } + break; default: { - const roomId = roomInfo.roomId; - const roomUsers = !!roomUsersDictionary - ? roomUsersDictionary[roomId] - : undefined; - const roomUsersShort = !!roomUsersShortDictionary - ? roomUsersShortDictionary[roomId] - : undefined; - - let users = []; - let existUsers = false; - if (!!roomUsers && roomUsers.userInfos.length > 0) { - existUsers = true; - users = roomUsers.userInfos.filter( - (userInfo) => userInfo.seq !== Number(loginRes.userSeq) - ); - } else if (!!roomUsersShort && roomUsersShort.userInfos.length > 0) { - existUsers = true; - users = roomUsersShort.userInfos.filter( - (userInfo) => userInfo.seq !== Number(loginRes.userSeq) - ); - } + const roomUsers = this.getRoomUserList( + user, + roomInfo.roomId, + roomUsersDictionary, + roomUsersShortDictionary + ); + const users = roomUsers.users.filter( + (userInfo) => !!userInfo.isJoinRoom + ); + const existUsers = roomUsers.existUsers; const curRoomName = roomInfo.roomName; if (!!curRoomName && curRoomName.trim().length > 0) { @@ -138,10 +155,84 @@ export class AppChatService { roomName = curRoomName; } else { if (users.length > 0) { - roomName = organizationTranslate.transform(users, 'name', ','); + roomName = users + .map((item) => organizationTranslate.transform(item, 'name')) + .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)) + .join(','); + // roomName = organizationTranslate.transform(users, 'name', ','); } else { if (existUsers) { - roomName = this.i18nService.t('room.noRoomUser'); + roomName = this.i18nService.t('chat:room.noRoomUser'); + } + } + } + } + break; + } + + return roomName; + } + getRoomName( + organizationTranslate: OrganizationTranslate, + user: User, + roomInfo: RoomInfo, + roomUsersMap?: RoomUserMap, + roomUsersShortMap?: RoomUserShortMap + ): string { + if (!roomInfo) { + return ''; + } + + let roomName = '...'; + + switch (roomInfo.roomType) { + case RoomType.Mytalk: + roomName = 'MyTalk'; + break; + case RoomType.Single: + { + const roomUsers = this.getRoomUserList01( + user, + roomUsersMap, + roomUsersShortMap + ); + + if (roomUsers.users.length > 0) { + roomName = roomUsers.users + .map((item) => organizationTranslate.transform(item, 'name')) + .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)) + .join(','); + } else { + roomName = this.i18nService.t('chat:room.noRoomUser'); + } + } + break; + default: + { + const roomUsers = this.getRoomUserList01( + user, + roomUsersMap, + roomUsersShortMap + ); + const users = roomUsers.users.filter( + (userInfo) => !!userInfo.isJoinRoom + ); + const existUsers = roomUsers.existUsers; + + const curRoomName = roomInfo.roomName; + if (!!curRoomName && curRoomName.trim().length > 0) { + // Exist RoomName. + roomName = curRoomName; + } else { + if (users.length > 0) { + roomName = users + .map((item) => organizationTranslate.transform(item, 'name')) + .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)) + .join(','); + // roomName = organizationTranslate.transform(users, 'name', ','); + } else { + if (existUsers) { + roomName = this.i18nService.t('chat:room.noRoomUser'); } } } @@ -155,41 +246,34 @@ export class AppChatService { /** * 방 프로필 이미지 생성. * cf) 방 참여인원의 프로필을 리턴. - * 없으면, defaultProfileImage, defaultProfileImageMulti, - * 멀티룸은 기본 defaultProfileImageMulti + * 없으면, '' 빈값을 리턴하고 ucapImage pipe 에서 default Image 로 처리. */ - getRoomProfileImage( + getRoomProfileImageByDic( + user: User, roomInfo: RoomInfo, - loginRes: LoginResponse, roomUsersDictionary?: Dictionary, - roomUsersShortDictionary?: Dictionary, - defaultProfileImage?: string, - defaultProfileImageMulti?: string + roomUsersShortDictionary?: Dictionary ): string { - defaultProfileImage = defaultProfileImage || this.defaultProfileImage; - defaultProfileImageMulti = - defaultProfileImageMulti || this.defaultProfileImageMulti; - let roomImage = ''; if (!!roomInfo) { switch (roomInfo.roomType) { case RoomType.Mytalk: { - if (!!loginRes && !!loginRes.userInfo) { - roomImage = loginRes.userInfo.profileImageFile; + if (!!user && !!user.info) { + roomImage = user.info.profileImageFile; } } break; case RoomType.Multi: { - roomImage = defaultProfileImageMulti; + // default image } break; default: { const roomUsers = this.getRoomUserList( - loginRes, + user, roomInfo.roomId, roomUsersDictionary, roomUsersShortDictionary @@ -206,8 +290,47 @@ export class AppChatService { } } - if (roomImage.trim().length === 0) { - roomImage = defaultProfileImage; + return roomImage; + } + getRoomProfileImage( + user: User, + roomInfo: RoomInfo, + roomUsersMap?: RoomUserMap, + roomUsersShortMap?: RoomUserShortMap + ): string { + let roomImage = ''; + + if (!!roomInfo) { + switch (roomInfo.roomType) { + case RoomType.Mytalk: + { + if (!!user && !!user.info) { + roomImage = user.info.profileImageFile; + } + } + break; + case RoomType.Multi: + { + // default image + } + break; + default: + { + const roomUsers = this.getRoomUserList01( + user, + roomUsersMap, + roomUsersShortMap + ); + if ( + !!roomUsers && + !!roomUsers.existUsers && + roomUsers.users.length > 0 + ) { + roomImage = roomUsers.users[0].profileImageFile; + } + } + break; + } } return roomImage; @@ -219,7 +342,7 @@ export class AppChatService { * roomUser 가 detail 정보라 우선함. */ getRoomUserList( - loginRes: LoginResponse, + user: User, roomId: string, roomUsersDictionary?: Dictionary, roomUsersShortDictionary?: Dictionary @@ -237,12 +360,37 @@ export class AppChatService { if (!!roomUsers && roomUsers.userInfos.length > 0) { existUsers = true; users = roomUsers.userInfos.filter( - (userInfo) => userInfo.seq !== Number(loginRes.userSeq) + (userInfo) => String(userInfo.seq) !== String(user.info.seq) ); } else if (!!roomUsersShort && roomUsersShort.userInfos.length > 0) { existUsers = true; users = roomUsersShort.userInfos.filter( - (userInfo) => userInfo.seq !== Number(loginRes.userSeq) + (userInfo) => String(userInfo.seq) !== String(user.info.seq) + ); + } + + return { + existUsers, + users + }; + } + getRoomUserList01( + user: User, + roomUsersMap?: RoomUserMap, + roomUsersShortMap?: RoomUserShortMap + ) { + let users = []; + let existUsers = false; + + if (!!roomUsersMap && roomUsersMap.userInfos.length > 0) { + existUsers = true; + users = roomUsersMap.userInfos.filter( + (userInfo) => String(userInfo.seq) !== String(user.info.seq) + ); + } else if (!!roomUsersShortMap && roomUsersShortMap.userInfos.length > 0) { + existUsers = true; + users = roomUsersShortMap.userInfos.filter( + (userInfo) => String(userInfo.seq) !== String(user.info.seq) ); } @@ -306,12 +454,13 @@ export class AppChatService { /** Send Masstext message */ sendMessageOfMassText( loginRes: LoginResponse, + user: User, deviceType: DeviceType, roomId: string, sentMessage: string ) { const req: MassTalkSaveRequest = { - userSeq: loginRes.userSeq, + userSeq: String(user.info.seq), deviceType, token: loginRes.tokenString, content: sentMessage.replace(/"/g, '\\"'), @@ -325,7 +474,7 @@ export class AppChatService { map((res) => { if (res.statusCode === StatusCode.Success) { this.sendEvent( - loginRes.userSeq, + String(user.info.seq), roomId, EventType.MassText, res.returnJson @@ -341,6 +490,30 @@ export class AppChatService { .subscribe(); } + copyFromContentText(text: string) { + if (this.clipboardService.copyFromContent(text)) { + alert('복사완료'); + // this.snackBarService.open( + // this.translateService.instant('common:clipboard.results.copied'), + // '', + // { + // duration: 3000, + // verticalPosition: 'top', + // horizontalPosition: 'center' + // } + // ); + } + } + + massTextDownload(massTalkDownloadReq: MassTalkDownloadRequest) { + this.commonApiService + .massTalkDownload(massTalkDownloadReq) + .pipe(take(1)) + .subscribe((res) => { + this.copyFromContentText(res.content); + }); + } + async sendMessageOfSticker( senderSeq: string, roomId: string, @@ -357,10 +530,10 @@ export class AppChatService { AlertDialogData, AlertDialogResult >(AlertDialogComponent, { - panelClass: 'miniSize-dialog', + panelClass: 'min-create-dialog', data: { - title: this.i18nService.t('errors.label'), - message: this.i18nService.t('errors.maxLengthOfMassText', { + title: this.i18nService.t('chat:errors.label'), + message: this.i18nService.t('chat:errors.maxLengthOfMassText', { maxLength: environment.productConfig.chat.masstextLength }) } @@ -386,128 +559,58 @@ export class AppChatService { /** Send Translation message */ sendMessageOfTranslate( - loginRes: LoginResponse, - deviceType: DeviceType, - destLocale: string, + senderSeq: string, roomId: string, - sentMessage: string, - selectedSticker?: StickerFilesInfo + eventType: EventType, + sentMessage: string ) { - // const destLocale = this.destLocale; - // const original = message; - // const roomSeq = this.roomInfoSubject.value.roomSeq; - // if (!!this.isTranslationProcess) { - // return; - // } - // this.isTranslationProcess = true; - // this.commonApiService - // .translationSave({ - // userSeq: this.loginResSubject.value.userSeq, - // deviceType: this.environmentsInfo.deviceType, - // token: this.loginResSubject.value.tokenString, - // roomSeq, - // original, - // srcLocale: '', - // destLocale - // } as TranslationSaveRequest) - // .pipe( - // take(1), - // map((res) => { - // if (res.statusCode === StatusCode.Success) { - // let sentMessage = ''; - // let eventType = EventType.Translation; - // let previewObject: TranslationEventJson | MassTranslationEventJson; - // if (res.translationSeq > 0) { - // // Mass Text Translation - // previewObject = res; - // sentMessage = res.returnJson; - // eventType = EventType.MassTranslation; - // } else { - // // Normal Text Translation - // previewObject = { - // locale: destLocale, - // original, - // translation: res.translation, - // stickername: '', - // stickerfile: !!this.selectedSticker - // ? this.selectedSticker.index - // : '' - // }; - // sentMessage = JSON.stringify(previewObject); - // eventType = EventType.Translation; - // } - // if (!!this.translationPreview) { - // // preview - // this.translationPreviewInfo = { - // previewInfo: res, - // translationType: eventType - // }; - // this.changeDetectorRef.detectChanges(); - // } else { - // // direct send - // this.store.dispatch( - // EventStore.send({ - // senderSeq: this.loginResSubject.value.userSeq, - // req: { - // roomSeq, - // eventType, - // sentMessage - // } - // }) - // ); - // if (!!this.translationPreviewInfo) { - // this.translationPreviewInfo = null; - // } - // } - // if (!!this.selectedSticker) { - // this.isShowStickerSelector = false; - // this.setStickerHistory(this.selectedSticker); - // this.selectedSticker = null; - // } - // } else { - // this.isTranslationProcess = false; - // this.dialogService.open< - // AlertDialogComponent, - // AlertDialogData, - // AlertDialogResult - // >(AlertDialogComponent, { - // panelClass: 'miniSize-dialog', - // data: { - // title: '', - // message: this.translateService.instant( - // 'chat.error.translateServerError' - // ) - // } - // }); - // this.logger.error('res', res); - // } - // }), - // catchError((error) => { - // this.isTranslationProcess = false; - // this.dialogService.open< - // AlertDialogComponent, - // AlertDialogData, - // AlertDialogResult - // >(AlertDialogComponent, { - // panelClass: 'miniSize-dialog', - // data: { - // title: '', - // message: this.translateService.instant( - // 'chat.error.translateServerError' - // ) - // } - // }); - // return of(this.logger.error('error', error)); - // }) - // ) - // .subscribe(() => { - // this.isTranslationProcess = false; - // }); + this.sendEvent(senderSeq, roomId, eventType, sentMessage); } + /** + * Mass Translation message detail + */ + massTranslationMessageDetail( + req: TransMassTalkDownloadRequest, + message: Info, + contentsType: string, + roomId: string + ) { + this.commonApiService + .transMassTalkDownload(req) + .pipe(take(1)) + .subscribe((res: TransMassTalkDownloadResponse) => { + let contents = ''; + + if (res.statusCode === StatusCode.Success) { + contents = contentsType === 'T' ? res.translation : res.original; + } else { + contents = + contentsType === 'T' + ? message.sentMessageJson.translation + : message.sentMessageJson.original; + } + + this.dialog.open< + TransDetailDialogComponent, + TransDetailDialogData, + TransDetailDialogResult + >(TransDetailDialogComponent, { + panelClass: 'mid-create-dialog', + data: { + contents, + message, + roomId, + defaultProfileImage: this.defaultProfileImage + } + }); + return; + }); + } /** Send AttachFile message */ sendMessageOfAttachFile( loginRes: LoginResponse, + user: User, deviceType: DeviceType, roomId: string, fileUploadItems: FileUploadItem[] @@ -546,7 +649,7 @@ export class AppChatService { } const req: FileTalkSaveRequest = { - userSeq: loginRes.userSeq, + userSeq: String(user.info.seq), deviceType, token: loginRes.tokenString, roomId, @@ -572,39 +675,74 @@ export class AppChatService { ); } - forkJoin(allObservables) - .pipe(take(1)) - .subscribe( - (resList) => { - for (const res of resList) { - this.store.dispatch( - ChattingActions.send({ - senderSeq: loginRes.userSeq, - req: { - roomId, - eventType: EventType.File, - sentMessage: JSON.stringify(res.returnJson) - } as SendEventRequest - }) - ); + this.ngZone.run(() => { + forkJoin(allObservables) + .pipe(take(1)) + .subscribe( + (resList) => { + for (const res of resList) { + this.store.dispatch( + ChattingActions.send({ + senderSeq: String(user.info.seq), + req: { + roomId, + eventType: EventType.File, + sentMessage: JSON.stringify(res.returnJson) + } as SendEventRequest + }) + ); + } + }, + (error) => { + this.logService.debug('onFileSelected error', error); + reject(error); + }, + () => { + resolve(true); } - }, - (error) => { - this.logService.debug('onFileSelected error', error); - const msg = this.i18nService.t('common.file.errors.failToUpload'); - alert(msg); - - reject(msg); - }, - () => { - resolve(true); - } - ); + ); + }); }; return new Promise(executor); } + /** + * Open Dialog For 'Exit Room'. + */ + exitRoomDialog(roomInfo: RoomInfo): void { + if (!roomInfo) { + return; + } + + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('chat:label.exitFromRoom'), + html: this.i18nService.t('chat:dialog.confirmExitFromRoom') + } + }); + + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && !!result.choice) { + this.store.dispatch( + RoomActions.del({ + req: { + roomId: roomInfo.roomId + } as ExitRequest + }) + ); + } + }); + } + /** * Open Dialog for 'New Room Open'. */ @@ -614,8 +752,7 @@ export class AppChatService { CreateDialogData, CreateDialogResult >(CreateDialogComponent, { - width: '100%', - height: '100%', + panelClass: 'max-create-dialog', data: {} }); @@ -635,12 +772,34 @@ export class AppChatService { .subscribe(); } - newOpenRoom( - userSeqs: string[], - isTimerRoom: boolean, - loginRes?: LoginResponse - ) { - if (!userSeqs || userSeqs.length === 0) { + openRoombyRoomId(roomId: string): void { + this.router.navigate( + [ + 'chat', + { + outlets: { content: 'chatroom' } + } + ], + { + queryParams: { roomId } + } + ); + } + + newOpenRoom(userSeqs: string[], isTimerRoom: boolean, user?: User) { + // popup all close. + this.dialog.closeAll(); + + if ( + !userSeqs || + userSeqs.length === 0 || + userSeqs.length > (environment.productConfig.chat.maxChatRoomUser || 299) + ) { + this.logService.error( + `open room user over size by ${ + environment.productConfig.chat.maxChatRoomUser || 299 + }` + ); return; } isTimerRoom = isTimerRoom || false; @@ -661,13 +820,13 @@ export class AppChatService { let req: OpenRequest; if ( userSeqs.length === 1 && - !!loginRes && - userSeqs[0] === loginRes.userSeq + !!user && + String(userSeqs[0]) === String(user.info.seq) ) { // MyTalk Open. req = { divCd: 'OPMYTALK', - userSeqs: [loginRes.talkWithMeBotSeq + ''] + userSeqs: [user.talkWithMeBotSeq + ''] }; } else { req = { @@ -678,4 +837,24 @@ export class AppChatService { this.store.dispatch(RoomActions.create({ req })); } } + + openFileviwer(data: FileViewerDialogData): void { + this.dialog.open< + FileViewerDialogComponent, + FileViewerDialogData, + FileViewerDialogResult + >(FileViewerDialogComponent, { + // position: { + // top: '50px' + // }, + maxWidth: '100vw', + maxHeight: '100vh', + // height: 'calc(100% - 50px)', + height: '100%', + width: '100%', + hasBackdrop: false, + panelClass: 'app-dialog-full', + data + }); + } } diff --git a/src/app/services/app-file.service.ts b/src/app/services/app-file.service.ts index 0dc2f68..0fa0c53 100644 --- a/src/app/services/app-file.service.ts +++ b/src/app/services/app-file.service.ts @@ -1,10 +1,32 @@ -import { Injectable, Inject } from '@angular/core'; +import { of } from 'rxjs'; +import { take, map, finalize, catchError } from 'rxjs/operators'; + +import { Injectable, Inject, NgZone } from '@angular/core'; -import { FileUtil } from '@ucap/core'; -import { I18nService, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; -import { CommonApiService } from '@ucap/ng-api-common'; import { Store } from '@ngrx/store'; +import { FileUtil, LoginSession, MimeUtil } from '@ucap/core'; +import { NativeService } from '@ucap/native'; +import { FileDownloadItem, StatusCode } from '@ucap/api'; +import { FileProfileSaveRequest } from '@ucap/api-common'; +import { LoginResponse } from '@ucap/protocol-authentication'; +import { FileEventJson } from '@ucap/protocol-event'; +import { DownCheckRequest } from '@ucap/protocol-file'; +import { UserInfoUpdateType, User } from '@ucap/protocol-info'; + +import { I18nService } from '@ucap/ng-i18n'; +import { CommonApiService } from '@ucap/ng-api-common'; +import { LogService } from '@ucap/ng-logger'; +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; +import { FileProtocolService } from '@ucap/ng-protocol-file'; + +import { UserActions } from '@ucap/ng-store-organization'; +import { ChattingActions } from '@ucap/ng-store-chat'; + +import { UserStore } from '@app/models/user-store'; + +import { AppAuthenticationService } from './app-authentication.service'; + @Injectable({ providedIn: 'root' }) @@ -12,10 +34,13 @@ export class AppFileService { constructor( private store: Store, private i18nService: I18nService, - private commonApiService: CommonApiService - ) { - this.i18nService.setDefaultNamespace('common'); - } + private commonApiService: CommonApiService, + private fileProtocolService: FileProtocolService, + private appAuthenticationService: AppAuthenticationService, + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, + private ngZone: NgZone, + private logService: LogService + ) {} async validUploadFile( fileList: FileList, @@ -28,7 +53,7 @@ export class AppFileService { for (let i = 0; i < fileList.length; i++) { const file = fileList[i]; if (file.size > fileAllowSize * 1024 * 1024) { - const msg = this.i18nService.t('common.file.errors.oversize', { + const msg = this.i18nService.t('common:file.errors.oversize', { maxSize: fileAllowSize }); alert(msg); @@ -43,7 +68,7 @@ export class AppFileService { FileUtil.getExtensions(fileList) ); if (!!checkExt) { - const msg = this.i18nService.t('common.file.errors.notSupporedType', { + const msg = this.i18nService.t('common:file.errors.notSupporedType', { supporedType: checkExt.join(',') }); alert(msg); @@ -56,7 +81,7 @@ export class AppFileService { // horizontalPosition: 'center', // data: { // html: this.translateService.instant( - // 'common.file.errors.notSupporedType', + // 'common:file.errors.notSupporedType', // { // supporedType: checkExt.join(',') // } @@ -72,7 +97,7 @@ export class AppFileService { fileList ); if (!!fakeMedia) { - const msg = this.i18nService.t('common.file.errors.notAcceptableMime', { + const msg = this.i18nService.t('common:file.errors.notAcceptableMime', { supporedType: fakeMedia.join(',') }); alert(msg); @@ -83,4 +108,233 @@ export class AppFileService { return valid; } + + validUploadProfileFile( + file: File, + fileAllowSize: number = 50 + ): Promise { + return new Promise(async (resolve, reject) => { + try { + if (file.size > fileAllowSize * 1024 * 1024) { + const msg = this.i18nService.t('common:file.errors.oversize', { + maxSize: fileAllowSize + }); + alert(msg); + + resolve(false); + } + const fakeMedia = await this.commonApiService.mediaFiles([file]); + if (!fakeMedia) { + const extension = FileUtil.getExtension( + file.name + ).toLocaleLowerCase(); + const msg = this.i18nService.t( + 'common:file.errors.notAcceptableMime', + { + supporedType: extension + } + ); + alert(msg); + + resolve(false); + } + resolve(true); + } catch (error) { + reject(error); + } + }); + } + + fileProfileSave(request: FileProfileSaveRequest, profileUploadUrl: string) { + this.validUploadProfileFile(request.fileUploadItem.file) + .then((res) => { + if (!!res) { + this._fileProfileSave(request, profileUploadUrl); + } + }) + .catch((resone) => { + this.logService.error(resone); + }); + } + private _fileProfileSave( + request: FileProfileSaveRequest, + profileUploadUrl: string + ) { + this.commonApiService + .fileProfileSave(request, profileUploadUrl) + .pipe( + take(1), + map((res) => { + console.log(res); + if (!res) { + return; + } + if (StatusCode.Success === res.statusCode) { + // this.onUpdateProfile(res.profileURL); + return res; + } else { + throw res; + } + }), + finalize(() => { + setTimeout(() => { + request.fileUploadItem.uploadingProgress$ = undefined; + }, 1000); + }) + ) + .subscribe( + (res) => { + this.logService.debug('profile: ', res); + const findIdx = res.profileURL.indexOf('ProfileImage'); + let imgInfo: string = res.profileURL; + + if (findIdx > -1) { + const startIdx = res.profileURL.indexOf('/', findIdx); + imgInfo = res.profileURL.substring(startIdx); + } + + this.store.dispatch( + UserActions.modifyInfo({ + req: { + type: UserInfoUpdateType.Image, + info: imgInfo + } + }) + ); + }, + (error) => { + this.logService.error(error); + // this.snackBarService.open( + // this.translateService.instant( + // 'profile.errors.failToChangeProfileImage' + // ), + // '', + // { + // duration: 3000, + // verticalPosition: 'bottom' + // } + // ); + } + ); + } + saveFile( + fileParams: { + fileInfo: FileEventJson; + fileDownloadItem: FileDownloadItem; + type: string; + fileName: string; + fileDownloadUrl?: string; + savePath?: string; + }, + loginRes: LoginResponse, + user: User, + loginSession: LoginSession + ) { + const fileName = fileParams.fileInfo.fileName; + const fileDownloadItem = fileParams.fileDownloadItem; + const fileDownloadUrl = fileParams.fileDownloadUrl; + const attachmentsSeq = fileParams.fileInfo.attachmentSeq; + const savePath = fileParams.savePath; + let downloadPath; + + const userStore: UserStore = this.appAuthenticationService.getUserStore(); + + if (!!userStore) { + downloadPath = userStore.settings.chat.downloadPath; + } + + const fileTalkDownloadError = (reason: any) => { + this.logService.warn(reason); + // this.snackBarService.openFromComponent< + // AlertSnackbarComponent, + // AlertSnackbarData + // >(AlertSnackbarComponent, { + // data: { + // html: this.translateService.instant('common:file.errors.failToSave'), + // buttonText: this.translateService.instant('common:file.errors.label') + // } + // }); + }; + + this.ngZone.run(() => { + this.commonApiService + .fileTalkDownload( + { + userSeq: String(user.info.seq), + deviceType: loginSession.deviceType, + token: loginRes.tokenString, + attachmentsSeq: attachmentsSeq + '', + fileDownloadItem + }, + fileDownloadUrl + ) + .pipe( + take(1), + map((rawBlob) => { + const mimeType = MimeUtil.getMimeFromExtension( + FileUtil.getExtension(fileName) + ); + const blob = rawBlob.slice(0, rawBlob.size, mimeType); + + FileUtil.fromBlobToBuffer(blob) + .then((buffer) => { + /** download check */ + this.store.dispatch( + ChattingActions.fileDownCheck({ + req: { seq: attachmentsSeq } as DownCheckRequest + }) + ); + + this.nativeService + .file_save(buffer, fileName, mimeType, savePath) + .then((filePath) => { + if (!!filePath) { + // const snackBarRef = this.snackBarService.open( + // this.translateService.instant( + // 'common:file.results.savedToPath', + // { + // path: filePath + // } + // ), + // this.translateService.instant('common:file.open'), + // { + // duration: 3000, + // verticalPosition: 'bottom', + // horizontalPosition: 'center' + // } + // ); + // snackBarRef.onAction().subscribe(() => { + // snackBarRef.dismiss(); + // this.ngZone.runOutsideAngular(() => { + // this.nativeService + // .openTargetItem(filePath) + // .catch((reason) => { + // this.logger.warn(reason); + // }); + // }); + // }); + } else { + fileTalkDownloadError('fail'); + } + }) + .catch((reason) => { + fileTalkDownloadError(reason); + }); + }) + .catch((reason) => { + fileTalkDownloadError(reason); + }); + }), + finalize(() => { + if (!!fileDownloadItem) { + setTimeout(() => { + fileDownloadItem.downloadingProgress$ = undefined; + }, 1000); + } + }), + catchError((error) => of(error)) + ) + .subscribe(); + }); + } } diff --git a/src/app/services/app-group.service.ts b/src/app/services/app-group.service.ts new file mode 100644 index 0000000..1862a50 --- /dev/null +++ b/src/app/services/app-group.service.ts @@ -0,0 +1,471 @@ +import { of } from 'rxjs'; +import { take, catchError, map } from 'rxjs/operators'; + +import { Injectable, Inject } from '@angular/core'; + +import { MatDialog } from '@angular/material/dialog'; + +import { Store } from '@ngrx/store'; + +import { NativeService } from '@ucap/native'; +import { GroupDetailData, UserInfo } from '@ucap/protocol-sync'; +import { UserInfoF, UserInfoSS, UserInfoDN } from '@ucap/protocol-query'; +import { UserInfoUpdateType } from '@ucap/protocol-info'; + +import { LogService } from '@ucap/ng-logger'; +import { I18nService } from '@ucap/ng-i18n'; +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; + +import { UserActions } from '@ucap/ng-store-organization'; +import { LoginActions } from '@ucap/ng-store-authentication'; +import { GroupActions, BuddyActions } from '@ucap/ng-store-group'; + +import { + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult, + AlertDialogComponent, + AlertDialogData, + AlertDialogResult +} from '@ucap/ng-ui'; + +import { EditInlineInputDialogComponent } from '@app/sections/group/dialogs/edit-inline-input.dialog.component'; +import { UserInfoTypes, GroupManageType } from '@app/types'; +import { + EditUserDialogComponent, + EditUserDialogData, + EditUserDialogResult +} from '@app/sections/group/dialogs/edit-user.dialog.component'; + +@Injectable({ + providedIn: 'root' +}) +export class AppGroupService { + constructor( + private dialog: MatDialog, + private store: Store, + private i18nService: I18nService, + protected logService: LogService, + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService + ) {} + + addMemberToGroup( + targetGroup: GroupDetailData, + userInfoList: UserInfoTypes[] + ) { + const targetUserSeqs: string[] = [...targetGroup.userSeqs]; + + userInfoList.map((userInfo) => { + const find = targetUserSeqs.indexOf(String(userInfo.seq)); + if (find < 0) { + targetUserSeqs.push(String(userInfo.seq)); + } + }); + + this._updateMember(targetGroup, targetUserSeqs); + } + + moveMemberToGroup( + fromGroup: GroupDetailData, + toGroup: GroupDetailData, + userInfoList: UserInfoTypes[] + ) { + const targetUserSeqs: string[] = []; + + userInfoList.map((userInfo) => { + targetUserSeqs.push(String(userInfo.seq)); + }); + + this.store.dispatch( + GroupActions.moveMember({ + fromGroup, + toGroup, + targetUserSeq: targetUserSeqs + }) + ); + } + copyMemberToGroup( + targetGroup: GroupDetailData, + userInfoList: UserInfoTypes[] + ) { + const targetUserSeqs: string[] = [...targetGroup.userSeqs]; + + userInfoList.map((userInfo) => { + const find = targetUserSeqs.indexOf(String(userInfo.seq)); + if (find < 0) { + targetUserSeqs.push(String(userInfo.seq)); + } + }); + + this._updateMember(targetGroup, targetUserSeqs); + } + removeMemberToGroup( + contens: string, + userInfoList: UserInfoTypes[], + group: GroupDetailData + ): Promise { + return new Promise((resolve, rejects) => { + try { + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('group:dialog.title.removeBuddy'), + html: contens + } + }); + dialogRef + .afterClosed() + .pipe( + take(1), + map((result) => { + if (!!result && result.choice) { + const trgtUserSeq: string[] = []; + const selectSeq: string[] = []; + + userInfoList.map((user) => selectSeq.push(String(user.seq))); + + group.userSeqs.filter((buddySeq) => { + if ( + !trgtUserSeq.includes(buddySeq) && + !selectSeq.includes(buddySeq) + ) { + trgtUserSeq.push(buddySeq); + } + }); + + this._updateMember(group, trgtUserSeq); + resolve(); + } + }), + catchError((err) => { + return of(err); + }) + ) + .subscribe(); + } catch (err) { + rejects(); + } + }); + } + + createGroup(groupName: string, userInfoList: UserInfoTypes[]): Promise { + return new Promise((resolve, rejects) => { + try { + const targetUserSeqs: string[] = []; + userInfoList.map((user) => targetUserSeqs.push(String(user.seq))); + this.store.dispatch( + GroupActions.create({ + groupName, + targetUserSeqs + }) + ); + resolve(); + } catch (error) { + rejects(error); + } + }); + } + removeGroup(groupBuddyList: { + group: GroupDetailData; + buddyList: UserInfo[]; + }): Promise { + return new Promise((resolve, rejects) => { + try { + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('group:dialog.title.removeGroup'), + html: this.i18nService.t('group:dialog.removeGroupConfirm', { + targetGroup: `${groupBuddyList.group.name}` + }) + } + }); + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && !!result.choice) { + this.store.dispatch( + GroupActions.del({ group: groupBuddyList.group }) + ); + + resolve(); + } + }); + } catch (error) { + rejects(error); + } + }); + } + updateGroupName(group: GroupDetailData, rect: any): Promise { + return new Promise((resolve, rejects) => { + try { + const dialogRef = this.dialog.open(EditInlineInputDialogComponent, { + width: rect.width, + height: rect.height, + panelClass: 'ucap-edit-group-name-dialog', + data: { + type: 'GROUP_NAME', + curValue: group.name, + placeholder: this.i18nService.t('group:dialog.newGroupName'), + maxLength: 20, + left: rect.left, + top: rect.top + } + }); + + dialogRef + .afterClosed() + .pipe( + take(1), + map((result) => { + if ( + !!result && + result.choice && + result.curValue.localeCompare(name) !== 0 + ) { + this.store.dispatch( + GroupActions.update({ + req: { + groupSeq: group.seq, + groupName: result.curValue, + userSeqs: group.userSeqs + } + }) + ); + resolve(); + } + }), + catchError((err) => { + return of(err); + }) + ) + .subscribe(); + } catch (error) { + rejects(error); + } + }); + } + + updateBuddyByToggle(params: { + userInfo: UserInfoSS; + isBuddy: boolean; + }): Promise { + return new Promise((resolve, rejects) => { + try { + if (params.isBuddy) { + // 동료추가 + const dialogRef = this.dialog.open< + EditUserDialogComponent, + EditUserDialogData, + EditUserDialogResult + >(EditUserDialogComponent, { + width: '100%', + height: '100%', + data: { + title: this.i18nService.t('group:dialog.title.addBuddy'), + type: GroupManageType.Add, + userInfos: [params.userInfo] + } + }); + dialogRef + .afterClosed() + .pipe( + take(1), + map((result: EditUserDialogResult) => { + if (result.type === GroupManageType.Add) { + if ( + !!result.selectGroupList && + result.selectGroupList.length > 0 + ) { + result.selectGroupList.forEach((g) => { + this.addMemberToGroup(g, result.selelctUserList); + }); + } + } else if (result.type === GroupManageType.Create) { + this.createGroup(result.groupName, result.selelctUserList); + } + resolve(false); + }), + catchError((err) => { + return of(err); + }) + ) + .subscribe(); + } else { + // 동료삭제 + + this.removeBuddy(params.userInfo) + .then(() => { + resolve(true); + }) + .catch((reason) => {}) + .finally(() => {}); + } + } catch (error) { + rejects(error); + } + }); + } + + updateBuddy(userInfo: UserInfoTypes, isFavorite: boolean): Promise { + return new Promise((resolve, rejects) => { + try { + this.store.dispatch( + BuddyActions.update({ + req: { seq: Number(userInfo.seq), isFavorit: isFavorite } + }) + ); + resolve(); + } catch (error) { + rejects(error); + } + }); + } + removeBuddy(userInfo: UserInfoTypes): Promise { + return new Promise((resolve, rejects) => { + try { + const tempUser = userInfo as + | UserInfo + | UserInfoSS + | UserInfoF + | UserInfoDN; + + const dialogRef = this.dialog.open< + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult + >(ConfirmDialogComponent, { + panelClass: 'min-create-dialog', + data: { + title: this.i18nService.t('group:dialog.title.removeBuddy'), + html: this.i18nService.t( + 'group:dialog.removeBuddyFromProfile', + + { target: `${tempUser.name} ${tempUser.grade}` } + ) + } + }); + + dialogRef + .afterClosed() + .pipe(take(1)) + .subscribe((result) => { + if (!!result && !!result.choice) { + this.store.dispatch( + BuddyActions.delAndClear({ seq: Number(userInfo.seq) }) + ); + + resolve(); + } + }); + } catch (error) { + rejects(error); + } + }); + } + updateNickname( + userInfo: UserInfoF, + nickname: string, + rect?: any + ): Promise { + return new Promise((resolve, rejects) => { + try { + if (!!rect) { + const dialogRef = this.dialog.open(EditInlineInputDialogComponent, { + width: rect.width - 30 + '', + height: rect.height, + panelClass: 'ucap-edit-group-name-dialog', + data: { + type: 'NICKNAME', + curValue: userInfo.nickName, + placeholder: this.i18nService.t('group:dialog.newNickname'), + maxLength: 20, + left: rect.left + 70, + top: rect.top + } + }); + + dialogRef + .afterClosed() + .pipe( + take(1), + map((result) => { + if ( + !!result && + result.choice && + result.curValue.localeCompare(userInfo.nickName) !== 0 + ) { + this._updateNickname(userInfo.seq, result.curValue); + resolve(); + } + }), + catchError((err) => { + return of(err); + }) + ) + .subscribe(); + } else { + if (nickname.trim().localeCompare('') === 0) { + nickname = ' '; + } + this._updateNickname(userInfo.seq, nickname); + resolve(); + } + } catch (error) { + rejects(error); + } + }); + } + + updateIntro(info: string, type: UserInfoUpdateType) { + if (!!info && info.trim().localeCompare('') === 0) { + info = ' '; + } + this.store.dispatch( + UserActions.modifyInfo({ + req: { + type, + info + } + }) + ); + } + private _updateNickname(userSeq: number | string, nickname: string) { + let userSeqf: number; + try { + userSeqf = Number(userSeq); + } catch (error) { + this.logService.error(error); + } + + this.store.dispatch( + BuddyActions.nickname({ + req: { + userSeq: Number(userSeq), + nickname + } + }) + ); + } + + private _updateMember( + targetGroup: GroupDetailData, + targetUserSeqs: string[] + ) { + this.store.dispatch( + GroupActions.updateMember({ + targetGroup, + targetUserSeqs + }) + ); + } +} diff --git a/src/app/services/app-native.service.ts b/src/app/services/app-native.service.ts index 4c23dfa..d476131 100644 --- a/src/app/services/app-native.service.ts +++ b/src/app/services/app-native.service.ts @@ -10,6 +10,7 @@ import { NativeService } from '@ucap/native'; import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; import { LoginSelector } from '@ucap/ng-store-authentication'; +import { AppAccountService } from './app-account.service'; @Injectable({ providedIn: 'root' @@ -17,13 +18,14 @@ import { LoginSelector } from '@ucap/ng-store-authentication'; export class AppNativeService { constructor( @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, + private appAccountService: AppAccountService, private store: Store, private matDialog: MatDialog, private ngZone: NgZone ) {} subscribe(): void { - this.nativeService.logout().subscribe(() => { + this.nativeService.app_onLogout$().subscribe(() => { this.ngZone.run(() => { this.matDialog.closeAll(); @@ -33,11 +35,12 @@ export class AppNativeService { }); }); - this.nativeService.changeStatus().subscribe((statusCode) => {}); + this.nativeService.app_onStatus$().subscribe((statusCode) => {}); - this.nativeService.showSetting().subscribe(() => { + this.nativeService.app_onShowSetting$().subscribe(() => { this.ngZone.run(() => { // this.store.dispatch(SettingsStore.showDialog()); + this.appAccountService.dialogForSettings(); }); }); } diff --git a/src/app/services/app-notification.service.ts b/src/app/services/app-notification.service.ts new file mode 100644 index 0000000..db70825 --- /dev/null +++ b/src/app/services/app-notification.service.ts @@ -0,0 +1,935 @@ +import { of } from 'rxjs'; +import { + tap, + withLatestFrom, + catchError, + finalize, + take, + map +} from 'rxjs/operators'; + +import { Injectable, Inject, NgZone } from '@angular/core'; + +import { MatDialog } from '@angular/material/dialog'; + +import { Store, select } from '@ngrx/store'; +import { Dictionary } from '@ngrx/entity'; + +import { LocaleCode, NotificationMethod } from '@ucap/core'; +import { + NativeService, + WindowState, + NotificationType, + NotificationRequest +} from '@ucap/native'; +import { + SSVC_TYPE_LOGOUT_RES, + LogoutNotification, + SSVC_TYPE_LOGOUT_REMOTE_NOTI, + LogoutRemoteNotification +} from '@ucap/protocol-authentication'; +import { + SSVC_TYPE_EVENT_SEND_RES, + SSVC_TYPE_EVENT_SEND_NOTI, + SSVC_TYPE_EVENT_READ_RES, + SSVC_TYPE_EVENT_READ_NOTI, + SSVC_TYPE_EVENT_CANCEL_NOTI, + SSVC_TYPE_EVENT_DEL_RES, + SendNotification, + ReadNotification, + CancelNotification, + DelNotification as EventDelNotification, + ReadRequest as EventReadRequest, + EventType +} from '@ucap/protocol-event'; +import { + SSVC_TYPE_INFO_USER_NOTI, + UserNotification +} from '@ucap/protocol-info'; +import { + SSVC_TYPE_GROUP_UPD_RES2, + SSVC_TYPE_GROUP_ADD_RES, + SSVC_TYPE_GROUP_DEL_RES, + UpdateNotification as GroupUpdateNotification, + AddNotification as GroupAddNotification, + DelNotification as GroupDelNotification +} from '@ucap/protocol-group'; +import { + SSVC_TYPE_BUDDY_UPD_RES, + SSVC_TYPE_BUDDY_ADD_RES, + SSVC_TYPE_BUDDY_DEL_RES, + UpdateNotification as BuddyUpdateNotification, + AddNotification as BuddyAddNotification, + DelNotification as BuddyDelNotification +} from '@ucap/protocol-buddy'; +import { + SSVC_TYPE_ROOM_INVITE_RES, + SSVC_TYPE_ROOM_INVITE_NOTI, + SSVC_TYPE_ROOM_UPD_RES, + SSVC_TYPE_ROOM_EXIT_RES, + SSVC_TYPE_ROOM_EXIT_NOTI, + SSVC_TYPE_ROOM_EXIT_FORCING_RES, + SSVC_TYPE_ROOM_EXIT_FORCING_NOTI, + SSVC_TYPE_ROOM_FONT_UPD_NOTI, + InviteNotification, + UpdateNotification as RoomUpdateNotification, + ExitNotification, + ExitForcingResponse, + ExitForcingNotification, + UpdateFontNotification +} from '@ucap/protocol-room'; +import { + SSVC_TYPE_STATUS_NOTI, + StatusNotification +} from '@ucap/protocol-status'; +import { + SSVC_TYPE_OPTION_REG_UPD_RES, + RegUpdateNotification +} from '@ucap/protocol-option'; +import { + SSVC_TYPE_UMG_NOTI, + SSVC_TYPE_UMG_DELETE_NOTI, + UmgNotiNotification, + UmgDeleteNotiNotification +} from '@ucap/protocol-umg'; +import { UserInfo } from '@ucap/protocol-sync'; + +import { LogService } from '@ucap/ng-logger'; +import { I18nService } from '@ucap/ng-i18n'; +import { + LocalStorageService, + SessionStorageService +} from '@ucap/ng-web-storage'; +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; +import { AuthenticationProtocolService } from '@ucap/ng-protocol-authentication'; +import { EventProtocolService } from '@ucap/ng-protocol-event'; +import { InfoProtocolService } from '@ucap/ng-protocol-info'; +import { RoomProtocolService } from '@ucap/ng-protocol-room'; +import { GroupProtocolService } from '@ucap/ng-protocol-group'; +import { BuddyProtocolService } from '@ucap/ng-protocol-buddy'; +import { QueryProtocolService } from '@ucap/ng-protocol-query'; +import { StatusProtocolService } from '@ucap/ng-protocol-status'; +import { OptionProtocolService } from '@ucap/ng-protocol-option'; +import { UmgProtocolService } from '@ucap/ng-protocol-umg'; + +import { + RoomSelector, + RoomActions, + ChattingActions, + ChattingSelector, + ChatUtil +} from '@ucap/ng-store-chat'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; +import { CommonActions, UserSelector } from '@ucap/ng-store-organization'; + +import { UserStore } from '@app/models/user-store'; + +import { AppAuthenticationService } from './app-authentication.service'; + +@Injectable() +export class AppNotificationService { + constructor( + private authenticationProtocolService: AuthenticationProtocolService, + private eventProtocolService: EventProtocolService, + private infoProtocolService: InfoProtocolService, + private roomProtocolService: RoomProtocolService, + private groupProtocolService: GroupProtocolService, + private buddyProtocolService: BuddyProtocolService, + private queryProtocolService: QueryProtocolService, + private statusProtocolService: StatusProtocolService, + private optionProtocolService: OptionProtocolService, + private umgProtocolService: UmgProtocolService, + + private localStorageService: LocalStorageService, + private sessionStorageService: SessionStorageService, + private appAuthenticationService: AppAuthenticationService, + + private store: Store, + private ngZone: NgZone, + private dialog: MatDialog, + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, + private i18nService: I18nService, + private logService: LogService + ) {} + + public subscribe(): void { + this.authenticationProtocolService.logoutNotification$ + .pipe( + tap((notiOrRes) => { + switch (notiOrRes.SSVC_TYPE) { + case SSVC_TYPE_LOGOUT_RES: + { + const res = notiOrRes as LogoutNotification; + this.logService.debug( + 'Notification::authenticationProtocolService::LogoutResponse', + res + ); + + // this.sessionStorageService.set(KEY_LOGOUT_INFO, { + // personLogout: true, + // reasonCode: res.reasonCode, + // ip: res.ip, + // mac: res.mac + // } as LogoutInfo); + } + break; + case SSVC_TYPE_LOGOUT_REMOTE_NOTI: + { + const noti = notiOrRes as LogoutRemoteNotification; + this.logService.debug( + 'Notification::authenticationProtocolService::LogoutRemoteNotification', + noti + ); + + // this.sessionStorageService.set(KEY_LOGOUT_INFO, { + // personLogout: true, + // reasonCode: ServerErrorCode.ERRCD_FORCE_INIT, + // forceType: noti.requestDeviceType + // } as LogoutInfo); + } + break; + default: + break; + } + this.dialog.closeAll(); + + // this.store.dispatch(AuthenticationStore.loginRedirect()); + }) + ) + .subscribe(); + + this.eventProtocolService.notification$ + .pipe( + withLatestFrom( + this.store.pipe(select(UserSelector.user)), + this.store.pipe(select(ConfigurationSelector.versionInfo2Response)), + this.store.pipe(select(ChattingSelector.activeRoomId)), + this.store.pipe(select(RoomSelector.rooms)), + this.store.pipe(select(RoomSelector.roomUsers)), + this.store.pipe( + select( + (state: any) => + state.group.buddy.buddies.entities as Dictionary + ) + ) + ), + tap( + ([ + notiOrRes, + user, + versionInfo2Res, + activeRoomId, + roomList, + roomUsers, + buddyList + ]) => { + switch (notiOrRes.SSVC_TYPE) { + case SSVC_TYPE_EVENT_SEND_RES: + case SSVC_TYPE_EVENT_SEND_NOTI: + { + const noti = notiOrRes as SendNotification; + const roomId = noti.roomId; + const eventInfo = noti.info; + const trgtRoom = roomList.find( + (roomInfo) => roomInfo.roomId === roomId + ); + + this.logService.debug( + 'Notification::eventProtocolService::SendNotification', + noti + ); + + // Add Event.. + this.store.dispatch( + ChattingActions.addEvent({ + roomId, + info: eventInfo + }) + ); + + // Unread Count.. + let doReadRequest = false; + if (!!activeRoomId && activeRoomId === roomId) { + // Active room. + doReadRequest = true; + } + + // Get Window state. + const windowState = this.nativeService.window_onState$() + .value; + const isWindowFocusState = this.nativeService.window_onFocus$() + .value; + + // // unread count.. + // // 현재 방이 열려 있고, + // if ( + // !!curRoomInfo && + // !!curRoomInfo.roomSeq && + // curRoomInfo.roomSeq === noti.roomSeq + // ) { + // // 윈도우의 상태가 최소화, tray 상태가 아니면서 조직도탭을 보고 있지 않다면, + // if ( + // !!windowState && + // windowState.windowState !== WindowState.Minimized && + // windowState.windowState !== WindowState.Hidden && + // windowState.windowFocusState === + // ElectronBrowserWindowChannel.Focus && + // gnbMenuIndex !== MainMenu.Organization + // ) { + // // 대화방을 보고 있다고 판단하고 event_read_req 한다. + // doReadRequest = true; + // } + // } + + if (doReadRequest) { + this.store.dispatch( + ChattingActions.read({ + roomId, + lastReadSeq: noti.info.seq + } as EventReadRequest) + ); + } else { + // // not opened room :: unread count increased + if (notiOrRes.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_RES) { + /** + * 다른 디바이스에서 대화를 송신 할경우 RES 가 noti 로 유입될 수 있다. + * 이때 unread count 를 중가하지 않는다. + */ + } else { + if ( + !!trgtRoom && + noti.info.type !== EventType.Join && + noti.info.type !== EventType.Exit && + noti.info.type !== EventType.RenameRoom && + noti.info.type !== EventType.ForcedExit + ) { + this.store.dispatch( + RoomActions.updateUnreadCount({ + roomId, + noReadCnt: trgtRoom.noReadCnt + 1 + }) + ); + } + } + } + + // notification.. + if (notiOrRes.SSVC_TYPE === SSVC_TYPE_EVENT_SEND_NOTI) { + let doNoti = true; + + // 방별 알림이 꺼져 있으면 노티 안함. > 우선순위 최상위. + if (!!trgtRoom && !trgtRoom.receiveAlarm) { + doNoti = false; + } + + // 현재 열려 있는 방일경우 노티 안함. + if (!!activeRoomId && activeRoomId === roomId) { + if ( + !!windowState && + windowState !== WindowState.Minimized && + windowState !== WindowState.Hidden && + !!isWindowFocusState + ) { + doNoti = false; + } + } + // // 포커스 아웃일때 무조건 노티. + // // Case 1 : 단순 포커스 아웃. + // // Case 2 : hidden 시 포커스 인 상태이지만 위에서 필터링 됨. + // if (!isWindowFocusState) { + // doNoti = true; + // } + + if (doNoti) { + const userStore: UserStore = this.appAuthenticationService.getUserStore(); + if (userStore.settings.notification.use) { + if ( + userStore.settings.notification.method === + NotificationMethod.Sound + ) { + const audio = new Audio( + 'assets/sounds/messageAlarm.mp3' + ); + audio.play(); + } else { + const contents = ChatUtil.convertFinalEventMessage( + noti.eventType, + noti.info.sentMessageJson + ); + if (!!contents) { + const notiReq: NotificationRequest = { + type: NotificationType.Event, + seq: roomId, + title: this.i18nService.t( + 'common:notification.titleChatEventArrived' + ), + contents, + image: '', + useSound: [ + NotificationMethod.Sound, + NotificationMethod.SoundAndAlert + ].some( + (n) => + n === userStore.settings.notification.method + ) + ? true + : false, + displayTime: + (userStore.settings.notification + .alertExposureTime || 5) * 1000 + }; + // Sender Info setting + // STEP 1 >> In buddy group. + let senderInfo: any = buddyList[noti.SENDER_SEQ]; + // STEP 2 >> In Current Room Users. + if (!senderInfo) { + if (!!roomUsers && roomUsers.length > 0) { + const userMap = roomUsers.find( + (item) => item.roomId === roomId + ); + if (!!userMap) { + senderInfo = userMap.userInfos.find( + (item) => + item.seq + '' === noti.SENDER_SEQ + '' + ); + } + } + } + // STEP 3 >> user protocol + if (!senderInfo) { + this.queryProtocolService + .dataUser({ + divCd: 'OPENNOTI', + seq: Number(noti.SENDER_SEQ), + senderCompanyCode: user.info.companyCode, + senderEmployeeType: user.info.employeeType + }) + .pipe( + take(1), + map((res) => { + if (!!res && !!res.userInfo) { + senderInfo = res.userInfo; + let name = senderInfo.name; + let grade = senderInfo.grade; + switch ( + this.i18nService.currentLng.toUpperCase() + ) { + case LocaleCode.English: + name = senderInfo.nameEn; + grade = senderInfo.gradeEn; + break; + case LocaleCode.Chinese: + name = senderInfo.nameCn; + grade = senderInfo.gradeCn; + break; + } + notiReq.title = this.i18nService.t( + 'common:notification.titleChatEventArrivedByUser', + { + userInfo: !!grade + ? `${name} ${grade}` + : name + } + ); + // Image set. + if (!!senderInfo.profileImageFile) { + notiReq.image = `${versionInfo2Res.profileRoot}${senderInfo.profileImageFile}`; + } + } + }), + catchError((error) => { + return of(); + }), + finalize(() => { + this.nativeService.app_showNotify(notiReq); + }) + ) + .subscribe(); + } else { + // Sender Info setting. + // name set + let name = senderInfo.name; + let grade = senderInfo.grade; + switch ( + this.i18nService.currentLng.toUpperCase() + ) { + case LocaleCode.English: + name = senderInfo.nameEn; + grade = senderInfo.gradeEn; + break; + case LocaleCode.Chinese: + name = senderInfo.nameCn; + grade = senderInfo.gradeCn; + break; + } + notiReq.title = this.i18nService.t( + 'common:notification.titleChatEventArrivedByUser', + { + userInfo: !!grade ? `${name} ${grade}` : name + } + ); + // Image set. + if (!!senderInfo.profileImageFile) { + notiReq.image = `${versionInfo2Res.profileRoot}${senderInfo.profileImageFile}`; + } + // express noti popup + this.nativeService.app_showNotify(notiReq); + } + } + } + } + } + } + } + break; + case SSVC_TYPE_EVENT_READ_RES: + case SSVC_TYPE_EVENT_READ_NOTI: + { + // 대화방 unread count 처리. + const noti = notiOrRes as ReadNotification; + this.logService.debug( + 'Notification::eventProtocolService::ReadNotification', + noti + ); + + // roomUsers lastReadEventSeq update. + this.store.dispatch(ChattingActions.readSuccess(noti)); + + // roomInfo noReadCount update. + if (notiOrRes.SSVC_TYPE === SSVC_TYPE_EVENT_READ_RES) { + this.store.dispatch( + RoomActions.updateUnreadCount({ + roomId: noti.roomId + }) + ); + } + } + break; + case SSVC_TYPE_EVENT_CANCEL_NOTI: + { + const noti = notiOrRes as CancelNotification; + this.logService.debug( + 'Notification::eventProtocolService::CancelNotification', + noti + ); + this.store.dispatch( + ChattingActions.cancelNotification({ + noti + }) + ); + } + break; + case SSVC_TYPE_EVENT_DEL_RES: + { + const noti = notiOrRes as EventDelNotification; + this.logService.debug( + 'Notification::eventProtocolService::DelNotification', + noti + ); + this.store.dispatch( + ChattingActions.delNotification({ + noti + }) + ); + } + break; + default: + break; + } + } + ) + ) + .subscribe(); + + this.infoProtocolService.notification$ + .pipe( + tap((notiOrRes) => { + switch (notiOrRes.SSVC_TYPE) { + case SSVC_TYPE_INFO_USER_NOTI: + { + const noti = notiOrRes as UserNotification; + this.logService.debug( + 'Notification::infoProtocolService::UserNotification', + noti + ); + this.store.dispatch( + CommonActions.userNotification({ + noti + }) + ); + } + break; + default: + break; + } + }) + ) + .subscribe(); + + this.groupProtocolService.notification$ + .pipe( + tap((notiOrRes) => { + switch (notiOrRes.SSVC_TYPE) { + case SSVC_TYPE_GROUP_UPD_RES2: + { + const noti = notiOrRes as GroupUpdateNotification; + this.logService.debug( + 'Notification::groupProtocolService::GroupUpdateNotification', + noti + ); + // this.store.dispatch( + // SyncStore.group2({ + // syncDate + // }) + // ); + } + break; + case SSVC_TYPE_GROUP_ADD_RES: + { + const noti = notiOrRes as GroupAddNotification; + this.logService.debug( + 'Notification::groupProtocolService::GroupAddNotification', + noti + ); + // this.store.dispatch(SyncStore.createGroupSuccess(noti)); + } + break; + case SSVC_TYPE_GROUP_DEL_RES: + { + const noti = notiOrRes as GroupDelNotification; + this.logService.debug( + 'Notification::groupProtocolService::GroupDelNotification', + noti + ); + // this.store.dispatch(SyncStore.delGroupSuccess(noti)); + } + break; + + default: + break; + } + }) + ) + .subscribe(); + + this.buddyProtocolService.notification$ + .pipe( + tap((notiOrRes) => { + switch (notiOrRes.SSVC_TYPE) { + case SSVC_TYPE_BUDDY_UPD_RES: + { + const noti = notiOrRes as BuddyUpdateNotification; + this.logService.debug( + 'Notification::groupProtocolService::BuddyUpdateNotification', + noti + ); + // this.store.dispatch(SyncStore.updateBuddySuccess(noti)); + } + break; + case SSVC_TYPE_BUDDY_ADD_RES: + { + const noti = notiOrRes as BuddyAddNotification; + this.logService.debug( + 'Notification::groupProtocolService::BuddyAddNotification', + noti + ); + // this.store.dispatch(SyncStore.buddy2({ syncDate })); + } + break; + case SSVC_TYPE_BUDDY_DEL_RES: + { + const noti = notiOrRes as BuddyDelNotification; + this.logService.debug( + 'Notification::groupProtocolService::BuddyDelNotification', + noti + ); + // this.store.dispatch(SyncStore.delBuddySuccess(noti)); + } + break; + + default: + break; + } + }) + ) + .subscribe(); + + this.roomProtocolService.notification$ + .pipe( + withLatestFrom(this.store.pipe(select(UserSelector.user))), + tap(([notiOrRes, user]) => { + switch (notiOrRes.SSVC_TYPE) { + case SSVC_TYPE_ROOM_INVITE_RES: + case SSVC_TYPE_ROOM_INVITE_NOTI: + { + const noti = notiOrRes as InviteNotification; + this.logService.debug( + 'Notification::roomProtocolService::InviteNotification', + noti + ); + this.store.dispatch( + RoomActions.inviteNotification({ + noti, + localeCode: LocaleCode.Korean + }) + ); + } + break; + case SSVC_TYPE_ROOM_UPD_RES: + { + const noti = notiOrRes as RoomUpdateNotification; + this.logService.debug( + 'Notification::roomProtocolService::RoomUpdateNotification', + noti + ); + + if (noti.SENDER_SEQ === String(user.info.seq)) { + // Update Res. + this.store.dispatch( + RoomActions.updateSuccess({ + res: { + roomId: noti.roomId, + roomName: + noti.roomName.trim().length === 0 + ? ' ' + : noti.roomName.trim(), + receiveAlarm: noti.receiveAlarm, + syncAll: false // ignore params + } + }) + ); + } else { + // Update Room Name Noti. + this.store.dispatch( + RoomActions.updateRoomName({ + res: { + roomId: noti.roomId, + roomName: + noti.roomName.trim().length === 0 + ? ' ' + : noti.roomName.trim(), + receiveAlarm: noti.receiveAlarm, + syncAll: false // ignore params + } + }) + ); + } + } + break; + case SSVC_TYPE_ROOM_EXIT_RES: + case SSVC_TYPE_ROOM_EXIT_NOTI: + { + const noti = notiOrRes as ExitNotification; + this.logService.debug( + 'Notification::roomProtocolService::ExitNotification', + noti + ); + + this.store.dispatch( + RoomActions.exitNotification({ + roomId: noti.roomId, + senderSeq: noti.SENDER_SEQ + }) + ); + } + break; + case SSVC_TYPE_ROOM_EXIT_FORCING_RES: + case SSVC_TYPE_ROOM_EXIT_FORCING_NOTI: + { + const res = notiOrRes as ExitForcingResponse; + this.logService.debug( + 'Notification::roomProtocolService::ExitForcingNotification RES', + res + ); + + this.store.dispatch(RoomActions.expelNotification({ res })); + } + break; + case SSVC_TYPE_ROOM_FONT_UPD_NOTI: + { + const noti = notiOrRes as UpdateFontNotification; + this.logService.debug( + 'Notification::roomProtocolService::UpdateFontNotification', + noti + ); + // this.store.dispatch( + // RoomStore.updateFontNotification({ + // noti + // }) + // ); + } + break; + default: + break; + } + }) + ) + .subscribe(); + + this.statusProtocolService.notification$ + .pipe( + tap((notiOrRes) => { + switch (notiOrRes.SSVC_TYPE) { + case SSVC_TYPE_STATUS_NOTI: + { + const noti = notiOrRes as StatusNotification; + this.logService.debug( + 'Notification::statusProtocolService::StatusNotification', + noti + ); + // this.store.dispatch( + // StatusStore.statusNotification({ + // noti + // }) + // ); + } + break; + default: + break; + } + }) + ) + .subscribe(); + + this.optionProtocolService.notification$ + .pipe( + tap((notiOrRes) => { + switch (notiOrRes.SSVC_TYPE) { + case SSVC_TYPE_OPTION_REG_UPD_RES: + { + const noti = notiOrRes as RegUpdateNotification; + this.logService.debug( + 'Notification::optionProtocolService::RegUpdateNotification', + noti + ); + + // const appUserInfo: AppUserInfo = this.localStorageService.encGet< + // AppUserInfo + // >(KEY_APP_USER_INFO, environment.customConfig.appKey); + + // const modifiedSettings: Settings = clone(appUserInfo.settings); + // // 모바일에서 해당 값만 수정함. + // modifiedSettings.notification.receiveForMobile = + // noti.mobileNotification; + // appUserInfo.settings = modifiedSettings; + + // this.localStorageService.encSet( + // KEY_APP_USER_INFO, + // appUserInfo, + // environment.customConfig.appKey + // ); + } + break; + default: + break; + } + }) + ) + .subscribe(); + + this.umgProtocolService.notification$ + .pipe( + tap((notiOrRes) => { + switch (notiOrRes.SSVC_TYPE) { + case SSVC_TYPE_UMG_NOTI: + { + const noti = notiOrRes as UmgNotiNotification; + this.logService.debug( + 'Notification::umgProtocolService::UmgNotiNotification', + noti + ); + + // // unreadCount refresh.. + // this.store.dispatch(MessageStore.retrieveUnreadCount({})); + + // // Receive Message List refresh.. + // this.store.dispatch( + // MessageStore.retrieveMessage({ + // messageType: MessageType.Receive + // }) + // ); + + // // notification.. + // const appUserInfo = this.localStorageService.encGet< + // AppUserInfo + // >(KEY_APP_USER_INFO, environment.customConfig.appKey); + + // if (appUserInfo.settings.notification.use) { + // if ( + // appUserInfo.settings.notification.method === + // NotificationMethod.Sound + // ) { + // const audio = new Audio('assets/sounds/messageAlarm.mp3'); + // audio.play(); + // } else { + // const notiReq: NotificationRequest = { + // type: NotificationType.Message, + // seq: noti.keyId, + // title: this.translateService.instant( + // 'notification.titleMessageArrived' + // ), + // contents: noti.text, + // image: noti.senderInfo.profileImageFile, + // useSound: [ + // NotificationMethod.Sound, + // NotificationMethod.SoundAndAlert + // ].some( + // (n) => n === appUserInfo.settings.notification.method + // ) + // ? true + // : false, + // displayTime: + // appUserInfo.settings.notification.alertExposureTime * + // 1000 + // }; + // this.nativeService.notify(notiReq); + // } + // } + + // // direct open detail + // if (appUserInfo.settings.notification.receiveForMessage) { + // this.store.dispatch( + // MessageStore.detailMessage({ + // messageType: MessageType.Receive, + // msgId: Number(noti.keyId) + // }) + // ); + // } + } + break; + case SSVC_TYPE_UMG_DELETE_NOTI: + { + const noti = notiOrRes as UmgDeleteNotiNotification; + this.logService.debug( + 'Notification::umgProtocolService::UmgDeleteNotiNotification', + noti + ); + + // // Remove one Receive Message + // if (!!noti && !!noti.keyId) { + // // clear badge in left navi + // this.store.dispatch(MessageStore.retrieveUnreadCount({})); + + // // delete message in receive message list + // this.store.dispatch( + // deleteMessageSuccess({ + // messageType: MessageType.Receive, + // msgList: [ + // { + // msgId: Number(noti.keyId) + // } + // ] + // }) + // ); + // } + } + break; + + default: + break; + } + }) + ) + .subscribe(); + } +} diff --git a/src/app/services/app.service.ts b/src/app/services/app.service.ts index cd0b5a8..3678899 100644 --- a/src/app/services/app.service.ts +++ b/src/app/services/app.service.ts @@ -2,8 +2,10 @@ import { Injectable, Inject } from '@angular/core'; import { Store } from '@ngrx/store'; +import * as detectBrowser from 'detect-browser'; + import { DeviceType, DesktopType } from '@ucap/core'; -import { NativeService, NativeType, OsType } from '@ucap/native'; +import { NativeService, NativeType } from '@ucap/native'; import { I18nService } from '@ucap/ng-i18n'; import { LogService } from '@ucap/ng-logger'; @@ -17,11 +19,14 @@ import { LoginActions } from '@ucap/ng-store-authentication'; import { environment } from '@environments'; import { AppAuthenticationService } from './app-authentication.service'; -import { take } from 'rxjs/operators'; +import { AppNotificationService } from './app-notification.service'; +import { GroupOpenInfo } from '@app/models/group-open-info'; +import { GroupDetailData } from '@ucap/protocol-sync'; @Injectable() export class AppService { readonly companyGroupCode = environment.companyConfig.companyGroupCode; + bannedGroupNames: string[] = []; constructor( private i18nService: I18nService, @@ -30,12 +35,25 @@ export class AppService { private protocolService: ProtocolService, private pingProtocolService: PingProtocolService, private appAuthenticationService: AppAuthenticationService, + private appNotificationService: AppNotificationService, private logService: LogService, private store: Store, @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService ) {} initialize(): Promise { + const initApp = new Promise(async (resolve, reject) => { + try { + this.nativeService.window_onFocus$().subscribe((focus) => { + console.log('window_onFocus$', focus); + }); + + resolve(); + } catch (error) { + reject(error); + } + }); + const initSession = new Promise(async (resolve, reject) => { try { let loginSession = this.appAuthenticationService.getLoginSession(); @@ -45,7 +63,7 @@ export class AppService { let deviceType: DeviceType; let desktopType: DesktopType; - switch (this.nativeService.type()) { + switch (await this.nativeService.platform_nativeType()) { case NativeType.Browser: deviceType = DeviceType.Web; break; @@ -56,18 +74,19 @@ export class AppService { break; } - const osType = await this.nativeService.osType(); + const groupInfo = { + lastGroupSeq: 0, + groupSeqs: [] + } as GroupOpenInfo; - switch (osType) { - case OsType.Windows: - desktopType = DesktopType.Windows; - break; - case OsType.MacOS: - desktopType = DesktopType.MacOS; - break; - default: - desktopType = DesktopType.Linux; - break; + const info = detectBrowser.detect(); + if (info.os.startsWith('Windows')) { + desktopType = DesktopType.Windows; + } else if (info.os.startsWith('Mac OS')) { + desktopType = DesktopType.MacOS; + } else if (info.os.startsWith('Linux')) { + desktopType = DesktopType.Linux; + } else { } this.appAuthenticationService.setLoginSession({ @@ -75,7 +94,8 @@ export class AppService { deviceType, desktopType, companyGroupCode: this.companyGroupCode, - alive: false + alive: false, + groupInfo }); this.dateService.setDefaultTimezone('Asia/Seoul'); @@ -88,44 +108,36 @@ export class AppService { }); const initI18n = new Promise(async (resolve, reject) => { - switch (this.nativeService.type()) { - case NativeType.Browser: - const xhr = await import('i18next-xhr-backend').then( - (m) => m.default - ); - const languageDetector = await import( - 'i18next-browser-languagedetector' - ).then((m) => m.default); - this.i18nService.use(xhr).use(languageDetector); - break; - // case NativeType.Electron: - // const nodeFs = await import('i18next-node-fs-backend').then(m => m); - // i18nService.use(nodeFs); - // break; - default: - break; + const backends = environment.productConfig.i18next.useBackends; + if (!!backends && 0 < backends.length) { + for (const backend of backends) { + this.i18nService.use(backend); + } } this.i18nService - .init({ - whitelist: ['ko', 'en'], - fallbackLng: 'ko', - debug: true, // set debug? - returnEmptyString: false, - ns: [ - 'common', - 'organization', - 'authentication', - 'group', - 'chat', - 'call', - 'message' - ], - backend: { - loadPath: 'assets/i18n/{{lng}}/{{ns}}.json' - } - }) + .init(environment.productConfig.i18next.options) .then(() => { + const langs = ['en', 'ko']; + const bannedNameKeys = [ + 'category.favorite', + 'category.default', + 'category.myDept' + ]; + + langs.map((lang) => { + this.i18nService.changeLanguage(lang); + bannedNameKeys.map((key) => { + const tempResource = this.i18nService.getResource( + lang, + 'group', + key + ); + + this.bannedGroupNames.push(tempResource); + }); + }); + this.dateService.setLocale(this.i18nService.currentLng); resolve(); @@ -177,6 +189,49 @@ export class AppService { } }); - return Promise.all([initSession, initI18n, initOrganization, initProtocol]); + const initNotification = new Promise(async (resolve, reject) => { + try { + this.appNotificationService.subscribe(); + resolve(); + } catch (error) { + reject(error); + } + }); + + return Promise.all([ + initSession, + initI18n, + initOrganization, + initProtocol, + initNotification, + initApp + ]); + } + + validateGroupName(groupName: string, groupList: GroupDetailData[]): string { + const forbidden = /[\{\}\[\]\/?.;:|\)*~`!^+<>@\#$%&\\\=\(\'\"]/g.test( + groupName + ); + + const ban = this.bannedGroupNames.filter( + (name) => name.toLowerCase() === groupName.trim().toLowerCase() + )[0]; + + if (forbidden) { + return this.i18nService.t('group:error.useOnlyForSpecialCharacter', { + specialCharacter: '-,' + }); + } else if (groupName.trim().localeCompare('') === 0) { + return this.i18nService.t('group:error.requireInput'); + } else if (!!ban && ban !== '') { + return this.i18nService.t('group:error.bannedWords', { + bannedWords: this.bannedGroupNames.join(',') + }); + } else if ( + -1 < groupList.findIndex((g) => g.name.trim() === groupName.trim()) + ) { + return this.i18nService.t('group:error.sameNameExist'); + } + return null; } } diff --git a/src/app/store/actions.ts b/src/app/store/actions.ts index aa717d5..d84ec85 100644 --- a/src/app/store/actions.ts +++ b/src/app/store/actions.ts @@ -1,4 +1,5 @@ import * as AppActions from './app/actions'; import * as AppAuthenticationActions from './authentication/actions'; +import * as AppRoomActions from './room/actions'; -export { AppActions, AppAuthenticationActions }; +export { AppActions, AppAuthenticationActions, AppRoomActions }; diff --git a/src/app/store/app/actions.ts b/src/app/store/app/actions.ts index 668fe3d..44efcc4 100644 --- a/src/app/store/app/actions.ts +++ b/src/app/store/app/actions.ts @@ -6,3 +6,8 @@ export const windowResized = createAction( '[ucap::LG::app] windowResized', props<{ width: number; height: number }>() ); + +export const idleTimeChanged = createAction( + '[ucap::LG::app] idleTimeChanged', + props<{ idleTime: number }>() +); diff --git a/src/app/store/app/effects.ts b/src/app/store/app/effects.ts index dacf263..2d2bbb4 100644 --- a/src/app/store/app/effects.ts +++ b/src/app/store/app/effects.ts @@ -1,21 +1,82 @@ -import { map, exhaustMap } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { tap, takeUntil } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; +import { Injectable, Inject } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; -import { LoginResponse } from '@ucap/protocol-authentication'; - -import { SessionStorageService } from '@ucap/ng-web-storage'; +import { NativeService } from '@ucap/native'; +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; import { LoginActions } from '@ucap/ng-store-authentication'; - -import { AppKey } from '@app/types/app-key.type'; +import { idleTimeChanged } from './actions'; @Injectable() export class Effects { + webLoginSuccessForNativeService$ = createEffect( + () => + this.actions$.pipe( + ofType(LoginActions.webLoginSuccess), + tap((action) => { + this.nativeService.app_postLogin(); + }) + ), + { dispatch: false } + ); + + loginSuccessForNativeService$ = createEffect( + () => + this.actions$.pipe( + ofType(LoginActions.loginSuccess), + tap((action) => { + if (!this.logoutSubject) { + this.logoutSubject = new Subject(); + } + // this.nativeService.idle_startCheck(1000); + + // this.nativeService + // .idle_onState$() + // .pipe(takeUntil(this.logoutSubject)) + // .subscribe((idle) => { + // console.log('idle', idle); + // }); + }) + ), + { dispatch: false } + ); + + logoutSuccessForNativeService$ = createEffect( + () => + this.actions$.pipe( + ofType(LoginActions.logoutSuccess), + tap((action) => { + this.logoutSubject.next(); + this.logoutSubject.complete(); + this.logoutSubject = undefined; + + // this.nativeService.idle_stopCheck(); + + this.nativeService.app_postLogout(); + }) + ), + { dispatch: false } + ); + + idleTimeChanged$ = createEffect( + () => + this.actions$.pipe( + ofType(idleTimeChanged), + tap((action) => { + this.nativeService.idle_changeLimitTime(action.idleTime); + }) + ), + { dispatch: false } + ); + + private logoutSubject: Subject; + constructor( private actions$: Actions, - private sessionStorageService: SessionStorageService + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService ) {} } diff --git a/src/app/store/authentication/effects.ts b/src/app/store/authentication/effects.ts index 721daba..5ec07ab 100644 --- a/src/app/store/authentication/effects.ts +++ b/src/app/store/authentication/effects.ts @@ -1,5 +1,5 @@ import { interval, timer } from 'rxjs'; -import { map, exhaustMap, tap, takeUntil, take } from 'rxjs/operators'; +import { tap, takeUntil } from 'rxjs/operators'; import { Injectable, Inject } from '@angular/core'; import { Router } from '@angular/router'; @@ -8,7 +8,6 @@ import { Actions, createEffect, ofType } from '@ngrx/effects'; import { LoginTry } from '@ucap/pi'; -import { LoginResponse } from '@ucap/protocol-authentication'; import { NativeService } from '@ucap/native'; import { LogService } from '@ucap/ng-logger'; @@ -18,7 +17,6 @@ import { LoginActions } from '@ucap/ng-store-authentication'; import { AppKey } from '@app/types/app-key.type'; import { AppAuthenticationService } from '@app/services/app-authentication.service'; -import { AppActions } from '@app/store/actions'; import { environment } from '@environments'; @@ -33,7 +31,7 @@ export class Effects { this.actions$.pipe( ofType(LoginActions.webLoginSuccess), tap((params) => { - this.nativeService.checkForUpdates(params.login2Response.version); + this.nativeService.app_checkForUpdates(params.login2Response.version); this.appAuthenticationService.postWebLogin( { companyCode: params.companyCode, diff --git a/src/app/store/effects.ts b/src/app/store/effects.ts index a92aa2d..9fdada9 100644 --- a/src/app/store/effects.ts +++ b/src/app/store/effects.ts @@ -2,5 +2,10 @@ import { Type } from '@angular/core'; import { Effects as AppEffects } from './app/effects'; import { Effects as AppAuthenticationEffects } from './authentication/effects'; +import { Effects as AppRoomEffects } from './room/effects'; -export const effects: Type[] = [AppEffects, AppAuthenticationEffects]; +export const effects: Type[] = [ + AppEffects, + AppAuthenticationEffects, + AppRoomEffects +]; diff --git a/src/app/store/reducers.ts b/src/app/store/reducers.ts index 75a182f..b3dbd67 100644 --- a/src/app/store/reducers.ts +++ b/src/app/store/reducers.ts @@ -7,6 +7,7 @@ import { State } from './state'; import { reducer as appReducer } from './app/reducers'; import { reducer as appAuthenticationReducer } from './authentication/reducers'; +import { reducer as appRoomReducer } from './room/reducers'; export const ROOT_REDUCERS = new InjectionToken< ActionReducerMap @@ -14,6 +15,7 @@ export const ROOT_REDUCERS = new InjectionToken< factory: () => ({ appRouter: fromRouter.routerReducer, app: appReducer, - appAuthentication: appAuthenticationReducer + appAuthentication: appAuthenticationReducer, + appRoom: appRoomReducer }) }); diff --git a/src/app/store/room/actions.ts b/src/app/store/room/actions.ts new file mode 100644 index 0000000..2437cb6 --- /dev/null +++ b/src/app/store/room/actions.ts @@ -0,0 +1,3 @@ +import { createAction, props } from '@ngrx/store'; + +export const initializer = createAction('[ucap::app::chat:room] initializer'); diff --git a/src/app/store/room/effects.ts b/src/app/store/room/effects.ts new file mode 100644 index 0000000..841c165 --- /dev/null +++ b/src/app/store/room/effects.ts @@ -0,0 +1,90 @@ +import { map, tap } from 'rxjs/operators'; + +import { Injectable, Inject } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Actions, createEffect, ofType } from '@ngrx/effects'; + +import { NativeService } from '@ucap/native'; + +import { LogService } from '@ucap/ng-logger'; + +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; + +import { environment } from '@environments'; +import { RoomActions } from '@ucap/ng-store-chat'; + +import { Store } from '@ngrx/store'; + +@Injectable() +export class Effects { + createSuccess$ = createEffect( + () => { + return this.actions$.pipe( + ofType(RoomActions.createRoomSuccess), + map((action) => action.res), + tap((res) => { + this.router.navigate( + [ + 'chat', + { + outlets: { content: 'chatroom' } + } + ], + { + queryParams: { roomId: res.roomId } + } + ); + }) + ); + }, + { dispatch: false } + ); + + selectedRoomSuccess$ = createEffect( + () => { + return this.actions$.pipe( + ofType(RoomActions.selectedRoomSuccess), + tap((action) => { + this.router.navigate( + [ + 'chat', + { + outlets: { content: 'chatroom' } + } + ], + { + queryParams: { roomId: action.roomId } + } + ); + }) + ); + }, + { dispatch: false } + ); + + clearSelectedRoom$ = createEffect( + () => { + return this.actions$.pipe( + ofType(RoomActions.clearSelectedRoom), + tap(() => { + this.router.navigate([ + 'chat', + { + outlets: { content: 'index' } + } + ]); + }) + ); + }, + { dispatch: false } + ); + + constructor( + private actions$: Actions, + private store: Store, + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, + private router: Router, + private logService: LogService + ) {} +} diff --git a/src/app/store/room/reducers.ts b/src/app/store/room/reducers.ts new file mode 100644 index 0000000..c02394c --- /dev/null +++ b/src/app/store/room/reducers.ts @@ -0,0 +1,5 @@ +import { createReducer, on } from '@ngrx/store'; + +import { initialState } from './state'; + +export const reducer = createReducer(initialState); diff --git a/src/app/store/room/state.ts b/src/app/store/room/state.ts new file mode 100644 index 0000000..1e05bd0 --- /dev/null +++ b/src/app/store/room/state.ts @@ -0,0 +1,9 @@ +import { Selector, createSelector } from '@ngrx/store'; + +export interface State {} + +export const initialState: State = {}; + +export function selectors(selector: Selector) { + return {}; +} diff --git a/src/app/store/state.ts b/src/app/store/state.ts index 508bee6..9c4f2cd 100644 --- a/src/app/store/state.ts +++ b/src/app/store/state.ts @@ -10,11 +10,13 @@ import { environment } from '@environments'; import * as AppState from './app/state'; import * as AppAuthenticationState from './authentication/state'; +import * as AppRoomState from './room/state'; export interface State { appRouter: fromRouter.RouterReducerState; app: AppState.State; appAuthentication: AppAuthenticationState.State; + appRoom: AppRoomState.State; } export const metaReducers: MetaReducer[] = !environment.production diff --git a/src/app/types/app-key.type.ts b/src/app/types/app-key.type.ts index dc701eb..c3cb5c7 100644 --- a/src/app/types/app-key.type.ts +++ b/src/app/types/app-key.type.ts @@ -2,5 +2,6 @@ export enum AppKey { LoginTry = 'ucap::LG::LOGIN_TRY', LoginSession = 'ucap::LG::LOGIN_SESSION', LogoutSession = 'ucap::LG::LOGOUT_SESSION', - UserStore = 'ucap::LG::USER_STORE' + UserStore = 'ucap::LG::USER_STORE', + HistoryRoomId = 'ucap::LG::HISTORY_ROOMID' } diff --git a/src/app/types/group-manage.type.ts b/src/app/types/group-manage.type.ts new file mode 100644 index 0000000..305ec38 --- /dev/null +++ b/src/app/types/group-manage.type.ts @@ -0,0 +1,7 @@ +export enum GroupManageType { + Create = 'CREATE_TYPE', + Add = 'ADD_TYPE', + Copy = 'COPY_TYPE', + Move = 'MOVE_TYPE', + None = 'NONE_TYPE' +} diff --git a/src/app/types/group-user.dialog.type.ts b/src/app/types/group-user.dialog.type.ts deleted file mode 100644 index 23fb2ce..0000000 --- a/src/app/types/group-user.dialog.type.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum GroupUserDialaogType { - Create = 'CREATE_GROUP', - Add = 'ADD_GROUP', - Copy = 'COPY_GROUP', - Move = 'MOVE_GROUP' -} diff --git a/src/app/types/index.ts b/src/app/types/index.ts index 04138f8..d0f1046 100644 --- a/src/app/types/index.ts +++ b/src/app/types/index.ts @@ -1,4 +1,5 @@ export * from './app-key.type'; export * from './select-user.dialog.type'; export * from './tokens'; -export * from './group-user.dialog.type'; +export * from './group-manage.type'; +export * from './user.type'; diff --git a/src/app/types/user.type.ts b/src/app/types/user.type.ts new file mode 100644 index 0000000..f9bed1d --- /dev/null +++ b/src/app/types/user.type.ts @@ -0,0 +1,14 @@ +import { UserInfo } from '@ucap/protocol-sync'; +import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; +import { + UserInfo as RoomUserInfo, + UserInfoShort as RoomUserInfoShort +} from '@ucap/protocol-room'; + +export type UserInfoTypes = + | UserInfo + | UserInfoSS + | UserInfoF + | UserInfoDN + | RoomUserInfo + | RoomUserInfoShort; diff --git a/src/app/ucap/authentication/components/login.component.html b/src/app/ucap/authentication/components/login.component.html index bdf12cb..8686e59 100644 --- a/src/app/ucap/authentication/components/login.component.html +++ b/src/app/ucap/authentication/components/login.component.html @@ -11,55 +11,50 @@ >
    - +

    Welcome to Messenger

    diff --git a/src/app/ucap/authentication/components/login.component.ts b/src/app/ucap/authentication/components/login.component.ts index 49cbc6e..e4e7314 100644 --- a/src/app/ucap/authentication/components/login.component.ts +++ b/src/app/ucap/authentication/components/login.component.ts @@ -1,7 +1,7 @@ import { Subject } from 'rxjs'; import { take, takeUntil } from 'rxjs/operators'; -import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; +import { Component, OnInit, OnDestroy, ViewChild, Inject } from '@angular/core'; import { MatCheckbox } from '@angular/material/checkbox'; @@ -9,7 +9,9 @@ import { Store, select } from '@ngrx/store'; import { Company } from '@ucap/api-external'; import { LoginTry } from '@ucap/pi'; +import { NativeService } from '@ucap/native'; +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; import { LogService } from '@ucap/ng-logger'; import { I18nService } from '@ucap/ng-i18n'; import { @@ -27,6 +29,7 @@ import { AppKey } from '@app/types/app-key.type'; import { AppAuthenticationService } from '@app/services/app-authentication.service'; import { environment } from '@environments'; +import { UrlConfig } from '@ucap/core'; @Component({ selector: 'app-authentication-login', @@ -56,7 +59,7 @@ export class LoginComponent implements OnInit, OnDestroy { loginProcessing = false; loginTry: LoginTry; - private ngOnDestroySubject: Subject; + private ngOnDestroySubject: Subject = new Subject(); constructor( private piService: PiService, @@ -64,14 +67,14 @@ export class LoginComponent implements OnInit, OnDestroy { private sessionStorageService: SessionStorageService, private localStorageService: LocalStorageService, private i18nService: I18nService, + private store: Store, private appAuthenticationService: AppAuthenticationService, + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, private logService: LogService ) {} ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - this.localStorageService .encGet$( AppKey.UserStore, @@ -80,10 +83,7 @@ export class LoginComponent implements OnInit, OnDestroy { .pipe(takeUntil(this.ngOnDestroySubject)) .subscribe((userStore) => (this.userStore = userStore)); - this.appAuthenticationService - .getLoginSession$() - .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe((loginSession) => (this.loginSession = loginSession)); + this.loginSession = this.appAuthenticationService.getLoginSession(); this.sessionStorageService .get$(AppKey.LoginTry) @@ -164,8 +164,43 @@ export class LoginComponent implements OnInit, OnDestroy { ); } - onClickForgotPassword(lng: string) { - this.i18nService.changeLanguage(lng); + onClickForgotPassword() { + const piUrls = UrlConfig.getUrls( + environment.piModuleConfig.hostConfig, + environment.piModuleConfig.urls + ); + if ( + !!piUrls && + !!piUrls.passwordInitStep1 && + piUrls.passwordInitStep1.length > 0 + ) { + this.nativeService.platform_openDefaultBrowser( + piUrls.passwordInitStep1 + `?locale=${this.i18nService.currentLng}`, + { + name: this.i18nService.t( + 'authentication:login.labels.changePassword' + ), + features: + 'menubar=no,location=no,resizable=yes,scrollbars=yes,status=no,width=400,height=510' + } + ); + } + } + + onClickPrivacy() { + const piUrls = UrlConfig.getUrls( + environment.piModuleConfig.hostConfig, + environment.piModuleConfig.urls + ); + + if (!!piUrls && !!piUrls.policyPrivacy && piUrls.policyPrivacy.length > 0) { + this.nativeService.platform_openDefaultBrowser( + piUrls.policyPrivacy + `?locale=${this.i18nService.currentLng}`, + { + name: this.i18nService.t('authentication:login.labels.privacyPolicy') + } + ); + } } private onWebLoginFailure( diff --git a/src/app/ucap/chat/chat.module.ts b/src/app/ucap/chat/chat.module.ts index a63f8f1..f3fb4be 100644 --- a/src/app/ucap/chat/chat.module.ts +++ b/src/app/ucap/chat/chat.module.ts @@ -22,6 +22,11 @@ import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTabsModule } from '@angular/material/tabs'; import { MatSelectModule } from '@angular/material/select'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; +import { MatButtonModule } from '@angular/material/button'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { AppGroupModule } from '../group/group.module'; @NgModule({ imports: [ @@ -39,6 +44,10 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle'; MatTooltipModule, MatTabsModule, MatSelectModule, + MatButtonModule, + MatRadioModule, + MatFormFieldModule, + MatInputModule, I18nModule, UiModule, @@ -46,6 +55,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle'; GroupUiModule, ChatUiModule, + AppGroupModule, AppLayoutsModule ], exports: [...COMPONENTS], diff --git a/src/app/ucap/chat/components/attach-downcheck.component.html b/src/app/ucap/chat/components/attach-downcheck.component.html new file mode 100644 index 0000000..6810c31 --- /dev/null +++ b/src/app/ucap/chat/components/attach-downcheck.component.html @@ -0,0 +1,78 @@ +
    +
    +
    +
    + + + {{ 'chat:dialog.title.fileDownloadCheck' | ucapI18n }} + + {{ filteredRoomUsers.length }} +
    +
    + +
    +
    +
    + + + + + +
    +
    +
    diff --git a/src/app/ucap/chat/components/attach-downcheck.component.scss b/src/app/ucap/chat/components/attach-downcheck.component.scss new file mode 100644 index 0000000..ff08a89 --- /dev/null +++ b/src/app/ucap/chat/components/attach-downcheck.component.scss @@ -0,0 +1,12 @@ +@import '~@ucap/lg-scss/mixins'; + +.dataroom-contents { + width: 100%; + height: 100%; + .drawer-body { + height: 100%; + } +} +.ucap-virtual-scroll-viewport { + height: auto !important; +} diff --git a/src/app/sections/group/components/component-ui/profile-list-item.component.spec.ts b/src/app/ucap/chat/components/attach-downcheck.component.spec.ts similarity index 55% rename from src/app/sections/group/components/component-ui/profile-list-item.component.spec.ts rename to src/app/ucap/chat/components/attach-downcheck.component.spec.ts index 80e7d67..9122dcb 100644 --- a/src/app/sections/group/components/component-ui/profile-list-item.component.spec.ts +++ b/src/app/ucap/chat/components/attach-downcheck.component.spec.ts @@ -2,20 +2,20 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; -import { ProfileListItemComponent } from './profile-list-item.component'; +import { AttachDowncheckComponent } from './attach-downcheck.component'; -describe('ucap::ui-organization::ProfileListItemComponent', () => { - let component: ProfileListItemComponent; - let fixture: ComponentFixture; +describe('ucap::ui-organization::CreateChatDialogComponent', () => { + let component: AttachDowncheckComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ProfileListItemComponent] + declarations: [AttachDowncheckComponent] }).compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(ProfileListItemComponent); + fixture = TestBed.createComponent(AttachDowncheckComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/ucap/chat/components/attach-downcheck.component.ts b/src/app/ucap/chat/components/attach-downcheck.component.ts new file mode 100644 index 0000000..82cb8cf --- /dev/null +++ b/src/app/ucap/chat/components/attach-downcheck.component.ts @@ -0,0 +1,231 @@ +import { Subject, combineLatest, merge } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + EventEmitter, + Output, + Input +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { VersionInfo2Response } from '@ucap/api-public'; +import { + UserInfo as RoomUserInfo, + UserInfoShort as RoomUserInfoShort +} from '@ucap/protocol-room'; +import { User } from '@ucap/protocol-info'; +import { FileDownloadInfo } from '@ucap/protocol-file'; + +import { RoomSelector, ChattingSelector } from '@ucap/ng-store-chat'; +import { UserSelector } from '@ucap/ng-store-organization'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; + +export type UserInfoTypes = RoomUserInfo | RoomUserInfoShort; +export type FilterType = 'ALL' | 'DOWN' | 'NOTDOWN'; + +@Component({ + selector: 'app-chat-attach-downcheck', + templateUrl: './attach-downcheck.component.html', + styleUrls: ['./attach-downcheck.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AttachDowncheckComponent implements OnInit, OnDestroy { + private roomIdSubject = new Subject(); + private ngOnDestroySubject = new Subject(); + + @Input() + set roomId(value: string) { + this._roomId = value; + if (!!this.roomIdSubject) { + this.roomIdSubject.next(value); + } + this._initializeData(); + } + get roomId(): string { + return this._roomId; + } + // tslint:disable-next-line: variable-name + _roomId: string; + + @Input() + set attachmentSeq(value: number) { + this._attachmentSeq = value; + this._initializeData(); + } + // tslint:disable-next-line: variable-name + _attachmentSeq: number; + + filterType: FilterType = 'ALL'; + + @Output() + closed = new EventEmitter(); + + roomUsers: RoomUserInfo[] = []; + roomUsersShort: RoomUserInfoShort[] = []; + currentRoomUsers: (RoomUserInfo | RoomUserInfoShort)[]; + filteredRoomUsers: (RoomUserInfo | RoomUserInfoShort)[]; + fileInfoCheckList: FileDownloadInfo[]; + + user: User; + versionInfo2Res: VersionInfo2Response; + + constructor( + private store: Store, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit(): void { + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + + this._initializeData(); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + + if (!!this.roomIdSubject) { + this.roomIdSubject.next(); + this.roomIdSubject.complete(); + } + } + + private _initializeData() { + combineLatest([ + this.store.pipe(select(UserSelector.user)), + this.store.pipe(select(RoomSelector.roomUser, this.roomId)), + this.store.pipe(select(RoomSelector.roomUserShort, this.roomId)), + this.store.pipe(select(ChattingSelector.fileInfoCheckList, this.roomId)) + ]) + .pipe(takeUntil(merge(this.ngOnDestroySubject, this.roomIdSubject))) + .subscribe(([user, roomUser, roomUserShort, fileInfoCheckList]) => { + this.user = user; + + // retrieve room users. + if (!!user) { + if ( + !!roomUser && + !!roomUser.userInfos && + roomUser.userInfos.length > 0 + ) { + this.currentRoomUsers = roomUser.userInfos.filter( + (item) => + !!item.isJoinRoom && String(item.seq) !== String(user.info.seq) + ); + } else if ( + !!roomUserShort && + !!roomUserShort.userInfos && + roomUserShort.userInfos.length > 0 + ) { + this.currentRoomUsers = roomUserShort.userInfos.filter( + (item) => + !!item.isJoinRoom && String(item.seq) !== String(user.info.seq) + ); + } + } + + // fileInfoDown check. + if (!!this._attachmentSeq) { + this.fileInfoCheckList = fileInfoCheckList.filter( + (item) => item.seq === this._attachmentSeq + ); + } + + this.getFiltered(); + }); + } + + onChangeFilterType(type: FilterType) { + this.filterType = type; + this.getFiltered(); + } + + onClosed(event: MouseEvent): void { + this.closed.emit(); + } + + getFiltered() { + switch (this.filterType) { + case 'ALL': + { + this.filteredRoomUsers = this.currentRoomUsers.slice(); + } + break; + case 'DOWN': + { + this.filteredRoomUsers = this.currentRoomUsers.filter((curUser) => { + return ( + this.fileInfoCheckList.findIndex( + (downUser) => + !!downUser.isDownload && + String(downUser.userSeq) === String(curUser.seq) + ) > -1 + ); + }); + } + break; + case 'NOTDOWN': + { + this.filteredRoomUsers = this.currentRoomUsers.filter((curUser) => { + return ( + this.fileInfoCheckList.findIndex( + (downUser) => + !downUser.isDownload && + String(downUser.userSeq) === String(curUser.seq) + ) > -1 || + !this.fileInfoCheckList.some( + (downUser) => String(downUser.userSeq) === String(curUser.seq) + ) + ); + }); + } + break; + } + + this.changeDetectorRef.markForCheck(); + } + + isMe(userInfo: UserInfoTypes) { + if (!!this.user) { + return String(this.user.info.seq) === String(userInfo.seq); + } else { + return false; + } + } + + isDownload(userInfo: UserInfoTypes): boolean { + if (!!this.fileInfoCheckList && this.fileInfoCheckList.length > 0) { + return this.fileInfoCheckList.some( + (item) => + !!item.isDownload && String(item.userSeq) === String(userInfo.seq) + ); + } + return false; + } + getDownloadDateString(userInfo: UserInfoTypes): string { + if (!!this.fileInfoCheckList && this.fileInfoCheckList.length > 0) { + const obj = this.fileInfoCheckList.find( + (item) => + !!item.isDownload && String(item.userSeq) === String(userInfo.seq) + ); + if (!!obj) { + return obj.downloadDate; + } + } + } +} diff --git a/src/app/ucap/chat/components/attach-file-list.component.html b/src/app/ucap/chat/components/attach-file-list.component.html new file mode 100644 index 0000000..707d598 --- /dev/null +++ b/src/app/ucap/chat/components/attach-file-list.component.html @@ -0,0 +1,16 @@ +
    + + + + + +
    diff --git a/src/app/ucap/chat/components/attach-file-list.component.scss b/src/app/ucap/chat/components/attach-file-list.component.scss new file mode 100644 index 0000000..4dde968 --- /dev/null +++ b/src/app/ucap/chat/components/attach-file-list.component.scss @@ -0,0 +1,4 @@ +.attach-file-list-container { + width: 100%; + height: 100%; +} diff --git a/src/app/ucap/chat/components/attach-file-list.component.spec.ts b/src/app/ucap/chat/components/attach-file-list.component.spec.ts new file mode 100644 index 0000000..2b4452a --- /dev/null +++ b/src/app/ucap/chat/components/attach-file-list.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AttachFileListComponent } from './attach-file-list.component'; + +describe('app::ucap::organization::AttachFileListComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [AttachFileListComponent] + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AttachFileListComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'ucap-lg-web'`, () => { + const fixture = TestBed.createComponent(AttachFileListComponent); + const app = fixture.componentInstance; + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AttachFileListComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement; + expect(compiled.querySelector('.content span').textContent).toContain( + 'ucap-lg-web app is running!' + ); + }); +}); diff --git a/src/app/ucap/chat/components/attach-file-list.component.ts b/src/app/ucap/chat/components/attach-file-list.component.ts new file mode 100644 index 0000000..3623783 --- /dev/null +++ b/src/app/ucap/chat/components/attach-file-list.component.ts @@ -0,0 +1,191 @@ +import { Subject, combineLatest } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + Output, + EventEmitter +} from '@angular/core'; + +import { + UserInfo as RoomUserInfo, + UserInfoShort as RoomUserInfoShort +} from '@ucap/protocol-room'; +import { + RoomUserMap, + RoomUserShortMap +} from '@ucap/ng-store-chat/lib/store/room/state'; +import { FileInfo, InfoRequest, FileDownloadInfo } from '@ucap/protocol-file'; +import { LogService } from '@ucap/ng-logger'; +import { Store, select } from '@ngrx/store'; +import { + RoomSelector, + ChattingActions, + ChattingSelector +} from '@ucap/ng-store-chat'; +import { FileDownloadItem } from '@ucap/api'; +import { FileType } from '@ucap/protocol-event'; + +@Component({ + selector: 'app-chat-attach-file-list', + templateUrl: './attach-file-list.component.html', + styleUrls: ['./attach-file-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AttachFileListComponent implements OnInit, OnDestroy { + @Input() + roomId: string; + + @Input() + fileInfoList: { + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }[] = []; + + @Input() + selectedList: { + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }[] = []; + + @Output() + changeCheck = new EventEmitter<{ + checked: boolean; + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }>(); + + @Output() + showDownCheck = new EventEmitter(); + + roomUserMap: RoomUserMap; + roomUserShortMap: RoomUserShortMap; + downloadTotalCount: number; + downloadCheckList: FileDownloadInfo[] = []; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private store: Store, + private changeDetectorRef: ChangeDetectorRef, + private logService: LogService + ) {} + + ngOnInit(): void { + if (!!this.roomId) { + this.store.dispatch( + ChattingActions.fileInfos({ + req: { roomId: this.roomId, type: FileType.File } as InfoRequest + }) + ); + } + + combineLatest([ + this.store.pipe(select(RoomSelector.roomUser, this.roomId)), + this.store.pipe(select(RoomSelector.roomUserShort, this.roomId)) + ]) + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe(([roomUsers, roomUsersShort]) => { + this.roomUserMap = roomUsers; + this.roomUserShortMap = roomUsersShort; + + if ( + !!this.roomUserMap && + !!this.roomUserMap.userInfos && + this.roomUserMap.userInfos.length > 0 + ) { + this.downloadTotalCount = this.roomUserMap.userInfos.length; + } else if ( + !!this.roomUserShortMap && + !!this.roomUserShortMap.userInfos && + this.roomUserShortMap.userInfos.length > 0 + ) { + this.downloadTotalCount = this.roomUserShortMap.userInfos.length; + } + }); + + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ChattingSelector.fileInfoCheckList, this.roomId) + ) + .subscribe((downList) => { + this.downloadCheckList = downList; + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + getDownloadedCount(fileInfo: FileInfo): number { + if (!!this.downloadCheckList && this.downloadCheckList.length > 0) { + return this.downloadCheckList.filter((item) => item.seq === fileInfo.seq) + .length; + } + return 0; + } + + getItemChecked(fileInfo: FileInfo): boolean { + if (!!this.selectedList && this.selectedList.length > 0) { + return this.selectedList.some( + (info) => info.fileInfo.seq === fileInfo.seq + ); + } + return false; + } + + getDownloadItem(fileInfo: FileInfo): FileDownloadItem { + const obj = this.selectedList.find( + (item) => item.fileInfo.seq === fileInfo.seq + ); + + if (!!obj) { + return obj.fileDownloadItem; + } else { + return new FileDownloadItem(); + } + } + + onShowDownCheck(fileInfo: FileInfo): void { + this.showDownCheck.emit(fileInfo); + } + + onChangeCheck(param: { + checked: boolean; + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }) { + this.changeCheck.emit(param); + } + + getSenderInfo(userSeq: string): RoomUserInfo | RoomUserInfoShort { + if ( + !!this.roomUserShortMap && + !!this.roomUserShortMap.userInfos && + this.roomUserShortMap.userInfos.length > 0 + ) { + return this.roomUserShortMap.userInfos.find( + (item) => item.seq + '' === userSeq + '' + ); + } + + if ( + !!this.roomUserMap && + !!this.roomUserMap.userInfos && + this.roomUserMap.userInfos.length > 0 + ) { + return this.roomUserMap.userInfos.find( + (item) => item.seq + '' === userSeq + '' + ); + } + } +} diff --git a/src/app/ucap/chat/components/attach-image-list.component.html b/src/app/ucap/chat/components/attach-image-list.component.html new file mode 100644 index 0000000..2ea0de2 --- /dev/null +++ b/src/app/ucap/chat/components/attach-image-list.component.html @@ -0,0 +1,15 @@ +
    + + + + + +
    diff --git a/src/app/ucap/chat/components/attach-image-list.component.scss b/src/app/ucap/chat/components/attach-image-list.component.scss new file mode 100644 index 0000000..499f4d5 --- /dev/null +++ b/src/app/ucap/chat/components/attach-image-list.component.scss @@ -0,0 +1,4 @@ +.attach-image-list-container { + width: 100%; + height: 100%; +} diff --git a/src/app/ucap/chat/components/attach-image-list.component.spec.ts b/src/app/ucap/chat/components/attach-image-list.component.spec.ts new file mode 100644 index 0000000..3e8781c --- /dev/null +++ b/src/app/ucap/chat/components/attach-image-list.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AttachImageListComponent } from './attach-image-list.component'; + +describe('app::ucap::organization::AttachImageListComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [AttachImageListComponent] + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AttachImageListComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'ucap-lg-web'`, () => { + const fixture = TestBed.createComponent(AttachImageListComponent); + const app = fixture.componentInstance; + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AttachImageListComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement; + expect(compiled.querySelector('.content span').textContent).toContain( + 'ucap-lg-web app is running!' + ); + }); +}); diff --git a/src/app/ucap/chat/components/attach-image-list.component.ts b/src/app/ucap/chat/components/attach-image-list.component.ts new file mode 100644 index 0000000..65d0b9b --- /dev/null +++ b/src/app/ucap/chat/components/attach-image-list.component.ts @@ -0,0 +1,117 @@ +import { Subject, of, combineLatest } from 'rxjs'; +import { takeUntil, take, map, catchError } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + Output, + EventEmitter +} from '@angular/core'; + +import { FileInfo } from '@ucap/protocol-file'; +import { LogService } from '@ucap/ng-logger'; +import { FileDownloadItem } from '@ucap/api'; +import { FileEventJson } from '@ucap/protocol-event'; + +@Component({ + selector: 'app-chat-attach-image-list', + templateUrl: './attach-image-list.component.html', + styleUrls: ['./attach-image-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AttachImageListComponent implements OnInit, OnDestroy { + @Input() + fileInfoList: { + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }[] = []; + + @Input() + selectedList: { + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }[] = []; + + @Output() + changeCheck = new EventEmitter<{ + checked: boolean; + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }>(); + + @Output() + save = new EventEmitter<{ + fileInfo: FileEventJson; + fileDownloadItem: FileDownloadItem; + type: string; + }>(); + + @Output() + openViewer = new EventEmitter(); + + @Output() + delete = new EventEmitter(); + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private changeDetectorRef: ChangeDetectorRef, + private logService: LogService + ) {} + + ngOnInit(): void {} + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + getItemChecked(fileInfo: FileInfo): boolean { + if (!!this.selectedList && this.selectedList.length > 0) { + return this.selectedList.some( + (info) => info.fileInfo.seq === fileInfo.seq + ); + } + return false; + } + + getDownloadItem(fileInfo: FileInfo): FileDownloadItem { + const obj = this.selectedList.find( + (item) => item.fileInfo.seq === fileInfo.seq + ); + + if (!!obj) { + return obj.fileDownloadItem; + } else { + return new FileDownloadItem(); + } + } + + onChangeCheck(param: { + checked: boolean; + fileInfo: FileInfo; + fileDownloadItem: FileDownloadItem; + }) { + this.changeCheck.emit(param); + } + + onClickSave(params: { + fileInfo: FileEventJson; + fileDownloadItem: FileDownloadItem; + type: string; + }) { + this.save.emit(params); + } + onClickOpenViewer(fileInfo: FileInfo) { + this.openViewer.emit(fileInfo); + } + onClickDelete(fileInfo: FileInfo) { + this.delete.emit(fileInfo); + } +} diff --git a/src/app/ucap/chat/components/email-send.selector.component.html b/src/app/ucap/chat/components/email-send.selector.component.html index 925a752..4900c8a 100644 --- a/src/app/ucap/chat/components/email-send.selector.component.html +++ b/src/app/ucap/chat/components/email-send.selector.component.html @@ -1,15 +1,27 @@ - +
    -

    대화내용 메일 전송

    +

    {{ 'chat:label.emailSend' | ucapI18n }}

    - - +
    + + +
    diff --git a/src/app/ucap/chat/components/email-send.selector.component.scss b/src/app/ucap/chat/components/email-send.selector.component.scss index ed7da72..c181d1b 100644 --- a/src/app/ucap/chat/components/email-send.selector.component.scss +++ b/src/app/ucap/chat/components/email-send.selector.component.scss @@ -1,9 +1,18 @@ -.bubble-main { - padding: 10px; - text-align: left; - span { - word-wrap: break-word; - white-space: pre-wrap; - word-break: break-word; +.btn-email-send-area { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + height: 44px; + button { + border: 1px solid rgba(153, 153, 153, 0.76); + height: 30px; + width: 148px; + background-color: #fff; + border-radius: 2px; + overflow: hidden; + & + button { + margin-left: 5px; + } } } diff --git a/src/app/ucap/chat/components/email-send.selector.component.ts b/src/app/ucap/chat/components/email-send.selector.component.ts index 29ca698..df812a3 100644 --- a/src/app/ucap/chat/components/email-send.selector.component.ts +++ b/src/app/ucap/chat/components/email-send.selector.component.ts @@ -1,21 +1,42 @@ import { Component, OnInit, - Input, - ElementRef, AfterViewInit, - Inject + Output, + EventEmitter, + OnDestroy } from '@angular/core'; +import { SendEventMailType } from '@ucap/pi'; + @Component({ selector: 'app-chat-selector-email-send', templateUrl: './email-send.selector.component.html', styleUrls: ['./email-send.selector.component.scss'] }) -export class EmailSendSelectorComponent implements OnInit, AfterViewInit { +export class EmailSendSelectorComponent + implements OnInit, OnDestroy, AfterViewInit { + @Output() + sendEventEmail = new EventEmitter(); + + @Output() + closed = new EventEmitter(); + + SendEventMailType = SendEventMailType; + constructor() {} ngOnInit() {} + ngOnDestroy(): void {} + ngAfterViewInit(): void {} + + onClickClose(): void { + this.closed.emit(); + } + + onSendEventEmail(type: SendEventMailType): void { + this.sendEventEmail.emit(type); + } } diff --git a/src/app/ucap/chat/components/file-upload.selector.component.html b/src/app/ucap/chat/components/file-upload.selector.component.html index 0b8ddf4..48b4148 100644 --- a/src/app/ucap/chat/components/file-upload.selector.component.html +++ b/src/app/ucap/chat/components/file-upload.selector.component.html @@ -1,46 +1,20 @@ - +
    -

    파일전송

    - 이 영역으로 파일을 드래그 하시면 업로드 됩니다. +
    +

    {{ 'chat:label.fileSends.fileSend' | ucapI18n }}

    + +
    -
    - +
    - - - -
    {{ fileUploadItem.file.name }}
    - - -
    - - + {{ fileUploadItem.file.name }} + +
    + + + +
    -
    - - -
    + diff --git a/src/app/ucap/chat/components/file-upload.selector.component.scss b/src/app/ucap/chat/components/file-upload.selector.component.scss index ed7da72..f6791b1 100644 --- a/src/app/ucap/chat/components/file-upload.selector.component.scss +++ b/src/app/ucap/chat/components/file-upload.selector.component.scss @@ -1,9 +1,103 @@ -.bubble-main { - padding: 10px; - text-align: left; - span { - word-wrap: break-word; - white-space: pre-wrap; - word-break: break-word; +@import '~@ucap/lg-scss/mixins'; + +.ucap-selector-titlesection { + display: flex; + flex-direction: row; + align-items: center; + .description { + margin-left: 10px; + font-size: 0.875em; + color: #666; + } +} + +.file-items-box { + display: flex; + flex-direction: row; + flex-flow: wrap; + overflow-y: auto; + max-height: 102px; + min-height: 34px; + padding: 0 16px; + .file-item { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + height: 24px; + border-radius: 12px; + margin: 5px; + position: relative; + overflow: hidden; + min-width: 65px; + .file-upload-name { + flex-grow: 1; + padding: 0 12px; + @include ellipsis(1); + } + .btn-close { + @include ucapMatButton(24px, 24px, 0, 24px); + font-size: 12px; + width: 24px; + margin-right: 5px; + .icon-close { + color: $white; + font-size: 16px; + } + } + .progress-area { + position: absolute; + padding: 0 7px 0 10px; + z-index: 3; + background-color: rgba(0, 0, 0, 0.5); + border-radius: 12px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + height: 100%; + &:after { + content: ''; + width: 12px; + height: 12px; + position: absolute; + z-index: 2; + right: 13px; + top: 6px; + background-color: $white; + display: inline-block; + border-radius: 50%; + } + .mat-icon { + font-size: 1.429em; + position: relative; + z-index: 3; + margin-left: 10px; + } + } + &.cancel-btn-none { + .progress-area { + padding: 0 10px 0 10px; + &:after { + display: none; + } + button { + display: none; + } + } + } + } +} +.btn-file-area { + display: flex; + flex-direction: row; + background-color: $white; + padding: 15px 16px 10px; + justify-content: flex-end; + button { + @include ucap-button-flat-stroked(100px); + widows: 100px; + height: 30px; + border: 1px solid rgba(153, 153, 153, 0.76); } } diff --git a/src/app/ucap/chat/components/file-upload.selector.component.ts b/src/app/ucap/chat/components/file-upload.selector.component.ts index bffba22..7a5e590 100644 --- a/src/app/ucap/chat/components/file-upload.selector.component.ts +++ b/src/app/ucap/chat/components/file-upload.selector.component.ts @@ -4,7 +4,9 @@ import { ElementRef, AfterViewInit, ChangeDetectorRef, - ChangeDetectionStrategy + ChangeDetectionStrategy, + Output, + EventEmitter } from '@angular/core'; import { FileUploadItem } from '@ucap/api'; @@ -15,6 +17,9 @@ import { FileUploadItem } from '@ucap/api'; changeDetection: ChangeDetectionStrategy.OnPush }) export class FileUploadSelectorComponent implements OnInit, AfterViewInit { + @Output() + closed = new EventEmitter(); + fileUploadItems: FileUploadItem[]; uploadItems: DataTransferItem[]; @@ -27,6 +32,10 @@ export class FileUploadSelectorComponent implements OnInit, AfterViewInit { ngAfterViewInit(): void {} + onClickClose(): void { + this.closed.emit(); + } + onDragEnter(items: DataTransferItemList): void { if (!items || 0 === items.length) { return; @@ -98,6 +107,6 @@ export class FileUploadSelectorComponent implements OnInit, AfterViewInit { // this.elementRef.nativeElement.style.display = 'none'; // } // } - this.changeDetectorRef.detectChanges(); + this.changeDetectorRef.markForCheck(); } } diff --git a/src/app/ucap/chat/components/index.ts b/src/app/ucap/chat/components/index.ts index e5efec4..7ee94b4 100644 --- a/src/app/ucap/chat/components/index.ts +++ b/src/app/ucap/chat/components/index.ts @@ -5,6 +5,13 @@ import { EmailSendSelectorComponent } from './email-send.selector.component'; import { StickerSelectorComponent } from './sticker.selector.component'; import { FileUploadSelectorComponent } from './file-upload.selector.component'; import { MessageBoxComponent } from './message-box.component'; +import { RecentMessageComponent } from './recent-message.component'; +import { RoomListComponent } from './room-list.component'; +import { AttachFileListComponent } from './attach-file-list.component'; +import { AttachImageListComponent } from './attach-image-list.component'; +import { RoomSettingComponent } from './room-setting.component'; +import { AttachDowncheckComponent } from './attach-downcheck.component'; +import { RoomListItem01Component } from './room-list-item-01.component'; export const COMPONENTS = [ RoomExpansionComponent, @@ -14,5 +21,13 @@ export const COMPONENTS = [ EmailSendSelectorComponent, FileUploadSelectorComponent, - MessageBoxComponent + MessageBoxComponent, + RecentMessageComponent, + RoomListComponent, + RoomSettingComponent, + AttachDowncheckComponent, + RoomListItem01Component, + + AttachFileListComponent, + AttachImageListComponent ]; diff --git a/src/app/ucap/chat/components/message-box.component.html b/src/app/ucap/chat/components/message-box.component.html index 7c1eec5..bf2f2df 100644 --- a/src/app/ucap/chat/components/message-box.component.html +++ b/src/app/ucap/chat/components/message-box.component.html @@ -41,6 +41,11 @@ [message]="message" > + + -
    +
    +
    -
    +
    {{ unreadCount }}
    diff --git a/src/app/ucap/chat/components/message-box.component.scss b/src/app/ucap/chat/components/message-box.component.scss index e69de29..de949a4 100644 --- a/src/app/ucap/chat/components/message-box.component.scss +++ b/src/app/ucap/chat/components/message-box.component.scss @@ -0,0 +1,262 @@ +@import '~@ucap/lg-scss/mixins'; + +.hide { + opacity: 0 !important; +} + +.message-row { + width: 100%; + min-width: 270px; + height: 100%; + .chat-row { + position: relative; + display: flex; + flex-direction: row; + margin-left: 0; + padding-bottom: 20px; + .ballon-container { + display: flex; + flex-direction: column; + overflow: hidden; + max-width: 85%; + //chat box + box-shadow: 0 3px 2px 0 rgba(0, 0, 0, 0.16); + border: solid 1px #f998a0; + background-color: #ffffff; + border-radius: 0 10px 10px 10px; + margin-left: 50px; + @include screen(xs) { + margin-left: 28px; + } + .header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-content: center; + padding: 0 0 0 16px; + min-height: 40px; + .user-profile-thumb { + position: absolute; + left: 0; + top: 0; + //height: 41px; + @include profile-avatar-default( + //0, + //14, + //$green, + //18px + 0, + 0, + $green, + 0 + ); //오른 아래 공간, 모바일 온라인 아이콘 크기, 모바일 아이콘 색, 모바일 아이콘 bg크기 + //.presence { + //PC 상태 + // @include presence-state(8px); //원크기 + //} + .profile-image { + @include avatar-img(40px, 0); //아바타 크기, 왼쪽공간 + background-color: #ffe8cb; + cursor: pointer; + img { + width: 40px; + height: 40px; + } + } + } + .sender-info { + display: flex; + flex-direction: row; + align-items: center; + color: $gray-re3; + .name { + font-size: 1em; + font-weight: 600; + white-space: nowrap; + } + .grade { + font-size: 0.929em; + white-space: nowrap; + } + } + .date { + display: flex; + align-items: center; + flex-grow: 1; + justify-self: start; + padding-left: 4px; + color: $gray-re6; + font-size: 0.857em; + white-space: nowrap; + } + .more-area-box { + display: flex; + justify-content: flex-end; + min-width: 158px; + position: sticky; + right: 0; + + .btn-area { + align-items: center; + display: flex; + flex-direction: row-reverse; + //min-width: 40px; + position: relative; + //bubble btn set + .btn-bubble-btn-set { + visibility: hidden; + display: inline-flex; + flex-direction: row; + align-items: center; + flex-grow: 1; + height: 40px; + position: absolute; + z-index: 5; + right: -150px; + //top: -20px; + transition: all 0.1s linear; + background-color: rgba(255, 255, 255, 0.7); + + button { + width: 28px; + height: 28px; + & + button { + margin-left: 2px; + } + &:hover { + background-color: #ffc5ca; + } + &:last-of-type { + display: none; + } + &[disabled='true'] { + opacity: 0.5; + &:hover { + background-color: transparent; + } + } + } + } + .btn-bubble-more { + flex-grow: 0; + transition: all 0.2s linear; + opacity: 1; + cursor: progress; + } + &:hover { + min-width: 158px; + .btn-bubble-btn-set { + visibility: visible; + transition: all 0.2s linear; + //top: 0; + right: 8px; + } + .btn-bubble-more { + visibility: hidden; + transition: all 0.2s linear; + opacity: 0; + } + } + } + } + } + .contents { + } + &.disable-bubble { + border: 1px solid #ccc; + } + &.search-bubble { + border: 2px solid #81757a; + } + } + .unread-count { + background-color: #feebe5; + height: 18px; + line-height: 18px; + border-radius: 9px; + padding: 0 6px; + margin-left: 3px; + margin-bottom: 2px; + align-self: flex-end; + font-size: 0.9em; + font-weight: 600; + } + } + &.me { + .chat-row { + flex-direction: row-reverse; + margin-left: 0; + margin-right: 0; + .ballon-container { + border-radius: 10px 10px 0 10px; + margin-left: 0; + background-color: #ffe8cb; + border-color: #f69532; + .header { + padding: 0 16px 0 0; + @include screen(xs) { + position: relative; + } + .user-profile-thumb { + display: none; + } + .sender-info { + display: none; + } + .date { + order: 3; + justify-content: flex-end; + } + .more-area-box { + justify-content: flex-start; + @include screen(xs) { + position: sticky; + left: 0; + } + .btn-area { + flex-direction: row; + order: 1; + position: relative; + @include screen(xs) { + position: none; + left: 0; + } + .btn-bubble-btn-set { + order: 2; + right: auto; + left: -140px; + background-color: rgba(255, 232, 203, 0.7); + button { + &:hover { + background-color: rgba(255, 127, 61, 0.7); + } + &[disabled='true'] { + background-color: transparent; + } + &:first-of-type { + display: none; + } + &:last-of-type { + display: block; + } + } + } + &:hover { + .btn-bubble-btn-set { + right: auto; + left: 8px; + } + } + } + } + } + .contents { + } + } + .unread-count { + margin-left: 0; + margin-right: 3px; + } + } + } +} diff --git a/src/app/ucap/chat/components/message-box.component.ts b/src/app/ucap/chat/components/message-box.component.ts index 9b00abf..785e741 100644 --- a/src/app/ucap/chat/components/message-box.component.ts +++ b/src/app/ucap/chat/components/message-box.component.ts @@ -1,3 +1,6 @@ +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + import { Component, OnInit, @@ -5,43 +8,58 @@ import { EventEmitter, Output, AfterViewInit, - ElementRef, - ViewChild, ChangeDetectorRef, - OnDestroy + OnDestroy, + Inject } from '@angular/core'; import { Store, select } from '@ngrx/store'; -import { map, catchError, takeUntil, take } from 'rxjs/operators'; -import { of, Subject } from 'rxjs'; +import { MatDialog } from '@angular/material/dialog'; + +import { LoginSession, DeviceType } from '@ucap/core'; +import { NativeService } from '@ucap/native'; +import { FileDownloadItem } from '@ucap/api'; +import { LoginResponse } from '@ucap/protocol-authentication'; import { Info, EventJson, EventType, FileType, - StickerEventJson + StickerEventJson, + isRecalled, + isCopyable, + isRecallable, + isForwardable, + FileEventJson, + MassTranslationEventJson } from '@ucap/protocol-event'; import { UserInfo as RoomUserInfo, UserInfoShort as RoomUserInfoShort, - RoomInfo + RoomInfo, + RoomType } from '@ucap/protocol-room'; -import { LoginResponse } from '@ucap/protocol-authentication'; -import { LoginSession } from '@ucap/core'; +import { User } from '@ucap/protocol-info'; import { I18nService } from '@ucap/ng-i18n'; import { LocalStorageService } from '@ucap/ng-web-storage'; import { LogService } from '@ucap/ng-logger'; -import { CommonApiService } from '@ucap/ng-api-common'; +import { UCAP_NATIVE_SERVICE } from '@ucap/ng-native'; + +import { UserSelector } from '@ucap/ng-store-organization'; +import { LoginSelector } from '@ucap/ng-store-authentication'; + +import { SelectFileInfo } from '@ucap/ng-ui'; import { AppAuthenticationService } from '@app/services/app-authentication.service'; -import { MatDialog } from '@angular/material/dialog'; -import { LoginSelector } from '@ucap/ng-store-authentication'; -import { MassTalkDownloadRequest } from '@ucap/api-common'; -import { StatusCode } from '@ucap/api'; -import { Dictionary } from '@ngrx/entity'; +import { + TextDetailDialogComponent, + TextDetailDialogData, + TextDetailDialogResult +} from '@app/sections/chat/dialogs/text-detail.dialog.component'; +import { AppFileService } from '@app/services/app-file.service'; @Component({ selector: 'app-chat-message-box', @@ -57,6 +75,8 @@ export class MessageBoxComponent implements OnInit, OnDestroy, AfterViewInit { @Input() isMe = false; @Input() + translationSimpleview: boolean; + @Input() senderInfo: RoomUserInfoShort | RoomUserInfo; @Input() defaultProfileImage = ''; @@ -70,9 +90,15 @@ export class MessageBoxComponent implements OnInit, OnDestroy, AfterViewInit { loginSession: LoginSession; loginRes: LoginResponse; + user: User; EventType = EventType; + isRecalledMessage = isRecalled; + isCopyableMessage = isCopyable; + isRecallableMessage = isRecallable; + isForwardableMessage = isForwardable; + ////////////////////////////////// @Input() messageType: string; @@ -83,7 +109,26 @@ export class MessageBoxComponent implements OnInit, OnDestroy, AfterViewInit { @Input() fileType: FileType; - private ngOnDestroySubject: Subject; + @Output() + fileViewer: EventEmitter = new EventEmitter(); + + @Output() + messageContextMenu = new EventEmitter<{ + menuType: string; + message: Info; + }>(); + + @Output() + massTranslationDetail = new EventEmitter<{ + message: Info; + contentsType: string; + }>(); + + @Output() + openProfile = new EventEmitter(); + + private ngOnDestroySubject: Subject = new Subject(); + constructor( private store: Store, private i18nService: I18nService, @@ -91,25 +136,30 @@ export class MessageBoxComponent implements OnInit, OnDestroy, AfterViewInit { private localStorageService: LocalStorageService, private logService: LogService, private appAuthenticationService: AppAuthenticationService, - private commonApiService: CommonApiService, + private appFileService: AppFileService, + @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, private changeDetectorRef: ChangeDetectorRef ) {} ngOnInit() { - this.ngOnDestroySubject = new Subject(); + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; + }); + this.store .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) .subscribe((loginRes) => { this.loginRes = loginRes; }); - this.appAuthenticationService - .getLoginSession$() - .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe((loginSession) => (this.loginSession = loginSession)); + + this.loginSession = this.appAuthenticationService.getLoginSession(); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } @@ -149,48 +199,125 @@ export class MessageBoxComponent implements OnInit, OnDestroy, AfterViewInit { } } + onOpenProfile(userInfo: RoomUserInfoShort | RoomUserInfo): void { + this.openProfile.emit(String(userInfo.seq)); + } + /** * Url link Open Event. */ onOpenLink(url: string): void { - console.log('onOpenLink', url); + this.nativeService.platform_openDefaultBrowser(url); } /** * Detail view > Mass text. */ onOpenMassDetail(eventMassSeq: number): void { - const req = { - userSeq: this.loginRes.userSeq, - deviceType: this.loginSession.deviceType, - eventMassSeq: Number(eventMassSeq), - token: this.loginRes.tokenString - } as MassTalkDownloadRequest; + const dialogRef = this.dialog.open< + TextDetailDialogComponent, + TextDetailDialogData, + TextDetailDialogResult + >(TextDetailDialogComponent, { + panelClass: 'mid-create-dialog', + data: { + message: this.message, + roomId: this.roomInfo.roomId, + userSeq: String(this.user.info.seq), + deviceType: this.loginSession.deviceType, + token: this.loginRes.tokenString, + eventMassSeq: Number(eventMassSeq) + } + }); + } - this.commonApiService - .massTalkDownload(req) - .pipe( - take(1), - map((res) => { - if (res.statusCode === StatusCode.Success) { - console.log('massTalkDownload', res.content); - // const result = this.dialog.open< - // MassDetailComponent, - // MassDetailDialogData - // >(MassDetailComponent, { - // data: { - // title: this.i18nService.t('chat.detailView'), - // contents: res.content - // } - // }); - } else { - this.logService.error( - `commonApiService] massTalkDownload ${res?.errorMessage}` - ); - } - }), - catchError((error) => of({ error })) + /** + * Detail view > Mass translation text + */ + onOpenMassTranslationDetail(params: { + message: Info; + contentsType: string; + }) { + this.massTranslationDetail.emit(params); + } + isShowUnreadCount = () => { + if ( + !!this.roomInfo && + [RoomType.Mytalk, RoomType.Allim, RoomType.Bot, RoomType.Link].some( + (v) => v === this.roomInfo.roomType ) - .subscribe(); + ) { + return false; + } + return true; + }; + + onClickMessageContextMenu(menuType: string) { + this.messageContextMenu.emit({ + menuType, + message: this.message + }); + } + + /** [Event] Image Viewer */ + openViewer(fileEvent: SelectFileInfo): void { + this.fileViewer.emit(fileEvent); + } + + fileSave(params: { + fileInfo: FileEventJson; + fileDownloadItem: FileDownloadItem; + type: string; + }): void { + if ( + params.type === 'saveAs' && + this.loginSession.deviceType === DeviceType.PC + ) { + this.nativeService + .file_selectForSave({ defaultPath: params.fileInfo.fileName }) + .then((result) => { + if (!!result) { + if (!!result.canceled) { + // 취소함. + } else { + this.appFileService.saveFile( + { + fileInfo: params.fileInfo, + fileDownloadItem: params.fileDownloadItem, + type: params.type, + fileName: params.fileInfo.fileName, + fileDownloadUrl: undefined, + savePath: result.filePath + }, + this.loginRes, + this.user, + this.loginSession + ); + } + } + }) + .catch((reason) => { + // this.snackBarService.open( + // this.translateService.instant( + // 'common:file.errors.failToSpecifyPath' + // ), + // this.translateService.instant('common:file.errors.label') + // ); + }); + } else { + this.appFileService.saveFile( + { + fileInfo: params.fileInfo, + fileDownloadItem: params.fileDownloadItem, + type: params.type, + fileName: params.fileInfo.fileName, + fileDownloadUrl: undefined, + savePath: undefined + }, + this.loginRes, + this.user, + this.loginSession + ); + } } } diff --git a/src/app/ucap/chat/components/recent-message.component.html b/src/app/ucap/chat/components/recent-message.component.html new file mode 100644 index 0000000..a958e69 --- /dev/null +++ b/src/app/ucap/chat/components/recent-message.component.html @@ -0,0 +1,40 @@ +
    + +
    diff --git a/src/app/ucap/chat/components/recent-message.component.scss b/src/app/ucap/chat/components/recent-message.component.scss new file mode 100644 index 0000000..c181d1b --- /dev/null +++ b/src/app/ucap/chat/components/recent-message.component.scss @@ -0,0 +1,18 @@ +.btn-email-send-area { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + height: 44px; + button { + border: 1px solid rgba(153, 153, 153, 0.76); + height: 30px; + width: 148px; + background-color: #fff; + border-radius: 2px; + overflow: hidden; + & + button { + margin-left: 5px; + } + } +} diff --git a/src/app/ucap/chat/components/recent-message.component.spec.ts b/src/app/ucap/chat/components/recent-message.component.spec.ts new file mode 100644 index 0000000..6185a3b --- /dev/null +++ b/src/app/ucap/chat/components/recent-message.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RecentMessageComponent } from './recent-message.component'; + +describe('Chat::MessageBox::RecentMessageComponent', () => { + let component: RecentMessageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [RecentMessageComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RecentMessageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/ucap/chat/components/recent-message.component.ts b/src/app/ucap/chat/components/recent-message.component.ts new file mode 100644 index 0000000..0f229f1 --- /dev/null +++ b/src/app/ucap/chat/components/recent-message.component.ts @@ -0,0 +1,39 @@ +import { + Component, + OnInit, + AfterViewInit, + Output, + EventEmitter, + Input +} from '@angular/core'; +import { + UserInfo as RoomUserInfo, + UserInfoShort as RoomUserInfoShort +} from '@ucap/protocol-room'; + +@Component({ + selector: 'app-chat-recent-message', + templateUrl: './recent-message.component.html', + styleUrls: ['./recent-message.component.scss'] +}) +export class RecentMessageComponent implements OnInit, AfterViewInit { + @Input() + senderInfo: RoomUserInfoShort | RoomUserInfo; + @Input() + defaultProfileImage = ''; + @Input() + profileImageRoot = ''; + + @Output() + gotoBottom = new EventEmitter(); + + constructor() {} + + ngOnInit() {} + + ngAfterViewInit(): void {} + + onClickGotoBottom(): void { + this.gotoBottom.emit(); + } +} diff --git a/src/app/ucap/chat/components/room-expansion.component.html b/src/app/ucap/chat/components/room-expansion.component.html index a5bd14a..4ca16dd 100644 --- a/src/app/ucap/chat/components/room-expansion.component.html +++ b/src/app/ucap/chat/components/room-expansion.component.html @@ -1,32 +1,149 @@ -
    - - - - - - - - {{ node.nodeType | ucapDate: 'LL' }} - {{ node.nodeType | ucapDate: 'dddd' }} - - ({{ 'room.today' | ucapI18n }}) - - - - +
    + +
    +
    + + + + + + + + + {{ node.nodeType | ucapDate: 'LL' }} + {{ node.nodeType | ucapDate: 'dddd' }} + + ({{ 'chat:room.today' | ucapI18n }}) + + + ({{ 'chat:room.yesterday' | ucapI18n }}) + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NO + + + +
    +
    diff --git a/src/app/ucap/chat/components/room-expansion.component.scss b/src/app/ucap/chat/components/room-expansion.component.scss index 3f67707..3e5b11a 100644 --- a/src/app/ucap/chat/components/room-expansion.component.scss +++ b/src/app/ucap/chat/components/room-expansion.component.scss @@ -1,4 +1,42 @@ .room-expansion-container { width: 100%; height: 100%; + padding: 0; + .empty { + widows: 100%; + height: 100%; + background-color: #f1f2f6; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + svg { + .empty-fix-1, + .empty-fix-3 { + stroke: #bababa; + stroke-width: 3px; + } + + .empty-fix-1 { + fill: #fff; + } + + .empty-fix-3 { + stroke-linecap: round; + } + + .empty-fix-3, + .empty-fix-5 { + fill: none; + } + + .empty-fix-4 { + fill: #bababa; + } + + .empty-fix-7 { + stroke: none; + } + } + } } diff --git a/src/app/ucap/chat/components/room-expansion.component.ts b/src/app/ucap/chat/components/room-expansion.component.ts index ce0cf29..5426c0e 100644 --- a/src/app/ucap/chat/components/room-expansion.component.ts +++ b/src/app/ucap/chat/components/room-expansion.component.ts @@ -1,3 +1,5 @@ +import moment from 'moment'; + import { Subject, combineLatest } from 'rxjs'; import { takeUntil, take } from 'rxjs/operators'; @@ -13,80 +15,64 @@ import { } from '@angular/core'; import { Store, select } from '@ngrx/store'; +import { Dictionary } from '@ngrx/entity'; -import { - FixedSizeVirtualScrollStrategy, - VIRTUAL_SCROLL_STRATEGY -} from '@angular/cdk/scrolling'; - -import { VersionInfo2Response } from '@ucap/api-public'; -import { LoginResponse } from '@ucap/protocol-authentication'; -import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; +import { UserInfo } from '@ucap/protocol-sync'; import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; +import { RoomInfo, RoomType, UpdateRequest } from '@ucap/protocol-room'; +import { User } from '@ucap/protocol-info'; + +import { UserSelector } from '@ucap/ng-store-organization'; import { - RoomInfo, - RoomType, - ExitAllRequest, - UpdateRequest, - ExitRequest -} from '@ucap/protocol-room'; - -import { LogService } from '@ucap/ng-logger'; - -import { RoomSelector, RoomActions } from '@ucap/ng-store-chat'; -import { - LoginSelector, - ConfigurationSelector -} from '@ucap/ng-store-authentication'; -import { BuddySelector, GroupSelector } from '@ucap/ng-store-group'; + RoomSelector, + RoomActions, + RoomUserMap, + RoomUserShortMap +} from '@ucap/ng-store-chat'; +import { DateService } from '@ucap/ng-ui'; import { TranslatePipe as OrganizationTranslate, TranslateService } from '@ucap/ng-ui-organization'; -import { NodeType, FlatNode } from '@ucap/ng-ui-group'; -import { AppAuthenticationService } from '@app/services/app-authentication.service'; -import { LoginSession } from '@app/models/login-session'; import { AppChatService } from '@app/services/app-chat.service'; -import { - DateService, - ConfirmDialogComponent, - ConfirmDialogData, - ConfirmDialogResult -} from '@ucap/ng-ui'; -import { Dictionary } from '@ngrx/entity'; -import { - RoomUserMap, - RoomUserShortMap -} from '@ucap/ng-store-chat/lib/store/room/state'; -import { MatDialog } from '@angular/material/dialog'; -import { I18nService } from '@ucap/ng-i18n'; +import { SearchInfo } from '@app/pages/chat/models/search-info'; export type UserInfoTypes = UserInfo | UserInfoSS | UserInfoF | UserInfoDN; -export class GroupVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy { - constructor() { - super(60, 150, 200); // (itemSize, minBufferPx, maxBufferPx) - } -} - @Component({ selector: 'app-chat-room-expansion', templateUrl: './room-expansion.component.html', styleUrls: ['./room-expansion.component.scss'], - providers: [ - { - provide: VIRTUAL_SCROLL_STRATEGY, - useClass: GroupVirtualScrollStrategy - } - ], changeDetection: ChangeDetectionStrategy.OnPush }) export class RoomExpansionComponent implements OnInit, OnDestroy { + @Input() + set searchObj(obj: SearchInfo) { + this._searchObj = obj; + + if (obj.isShowSearch && obj.searchWord.localeCompare('') !== 0) { + this._onRoomSearch(obj); + } else { + this._searchObj.isShowSearch = false; + this.roomList = this.originalRoomList; + this._initRoomGroup(); + } + } + + get searchObj(): SearchInfo { + return this._searchObj; + } + // tslint:disable-next-line: variable-name + _searchObj: SearchInfo; + @Input() checkable = false; + @Input() + currentRoomId?: string; + @Input() selectedRoomList: RoomInfo[] = []; @@ -99,27 +85,30 @@ export class RoomExpansionComponent implements OnInit, OnDestroy { @Output() openChatRoom: EventEmitter = new EventEmitter(); - defaultProfileImage: string; - defaultProfileImageMulti: string; + @Output() + searchResultList = new EventEmitter(); - versionInfo2Res: VersionInfo2Response; - loginRes: LoginResponse; + user: User; + processing = false; + + originalRoomList: RoomInfo[]; roomList: RoomInfo[]; + standbyRooms: string[]; roomUsersDictionary: Dictionary; roomUsersShortDictionary: Dictionary; roomGroup: { division: string; roomList: RoomInfo[] }[]; organizationTranslate: OrganizationTranslate; - private ngOnDestroySubject: Subject; + RoomType = RoomType; + + private ngOnDestroySubject: Subject = new Subject(); constructor( private appChatService: AppChatService, private dateService: DateService, - private i18nService: I18nService, private translateService: TranslateService, - private dialog: MatDialog, private store: Store, private changeDetectorRef: ChangeDetectorRef ) { @@ -127,28 +116,13 @@ export class RoomExpansionComponent implements OnInit, OnDestroy { this.translateService, this.changeDetectorRef ); - - // default image setting - this.defaultProfileImage = this.appChatService.defaultProfileImage; - this.defaultProfileImageMulti = this.appChatService.defaultProfileImage; } ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - this.store - .pipe( - takeUntil(this.ngOnDestroySubject), - select(ConfigurationSelector.versionInfo2Response) - ) - .subscribe((versionInfo2Res) => { - this.versionInfo2Res = versionInfo2Res; - }); - - this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; }); combineLatest([ @@ -157,18 +131,16 @@ export class RoomExpansionComponent implements OnInit, OnDestroy { ]) .pipe(takeUntil(this.ngOnDestroySubject)) .subscribe(([rooms, standbyRooms]) => { - rooms = (rooms || []).filter((info) => { - return ( - info.isJoinRoom && - !standbyRooms.find((standbyRoom) => standbyRoom === info.roomId) - ); - }); + this.standbyRooms = standbyRooms; + this.originalRoomList = rooms; this.roomList = rooms; - // groupping. - this.initGroup(); + if (!!this._searchObj && !!this._searchObj.isShowSearch) { + this._onRoomSearch(this.searchObj); + } - this.changeDetectorRef.detectChanges(); + // groupping. + this._initRoomGroup(); }); combineLatest([ @@ -191,7 +163,6 @@ export class RoomExpansionComponent implements OnInit, OnDestroy { .subscribe(([roomUsers, roomUsersShort]) => { this.roomUsersDictionary = roomUsers; this.roomUsersShortDictionary = roomUsersShort; - this.changeDetectorRef.detectChanges(); }); } @@ -202,9 +173,17 @@ export class RoomExpansionComponent implements OnInit, OnDestroy { } } - initGroup() { + private _initRoomGroup() { this.roomGroup = []; + // standby room exclude. + this.roomList = (this.roomList || []).filter((info) => { + return ( + info.isJoinRoom && + !this.standbyRooms.find((standbyRoom) => standbyRoom === info.roomId) + ); + }); + this.roomList.forEach((roomInfo) => { const date = roomInfo.finalEventDate; let division = ''; @@ -235,40 +214,56 @@ export class RoomExpansionComponent implements OnInit, OnDestroy { }); } }); + + this.changeDetectorRef.markForCheck(); } - getRoomName(roomInfo: RoomInfo): string { - if (!roomInfo) { - return ''; + private _onRoomSearch(obj: SearchInfo) { + const searchRoomList: RoomInfo[] = []; + + this.processing = true; + this.changeDetectorRef.markForCheck(); + + this.originalRoomList.forEach((roomInfo) => { + if (roomInfo.roomName.indexOf(obj.searchWord) > -1) { + searchRoomList.push(roomInfo); + } else { + const roomUsers = this.appChatService.getRoomUserList( + this.user, + roomInfo.roomId, + this.roomUsersDictionary, + this.roomUsersShortDictionary + ); + + if ( + roomUsers.existUsers && + roomUsers.users.filter( + (userInfo) => + userInfo.name.indexOf(obj.searchWord) > -1 || + userInfo.nameEn.indexOf(obj.searchWord) > -1 || + userInfo.nameCn.indexOf(obj.searchWord) > -1 + ).length > 0 + ) { + searchRoomList.push(roomInfo); + } + } + }); + + this.searchResultList.emit(searchRoomList); + this.roomList = searchRoomList.slice(); + this.processing = false; + this._initRoomGroup(); + } + + isToday(date: any): 'T' | 'Y' | undefined { + if (this.dateService.isToday(date)) { + return 'T'; + } else if (this.dateService.isToday(moment(date).add(1, 'days').format())) { + return 'Y'; + } else { + return undefined; } - - const roomName = this.appChatService.getRoomName( - this.organizationTranslate, - this.loginRes, - roomInfo, - this.roomUsersDictionary, - this.roomUsersShortDictionary - ); - - return roomName; - } - - getRoomProfileImage(roomInfo: RoomInfo): string { - let roomImage = ''; - if (!!roomInfo) { - roomImage = this.appChatService.getRoomProfileImage( - roomInfo, - this.loginRes, - this.roomUsersDictionary, - this.roomUsersShortDictionary - ); - } - - return roomImage; - } - - isToday(date: any) { - return this.dateService.isToday(date); + // return this.dateService.isToday(date); } onToggleAlarm(roomInfo: RoomInfo): void { @@ -289,40 +284,12 @@ export class RoomExpansionComponent implements OnInit, OnDestroy { } as UpdateRequest }) ); - this.changeDetectorRef.detectChanges(); + this.changeDetectorRef.markForCheck(); } onDelRoom(roomInfo: RoomInfo): void { - if (!roomInfo) { - return; - } - - const dialogRef = this.dialog.open< - ConfirmDialogComponent, - ConfirmDialogData, - ConfirmDialogResult - >(ConfirmDialogComponent, { - data: { - title: this.i18nService.t('dialog.title.exitFromRoom'), - html: this.i18nService.t('dialog.confirmExitFromRoom') - } - }); - - dialogRef - .afterClosed() - .pipe(take(1)) - .subscribe((result) => { - if (!!result && !!result.choice) { - this.store.dispatch( - RoomActions.del({ - req: { - roomId: roomInfo.roomId - } as ExitRequest - }) - ); - } - }); - this.changeDetectorRef.detectChanges(); + this.appChatService.exitRoomDialog(roomInfo); + this.changeDetectorRef.markForCheck(); } getChecked(roomInfo: RoomInfo): boolean { diff --git a/src/app/ucap/chat/components/room-expansion.strategy.ts b/src/app/ucap/chat/components/room-expansion.strategy.ts deleted file mode 100644 index 5624900..0000000 --- a/src/app/ucap/chat/components/room-expansion.strategy.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Observable, Subject } from 'rxjs'; - -import { - VirtualScrollStrategy, - CdkVirtualScrollViewport -} from '@angular/cdk/scrolling'; -import { distinctUntilChanged } from 'rxjs/operators'; - -export class ChatRoomVirtualScrollStrategy implements VirtualScrollStrategy { - scrolledIndexChange: Observable; - - private indexSubject = new Subject(); - private viewport: CdkVirtualScrollViewport | null = null; - - constructor() { - this.scrolledIndexChange = this.indexSubject.pipe(distinctUntilChanged()); - } - - attach(viewport: CdkVirtualScrollViewport): void { - this.viewport = viewport; - } - detach(): void { - this.indexSubject.complete(); - this.viewport = null; - } - onContentScrolled(): void {} - onDataLengthChanged(): void {} - onContentRendered(): void {} - onRenderedOffsetChanged(): void {} - scrollToIndex(index: number, behavior: ScrollBehavior): void {} -} diff --git a/src/app/ucap/chat/components/room-list-item-01.component.html b/src/app/ucap/chat/components/room-list-item-01.component.html new file mode 100644 index 0000000..026c3bb --- /dev/null +++ b/src/app/ucap/chat/components/room-list-item-01.component.html @@ -0,0 +1,21 @@ +
    + +
    diff --git a/src/app/ucap/chat/components/room-list-item-01.component.scss b/src/app/ucap/chat/components/room-list-item-01.component.scss new file mode 100644 index 0000000..be45828 --- /dev/null +++ b/src/app/ucap/chat/components/room-list-item-01.component.scss @@ -0,0 +1,4 @@ +.profile-list-container { + width: 100%; + height: 100%; +} diff --git a/src/app/ucap/chat/components/room-list-item-01.component.spec.ts b/src/app/ucap/chat/components/room-list-item-01.component.spec.ts new file mode 100644 index 0000000..bb28696 --- /dev/null +++ b/src/app/ucap/chat/components/room-list-item-01.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { RoomListItem01Component } from './room-list-item-01.component'; + +describe('app::ucap::caht::RoomListItem01Component', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [RoomListItem01Component] + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(RoomListItem01Component); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'ucap-lg-web'`, () => { + const fixture = TestBed.createComponent(RoomListItem01Component); + const app = fixture.componentInstance; + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(RoomListItem01Component); + fixture.detectChanges(); + const compiled = fixture.nativeElement; + expect(compiled.querySelector('.content span').textContent).toContain( + 'ucap-lg-web app is running!' + ); + }); +}); diff --git a/src/app/ucap/chat/components/room-list-item-01.component.ts b/src/app/ucap/chat/components/room-list-item-01.component.ts new file mode 100644 index 0000000..212a623 --- /dev/null +++ b/src/app/ucap/chat/components/room-list-item-01.component.ts @@ -0,0 +1,187 @@ +import { Subject, combineLatest } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + Output, + EventEmitter +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { VersionInfo2Response } from '@ucap/api-public'; +import { RoomInfo, RoomType } from '@ucap/protocol-room'; +import { User } from '@ucap/protocol-info'; + +import { I18nService } from '@ucap/ng-i18n'; +import { UserSelector } from '@ucap/ng-store-organization'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; +import { + RoomSelector, + RoomUserMap, + RoomUserShortMap +} from '@ucap/ng-store-chat'; + +import { + TranslatePipe as OrganizationTranslate, + TranslateService +} from '@ucap/ng-ui-organization'; + +import { AppChatService } from '@app/services/app-chat.service'; + +@Component({ + selector: 'app-chat-room-list-item-01', + templateUrl: './room-list-item-01.component.html', + styleUrls: ['./room-list-item-01.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class RoomListItem01Component implements OnInit, OnDestroy { + private ngOnDestroySubject: Subject = new Subject(); + + @Input() + roomInfo: RoomInfo; + + @Input() + currentRoomId: string; + + @Input() + checkable = false; + + @Input() + selectedRoomList: RoomInfo[] = []; + + @Input() + moreable = true; + + @Output() + toggleItem = new EventEmitter<{ + checked: boolean; + roomInfo: RoomInfo; + }>(); + + @Output() + openChatRoom = new EventEmitter(); + + @Output() + toggleAlarm = new EventEmitter(); + + @Output() + delRoom = new EventEmitter(); + + user: User; + versionInfo2Res: VersionInfo2Response; + + organizationTranslate: OrganizationTranslate; + + defaultProfileImage: string; + defaultProfileImageMulti: string; + + roomUserMap: RoomUserMap; + roomUserShortMap: RoomUserShortMap; + + RoomType = RoomType; + + constructor( + private store: Store, + private changeDetectorRef: ChangeDetectorRef, + private appChatService: AppChatService, + private i18nService: I18nService, + private translateService: TranslateService + ) { + this.translateService.setDefaultLang(this.i18nService.currentLng); + this.translateService.use(this.i18nService.currentLng); + this.organizationTranslate = new OrganizationTranslate( + this.translateService, + this.changeDetectorRef + ); + + // default image setting + this.defaultProfileImage = this.appChatService.defaultProfileImage; + this.defaultProfileImageMulti = this.appChatService.defaultProfileImageMulti; + } + + ngOnInit(): void { + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + + combineLatest([ + this.store.pipe(select(UserSelector.user)), + this.store.pipe(select(RoomSelector.roomUser, this.roomInfo.roomId)), + this.store.pipe(select(RoomSelector.roomUserShort, this.roomInfo.roomId)) + ]) + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe(([user, roomUserMap, roomUserShortMap]) => { + this.user = user; + this.roomUserMap = roomUserMap; + this.roomUserShortMap = roomUserShortMap; + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + getRoomName(): string { + if (!this.roomInfo) { + return ''; + } + const roomName = this.appChatService.getRoomName( + this.organizationTranslate, + this.user, + this.roomInfo, + this.roomUserMap, + this.roomUserShortMap + ); + + return roomName; + } + + getRoomProfileImage(): string { + if (!this.roomInfo) { + return ''; + } + const roomImage = this.appChatService.getRoomProfileImage( + this.user, + this.roomInfo, + this.roomUserMap, + this.roomUserShortMap + ); + + return roomImage; + } + + getChecked(roomInfo: RoomInfo): boolean { + if (this.selectedRoomList.some((info) => info.roomId === roomInfo.roomId)) { + return true; + } else { + return false; + } + } + + onToggleRoom(event: { checked: boolean; roomInfo: RoomInfo }): void { + this.toggleItem.emit(event); + } + onOpenChatRoom(roomInfo: RoomInfo) { + this.openChatRoom.emit(roomInfo); + } + onToggleAlarm(roomInfo: RoomInfo) { + this.toggleAlarm.emit(roomInfo); + } + onDelRoom(roomInfo: RoomInfo) { + this.delRoom.emit(roomInfo); + } +} diff --git a/src/app/ucap/chat/components/room-list.component.html b/src/app/ucap/chat/components/room-list.component.html new file mode 100644 index 0000000..40531f6 --- /dev/null +++ b/src/app/ucap/chat/components/room-list.component.html @@ -0,0 +1,12 @@ +
    + + + + + +
    diff --git a/src/app/ucap/chat/components/room-list.component.scss b/src/app/ucap/chat/components/room-list.component.scss new file mode 100644 index 0000000..be45828 --- /dev/null +++ b/src/app/ucap/chat/components/room-list.component.scss @@ -0,0 +1,4 @@ +.profile-list-container { + width: 100%; + height: 100%; +} diff --git a/src/app/sections/group/components/component-ui/profile.component.spec.ts b/src/app/ucap/chat/components/room-list.component.spec.ts similarity index 66% rename from src/app/sections/group/components/component-ui/profile.component.spec.ts rename to src/app/ucap/chat/components/room-list.component.spec.ts index 0dc6211..c9660ae 100644 --- a/src/app/sections/group/components/component-ui/profile.component.spec.ts +++ b/src/app/ucap/chat/components/room-list.component.spec.ts @@ -1,28 +1,28 @@ import { TestBed, async } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { ProfileComponent } from './profile.component'; +import { Profile01Component } from './profile-01.component'; -describe('app::sections::group::ProfileComponent', () => { +describe('app::ucap::organization::Profile01Component', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule], - declarations: [ProfileComponent] + declarations: [Profile01Component] }).compileComponents(); })); it('should create the app', () => { - const fixture = TestBed.createComponent(ProfileComponent); + const fixture = TestBed.createComponent(Profile01Component); const app = fixture.componentInstance; expect(app).toBeTruthy(); }); it(`should have as title 'ucap-lg-web'`, () => { - const fixture = TestBed.createComponent(ProfileComponent); + const fixture = TestBed.createComponent(Profile01Component); const app = fixture.componentInstance; }); it('should render title', () => { - const fixture = TestBed.createComponent(ProfileComponent); + const fixture = TestBed.createComponent(Profile01Component); fixture.detectChanges(); const compiled = fixture.nativeElement; expect(compiled.querySelector('.content span').textContent).toContain( diff --git a/src/app/ucap/chat/components/room-list.component.ts b/src/app/ucap/chat/components/room-list.component.ts new file mode 100644 index 0000000..7f83a74 --- /dev/null +++ b/src/app/ucap/chat/components/room-list.component.ts @@ -0,0 +1,94 @@ +import { Subject, combineLatest } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + Output, + EventEmitter +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { RoomInfo } from '@ucap/protocol-room'; +import { UserInfoSS } from '@ucap/protocol-query'; + +import { RoomSelector } from '@ucap/ng-store-chat'; + +@Component({ + selector: 'app-chat-room-list', + templateUrl: './room-list.component.html', + styleUrls: ['./room-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class RoomListComponent implements OnInit, OnDestroy { + @Input() + checkable = false; + + @Input() + isDialog = false; + + @Input() + isMultiCheck = false; + + @Output() + clicked: EventEmitter<{ userInfo: UserInfoSS }> = new EventEmitter(); + + @Output() + toggleRoom: EventEmitter = new EventEmitter(); + + selectedRoomList: RoomInfo[] = []; + selectedRoom: RoomInfo; + + roomList: RoomInfo[]; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private store: Store, + private changeDetectorRef: ChangeDetectorRef + ) {} + + ngOnInit(): void { + combineLatest([ + this.store.pipe(select(RoomSelector.rooms)), + this.store.pipe(select(RoomSelector.standbyRooms)) + ]) + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe(([rooms, standbyRooms]) => { + rooms = (rooms || []).filter((info) => { + return ( + info.isJoinRoom && + !standbyRooms.find((standbyRoom) => standbyRoom === info.roomId) + ); + }); + this.roomList = rooms; + this.changeDetectorRef.markForCheck(); + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onToggleRoom(event: { checked: boolean; roomInfo: RoomInfo }): void { + if (!!this.isMultiCheck) { + } else { + if (!!event.checked) { + this.selectedRoom = event.roomInfo; + this.selectedRoomList = [this.selectedRoom]; + } else { + this.selectedRoom = undefined; + this.selectedRoomList = []; + } + } + this.toggleRoom.emit(this.selectedRoom); + } +} diff --git a/src/app/ucap/chat/components/room-setting.component.html b/src/app/ucap/chat/components/room-setting.component.html new file mode 100644 index 0000000..e2d16a3 --- /dev/null +++ b/src/app/ucap/chat/components/room-setting.component.html @@ -0,0 +1,77 @@ +
    +
    +
    +
    +

    + {{ 'chat:dialog.roomName' | ucapI18n }} +

    + + + + {{ input.value?.length || 0 }}/20 + +
    +
    +

    + {{ 'chat:dialog.roomNameChangeTarget' | ucapI18n }} +

    + + {{ + 'chat:dialog.me' | ucapI18n + }} + {{ + 'chat:dialog.all' | ucapI18n + }} + + +
    +
    + +
    +
    +

    + {{ 'chat:dialog.settingTimer' | ucapI18n }} +

    + + + + {{ timer.text }} + + + {{ 'chat:dialog:settingTimerHint' | ucapI18n }} + +
    +
    +
    +
    diff --git a/src/app/ucap/chat/components/room-setting.component.scss b/src/app/ucap/chat/components/room-setting.component.scss new file mode 100644 index 0000000..57c053a --- /dev/null +++ b/src/app/ucap/chat/components/room-setting.component.scss @@ -0,0 +1,15 @@ +.mat-form-field { + font-size: 1em; + width: 100%; + .btn-close { + margin-top: 2px; + color: #fd78a1 !important; + &.hide { + display: none; + } + } +} +.write-timer-set { + display: flex; + flex-direction: column; +} diff --git a/src/app/sections/organization/components/detail-table.component.spec.ts b/src/app/ucap/chat/components/room-setting.component.spec.ts similarity index 52% rename from src/app/sections/organization/components/detail-table.component.spec.ts rename to src/app/ucap/chat/components/room-setting.component.spec.ts index 17ac9d1..a58ae44 100644 --- a/src/app/sections/organization/components/detail-table.component.spec.ts +++ b/src/app/ucap/chat/components/room-setting.component.spec.ts @@ -1,19 +1,19 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { DetailTableComponent } from './detail-table.component'; +import { RoomSettingComponent } from './room-setting.component'; -describe('Organization::DetailTableComponent', () => { - let component: DetailTableComponent; - let fixture: ComponentFixture; +describe('app::ucap::chat::RoomSettingComponent', () => { + let component: RoomSettingComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [DetailTableComponent] + declarations: [RoomSettingComponent] }).compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(DetailTableComponent); + fixture = TestBed.createComponent(RoomSettingComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/ucap/chat/components/room-setting.component.ts b/src/app/ucap/chat/components/room-setting.component.ts new file mode 100644 index 0000000..e39af50 --- /dev/null +++ b/src/app/ucap/chat/components/room-setting.component.ts @@ -0,0 +1,138 @@ +import { + Component, + OnInit, + OnDestroy, + Output, + EventEmitter, + Input, + ChangeDetectorRef, + ChangeDetectionStrategy +} from '@angular/core'; +import { + UserInfo as RoomUserInfo, + UserInfoShort as RoomUserInfoShort, + RoomInfo +} from '@ucap/protocol-room'; +import { I18nService } from '@ucap/ng-i18n'; +import { Store } from '@ngrx/store'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { MatSelectChange } from '@angular/material/select'; +import { MatRadioChange } from '@angular/material/radio'; + +@Component({ + selector: 'app-chat-room-setting', + templateUrl: './room-setting.component.html', + styleUrls: ['./room-setting.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class RoomSettingComponent implements OnInit, OnDestroy { + @Input() + roomInfo: RoomInfo; + + @Input() + isDialog = false; + + @Output() + changedRoomSetting: EventEmitter<{ + invalid?: boolean; + roomName?: string; + checkedMe?: string; + timerInterval?: number; + }> = new EventEmitter(); + + private ngOnDestroySubject = new Subject(); + + constructor( + private i18nService: I18nService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + roomName: string; + timerArray: { value: number; text: string }[]; + checkMe: string; + isInvlid: boolean; + + ngOnInit() { + this.i18nService.languageChanged$ + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe((_) => { + this._setTimerArray(); + }); + this._setTimerArray(); + this.checkMe = 'all'; + } + + ngOnDestroy() { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + // onKeyupName() { + // this.chatSettingForm.get('roomName').markAsTouched(); + // } + + onChangeName(roomName: string) { + this.roomName = roomName; + this.isInvlid = false; + + if (!!this.roomName && this.roomName.trim().localeCompare('') !== 0) { + const forbidden = /[\{\}\[\]\/?.;:|\)*~`!^+<>@\#$%&\\\=\(\'\"]/g.test( + this.roomName + ); + + if (forbidden) { + this.isInvlid = true; + } + } + + this.changedRoomSetting.emit({ + invalid: this.isInvlid, + roomName: this.roomName, + checkedMe: this.checkMe + }); + } + + private _setTimerArray() { + const hourFrom = this.i18nService.t('common:units.hourFrom'); + const minute = this.i18nService.t('common:units.minute'); + const second = this.i18nService.t('common:units.second'); + + this.timerArray = [ + { value: 5, text: `5 ${second}` }, + { value: 10, text: `10 ${second}` }, + { value: 30, text: `30 ${second}` }, + { value: 60, text: `1 ${minute}` }, + { value: 300, text: `5 ${minute}` }, + { value: 600, text: `10 ${minute}` }, + { value: 1800, text: `30 ${minute}` }, + { value: 3600, text: `1 ${hourFrom}` }, + { value: 21600, text: `6 ${hourFrom}` }, + { value: 43200, text: `12 ${hourFrom}` }, + { value: 86400, text: `24 ${hourFrom}` } + ]; + } + + onChangeRadioBtn(event: MatRadioChange) { + if (event.value === 'me') { + this.checkMe = 'me'; + } else { + this.checkMe = 'all'; + } + + this.changedRoomSetting.emit({ + invalid: this.isInvlid, + roomName: this.roomName, + checkedMe: this.checkMe + }); + } + onChangeSelection(value: number) { + const interval = value; + + this.changedRoomSetting.emit({ + timerInterval: !value ? 86400 : value + }); + } +} diff --git a/src/app/ucap/chat/components/sticker.selector.component.html b/src/app/ucap/chat/components/sticker.selector.component.html index 8b33ca2..42b18b9 100644 --- a/src/app/ucap/chat/components/sticker.selector.component.html +++ b/src/app/ucap/chat/components/sticker.selector.component.html @@ -1,6 +1,6 @@ - +
    -

    이모티콘

    +

    {{ 'chat:label.imoticon' | ucapI18n }}

    @@ -10,7 +10,7 @@
    -
    +
    (); @Output() - closeSticker = new EventEmitter(); + closed = new EventEmitter(); stickerHistory: string[] = []; - stickerBasePath = '../../../../assets/sticker/'; + stickerBasePath = 'assets/sticker/'; stickerInfoList: StickerInfo[] = []; stickerFileInfoList: StickerFilesInfo[] = []; currentSticker: StickerFilesInfo; @@ -115,6 +115,6 @@ export class StickerSelectorComponent implements OnInit, AfterViewInit { } onClickClose() { - this.closeSticker.emit(); + this.closed.emit(); } } diff --git a/src/app/ucap/chat/components/translation.selector.component.html b/src/app/ucap/chat/components/translation.selector.component.html index c26bd54..d3507e1 100644 --- a/src/app/ucap/chat/components/translation.selector.component.html +++ b/src/app/ucap/chat/components/translation.selector.component.html @@ -1,39 +1,91 @@ - +
    -

    번역

    +

    {{ 'chat:label.translations.translation' | ucapI18n }}

    -
    -
    -
    - 미리보기 영역입니다. -
    -
    - - -
    -
    -
    - 대상언어 - - - 번역없음 - 영어 - 일어 - - +
    +
    +
    + {{ + 'chat:label.translations.targetLanguage' | ucapI18n + }} + + + {{ + 'chat:label.translations.noTranslation' | ucapI18n + }} + {{ dest.text }} + + + - - 간략보기 - - - 미리보기 - -
    +
    + + {{ 'chat:label.translations.simpleView' | ucapI18n }} + + + {{ 'chat:label.translations.preview' | ucapI18n }} + +
    +
    + +
    +
    + {{ translationPreviewInfo.previewInfo.translation }} +
    +
    + + +
    +
    + + +
    diff --git a/src/app/ucap/chat/components/translation.selector.component.scss b/src/app/ucap/chat/components/translation.selector.component.scss index ed7da72..bedd0b9 100644 --- a/src/app/ucap/chat/components/translation.selector.component.scss +++ b/src/app/ucap/chat/components/translation.selector.component.scss @@ -1,9 +1,130 @@ -.bubble-main { - padding: 10px; - text-align: left; - span { - word-wrap: break-word; - white-space: pre-wrap; - word-break: break-word; +@import '~@ucap/lg-scss/mixins'; + +.translation-info-box { + display: flex; + flex-direction: column; + .translation-input-box { + display: flex; + flex-direction: row; + align-items: center; + height: 44px; + padding: 0 16px; + .text-language { + font-size: 0.929em; + font-weight: 600; + color: #333; + flex-grow: 0; + white-space: nowrap; + @include screen(mid) { + display: none; + } + } + .select-language { + margin-left: 18px; + padding-bottom: 0; + flex-grow: 0; + @include ucapMatFormField(0, 0, 230px, 330px, 230px, 40px, 24px); + @include screen(custom, max, 1320) { + @include ucapMatFormField(0, 0, 130px, 150px, 130px, 40px, 24px); + } + //@include screen(mid) { + // @include ucapMatFormField(0, 0, 100px, 120px, 100px, 40px, 24px); + //} + @include screen(mid) { + margin-left: 16; + } + @include screen(xs) { + margin-left: 0; + } + } + .btn-language-view { + flex-grow: 1; + display: flex; + flex-direction: row-reverse; + align-items: center; + .btn-simpleview { + order: 2; + br { + display: none; + } + } + .btn-preview { + order: 1; + margin-left: 20px; + br { + display: none; + } + } + @include screen(custom, max, 414) { + .btn-simpleview { + line-height: 1; + margin-left: 15px; + + br { + display: block; + } + } + .btn-preview { + line-height: 1; + margin-left: 12px; + + br { + display: block; + } + } + } + } + } + .preview { + background-color: rgba(0, 0, 0, 0.6); + height: 70px; + padding: 13px 45px 13px 16px; + position: relative; + overflow-y: auto; + display: flex; + flex-direction: row; + align-items: flex-start; + .preview-contents { + font-size: 0.929em; + line-height: 1.67; + color: #fff; + flex-grow: 1; + } + .btn-area { + align-self: flex-start; + justify-content: flex-end; + .btn-message-send { + width: 38px; + height: 38px; + box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.11); + border: solid 3px #ffffff; + background-image: linear-gradient(225deg, #fadfaa 13%, #f92465 95%); + font-size: 1.714em; + position: fixed; + z-index: 10; + right: 20px; + border-radius: 50%; + color: $white; + text-align: center; + padding: 4px; + } + } + } + .btn-language-conversion { + width: 38px; + height: 38px; + box-shadow: 0px 2px 4px 0 rgba(0, 0, 0, 0.11); + border: solid 3px #ffffff; + background-color: #584f52; + font-size: 1.714em; + position: absolute; + z-index: 15; + bottom: 25px; + right: 20px; + transition: all 0.3s; + border-radius: 50%; + color: $white; + text-align: center; + padding: 4px; } } diff --git a/src/app/ucap/chat/components/translation.selector.component.ts b/src/app/ucap/chat/components/translation.selector.component.ts index 8546cd8..f9bf62a 100644 --- a/src/app/ucap/chat/components/translation.selector.component.ts +++ b/src/app/ucap/chat/components/translation.selector.component.ts @@ -1,11 +1,16 @@ import { Component, OnInit, - Input, - ElementRef, AfterViewInit, - Inject + EventEmitter, + Output, + Input } from '@angular/core'; +import { MatSelectChange } from '@angular/material/select'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { TranslationSaveResponse } from '@ucap/api-common'; +import { EventType } from '@ucap/protocol-event'; +import { MatSlideToggleChange } from '@angular/material/slide-toggle'; @Component({ selector: 'app-chat-selector-translation', @@ -13,9 +18,194 @@ import { styleUrls: ['./translation.selector.component.scss'] }) export class TranslationSelectorComponent implements OnInit, AfterViewInit { - constructor() {} + @Input() + destLocale: string; - ngOnInit() {} + @Input() + simpleView: boolean; + + @Input() + preView: boolean; + + @Input() + translationPreviewInfo: { + previewInfo: TranslationSaveResponse | null; + translationType: EventType.Translation | EventType.MassTranslation; + }; + + @Output() + closed = new EventEmitter(); + + @Output() + changeDestLocale = new EventEmitter(); + + @Output() + sendTranslationMessage = new EventEmitter<{ + previewInfo: TranslationSaveResponse | null; + translationType: EventType.Translation | EventType.MassTranslation; + }>(); + + @Output() + translationMessage = new EventEmitter(); + + @Output() + changeTranslationSimpleview = new EventEmitter(); + @Output() + changeTranslationPreview = new EventEmitter(); + + translationDestList: { + key: string; + text: string; + }[] = [ + { key: 'af', text: 'Afrikaans' }, + { key: 'sq', text: 'Albanian' }, + { key: 'am', text: 'Amharic' }, + { key: 'ar', text: 'Arabic' }, + { key: 'hy', text: 'Armenian' }, + { key: 'az', text: 'Azeerbaijani' }, + { key: 'eu', text: 'Basque' }, + { key: 'be', text: 'Belarusian' }, + { key: 'bn', text: 'Bengali' }, + { key: 'bs', text: 'Bosnian' }, + { key: 'bg', text: 'Bulgarian' }, + { key: 'ca', text: 'Catalan' }, + { key: 'ceb', text: 'Cebuano' }, + { key: 'zh-CN', text: 'Chinese(Simplified)' }, + { key: 'zh-TW', text: 'Chinese(Traditional)' }, + { key: 'co', text: 'Corsican' }, + { key: 'hr', text: 'Croatian' }, + { key: 'cs', text: 'Czech' }, + { key: 'da', text: 'Danish' }, + { key: 'nl', text: 'Dutch' }, + { key: 'en', text: 'English' }, + { key: 'eo', text: 'Esperanto' }, + { key: 'fi', text: 'Finnish' }, + { key: 'fr', text: 'French' }, + { key: 'fy', text: 'Frisian' }, + { key: 'gl', text: 'Galician' }, + { key: 'ka', text: 'Georgian' }, + { key: 'de', text: 'German' }, + { key: 'el', text: 'Greek' }, + { key: 'gu', text: 'Gujarati' }, + { key: 'ht', text: 'Haitian Creole' }, + { key: 'ha', text: 'Hausa' }, + { key: 'haw', text: 'Hawaiian' }, + { key: 'iw', text: 'Hebrew' }, + { key: 'hi', text: 'Hindi' }, + { key: 'hmn', text: 'Hmong' }, + { key: 'hu', text: 'Hungarian' }, + { key: 'is', text: 'Icelandic' }, + { key: 'ig', text: 'Igbo' }, + { key: 'id', text: 'Indonesian' }, + { key: 'ga', text: 'Irish' }, + { key: 'it', text: 'Italian' }, + { key: 'ja', text: 'Japanese' }, + { key: 'jw', text: 'Javanese' }, + { key: 'kn', text: 'Kannada' }, + { key: 'kk', text: 'Kazakh' }, + { key: 'km', text: 'Khmer' }, + { key: 'ko', text: 'Korean' }, + { key: 'ku', text: 'Kurdish' }, + { key: 'ky', text: 'Kyrgyz' }, + { key: 'lo', text: 'Lao' }, + { key: 'la', text: 'Latin' }, + { key: 'lv', text: 'Latvian' }, + { key: 'lt', text: 'Lithuanian' }, + { key: 'lb', text: 'Luxembourgish' }, + { key: 'mk', text: 'Macedonian' }, + { key: 'mg', text: 'Malagasy' }, + { key: 'ms', text: 'Malay' }, + { key: 'ml', text: 'Malayalam' }, + { key: 'mi', text: 'Maori' }, + { key: 'mr', text: 'Marathi' }, + { key: 'mn', text: 'Mongolian' }, + { key: 'my', text: 'Myanmar(Burmese)' }, + { key: 'ne', text: 'Nepali' }, + { key: 'no', text: 'Norwegian' }, + { key: 'ny', text: 'Nyanja(Chichewa)' }, + { key: 'ps', text: 'Pashto' }, + { key: 'fa', text: 'Persian' }, + { key: 'pl', text: 'Polish' }, + { key: 'pt', text: 'Portuguese' }, + { key: 'pa', text: 'Punjabi' }, + { key: 'ro', text: 'Romanian' }, + { key: 'ru', text: 'Russian' }, + { key: 'sm', text: 'Samoan' }, + { key: 'gd', text: 'Scots Gaelic' }, + { key: 'sr', text: 'Serbian' }, + { key: 'st', text: 'Sesotho' }, + { key: 'sn', text: 'Shona' }, + { key: 'sd', text: 'Sindhi' }, + { key: 'si', text: 'Sinhala(Sinhalese)' }, + { key: 'sk', text: 'Slovak' }, + { key: 'sl', text: 'Slovenian' }, + { key: 'so', text: 'Somali' }, + { key: 'es', text: 'Spanish' }, + { key: 'su', text: 'Sundanese' }, + { key: 'sw', text: 'Swahili' }, + { key: 'sv', text: 'Swedish' }, + { key: 'tl', text: 'Tagalog(Filipino)' }, + { key: 'tg', text: 'Tajik' }, + { key: 'ta', text: 'Tamil' }, + { key: 'te', text: 'Telugu' }, + { key: 'th', text: 'Thai' }, + { key: 'tr', text: 'Turkish' }, + { key: 'uk', text: 'Ukrainian' }, + { key: 'ur', text: 'Urdu' }, + { key: 'uz', text: 'Uzbek' }, + { key: 'vi', text: 'Vietnamese' }, + { key: 'cy', text: 'Welsh' }, + { key: 'xh', text: 'Xhosa' }, + { key: 'yi', text: 'Yiddish' }, + { key: 'yo', text: 'Yoruba' }, + { key: 'zu', text: 'Zulu' } + ]; + + translationForm: FormGroup; + + isShowTranslationSimpleview = false; + isShowTranslationPreview = false; + + constructor(private formBuilder: FormBuilder) {} + + ngOnInit() { + this.translationForm = this.formBuilder.group({ + destType: [this.destLocale] + }); + + this.isShowTranslationSimpleview = this.simpleView; + this.isShowTranslationPreview = this.preView; + } ngAfterViewInit(): void {} + + onClickClose(): void { + this.closed.emit(); + } + + onChangeSelection(event: MatSelectChange) { + this.destLocale = event.value; + this.changeDestLocale.emit(this.destLocale); + } + + onClickSendMessage(translationInfo: { + previewInfo: TranslationSaveResponse | null; + translationType: EventType.Translation | EventType.MassTranslation; + }) { + this.sendTranslationMessage.emit(translationInfo); + } + + onClickTranslationMessage(event: MouseEvent) { + this.translationMessage.emit(); + } + + onChangToggleSimpleview(event: MatSlideToggleChange) { + this.isShowTranslationSimpleview = event.checked; + this.changeTranslationSimpleview.emit(event.checked); + } + + onChangTogglePreview(event: MatSlideToggleChange) { + this.isShowTranslationPreview = event.checked; + this.changeTranslationPreview.emit(event.checked); + } } diff --git a/src/app/ucap/group/components/expansion.component.html b/src/app/ucap/group/components/expansion-01.component.html similarity index 50% rename from src/app/ucap/group/components/expansion.component.html rename to src/app/ucap/group/components/expansion-01.component.html index 2cf6494..baffc5f 100644 --- a/src/app/ucap/group/components/expansion.component.html +++ b/src/app/ucap/group/components/expansion-01.component.html @@ -1,47 +1,55 @@ -
    +
    - - {{ 'category.favorite' | ucapI18n }} + + {{ 'group:category.favorite' | ucapI18n }} - ({{ node.children?.length }}) + {{ getNodeGroupStatus(node) }} - {{ node.groupDetail.name }} - ({{ node.children?.length }}) + {{ node.groupDetail.name }} + + {{ getNodeGroupStatus(node) }} + - - {{ 'category.default' | ucapI18n }} + + {{ 'group:category.default' | ucapI18n }} - ({{ node.children?.length }}) + {{ getNodeGroupStatus(node) }} @@ -57,21 +65,29 @@ - {{ group.name }} + {{ + group.name + }} + {{ + getGroupNameBySeq(group) + }} + + @@ -105,7 +140,7 @@ #profileContextMenuTrigger="matMenuTrigger" [matMenuTriggerFor]="profileContextMenu" >
    - + - {{ 'moreMenu.profile.open' | ucapI18n }} + face{{ 'group:contextMenu.profileOpen' | ucapI18n }} diff --git a/src/app/ucap/group/components/expansion-01.component.scss b/src/app/ucap/group/components/expansion-01.component.scss new file mode 100644 index 0000000..1c79546 --- /dev/null +++ b/src/app/ucap/group/components/expansion-01.component.scss @@ -0,0 +1,56 @@ +@charset 'UTF-8'; + +@import '~@ucap/lg-scss/mixins'; + +.expansion-01-container { + width: 100%; + height: 100%; + .ucap-clickable { + display: flex; + min-height: 50px; + border-top: 10px solid #f1f2f6; + border-bottom: 1px solid $gray-rec; + li { + width: 100%; + .path { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + .group-info { + width: calc(100% - 100px); + justify-self: self-start; + flex-grow: 1; + font-size: 13px; + } + } + } + } + .currentGroup { + background-color: red; + } + .header-buddy, + .header-favorite, + .header-default { + display: flex; + flex-grow: 1; + flex-direction: row; + + .group-name { + @include ellipsis-column(1); + @include wordBreak(); + line-break: anywhere; + margin-right: 4px; + } + .group-member-amount { + flex: 0 0 auto; + color: #e42f66; + font-weight: 600; + } + } +} + +// +.manu-title { + @include menu-title(); +} diff --git a/src/app/sections/organization/components/tree.section.component.spec.ts b/src/app/ucap/group/components/expansion-01.component.spec.ts similarity index 66% rename from src/app/sections/organization/components/tree.section.component.spec.ts rename to src/app/ucap/group/components/expansion-01.component.spec.ts index fc648f7..34b843f 100644 --- a/src/app/sections/organization/components/tree.section.component.spec.ts +++ b/src/app/ucap/group/components/expansion-01.component.spec.ts @@ -1,28 +1,28 @@ import { TestBed, async } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { TreeSectionComponent } from './tree.section.component'; +import { Expansion01Component } from './expansion-01.component'; -describe('app::sections::organization::TreeSectionComponent', () => { +describe('app::ucap::group::Expansion01Component', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule], - declarations: [TreeSectionComponent] + declarations: [Expansion01Component] }).compileComponents(); })); it('should create the app', () => { - const fixture = TestBed.createComponent(TreeSectionComponent); + const fixture = TestBed.createComponent(Expansion01Component); const app = fixture.componentInstance; expect(app).toBeTruthy(); }); it(`should have as title 'ucap-lg-web'`, () => { - const fixture = TestBed.createComponent(TreeSectionComponent); + const fixture = TestBed.createComponent(Expansion01Component); const app = fixture.componentInstance; }); it('should render title', () => { - const fixture = TestBed.createComponent(TreeSectionComponent); + const fixture = TestBed.createComponent(Expansion01Component); fixture.detectChanges(); const compiled = fixture.nativeElement; expect(compiled.querySelector('.content span').textContent).toContain( diff --git a/src/app/ucap/group/components/expansion.component.ts b/src/app/ucap/group/components/expansion-01.component.ts similarity index 62% rename from src/app/ucap/group/components/expansion.component.ts rename to src/app/ucap/group/components/expansion-01.component.ts index d8750c4..45ca490 100644 --- a/src/app/ucap/group/components/expansion.component.ts +++ b/src/app/ucap/group/components/expansion-01.component.ts @@ -1,9 +1,10 @@ import { Subject, combineLatest, of } from 'rxjs'; -import { takeUntil, map, take, catchError } from 'rxjs/operators'; +import { takeUntil } from 'rxjs/operators'; import { Component, OnInit, + AfterViewInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, @@ -17,44 +18,36 @@ import { import { Store, select } from '@ngrx/store'; -import { - FixedSizeVirtualScrollStrategy, - VIRTUAL_SCROLL_STRATEGY -} from '@angular/cdk/scrolling'; +import { MatMenuTrigger } from '@angular/material/menu'; +import { StatusCode, PresenceType } from '@ucap/core'; import { VersionInfo2Response } from '@ucap/api-public'; -import { LoginResponse } from '@ucap/protocol-authentication'; +import { StatusBulkInfo } from '@ucap/protocol-status'; import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; +import { User } from '@ucap/protocol-info'; import { LogService } from '@ucap/ng-logger'; -import { NodeType, FlatNode } from '@ucap/ng-ui-group'; +import { I18nService } from '@ucap/ng-i18n'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; +import { BuddySelector, GroupSelector } from '@ucap/ng-store-group'; +import { PresenceSelector, UserSelector } from '@ucap/ng-store-organization'; + import { - LoginSelector, - ConfigurationSelector -} from '@ucap/ng-store-authentication'; -import { - BuddySelector, - GroupSelector, - BuddyActions, - GroupActions -} from '@ucap/ng-store-group'; + ExpansionComponent as UcapExpansionComponent, + NodeType, + FlatNode, + GroupNode +} from '@ucap/ng-ui-group'; +import { PresenceUtil } from '@ucap/ng-ui-organization'; import { AppAuthenticationService } from '@app/services/app-authentication.service'; import { LoginSession } from '@app/models/login-session'; -import { MatMenuTrigger } from '@angular/material/menu'; -import { MatDialog } from '@angular/material/dialog'; - -import { ExpansionComponent as UcapExpansionComponent } from '@ucap/ng-ui-group'; import { environment } from '@environments'; -import { PresenceSelector } from '@ucap/ng-store-organization'; -import { StatusCode } from '@ucap/core'; - -import { I18nService } from '@ucap/ng-i18n'; -import { ProfileListItem01Component } from '@ucap/ng-ui-organization'; -import { ProfileListItem02Component } from './profile-list-item-02.component'; +import { GroupOpenInfo } from '@app/models/group-open-info'; +import { SortViewType } from '@app/pages/group/types/sort-view.type'; export type UserInfoTypes = | UserInfo @@ -63,39 +56,21 @@ export type UserInfoTypes = | UserInfoDN | RoomUserInfo; -export class GroupVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy { - constructor() { - super(60, 150, 200); // (itemSize, minBufferPx, maxBufferPx) - } -} - @Component({ - selector: 'app-group-expansion', - templateUrl: './expansion.component.html', - styleUrls: ['./expansion.component.scss'], - providers: [ - { - provide: VIRTUAL_SCROLL_STRATEGY, - useClass: GroupVirtualScrollStrategy - } - ], + selector: 'app-group-expansion-01', + templateUrl: './expansion-01.component.html', + styleUrls: ['./expansion-01.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ExpansionComponent implements OnInit, OnDestroy { - @Input() - checkable = false; - - @Input() - editable = false; - - @Input() - isDialog = false; - +export class Expansion01Component implements OnInit, OnDestroy, AfterViewInit { @Input() selectedUserList: UserInfoTypes[]; @Input() - showType: string; + showType: SortViewType; + + @Input() + loginSession: LoginSession; @Output() profileMenu: EventEmitter = new EventEmitter(); @@ -119,10 +94,12 @@ export class ExpansionComponent implements OnInit, OnDestroy { }> = new EventEmitter(); @Output() - toggleCheckUser: EventEmitter<{ - checked: boolean; - userInfo: UserInfoSS; - }> = new EventEmitter(); + toggleCheckUser: EventEmitter< + { + checked: boolean; + userInfo: UserInfoSS; + }[] + > = new EventEmitter(); @Output() toggleCheckGroup = new EventEmitter<{ @@ -130,6 +107,15 @@ export class ExpansionComponent implements OnInit, OnDestroy { groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; }>(); + @Output() + floatingProfileMenu = new EventEmitter<{ + menuType: string; + userInfo: UserInfoF; + }>(); + + @Output() + groupDestroy = new EventEmitter(); + @ViewChild('groupExpansion', { static: false }) groupExpansion: UcapExpansionComponent; @@ -145,12 +131,12 @@ export class ExpansionComponent implements OnInit, OnDestroy { profileContextMenuTrigger: MatMenuTrigger; profileContextMenuPosition = { x: '0px', y: '0px' }; - loginSession: LoginSession; versionInfo2Res: VersionInfo2Response; - loginRes: LoginResponse; - isProfileClicked = false; + user: User; + groupMenuEvent: MouseEvent; - isSearchData = false; + statusBulkInfos: StatusBulkInfo[]; + currentSelectUser: UserInfo; displayOrder: NodeType[] = [ NodeType.Profile, @@ -159,15 +145,20 @@ export class ExpansionComponent implements OnInit, OnDestroy { NodeType.Default ]; - profile: UserInfo; favorites: UserInfo[]; + onlineFavorites: UserInfo[]; + groupBuddies: { group: GroupDetailData; buddyList: UserInfo[] }[]; onlineBuddies: { group: GroupDetailData; buddyList: UserInfo[] }[]; onOffBuddies: { group: GroupDetailData; buddyList: UserInfo[] }[]; + groupOpenInfo: GroupOpenInfo; + editablGroup: GroupDetailData = null; - private ngOnDestroySubject: Subject; + String = String; + + private ngOnDestroySubject: Subject = new Subject(); constructor( private appAuthenticationService: AppAuthenticationService, @@ -179,12 +170,7 @@ export class ExpansionComponent implements OnInit, OnDestroy { ) {} ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - - this.appAuthenticationService - .getLoginSession$() - .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe((loginSession) => (this.loginSession = loginSession)); + this.groupOpenInfo = this.loginSession.groupInfo; this.store .pipe( @@ -196,9 +182,9 @@ export class ExpansionComponent implements OnInit, OnDestroy { }); this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; }); combineLatest([ @@ -218,6 +204,22 @@ export class ExpansionComponent implements OnInit, OnDestroy { if (!!favorites && 0 < favorites.length) { this.favorites = favorites; + this.onlineFavorites = this.favorites.filter((favorite) => { + const tempBulkInfo = bulkInfos.filter( + (bulkInfo) => String(bulkInfo.userSeq) === String(favorite.seq) + ); + + if (!!tempBulkInfo && tempBulkInfo.length > 0) { + const pcStatus = tempBulkInfo[0].pcStatus; + if (pcStatus === StatusCode.OnLine) { + return favorite; + } + } + }); + this.changeDetectorRef.markForCheck(); + } else if (!!favorites && favorites.length === 0) { + this.favorites = undefined; + this.onlineFavorites = undefined; this.changeDetectorRef.markForCheck(); } @@ -251,23 +253,25 @@ export class ExpansionComponent implements OnInit, OnDestroy { this.groupBuddies.push({ group, buddyList: buddies.filter((buddy) => { - return -1 < group.userSeqs.indexOf(buddy.seq as any); + return -1 < group.userSeqs.indexOf(String(buddy.seq)); }) }); } + this.changeDetectorRef.markForCheck(); } - // 접속한 동료 리스트 if (!!bulkInfos && bulkInfos.length > 0) { + // 접속한 동료 리스트 this.onlineBuddies = []; + for (const group of groups) { const tempBuddyList = []; this.onlineBuddies.push({ group, buddyList: buddies.filter((buddy) => { - if (-1 < group.userSeqs.indexOf(buddy.seq as any)) { + if (-1 < group.userSeqs.indexOf(String(buddy.seq))) { const tempBulkinfos = bulkInfos.filter( (bulk) => bulk.userSeq === buddy.seq + '' ); @@ -275,10 +279,7 @@ export class ExpansionComponent implements OnInit, OnDestroy { const pcStatus = tempBulkinfos[0].pcStatus; const mStatus = tempBulkinfos[0].mobileStatus; - if ( - pcStatus === StatusCode.OnLine || - mStatus === StatusCode.OnLine - ) { + if (pcStatus === StatusCode.OnLine) { return tempBuddyList.push(buddy); } } @@ -287,24 +288,23 @@ export class ExpansionComponent implements OnInit, OnDestroy { }); } } - // 온/오프라인 리스트 if (!!bulkInfos && bulkInfos.length > 0) { this.onOffBuddies = []; const onlineGroup = { - SENDER_SEQ: this.loginRes.SENDER_SEQ, + SENDER_SEQ: String(this.user.info.seq), SSVC_TYPE: 5, SVC_TYPE: 82, seq: -998, - name: '온라인', + name: this.i18nService.t('group:label.online'), isActive: true } as GroupDetailData; const offlineGroup = { - SENDER_SEQ: this.loginRes.SENDER_SEQ, + SENDER_SEQ: String(this.user.info.seq), SSVC_TYPE: 5, SVC_TYPE: 82, seq: -999, - name: '오프라인', + name: this.i18nService.t('group:label.offline'), isActive: true } as GroupDetailData; @@ -316,23 +316,29 @@ export class ExpansionComponent implements OnInit, OnDestroy { group, buddyList: buddies.filter((buddy) => { const tempBulkinfos = bulkInfos.filter( - (bulk) => bulk.userSeq === buddy.seq + '' + (bulk) => bulk.userSeq + '' === buddy.seq + '' ); if (!!tempBulkinfos && tempBulkinfos.length > 0) { const pcStatus = tempBulkinfos[0].pcStatus; const mStatus = tempBulkinfos[0].mobileStatus; - if (group.name.localeCompare('온라인') === 0) { - if ( - pcStatus === StatusCode.OnLine || - mStatus === StatusCode.OnLine - ) { + if ( + group.name.localeCompare( + this.i18nService.t('group:label.online') + ) === 0 + ) { + if (pcStatus === StatusCode.OnLine) { return tempBuddyList.push(buddy); } - } else if (group.name.localeCompare('오프라인') === 0) { + } else if ( + group.name.localeCompare( + this.i18nService.t('group:label.offline') + ) === 0 + ) { if ( pcStatus === StatusCode.Offline && - mStatus === StatusCode.Offline + (mStatus === StatusCode.Offline || + mStatus === StatusCode.OnLine) ) { return tempBuddyList.push(buddy); } @@ -343,6 +349,15 @@ export class ExpansionComponent implements OnInit, OnDestroy { } } }); + + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(PresenceSelector.selectAllStatusBulkInfo) + ) + .subscribe((statusArr) => { + this.statusBulkInfos = statusArr; + }); } ngOnDestroy(): void { @@ -350,6 +365,42 @@ export class ExpansionComponent implements OnInit, OnDestroy { this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } + + if (!!this.groupOpenInfo && !!this.groupDestroy) { + this.groupDestroy.emit(this.groupOpenInfo); + } + } + + ngAfterViewInit() { + this._groupOpenBySessionInfo(); + } + + onClickNode(params: { node: FlatNode; isExpand }) { + // const findSeq = this.groupOpenInfo.groupSeqs.filter(seq => node.node.groupDetail.seq === seq)[0]; + const group = params.node.node.groupDetail; + + const i = this.groupOpenInfo.groupSeqs.findIndex( + (seq) => seq === group.seq + ); + if (!!params.isExpand) { + if (-1 === i) { + this.groupOpenInfo.groupSeqs.push(group.seq); + } + } else { + if (-1 < i) { + this.groupOpenInfo.groupSeqs = this.groupOpenInfo.groupSeqs.filter( + (seq) => seq !== group.seq + ); + } + } + + if ( + this.groupExpansion && + !!this.groupOpenInfo && + this.groupOpenInfo.groupSeqs.length > 0 + ) { + this.groupExpansion.groupOpenInfos = this.groupOpenInfo.groupSeqs; + } } onProfileMenu( @@ -362,11 +413,54 @@ export class ExpansionComponent implements OnInit, OnDestroy { } onClickUser(event: MouseEvent, userInfo: UserInfo) { + this.currentSelectUser = userInfo; this.clicked.emit({ event, userInfo }); } + getNodeGroupStatus(groupNode: GroupNode): string { + if ( + !!groupNode && + !!this.showType && + this.showType === SortViewType.onOff + ) { + return `${groupNode.children.length}`; + } + + if ( + !!this.statusBulkInfos && + this.statusBulkInfos.length > 0 && + !!groupNode && + !!groupNode.groupDetail && + !!groupNode.groupDetail.userSeqs && + groupNode.groupDetail.userSeqs.length > 0 && + !!this.showType && + this.showType !== SortViewType.onOff + ) { + let onlineCount = 0; + this.statusBulkInfos.map((statusInfo) => { + groupNode.groupDetail.userSeqs.filter((seq) => { + if (Number(seq) === Number(statusInfo.userSeq)) { + const pcOnline = PresenceUtil.isOnline(statusInfo, PresenceType.PC); + const mobileOnline = PresenceUtil.isOnline( + statusInfo, + PresenceType.MOBILE + ); + + if (pcOnline) { + onlineCount++; + } + } + }); + }); + + return `(${onlineCount}/${groupNode.children.length})`; + } else { + return `(0/0)`; + } + } + onClickMoreMenu(params: { event: MouseEvent; node: FlatNode }) { - if (this.showType.localeCompare('ON_OFF') === 0) { + if (this.showType === SortViewType.onOff) { return; } @@ -408,6 +502,8 @@ export class ExpansionComponent implements OnInit, OnDestroy { menuType === 'DIV1' || menuType === 'RENAME' || menuType === 'MANAGE_MEMBER' || + menuType === 'COPY_MEMBER' || + menuType === 'MOVE_MEMBER' || menuType === 'DELETE' ) { if ( @@ -441,7 +537,9 @@ export class ExpansionComponent implements OnInit, OnDestroy { if ( menuType === 'CHAT' || menuType === 'SEND_MESSAGE' || - menuType === 'DIV1' + menuType === 'DIV1' || + menuType === 'COPY_MEMBER' || + menuType === 'MOVE_MEMBER' ) { if (!!group && !!group.userSeqs && group.userSeqs.length > 0) { return true; @@ -472,9 +570,18 @@ export class ExpansionComponent implements OnInit, OnDestroy { right: clientRect.right }; } - const groupBuddyList = this.groupBuddies.filter( - (g) => g.group.seq === group.seq - ); + + let groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }[]; + + if (group.seq === -9999) { + groupBuddyList = []; + groupBuddyList.push({ group, buddyList: this.favorites }); + } else { + groupBuddyList = this.groupBuddies.filter( + (g) => g.group.seq === group.seq + ); + } + this.selectGroupMenu.emit({ menuType, groupBuddyList: groupBuddyList[0], @@ -482,39 +589,23 @@ export class ExpansionComponent implements OnInit, OnDestroy { }); } - /** 개별선택(토글) 이벤트 */ - onChangeCheckUser(param: { checked: boolean; userInfo: UserInfoSS }) { - this.toggleCheckUser.emit(param); - } - - /** 개별 체크여부 */ - getCheckedUser(userInfo: UserInfoTypes) { - if (!!this.selectedUserList && this.selectedUserList.length > 0) { - return ( - this.selectedUserList.filter( - (item) => (item.seq as any) === userInfo.seq - ).length > 0 - ); + getFavoritesForShowType(): UserInfo[] { + if (!!this.showType && this.showType === SortViewType.all) { + return this.favorites; + } else if (!!this.showType && this.showType === SortViewType.onlineBuddy) { + return this.onlineFavorites; + } else if (!!this.showType && this.showType === SortViewType.onOff) { + return null; } - return false; - } - - onToggleCheckGroup(params: { - isChecked: boolean; - groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; - }) { - this.toggleCheckGroup.emit({ - isChecked: params.isChecked, - groupBuddyList: params.groupBuddyList - }); + return null; } getBuddiesForShowType(): { group: GroupDetailData; buddyList: UserInfo[] }[] { - if (!this.showType || this.showType.localeCompare('ALL') === 0) { + if (!!this.showType && this.showType === SortViewType.all) { return this.groupBuddies; - } else if (this.showType.localeCompare('ONLINE_BUDDY') === 0) { + } else if (!!this.showType && this.showType === SortViewType.onlineBuddy) { return this.onlineBuddies; - } else if (this.showType.localeCompare('ON_OFF') === 0) { + } else if (!!this.showType && this.showType === SortViewType.onOff) { return this.onOffBuddies; } return null; @@ -528,6 +619,7 @@ export class ExpansionComponent implements OnInit, OnDestroy { }) { params.event.preventDefault(); params.event.stopPropagation(); + this.profileContextMenuPosition.x = params.event.clientX + 'px'; this.profileContextMenuPosition.y = params.event.clientY + 'px'; this.profileContextMenuTrigger.menu.focusFirstItem('mouse'); @@ -539,6 +631,10 @@ export class ExpansionComponent implements OnInit, OnDestroy { this.profileContextMenuTrigger.openMenu(); } + onFloatingProfileMenu(params: { menuType: string; userInfo: UserInfoF }) { + this.floatingProfileMenu.emit(params); + } + onClickProfileContextMenu( event: MouseEvent, menuType: string, @@ -546,9 +642,6 @@ export class ExpansionComponent implements OnInit, OnDestroy { group: GroupDetailData, rect: any ) { - event.preventDefault(); - event.stopPropagation(); - const clientRect = { width: rect.width, height: rect.height, @@ -568,20 +661,22 @@ export class ExpansionComponent implements OnInit, OnDestroy { group, rect: clientRect }); + + this.profileContextMenuTrigger.closeMenu(); } + getGroupNameBySeq(group: GroupDetailData): string { + if (!!group && group.seq === 0) { + return this.i18nService.t('group:category.default'); + } else if (!!group && group.seq === -9999) { + return this.i18nService.t('group:category.favorite'); + } + + return ''; + } onProfileMenuClose(event: MouseEvent) {} getShowProfileContextMenu(menuType: string, group: GroupDetailData) { - return true; - if (!!this.isSearchData) { - if (menuType === 'VIEW_PROFILE' || menuType === 'SEND_MESSAGE') { - return true; - } else { - return false; - } - } - if (!group || group === undefined) { if ( menuType === 'REGISTER_FAVORITE' || @@ -593,7 +688,6 @@ export class ExpansionComponent implements OnInit, OnDestroy { return false; } } - /** 수정불가 그룹 핸들링. */ if (!!group && !!environment.productConfig.group.fixedGroupSeqs) { const fixedGroupSeqs: number[] = @@ -609,4 +703,19 @@ export class ExpansionComponent implements OnInit, OnDestroy { return true; } + + private _groupOpenBySessionInfo() { + if ( + !!this.loginSession && + !!this.groupBuddies && + !!this.groupExpansion && + this.loginSession.groupInfo.groupSeqs.length > 0 && + this.groupBuddies.length > 0 + ) { + this.groupExpansion.expand( + this.loginSession.groupInfo.groupSeqs, + this.loginSession.groupInfo.lastGroupSeq + ); + } + } } diff --git a/src/app/ucap/group/components/expansion-02.component.html b/src/app/ucap/group/components/expansion-02.component.html new file mode 100644 index 0000000..6e7601a --- /dev/null +++ b/src/app/ucap/group/components/expansion-02.component.html @@ -0,0 +1,55 @@ +
    + + + + + + + + + {{ 'group:category.favorite' | ucapI18n }} + + {{ getNodeGroupStatus(node) }} + + + + + {{ node.groupDetail.name }} + + {{ getNodeGroupStatus(node) }} + + + + + + + {{ 'group:category.default' | ucapI18n }} + + {{ getNodeGroupStatus(node) }} + + + +
    diff --git a/src/app/ucap/group/components/expansion-02.component.scss b/src/app/ucap/group/components/expansion-02.component.scss new file mode 100644 index 0000000..7943109 --- /dev/null +++ b/src/app/ucap/group/components/expansion-02.component.scss @@ -0,0 +1,58 @@ +@charset 'UTF-8'; + +@import '~@ucap/lg-scss/mixins'; + +.expansion-02-container { + width: 100%; + height: 100%; + .ucap-clickable { + display: flex; + min-height: 50px; + border-top: 10px solid #f1f2f6; + border-bottom: 1px solid $gray-rec; + li { + width: 100%; + .path { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + .group-info { + width: calc(100% - 100px); + justify-self: self-start; + flex-grow: 1; + font-size: 13px; + } + } + } + } + .currentGroup { + background-color: red; + } + .header-buddy, + .header-favorite, + .header-default { + display: flex; + flex-grow: 1; + flex-direction: row; + + .group-name { + display: block; + overflow: hidden; + text-overflow: ellipsis; + -webkit-box-orient: vertical; + white-space: nowrap; + margin-right: 4px; + } + .group-member-amount { + flex: 0 0 auto; + color: #e42f66; + font-weight: 600; + } + } +} + +// +.manu-title { + @include menu-title(); +} diff --git a/src/app/ucap/group/components/expansion-02.component.spec.ts b/src/app/ucap/group/components/expansion-02.component.spec.ts new file mode 100644 index 0000000..da45376 --- /dev/null +++ b/src/app/ucap/group/components/expansion-02.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Expansion02Component } from './expansion-02.component'; + +describe('app::ucap::group::Expansion02Component', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [Expansion02Component] + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(Expansion02Component); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'ucap-lg-web'`, () => { + const fixture = TestBed.createComponent(Expansion02Component); + const app = fixture.componentInstance; + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(Expansion02Component); + fixture.detectChanges(); + const compiled = fixture.nativeElement; + expect(compiled.querySelector('.content span').textContent).toContain( + 'ucap-lg-web app is running!' + ); + }); +}); diff --git a/src/app/ucap/group/components/expansion-02.component.ts b/src/app/ucap/group/components/expansion-02.component.ts new file mode 100644 index 0000000..b28bb9d --- /dev/null +++ b/src/app/ucap/group/components/expansion-02.component.ts @@ -0,0 +1,425 @@ +import { Subject, combineLatest, of } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + EventEmitter, + Output, + ViewChild, + ElementRef, + Self +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { PresenceType } from '@ucap/core'; +import { VersionInfo2Response } from '@ucap/api-public'; +import { StatusBulkInfo } from '@ucap/protocol-status'; +import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; +import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; +import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; +import { User } from '@ucap/protocol-info'; + +import { LogService } from '@ucap/ng-logger'; +import { I18nService } from '@ucap/ng-i18n'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; +import { BuddySelector, GroupSelector } from '@ucap/ng-store-group'; +import { PresenceSelector, UserSelector } from '@ucap/ng-store-organization'; + +import { + ExpansionComponent as UcapExpansionComponent, + NodeType, + GroupNode +} from '@ucap/ng-ui-group'; +import { PresenceUtil } from '@ucap/ng-ui-organization'; + +import { AppAuthenticationService } from '@app/services/app-authentication.service'; + +import { environment } from '@environments'; + +export type UserInfoTypes = + | UserInfo + | UserInfoSS + | UserInfoF + | UserInfoDN + | RoomUserInfo; + +@Component({ + selector: 'app-group-expansion-02', + templateUrl: './expansion-02.component.html', + styleUrls: ['./expansion-02.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class Expansion02Component implements OnInit, OnDestroy { + @Input() + checkable = true; + + @Input() + isDialog = true; + + @Input() + selectedUserList: UserInfoTypes[]; + + @Input() + fixedUserList: UserInfoTypes[]; + + @Input() + selectedGroupHeader: GroupDetailData; + + @Output() + clicked = new EventEmitter<{ event: MouseEvent; userInfo: UserInfoTypes }>(); + + @Output() + toggleCheckUser: EventEmitter< + { + checked: boolean; + userInfo: UserInfoSS; + }[] + > = new EventEmitter(); + + @Output() + toggleCheckGroup = new EventEmitter<{ + isChecked: boolean; + groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; + }>(); + + @Output() + floatingProfileMenu = new EventEmitter<{ + menuType: string; + userInfo: UserInfoF; + }>(); + + @ViewChild('groupExpansion', { static: false }) + groupExpansion: UcapExpansionComponent; + + versionInfo2Res: VersionInfo2Response; + user: User; + + statusBulkInfos: StatusBulkInfo[]; + currentSelectUser: UserInfo; + + displayOrder: NodeType[] = [ + NodeType.Profile, + NodeType.Favorite, + NodeType.Buddy, + NodeType.Default + ]; + + profile: UserInfo; + favorites: UserInfo[]; + groupBuddies: { group: GroupDetailData; buddyList: UserInfo[] }[]; + onlineBuddies: { group: GroupDetailData; buddyList: UserInfo[] }[]; + onOffBuddies: { group: GroupDetailData; buddyList: UserInfo[] }[]; + + editablGroup: GroupDetailData = null; + + String = String; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private appAuthenticationService: AppAuthenticationService, + private store: Store, + private changeDetectorRef: ChangeDetectorRef, + private logService: LogService, + private i18nService: I18nService, + @Self() private elementRef: ElementRef + ) {} + + ngOnInit(): void { + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; + }); + + combineLatest([ + this.store.pipe(select(BuddySelector.buddies)), + this.store.pipe(select(GroupSelector.groups)), + this.store.pipe(select(PresenceSelector.selectAllStatusBulkInfo)) + ]) + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe(([buddies, groups, bulkInfos]) => { + buddies = buddies || []; + groups = groups || []; + bulkInfos = bulkInfos || []; + + const favorites = buddies + .filter((buddy) => buddy.isFavorit) + .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); + + if (!!favorites && 0 < favorites.length) { + this.favorites = favorites; + this.changeDetectorRef.markForCheck(); + } else if (!!favorites && favorites.length === 0) { + this.favorites = undefined; + this.changeDetectorRef.markForCheck(); + } + const tempOrder: GroupDetailData[] = []; + let defaultGroup: GroupDetailData; + const buddyGroup: GroupDetailData[] = []; + groups.forEach((group) => { + if (0 === group.seq) { + defaultGroup = group; + } else { + buddyGroup.push(group); + } + }); + + tempOrder.push( + ...buddyGroup.sort((a, b) => + a.name < b.name ? -1 : a.name > b.name ? 1 : 0 + ) + ); + + if (!!defaultGroup) { + tempOrder.push(defaultGroup); + } + + groups = tempOrder; + + if (!!groups && 0 < groups.length) { + this.groupBuddies = []; + + for (const group of groups) { + this.groupBuddies.push({ + group, + buddyList: buddies.filter((buddy) => { + return -1 < group.userSeqs.indexOf(String(buddy.seq)); + }) + }); + } + + this.changeDetectorRef.markForCheck(); + } + }); + + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(PresenceSelector.selectAllStatusBulkInfo) + ) + .subscribe((statusArr) => { + this.statusBulkInfos = statusArr; + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + psUpdate() { + if (!!this.groupExpansion) { + this.groupExpansion.psDirectiveRef.update(); + } + } + + onClickUser(event: MouseEvent, userInfo: UserInfo) { + this.currentSelectUser = userInfo; + this.clicked.emit({ event, userInfo }); + } + + getNodeGroupStatus(groupNode: GroupNode): string { + if ( + !!this.statusBulkInfos && + this.statusBulkInfos.length > 0 && + !!groupNode && + !!groupNode.groupDetail && + !!groupNode.groupDetail.userSeqs && + groupNode.groupDetail.userSeqs.length > 0 + ) { + let onlineCount = 0; + this.statusBulkInfos.map((statusInfo) => { + groupNode.groupDetail.userSeqs.filter((seq) => { + if (Number(seq) === Number(statusInfo.userSeq)) { + const pcOnline = PresenceUtil.isOnline(statusInfo, PresenceType.PC); + const mobileOnline = PresenceUtil.isOnline( + statusInfo, + PresenceType.MOBILE + ); + + if (pcOnline) { + onlineCount++; + } + } + }); + }); + + return `(${onlineCount}/${groupNode.children.length})`; + } else { + return `(0/0)`; + } + } + + onExpandMore() { + this.groupExpansion.expandMore(); + } + onExpandLess() { + this.groupExpansion.expandLess(); + } + + getShowGroupContextMenu(menuType: string, group: GroupDetailData) { + // 즐겨찾기 그룹 숨김메뉴 + if ( + menuType === 'DIV1' || + menuType === 'RENAME' || + menuType === 'MANAGE_MEMBER' || + menuType === 'DELETE' + ) { + if ( + !group || + group === undefined || + (group.seq < 0 && group.name === 'Favorite') + ) { + return false; + } + + /** 수정불가 그룹 핸들링. */ + if (!!group && !!environment.productConfig.group.fixedGroupSeqs) { + const fixedGroupSeqs: number[] = + environment.productConfig.group.fixedGroupSeqs; + if (!!fixedGroupSeqs && fixedGroupSeqs.length > 0) { + if (fixedGroupSeqs.indexOf(group.seq) > -1) { + return false; + } + } + } + } + + // 기본 그룹 숨김메뉴 + if (menuType === 'RENAME' || menuType === 'DELETE') { + if (!!group && group.seq === 0) { + return false; + } + } + + // 그룹원 0명인 그룹 메뉴 정리 + if ( + menuType === 'CHAT' || + menuType === 'SEND_MESSAGE' || + menuType === 'DIV1' + ) { + if (!!group && !!group.userSeqs && group.userSeqs.length > 0) { + return true; + } else { + return false; + } + } + + return true; + } + + /** 개별선택(토글) 이벤트 */ + onChangeCheckUser(param: { checked: boolean; userInfo: UserInfoSS }) { + this.toggleCheckUser.emit([param]); + } + + /** 개별 체크가능 여부 */ + getCheckableUser(userInfo: UserInfoTypes) { + if ( + !!this.checkable && + !!this.fixedUserList && + this.fixedUserList.length > 0 + ) { + return !this.fixedUserList.some( + (fix) => fix.seq + '' === userInfo.seq + '' + ); + } else { + return this.checkable; + } + } + /** 개별 체크여부 */ + getCheckedUser(userInfo: UserInfoTypes) { + if (!!this.selectedUserList && this.selectedUserList.length > 0) { + return ( + this.selectedUserList.filter( + (item) => String(item.seq) === String(userInfo.seq) + ).length > 0 + ); + } + return false; + } + + onToggleCheckGroup(params: { + isChecked: boolean; + groupBuddyList: { group: GroupDetailData; buddyList: UserInfo[] }; + }) { + let trgtUserList = params.groupBuddyList.buddyList; + + // fixedUserList filtering. + if (!!this.fixedUserList && this.fixedUserList.length > 0) { + trgtUserList = trgtUserList.filter( + (item) => !this.fixedUserList.some((fixed) => fixed.seq === item.seq) + ); + } + + if (!!trgtUserList && trgtUserList.length > 0) { + this.toggleCheckGroup.emit({ + isChecked: params.isChecked, + groupBuddyList: { + group: params.groupBuddyList.group, + buddyList: trgtUserList + } + }); + } + } + + onFloatingProfileMenu(params: { menuType: string; userInfo: UserInfoF }) { + this.floatingProfileMenu.emit(params); + } + + getGroupNameBySeq(group: GroupDetailData): string { + if (!!group && group.seq === 0) { + return this.i18nService.t('group:category.default'); + } else if (!!group && group.seq === -9999) { + return this.i18nService.t('group:category.favorite'); + } + + return ''; + } + + getShowProfileContextMenu(menuType: string, group: GroupDetailData) { + if (!group || group === undefined) { + if ( + menuType === 'REGISTER_FAVORITE' || + menuType === 'SEND_MESSAGE' || + menuType === 'REGISTER_NICKNAME' + ) { + // continue; + } else { + return false; + } + } + /** 수정불가 그룹 핸들링. */ + if (!!group && !!environment.productConfig.group.fixedGroupSeqs) { + const fixedGroupSeqs: number[] = + environment.productConfig.group.fixedGroupSeqs; + if (!!fixedGroupSeqs && fixedGroupSeqs.length > 0) { + if (fixedGroupSeqs.indexOf(group.seq) > -1) { + if (menuType === 'REMOVE_FROM_GROUP' || menuType === 'MOVE_BUDDY') { + return false; + } + } + } + } + + return true; + } +} diff --git a/src/app/ucap/group/components/expansion.component.scss b/src/app/ucap/group/components/expansion.component.scss deleted file mode 100644 index 72eaf11..0000000 --- a/src/app/ucap/group/components/expansion.component.scss +++ /dev/null @@ -1,27 +0,0 @@ -@charset 'UTF-8'; - -@import '~@ucap/lg-scss/mixins'; - -.expansion-container { - width: 100%; - height: 100%; - .ucap-clickable { - display: flex; - min-height: 60px; - border-top: 10px solid #f1f2f6; - border-bottom: 1px solid $gray-rec; - li { - width: 100%; - .path { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - .group-info { - justify-self: self-start; - flex-grow: 1; - } - } - } - } -} diff --git a/src/app/ucap/group/components/expansion.strategy.ts b/src/app/ucap/group/components/expansion.strategy.ts deleted file mode 100644 index cb565a2..0000000 --- a/src/app/ucap/group/components/expansion.strategy.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Observable, Subject } from 'rxjs'; - -import { - VirtualScrollStrategy, - CdkVirtualScrollViewport -} from '@angular/cdk/scrolling'; -import { distinctUntilChanged } from 'rxjs/operators'; - -export class GroupVirtualScrollStrategy implements VirtualScrollStrategy { - scrolledIndexChange: Observable; - - private indexSubject = new Subject(); - private viewport: CdkVirtualScrollViewport | null = null; - - constructor() { - this.scrolledIndexChange = this.indexSubject.pipe(distinctUntilChanged()); - } - - attach(viewport: CdkVirtualScrollViewport): void { - this.viewport = viewport; - } - detach(): void { - this.indexSubject.complete(); - this.viewport = null; - } - onContentScrolled(): void {} - onDataLengthChanged(): void {} - onContentRendered(): void {} - onRenderedOffsetChanged(): void {} - scrollToIndex(index: number, behavior: ScrollBehavior): void {} -} diff --git a/src/app/ucap/group/components/index.ts b/src/app/ucap/group/components/index.ts index 6bb4c0d..1d9f49e 100644 --- a/src/app/ucap/group/components/index.ts +++ b/src/app/ucap/group/components/index.ts @@ -1,10 +1,20 @@ -import { ExpansionComponent } from './expansion.component'; +import { Expansion01Component } from './expansion-01.component'; +import { Expansion02Component } from './expansion-02.component'; import { ProfileListItem02Component } from './profile-list-item-02.component'; +import { ProfileListItem03Component } from './profile-list-item-03.component'; import { ProfileListComponent } from './profile-list.component'; +import { ProfileList01Component } from './profile-list-01.component'; +import { NameInputComponent } from './name-input.component'; +import { ProfileImage01Component } from './profile-image-01.component'; export const COMPONENTS = [ - ExpansionComponent, + Expansion01Component, + Expansion02Component, ProfileListItem02Component, - ProfileListComponent + ProfileListItem03Component, + ProfileListComponent, + ProfileList01Component, + NameInputComponent, + ProfileImage01Component ]; diff --git a/src/app/ucap/group/components/name-input.component.html b/src/app/ucap/group/components/name-input.component.html new file mode 100644 index 0000000..9210739 --- /dev/null +++ b/src/app/ucap/group/components/name-input.component.html @@ -0,0 +1,43 @@ +
    +
    +
    + + {{ 'group:dialog.newGroupName' | ucapI18n }} + + {{ input.value?.length || 0 }}/20 + + + + {{ + 'group:error.bannedWords' + | ucapI18n: { bannedWords: appService.bannedGroupNames.join(',') } + }} + + + {{ 'group:error.sameNameExist' | ucapI18n }} + + +
    +
    diff --git a/src/app/ucap/group/components/name-input.component.scss b/src/app/ucap/group/components/name-input.component.scss new file mode 100644 index 0000000..921e279 --- /dev/null +++ b/src/app/ucap/group/components/name-input.component.scss @@ -0,0 +1,26 @@ +@import '~@ucap/lg-scss/mixins'; +.name-input-container { + width: 100%; + height: 100%; + display: flex; + flex-flow: column; + justify-content: center; + .img-group-add { + margin: 10px 0; + background-image: url(../../../../assets/images/bg/bg_group_add.png); + background-repeat: no-repeat; + background-position: 50% 50%; + width: 100%; + height: 160px; + margin-bottom: 40px; + } + .name-input-form { + width: 100%; + display: flex; + justify-content: center; + .mat-form-field { + width: 70%; + height:60px; + } + } +} diff --git a/src/app/ucap/group/components/expansion.component.spec.ts b/src/app/ucap/group/components/name-input.component.spec.ts similarity index 67% rename from src/app/ucap/group/components/expansion.component.spec.ts rename to src/app/ucap/group/components/name-input.component.spec.ts index 0f0057a..9c03891 100644 --- a/src/app/ucap/group/components/expansion.component.spec.ts +++ b/src/app/ucap/group/components/name-input.component.spec.ts @@ -1,28 +1,28 @@ import { TestBed, async } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { ExpansionComponent } from './expansion.component'; +import { NameInputComponent } from './name-input.component'; -describe('app::ucap::group::ExpansionComponent', () => { +describe('app::ucap::group::NameInputComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule], - declarations: [ExpansionComponent] + declarations: [NameInputComponent] }).compileComponents(); })); it('should create the app', () => { - const fixture = TestBed.createComponent(ExpansionComponent); + const fixture = TestBed.createComponent(NameInputComponent); const app = fixture.componentInstance; expect(app).toBeTruthy(); }); it(`should have as title 'ucap-lg-web'`, () => { - const fixture = TestBed.createComponent(ExpansionComponent); + const fixture = TestBed.createComponent(NameInputComponent); const app = fixture.componentInstance; }); it('should render title', () => { - const fixture = TestBed.createComponent(ExpansionComponent); + const fixture = TestBed.createComponent(NameInputComponent); fixture.detectChanges(); const compiled = fixture.nativeElement; expect(compiled.querySelector('.content span').textContent).toContain( diff --git a/src/app/ucap/group/components/name-input.component.ts b/src/app/ucap/group/components/name-input.component.ts new file mode 100644 index 0000000..47bb695 --- /dev/null +++ b/src/app/ucap/group/components/name-input.component.ts @@ -0,0 +1,127 @@ +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + Output, + EventEmitter +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { LogService } from '@ucap/ng-logger'; +import { Subject } from 'rxjs'; +import { + FormBuilder, + FormGroup, + Validators, + ValidatorFn, + AbstractControl +} from '@angular/forms'; +import { I18nService } from '@ucap/ng-i18n'; +import { GroupDetailData } from '@ucap/protocol-sync'; +import { StringUtil } from '@ucap/ng-core'; +import { takeUntil } from 'rxjs/operators'; +import { GroupSelector } from '@ucap/ng-store-group'; +import { AppService } from '@app/services/app.service'; + +@Component({ + selector: 'app-group-name-input', + templateUrl: './name-input.component.html', + styleUrls: ['./name-input.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class NameInputComponent implements OnInit, OnDestroy { + @Output() + chagneName: EventEmitter<{ + invalid: boolean; + groupName: string; + }> = new EventEmitter(); + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private store: Store, + private formBuilder: FormBuilder, + private changeDetectorRef: ChangeDetectorRef, + private i18nService: I18nService, + private logService: LogService, + public appService: AppService + ) {} + + inputForm: FormGroup; + groupList: GroupDetailData[]; + + ngOnInit(): void { + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(GroupSelector.groups)) + .subscribe((groups) => { + this.groupList = groups; + }); + + this.inputForm = this.formBuilder.group({ + groupName: [ + '', + [ + Validators.required, + StringUtil.checkSpecialCharacter(), + this.checkBanWords(), + this.checkSameName() + ] + ] + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onChange() { + const invalid = this.inputForm.invalid; + + const groupName = this.inputForm.get('groupName').value; + this.chagneName.emit({ invalid, groupName }); + } + + onKeyupName(event: KeyboardEvent) { + this.inputForm.get('groupName').markAsTouched(); + } + + checkBanWords(): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + if (!control || !control.value) { + return null; + } + + const ban = this.appService.bannedGroupNames.filter( + (name) => + name.toLowerCase() === (control.value as string).trim().toLowerCase() + )[0]; + + return !!ban && ban !== '' + ? { groupNameBanned: { value: control.value } } + : null; + }; + } + + checkSameName(): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + if ( + !control || + !control.value || + !this.groupList || + 0 === this.groupList.length + ) { + return null; + } + const v = (control.value as string).trim(); + const ban = -1 < this.groupList.findIndex((g) => g.name === v); + return ban ? { groupNameSamed: { value: control.value } } : null; + }; + } +} diff --git a/src/app/ucap/group/components/profile-image-01.component.html b/src/app/ucap/group/components/profile-image-01.component.html new file mode 100644 index 0000000..8020a40 --- /dev/null +++ b/src/app/ucap/group/components/profile-image-01.component.html @@ -0,0 +1,9 @@ +
    + + +
    diff --git a/src/app/ucap/group/components/profile-image-01.component.scss b/src/app/ucap/group/components/profile-image-01.component.scss new file mode 100644 index 0000000..198d940 --- /dev/null +++ b/src/app/ucap/group/components/profile-image-01.component.scss @@ -0,0 +1,6 @@ +@import '~@ucap/lg-scss/mixins'; + +.profile-image-container { + width: 100%; + height: 100%; +} diff --git a/src/app/ucap/group/components/profile-image-01.component.spec.ts b/src/app/ucap/group/components/profile-image-01.component.spec.ts new file mode 100644 index 0000000..f96305b --- /dev/null +++ b/src/app/ucap/group/components/profile-image-01.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { ProfileImage01Component } from './profile-image-01.component'; + +describe('app::ucap::group::ProfileImage01Component', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [ProfileImage01Component] + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(ProfileImage01Component); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'ucap-lg-web'`, () => { + const fixture = TestBed.createComponent(ProfileImage01Component); + const app = fixture.componentInstance; + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(ProfileImage01Component); + fixture.detectChanges(); + const compiled = fixture.nativeElement; + expect(compiled.querySelector('.content span').textContent).toContain( + 'ucap-lg-web app is running!' + ); + }); +}); diff --git a/src/app/ucap/group/components/profile-image-01.component.ts b/src/app/ucap/group/components/profile-image-01.component.ts new file mode 100644 index 0000000..015b09c --- /dev/null +++ b/src/app/ucap/group/components/profile-image-01.component.ts @@ -0,0 +1,72 @@ +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { VersionInfo2Response } from '@ucap/api-public'; +import { UserInfoSS } from '@ucap/protocol-query'; + +import { LogService } from '@ucap/ng-logger'; +import { PresenceSelector } from '@ucap/ng-store-organization'; +import { StatusBulkInfo } from '@ucap/protocol-status'; + +@Component({ + selector: 'app-group-profile-image-01', + templateUrl: './profile-image-01.component.html', + styleUrls: ['./profile-image-01.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ProfileImage01Component implements OnInit, OnDestroy { + @Input() + set userInfo(userInfo: UserInfoSS) { + this._userInfo = userInfo; + } + get userInfo(): UserInfoSS { + return this._userInfo; + } + // tslint:disable-next-line: variable-name + _userInfo: UserInfoSS; + + @Input() + versionInfo: VersionInfo2Response; + + presenceInfo: StatusBulkInfo; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private store: Store, + private changeDetectorRef: ChangeDetectorRef, + private logService: LogService + ) {} + + ngOnInit(): void { + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select( + PresenceSelector.selectStatusBulkInfo, + Number(this.userInfo?.seq) + ) + ) + .subscribe((status) => { + this.presenceInfo = status as any; + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } +} diff --git a/src/app/ucap/group/components/profile-list-01.component.html b/src/app/ucap/group/components/profile-list-01.component.html new file mode 100644 index 0000000..1dbc048 --- /dev/null +++ b/src/app/ucap/group/components/profile-list-01.component.html @@ -0,0 +1,138 @@ +
    +
    + +
    + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NO + + + +
    +
    +
    diff --git a/src/app/ucap/group/components/profile-list-01.component.scss b/src/app/ucap/group/components/profile-list-01.component.scss new file mode 100644 index 0000000..89e12ee --- /dev/null +++ b/src/app/ucap/group/components/profile-list-01.component.scss @@ -0,0 +1,39 @@ +.profile-list-01-item-container { + .empty { + widows: 100%; + height: 100%; + background-color: #f1f2f6; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + svg { + .empty-fix-1, + .empty-fix-3 { + stroke: #bababa; + stroke-width: 3px; + } + + .empty-fix-1 { + fill: #fff; + } + + .empty-fix-3 { + stroke-linecap: round; + } + + .empty-fix-3, + .empty-fix-5 { + fill: none; + } + + .empty-fix-4 { + fill: #bababa; + } + + .empty-fix-7 { + stroke: none; + } + } + } +} diff --git a/src/app/sections/group/components/search.section.component.spec.ts b/src/app/ucap/group/components/profile-list-01.component.spec.ts similarity index 66% rename from src/app/sections/group/components/search.section.component.spec.ts rename to src/app/ucap/group/components/profile-list-01.component.spec.ts index e45a1ca..6b05c9a 100644 --- a/src/app/sections/group/components/search.section.component.spec.ts +++ b/src/app/ucap/group/components/profile-list-01.component.spec.ts @@ -1,28 +1,28 @@ import { TestBed, async } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { SearchSectionComponent } from './search.section.component'; +import { ProfileList01Component } from './profile-list-01.component'; -describe('app::sections::group::SearchSectionComponent', () => { +describe('app::ucap::organization::ProfileList01Component', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule], - declarations: [SearchSectionComponent] + declarations: [ProfileList01Component] }).compileComponents(); })); it('should create the app', () => { - const fixture = TestBed.createComponent(SearchSectionComponent); + const fixture = TestBed.createComponent(ProfileList01Component); const app = fixture.componentInstance; expect(app).toBeTruthy(); }); it(`should have as title 'ucap-lg-web'`, () => { - const fixture = TestBed.createComponent(SearchSectionComponent); + const fixture = TestBed.createComponent(ProfileList01Component); const app = fixture.componentInstance; }); it('should render title', () => { - const fixture = TestBed.createComponent(SearchSectionComponent); + const fixture = TestBed.createComponent(ProfileList01Component); fixture.detectChanges(); const compiled = fixture.nativeElement; expect(compiled.querySelector('.content span').textContent).toContain( diff --git a/src/app/ucap/group/components/profile-list-01.component.ts b/src/app/ucap/group/components/profile-list-01.component.ts new file mode 100644 index 0000000..f9b8ce7 --- /dev/null +++ b/src/app/ucap/group/components/profile-list-01.component.ts @@ -0,0 +1,138 @@ +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + Output, + EventEmitter +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { VersionInfo2Response } from '@ucap/api-public'; +import { UserInfoF } from '@ucap/protocol-query'; + +import { LogService } from '@ucap/ng-logger'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; + +interface CheckedInfo { + checked: boolean; + userInfo: UserInfoF; +} +@Component({ + selector: 'app-group-profile-list-01', + templateUrl: './profile-list-01.component.html', + styleUrls: ['./profile-list-01.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ProfileList01Component implements OnInit, OnDestroy { + @Input() + checkable = false; + + @Input() + isDialog = false; + + @Input() + set userInfos(userInfos: UserInfoF[]) { + this._userInfos = userInfos; + } + get userInfos(): UserInfoF[] { + return this._userInfos; + } + // tslint:disable-next-line: variable-name + _userInfos: UserInfoF[]; + + @Input() selectedUser: UserInfoF[]; + + @Output() + clicked: EventEmitter<{ userInfo: UserInfoF }> = new EventEmitter(); + + @Output() + toggleCheck: EventEmitter = new EventEmitter(); + + versionInfo2Res: VersionInfo2Response; + + processing = false; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private store: Store, + private changeDetectorRef: ChangeDetectorRef, + private logService: LogService + ) {} + + ngOnInit(): void { + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + /** 전체 체크여부 */ + checkAll() { + if (!this.userInfos || 0 === this.userInfos.length) { + return; + } + + const checked: CheckedInfo[] = []; + this.userInfos.forEach((u) => { + checked.push({ + checked: true, + userInfo: u + }); + }); + this.toggleCheck.emit(checked); + } + + uncheckAll() { + if (!this.userInfos || 0 === this.userInfos.length) { + return; + } + + const unchecked: CheckedInfo[] = []; + this.userInfos.forEach((u) => { + unchecked.push({ + checked: false, + userInfo: u + }); + }); + this.toggleCheck.emit(unchecked); + } + + /** 개별 체크여부 */ + getCheckedUser(userInfo: UserInfoF) { + if (!!this.selectedUser && this.selectedUser.length > 0) { + return ( + this.selectedUser.filter((item) => item.seq === userInfo.seq).length > 0 + ); + } + return false; + } + + /** 개별선택(토글) 이벤트 */ + onChangeCheckUser(param: { checked: boolean; userInfo: UserInfoF }) { + this.toggleCheck.emit([param]); + this.changeDetectorRef.detectChanges(); + } + + onClickUser(userInfo: UserInfoF): void { + this.clicked.emit({ userInfo }); + } +} diff --git a/src/app/ucap/group/components/profile-list-item-02.component.html b/src/app/ucap/group/components/profile-list-item-02.component.html index 7d4e7a4..24ae6e0 100644 --- a/src/app/ucap/group/components/profile-list-item-02.component.html +++ b/src/app/ucap/group/components/profile-list-item-02.component.html @@ -7,33 +7,40 @@ [userInfo]="userInfo" [checkable]="checkable" [checked]="checked" - (openProfile)="onOpenProfile($event)" (changeCheck)="onChangeCheckUser($event)" + class="ucap-organization-profile-list-user" > -
    -
    - {{ userInfo.nickName }} - {{ userInfo.intro }} +
    +
    +

    {{ _userStatusMessage() }}

    +
    - + + + +
    + +
    diff --git a/src/app/ucap/group/components/profile-list-item-03.component.scss b/src/app/ucap/group/components/profile-list-item-03.component.scss new file mode 100644 index 0000000..7645a66 --- /dev/null +++ b/src/app/ucap/group/components/profile-list-item-03.component.scss @@ -0,0 +1,42 @@ +@import '~@ucap/ng-ui-material/material'; + +/// var +/// --ucap-organization-profile-list-item-01-size: 70px +.profile-list-container { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + //padding: 0 16px; + height: 60px; + align-items: center; + position: relative; + border-bottom: 1px solid #ccc; + overflow: hidden; + &:hover { + transition: all 0.2s; + background-color: #f1f2f6; + } + .chat-member-list-item { + width: 100%; + } + .icon-me { + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: auto; + margin-right: 4px; + border: 1px solid #fd578a; + border-radius: 14px; + width: 40px; + height: 20px; + color: #fd578a; + } +} + +.divider { + margin: 0 4px 0 5px; + width: 1px; + height: 15px; + display: inline-block; + background-color: #ccc; +} diff --git a/src/app/ucap/group/components/profile-list-item.component.spec.ts b/src/app/ucap/group/components/profile-list-item-03.component.spec.ts similarity index 100% rename from src/app/ucap/group/components/profile-list-item.component.spec.ts rename to src/app/ucap/group/components/profile-list-item-03.component.spec.ts diff --git a/src/app/ucap/group/components/profile-list-item-03.component.ts b/src/app/ucap/group/components/profile-list-item-03.component.ts new file mode 100644 index 0000000..797cc8f --- /dev/null +++ b/src/app/ucap/group/components/profile-list-item-03.component.ts @@ -0,0 +1,172 @@ +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + EventEmitter, + Output +} from '@angular/core'; + +import { select, Store } from '@ngrx/store'; + +import { VersionInfo2Response } from '@ucap/api-public'; +import { GroupDetailData } from '@ucap/protocol-sync'; +import { UserInfoSS } from '@ucap/protocol-query'; +import { + UserInfo as RoomUserInfo, + UserInfoShort as RoomUserInfoShort +} from '@ucap/protocol-room'; +import { StatusBulkInfo } from '@ucap/protocol-status'; +import { User } from '@ucap/protocol-info'; + +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; +import { PresenceSelector, UserSelector } from '@ucap/ng-store-organization'; + +import { ucapAnimations } from '@ucap/ng-ui'; + +export type UserInfoTypes = RoomUserInfo | RoomUserInfoShort; + +/** + * @Useage Chat > Room User List. + */ +@Component({ + selector: 'app-group-profile-list-item-03', + templateUrl: './profile-list-item-03.component.html', + styleUrls: ['./profile-list-item-03.component.scss'], + animations: ucapAnimations, + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ProfileListItem03Component implements OnInit, OnDestroy { + @Input() + userInfo: UserInfoTypes; + + @Input() + group: GroupDetailData; + + @Input() + checkable = false; + + @Input() + isMe = false; + + @Input() + checked = false; + + @Input() + isShow = false; + + @Input() + isDialog = false; + + @Output() + openProfile = new EventEmitter(); + + @Output() + openChatRoom = new EventEmitter(); + + @Output() + sendMessage = new EventEmitter(); + + @Output() + exitForcing = new EventEmitter(); + + user: User; + versionInfo2Res: VersionInfo2Response; + + presenceInfo: StatusBulkInfo; + + tempRect: any; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private changeDetectorRef: ChangeDetectorRef, + private store: Store + ) {} + + ngOnInit(): void { + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; + }); + + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(PresenceSelector.selectStatusBulkInfo, Number(this.userInfo.seq)) + ) + .subscribe((status) => { + this.presenceInfo = status; + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onOpenProfile(userInfo: UserInfoSS): void { + this.openProfile.emit(String(userInfo.seq)); + } + + onMouseover(event: MouseEvent): void { + if (!this.isDialog) { + this.isShow = true; + } + event.preventDefault(); + event.stopPropagation(); + } + onMouseleave(event: MouseEvent): void { + if (!this.isDialog) { + this.isShow = false; + } + + event.preventDefault(); + event.stopPropagation(); + } + + onClickProfileContextMenu(event: MouseEvent, type: string) { + event.preventDefault(); + event.stopPropagation(); + + switch (type) { + case 'PROFILE': + { + this.openProfile.emit(String(this.userInfo.seq)); + } + break; + case 'CHAT': + { + this.openChatRoom.emit(String(this.userInfo.seq)); + } + break; + case 'MESSAGE': + { + this.sendMessage.emit(String(this.userInfo.seq)); + } + break; + case 'EXIT': + { + this.exitForcing.emit(this.userInfo); + } + break; + } + } +} diff --git a/src/app/ucap/group/components/profile-list-item.component.html b/src/app/ucap/group/components/profile-list-item.component.html deleted file mode 100644 index 6ee735d..0000000 --- a/src/app/ucap/group/components/profile-list-item.component.html +++ /dev/null @@ -1,98 +0,0 @@ -
    - -
    - {{ userInfo.intro }} -
    -
    - {{ userInfo.nickName }} -
    - -
    - - - - -
    -
    - -
    -
    diff --git a/src/app/ucap/group/components/profile-list-item.component.scss b/src/app/ucap/group/components/profile-list-item.component.scss deleted file mode 100644 index 1111e59..0000000 --- a/src/app/ucap/group/components/profile-list-item.component.scss +++ /dev/null @@ -1,136 +0,0 @@ -@import '~@ucap/lg-scss/mixins'; - -.user-list { - display: flex; - flex-flow: row nowrap; - justify-content: space-between; - padding: 0 16px; - height: 70px; - align-items: center; - &.line-top { - border-top: 1px solid $gray-rec; - } - .user-profile-info { - display: inline-flex; - flex-direction: row; - flex-grow: 2.3; - .user-profile-thumb { - @include profile-avatar-default( - 0 5px 5px 0, - 8, - $green, - 18px - ); //오른 아래 공간, 모바일 온라인 아이콘 크기, 모바일 아이콘 색, 모바일 아이콘 bg크기 - .presence { - //PC 상태 - @include presence-state(8px); //원크기 - } - .profile-image { - @include avatar-img(36px, 2px); //아바타 크기, 왼쪽공간 - } - } - .user-info { - display: flex; - flex-direction: column; - align-items: flex-start; - justify-content: flex-start; - padding-left: 16px; - .user-n-g { - display: flex; - flex-flow: row-reverse nowrap; - align-items: flex-end; - height: 22px; - .user-name { - @include ellipsis-column(1); - height: 22px; - font: { - size: 16px; - weight: 600; - } - color: $gray-re21; - order: 1; - -ms-flex-order: 1; - } - .user-grade { - @include ellipsis(1); - align-self: stretch; - font: { - size: 13px; - } - color: $gray-re70; - margin-left: 4px; - order: 0; - -ms-flex-order: 0; - } - } - .dept-name { - @include ellipsis(1); - font-size: 12px; - color: $gray-re6; - line-height: 16px; - } - } - } - .intro { - display: inline-flex; - flex-flow: row nowrap; - flex-basis: 35%; - flex-grow: 0; - align-items: baseline; - p { - font-size: 11px; - line-height: 1.4; - @include ellipsis(2); - height: 30px; - } - &:before { - content: 'chat'; - @include font-family-ico($font-ico-default, 12, center, $lipstick); - flex-direction: row; - align-items: flex-start; - width: 12px; - height: 12px; - line-height: 12px; - margin-right: 4.8px; - position: relative; - top: 2px; - } - } - .btn-partner-set { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 25px; - border-top: 1px solid rgba(255, 255, 255, 0.8); - border-bottom: 1px solid rgba(255, 255, 255, 0.8); - height: 20px; - margin-top: 20px; - img { - vertical-align: top; - } - } - .intro-name { - display: inline-flex; - flex-flow: row nowrap; - flex-basis: 35%; - align-items: center; - justify-content: center; - overflow: hidden; - span { - display: inline-block; - text-align: center; - width: 100%; - height: 20px; - line-height: 20px; - color: $gray-re70; - font-size: 11px; - padding: 0 10px; - border-radius: 30px; - border: solid 1px $warm-pink; - background-color: #ffffff; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } -} diff --git a/src/app/ucap/group/components/profile-list-item.component.ts b/src/app/ucap/group/components/profile-list-item.component.ts deleted file mode 100644 index 49027ec..0000000 --- a/src/app/ucap/group/components/profile-list-item.component.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { - Component, - OnInit, - OnDestroy, - ChangeDetectionStrategy, - ChangeDetectorRef, - Input, - EventEmitter, - Output, - ElementRef, - Self -} from '@angular/core'; -import { UserInfo, GroupDetailData } from '@ucap/protocol-sync'; -import { UserInfoSS, UserInfoF, UserInfoDN } from '@ucap/protocol-query'; -import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; -import { StatusBulkInfo, StatusInfo } from '@ucap/protocol-status'; -import { PresenceType, StatusCode } from '@ucap/core'; -import { I18nService } from '@ucap/ng-i18n'; -import { ucapAnimations } from '@ucap/ng-ui'; - -export type UserInfoTypes = - | UserInfo - | UserInfoSS - | UserInfoF - | UserInfoDN - | RoomUserInfo; - -@Component({ - selector: 'app-group-profile-list-item', - templateUrl: './profile-list-item.component.html', - styleUrls: ['./profile-list-item.component.scss'], - animations: ucapAnimations, - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ProfileListItemComponent implements OnInit, OnDestroy { - @Input() - userInfo: UserInfoF; - - @Input() - group: GroupDetailData; - - @Input() - isSearchData = false; - - @Input() - defaultProfileImage: string; - - @Input() - profileImageRoot: string; - - @Input() - set presence(info: StatusBulkInfo | StatusInfo) { - this._presence = info; - } - get presence(): StatusBulkInfo | StatusInfo { - return this._presence; - } - _presence: StatusBulkInfo | StatusInfo; - - @Input() - showMenu = true; - - @Input() - checkable = false; - @Input() - set isChecked(checked: boolean) { - this._isChecked = checked; - } - - get isChecked(): boolean { - return this._isChecked; - } - _isChecked = false; - - @Output() - checked = new EventEmitter<{ - isChecked: boolean; - userInfo: UserInfoTypes; - }>(); - - @Output() - moreMenu: EventEmitter<{ - event: MouseEvent; - userInfo: UserInfoTypes; - group: GroupDetailData; - rect: any; - }> = new EventEmitter(); - - isClicked = false; - isShowMenu = false; - - @Input() - isMe = false; - - PresenceType = PresenceType; - isClickMore = false; - - constructor( - private changeDetectorRef: ChangeDetectorRef, - private i18nService: I18nService, - @Self() private elementRef: ElementRef - ) { - this.i18nService.setDefaultNamespace('organization'); - } - - ngOnInit(): void {} - - ngOnDestroy(): void {} - - onClickProfileImage(event: Event, userInfo: UserInfoTypes): void {} - - onChangeCheck(value: boolean, userInfo: UserInfoTypes) { - this.checked.emit({ - isChecked: value, - userInfo - }); - } - - getPresence(type: PresenceType): string { - let status: string; - let rtnClass = ''; - switch (type) { - case PresenceType.PC: - status = !!this.presence ? this.presence.pcStatus : undefined; - break; - case PresenceType.MOBILE: - status = !!this.presence ? this.presence.mobileStatus : undefined; - break; - } - - switch (status) { - case StatusCode.OnLine: - rtnClass = 'online'; - break; - case StatusCode.Away: - rtnClass = 'absence'; - break; - case StatusCode.Busy: - rtnClass = 'other-business'; - break; - default: - rtnClass = 'offline'; - break; - } - - return rtnClass; - } - - getPresenceMsg(): string { - let presenceMsg = this.i18nService.t('presence.offline'); - - if (!!this.presence) { - switch (this.presence.pcStatus) { - case StatusCode.OnLine: - presenceMsg = this.i18nService.t('presence.online'); - break; - case StatusCode.Away: - presenceMsg = this.i18nService.t('presence.away'); - break; - case StatusCode.Busy: - if ( - !!this.presence.statusMessage && - this.presence.statusMessage !== '.' - ) { - presenceMsg = this.presence.statusMessage; - } else { - presenceMsg = this.i18nService.t('presence.statusMessage1'); - } - break; - } - } - - return presenceMsg; - } - - onClickMore(event: MouseEvent) { - this.isClickMore = true; - const rect = this.elementRef.nativeElement.getBoundingClientRect(); - - this.moreMenu.emit({ - event, - userInfo: this.userInfo, - group: this.group, - rect - }); - } - - onClickProfileContextMenu(event: MouseEvent, type: string) {} - onClickProfile(event: MouseEvent) { - event.preventDefault(); - event.stopPropagation(); - if (this.showMenu && !this.isMe) { - this.isShowMenu = true; - } - } - onMouseover(event: MouseEvent): void { - if (this.showMenu && !this.isMe) { - this.isShowMenu = true; - if (this.isClickMore) { - this.isClickMore = false; - } - } - event.preventDefault(); - event.stopPropagation(); - } - onMouseleave(event: MouseEvent): void { - if (this.showMenu && !this.isMe) { - this.isShowMenu = false; - } - - event.preventDefault(); - event.stopPropagation(); - } -} diff --git a/src/app/ucap/group/components/profile-list.component.html b/src/app/ucap/group/components/profile-list.component.html index af8dd64..00c0080 100644 --- a/src/app/ucap/group/components/profile-list.component.html +++ b/src/app/ucap/group/components/profile-list.component.html @@ -1,21 +1,140 @@ -
    - - - - +
    + +
    + + + +
    -
    -
    -
    + [checkable]="getCheckable(userInfo)" + [isDialog]="isDialog" + [isMoreMenu]="isMoreMenu" + [checked]="getCheckedUser(userInfo)" + (click)="onClickUser(userInfo)" + (floatingProfileMenu)="onFloatingProfileMenu($event)" + (changeCheckUser)="onChangeCheckUser($event)" + > + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NO + + + +
    +
    diff --git a/src/app/ucap/group/components/profile-list.component.scss b/src/app/ucap/group/components/profile-list.component.scss index be45828..51f6c91 100644 --- a/src/app/ucap/group/components/profile-list.component.scss +++ b/src/app/ucap/group/components/profile-list.component.scss @@ -1,4 +1,39 @@ -.profile-list-container { - width: 100%; - height: 100%; +.profile-list-item-container { + .empty { + widows: 100%; + height: 100%; + background-color: #f1f2f6; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + svg { + .empty-fix-1, + .empty-fix-3 { + stroke: #bababa; + stroke-width: 3px; + } + + .empty-fix-1 { + fill: #fff; + } + + .empty-fix-3 { + stroke-linecap: round; + } + + .empty-fix-3, + .empty-fix-5 { + fill: none; + } + + .empty-fix-4 { + fill: #bababa; + } + + .empty-fix-7 { + stroke: none; + } + } + } } diff --git a/src/app/ucap/group/components/profile-list.component.ts b/src/app/ucap/group/components/profile-list.component.ts index 1b26cd3..c116016 100644 --- a/src/app/ucap/group/components/profile-list.component.ts +++ b/src/app/ucap/group/components/profile-list.component.ts @@ -1,5 +1,5 @@ -import { Subject, of } from 'rxjs'; -import { takeUntil, take, map, catchError } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { takeUntil, take } from 'rxjs/operators'; import { Component, @@ -15,48 +15,45 @@ import { import { Store, select } from '@ngrx/store'; import { VersionInfo2Response } from '@ucap/api-public'; -import { LoginResponse } from '@ucap/protocol-authentication'; + import { - AuthResponse, UserInfoSS, + UserInfoF, + UserInfoDN, DeptSearchType, DeptUserRequest } from '@ucap/protocol-query'; +import { UserInfo as RoomUserInfo } from '@ucap/protocol-room'; +import { UserInfo } from '@ucap/protocol-sync'; +import { User } from '@ucap/protocol-info'; import { LogService } from '@ucap/ng-logger'; -import { - LoginSelector, - AuthorizationSelector, - ConfigurationSelector -} from '@ucap/ng-store-authentication'; import { QueryProtocolService } from '@ucap/ng-protocol-query'; import { - DepartmentSelector, - PresenceActions -} from '@ucap/ng-store-organization'; -import { - FixedSizeVirtualScrollStrategy, - VIRTUAL_SCROLL_STRATEGY -} from '@angular/cdk/scrolling'; + LoginSelector, + ConfigurationSelector +} from '@ucap/ng-store-authentication'; +import { PresenceActions, UserSelector } from '@ucap/ng-store-organization'; import { SearchData } from '@app/ucap/organization/models/search-data'; -export class ProfileListVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy { - constructor() { - super(70, 250, 500); // (itemSize, minBufferPx, maxBufferPx) - } -} +const DEPT_ORDER_PROPERTY = 'order'; +export type UserInfoTypes = + | UserInfo + | UserInfoSS + | UserInfoF + | UserInfoDN + | RoomUserInfo; + +interface CheckedInfo { + checked: boolean; + userInfo: UserInfoSS; +} @Component({ selector: 'app-group-profile-list', templateUrl: './profile-list.component.html', styleUrls: ['./profile-list.component.scss'], - providers: [ - { - provide: VIRTUAL_SCROLL_STRATEGY, - useClass: ProfileListVirtualScrollStrategy - } - ], changeDetection: ChangeDetectionStrategy.OnPush }) export class ProfileListComponent implements OnInit, OnDestroy { @@ -66,12 +63,16 @@ export class ProfileListComponent implements OnInit, OnDestroy { @Input() isDialog = false; + @Input() + isMoreMenu = true; + @Input() set searchData(data: SearchData) { - if (!this.loginRes) { + if (!this.user) { this._searchData = data; return; } + this.searchMember(data); } // tslint:disable-next-line: variable-name @@ -79,14 +80,23 @@ export class ProfileListComponent implements OnInit, OnDestroy { @Input() selectedUser: UserInfoSS[]; + @Input() + fixedUserList?: UserInfoTypes[]; + @Output() searched: EventEmitter = new EventEmitter(); @Output() - toggleCheck: EventEmitter<{ - checked: boolean; - userInfo: UserInfoSS; - }> = new EventEmitter(); + clicked: EventEmitter<{ userInfo: UserInfoSS }> = new EventEmitter(); + + @Output() + floatingProfileMenu = new EventEmitter<{ + menuType: string; + userInfo: UserInfoF; + }>(); + + @Output() + toggleCheck: EventEmitter = new EventEmitter(); set userInfos(userInfos: UserInfoSS[]) { this._userInfos = userInfos; @@ -98,12 +108,12 @@ export class ProfileListComponent implements OnInit, OnDestroy { // tslint:disable-next-line: variable-name _userInfos: UserInfoSS[] = []; - loginRes: LoginResponse; + user: User; versionInfo2Res: VersionInfo2Response; processing = false; - private ngOnDestroySubject: Subject; + private ngOnDestroySubject: Subject = new Subject(); constructor( private queryProtocolService: QueryProtocolService, @@ -113,8 +123,6 @@ export class ProfileListComponent implements OnInit, OnDestroy { ) {} ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - this.store .pipe( takeUntil(this.ngOnDestroySubject), @@ -125,9 +133,9 @@ export class ProfileListComponent implements OnInit, OnDestroy { }); this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; if (!!this._searchData) { this.searchMember(this._searchData); this._searchData = undefined; @@ -137,15 +145,59 @@ export class ProfileListComponent implements OnInit, OnDestroy { ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } + /** 전체 체크여부 */ + checkAll() { + if (!this.userInfos || 0 === this.userInfos.length) { + return; + } + + const checked: CheckedInfo[] = []; + this.userInfos.forEach((u) => { + checked.push({ + checked: true, + userInfo: u + }); + }); + this.toggleCheck.emit(checked); + } + + uncheckAll() { + if (!this.userInfos || 0 === this.userInfos.length) { + return; + } + + const unchecked: CheckedInfo[] = []; + this.userInfos.forEach((u) => { + unchecked.push({ + checked: false, + userInfo: u + }); + }); + this.toggleCheck.emit(unchecked); + } + + getCheckable(userInfo: UserInfoSS): boolean { + if (!!this.fixedUserList && this.fixedUserList.length > 0) { + return !this.fixedUserList.some( + (item) => String(item.seq) === String(userInfo.seq) + ); + } + + return this.checkable; + } + /** 개별 체크여부 */ getCheckedUser(userInfo: UserInfoSS) { if (!!this.selectedUser && this.selectedUser.length > 0) { return ( - this.selectedUser.filter((item) => item.seq === userInfo.seq).length > 0 + this.selectedUser.filter( + (item) => String(item.seq) === String(userInfo.seq) + ).length > 0 ); } return false; @@ -153,11 +205,16 @@ export class ProfileListComponent implements OnInit, OnDestroy { /** 개별선택(토글) 이벤트 */ onChangeCheckUser(param: { checked: boolean; userInfo: UserInfoSS }) { - this.toggleCheck.emit(param); + this.toggleCheck.emit([param]); + this.changeDetectorRef.detectChanges(); } - onOpenProfile(userInfo: UserInfoSS): void { - alert('Open Profile'); + onFloatingProfileMenu(params: { menuType: string; userInfo: UserInfoF }) { + this.floatingProfileMenu.emit(params); + } + + onClickUser(userInfo: UserInfoSS): void { + this.clicked.emit({ userInfo }); } private searchMember(searchData: SearchData) { @@ -165,43 +222,85 @@ export class ProfileListComponent implements OnInit, OnDestroy { return; } + this.processing = true; + this.changeDetectorRef.markForCheck(); + const req = { divCd: 'ORGS', companyCode: searchData.companyCode, searchRange: DeptSearchType.All, search: searchData.searchWord, - senderCompanyCode: this.loginRes.userInfo.companyCode, - senderEmployeeType: this.loginRes.userInfo.employeeType + senderCompanyCode: this.user.info.companyCode, + senderEmployeeType: this.user.info.employeeType } as DeptUserRequest; - this.processing = true; this.queryProtocolService .deptUser(req) .pipe(take(1)) .subscribe( (data) => { - this.userInfos = data.userInfos.sort((a, b) => - a.name < b.name ? -1 : a.name > b.name ? 1 : 0 - ); - this.changeDetectorRef.detectChanges(); - - // 검색 결과에 따른 프레즌스 조회. - const userSeqList: string[] = []; - this.userInfos.map((user) => userSeqList.push(user.seq)); - - if (userSeqList.length > 0) { - this.store.dispatch( - PresenceActions.bulkInfo({ - divCd: 'orgSrch', - userSeqs: userSeqList - }) - ); - } + this._refreshUserInfos(data.userInfos); }, (error) => {}, () => { this.processing = false; + this.changeDetectorRef.markForCheck(); } ); } + + private sort(userInfos: UserInfoSS[]): UserInfoSS[] { + if (!userInfos || 0 === userInfos.length) { + return userInfos; + } + + const property = 'name'; + const ascending = undefined; + let deptA: any; + let deptB: any; + let c: any; + let d: any; + + return userInfos.slice().sort((a, b) => { + try { + deptA = a[DEPT_ORDER_PROPERTY]; + deptB = b[DEPT_ORDER_PROPERTY]; + + if (undefined === ascending) { + return deptA < deptB + ? -1 + : deptA > deptB + ? 1 + : a[property] < b[property] + ? -1 + : a[property] > b[property] + ? 1 + : 0; + } + + c = ascending ? a[property] : b[property]; + d = ascending ? b[property] : a[property]; + + return c < d ? -1 : c > d ? 1 : 0; + } catch (error) { + console.log(error); + } + }); + } + + private _refreshUserInfos(userInfos: UserInfoSS[]) { + this.userInfos = this.sort(userInfos); + + // 검색 결과에 따른 프레즌스 조회. + const userSeqList: string[] = userInfos.map((user) => user.seq); + + if (userSeqList.length > 0) { + this.store.dispatch( + PresenceActions.bulkInfo({ + divCd: 'orgSrch', + userSeqs: userSeqList + }) + ); + } + } } diff --git a/src/app/ucap/group/group.module.ts b/src/app/ucap/group/group.module.ts index 4b05f46..de55341 100644 --- a/src/app/ucap/group/group.module.ts +++ b/src/app/ucap/group/group.module.ts @@ -12,6 +12,7 @@ import { MatMenuModule } from '@angular/material/menu'; import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; import { MatTooltipModule } from '@angular/material/tooltip'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; import { UiModule } from '@ucap/ng-ui'; @@ -35,6 +36,7 @@ import { COMPONENTS } from './components'; MatMenuModule, MatButtonModule, MatTooltipModule, + MatProgressBarModule, I18nModule, UiModule, diff --git a/src/app/ucap/organization/components/index.ts b/src/app/ucap/organization/components/index.ts index 4503211..a93d204 100644 --- a/src/app/ucap/organization/components/index.ts +++ b/src/app/ucap/organization/components/index.ts @@ -1,11 +1,17 @@ import { Profile01Component } from './profile-01.component'; +import { ProfileImage01Component } from './profile-image-01.component'; import { ProfileListComponent } from './profile-list.component'; +import { ProfileMenu01Component } from './profile-menu-01.component'; +import { ProfileNavigationListComponent } from './profile-navigation-list.component'; import { SearchForTenantComponent } from './search-for-tenant.component'; import { TreeComponent } from './tree.component'; export const COMPONENTS = [ Profile01Component, + ProfileImage01Component, ProfileListComponent, + ProfileMenu01Component, + ProfileNavigationListComponent, SearchForTenantComponent, TreeComponent ]; diff --git a/src/app/ucap/organization/components/profile-01.component.html b/src/app/ucap/organization/components/profile-01.component.html index d114692..434c41b 100644 --- a/src/app/ucap/organization/components/profile-01.component.html +++ b/src/app/ucap/organization/components/profile-01.component.html @@ -1,19 +1,17 @@
    = new EventEmitter(); + @Output() + sendMessage: EventEmitter = new EventEmitter(); + @Output() + sendCall: EventEmitter = new EventEmitter(); + @Output() + sendSms: EventEmitter = new EventEmitter(); + @Output() + createConference: EventEmitter = new EventEmitter(); + @Output() + toggleFavorit: EventEmitter<{ + userInfo: UserInfoSS; + isFavorite: boolean; + }> = new EventEmitter(); + @Output() + toggleBuddy: EventEmitter<{ + userInfo: UserInfoSS; + isBuddy: boolean; + }> = new EventEmitter(); + + @Output() + uploadProfileImage: EventEmitter = new EventEmitter(); + @Output() + updateIntro: EventEmitter = new EventEmitter(); + @Output() + updateNickname: EventEmitter<{ + userInfo: UserInfoTypes; + nickname: string; + }> = new EventEmitter(); + @ViewChild('profileImageFileInput', { static: false }) profileImageFileInput: ElementRef; @@ -60,29 +99,33 @@ export class Profile01Component implements OnInit, OnDestroy { userInfo: UserInfoSS; - loginRes: LoginResponse; + user: User; authRes: AuthResponse; versionInfo2Res: VersionInfo2Response; - presence: StatusBulkInfo; + presences: Dictionary; String = String; + PresenceUtil = PresenceUtil; - private ngOnDestroySubject: Subject; + private ngOnDestroySubject: Subject = new Subject(); constructor( private queryProtocolService: QueryProtocolService, + private appChatService: AppChatService, private store: Store, private changeDetectorRef: ChangeDetectorRef, private logService: LogService, private i18nService: I18nService ) {} - isBuddy = false; PresenceType = PresenceType; + isBuddy = false; isFavorite = false; - ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); + currentNickname: string; + companyList: Company[]; + companyName: string; + ngOnInit(): void { this.store .pipe( takeUntil(this.ngOnDestroySubject), @@ -93,10 +136,10 @@ export class Profile01Component implements OnInit, OnDestroy { }); this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; - this.userSeq = this.loginRes.userSeq; + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; + this._refreshProfile(); }); this.store @@ -114,82 +157,70 @@ export class Profile01Component implements OnInit, OnDestroy { select(PresenceSelector.selectEntitiesStatusBulkInfo) ) .subscribe((statusBulkInfo) => { - this.presence = !!statusBulkInfo - ? statusBulkInfo[this.userSeq] - : undefined; + this.presences = statusBulkInfo; + }); + + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(BuddySelector.buddies)) + .subscribe((buddies) => { + const users = buddies.filter((buddy) => { + if (!!buddy && !!this.userInfo) { + return buddy.seq === Number(this.userInfo.seq); + } + }); + this.isBuddy = users.length > 0; + if (this.isBuddy) { + this.isFavorite = users[0].isFavorit; + this.currentNickname = users[0].nickName; + const profileImageFile = users[0].profileImageFile; + const intro = users[0].intro; + + this.userInfo = { ...this.userInfo, profileImageFile, intro }; + } + this.changeDetectorRef.markForCheck(); + }); + + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(CompanySelector.companyList) + ) + .subscribe((companyList) => { + if (!companyList) { + return; + } + this.companyList = companyList; }); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } - getPresence(type: PresenceType): string { - let status: string; - let rtnClass = ''; - switch (type) { - case PresenceType.PC: - status = !!this.presence ? this.presence.pcStatus : undefined; - break; - case PresenceType.MOBILE: - status = !!this.presence ? this.presence.mobileStatus : undefined; - break; + getPresenceInfo(): StatusBulkInfo | undefined { + if (!!this.presences) { + return this.presences[this._userSeq]; + } else { + return undefined; } - - switch (status) { - case StatusCode.OnLine: - rtnClass = 'online'; - break; - case StatusCode.Away: - rtnClass = 'absence'; - break; - case StatusCode.Busy: - rtnClass = 'other-business'; - break; - default: - rtnClass = 'offline'; - break; - } - - return rtnClass; } - getPresenceMsg(): string { - let presenceMsg = this.i18nService.t('presence.offline'); - - if (!!this.presence) { - switch (this.presence.pcStatus) { - case StatusCode.OnLine: - presenceMsg = this.i18nService.t('presence.online'); - break; - case StatusCode.Away: - presenceMsg = this.i18nService.t('presence.away'); - break; - case StatusCode.Busy: - if ( - !!this.presence.statusMessage && - this.presence.statusMessage !== '.' - ) { - presenceMsg = this.presence.statusMessage; - } else { - presenceMsg = this.i18nService.t('presence.statusMessage1'); - } - break; - } + _refreshProfile() { + if (!this.user || !this.userSeq) { + return; } - return presenceMsg; - } + this.currentNickname = ''; - refreshProfile() { this.queryProtocolService .dataUser({ divCd: 'OPENPROF', seq: Number(this.userSeq), - senderCompanyCode: this.loginRes.userInfo.companyCode, - senderEmployeeType: this.loginRes.userInfo.employeeType + senderCompanyCode: this.user.info.companyCode, + senderEmployeeType: this.user.info.employeeType }) .pipe( take(1), @@ -197,47 +228,65 @@ export class Profile01Component implements OnInit, OnDestroy { tap(([res, buddies]) => { this.userInfo = res.userInfo; const users = buddies.filter( - (buddy) => buddy.seq + '' === this.userInfo.seq + (buddy) => buddy.seq === Number(this.userInfo.seq) ); this.isBuddy = users.length > 0; if (this.isBuddy) { this.isFavorite = users[0].isFavorit; + this.currentNickname = users[0].nickName.trim(); } - this.changeDetectorRef.detectChanges(); + if ( + !this.userInfo.companyName || + (!!this.userInfo.companyName && + this.userInfo.companyName.trim() === '') + ) { + const company = this.companyList.filter( + (companyInfo) => + companyInfo.companyCode === this.userInfo.companyCode + ); + if (!!company) { + this.companyName = company[0].companyName; + } + } else { + this.companyName = this.userInfo.companyName; + } + this.changeDetectorRef.markForCheck(); }) ) .subscribe(); } - onProfileImageView() {} + onProfileImageView(userInfo: UserInfoSS) {} onChangeFileInput() {} onChangeIntro(intro: string) { - console.log(intro); - this.store.dispatch( - LoginActions.infoUser({ - req: { - type: UserInfoUpdateType.Intro, - info: intro - } - }) - ); + this.updateIntro.emit(intro); } onChangeNickname(nickname: string) { - console.log(nickname); + this.updateNickname.emit({ userInfo: this.userInfo, nickname }); } onToggleFavorite(params: { userInfo: UserInfoSS; isFavorite: boolean }) { - this.store.dispatch( - BuddyActions.update({ - req: { seq: Number(params.userInfo.seq), isFavorit: params.isFavorite } - }) - ); + this.toggleFavorit.emit(params); + } + onToggleBuddy(params: { userInfo: UserInfoSS; isBuddy: boolean }) { + this.toggleBuddy.emit(params); + } + onOpenChat(userInfo: UserInfoSS) { + this.openChat.emit(userInfo); + } + onSendCall(calleeNumber: string) { + this.sendCall.emit(calleeNumber); + } + onSendSms(smsNum: string) { + this.sendSms.emit(smsNum); + } + onCreateConference(userSeq: number) { + this.createConference.emit(userSeq); + } + onSendMessage(userInfo: UserInfoSS) { + this.sendMessage.emit(userInfo); } - onToggleBuddy(params: { userInfo: UserInfoSS; isBuddy: boolean }) {} - onOpenChat(userInfo: UserInfoSS) {} - onSendCall(calleeNumber: string) {} - onSendSms(smsNum: string) {} - onCreateConference(userSeq: number) {} - onSendMessage(userInfo: UserInfoSS) {} - onUploadProfileImage(profileImageFileUploadItem: FileUploadItem) {} + onUploadProfileImage(profileImageFileUploadItem: FileUploadItem) { + this.uploadProfileImage.emit(profileImageFileUploadItem); + } } diff --git a/src/app/ucap/organization/components/profile-image-01.component.html b/src/app/ucap/organization/components/profile-image-01.component.html new file mode 100644 index 0000000..7e320c9 --- /dev/null +++ b/src/app/ucap/organization/components/profile-image-01.component.html @@ -0,0 +1,10 @@ +
    + + +
    diff --git a/src/app/ucap/organization/components/profile-image-01.component.scss b/src/app/ucap/organization/components/profile-image-01.component.scss new file mode 100644 index 0000000..198d940 --- /dev/null +++ b/src/app/ucap/organization/components/profile-image-01.component.scss @@ -0,0 +1,6 @@ +@import '~@ucap/lg-scss/mixins'; + +.profile-image-container { + width: 100%; + height: 100%; +} diff --git a/src/app/ucap/organization/components/profile-image-01.component.spec.ts b/src/app/ucap/organization/components/profile-image-01.component.spec.ts new file mode 100644 index 0000000..a8f5045 --- /dev/null +++ b/src/app/ucap/organization/components/profile-image-01.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { ProfileImage01Component } from './profile-image-01.component'; + +describe('app::ucap::organization::ProfileImage01Component', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [ProfileImage01Component] + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(ProfileImage01Component); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'ucap-lg-web'`, () => { + const fixture = TestBed.createComponent(ProfileImage01Component); + const app = fixture.componentInstance; + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(ProfileImage01Component); + fixture.detectChanges(); + const compiled = fixture.nativeElement; + expect(compiled.querySelector('.content span').textContent).toContain( + 'ucap-lg-web app is running!' + ); + }); +}); diff --git a/src/app/ucap/organization/components/profile-image-01.component.ts b/src/app/ucap/organization/components/profile-image-01.component.ts new file mode 100644 index 0000000..5f289e9 --- /dev/null +++ b/src/app/ucap/organization/components/profile-image-01.component.ts @@ -0,0 +1,72 @@ +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectorRef, + Input, + Output, + EventEmitter +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { VersionInfo2Response } from '@ucap/api-public'; +import { UserInfoSS } from '@ucap/protocol-query'; +import { StatusBulkInfo } from '@ucap/protocol-status'; + +import { LogService } from '@ucap/ng-logger'; +import { PresenceSelector } from '@ucap/ng-store-organization'; + +@Component({ + selector: 'app-organization-profile-image-01', + templateUrl: './profile-image-01.component.html', + styleUrls: ['./profile-image-01.component.scss'] +}) +export class ProfileImage01Component implements OnInit, OnDestroy { + @Input() + userInfo: UserInfoSS; + + @Input() + versionInfo: VersionInfo2Response; + + @Output() + openProfile: EventEmitter = new EventEmitter(); + + presenceInfo: StatusBulkInfo; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private store: Store, + private changeDetectorRef: ChangeDetectorRef, + private logService: LogService + ) {} + + ngOnInit(): void { + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select( + PresenceSelector.selectStatusBulkInfo, + Number(this.userInfo?.seq) + ) + ) + .subscribe((status) => { + this.presenceInfo = status; + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onOpenProfile(userInfo: UserInfoSS) { + this.openProfile.emit(userInfo); + } +} diff --git a/src/app/ucap/organization/components/profile-list.component.html b/src/app/ucap/organization/components/profile-list.component.html index 726f0e0..171844a 100644 --- a/src/app/ucap/organization/components/profile-list.component.html +++ b/src/app/ucap/organization/components/profile-list.component.html @@ -1,21 +1,117 @@
    - - - - - - - +
    + +
    +
    + + + + + + + +
    + +
    +
    +
    +
    +
    + + + + + + + + + +
    +
    + + + + + + + + diff --git a/src/app/ucap/organization/components/profile-list.component.scss b/src/app/ucap/organization/components/profile-list.component.scss index be45828..bba0c46 100644 --- a/src/app/ucap/organization/components/profile-list.component.scss +++ b/src/app/ucap/organization/components/profile-list.component.scss @@ -1,4 +1,16 @@ +@import '~@ucap/lg-scss/mixins'; + .profile-list-container { width: 100%; height: 100%; + + .profile-list-progressbar { + width: 100%; + height: 4px; + } + + .profile-list { + width: 100%; + height: calc(100% - 4px); + } } diff --git a/src/app/ucap/organization/components/profile-list.component.ts b/src/app/ucap/organization/components/profile-list.component.ts index a96f379..eea75dd 100644 --- a/src/app/ucap/organization/components/profile-list.component.ts +++ b/src/app/ucap/organization/components/profile-list.component.ts @@ -9,18 +9,20 @@ import { ChangeDetectorRef, Input, Output, - EventEmitter + EventEmitter, + NgZone } from '@angular/core'; import { Store, select } from '@ngrx/store'; +import { SortOrder } from '@ucap/core'; import { VersionInfo2Response } from '@ucap/api-public'; -import { LoginResponse } from '@ucap/protocol-authentication'; import { UserInfoSS, DeptSearchType, DeptUserRequest } from '@ucap/protocol-query'; +import { User } from '@ucap/protocol-info'; import { LogService } from '@ucap/ng-logger'; import { @@ -31,21 +33,13 @@ import { QueryProtocolService } from '@ucap/ng-protocol-query'; import { DepartmentActions, DepartmentSelector, - PresenceActions + PresenceActions, + UserSelector } from '@ucap/ng-store-organization'; -import { - FixedSizeVirtualScrollStrategy, - VIRTUAL_SCROLL_STRATEGY -} from '@angular/cdk/scrolling'; import { SearchData } from '../models/search-data'; -import { SortOrder } from '@ucap/core'; -export class ProfileListVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy { - constructor() { - super(70, 250, 500); // (itemSize, minBufferPx, maxBufferPx) - } -} +const DEPT_ORDER_PROPERTY = 'order'; interface CheckedInfo { checked: boolean; @@ -56,18 +50,12 @@ interface CheckedInfo { selector: 'app-organization-profile-list', templateUrl: './profile-list.component.html', styleUrls: ['./profile-list.component.scss'], - providers: [ - { - provide: VIRTUAL_SCROLL_STRATEGY, - useClass: ProfileListVirtualScrollStrategy - } - ], changeDetection: ChangeDetectionStrategy.OnPush }) export class ProfileListComponent implements OnInit, OnDestroy { @Input() set searchData(data: SearchData) { - if (!this.loginRes) { + if (!this.user) { this._searchData = data; return; } @@ -80,6 +68,13 @@ export class ProfileListComponent implements OnInit, OnDestroy { selectedUser: UserInfoSS[]; @Input() + nodeType = '01'; + + @Input() + cacheSize: number; + + @Input() + /** Cycle order of sorting by use ascending: undefined -> true -> false */ set sortOrder(value: SortOrder) { this._sortOrder = value; this.userInfos = this.sort(this.userInfos); @@ -90,7 +85,7 @@ export class ProfileListComponent implements OnInit, OnDestroy { // tslint:disable-next-line: variable-name _sortOrder: SortOrder = { property: 'name', - ascending: true + ascending: undefined }; @Output() @@ -99,6 +94,15 @@ export class ProfileListComponent implements OnInit, OnDestroy { @Output() changedCheck: EventEmitter = new EventEmitter(); + @Output() + openProfile: EventEmitter = new EventEmitter(); + + @Output() + floatingProfileMenu: EventEmitter<{ + menuType: string; + userInfo: UserInfoSS; + }> = new EventEmitter(); + set userInfos(userInfos: UserInfoSS[]) { this._userInfos = userInfos; this.searched.emit(userInfos); @@ -109,12 +113,15 @@ export class ProfileListComponent implements OnInit, OnDestroy { // tslint:disable-next-line: variable-name _userInfos: UserInfoSS[] = []; - loginRes: LoginResponse; + user: User; versionInfo2Res: VersionInfo2Response; - processing = false; + showFabUserSeq: string; - private ngOnDestroySubject: Subject; + processing = false; + String = String; + + private ngOnDestroySubject: Subject = new Subject(); private myDeptDestroySubject: Subject; constructor( @@ -125,8 +132,6 @@ export class ProfileListComponent implements OnInit, OnDestroy { ) {} ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - this.store .pipe( takeUntil(this.ngOnDestroySubject), @@ -137,18 +142,20 @@ export class ProfileListComponent implements OnInit, OnDestroy { }); this.store - .pipe(takeUntil(this.ngOnDestroySubject), select(LoginSelector.loginRes)) - .subscribe((loginRes) => { - this.loginRes = loginRes; + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; if (!!this._searchData) { - this.searchMember(this._searchData); + const tempData = { ...this._searchData }; this._searchData = undefined; + this.searchMember(tempData); } }); } ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } @@ -168,6 +175,21 @@ export class ProfileListComponent implements OnInit, OnDestroy { this.changedCheck.emit(checked); } + uncheckAll() { + if (!this.userInfos || 0 === this.userInfos.length) { + return; + } + + const unchecked: CheckedInfo[] = []; + this.userInfos.forEach((u) => { + unchecked.push({ + checked: false, + userInfo: u + }); + }); + this.changedCheck.emit(unchecked); + } + /** 개별 체크여부 */ getCheckedUser(userInfo: UserInfoSS) { if (!!this.selectedUser && this.selectedUser.length > 0) { @@ -184,7 +206,8 @@ export class ProfileListComponent implements OnInit, OnDestroy { } onOpenProfile(userInfo: UserInfoSS): void { - alert('Open Profile'); + // alert('Open Profile'); + this.openProfile.emit(userInfo); } private getMyDeptMember() { @@ -192,17 +215,20 @@ export class ProfileListComponent implements OnInit, OnDestroy { const req: DeptUserRequest = { divCd: 'ORG', - companyCode: this.loginRes.companyCode, - seq: this.loginRes.departmentCode, + companyCode: this.user.companyCode, + seq: this.user.departmentCode, search: '', searchRange: DeptSearchType.All, - senderCompanyCode: this.loginRes.companyCode, - senderEmployeeType: this.loginRes.userInfo.employeeType + senderCompanyCode: this.user.companyCode, + senderEmployeeType: this.user.info.employeeType }; + this.processing = true; + this.store .pipe( takeUntil(this.ngOnDestroySubject), + takeUntil(this.myDeptDestroySubject), select(DepartmentSelector.myDepartmentUserInfoList) ) .subscribe( @@ -211,14 +237,17 @@ export class ProfileListComponent implements OnInit, OnDestroy { this.store.dispatch(DepartmentActions.myDeptUser({ req })); return; } - this.userInfos = this.sort(myDepartmentUserInfoList); + this._refreshUserInfos(myDepartmentUserInfoList); this.myDeptDestroySubject.next(); this.myDeptDestroySubject.complete(); this.myDeptDestroySubject = undefined; }, (error) => {}, - () => {} + () => { + console.log('this.ngOnDestroySubject'); + this.processing = false; + } ); } @@ -229,22 +258,43 @@ export class ProfileListComponent implements OnInit, OnDestroy { const property = this.sortOrder.property; const ascending = this.sortOrder.ascending; + let deptA: any; + let deptB: any; + let c: any; + let d: any; return userInfos.slice().sort((a, b) => { - let c: any; - let d: any; try { + deptA = a[DEPT_ORDER_PROPERTY]; + deptB = b[DEPT_ORDER_PROPERTY]; + + if (undefined === ascending) { + return deptA < deptB + ? -1 + : deptA > deptB + ? 1 + : a[property] < b[property] + ? -1 + : a[property] > b[property] + ? 1 + : 0; + } + c = ascending ? a[property] : b[property]; d = ascending ? b[property] : a[property]; + + return c < d ? -1 : c > d ? 1 : 0; } catch (error) { console.log(error); } - return c < d ? -1 : c > d ? 1 : 0; }); } private searchMember(searchData: SearchData) { - if (!searchData || (!searchData.companyCode && !searchData.deptSeq)) { + if ( + !searchData || + (!searchData.bySearch && undefined === searchData.deptSeq) + ) { this.getMyDeptMember(); return; } @@ -262,18 +312,18 @@ export class ProfileListComponent implements OnInit, OnDestroy { companyCode: searchData.companyCode, searchRange: DeptSearchType.All, search: searchData.searchWord, - senderCompanyCode: this.loginRes.userInfo.companyCode, - senderEmployeeType: this.loginRes.userInfo.employeeType + senderCompanyCode: this.user.info.companyCode, + senderEmployeeType: this.user.info.employeeType }; } else { req = { divCd: 'ORG', - companyCode: this.loginRes.companyCode, + companyCode: this.user.companyCode, seq: Number(searchData.deptSeq), search: '', searchRange: DeptSearchType.All, - senderCompanyCode: this.loginRes.companyCode, - senderEmployeeType: this.loginRes.userInfo.employeeType + senderCompanyCode: this.user.companyCode, + senderEmployeeType: this.user.info.employeeType }; } @@ -283,21 +333,7 @@ export class ProfileListComponent implements OnInit, OnDestroy { .pipe(take(1)) .subscribe( (data) => { - this.userInfos = this.sort(data.userInfos); - this.changeDetectorRef.detectChanges(); - - // 검색 결과에 따른 프레즌스 조회. - const userSeqList: string[] = []; - this.userInfos.map((user) => userSeqList.push(user.seq)); - - if (userSeqList.length > 0) { - this.store.dispatch( - PresenceActions.bulkInfo({ - divCd: 'orgSrch', - userSeqs: userSeqList - }) - ); - } + this._refreshUserInfos(data.userInfos); }, (error) => {}, () => { @@ -305,4 +341,42 @@ export class ProfileListComponent implements OnInit, OnDestroy { } ); } + + private _refreshUserInfos(userInfos: UserInfoSS[]) { + this.userInfos = this.sort(userInfos); + + // 검색 결과에 따른 프레즌스 조회. + const userSeqList: string[] = userInfos.map((user) => user.seq); + + if (userSeqList.length > 0) { + this.store.dispatch( + PresenceActions.bulkInfo({ + divCd: 'orgSrch', + userSeqs: userSeqList + }) + ); + } + } + + onMouseover(event: MouseEvent, userInfo: UserInfoSS): void { + this.showFabUserSeq = userInfo.seq; + + event.preventDefault(); + event.stopPropagation(); + } + onMouseleave(event: MouseEvent): void { + this.showFabUserSeq = undefined; + + event.preventDefault(); + event.stopPropagation(); + } + + onClickProfileContextMenu( + event: MouseEvent, + menuType: string, + userInfo: UserInfoSS + ) { + this.floatingProfileMenu.emit({ menuType, userInfo }); + event.stopPropagation(); + } } diff --git a/src/app/ucap/organization/components/profile-menu-01.component.html b/src/app/ucap/organization/components/profile-menu-01.component.html new file mode 100644 index 0000000..0f9a673 --- /dev/null +++ b/src/app/ucap/organization/components/profile-menu-01.component.html @@ -0,0 +1,89 @@ +
    + + + + + + + +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    diff --git a/src/app/ucap/organization/components/profile-menu-01.component.scss b/src/app/ucap/organization/components/profile-menu-01.component.scss new file mode 100644 index 0000000..3217abc --- /dev/null +++ b/src/app/ucap/organization/components/profile-menu-01.component.scss @@ -0,0 +1,6 @@ +@import '~@ucap/lg-scss/mixins'; + +.profile-menu-container { + width: 100%; + height: 100%; +} diff --git a/src/app/ucap/organization/components/profile-menu-01.component.spec.ts b/src/app/ucap/organization/components/profile-menu-01.component.spec.ts new file mode 100644 index 0000000..7a48b41 --- /dev/null +++ b/src/app/ucap/organization/components/profile-menu-01.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { ProfileMenu01Component } from './profile-menu-01.component'; + +describe('app::ucap::organization::ProfileMenu01Component', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [ProfileMenu01Component] + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(ProfileMenu01Component); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'ucap-lg-web'`, () => { + const fixture = TestBed.createComponent(ProfileMenu01Component); + const app = fixture.componentInstance; + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(ProfileMenu01Component); + fixture.detectChanges(); + const compiled = fixture.nativeElement; + expect(compiled.querySelector('.content span').textContent).toContain( + 'ucap-lg-web app is running!' + ); + }); +}); diff --git a/src/app/ucap/organization/components/profile-menu-01.component.ts b/src/app/ucap/organization/components/profile-menu-01.component.ts new file mode 100644 index 0000000..19d2850 --- /dev/null +++ b/src/app/ucap/organization/components/profile-menu-01.component.ts @@ -0,0 +1,199 @@ +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Output, + EventEmitter +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { StatusType, StatusCode } from '@ucap/core'; +import { VersionInfo2Response } from '@ucap/api-public'; +import { User, UserInfoUpdateType } from '@ucap/protocol-info'; + +import { LogService } from '@ucap/ng-logger'; + +import { + PresenceActions, + UserActions, + UserSelector +} from '@ucap/ng-store-organization'; +import { ConfigurationSelector } from '@ucap/ng-store-authentication'; + +import { AppAuthenticationService } from '@app/services/app-authentication.service'; +import { UserStore } from '@app/models/user-store'; +import { AppActions } from '@app/store/actions'; + +@Component({ + selector: 'app-organization-profile-menu-01', + templateUrl: './profile-menu-01.component.html', + styleUrls: ['./profile-menu-01.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ProfileMenu01Component implements OnInit, OnDestroy { + @Output() + selectedProfileManage: EventEmitter = new EventEmitter(); + + @Output() + selectedNotice: EventEmitter = new EventEmitter(); + + @Output() + selectedSettings: EventEmitter = new EventEmitter(); + + @Output() + selectedLogout: EventEmitter = new EventEmitter(); + + @Output() + selectedExit: EventEmitter = new EventEmitter(); + + @Output() + done: EventEmitter = new EventEmitter(); + + user: User; + + userStore: UserStore; + + versionInfo2Res: VersionInfo2Response; + + idleTimePeriods: number[] = [10, 20, 30]; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private store: Store, + private changeDetectorRef: ChangeDetectorRef, + private appAuthenticationService: AppAuthenticationService, + private logService: LogService + ) {} + + ngOnInit(): void { + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; + this.changeDetectorRef.markForCheck(); + }); + + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + + this.appAuthenticationService + .getUserStore$() + .pipe(takeUntil(this.ngOnDestroySubject)) + .subscribe((userStore) => { + this.userStore = userStore; + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + onChangedIntro(intro: string) { + this.store.dispatch( + UserActions.modifyInfo({ + req: { + type: UserInfoUpdateType.Intro, + info: intro + } + }) + ); + } + + onClickProfileManage(event: Event) { + this.selectedProfileManage.emit(); + } + + onClickNotice(event: Event) { + this.selectedNotice.emit(); + } + + onClickSettings(event: Event) { + this.selectedSettings.emit(); + } + + onChangedStatusMessage(data: { index: number; message: string }) { + console.log('onChangedStatusMessage', data); + this.store.dispatch( + PresenceActions.messageUpdate({ + req: { + index: data.index, + statusMessage: data.message + } + }) + ); + } + + onSelectedStatus(status: 'online' | 'away' | 'busy1' | 'busy2' | 'busy3') { + let statusCode: StatusCode; + let statusMessage: string; + switch (status) { + case 'online': + statusCode = StatusCode.OnLine; + break; + case 'away': + statusCode = StatusCode.Away; + break; + case 'busy1': + statusCode = StatusCode.Busy; + statusMessage = this.user.statusMessage1; + break; + case 'busy2': + statusCode = StatusCode.Busy; + statusMessage = this.user.statusMessage2; + break; + case 'busy3': + statusCode = StatusCode.Busy; + statusMessage = this.user.statusMessage3; + break; + + default: + break; + } + + this.store.dispatch( + PresenceActions.status({ + req: { + statusDivisionType: StatusType.Messenger, + statusType: statusCode, + statusMessage + } + }) + ); + + this.done.emit(); + } + + onChangedIdleCheckTime(idleCheckTime: number) { + this.appAuthenticationService.setUserStore({ + ...this.userStore, + idleCheckTime + }); + this.store.dispatch( + AppActions.idleTimeChanged({ idleTime: idleCheckTime }) + ); + } + + onLogout() { + this.selectedLogout.emit(); + } + + onExit() { + this.selectedExit.emit(); + } +} diff --git a/src/app/ucap/organization/components/profile-navigation-list.component.html b/src/app/ucap/organization/components/profile-navigation-list.component.html new file mode 100644 index 0000000..cca0e6d --- /dev/null +++ b/src/app/ucap/organization/components/profile-navigation-list.component.html @@ -0,0 +1,77 @@ +
    +
    + +
    + + + + +
    +
    +
    +
    + +
    +
    + + +
    + + +
    + {{ item.node | ucapOrganizationTranslate: 'name' }} +
    +
    + + + +
    +
    +

    {{ _userStatusMessage(item.node) }}

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + diff --git a/src/app/ucap/organization/components/profile-navigation-list.component.scss b/src/app/ucap/organization/components/profile-navigation-list.component.scss new file mode 100644 index 0000000..4b6c8b3 --- /dev/null +++ b/src/app/ucap/organization/components/profile-navigation-list.component.scss @@ -0,0 +1,105 @@ +@import '~@ucap/lg-scss/mixins'; + +.profile-navigation-list-container { + width: 100%; + height: 100%; + overflow: hidden; + + .organization-tree-simple { + height: 50px; + display: flex; + flex-direction: row; + align-items: center; + background-color: #f1f2f6; + padding: 0 16px; + overflow: hidden; + .organization-dept { + display: flex; + align-content: center; + &:first-child { + .btn-navigate-next { + display: none; + } + } + &:last-child { + .dept-name { + background-color: #fd578a; + color: #ffffff; + border: none; + } + } + .dept-name { + border-radius: 100px; + height: 30px; + box-shadow: none; + border: 1px solid #999999; + color: #666666; + } + .btn-navigate-next { + width: 30px; + height: 30px; + color: #cccccc; + } + } + } + + .profile-navigation-list-progressbar { + width: 100%; + height: 4px; + } + + .profile-navigation-list { + width: 100%; + height: calc(100% - 55px); + + .list-dept-name { + padding: 0 5px; + position: relative; + display: flex; + align-items: center; + height: 60px; + color: #666; + font-weight: 600; + border-top: 1px solid #ccc; + cursor: pointer; + &:before { + content: 'account_tree'; + font-family: 'Material Icons Outlined'; + font-size: 20px; + color: #666; + flex: 0 0 50px; + text-align: center; + font-weight: 400; + } + span { + flex: 1 0 auto; + } + &:after { + content: 'keyboard_arrow_right'; + font-family: 'Material Icons Outlined'; + font-size: 20px; + color: #666; + flex: 0 0 30px; + text-align: center; + font-weight: 400; + } + } + + .list-member-info { + padding: 0 5px; + display: flex; + border-top: 1px solid #ccc; + width: calc(100% - 7px); + height: 60px; + } + + .ng-star-inserted { + &:first-of-type { + .list-dept-name, + .list-member-info { + border-top: none; + } + } + } + } +} diff --git a/src/app/ucap/organization/components/profile-navigation-list.component.spec.ts b/src/app/ucap/organization/components/profile-navigation-list.component.spec.ts new file mode 100644 index 0000000..c9660ae --- /dev/null +++ b/src/app/ucap/organization/components/profile-navigation-list.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Profile01Component } from './profile-01.component'; + +describe('app::ucap::organization::Profile01Component', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [Profile01Component] + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(Profile01Component); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'ucap-lg-web'`, () => { + const fixture = TestBed.createComponent(Profile01Component); + const app = fixture.componentInstance; + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(Profile01Component); + fixture.detectChanges(); + const compiled = fixture.nativeElement; + expect(compiled.querySelector('.content span').textContent).toContain( + 'ucap-lg-web app is running!' + ); + }); +}); diff --git a/src/app/ucap/organization/components/profile-navigation-list.component.ts b/src/app/ucap/organization/components/profile-navigation-list.component.ts new file mode 100644 index 0000000..24c4f95 --- /dev/null +++ b/src/app/ucap/organization/components/profile-navigation-list.component.ts @@ -0,0 +1,408 @@ +import { Subject } from 'rxjs'; +import { takeUntil, take } from 'rxjs/operators'; + +import { + Component, + OnInit, + OnDestroy, + ChangeDetectionStrategy, + ChangeDetectorRef, + Input, + Output, + EventEmitter, + NgZone, + ViewChild +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { VersionInfo2Response } from '@ucap/api-public'; +import { + DeptSearchType, + DeptUserRequest, + DeptInfo, + UserInfoSS +} from '@ucap/protocol-query'; + +import { LogService } from '@ucap/ng-logger'; +import { + LoginSelector, + ConfigurationSelector +} from '@ucap/ng-store-authentication'; +import { QueryProtocolService } from '@ucap/ng-protocol-query'; +import { + DepartmentSelector, + PresenceActions, + UserSelector +} from '@ucap/ng-store-organization'; + +import { UserInfoTypes } from '@app/types'; + +import { BreadcrumbComponent } from '@ucap/ng-ui'; +import { User } from '@ucap/protocol-info'; + +const DEPT_ORDER_PROPERTY = 'order'; + +enum NodeType { + dept = 'dept', + member = 'member' +} + +interface CheckedInfo { + checked: boolean; + userInfo: UserInfoTypes; +} + +interface NavigationNode { + nodeType: NodeType; + node: DeptInfo | UserInfoTypes; +} + +@Component({ + selector: 'app-organization-profile-navigation-list', + templateUrl: './profile-navigation-list.component.html', + styleUrls: ['./profile-navigation-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ProfileNavigationListComponent implements OnInit, OnDestroy { + @Input() + selectedUser: UserInfoTypes[]; + + @Input() + fixedUserList: UserInfoTypes[]; + + @Input() + displayRootDepartment = false; + + @Output() + searched: EventEmitter = new EventEmitter(); + + @Output() + changedCheck: EventEmitter = new EventEmitter(); + + @ViewChild('breadcrumbForOrganization', { static: false }) + breadcrumbForOrganization: BreadcrumbComponent; + + set departmentCode(departmentCode: number) { + this._departmentCode = departmentCode; + this._changedDepartmentCode(); + } + get departmentCode(): number { + return this._departmentCode; + } + // tslint:disable-next-line: variable-name + _departmentCode: number; + + set userInfos(userInfos: UserInfoTypes[]) { + this._userInfos = userInfos; + this._changedItems(); + this.searched.emit(userInfos); + } + get userInfos() { + return this._userInfos; + } + // tslint:disable-next-line: variable-name + _userInfos: UserInfoTypes[] = []; + + set childLevelDeptInfoList(list: DeptInfo[]) { + this._childLevelDeptInfoList = list; + this._changedItems(); + } + get childLevelDeptInfoList() { + return this._childLevelDeptInfoList; + } + // tslint:disable-next-line: variable-name + _childLevelDeptInfoList: DeptInfo[] = []; + + user: User; + versionInfo2Res: VersionInfo2Response; + + breadcrumbs: DeptInfo[]; + departmentInfoList: DeptInfo[] = []; + nodes: NavigationNode[] = []; + + processing = false; + + itemSize = 60; + statusMsgClass: string; + NodeType = NodeType; + String = String; + + private ngOnDestroySubject: Subject = new Subject(); + + constructor( + private queryProtocolService: QueryProtocolService, + private store: Store, + private changeDetectorRef: ChangeDetectorRef, + private logService: LogService + ) {} + + ngOnInit(): void { + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(ConfigurationSelector.versionInfo2Response) + ) + .subscribe((versionInfo2Res) => { + this.versionInfo2Res = versionInfo2Res; + }); + + this.store + .pipe(takeUntil(this.ngOnDestroySubject), select(UserSelector.user)) + .subscribe((user) => { + this.user = user; + this.departmentCode = user.departmentCode; + }); + + this.store + .pipe( + takeUntil(this.ngOnDestroySubject), + select(DepartmentSelector.departmentInfoList) + ) + .subscribe((departmentInfoList) => { + this.departmentInfoList = departmentInfoList; + this._changedDepartmentCode(); + }); + } + + ngOnDestroy(): void { + if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); + this.ngOnDestroySubject.complete(); + } + } + + checkAll() { + if (!this.userInfos || 0 === this.userInfos.length) { + return; + } + + const checked: CheckedInfo[] = []; + this.userInfos.forEach((u) => { + checked.push({ + checked: true, + userInfo: u + }); + }); + this.changedCheck.emit(checked); + } + + uncheckAll() { + if (!this.userInfos || 0 === this.userInfos.length) { + return; + } + + const unchecked: CheckedInfo[] = []; + this.userInfos.forEach((u) => { + unchecked.push({ + checked: false, + userInfo: u + }); + }); + this.changedCheck.emit(unchecked); + } + + /** 개별 체크가능 여부 */ + getCheckableUser(userInfo: UserInfoTypes) { + let rtnValue = true; + + if (!!this.fixedUserList && this.fixedUserList.length > 0) { + rtnValue = !this.fixedUserList.some( + (fix) => fix.seq + '' === userInfo.seq + '' + ); + } + + if (!!rtnValue && !!this.user.info.seq) { + rtnValue = String(this.user.info.seq) !== String(userInfo.seq); + } + + return rtnValue; + } + /** 개별 체크여부 */ + getCheckedUser(userInfo: UserInfoTypes) { + if (!!this.selectedUser && this.selectedUser.length > 0) { + return ( + this.selectedUser.filter( + (item) => String(item.seq) === String(userInfo.seq) + ).length > 0 + ); + } + return false; + } + + /** 개별선택(토글) 이벤트 */ + onChangeCheckUser(param: { checked: boolean; userInfo: UserInfoTypes }) { + this.changedCheck.emit([param]); + } + + onOpenProfile(userInfo: UserInfoTypes): void { + alert('Open Profile'); + } + + onClickDepartment(deptInfo: DeptInfo) { + this.departmentCode = deptInfo.seq; + } + + private sort(userInfos: UserInfoTypes[]): UserInfoTypes[] { + if (!userInfos || 0 === userInfos.length) { + return userInfos; + } + + const property = 'name'; + const ascending = true; + let deptA: any; + let deptB: any; + + return userInfos.slice().sort((a, b) => { + try { + deptA = a[DEPT_ORDER_PROPERTY]; + deptB = b[DEPT_ORDER_PROPERTY]; + + return deptA < deptB + ? -1 + : deptA > deptB + ? 1 + : a[property] < b[property] + ? -1 + : a[property] > b[property] + ? 1 + : 0; + } catch (error) { + console.log(error); + } + }); + } + + private _searchMember(departmentCode: number) { + const req: DeptUserRequest = { + divCd: 'ORG', + companyCode: this.user.companyCode, + seq: departmentCode, + search: '', + searchRange: DeptSearchType.All, + senderCompanyCode: this.user.companyCode, + senderEmployeeType: this.user.info.employeeType + }; + + this.processing = true; + this.queryProtocolService + .deptUser(req) + .pipe(take(1)) + .subscribe( + (data) => { + this._refreshUserInfos(data.userInfos); + }, + (error) => { + console.log('error', error); + }, + () => { + this.processing = false; + this.changeDetectorRef.markForCheck(); + } + ); + } + + private _refreshUserInfos(userInfos: UserInfoTypes[]) { + this.userInfos = this.sort(userInfos); + + // 검색 결과에 따른 프레즌스 조회. + const userSeqList: string[] = userInfos.map((user) => String(user.seq)); + + if (userSeqList.length > 0) { + this.store.dispatch( + PresenceActions.bulkInfo({ + divCd: 'orgSrch', + userSeqs: userSeqList + }) + ); + } + } + + private _changedItems() { + const nodeList: NavigationNode[] = []; + + if ( + !!this.childLevelDeptInfoList && + 0 < this.childLevelDeptInfoList.length + ) { + this.childLevelDeptInfoList.forEach((d) => { + nodeList.push({ + nodeType: NodeType.dept, + node: d + }); + }); + } + + if (!!this.userInfos && 0 < this.userInfos.length) { + this.userInfos.forEach((u) => { + nodeList.push({ + nodeType: NodeType.member, + node: u + }); + }); + } + + this.nodes = nodeList; + } + + private _changedDepartmentCode() { + if (!this.departmentCode || !this.departmentInfoList) { + return; + } + + const hierarchy: DeptInfo[] = []; + this._hierarchyDepartmentInfo(this.departmentCode, hierarchy); + + if (0 === hierarchy.length) { + return; + } + + const currentDepthInfo = hierarchy[0]; + this.breadcrumbs = hierarchy.reverse(); + this.childLevelDeptInfoList = this._getChildLevelDepthInfos( + currentDepthInfo + ); + + this._searchMember(this.departmentCode); + this.changeDetectorRef.markForCheck(); + } + + private _hierarchyDepartmentInfo( + departmentCode: number, + hierarchy: DeptInfo[] + ) { + const depthInfo = this.departmentInfoList.find( + (deptInfo) => deptInfo.seq === departmentCode + ); + if (!depthInfo) { + return; + } + + if (0 === depthInfo.parentSeq) { + if (this.displayRootDepartment) { + hierarchy.push(depthInfo); + } + return; + } + + hierarchy.push(depthInfo); + + this._hierarchyDepartmentInfo(depthInfo.parentSeq, hierarchy); + } + + private _getChildLevelDepthInfos(deptInfo: DeptInfo): DeptInfo[] { + return this.departmentInfoList + .filter((d) => deptInfo.seq === d.parentSeq) + .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); + } + + _userStatusMessage(userInfo: UserInfoSS): string { + if (!!userInfo && !!userInfo.intro && userInfo.intro.trim() !== '') { + this.statusMsgClass = 'intro'; + return userInfo.intro; + } + + this.statusMsgClass = null; + return ''; + } +} diff --git a/src/app/ucap/organization/components/search-for-tenant.component.html b/src/app/ucap/organization/components/search-for-tenant.component.html index 85313ee..2c24ed1 100644 --- a/src/app/ucap/organization/components/search-for-tenant.component.html +++ b/src/app/ucap/organization/components/search-for-tenant.component.html @@ -1,9 +1,10 @@
    diff --git a/src/app/ucap/organization/components/search-for-tenant.component.scss b/src/app/ucap/organization/components/search-for-tenant.component.scss index 6d08ee7..b26b62f 100644 --- a/src/app/ucap/organization/components/search-for-tenant.component.scss +++ b/src/app/ucap/organization/components/search-for-tenant.component.scss @@ -1,10 +1,16 @@ +@import '~@ucap/lg-scss/mixins'; + .search-container { width: 100%; height: 100%; - padding: 4px 30px; + padding: 0 16px 10px; background-color: white; - height: 48px; - flex-basis: 48px; + height: 50px; + flex-basis: 50px; align-self: center; } + +.placeholder-gray { + color: #999999; +} diff --git a/src/app/ucap/organization/components/search-for-tenant.component.ts b/src/app/ucap/organization/components/search-for-tenant.component.ts index 8f12f87..b5d1c03 100644 --- a/src/app/ucap/organization/components/search-for-tenant.component.ts +++ b/src/app/ucap/organization/components/search-for-tenant.component.ts @@ -9,7 +9,8 @@ import { ChangeDetectorRef, Output, EventEmitter, - Input + Input, + ViewChild } from '@angular/core'; import { Store, select } from '@ngrx/store'; @@ -22,6 +23,7 @@ import { CompanySelector } from '@ucap/ng-store-organization'; import { AppAuthenticationService } from '@app/services/app-authentication.service'; import { SearchData } from '../models/search-data'; import { UserStore } from '@app/models/user-store'; +import { SearchForTenantComponent as UcapSearchForTenantComponent } from '@ucap/ng-ui-organization'; @Component({ selector: 'app-organization-search-for-tenant', @@ -32,15 +34,21 @@ import { UserStore } from '@app/models/user-store'; export class SearchForTenantComponent implements OnInit, OnDestroy { @Output() searchDataChange: EventEmitter = new EventEmitter(); @Input() set searchData(value: SearchData) { - this._searchData = value; - if (!this._searchData) { - this._searchData = { + let data: SearchData = { + ...value + }; + + if (!data.companyCode) { + data = { + ...data, companyCode: this.userStore.companyCode }; - } else { - if (!this._searchData.companyCode) { - this._searchData.companyCode = this.userStore.companyCode; - } + } + this._searchData = { + ...data + }; + if (!!this.searchForTenant && !!value && !!value.searchWord) { + this.searchForTenant.changeSearchword(value.searchWord); } } get searchData() { @@ -52,9 +60,12 @@ export class SearchForTenantComponent implements OnInit, OnDestroy { @Output() canceled: EventEmitter = new EventEmitter(); + @ViewChild('ucapOrgSearchForTenant', { static: true }) + searchForTenant: UcapSearchForTenantComponent; + companyList: Company[]; - private ngOnDestroySubject = new Subject(); + private ngOnDestroySubject: Subject = new Subject(); private userStore: UserStore; constructor( @@ -67,8 +78,6 @@ export class SearchForTenantComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - this.store .pipe( takeUntil(this.ngOnDestroySubject), @@ -81,11 +90,15 @@ export class SearchForTenantComponent implements OnInit, OnDestroy { ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } onChanged(data: { companyCode: string; searchWord: string }): void { + if (!!data && data.searchWord.localeCompare('') === 0) { + return; + } this.searchDataChange.emit({ companyCode: data.companyCode, searchWord: data.searchWord @@ -93,14 +106,9 @@ export class SearchForTenantComponent implements OnInit, OnDestroy { } onCanceled(): void { - this.searchDataChange.emit({ - companyCode: '', - searchWord: '' - }); + this.searchForTenant.changeSearchword(''); + this.searchForTenant.inputSearchWord.nativeElement.value = ''; + this.canceled.emit(); } - - // onCanceled(): void { - // this.canceled.emit(); - // } } diff --git a/src/app/ucap/organization/components/tree.component.html b/src/app/ucap/organization/components/tree.component.html index 683b4b3..1e429b4 100644 --- a/src/app/ucap/organization/components/tree.component.html +++ b/src/app/ucap/organization/components/tree.component.html @@ -2,7 +2,8 @@ diff --git a/src/app/ucap/organization/components/tree.component.scss b/src/app/ucap/organization/components/tree.component.scss index 582f4ce..84ea348 100644 --- a/src/app/ucap/organization/components/tree.component.scss +++ b/src/app/ucap/organization/components/tree.component.scss @@ -1,3 +1,5 @@ +@import '~@ucap/lg-scss/mixins'; + .tree-container { width: 100%; height: 100%; diff --git a/src/app/ucap/organization/components/tree.component.ts b/src/app/ucap/organization/components/tree.component.ts index 7752b58..1d37b34 100644 --- a/src/app/ucap/organization/components/tree.component.ts +++ b/src/app/ucap/organization/components/tree.component.ts @@ -15,43 +15,26 @@ import { import { Store, select } from '@ngrx/store'; -import { - FixedSizeVirtualScrollStrategy, - VIRTUAL_SCROLL_STRATEGY -} from '@angular/cdk/scrolling'; - -import { LoginResponse } from '@ucap/protocol-authentication'; import { DeptInfo } from '@ucap/protocol-query'; import { LogService } from '@ucap/ng-logger'; -import { LoginSelector } from '@ucap/ng-store-authentication'; + import { DepartmentSelector } from '@ucap/ng-store-organization'; import { TreeComponent as UCAPTreeComponent } from '@ucap/ng-ui-organization'; import { environment } from '@environments'; -export class TreeVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy { - constructor() { - super(46, 250, 500); // (itemSize, minBufferPx, maxBufferPx) - } -} - @Component({ selector: 'app-organization-tree', templateUrl: './tree.component.html', styleUrls: ['./tree.component.scss'], - providers: [ - { - provide: VIRTUAL_SCROLL_STRATEGY, - useClass: TreeVirtualScrollStrategy - } - ], changeDetection: ChangeDetectionStrategy.OnPush }) export class TreeComponent implements OnInit, OnDestroy { @Input() set initialExpanded(seq: number) { + this.currentDeptSeq = seq; if (!!this.treeData) { this.expand(seq); return; @@ -73,9 +56,9 @@ export class TreeComponent implements OnInit, OnDestroy { expanded?: number[]; }; - loginRes: LoginResponse; + currentDeptSeq: number; - private ngOnDestroySubject: Subject; + private ngOnDestroySubject: Subject = new Subject(); constructor( private store: Store, @@ -84,15 +67,11 @@ export class TreeComponent implements OnInit, OnDestroy { ) {} ngOnInit(): void { - this.ngOnDestroySubject = new Subject(); - combineLatest([ - this.store.pipe(select(LoginSelector.loginRes)), this.store.pipe(select(DepartmentSelector.departmentInfoList)) ]) .pipe(takeUntil(this.ngOnDestroySubject)) - .subscribe(([loginRes, deptInfoList]) => { - this.loginRes = loginRes; + .subscribe(([deptInfoList]) => { this.treeData = { deptInfoList, displayRoot: environment.productConfig.organization.displayRoot, @@ -101,7 +80,7 @@ export class TreeComponent implements OnInit, OnDestroy { : undefined }; this.changeDetectorRef.markForCheck(); - if (!!loginRes && !!deptInfoList) { + if (!!deptInfoList) { this._initialExpanded = undefined; } }); @@ -109,11 +88,13 @@ export class TreeComponent implements OnInit, OnDestroy { ngOnDestroy(): void { if (!!this.ngOnDestroySubject) { + this.ngOnDestroySubject.next(); this.ngOnDestroySubject.complete(); } } onClickNode(node: DeptInfo) { + this.currentDeptSeq = node.seq; this.clicked.emit(node); } diff --git a/src/app/ucap/organization/components/tree.strategy.ts b/src/app/ucap/organization/components/tree.strategy.ts deleted file mode 100644 index 564488d..0000000 --- a/src/app/ucap/organization/components/tree.strategy.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Observable, Subject } from 'rxjs'; - -import { - VirtualScrollStrategy, - CdkVirtualScrollViewport -} from '@angular/cdk/scrolling'; -import { distinctUntilChanged } from 'rxjs/operators'; - -export class OrganizationTreeVirtualScrollStrategy - implements VirtualScrollStrategy { - scrolledIndexChange: Observable; - - private indexSubject = new Subject(); - private viewport: CdkVirtualScrollViewport | null = null; - - constructor() { - this.scrolledIndexChange = this.indexSubject.pipe(distinctUntilChanged()); - } - - attach(viewport: CdkVirtualScrollViewport): void { - this.viewport = viewport; - } - detach(): void { - this.indexSubject.complete(); - this.viewport = null; - } - onContentScrolled(): void {} - onDataLengthChanged(): void {} - onContentRendered(): void {} - onRenderedOffsetChanged(): void {} - scrollToIndex(index: number, behavior: ScrollBehavior): void {} -} diff --git a/src/app/ucap/organization/organization.module.ts b/src/app/ucap/organization/organization.module.ts index c29a7fd..79ed236 100644 --- a/src/app/ucap/organization/organization.module.ts +++ b/src/app/ucap/organization/organization.module.ts @@ -11,12 +11,13 @@ import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatRippleModule } from '@angular/material/core'; import { MatSelectModule } from '@angular/material/select'; import { MatTreeModule } from '@angular/material/tree'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n'; +import { I18nModule } from '@ucap/ng-i18n'; import { UiModule } from '@ucap/ng-ui'; import { OrganizationUiModule } from '@ucap/ng-ui-organization'; @@ -34,6 +35,7 @@ import { COMPONENTS } from './components'; MatFormFieldModule, MatIconModule, MatInputModule, + MatProgressBarModule, MatRippleModule, MatSelectModule, MatTreeModule, @@ -46,12 +48,6 @@ import { COMPONENTS } from './components'; ], exports: [...COMPONENTS], declarations: [...COMPONENTS], - entryComponents: [], - providers: [ - { - provide: UCAP_I18N_NAMESPACE, - useValue: ['organization', 'common'] - } - ] + entryComponents: [] }) export class AppOrganizationModule {} diff --git a/src/assets/fonts/materialicons/materialicons-v52.woff b/src/assets/fonts/materialicons/materialicons-v52.woff new file mode 100644 index 0000000..0d6f66c Binary files /dev/null and b/src/assets/fonts/materialicons/materialicons-v52.woff differ diff --git a/src/assets/fonts/materialicons/materialicons-v52.woff2 b/src/assets/fonts/materialicons/materialicons-v52.woff2 new file mode 100644 index 0000000..2546ee6 Binary files /dev/null and b/src/assets/fonts/materialicons/materialicons-v52.woff2 differ diff --git a/src/assets/fonts/materialicons/materialiconsoutlined-v21.woff b/src/assets/fonts/materialicons/materialiconsoutlined-v21.woff new file mode 100644 index 0000000..9b3e0d5 Binary files /dev/null and b/src/assets/fonts/materialicons/materialiconsoutlined-v21.woff differ diff --git a/src/assets/fonts/materialicons/materialiconsoutlined-v21.woff2 b/src/assets/fonts/materialicons/materialiconsoutlined-v21.woff2 new file mode 100644 index 0000000..24c382e Binary files /dev/null and b/src/assets/fonts/materialicons/materialiconsoutlined-v21.woff2 differ diff --git a/src/assets/fonts/materialicons/materialiconsround-v21.woff b/src/assets/fonts/materialicons/materialiconsround-v21.woff new file mode 100644 index 0000000..9235713 Binary files /dev/null and b/src/assets/fonts/materialicons/materialiconsround-v21.woff differ diff --git a/src/assets/fonts/materialicons/materialiconsround-v21.woff2 b/src/assets/fonts/materialicons/materialiconsround-v21.woff2 new file mode 100644 index 0000000..7d6f039 Binary files /dev/null and b/src/assets/fonts/materialicons/materialiconsround-v21.woff2 differ diff --git a/src/assets/fonts/materialicons/materialiconssharp-v22.woff b/src/assets/fonts/materialicons/materialiconssharp-v22.woff new file mode 100644 index 0000000..e0e7483 Binary files /dev/null and b/src/assets/fonts/materialicons/materialiconssharp-v22.woff differ diff --git a/src/assets/fonts/materialicons/materialiconssharp-v22.woff2 b/src/assets/fonts/materialicons/materialiconssharp-v22.woff2 new file mode 100644 index 0000000..025d127 Binary files /dev/null and b/src/assets/fonts/materialicons/materialiconssharp-v22.woff2 differ diff --git a/src/assets/fonts/materialicons/materialiconstwotone-v20.woff b/src/assets/fonts/materialicons/materialiconstwotone-v20.woff new file mode 100644 index 0000000..798c79e Binary files /dev/null and b/src/assets/fonts/materialicons/materialiconstwotone-v20.woff differ diff --git a/src/assets/fonts/materialicons/materialiconstwotone-v20.woff2 b/src/assets/fonts/materialicons/materialiconstwotone-v20.woff2 new file mode 100644 index 0000000..4c6952b Binary files /dev/null and b/src/assets/fonts/materialicons/materialiconstwotone-v20.woff2 differ diff --git a/src/assets/i18n/en/authentication.json b/src/assets/i18n/en/authentication.json index fcf54b4..93d8f9f 100644 --- a/src/assets/i18n/en/authentication.json +++ b/src/assets/i18n/en/authentication.json @@ -2,13 +2,16 @@ "login": { "labels": { "doLogin": "Login", + "doLogout": "Logout", "rememberMe": "Remember me", "autoLogin": "Auto login", "instructionsOfLogin": "LOGIN TO YOUR ACCOUNT", "forgotPassword": "Forgot Password", "resetPassword": "Reset Password", "notesOnUse": "Notes on use", - "selectCompany": "Select company" + "selectCompany": "Select company", + "changePassword": "Change login password", + "privacyPolicy": "Privacy Policy" }, "fields": { "company": "Company", @@ -21,6 +24,48 @@ "requireLoginPw": "password is required.", "failed": "Failed to login", "attemptsExceeded": "Password error count exceeded." + }, + "settings": { + "login": "Login", + "autoStartOnBoot": "Auto start on OS boot time", + "autoLogin": "Auto login", + "autoHide": "Hide window on excuted" + } + }, + "password": { + "labels": {}, + "fields": { + "changePassword": "Change login password", + "currentPassword": "Current login password", + "newPassword": "New login password", + "newPasswordConfirm": "New login password confirm" + }, + "errors": { + "requireCurrentPassword": "Current password is required.", + "notSameWithCurrentPassword": "Does not match current password", + "sameWithCurrentPassword": "Same as current password", + "requireNewPassword": "New password is required.", + "requireNewPasswordConfirm": "New password confirm is required.", + "notSameWithNewPassword": "Does not match new password and new password confirm.", + "notContainSpacesForPassword": "Passwords cannot contain spaces", + "notContainUseridForPassword": "User ID cannot be included in the password", + "notContainPhonenumberForPassword": "Your mobile phone number cannot be included in your password", + "notAllowedAlphaNumOver3TimesForPassword": "You can't use numbers or letters over 3 times.", + "notAllowedConsecutiveAlphaNumOver3TimesForPassword": "You cannot use consecutive numbers or letters more than 3 times.", + "notSatisfiedCombineForPassword": "Combination of two or more kinds of letters, numbers and special characters.", + "minLengthCombineForPassword": "Password must be {{lengthOfPassword}} characters if {{countOfCombine}} combination.", + "failToChange": "Failed to change password." + }, + "notice": { + "condition": "Condition", + "condition1": "At least 8 digits: Can be set to 3 combinations of English lowercase letters, numbers, and special characters", + "condition2": "At least 10 digits: Can be set to 2 combinations of English lowercase letters, numbers, and special characters", + "condition3": "Password cannot be set if it contains more than 3 digits", + "condition4": "Password cannot be set if 3 or more consecutive numbers or characters are included", + "condition5": "Password cannot be set when user ID and mobile phone number are included" + }, + "settings": { + "label": "Login password" } } } diff --git a/src/assets/i18n/en/call.json b/src/assets/i18n/en/call.json index 0967ef4..1b539f1 100644 --- a/src/assets/i18n/en/call.json +++ b/src/assets/i18n/en/call.json @@ -1 +1,5 @@ -{} +{ + "settings": { + "label": "Phone call" + } +} diff --git a/src/assets/i18n/en/chat.json b/src/assets/i18n/en/chat.json index 833dea1..5d84f26 100644 --- a/src/assets/i18n/en/chat.json +++ b/src/assets/i18n/en/chat.json @@ -3,18 +3,28 @@ "searchRoomByName": "Search by room name and user", "noRoomUser": "No chat users", "today": "Today", - "openRoom": "Open room", - "turnOnRoomAlert": "Turn on room alert", - "turnOffRoomAlert": "Turn off room alert", - "exitFromRoom": "Exit room" + "yesterday": "Yesterday", + "noSelectRoom": "No chat room selected." }, "label": { "chat": "Chat", "menu": "Menu", "search": "Search", - "notificationIsOn": "Notification is on", - "notificationIsOff": "Notification is off", + "favorite": "Favorite", + "turnOnRoomAlert": "Turn on room alert", + "turnOffRoomAlert": "Turn off room alert", + "data": "Data", + "image": "Image", + "video": "Video", + "file": "File", + "event": "Event", + "selectedRoom": "Selected rooms", "showRoomUsers": "Show room users", + "addRoomUsers": "Add room users", + "addGroup": "Add group", + "roomSetting": "Setting", + "openRoom": "Open room", + "exitFromRoom": "Exit room", "send": "Send", "attachFile": "Attach file", "attachImage": "Attach image", @@ -22,13 +32,36 @@ "imoticon": "Imoticon", "emailSend": "Send email for chat.", "translation": "Trnaslation", - "gams": "+GAMS" + "gams": "+GAMS", + "replayEvent": "Replay", + "copyChatText": "Copy chat", + "forwardEventTo": "Forward chat to ...", + "forwardEventToMe": "Forward chat to me", + "removeEvent": "Remove chat", + "recallEvent": "Recall chat", + "openViewer": "Open Viewer", + "inputChatMessage": "Please enter a message", + "translations": { + "translation": "Translation", + "targetLanguage": "Traget language", + "noTranslation": "No translation", + "simpleView": "Simple View", + "preview": "Preview" + }, + "emailSends": { + "sendAll": "Send All", + "sendMe": "Send Me" + }, + "fileSends": { + "fileSend": "File Send", + "dragHere": "Drag files to this area to upload them." + } }, "event": { - "inviteToRoomWith": "{{owner}} invited {{inviter}}.", - "exitFromRoomWith": "{{exitor}} has left.", + "inviteToRoomWith": "{{owner}} invited {{inviter}}.", + "exitFromRoomWith": "{{exitor}} has left.", "ejectedFromRoomWith": "{{requester}} has eject {{ejected}}.", - "renamedRoomWith": "{{requester}} has changed their chat room name to '{{roomName}}'.", + "renamedRoomWith": "{{requester}} has changed their chat room name to '{{roomName}}'.", "setTimerWith": "{{requester}} set a timer ({{timer}})", "iosCapture": "{{requester}} captured a conversation", "showMassTranslationOfOriginal": "Show original", @@ -42,32 +75,81 @@ "scheduleTypeDefault": "[Event] Processing..", "scheduleTypePrefix": "[Event] ", "scheduleTypeSurfixLeft": " left", + "showScheduleDetail": "Show detail", "showPreviousEvents": "Show previous", - "moreUnreadEventsWith": "There is unread messages({{countOfUnread}})" + "moreUnreadEventsWith": "There is unread messages({{countOfUnread}})", + "isRoomTypeSecret": "This room is secret type", + "noRecentChat": "No recent conversation" }, "errors": { "label": "Chat erros", "inputChatMessage": "Please enter a chat message", "maxLengthOfMassText": "If you include a sticker, you can't send more than {{maxLength}} characters.", - "maxCountOfRoomMemberWith": "you can't open room with more than {{maxCount}} peoples.", + "maxCountOfRoomMemberWith": "You cannot chat with more than {{maxCount}} people including me.", "emptyOpenRoomType": "Pleas select type of room", - "translateServerError": "Failed to translate" + "translateServerError": "Failed to translate", + "addBuddyForGroup": "No group name was specified, or no group was selected." }, "dialog": { "title": { - "exitFromRoom": "Exit room", - "newChatRoom": "Add new chat room" + "removeChat": "Remove chat", + "newChatRoom": "Add new chat room", + "forwardTo": "Forward chat to ...", + "ejectFromRoom": "Eject from room", + "detail": "Detail", + "roomNameGuide": "Guide to setting chat room name", + "roomTimerGuide": "Timer setting guide", + "fileDownloadCheck": "Downloaded users", + "subSelectRoomType": "Select room type", + "subSelectUser": "Select users" }, + "confirmRemoveChat": "Do you want to delete the chat?
    Deleted messages apply only to your chat room and are not deleted from their chat rooms.", + "confirmRecallEvent": "Do you want to recall the chat?
    It is also retrieved from the other party's chat window.", "confirmExitFromRoom": "Do you want to exit the chat room?
    Exiting will delete your chat history and chat room information.", + "confirmAddBuddyForNewGroup": "Do you want to add chat room members after creating '{{targetGroups}}' group?", + "confirmAddBuddyForGroup": "Would you like to add chat room members to the '{{targetGroups}}' group?", + "confirmEjectFromRoom": "Do you want to eject member[{{targetMember}}] from room?", + "confirmSendEventEmailAll": "Would you like to email the conversation to everyone?", + "confirmSendEventEmailMe": "Would you like to email the conversation to me?", "normalRoom": "Basic Room", "timerRoom": "Timer Room", "normalRoomDescription": "Up to {{maxCount}} peoples
    can join.", - "timerRoomDescription": "When setting the timer,
    the conversation is automatically deleted.", + "timerRoomDescription": "When setting the timer After the set time,
    the conversation is automatically deleted.", + "roomNameGuideDescription": "Try setting a chat room name.
    If you set a name in the chat room setup step, it applies equally to all members.", + "roomTimerGuideDescription": "We opened a secret chat room.
    The default timer is 24 hours and all messages are deleted after 24 hours.
    If you want to change the timer, please select the desired time below.", + "sendEventEmailSuccess": "The contents of the conversation were delivered by e-mail.", + "selectedUserList": "Selected users", + "roomName": "Room Name", + "roomNameChangeTarget": "Change target", + "me": "Me", + "all": "All", + "settingTimer": "Setting Timer", + "settingTimerHint": "※ If the set time is exceeded, the conversation is deleted.", + "group": "Group", + "roomList": "Room List", + "searchResult": "Search Result", "button": { + "save": "Save", "cancel": "Cancel", "previous": "Previous", "selectRoomUser": "Choice user", - "openRoom": "Open chat room" + "openRoom": "Open chat room", + "addUser": "Add Complate", + "settingSave": "Setting Complate", + "addUserChatRoom": "Invite room user", + "addGroupMember": "Add member for group", + "del": "Delete", + "changeFolder": "Change folder", + "openViewer": "Viewer", + "download": "Download", + "all": "All", + "receiveFiles": "Receive {{targetObject}}", + "sendFiles": "Send {{targetObject}}", + "downloaded": "Downloaded", + "downloadNotYet": "Not downloaded" } + }, + "settings": { + "label": "Chat" } } diff --git a/src/assets/i18n/en/common.json b/src/assets/i18n/en/common.json index 042b70e..7c9a4b3 100644 --- a/src/assets/i18n/en/common.json +++ b/src/assets/i18n/en/common.json @@ -1,31 +1,83 @@ { - "common": { - "messages": { - "no": "No", - "yes": "Yes", - "confirm": "Confirm" - }, - "units": { - "date": "Date", - "time": "Time", - "hour": "hour", - "hourFrom": "hour", - "minute": "minute", - "second": "second", - "persons": "person(s)", - "hourLaterWith": "(An) {{hour}} hour(s) later", - "tomorrowMorning": "Tomorrow morning", - "tomorrowAfternoon": "Tomorrow afternoon", - "weekLaterWith": "(A) {{week}} week(s) later", - "monthLaterWith": "(A) {{month}} month(s) later" - }, - "file": { - "errors": { - "failToUpload": "File upload failed.", - "notSupporedType": "File format is not supported.
    ({{supporedType}})", - "notAcceptableMime": "File type is invalid.
    ({{supporedType}})", - "oversize": "You cannot upload files larger than {{size}} megabytes." - } + "useSpecialCharactor": "Special characters can only use -,_", + "messages": { + "no": "No", + "yes": "Yes", + "confirm": "Confirm", + "select": "Select", + "selectAll": "Select all", + "unselect": "Unselect", + "searching": "Searching", + "cancel": "Cancel", + "complate": "Complate", + "close": "Close", + "modify": "Modify", + "remove": "Remove", + "apply": "Apply", + "minimizeWindow": "Minimize window", + "maxmizeWindow": "Maxmize window", + "restoreWindow": "Restore window", + "Close window": "Close window", + "zoomOut": "Zoom out", + "zoomIn": "Zoom in", + "zoomReset": "Zoom reset", + "sirWith": "{{sir}}", + "exit": "Exit" + }, + "units": { + "date": "Date", + "time": "Time", + "hour": "hour", + "hourFrom": "hour", + "minute": "minute", + "second": "second", + "persons": "person(s)", + "hourLaterWith": "(An) {{hour}} hour(s) later", + "tomorrowMorning": "Tomorrow morning", + "tomorrowAfternoon": "Tomorrow afternoon", + "weekLaterWith": "(A) {{week}} week(s) later", + "monthLaterWith": "(A) {{month}} month(s) later" + }, + "file": { + "fileOpen": "Open file", + "folderOpen": "Open folder", + "download": "Download", + "delete": "Delete", + "save": "Save", + "saveAs": "Save as", + "saveAll": "Save all", + "refresh": "Refresh", + "downloading": "Downloading...", + "errors": { + "failToUpload": "File upload failed.", + "noPreview": "This file does not support preview.", + "cantPlay": "This file does not support playing.", + "notSupporedType": "File format is not supported.
    ({{supporedType}})", + "notAcceptableMime": "File type is invalid.
    ({{supporedType}})", + "oversize": "You cannot upload files larger than {{size}} megabytes." } + }, + "player": { + "play": "Play", + "stop": "Stop" + }, + "notification": { + "titleChatEventArrivedByUser": "A Message of chat from {{userInfo}}.", + "titleChatEventArrived": "A message of chat has arrived.", + "titleMessageArrivedByUser": "A Message from {{userInfo}}.", + "titleMessageArrived": "A message has arrived." + }, + "tooltip": { + "group": "Group", + "chat": "Chat", + "organization": "Organization", + "message": "Message", + "call": "Call", + "videoConference": "Video Conference", + "mobile": "Call mobile", + "office": "Call office", + "exitForcing": "Exit Forcing", + "profile": "Profile", + "more": "More" } } diff --git a/src/assets/i18n/en/group.json b/src/assets/i18n/en/group.json index 348512d..32e9250 100644 --- a/src/assets/i18n/en/group.json +++ b/src/assets/i18n/en/group.json @@ -1,58 +1,80 @@ { - "label": { - "confirmRemoveBuddy": "Do you want to delete the member?
    Deleted member apply only to your group and are not deleted from their group." - }, "category": { "favorite": "Favorite", "default": "Default", "myDept": "My Dept" }, - "moreMenu": { - "show": { - "all": "View all", - "onlineBuddy": "View only connected buddy", - "onOff": "View online offline " - }, - "group": { - "addNew": "Add new group", - "expandMore": "Expand all groups", - "expandLess": "Collapse all groups", - "changeOrder": "Change order", - "startChatWithGroup": "Chat with group", - "sendMessageToGroup": "Send message to group", - "groupMemberManagement": "Group Member Management", - "changeGroupName": "Change Group Name", - "removeGroup": "Remove group" - }, - "profile": { - "open": "Open profile", - "favorite": "Favorite", - "nickname": "Nickname", - "moveBuddy": "Move Member", - "copyBuddy": "Copy Member", - "removeBuddy": "Remove Member" - }, - "error": { - "label": "Group errors", - "requireName": "Group name is required." - } + "label": { + "group": "Group", + "member": "Member", + "organization": "Organization", + "addNewGroup": "Add new group", + "existingGroup": "Assign existing group", + "searchResult": "Search Result", + "online": "Online", + "offline": "Offline" }, - "profile": { - "labels": { - "myProfile": "My profile", - "company": "Company", - "email": "Email", - "linePhoneNumber": "Office", - "mobilePhoneNumber": "Mobile", - "department": "Department", - "chat": "Chat", - "sms": "SMS", - "videoConference": "Conference", - "message": "Message" + "dialog": { + "title": { + "addBuddy": "Add a Buddy", + "removeBuddy": "Remove a buddy", + "removeGroup": "Remove a group", + "createGroup": "Create a group", + "subTitleGroupInfo": "Group information registration", + "subTitleSelectMember": "Member selection", + "copyGroup": "Copy Group", + "moveGroup": "Move Group", + "addMember": "Add a Member", + "managementGroup": "Group Member Management", + "messageGroup": "Send message to group" }, - "fields": { - "intro": "인트로" + "btn": { + "cancel": "Cancel", + "compleate": "Compleate", + "addGroupCompleate": "Add group completed", + "selectMemberAndComplete": "Select member and complete." }, - "errors": {} + "newGroupName": "Please enter a new group name", + "newNickname": "Please enter a new nickname", + "selectedMember": "Selected member", + "removeBuddyConfirm": " {{targetMember}} Are you sure you want to delete the selected member?
    The selected member is only deleted from this group.", + "removeGroupConfirm": "Do you want to remove {{target}} from {{targetGroup}} group?", + "removeBuddyFromMenu": "Do you want to remove {{target}} from {{targetGroup}} group?", + "removeBuddyFromProfile": "Do you want to remove {{target}} from group?
    Deleting it from your profile will remove it from all groups." + }, + "error": { + "title": { + "default": "Error Group" + }, + "useOnlyForSpecialCharacter": "Only {{specialCharacter}} can be used for special characters.", + "requireInput": "This is required.", + "requireGroupName": "Group name is required.", + "invalidGroupName": "Invalid group name.", + "notSelectedUser": "The selected user does not exist.", + "bannedWords": "Prohibited word. [{{bannedWords}}]", + "sameNameExist": "Group name already exists." + }, + "contextMenu": { + "all": "View all", + "onlineBuddy": "View only connected buddy", + "onOffBuddy": "View online offline", + "addNewGroup": "Add new group", + "expandMore": "Expand all groups", + "expandLess": "Collapse all groups", + "changeOrder": "Change order", + "startChatWithGroup": "Chat with group", + "sendMessageToGroup": "Send message to group", + "groupMemberManagement": "Group Member Management", + "changeGroupName": "Change Group Name", + "removeGroup": "Remove group", + "profileOpen": "Open profile", + "unfavorite": "Unfavorite", + "addFavorite": "Add Favorite", + "setNickname": "Set Nickname", + "copyGroupMember": "Copy Group", + "moveGroupMember": "Move Group", + "moveBuddy": "Move Member", + "copyBuddy": "Copy Member", + "removeBuddyFromGroup": "Remove Member" } } diff --git a/src/assets/i18n/en/locale.json b/src/assets/i18n/en/locale.json new file mode 100644 index 0000000..80060ff --- /dev/null +++ b/src/assets/i18n/en/locale.json @@ -0,0 +1,602 @@ +{ + "languages": { + "ko": "Korean", + "en": "English", + "cn": "China" + }, + "timezone": { + "Africa/Abidjan": "Africa/Abidjan", + "Africa/Accra": "Africa/Accra", + "Africa/Addis_Ababa": "Africa/Addis_Ababa", + "Africa/Algiers": "Africa/Algiers", + "Africa/Asmara": "Africa/Asmara", + "Africa/Asmera": "Africa/Asmera", + "Africa/Bamako": "Africa/Bamako", + "Africa/Bangui": "Africa/Bangui", + "Africa/Banjul": "Africa/Banjul", + "Africa/Bissau": "Africa/Bissau", + "Africa/Blantyre": "Africa/Blantyre", + "Africa/Brazzaville": "Africa/Brazzaville", + "Africa/Bujumbura": "Africa/Bujumbura", + "Africa/Cairo": "Africa/Cairo", + "Africa/Casablanca": "Africa/Casablanca", + "Africa/Ceuta": "Africa/Ceuta", + "Africa/Conakry": "Africa/Conakry", + "Africa/Dakar": "Africa/Dakar", + "Africa/Dar_es_Salaam": "Africa/Dar_es_Salaam", + "Africa/Djibouti": "Africa/Djibouti", + "Africa/Douala": "Africa/Douala", + "Africa/El_Aaiun": "Africa/El_Aaiun", + "Africa/Freetown": "Africa/Freetown", + "Africa/Gaborone": "Africa/Gaborone", + "Africa/Harare": "Africa/Harare", + "Africa/Johannesburg": "Africa/Johannesburg", + "Africa/Juba": "Africa/Juba", + "Africa/Kampala": "Africa/Kampala", + "Africa/Khartoum": "Africa/Khartoum", + "Africa/Kigali": "Africa/Kigali", + "Africa/Kinshasa": "Africa/Kinshasa", + "Africa/Lagos": "Africa/Lagos", + "Africa/Libreville": "Africa/Libreville", + "Africa/Lome": "Africa/Lome", + "Africa/Luanda": "Africa/Luanda", + "Africa/Lubumbashi": "Africa/Lubumbashi", + "Africa/Lusaka": "Africa/Lusaka", + "Africa/Malabo": "Africa/Malabo", + "Africa/Maputo": "Africa/Maputo", + "Africa/Maseru": "Africa/Maseru", + "Africa/Mbabane": "Africa/Mbabane", + "Africa/Mogadishu": "Africa/Mogadishu", + "Africa/Monrovia": "Africa/Monrovia", + "Africa/Nairobi": "Africa/Nairobi", + "Africa/Ndjamena": "Africa/Ndjamena", + "Africa/Niamey": "Africa/Niamey", + "Africa/Nouakchott": "Africa/Nouakchott", + "Africa/Ouagadougou": "Africa/Ouagadougou", + "Africa/Porto-Novo": "Africa/Porto-Novo", + "Africa/Sao_Tome": "Africa/Sao_Tome", + "Africa/Timbuktu": "Africa/Timbuktu", + "Africa/Tripoli": "Africa/Tripoli", + "Africa/Tunis": "Africa/Tunis", + "Africa/Windhoek": "Africa/Windhoek", + "America/Adak": "America/Adak", + "America/Anchorage": "America/Anchorage", + "America/Anguilla": "America/Anguilla", + "America/Antigua": "America/Antigua", + "America/Araguaina": "America/Araguaina", + "America/Argentina/Buenos_Aires": "America/Argentina/Buenos_Aires", + "America/Argentina/Catamarca": "America/Argentina/Catamarca", + "America/Argentina/ComodRivadavia": "America/Argentina/ComodRivadavia", + "America/Argentina/Cordoba": "America/Argentina/Cordoba", + "America/Argentina/Jujuy": "America/Argentina/Jujuy", + "America/Argentina/La_Rioja": "America/Argentina/La_Rioja", + "America/Argentina/Mendoza": "America/Argentina/Mendoza", + "America/Argentina/Rio_Gallegos": "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta": "America/Argentina/Salta", + "America/Argentina/San_Juan": "America/Argentina/San_Juan", + "America/Argentina/San_Luis": "America/Argentina/San_Luis", + "America/Argentina/Tucuman": "America/Argentina/Tucuman", + "America/Argentina/Ushuaia": "America/Argentina/Ushuaia", + "America/Aruba": "America/Aruba", + "America/Asuncion": "America/Asuncion", + "America/Atikokan": "America/Atikokan", + "America/Atka": "America/Atka", + "America/Bahia": "America/Bahia", + "America/Bahia_Banderas": "America/Bahia_Banderas", + "America/Barbados": "America/Barbados", + "America/Belem": "America/Belem", + "America/Belize": "America/Belize", + "America/Blanc-Sablon": "America/Blanc-Sablon", + "America/Boa_Vista": "America/Boa_Vista", + "America/Bogota": "America/Bogota", + "America/Boise": "America/Boise", + "America/Buenos_Aires": "America/Buenos_Aires", + "America/Cambridge_Bay": "America/Cambridge_Bay", + "America/Campo_Grande": "America/Campo_Grande", + "America/Cancun": "America/Cancun", + "America/Caracas": "America/Caracas", + "America/Catamarca": "America/Catamarca", + "America/Cayenne": "America/Cayenne", + "America/Cayman": "America/Cayman", + "America/Chicago": "America/Chicago", + "America/Chihuahua": "America/Chihuahua", + "America/Coral_Harbour": "America/Coral_Harbour", + "America/Cordoba": "America/Cordoba", + "America/Costa_Rica": "America/Costa_Rica", + "America/Creston": "America/Creston", + "America/Cuiaba": "America/Cuiaba", + "America/Curacao": "America/Curacao", + "America/Danmarkshavn": "America/Danmarkshavn", + "America/Dawson": "America/Dawson", + "America/Dawson_Creek": "America/Dawson_Creek", + "America/Denver": "America/Denver", + "America/Detroit": "America/Detroit", + "America/Dominica": "America/Dominica", + "America/Edmonton": "America/Edmonton", + "America/Eirunepe": "America/Eirunepe", + "America/El_Salvador": "America/El_Salvador", + "America/Ensenada": "America/Ensenada", + "America/Fort_Nelson": "America/Fort_Nelson", + "America/Fort_Wayne": "America/Fort_Wayne", + "America/Fortaleza": "America/Fortaleza", + "America/Glace_Bay": "America/Glace_Bay", + "America/Godthab": "America/Godthab", + "America/Goose_Bay": "America/Goose_Bay", + "America/Grand_Turk": "America/Grand_Turk", + "America/Grenada": "America/Grenada", + "America/Guadeloupe": "America/Guadeloupe", + "America/Guatemala": "America/Guatemala", + "America/Guayaquil": "America/Guayaquil", + "America/Guyana": "America/Guyana", + "America/Halifax": "America/Halifax", + "America/Havana": "America/Havana", + "America/Hermosillo": "America/Hermosillo", + "America/Indiana/Indianapolis": "America/Indiana/Indianapolis", + "America/Indiana/Knox": "America/Indiana/Knox", + "America/Indiana/Marengo": "America/Indiana/Marengo", + "America/Indiana/Petersburg": "America/Indiana/Petersburg", + "America/Indiana/Tell_City": "America/Indiana/Tell_City", + "America/Indiana/Vevay": "America/Indiana/Vevay", + "America/Indiana/Vincennes": "America/Indiana/Vincennes", + "America/Indiana/Winamac": "America/Indiana/Winamac", + "America/Indianapolis": "America/Indianapolis", + "America/Inuvik": "America/Inuvik", + "America/Iqaluit": "America/Iqaluit", + "America/Jamaica": "America/Jamaica", + "America/Jujuy": "America/Jujuy", + "America/Juneau": "America/Juneau", + "America/Kentucky/Louisville": "America/Kentucky/Louisville", + "America/Kentucky/Monticello": "America/Kentucky/Monticello", + "America/Knox_IN": "America/Knox_IN", + "America/Kralendijk": "America/Kralendijk", + "America/La_Paz": "America/La_Paz", + "America/Lima": "America/Lima", + "America/Los_Angeles": "America/Los_Angeles", + "America/Louisville": "America/Louisville", + "America/Lower_Princes": "America/Lower_Princes", + "America/Maceio": "America/Maceio", + "America/Managua": "America/Managua", + "America/Manaus": "America/Manaus", + "America/Marigot": "America/Marigot", + "America/Martinique": "America/Martinique", + "America/Matamoros": "America/Matamoros", + "America/Mazatlan": "America/Mazatlan", + "America/Mendoza": "America/Mendoza", + "America/Menominee": "America/Menominee", + "America/Merida": "America/Merida", + "America/Metlakatla": "America/Metlakatla", + "America/Mexico_City": "America/Mexico_City", + "America/Miquelon": "America/Miquelon", + "America/Moncton": "America/Moncton", + "America/Monterrey": "America/Monterrey", + "America/Montevideo": "America/Montevideo", + "America/Montreal": "America/Montreal", + "America/Montserrat": "America/Montserrat", + "America/Nassau": "America/Nassau", + "America/New_York": "America/New_York", + "America/Nipigon": "America/Nipigon", + "America/Nome": "America/Nome", + "America/Noronha": "America/Noronha", + "America/North_Dakota/Beulah": "America/North_Dakota/Beulah", + "America/North_Dakota/Center": "America/North_Dakota/Center", + "America/North_Dakota/New_Salem": "America/North_Dakota/New_Salem", + "America/Ojinaga": "America/Ojinaga", + "America/Panama": "America/Panama", + "America/Pangnirtung": "America/Pangnirtung", + "America/Paramaribo": "America/Paramaribo", + "America/Phoenix": "America/Phoenix", + "America/Port-au-Prince": "America/Port-au-Prince", + "America/Port_of_Spain": "America/Port_of_Spain", + "America/Porto_Acre": "America/Porto_Acre", + "America/Porto_Velho": "America/Porto_Velho", + "America/Puerto_Rico": "America/Puerto_Rico", + "America/Punta_Arenas": "America/Punta_Arenas", + "America/Rainy_River": "America/Rainy_River", + "America/Rankin_Inlet": "America/Rankin_Inlet", + "America/Recife": "America/Recife", + "America/Regina": "America/Regina", + "America/Resolute": "America/Resolute", + "America/Rio_Branco": "America/Rio_Branco", + "America/Rosario": "America/Rosario", + "America/Santa_Isabel": "America/Santa_Isabel", + "America/Santarem": "America/Santarem", + "America/Santiago": "America/Santiago", + "America/Santo_Domingo": "America/Santo_Domingo", + "America/Sao_Paulo": "America/Sao_Paulo", + "America/Scoresbysund": "America/Scoresbysund", + "America/Shiprock": "America/Shiprock", + "America/Sitka": "America/Sitka", + "America/St_Barthelemy": "America/St_Barthelemy", + "America/St_Johns": "America/St_Johns", + "America/St_Kitts": "America/St_Kitts", + "America/St_Lucia": "America/St_Lucia", + "America/St_Thomas": "America/St_Thomas", + "America/St_Vincent": "America/St_Vincent", + "America/Swift_Current": "America/Swift_Current", + "America/Tegucigalpa": "America/Tegucigalpa", + "America/Thule": "America/Thule", + "America/Thunder_Bay": "America/Thunder_Bay", + "America/Tijuana": "America/Tijuana", + "America/Toronto": "America/Toronto", + "America/Tortola": "America/Tortola", + "America/Vancouver": "America/Vancouver", + "America/Virgin": "America/Virgin", + "America/Whitehorse": "America/Whitehorse", + "America/Winnipeg": "America/Winnipeg", + "America/Yakutat": "America/Yakutat", + "America/Yellowknife": "America/Yellowknife", + "Antarctica/Casey": "Antarctica/Casey", + "Antarctica/Davis": "Antarctica/Davis", + "Antarctica/DumontDUrville": "Antarctica/DumontDUrville", + "Antarctica/Macquarie": "Antarctica/Macquarie", + "Antarctica/Mawson": "Antarctica/Mawson", + "Antarctica/McMurdo": "Antarctica/McMurdo", + "Antarctica/Palmer": "Antarctica/Palmer", + "Antarctica/Rothera": "Antarctica/Rothera", + "Antarctica/South_Pole": "Antarctica/South_Pole", + "Antarctica/Syowa": "Antarctica/Syowa", + "Antarctica/Troll": "Antarctica/Troll", + "Antarctica/Vostok": "Antarctica/Vostok", + "Arctic/Longyearbyen": "Arctic/Longyearbyen", + "Asia/Aden": "Asia/Aden", + "Asia/Almaty": "Asia/Almaty", + "Asia/Amman": "Asia/Amman", + "Asia/Anadyr": "Asia/Anadyr", + "Asia/Aqtau": "Asia/Aqtau", + "Asia/Aqtobe": "Asia/Aqtobe", + "Asia/Ashgabat": "Asia/Ashgabat", + "Asia/Ashkhabad": "Asia/Ashkhabad", + "Asia/Atyrau": "Asia/Atyrau", + "Asia/Baghdad": "Asia/Baghdad", + "Asia/Bahrain": "Asia/Bahrain", + "Asia/Baku": "Asia/Baku", + "Asia/Bangkok": "Asia/Bangkok", + "Asia/Barnaul": "Asia/Barnaul", + "Asia/Beirut": "Asia/Beirut", + "Asia/Bishkek": "Asia/Bishkek", + "Asia/Brunei": "Asia/Brunei", + "Asia/Calcutta": "Asia/Calcutta", + "Asia/Chita": "Asia/Chita", + "Asia/Choibalsan": "Asia/Choibalsan", + "Asia/Chongqing": "Asia/Chongqing", + "Asia/Chungking": "Asia/Chungking", + "Asia/Colombo": "Asia/Colombo", + "Asia/Dacca": "Asia/Dacca", + "Asia/Damascus": "Asia/Damascus", + "Asia/Dhaka": "Asia/Dhaka", + "Asia/Dili": "Asia/Dili", + "Asia/Dubai": "Asia/Dubai", + "Asia/Dushanbe": "Asia/Dushanbe", + "Asia/Famagusta": "Asia/Famagusta", + "Asia/Gaza": "Asia/Gaza", + "Asia/Harbin": "Asia/Harbin", + "Asia/Hebron": "Asia/Hebron", + "Asia/Ho_Chi_Minh": "Asia/Ho_Chi_Minh", + "Asia/Hong_Kong": "Asia/Hong_Kong", + "Asia/Hovd": "Asia/Hovd", + "Asia/Irkutsk": "Asia/Irkutsk", + "Asia/Istanbul": "Asia/Istanbul", + "Asia/Jakarta": "Asia/Jakarta", + "Asia/Jayapura": "Asia/Jayapura", + "Asia/Jerusalem": "Asia/Jerusalem", + "Asia/Kabul": "Asia/Kabul", + "Asia/Kamchatka": "Asia/Kamchatka", + "Asia/Karachi": "Asia/Karachi", + "Asia/Kashgar": "Asia/Kashgar", + "Asia/Kathmandu": "Asia/Kathmandu", + "Asia/Katmandu": "Asia/Katmandu", + "Asia/Khandyga": "Asia/Khandyga", + "Asia/Kolkata": "Asia/Kolkata", + "Asia/Krasnoyarsk": "Asia/Krasnoyarsk", + "Asia/Kuala_Lumpur": "Asia/Kuala_Lumpur", + "Asia/Kuching": "Asia/Kuching", + "Asia/Kuwait": "Asia/Kuwait", + "Asia/Macao": "Asia/Macao", + "Asia/Macau": "Asia/Macau", + "Asia/Magadan": "Asia/Magadan", + "Asia/Makassar": "Asia/Makassar", + "Asia/Manila": "Asia/Manila", + "Asia/Muscat": "Asia/Muscat", + "Asia/Nicosia": "Asia/Nicosia", + "Asia/Novokuznetsk": "Asia/Novokuznetsk", + "Asia/Novosibirsk": "Asia/Novosibirsk", + "Asia/Omsk": "Asia/Omsk", + "Asia/Oral": "Asia/Oral", + "Asia/Phnom_Penh": "Asia/Phnom_Penh", + "Asia/Pontianak": "Asia/Pontianak", + "Asia/Pyongyang": "Asia/Pyongyang", + "Asia/Qatar": "Asia/Qatar", + "Asia/Qostanay": "Asia/Qostanay", + "Asia/Qyzylorda": "Asia/Qyzylorda", + "Asia/Rangoon": "Asia/Rangoon", + "Asia/Riyadh": "Asia/Riyadh", + "Asia/Saigon": "Asia/Saigon", + "Asia/Sakhalin": "Asia/Sakhalin", + "Asia/Samarkand": "Asia/Samarkand", + "Asia/Seoul": "Asia/Seoul", + "Asia/Shanghai": "Asia/Shanghai", + "Asia/Singapore": "Asia/Singapore", + "Asia/Srednekolymsk": "Asia/Srednekolymsk", + "Asia/Taipei": "Asia/Taipei", + "Asia/Tashkent": "Asia/Tashkent", + "Asia/Tbilisi": "Asia/Tbilisi", + "Asia/Tehran": "Asia/Tehran", + "Asia/Tel_Aviv": "Asia/Tel_Aviv", + "Asia/Thimbu": "Asia/Thimbu", + "Asia/Thimphu": "Asia/Thimphu", + "Asia/Tokyo": "Asia/Tokyo", + "Asia/Tomsk": "Asia/Tomsk", + "Asia/Ujung_Pandang": "Asia/Ujung_Pandang", + "Asia/Ulaanbaatar": "Asia/Ulaanbaatar", + "Asia/Ulan_Bator": "Asia/Ulan_Bator", + "Asia/Urumqi": "Asia/Urumqi", + "Asia/Ust-Nera": "Asia/Ust-Nera", + "Asia/Vientiane": "Asia/Vientiane", + "Asia/Vladivostok": "Asia/Vladivostok", + "Asia/Yakutsk": "Asia/Yakutsk", + "Asia/Yangon": "Asia/Yangon", + "Asia/Yekaterinburg": "Asia/Yekaterinburg", + "Asia/Yerevan": "Asia/Yerevan", + "Atlantic/Azores": "Atlantic/Azores", + "Atlantic/Bermuda": "Atlantic/Bermuda", + "Atlantic/Canary": "Atlantic/Canary", + "Atlantic/Cape_Verde": "Atlantic/Cape_Verde", + "Atlantic/Faeroe": "Atlantic/Faeroe", + "Atlantic/Faroe": "Atlantic/Faroe", + "Atlantic/Jan_Mayen": "Atlantic/Jan_Mayen", + "Atlantic/Madeira": "Atlantic/Madeira", + "Atlantic/Reykjavik": "Atlantic/Reykjavik", + "Atlantic/South_Georgia": "Atlantic/South_Georgia", + "Atlantic/St_Helena": "Atlantic/St_Helena", + "Atlantic/Stanley": "Atlantic/Stanley", + "Australia/ACT": "Australia/ACT", + "Australia/Adelaide": "Australia/Adelaide", + "Australia/Brisbane": "Australia/Brisbane", + "Australia/Broken_Hill": "Australia/Broken_Hill", + "Australia/Canberra": "Australia/Canberra", + "Australia/Currie": "Australia/Currie", + "Australia/Darwin": "Australia/Darwin", + "Australia/Eucla": "Australia/Eucla", + "Australia/Hobart": "Australia/Hobart", + "Australia/LHI": "Australia/LHI", + "Australia/Lindeman": "Australia/Lindeman", + "Australia/Lord_Howe": "Australia/Lord_Howe", + "Australia/Melbourne": "Australia/Melbourne", + "Australia/NSW": "Australia/NSW", + "Australia/North": "Australia/North", + "Australia/Perth": "Australia/Perth", + "Australia/Queensland": "Australia/Queensland", + "Australia/South": "Australia/South", + "Australia/Sydney": "Australia/Sydney", + "Australia/Tasmania": "Australia/Tasmania", + "Australia/Victoria": "Australia/Victoria", + "Australia/West": "Australia/West", + "Australia/Yancowinna": "Australia/Yancowinna", + "Brazil/Acre": "Brazil/Acre", + "Brazil/DeNoronha": "Brazil/DeNoronha", + "Brazil/East": "Brazil/East", + "Brazil/West": "Brazil/West", + "CET": "CET", + "CST6CDT": "CST6CDT", + "Canada/Atlantic": "Canada/Atlantic", + "Canada/Central": "Canada/Central", + "Canada/Eastern": "Canada/Eastern", + "Canada/Mountain": "Canada/Mountain", + "Canada/Newfoundland": "Canada/Newfoundland", + "Canada/Pacific": "Canada/Pacific", + "Canada/Saskatchewan": "Canada/Saskatchewan", + "Canada/Yukon": "Canada/Yukon", + "Chile/Continental": "Chile/Continental", + "Chile/EasterIsland": "Chile/EasterIsland", + "Cuba": "Cuba", + "EET": "EET", + "EST": "EST", + "EST5EDT": "EST5EDT", + "Egypt": "Egypt", + "Eire": "Eire", + "Etc/GMT": "Etc/GMT", + "Etc/GMT+0": "Etc/GMT+0", + "Etc/GMT+1": "Etc/GMT+1", + "Etc/GMT+10": "Etc/GMT+10", + "Etc/GMT+11": "Etc/GMT+11", + "Etc/GMT+12": "Etc/GMT+12", + "Etc/GMT+2": "Etc/GMT+2", + "Etc/GMT+3": "Etc/GMT+3", + "Etc/GMT+4": "Etc/GMT+4", + "Etc/GMT+5": "Etc/GMT+5", + "Etc/GMT+6": "Etc/GMT+6", + "Etc/GMT+7": "Etc/GMT+7", + "Etc/GMT+8": "Etc/GMT+8", + "Etc/GMT+9": "Etc/GMT+9", + "Etc/GMT-0": "Etc/GMT-0", + "Etc/GMT-1": "Etc/GMT-1", + "Etc/GMT-10": "Etc/GMT-10", + "Etc/GMT-11": "Etc/GMT-11", + "Etc/GMT-12": "Etc/GMT-12", + "Etc/GMT-13": "Etc/GMT-13", + "Etc/GMT-14": "Etc/GMT-14", + "Etc/GMT-2": "Etc/GMT-2", + "Etc/GMT-3": "Etc/GMT-3", + "Etc/GMT-4": "Etc/GMT-4", + "Etc/GMT-5": "Etc/GMT-5", + "Etc/GMT-6": "Etc/GMT-6", + "Etc/GMT-7": "Etc/GMT-7", + "Etc/GMT-8": "Etc/GMT-8", + "Etc/GMT-9": "Etc/GMT-9", + "Etc/GMT0": "Etc/GMT0", + "Etc/Greenwich": "Etc/Greenwich", + "Etc/UCT": "Etc/UCT", + "Etc/UTC": "Etc/UTC", + "Etc/Universal": "Etc/Universal", + "Etc/Zulu": "Etc/Zulu", + "Europe/Amsterdam": "Europe/Amsterdam", + "Europe/Andorra": "Europe/Andorra", + "Europe/Astrakhan": "Europe/Astrakhan", + "Europe/Athens": "Europe/Athens", + "Europe/Belfast": "Europe/Belfast", + "Europe/Belgrade": "Europe/Belgrade", + "Europe/Berlin": "Europe/Berlin", + "Europe/Bratislava": "Europe/Bratislava", + "Europe/Brussels": "Europe/Brussels", + "Europe/Bucharest": "Europe/Bucharest", + "Europe/Budapest": "Europe/Budapest", + "Europe/Busingen": "Europe/Busingen", + "Europe/Chisinau": "Europe/Chisinau", + "Europe/Copenhagen": "Europe/Copenhagen", + "Europe/Dublin": "Europe/Dublin", + "Europe/Gibraltar": "Europe/Gibraltar", + "Europe/Guernsey": "Europe/Guernsey", + "Europe/Helsinki": "Europe/Helsinki", + "Europe/Isle_of_Man": "Europe/Isle_of_Man", + "Europe/Istanbul": "Europe/Istanbul", + "Europe/Jersey": "Europe/Jersey", + "Europe/Kaliningrad": "Europe/Kaliningrad", + "Europe/Kiev": "Europe/Kiev", + "Europe/Kirov": "Europe/Kirov", + "Europe/Lisbon": "Europe/Lisbon", + "Europe/Ljubljana": "Europe/Ljubljana", + "Europe/London": "Europe/London", + "Europe/Luxembourg": "Europe/Luxembourg", + "Europe/Madrid": "Europe/Madrid", + "Europe/Malta": "Europe/Malta", + "Europe/Mariehamn": "Europe/Mariehamn", + "Europe/Minsk": "Europe/Minsk", + "Europe/Monaco": "Europe/Monaco", + "Europe/Moscow": "Europe/Moscow", + "Europe/Nicosia": "Europe/Nicosia", + "Europe/Oslo": "Europe/Oslo", + "Europe/Paris": "Europe/Paris", + "Europe/Podgorica": "Europe/Podgorica", + "Europe/Prague": "Europe/Prague", + "Europe/Riga": "Europe/Riga", + "Europe/Rome": "Europe/Rome", + "Europe/Samara": "Europe/Samara", + "Europe/San_Marino": "Europe/San_Marino", + "Europe/Sarajevo": "Europe/Sarajevo", + "Europe/Saratov": "Europe/Saratov", + "Europe/Simferopol": "Europe/Simferopol", + "Europe/Skopje": "Europe/Skopje", + "Europe/Sofia": "Europe/Sofia", + "Europe/Stockholm": "Europe/Stockholm", + "Europe/Tallinn": "Europe/Tallinn", + "Europe/Tirane": "Europe/Tirane", + "Europe/Tiraspol": "Europe/Tiraspol", + "Europe/Ulyanovsk": "Europe/Ulyanovsk", + "Europe/Uzhgorod": "Europe/Uzhgorod", + "Europe/Vaduz": "Europe/Vaduz", + "Europe/Vatican": "Europe/Vatican", + "Europe/Vienna": "Europe/Vienna", + "Europe/Vilnius": "Europe/Vilnius", + "Europe/Volgograd": "Europe/Volgograd", + "Europe/Warsaw": "Europe/Warsaw", + "Europe/Zagreb": "Europe/Zagreb", + "Europe/Zaporozhye": "Europe/Zaporozhye", + "Europe/Zurich": "Europe/Zurich", + "GB": "GB", + "GB-Eire": "GB-Eire", + "GMT": "GMT", + "GMT+0": "GMT+0", + "GMT-0": "GMT-0", + "GMT0": "GMT0", + "Greenwich": "Greenwich", + "HST": "HST", + "Hongkong": "Hongkong", + "Iceland": "Iceland", + "Indian/Antananarivo": "Indian/Antananarivo", + "Indian/Chagos": "Indian/Chagos", + "Indian/Christmas": "Indian/Christmas", + "Indian/Cocos": "Indian/Cocos", + "Indian/Comoro": "Indian/Comoro", + "Indian/Kerguelen": "Indian/Kerguelen", + "Indian/Mahe": "Indian/Mahe", + "Indian/Maldives": "Indian/Maldives", + "Indian/Mauritius": "Indian/Mauritius", + "Indian/Mayotte": "Indian/Mayotte", + "Indian/Reunion": "Indian/Reunion", + "Iran": "Iran", + "Israel": "Israel", + "Jamaica": "Jamaica", + "Japan": "Japan", + "Kwajalein": "Kwajalein", + "Libya": "Libya", + "MET": "MET", + "MST": "MST", + "MST7MDT": "MST7MDT", + "Mexico/BajaNorte": "Mexico/BajaNorte", + "Mexico/BajaSur": "Mexico/BajaSur", + "Mexico/General": "Mexico/General", + "NZ": "NZ", + "NZ-CHAT": "NZ-CHAT", + "Navajo": "Navajo", + "PRC": "PRC", + "PST8PDT": "PST8PDT", + "Pacific/Apia": "Pacific/Apia", + "Pacific/Auckland": "Pacific/Auckland", + "Pacific/Bougainville": "Pacific/Bougainville", + "Pacific/Chatham": "Pacific/Chatham", + "Pacific/Chuuk": "Pacific/Chuuk", + "Pacific/Easter": "Pacific/Easter", + "Pacific/Efate": "Pacific/Efate", + "Pacific/Enderbury": "Pacific/Enderbury", + "Pacific/Fakaofo": "Pacific/Fakaofo", + "Pacific/Fiji": "Pacific/Fiji", + "Pacific/Funafuti": "Pacific/Funafuti", + "Pacific/Galapagos": "Pacific/Galapagos", + "Pacific/Gambier": "Pacific/Gambier", + "Pacific/Guadalcanal": "Pacific/Guadalcanal", + "Pacific/Guam": "Pacific/Guam", + "Pacific/Honolulu": "Pacific/Honolulu", + "Pacific/Johnston": "Pacific/Johnston", + "Pacific/Kiritimati": "Pacific/Kiritimati", + "Pacific/Kosrae": "Pacific/Kosrae", + "Pacific/Kwajalein": "Pacific/Kwajalein", + "Pacific/Majuro": "Pacific/Majuro", + "Pacific/Marquesas": "Pacific/Marquesas", + "Pacific/Midway": "Pacific/Midway", + "Pacific/Nauru": "Pacific/Nauru", + "Pacific/Niue": "Pacific/Niue", + "Pacific/Norfolk": "Pacific/Norfolk", + "Pacific/Noumea": "Pacific/Noumea", + "Pacific/Pago_Pago": "Pacific/Pago_Pago", + "Pacific/Palau": "Pacific/Palau", + "Pacific/Pitcairn": "Pacific/Pitcairn", + "Pacific/Pohnpei": "Pacific/Pohnpei", + "Pacific/Ponape": "Pacific/Ponape", + "Pacific/Port_Moresby": "Pacific/Port_Moresby", + "Pacific/Rarotonga": "Pacific/Rarotonga", + "Pacific/Saipan": "Pacific/Saipan", + "Pacific/Samoa": "Pacific/Samoa", + "Pacific/Tahiti": "Pacific/Tahiti", + "Pacific/Tarawa": "Pacific/Tarawa", + "Pacific/Tongatapu": "Pacific/Tongatapu", + "Pacific/Truk": "Pacific/Truk", + "Pacific/Wake": "Pacific/Wake", + "Pacific/Wallis": "Pacific/Wallis", + "Pacific/Yap": "Pacific/Yap", + "Poland": "Poland", + "Portugal": "Portugal", + "ROC": "ROC", + "ROK": "ROK", + "Singapore": "Singapore", + "Turkey": "Turkey", + "UCT": "UCT", + "US/Alaska": "US/Alaska", + "US/Aleutian": "US/Aleutian", + "US/Arizona": "US/Arizona", + "US/Central": "US/Central", + "US/East-Indiana": "US/East-Indiana", + "US/Eastern": "US/Eastern", + "US/Hawaii": "US/Hawaii", + "US/Indiana-Starke": "US/Indiana-Starke", + "US/Michigan": "US/Michigan", + "US/Mountain": "US/Mountain", + "US/Pacific": "US/Pacific", + "US/Pacific-New": "US/Pacific-New", + "US/Samoa": "US/Samoa", + "UTC": "UTC", + "Universal": "Universal", + "W-SU": "W-SU", + "WET": "WET", + "Zulu": "Zulu" + } +} diff --git a/src/assets/i18n/en/organization.json b/src/assets/i18n/en/organization.json index f98d3fe..2028e9b 100644 --- a/src/assets/i18n/en/organization.json +++ b/src/assets/i18n/en/organization.json @@ -1,11 +1,14 @@ { "label": { + "organization": "Organization", "selectedUsers": "Selected Users", "addGroup": "Add Group", "chat": "Chat", "message": "Message", "call": "Call", - "videoConference": "Video Conference" + "videoConference": "Video Conference", + "searchResult": "Search Result", + "sortName": "Name" }, "presence": { "offline": "Offline", @@ -14,5 +17,65 @@ "statusMessage1": "Busy", "statusMessage2": "In conference", "statusMessage3": "In intensive work" + }, + "dialog": { + "title": { + "addGroup": "Add Group" + }, + "confirmAddBuddyForNewGroup": "Do you want to add members after creating '{{targetGroups}}' group?", + "confirmAddBuddyForGroup": "Would you like to add members to the '{{targetGroups}}' group?", + "errorAddBuddyForGroup": "No group name was specified, or no group was selected.", + "button": { + "addUser": "Add Complate" + } + }, + "profile": { + "me": "My profile", + "other": "Profile", + "company": "Company", + "email": "Email", + "lineNumber": "Office", + "mobileNumber": "Mobile", + "department": "Department", + "mytalk": "MyTalk", + "setting": "Setting", + "chat": "Chat", + "sms": "SMS", + "videoConference": "Conference", + "message": "Message", + "notSetNickname": "Not set nickname", + "chatWithUs": "Conversation with us.", + "unreadChat": "Unread conversation", + "notificationBot": "Notification Bot", + "noConversation": "There is no conversation.", + "placeholderForIntro": "Would you like to change the intro message?" + }, + "settings": { + "label": "Settings", + "sub": { + "general": "Genernal", + "notification": "Notification" + }, + "language": { + "messenger": "Language", + "hr": "HR language" + }, + "timezone": "Timezone", + "notification": { + "receival": "Get notified", + "receive": "Receive", + "notReceive": "Not receive", + "method": "Notification method", + "methodTypeSound": "Sound", + "methodTypeAlert": "Alert", + "methodTypeSoundAndAlert": "Sound + Alert", + "settingOfAlertWindow": "Alert | PC notification window exposure time", + "receiveForMobile": "Mobile notifications", + "receiveForMobileTypeAlways": "Always(When canceled, receive mobile notification only when PC version is absent)", + "descriptionReceiveForMobileTypeAlways": "Receive mobile notifications only when PC version is absent on release", + "message": "Message", + "receiveForMessage": "Message notifications", + "receiveForMessageTypePopup": "Alert view when receiving a message" + } } } diff --git a/src/assets/i18n/ko/authentication.json b/src/assets/i18n/ko/authentication.json index 5ef18c6..a3b7138 100644 --- a/src/assets/i18n/ko/authentication.json +++ b/src/assets/i18n/ko/authentication.json @@ -2,13 +2,16 @@ "login": { "labels": { "doLogin": "로그인", + "doLogout": "로그아웃", "rememberMe": "아이디 저장", "autoLogin": "자동 로그인", "instructionsOfLogin": "계정에 로그인 하세요.", "forgotPassword": "비밀번호 찾기", "resetPassword": "비밀번호 초기화", "notesOnUse": "이용시 주의 사항", - "selectCompany": "회사 선택" + "selectCompany": "회사 선택", + "changePassword": "비밀번호 변경", + "privacyPolicy": "개인정보 처리방침" }, "fields": { "company": "회사명", @@ -21,6 +24,53 @@ "requireLoginPw": "비밀번호를 입력해 주세요.", "failed": "로그인에 실패하였습니다.", "attemptsExceeded": "비밀번호 오류 횟수 초과입니다." + }, + "settings": { + "login": "로그인 설정", + "autoStartOnBoot": "Windows 실행 시 자동 실행", + "autoLogin": "실행 시 자동 로그인", + "autoHide": "실행 시 창 숨기기" + } + }, + "password": { + "labels": {}, + "fields": { + "changePassword": "비밀번호 변경", + "currentPassword": "현재 비밀번호", + "newPassword": "신규 비밀번호", + "newPasswordConfirm": "신규 비밀번호 확인" + }, + "placeholder": { + "currentPassword": "현재 비밀번호 입력", + "newPassword": "신규 비밀번호", + "newPasswordConfirm": "신규 비밀번호 확인" + }, + "errors": { + "requireCurrentPassword": "현재 비밀번호를 입력해 주세요", + "notSameWithCurrentPassword": "현재 비밀번호와 일치하지 않습니다", + "sameWithCurrentPassword": "현재 비밀번호와 동일합니다", + "requireNewPassword": "신규 비밀번호를 입력해 주세요", + "requireNewPasswordConfirm": "신규 비밀번호 확인을 입력해 주세요", + "notSameWithNewPassword": "신규 비밀번호와 신규 비밀번호 확인이 다릅니다", + "notContainSpacesForPassword": "비밀번호에는 공백을 입력할 수 없습니다", + "notContainUseridForPassword": "사용자 ID를 비밀번호에 포함할 수 없습니다", + "notContainPhonenumberForPassword": "사용자 휴대폰번호를 비밀번호에 포함할 수 없습니다", + "notAllowedAlphaNumOver3TimesForPassword": "숫자나 문자를 3번이상 반복적으로 사용할 수 없습니다", + "notAllowedConsecutiveAlphaNumOver3TimesForPassword": "연속되는 숫자나 문자를 3번이상 사용할 수 없습니다", + "notSatisfiedCombineForPassword": "문자, 숫자, 특수문자 중 2종류 이상 조합을 해야 합니다", + "minLengthCombineForPassword": "비밀번호는 {{countOfCombine}}가지가 조합된 경우 {{lengthOfPassword}}자를 넘어야 합니다", + "failToChange": "비밀번호 변경에 실패하였습니다." + }, + "notice": { + "condition": "비밀번호 조건", + "condition1": "최소 8자리 이상 : 영어 소문자, 숫자, 특수문자 중 3종류 조합으로 설정 가능", + "condition2": "최소 10자리 이상 : 영어 소문자, 숫자, 특수문자 중 2종류 조합으로 설정 가능", + "condition3": "반복적인 숫자 또는 문자 3자리 이상 포함 시 비밀번호 설정 불가", + "condition4": "연속적인 숫자 또는 문자 3자리 이상 포함 시 비밀번호 설정 불가", + "condition5": "사용자 ID와 휴대폰 번호 포함 시 비밀번호 설정 불가" + }, + "settings": { + "label": "비밀번호" } } } diff --git a/src/assets/i18n/ko/call.json b/src/assets/i18n/ko/call.json index 0967ef4..26d7d24 100644 --- a/src/assets/i18n/ko/call.json +++ b/src/assets/i18n/ko/call.json @@ -1 +1,5 @@ -{} +{ + "settings": { + "label": "통화" + } +} diff --git a/src/assets/i18n/ko/chat.json b/src/assets/i18n/ko/chat.json index f5288d2..96402b5 100644 --- a/src/assets/i18n/ko/chat.json +++ b/src/assets/i18n/ko/chat.json @@ -3,18 +3,28 @@ "searchRoomByName": "대화방명, 대화 참석자 검색", "noRoomUser": "대화상대 없음", "today": "오늘", - "openRoom": "대화방 열기", - "turnOnRoomAlert": "대화방 알람 켜기", - "turnOffRoomAlert": "대화방 알람 끄기", - "exitFromRoom": "대화방 나가기" + "yesterday": "어제", + "noSelectRoom": "선택된 대화방이 없습니다." }, "label": { "chat": "대화", "menu": "메뉴", "search": "검색", - "notificationIsOn": "알림 켜짐", - "notificationIsOff": "알림 꺼짐", - "showRoomUsers": "대화방 참여인원 보기", + "favorite": "즐겨찾기", + "turnOnRoomAlert": "대화방 알람 켜기", + "turnOffRoomAlert": "대화방 알람 끄기", + "data": "자료실", + "image": "이미지", + "video": "동영상", + "file": "파일", + "event": "이벤트", + "selectedRoom": "선택된 대화방", + "showRoomUsers": "대화방 멤버", + "addRoomUsers": "대화방 멤버 추가", + "addGroup": "그룹멤버로 추가", + "roomSetting": "대화방 설정", + "openRoom": "대화방 열기", + "exitFromRoom": "대화방 나가기", "send": "전송", "attachFile": "첨부파일", "attachImage": "첨부이미지", @@ -22,11 +32,34 @@ "imoticon": "이모티콘", "emailSend": "대화내용 메일전송", "translation": "대화내용 번역", - "gams": "+GAMS" + "gams": "+GAMS", + "replayEvent": "답장", + "copyChatText": "복사", + "forwardEventTo": "전달", + "forwardEventToMe": "나에게 전달", + "removeEvent": "삭제", + "recallEvent": "회수", + "openViewer": "뷰어로 열기", + "inputChatMessage": "메시지를 입력하세요", + "translations": { + "translation": "번역", + "targetLanguage": "대상언어", + "noTranslation": "번역없음", + "simpleView": "간략보기", + "preview": "미리보기" + }, + "emailSends": { + "sendAll": "모두에게 보내기", + "sendMe": "나에게 보내기" + }, + "fileSends": { + "fileSend": "파일전송", + "dragHere": "이 영역으로 파일을 드래그 하시면 업로드 됩니다." + } }, "event": { - "inviteToRoomWith": "{{owner}}님이 {{inviter}}님을 초대했습니다.", - "exitFromRoomWith": "{{exitor}}님이 퇴장하셨습니다.", + "inviteToRoomWith": "{{owner}}님이 {{inviter}}님을 초대했습니다.", + "exitFromRoomWith": "{{exitor}}님이 퇴장하셨습니다.", "ejectedFromRoomWith": "{{requester}}님이 {{ejected}}님을 퇴장 시키셨습니다.", "renamedRoomWith": "{{requester}}님이 대화방명을 '{{roomName}}'으로 변경하셨습니다.", "setTimerWith": "{{requester}}님이 타이머를 설정하였습니다. ({{timer}})", @@ -42,32 +75,81 @@ "scheduleTypeDefault": "[이벤트] 조회중..", "scheduleTypePrefix": "[이벤트] ", "scheduleTypeSurfixLeft": " 전 알림", + "showScheduleDetail": "상세 보기", "showPreviousEvents": "이전 대화 보기", - "moreUnreadEventsWith": "안읽은 메시지가 ({{countOfUnread}})개 더 있습니다." + "moreUnreadEventsWith": "안읽은 메시지가 ({{countOfUnread}})개 더 있습니다.", + "isRoomTypeSecret": "비밀 대화방입니다", + "noRecentChat": "최근 대화 없음" }, "errors": { "label": "대화 에러", "inputChatMessage": "대화 내용을 입력해 주세요.", "maxLengthOfMassText": "스티커를 포함할 경우 {{maxLength}}자 이상 보낼 수 없습니다.", - "maxCountOfRoomMemberWith": "{{maxCount}}명 이상 대화할 수 없습니다.", + "maxCountOfRoomMemberWith": "나를 포함하여 {{maxCount}}명 이상 대화할 수 없습니다.", "emptyOpenRoomType": "대화방 타입을 선택해 주세요.", - "translateServerError": "번역하지 못했습니다." + "translateServerError": "번역하지 못했습니다.", + "addBuddyForGroup": "그룹명을 지정하지 않았거나, 선택된 그룹이 없습니다." }, "dialog": { "title": { - "exitFromRoom": "대화방 나가기", - "newChatRoom": "새로운 대화방 추가" + "removeChat": "대화 삭제", + "newChatRoom": "새 대화방 추가", + "forwardTo": "대화 전달", + "ejectFromRoom": "강제 퇴장", + "detail": "전체보기", + "roomNameGuide": "대화방 이름 설정 안내", + "roomTimerGuide": "타이머 설정 안내", + "fileDownloadCheck": "파일 확인자", + "subSelectRoomType": "대화방 유형 선택", + "subSelectUser": "멤버 선택" }, + "confirmRemoveChat": "선택한 메시지를 삭제하시겠습니까?
    삭제된 메시지는 내 대화방에서만 적용되며 상대방의 대화방에서는 삭제되지 않습니다.", + "confirmRecallEvent": "해당 대화를 회수하시겠습니까?
    상대방 대화창에서도 회수됩니다.", "confirmExitFromRoom": "대화방을 나가시겠습니까?
    나가기를 하면 대화내용 및 대화방 정보가 삭제됩니다.", + "confirmAddBuddyForNewGroup": "대화방 멤버를 '{{targetGroups}}' 그룹을 생성 후 추가하시겠습니까?", + "confirmAddBuddyForGroup": "대화방 멤버를 '{{targetGroups}}' 그룹에 추가하시겠습니까?", + "confirmEjectFromRoom": "{{targetMember}} 님을 대화방에서 퇴장 시키겠습니까?", + "confirmSendEventEmailAll": "대화내용을 이메일로 모두에게 보내시겠습니까?", + "confirmSendEventEmailMe": "대화내용을 이메일로 나에게 보내시겠습니까?", "normalRoom": "일반 대화방", "timerRoom": "타이머 대화방", - "normalRoomDescription": "{{maxCount}}명 까지
    참여가 가능합니다.", - "timerRoomDescription": "타이머 설정시
    대화내용이 자동으로 삭제됩니다.", + "normalRoomDescription": "{{maxCount}}명까지
    참여가 가능합니다.", + "timerRoomDescription": "타이머 설정 시 설정한 시간이 지나면
    대화내용이 자동으로 삭제됩니다.", + "roomNameGuideDescription": "대화방 이름을 설정해 보세요.
    대화방 설정 단계에서 이름을 설정하시면 모든 멤버들에게 똑같이 적용됩니다.", + "roomTimerGuideDescription": "비밀 대화방을 개설하였습니다.
    기본 타이머는 24시간이며 24시간 후에 모든 메시지가 삭제됩니다.
    타이머 변경을 원하시면 아래에서 원하시는 시간을 선택하세요.", + "sendEventEmailSuccess": "대화내용을 메일로 전달하였습니다.", + "selectedUserList": "선택한 멤버", + "roomName": "대화방 이름", + "roomNameChangeTarget": "변경 적용 대상자", + "me": "나", + "all": "전체", + "settingTimer": "타이머 설정", + "settingTimerHint": "※ 설정시간이 초과되면 대화내용이 삭제됩니다.", + "group": "그룹", + "roomList": "대화방 리스트", + "searchResult": "검색결과", "button": { + "save": "완료", "cancel": "취소", "previous": "이전", - "selectRoomUser": "대화방 멤버 선택", - "openRoom": "대화방 생성" + "selectRoomUser": "멤버 선택", + "openRoom": "대화방 생성", + "addUser": "추가 완료", + "settingSave": "설정 완료", + "addUserChatRoom": "대화방 멤버 추가", + "addGroupMember": "그룹 멤버로 추가", + "del": "삭제", + "changeFolder": "폴더변경", + "openViewer": "뷰어보기", + "download": "다운로드", + "all": "전체", + "receiveFiles": "받은{{targetObject}}", + "sendFiles": "보낸{{targetObject}}", + "downloaded": "확인", + "downloadNotYet": "미확인" } + }, + "settings": { + "label": "대화" } } diff --git a/src/assets/i18n/ko/common.json b/src/assets/i18n/ko/common.json index e27b753..51d2eb8 100644 --- a/src/assets/i18n/ko/common.json +++ b/src/assets/i18n/ko/common.json @@ -1,31 +1,83 @@ { - "common": { - "messages": { - "no": "아니요", - "yes": "예", - "confirm": "확인" - }, - "units": { - "date": "날짜", - "time": "시간", - "hour": "시", - "hourFrom": "시간", - "minute": "분", - "second": "초", - "persons": "명", - "hourLaterWith": "{{hour}}시간 뒤", - "tomorrowMorning": "내일 아침", - "tomorrowAfternoon": "내일 오후", - "weekLaterWith": "{{week}}주일 뒤", - "monthLaterWith": "{{month}}달 뒤" - }, - "file": { - "errors": { - "failToUpload": "파일 업로드에 실패하였습니다.", - "notSupporedType": "지원하지 않는 파일형식입니다.
    ({{supporedType}})", - "notAcceptableMime": "유효하지 않은 파일 타입입니다.
    ({{supporedType}})", - "oversize": "{{maxSize}}MB 이상 파일을 업로드 할 수 없습니다." - } + "useSpecialCharactor": "특수문자는 -,_ 만 사용할 수 있습니다.", + "messages": { + "no": "아니요", + "yes": "예", + "confirm": "확인", + "select": "선택", + "selectAll": "전체 선택", + "unselect": "선택 해제", + "searching": "검색중", + "cancel": "취소", + "complate": "완료", + "close": "닫기", + "modify": "수정", + "remove": "삭제", + "apply": "적용", + "minimizeWindow": "창 최소화", + "maxmizeWindow": "창 최대화", + "restoreWindow": "창 이전 크기", + "closeWindow": "창 닫기", + "zoomOut": "축소", + "zoomIn": "확대", + "zoomReset": "원본 비율", + "sirWith": "{{sir}}님", + "exit": "종료" + }, + "units": { + "date": "날짜", + "time": "시간", + "hour": "시", + "hourFrom": "시간", + "minute": "분", + "second": "초", + "persons": "명", + "hourLaterWith": "{{hour}}시간 뒤", + "tomorrowMorning": "내일 아침", + "tomorrowAfternoon": "내일 오후", + "weekLaterWith": "{{week}}주일 뒤", + "monthLaterWith": "{{month}}달 뒤" + }, + "file": { + "fileOpen": "파일 열기", + "folderOpen": "폴더 열기", + "download": "파일 다운로드", + "delete": "파일 삭제", + "save": "저장", + "saveAs": "다른 이름으로 저장", + "saveAll": "파일 모두 저장", + "refresh": "새로고침", + "downloading": "다운로드중...", + "errors": { + "failToUpload": "파일 업로드에 실패하였습니다.", + "noPreview": "미리보기를 지원하지 않는 파일입니다.", + "cantPlay": "재생을 지원하지 않는 파일입니다.", + "notSupporedType": "지원하지 않는 파일형식입니다.
    ({{supporedType}})", + "notAcceptableMime": "유효하지 않은 파일 타입입니다.
    ({{supporedType}})", + "oversize": "{{maxSize}}MB 이상 파일을 업로드 할 수 없습니다." } + }, + "player": { + "play": "재생", + "stop": "멈춤" + }, + "notification": { + "titleChatEventArrivedByUser": "{{userInfo}} 님의 메세지.", + "titleChatEventArrived": "메세지가 도착했습니다.", + "titleMessageArrivedByUser": "{{userInfo}} 님의 쪽지.", + "titleMessageArrived": "쪽지가 도착했습니다." + }, + "tooltip": { + "group": "그룹", + "chat": "대화", + "organization": "조직도", + "message": "쪽지", + "call": "전화", + "videoConference": "화상회의", + "mobile": "모바일", + "office": "내선전화", + "exitForcing": "강제퇴장", + "profile": "프로필", + "more": "더보기" } } diff --git a/src/assets/i18n/ko/group.json b/src/assets/i18n/ko/group.json index 049495e..e2efd5f 100644 --- a/src/assets/i18n/ko/group.json +++ b/src/assets/i18n/ko/group.json @@ -1,62 +1,81 @@ { - "label": { - "confirmRemoveBuddy": "선택한 멤버를 삭제하시겠습니까?\n해당 그룹에서만 선택하신 멤버가 삭제됩니다." - }, "category": { "favorite": "즐겨찾기", "default": "기본", "myDept": "소속부서" }, - "moreMenu": { - "show": { - "all": "전체 보기", - "onlineBuddy": "접속한 동료만 보기", - "onOff": "온/오프라인 보기" - }, - "group": { - "addNew": "새 그룹 추가", - "expandMore": "그룹 전체 열기", - "expandLess": "그룹 전체 닫기", - "changeOrder": "그룹 순서 바꾸기", - "startChatWithGroup": "그룹 대화하기", - "sendMessageToGroup": "그룹 쪽지 보내기", - "groupMemberManagement": "그룹 멤버 관리", - "changeGroupName": "그룹 이름 바꾸기", - "removeGroup": "그룹 삭제" - }, - "profile": { - "open": "프로필 보기", - "favorite": "즐겨찾기 설정", - "nickname": "닉네임 설정", - "moveBuddy": "대화상대 이동", - "copyBuddy": "대화상대 복사", - "removeBuddy": "이 그룹에서 삭제" - }, - "confirm": { - "removeGroup": "그룹을 삭제하시겠습니까?
    그룹 멤버는 해당 그룹에서만 삭제됩니다." - }, - "error": { - "label": "그룹 에러", - "requireName": "그룹명은 필수입력입니다." - } + "label": { + "group": "그룹", + "member": "멤버", + "organization": "조직도", + "addNewGroup": "새 그룹 추가", + "existingGroup": "기존 그룹 지정", + "searchResult": "검색결과", + "addMember": "대화상대 추가", + "online": "온라인", + "offline": "오프라인" }, - - "profile": { - "labels": { - "myProfile": "내 프로필", - "company": "회사", - "email": "이메일", - "linePhoneNumber": "사무실", - "mobilePhoneNumber": "핸드폰", - "department": "부서", - "chat": "대화", - "sms": "SMS", - "videoConference": "화상회의", - "message": "쪽지" + "dialog": { + "title": { + "addBuddy": "동료추가", + "removeBuddy": "동료삭제", + "removeGroup": "그룹삭제", + "createGroup": "그룹생성", + "subTitleGroupInfo": "그룹정보 등록", + "subTitleSelectMember": "멤버 선택", + "copyGroup": "그룹 멤버 복사", + "moveGroup": "그룹 멤버 이동", + "managementGroup": "그룹 멤버 관리", + "messageGroup": "그룹쪽지" }, - "fields": { - "intro": "인트로" + "btn": { + "remove": "삭제", + "cancel": "취소", + "compleate": "완료", + "addGroupCompleate": "그룹 추가 완료", + "selectMemberAndComplete": "멤버 선택 후 완료" }, - "errors": {} + "newGroupName": "새로운 그룹명을 입력 해 주세요", + "newNickname": "닉네임을 설정하세요.", + "selectedMember": "선택한 멤버", + "removeBuddyConfirm": "{{targetMember}} 선택한 멤버를 삭제하시겠습니까?
    해당 그룹에서만 선택하신 멤버가 삭제됩니다.", + "removeGroupConfirm": "{{targetGroup}}을 삭제하시겠습니까?
    그룹 멤버는 해당 그룹에서만 삭제됩니다.", + "removeBuddyFromMenu": "{{target}}{{targetGroup}}그룹에서 삭제하시겠습니까?", + "removeBuddyFromProfile": "{{target}}를 그룹에서 삭제하시겠습니까?
    프로필에서 삭제하면 모든 그룹에서 삭제됩니다." + }, + "error": { + "title": { + "default": "에러" + }, + "useOnlyForSpecialCharacter": "특수문자는 {{specialCharacter}}만 사용할 수 있습니다.", + "requireInput": "필수 입력사항입니다.", + "requireGroupName": "그룹명은 필수입력입니다.", + "invalidGroupName": "유효하지 않은 그룹명입니다.", + "notSelectedUser": "선택된 유저가 존재하지 않습니다.", + "bannedWords": "금지단어 [{{bannedWords}}]", + "sameNameExist": "이미 존재하는 그룹명입니다." + }, + "contextMenu": { + "all": "전체 보기", + "onlineBuddy": "접속한 동료만 보기", + "onOffBuddy": "온/오프라인 보기", + "addNewGroup": "새 그룹 추가", + "expandMore": "그룹 전체 열기", + "expandLess": "그룹 전체 닫기", + "changeOrder": "그룹 순서 바꾸기", + "startChatWithGroup": "그룹 대화하기", + "sendMessageToGroup": "그룹 쪽지 보내기", + "groupMemberManagement": "그룹 멤버 관리", + "changeGroupName": "그룹 이름 바꾸기", + "removeGroup": "그룹 삭제", + "profileOpen": "프로필 보기", + "unfavorite": "즐겨찾기 해제", + "addFavorite": "즐겨찾기 등록", + "setNickname": "닉네임 설정", + "copyGroupMember": "그룹 복사", + "moveGroupMember": "그룹 이동", + "moveBuddy": "대화상대 이동", + "copyBuddy": "대화상대 복사", + "removeBuddyFromGroup": "이 그룹에서 삭제" } } diff --git a/src/assets/i18n/ko/locale.json b/src/assets/i18n/ko/locale.json new file mode 100644 index 0000000..66c4c41 --- /dev/null +++ b/src/assets/i18n/ko/locale.json @@ -0,0 +1,602 @@ +{ + "languages": { + "ko": "한국어", + "en": "영어", + "cn": "중국어" + }, + "timezone": { + "Africa/Abidjan": "아프리카/아비 장", + "Africa/Accra": "아프리카/아크라", + "Africa/Addis_Ababa": "아프리카/아디스 아바바", + "Africa/Algiers": "아프리카/알제", + "Africa/Asmara": "아프리카/아스 마라", + "Africa/Asmera": "아프리카/아스 메라", + "Africa/Bamako": "아프리카/바마코", + "Africa/Bangui": "아프리카/반 귀이", + "Africa/Banjul": "아프리카/반줄", + "Africa/Bissau": "아프리카/비사우", + "Africa/Blantyre": "아프리카/블랜 타이어", + "Africa/Brazzaville": "아프리카/브라자빌", + "Africa/Bujumbura": "아프리카/부줌 부라", + "Africa/Cairo": "아프리카/카이로", + "Africa/Casablanca": "아프리카/카사 블랑카", + "Africa/Ceuta": "아프리카/세 우타", + "Africa/Conakry": "아프리카/코나 크리", + "Africa/Dakar": "아프리카/다카르", + "Africa/Dar_es_Salaam": "아프리카/다르 에스 살람", + "Africa/Djibouti": "아프리카/지부티", + "Africa/Douala": "아프리카/두 알라", + "Africa/El_Aaiun": "아프리카/엘 아이 운", + "Africa/Freetown": "아프리카/프리 타운", + "Africa/Gaborone": "아프리카/가보 로네", + "Africa/Harare": "아프리카/해러", + "Africa/Johannesburg": "아프리카/요하네스 버그", + "Africa/Juba": "아프리카/유바", + "Africa/Kampala": "아프리카/캄팔라", + "Africa/Khartoum": "아프리카/하르툼", + "Africa/Kigali": "아프리카/키 갈리", + "Africa/Kinshasa": "아프리카/킨 샤사", + "Africa/Lagos": "아프리카/라고스", + "Africa/Libreville": "아프리카/리버빌", + "Africa/Lome": "아프리카/로마", + "Africa/Luanda": "아프리카/루안다", + "Africa/Lubumbashi": "아프리카/루 붐바시", + "Africa/Lusaka": "아프리카/루사카", + "Africa/Malabo": "아프리카/말라 보", + "Africa/Maputo": "아프리카/마푸토", + "Africa/Maseru": "아프리카/마세 루", + "Africa/Mbabane": "아프리카/음 바바 네", + "Africa/Mogadishu": "아프리카/모가디슈", + "Africa/Monrovia": "아프리카/몬로 비아", + "Africa/Nairobi": "아프리카/나이로비", + "Africa/Ndjamena": "아프리카/냐자 메나", + "Africa/Niamey": "아프리카/니암", + "Africa/Nouakchott": "아프리카/누악 쇼트", + "Africa/Ouagadougou": "아프리카/오아가 두구", + "Africa/Porto-Novo": "아프리카/포르토 노보", + "Africa/Sao_Tome": "아프리카/사 오토메", + "Africa/Timbuktu": "아프리카/팀북투", + "Africa/Tripoli": "아프리카/트리폴리", + "Africa/Tunis": "아프리카/튀니지", + "Africa/Windhoek": "아프리카/빈트 후크", + "America/Adak": "아메리카/아닥", + "America/Anchorage": "아메리카/앵커리지", + "America/Anguilla": "아메리카/앵귈라", + "America/Antigua": "아메리카/안티구아", + "America/Araguaina": "아메리카/아라구아나", + "America/Argentina/Buenos_Aires": "아메리카/아르헨티나/부에노스 아이레스", + "America/Argentina/Catamarca": "아메리카/아르헨티나/카타 마르카", + "America/Argentina/ComodRivadavia": "아메리카/아르헨티나/코모드 리바다비아", + "America/Argentina/Cordoba": "아메리카/아르헨티나/코르도바", + "America/Argentina/Jujuy": "아메리카/아르헨티나/주이", + "America/Argentina/La_Rioja": "아메리카/아르헨티나/라 리오하", + "America/Argentina/Mendoza": "아메리카/아르헨티나/멘도사", + "America/Argentina/Rio_Gallegos": "아메리카/아르헨티나/리오 갈레고스", + "America/Argentina/Salta": "아메리카/아르헨티나/살타", + "America/Argentina/San_Juan": "아메리카/아르헨티나/산후 안", + "America/Argentina/San_Luis": "아메리카/아르헨티나/산 루이스", + "America/Argentina/Tucuman": "아메리카/아르헨티나/투쿠 만", + "America/Argentina/Ushuaia": "아메리카/아르헨티나/우수아", + "America/Aruba": "아메리카/아루바", + "America/Asuncion": "아메리카/아순시온", + "America/Atikokan": "아메리카/아 티코 칸", + "America/Atka": "아메리카/아트카", + "America/Bahia": "아메리카/바이아", + "America/Bahia_Banderas": "아메리카/바이아 반데라스", + "America/Barbados": "아메리카/바베이도스", + "America/Belem": "아메리카/벨렘", + "America/Belize": "아메리카/벨리즈", + "America/Blanc-Sablon": "아메리카/블랑-사블 론", + "America/Boa_Vista": "아메리카/보아 비스타", + "America/Bogota": "아메리카/보고타", + "America/Boise": "아메리카/보이즈", + "America/Buenos_Aires": "아메리카/부에노스 아이레스", + "America/Cambridge_Bay": "아메리카/캠브리지 _ 베이", + "America/Campo_Grande": "아메리카/캄포 그란데", + "America/Cancun": "아메리카/칸쿤", + "America/Caracas": "아메리카/카라카스", + "America/Catamarca": "아메리카/카타 마르카", + "America/Cayenne": "아메리카/카이엔", + "America/Cayman": "아메리카/케이맨", + "America/Chicago": "아메리카/시카고", + "America/Chihuahua": "아메리카/치와와", + "America/Coral_Harbour": "아메리카/코럴 하버", + "America/Cordoba": "아메리카/코르도바", + "America/Costa_Rica": "아메리카/코스타리카", + "America/Creston": "아메리카/크레 스턴", + "America/Cuiaba": "아메리카/쿠이 아바", + "America/Curacao": "아메리카/쿠라 카오", + "America/Danmarkshavn": "아메리카/단마크하븐", + "America/Dawson": "아메리카/도슨", + "America/Dawson_Creek": "아메리카/도슨 크릭", + "America/Denver": "아메리카/덴버", + "America/Detroit": "아메리카/디트로이트", + "America/Dominica": "아메리카/도미니카", + "America/Edmonton": "아메리카/에드먼턴", + "America/Eirunepe": "아메리카/에루네페", + "America/El_Salvador": "아메리카/엘살바도르", + "America/Ensenada": "아메리카/엔 세나다", + "America/Fort_Nelson": "아메리카/포트 넬슨", + "America/Fort_Wayne": "아메리카/포트 웨인", + "America/Fortaleza": "아메리카/포르탈레자", + "America/Glace_Bay": "아메리카/그레이스 베이", + "America/Godthab": "아메리카/고트 하브", + "America/Goose_Bay": "아메리카/구스 베이", + "America/Grand_Turk": "아메리카/그랜드 터크", + "America/Grenada": "아메리카/그레나다", + "America/Guadeloupe": "아메리카/과들루프", + "America/Guatemala": "아메리카/과테말라", + "America/Guayaquil": "아메리카/과야퀼", + "America/Guyana": "아메리카/가이아나", + "America/Halifax": "아메리카/할리팩스", + "America/Havana": "아메리카/하바나", + "America/Hermosillo": "아메리카/헤르모실로", + "America/Indiana/Indianapolis": "아메리카/인디애나/인디애나폴리스", + "America/Indiana/Knox": "아메리카/인디애나/녹스", + "America/Indiana/Marengo": "아메리카/인디애나/마렝고", + "America/Indiana/Petersburg": "아메리카/인디애나/피터스버그", + "America/Indiana/Tell_City": "아메리카/인디애나/텔시티", + "America/Indiana/Vevay": "아메리카/인디애나/비베이", + "America/Indiana/Vincennes": "아메리카/인디애나/빈센테", + "America/Indiana/Winamac": "아메리카/인디애나/윈아멕", + "America/Indianapolis": "아메리카/인디애나폴리스", + "America/Inuvik": "아메리카/이누빅", + "America/Iqaluit": "아메리카/이칼루이트", + "America/Jamaica": "아메리카/자메이카", + "America/Jujuy": "아메리카/주이", + "America/Juneau": "아메리카/주노", + "America/Kentucky/Louisville": "아메리카/켄터키/루이스 빌", + "America/Kentucky/Monticello": "아메리카/켄터키/몬티 첼로", + "America/Knox_IN": "아메리카/녹스 IN", + "America/Kralendijk": "아메리카/크라랜디직", + "America/La_Paz": "아메리카/라 파즈", + "America/Lima": "아메리카/리마", + "America/Los_Angeles": "아메리카/로스 앤젤레스", + "America/Louisville": "아메리카/루이스 빌", + "America/Lower_Princes": "아메리카/하위 프린스", + "America/Maceio": "아메리카/마세 이오", + "America/Managua": "아메리카/마나과", + "America/Manaus": "아메리카/마나우스", + "America/Marigot": "아메리카/마리 고", + "America/Martinique": "아메리카/마르티니크", + "America/Matamoros": "아메리카/마타 모로", + "America/Mazatlan": "아메리카/마자 틀란", + "America/Mendoza": "아메리카/멘도사", + "America/Menominee": "아메리카/메노 민", + "America/Merida": "아메리카/메리다", + "America/Metlakatla": "아메리카/메틀 라카 틀라", + "America/Mexico_City": "아메리카/멕시코시티", + "America/Miquelon": "아메리카/미켈론", + "America/Moncton": "아메리카/몬크톤", + "America/Monterrey": "아메리카/몬테레이", + "America/Montevideo": "아메리카/몬테비데오", + "America/Montreal": "아메리카/몬트리올", + "America/Montserrat": "아메리카/몬체 라트", + "America/Nassau": "아메리카/나소", + "America/New_York": "아메리카/뉴욕", + "America/Nipigon": "아메리카/니피곤", + "America/Nome": "아메리카/노메", + "America/Noronha": "아메리카/노로냐", + "America/North_Dakota/Beulah": "아메리카/노스 다코타/부라", + "America/North_Dakota/Center": "아메리카/노스 다코타/센터", + "America/North_Dakota/New_Salem": "아메리카/노스 다코타/뉴 살렘", + "America/Ojinaga": "아메리카/오지나가", + "America/Panama": "아메리카/파나마", + "America/Pangnirtung": "아메리카/판니르퉁", + "America/Paramaribo": "아메리카/파라마리보", + "America/Phoenix": "아메리카/피닉스", + "America/Port-au-Prince": "아메리카/포트 오 프린스", + "America/Port_of_Spain": "아메리카/포트 오브 스페인", + "America/Porto_Acre": "아메리카/포르토 아크레", + "America/Porto_Velho": "아메리카/포르토 벨호", + "America/Puerto_Rico": "아메리카/푸에르토 리코", + "America/Punta_Arenas": "아메리카/푼타 아레나", + "America/Rainy_River": "아메리카/레이니 리버", + "America/Rankin_Inlet": "아메리카/랜킨 인렛", + "America/Recife": "아메리카/레시페", + "America/Regina": "아메리카/레지나", + "America/Resolute": "아메리카/리졸루트", + "America/Rio_Branco": "아메리카/리오 브랑코", + "America/Rosario": "아메리카/로자리오", + "America/Santa_Isabel": "아메리카/산타 이사벨", + "America/Santarem": "아메리카/산타렘", + "America/Santiago": "아메리카/산티아고", + "America/Santo_Domingo": "아메리카/산토 도밍고", + "America/Sao_Paulo": "아메리카/상파울루", + "America/Scoresbysund": "아메리카/스코어스비순드", + "America/Shiprock": "아메리카/쉽락", + "America/Sitka": "아메리카/싯카", + "America/St_Barthelemy": "아메리카/세인트 바델레미", + "America/St_Johns": "아메리카/세인트 존스", + "America/St_Kitts": "아메리카/세인트 키츠", + "America/St_Lucia": "아메리카/세인트 루시아", + "America/St_Thomas": "아메리카/세인트 토마스", + "America/St_Vincent": "아메리카/세인트 빈센트", + "America/Swift_Current": "아메리카/스위프트 커런트", + "America/Tegucigalpa": "아메리카/테구시갈파", + "America/Thule": "아메리카/툴레", + "America/Thunder_Bay": "아메리카/선더 베이", + "America/Tijuana": "아메리카/티후아나", + "America/Toronto": "아메리카/토론토", + "America/Tortola": "아메리카/토르톨라", + "America/Vancouver": "아메리카/밴쿠버", + "America/Virgin": "아메리카/버진", + "America/Whitehorse": "아메리카/화이트호스", + "America/Winnipeg": "아메리카/위니펙", + "America/Yakutat": "아메리카/야쿠타트", + "America/Yellowknife": "아메리카/옐로나이프", + "Antarctica/Casey": "남극/케이시", + "Antarctica/Davis": "남극/데이비스", + "Antarctica/DumontDUrville": "남극/듀몽듀르빌", + "Antarctica/Macquarie": "남극/맥쿼리", + "Antarctica/Mawson": "남극/모손", + "Antarctica/McMurdo": "남극 대륙/맥머도", + "Antarctica/Palmer": "남극/팔머", + "Antarctica/Rothera": "남극/로 데라", + "Antarctica/South_Pole": "남극/남극", + "Antarctica/Syowa": "남극/요와", + "Antarctica/Troll": "남극/트롤", + "Antarctica/Vostok": "남극/보스 토크", + "Arctic/Longyearbyen": "북극/롱 이어 비엔", + "Asia/Aden": "아시아/아덴", + "Asia/Almaty": "아시아/알마티", + "Asia/Amman": "아시아/암만", + "Asia/Anadyr": "아시아/아나 디르", + "Asia/Aqtau": "아시아/악 타우", + "Asia/Aqtobe": "아시아/악 토베", + "Asia/Ashgabat": "아시아/아시가 바트", + "Asia/Ashkhabad": "아시아/아쉬 카바 드", + "Asia/Atyrau": "아시아/아티 라우", + "Asia/Baghdad": "아시아/바그다드", + "Asia/Bahrain": "아시아/바레인", + "Asia/Baku": "아시아/바쿠", + "Asia/Bangkok": "아시아/방콕", + "Asia/Barnaul": "아시아/바르나울", + "Asia/Beirut": "아시아/베이루트", + "Asia/Bishkek": "아시아/비슈케크", + "Asia/Brunei": "아시아/브루나이", + "Asia/Calcutta": "아시아/캘커타", + "Asia/Chita": "아시아/치타", + "Asia/Choibalsan": "아시아/최발산", + "Asia/Chongqing": "아시아/충칭", + "Asia/Chungking": "아시아/청킹", + "Asia/Colombo": "아시아/콜롬보", + "Asia/Dacca": "아시아/다카", + "Asia/Damascus": "아시아/다마스커스", + "Asia/Dhaka": "아시아/다카", + "Asia/Dili": "아시아/힌디어", + "Asia/Dubai": "아시아/두바이", + "Asia/Dushanbe": "아시아/두샨베", + "Asia/Famagusta": "아시아/파 마구 스타", + "Asia/Gaza": "아시아/가자", + "Asia/Harbin": "아시아/하얼빈", + "Asia/Hebron": "아시아/헤브론", + "Asia/Ho_Chi_Minh": "아시아/호치민", + "Asia/Hong_Kong": "아시아/홍콩", + "Asia/Hovd": "아시아/호브드", + "Asia/Irkutsk": "아시아/이르쿠츠크", + "Asia/Istanbul": "아시아/이스탄불", + "Asia/Jakarta": "아시아/자카르타", + "Asia/Jayapura": "아시아/자야푸라", + "Asia/Jerusalem": "아시아/예루살렘", + "Asia/Kabul": "아시아/카불", + "Asia/Kamchatka": "아시아/캄차카", + "Asia/Karachi": "아시아/카라치", + "Asia/Kashgar": "아시아/카슈가르", + "Asia/Kathmandu": "아시아/카트만두", + "Asia/Katmandu": "아시아/카트만두", + "Asia/Khandyga": "아시아/칸디가", + "Asia/Kolkata": "아시아/콜카타", + "Asia/Krasnoyarsk": "아시아/크라스 노야 르 스크", + "Asia/Kuala_Lumpur": "아시아/쿠알라 룸푸르", + "Asia/Kuching": "아시아/쿠칭", + "Asia/Kuwait": "아시아/쿠웨이트", + "Asia/Macao": "아시아/마카오", + "Asia/Macau": "아시아/마카오", + "Asia/Magadan": "아시아/마가 단", + "Asia/Makassar": "아시아/마카 사르", + "Asia/Manila": "아시아/마닐라", + "Asia/Muscat": "아시아/무스카트", + "Asia/Nicosia": "아시아/니코 시아", + "Asia/Novokuznetsk": "아시아/노보 쿠즈네츠 크", + "Asia/Novosibirsk": "아시아/노보시비르스크", + "Asia/Omsk": "아시아/옴 스크", + "Asia/Oral": "아시아/오랄", + "Asia/Phnom_Penh": "아시아/프놈펜", + "Asia/Pontianak": "아시아/폰티아낙", + "Asia/Pyongyang": "아시아/평양", + "Asia/Qatar": "아시아/카타르", + "Asia/Qostanay": "아시아/쿠스타나이", + "Asia/Qyzylorda": "아시아/키질로르다", + "Asia/Rangoon": "아시아/랑군", + "Asia/Riyadh": "아시아/리야드", + "Asia/Saigon": "아시아/사이공", + "Asia/Sakhalin": "아시아/사할린", + "Asia/Samarkand": "아시아/사마르 칸트", + "Asia/Seoul": "아시아/서울", + "Asia/Shanghai": "아시아/상하이", + "Asia/Singapore": "아시아/싱가포르", + "Asia/Srednekolymsk": "아시아/스레 네 콜림 스크", + "Asia/Taipei": "아시아/타이페이", + "Asia/Tashkent": "아시아/타슈켄트", + "Asia/Tbilisi": "아시아/트빌리시", + "Asia/Tehran": "아시아/테헤란", + "Asia/Tel_Aviv": "아시아/텔아비브", + "Asia/Thimbu": "아시아/팀부", + "Asia/Thimphu": "아시아/팀푸", + "Asia/Tokyo": "아시아/도쿄", + "Asia/Tomsk": "아시아/톰스크", + "Asia/Ujung_Pandang": "아시아/엔드", + "Asia/Ulaanbaatar": "아시아/울란바토르", + "Asia/Ulan_Bator": "아시아/울란바토르", + "Asia/Urumqi": "아시아/우루무치", + "Asia/Ust-Nera": "아시아/우스트네라", + "Asia/Vientiane": "아시아/비엔티안", + "Asia/Vladivostok": "아시아/블라디보스토크", + "Asia/Yakutsk": "아시아/야쿠츠크", + "Asia/Yangon": "아시아/양곤", + "Asia/Yekaterinburg": "아시아/예카테린부르크", + "Asia/Yerevan": "아시아/예레반", + "Atlantic/Azores": "대서양/아조레스", + "Atlantic/Bermuda": "대서양/버뮤다", + "Atlantic/Canary": "대서양/카나리아", + "Atlantic/Cape_Verde": "대서양/카보 베르데", + "Atlantic/Faeroe": "대서양/파에로", + "Atlantic/Faroe": "대서양/파로", + "Atlantic/Jan_Mayen": "대서양/얀 마옌", + "Atlantic/Madeira": "대서양/마데이라", + "Atlantic/Reykjavik": "대서양/레이캬비크", + "Atlantic/South_Georgia": "대서양/사우스 조지아", + "Atlantic/St_Helena": "대서양/세인트 헬레나", + "Atlantic/Stanley": "대서양/스탠리", + "Australia/ACT": "오스트레일리아/ACT", + "Australia/Adelaide": "오스트레일리아/애들레이드", + "Australia/Brisbane": "오스트레일리아/브리즈번", + "Australia/Broken_Hill": "오스트레일리아/브로큰 힐", + "Australia/Canberra": "오스트레일리아/캔버라", + "Australia/Currie": "오스트레일리아/커리", + "Australia/Darwin": "오스트레일리아/다윈", + "Australia/Eucla": "오스트레일리아/유클라", + "Australia/Hobart": "오스트레일리아/호바트", + "Australia/LHI": "오스트레일리아/LHI", + "Australia/Lindeman": "오스트레일리아/린드만", + "Australia/Lord_Howe": "오스트레일리아/로드 호우", + "Australia/Melbourne": "오스트레일리아/멜버른", + "Australia/NSW": "오스트레일리아/NSW", + "Australia/North": "오스트레일리아/북부", + "Australia/Perth": "오스트레일리아/퍼스", + "Australia/Queensland": "오스트레일리아/퀸즐랜드", + "Australia/South": "오스트레일리아/남부", + "Australia/Sydney": "오스트레일리아/시드니", + "Australia/Tasmania": "오스트레일리아/태즈 매니아", + "Australia/Victoria": "오스트레일리아/빅토리아", + "Australia/West": "오스트레일리아/서부", + "Australia/Yancowinna": "오스트레일리아/얀 카우 나", + "Brazil/Acre": "브라질/에이커", + "Brazil/DeNoronha": "브라질/드노르온하", + "Brazil/East": "브라질/동부", + "Brazil/West": "브라질/서부", + "CET": "CET", + "CST6CDT": "CST6CDT", + "Canada/Atlantic": "캐나다/대서양", + "Canada/Central": "캐나다/중앙", + "Canada/Eastern": "캐나다/동부", + "Canada/Mountain": "캐나다/마운틴", + "Canada/Newfoundland": "캐나다/뉴펀들랜드", + "Canada/Pacific": "캐나다/태평양", + "Canada/Saskatchewan": "캐나다/서스캐처원", + "Canada/Yukon": "캐나다/유콘", + "Chile/Continental": "칠레/컨티넨탈", + "Chile/EasterIsland": "칠레/이스터섬", + "Cuba": "쿠바", + "EET": "EET", + "EST": "EST", + "EST5EDT": "EST5EDT", + "Egypt": "이집트", + "Eire": "에이레", + "Etc/GMT": "기타/GMT", + "Etc/GMT+0": "기타/GMT+0", + "Etc/GMT+1": "기타/GMT+1", + "Etc/GMT+10": "기타/GMT+10", + "Etc/GMT+11": "기타/GMT+11", + "Etc/GMT+12": "기타/GMT+12", + "Etc/GMT+2": "기타/GMT+2", + "Etc/GMT+3": "기타/GMT+3", + "Etc/GMT+4": "기타/GMT+4", + "Etc/GMT+5": "기타/GMT+5", + "Etc/GMT+6": "기타/GMT+6", + "Etc/GMT+7": "기타/GMT+7", + "Etc/GMT+8": "기타/GMT+8", + "Etc/GMT+9": "기타/GMT+9", + "Etc/GMT-0": "기타/GMT-0", + "Etc/GMT-1": "기타/GMT-1", + "Etc/GMT-10": "기타/GMT-10", + "Etc/GMT-11": "기타/GMT-11", + "Etc/GMT-12": "기타/GMT-12", + "Etc/GMT-13": "기타/GMT-13", + "Etc/GMT-14": "기타/GMT-14", + "Etc/GMT-2": "기타/GMT-2", + "Etc/GMT-3": "기타/GMT-3", + "Etc/GMT-4": "기타/GMT-4", + "Etc/GMT-5": "기타/GMT-5", + "Etc/GMT-6": "기타/GMT-6", + "Etc/GMT-7": "기타/GMT-7", + "Etc/GMT-8": "기타/GMT-8", + "Etc/GMT-9": "기타/GMT-9", + "Etc/GMT0": "기타/GMT0", + "Etc/Greenwich": "기타/그리니치", + "Etc/UCT": "기타/UCT", + "Etc/UTC": "기타/UTC", + "Etc/Universal": "기타/유니버설", + "Etc/Zulu": "기타/줄루", + "Europe/Amsterdam": "유럽/암스테르담", + "Europe/Andorra": "유럽/안도라", + "Europe/Astrakhan": "유럽/아스트라칸", + "Europe/Athens": "유럽/아테네", + "Europe/Belfast": "유럽/벨파스트", + "Europe/Belgrade": "유럽/베오그라드", + "Europe/Berlin": "유럽/베를린", + "Europe/Bratislava": "유럽/브라티슬라바", + "Europe/Brussels": "유럽/브뤼셀", + "Europe/Bucharest": "유럽/부쿠레슈티", + "Europe/Budapest": "유럽/부다페스트", + "Europe/Busingen": "유럽/빙고", + "Europe/Chisinau": "유럽/치사 나우", + "Europe/Copenhagen": "유럽/코펜하겐", + "Europe/Dublin": "유럽/더블린", + "Europe/Gibraltar": "유럽/지브롤터", + "Europe/Guernsey": "유럽/건지", + "Europe/Helsinki": "유럽/헬싱키", + "Europe/Isle_of_Man": "유럽/아일 오브 맨", + "Europe/Istanbul": "유럽/이스탄불", + "Europe/Jersey": "유럽/저지", + "Europe/Kaliningrad": "유럽/칼리닌그라드", + "Europe/Kiev": "유럽/키예프", + "Europe/Kirov": "유럽/키로프", + "Europe/Lisbon": "유럽/리스본", + "Europe/Ljubljana": "유럽/류블 랴나", + "Europe/London": "유럽/런던", + "Europe/Luxembourg": "유럽/룩셈부르크", + "Europe/Madrid": "유럽/마드리드", + "Europe/Malta": "유럽/몰타", + "Europe/Mariehamn": "유럽/마리에 함", + "Europe/Minsk": "유럽/민스크", + "Europe/Monaco": "유럽/모나코", + "Europe/Moscow": "유럽/모스크바", + "Europe/Nicosia": "유럽/니코 시아", + "Europe/Oslo": "유럽/오슬로", + "Europe/Paris": "유럽/파리", + "Europe/Podgorica": "유럽/포드로 리카", + "Europe/Prague": "유럽/프라하", + "Europe/Riga": "유럽/리가", + "Europe/Rome": "유럽/로마", + "Europe/Samara": "유럽/사마라", + "Europe/San_Marino": "유럽/산 마리노", + "Europe/Sarajevo": "유럽/사라예보", + "Europe/Saratov": "유럽/사라 토프", + "Europe/Simferopol": "유럽/심 페로 폴", + "Europe/Skopje": "유럽/스코 페", + "Europe/Sofia": "유럽/소피아", + "Europe/Stockholm": "유럽/스톡홀름", + "Europe/Tallinn": "유럽/탈린", + "Europe/Tirane": "유럽/티 라네", + "Europe/Tiraspol": "유럽/티라스 폴", + "Europe/Ulyanovsk": "유럽/울리 야놉 스크", + "Europe/Uzhgorod": "유럽/우즈 고로드", + "Europe/Vaduz": "유럽/파두 츠", + "Europe/Vatican": "유럽/바티칸", + "Europe/Vienna": "유럽/비엔나", + "Europe/Vilnius": "유럽/빌니우스", + "Europe/Volgograd": "유럽/볼고그라드", + "Europe/Warsaw": "유럽/바르샤바", + "Europe/Zagreb": "유럽/자그레브", + "Europe/Zaporozhye": "유럽/자포 로제", + "Europe/Zurich": "유럽/취리히", + "GB": "GB", + "GB-Eire": "GB-Eire", + "GMT": "GMT", + "GMT+0": "GMT+0", + "GMT-0": "GMT-0", + "GMT0": "GMT0", + "Greenwich": "그리니치", + "HST": "HST", + "Hongkong": "홍콩", + "Iceland": "아이슬란드", + "Indian/Antananarivo": "인도/안타나나리보", + "Indian/Chagos": "인도/차고 스", + "Indian/Christmas": "인도/크리스마스", + "Indian/Cocos": "인도/코코스", + "Indian/Comoro": "인도/코모로", + "Indian/Kerguelen": "인도/케르겔렌", + "Indian/Mahe": "인도/마헤", + "Indian/Maldives": "인도/몰디브", + "Indian/Mauritius": "인도/모리셔스", + "Indian/Mayotte": "인도/마 요트", + "Indian/Reunion": "인도/리유니언", + "Iran": "이란", + "Israel": "이스라엘", + "Jamaica": "자메이카", + "Japan": "일본", + "Kwajalein": "콰잘 레인", + "Libya": "리비아", + "MET": "MET", + "MST": "MST", + "MST7MDT": "MST7MDT", + "Mexico/BajaNorte": "멕시코/바하 노르 테", + "Mexico/BajaSur": "멕시코/바하 수르", + "Mexico/General": "멕시코/제너럴", + "NZ": "NZ", + "NZ-CHAT": "NZ-CHAT", + "Navajo": "나바호", + "PRC": "PRC", + "PST8PDT": "PST8PDT", + "Pacific/Apia": "태평양/아피아", + "Pacific/Auckland": "태평양/오클랜드", + "Pacific/Bougainville": "태평양/부 겐빌", + "Pacific/Chatham": "태평양/채텀", + "Pacific/Chuuk": "태평양/축", + "Pacific/Easter": "태평양/이스터", + "Pacific/Efate": "태평양/이파테", + "Pacific/Enderbury": "태평양/엔더버리", + "Pacific/Fakaofo": "태평양/파카오포", + "Pacific/Fiji": "태평양/피지", + "Pacific/Funafuti": "태평양/후나후티", + "Pacific/Galapagos": "태평양/갈라파고스", + "Pacific/Gambier": "태평양/겜비어", + "Pacific/Guadalcanal": "태평양/과달카날", + "Pacific/Guam": "태평양/괌", + "Pacific/Honolulu": "태평양/호놀룰루", + "Pacific/Johnston": "태평양/존스턴", + "Pacific/Kiritimati": "태평양/키리 티 마티", + "Pacific/Kosrae": "태평양/코스 라에", + "Pacific/Kwajalein": "태평양/콰자 레인", + "Pacific/Majuro": "태평양/마주로", + "Pacific/Marquesas": "태평양/마르크사", + "Pacific/Midway": "퍼시픽/미드웨이", + "Pacific/Nauru": "태평양/나우루", + "Pacific/Niue": "태평양/니우에", + "Pacific/Norfolk": "태평양/노퍽", + "Pacific/Noumea": "태평양/누메아", + "Pacific/Pago_Pago": "태평양/파고 파고", + "Pacific/Palau": "태평양/팔라우", + "Pacific/Pitcairn": "태평양/핏 케언", + "Pacific/Pohnpei": "태평양/퐁 페이", + "Pacific/Ponape": "태평양/포네이프", + "Pacific/Port_Moresby": "태평양/포트 모르즈비", + "Pacific/Rarotonga": "태평양/로로 통가", + "Pacific/Saipan": "태평양/사이판", + "Pacific/Samoa": "태평양/사모아", + "Pacific/Tahiti": "태평양/타히티", + "Pacific/Tarawa": "태평양/타라와", + "Pacific/Tongatapu": "태평양/통가 타푸", + "Pacific/Truk": "태평양/트럭", + "Pacific/Wake": "퍼시픽/웨이크", + "Pacific/Wallis": "태평양/왈리스", + "Pacific/Yap": "태평양/야프", + "Poland": "폴란드", + "Portugal": "포르투갈", + "ROC": "ROC", + "ROK": "ROK", + "Singapore": "싱가포르", + "Turkey": "터키", + "UCT": "UCT", + "US/Alaska": "미국/알래스카", + "US/Aleutian": "미국/알류 티안", + "US/Arizona": "미국/애리조나", + "US/Central": "미국/중앙", + "US/East-Indiana": "미국/동부 인디애나", + "US/Eastern": "미국/동부", + "US/Hawaii": "미국/하와이", + "US/Indiana-Starke": "미국/인디애나-스타크", + "US/Michigan": "미국/미시간", + "US/Mountain": "미국/마운틴", + "US/Pacific": "미국/태평양", + "US/Pacific-New": "미국/태평양 뉴", + "US/Samoa": "미국/사모아", + "UTC": "UTC", + "Universal": "유니버설", + "W-SU": "W-SU", + "WET": "WET", + "Zulu": "줄루" + } +} diff --git a/src/assets/i18n/ko/organization.json b/src/assets/i18n/ko/organization.json index 2a16749..f963948 100644 --- a/src/assets/i18n/ko/organization.json +++ b/src/assets/i18n/ko/organization.json @@ -1,11 +1,14 @@ { "label": { - "selectedUsers": "선택된 대화상대", + "organization": "조직도", + "selectedUsers": "선택한 대화상대", "addGroup": "그룹추가", "chat": "대화", "message": "쪽지", "call": "전화", - "videoConference": "화상" + "videoConference": "화상", + "searchResult": "검색결과", + "sortName": "이름" }, "presence": { "offline": "오프라인", @@ -14,5 +17,65 @@ "statusMessage1": "다른용무중", "statusMessage2": "회의중", "statusMessage3": "집중근무중" + }, + "dialog": { + "title": { + "addGroup": "그룹추가" + }, + "confirmAddBuddyForNewGroup": "멤버를 '{{targetGroups}}' 그룹을 생성 후 추가하시겠습니까?", + "confirmAddBuddyForGroup": "멤버를 '{{targetGroups}}' 그룹에 추가하시겠습니까?", + "errorAddBuddyForGroup": "그룹명을 지정하지 않았거나, 선택된 그룹이 없습니다.", + "button": { + "addUser": "추가 완료" + } + }, + "profile": { + "me": "내 프로필", + "other": "프로필", + "company": "회사", + "email": "이메일", + "lineNumber": "사무실", + "mobileNumber": "핸드폰", + "department": "부서", + "mytalk": "MyTalk", + "setting": "환경설정", + "chat": "대화", + "sms": "SMS", + "videoConference": "화상회의", + "message": "쪽지", + "notSetNickname": "닉네임 미설정", + "chatWithUs": "함께 참여한 대화", + "unreadChat": "읽지 않은 대화", + "notificationBot": "알림봇", + "noConversation": "대화 내용이 없습니다.", + "placeholderForIntro": "인트로 메시지를 바꿔보세요." + }, + "settings": { + "label": "환경설정", + "sub": { + "general": "기본설정", + "notification": "알림" + }, + "language": { + "messenger": "Messenger 언어설정", + "hr": "인사정보 언어 설정" + }, + "timezone": "시간대 설정", + "notification": { + "receival": "알림 받기", + "receive": "알림 받음", + "notReceive": "알림 받지 않음", + "method": "알림 방법", + "methodTypeSound": "소리", + "methodTypeAlert": "알림창", + "methodTypeSoundAndAlert": "소리 + 알림창", + "settingOfAlertWindow": "알림창 설정 | PC 알림창 노출시간(초)", + "receiveForMobile": "모바일 알림", + "receiveForMobileTypeAlways": "항상 수신(해제 시에는 PC 버전이 부재중인 경우에만 모바일 알림 수신)", + "message": "쪽지", + "receiveForMessage": "쪽지 알림", + "receiveForMessageTypePopup": "쪽지 수신 시 팝업보기", + "descriptionReceiveForMobileTypePopup": "해제 시 쪽지 알림만 받음" + } } } diff --git a/src/assets/images/bg/bg_group_add.png b/src/assets/images/bg/bg_group_add.png new file mode 100644 index 0000000..cbc7499 Binary files /dev/null and b/src/assets/images/bg/bg_group_add.png differ diff --git a/src/assets/images/bg_login.png b/src/assets/images/bg_login.png deleted file mode 100644 index c7ac0f5..0000000 Binary files a/src/assets/images/bg_login.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_doc.png b/src/assets/images/file/icon_talk_doc.png deleted file mode 100644 index 7b70afa..0000000 Binary files a/src/assets/images/file/icon_talk_doc.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_doc_d.png b/src/assets/images/file/icon_talk_doc_d.png deleted file mode 100644 index 2742ccf..0000000 Binary files a/src/assets/images/file/icon_talk_doc_d.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_exe.png b/src/assets/images/file/icon_talk_exe.png deleted file mode 100644 index d60f861..0000000 Binary files a/src/assets/images/file/icon_talk_exe.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_exe_d.png b/src/assets/images/file/icon_talk_exe_d.png deleted file mode 100644 index 218da21..0000000 Binary files a/src/assets/images/file/icon_talk_exe_d.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_file.png b/src/assets/images/file/icon_talk_file.png deleted file mode 100644 index 72cecf6..0000000 Binary files a/src/assets/images/file/icon_talk_file.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_file_d.png b/src/assets/images/file/icon_talk_file_d.png deleted file mode 100644 index d11676a..0000000 Binary files a/src/assets/images/file/icon_talk_file_d.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_hwp.png b/src/assets/images/file/icon_talk_hwp.png deleted file mode 100644 index 0fb4dfd..0000000 Binary files a/src/assets/images/file/icon_talk_hwp.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_hwp_d.png b/src/assets/images/file/icon_talk_hwp_d.png deleted file mode 100644 index ef7effd..0000000 Binary files a/src/assets/images/file/icon_talk_hwp_d.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_img.png b/src/assets/images/file/icon_talk_img.png deleted file mode 100644 index f36fd8a..0000000 Binary files a/src/assets/images/file/icon_talk_img.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_img_d.png b/src/assets/images/file/icon_talk_img_d.png deleted file mode 100644 index 8dc5eef..0000000 Binary files a/src/assets/images/file/icon_talk_img_d.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_ppt.png b/src/assets/images/file/icon_talk_ppt.png deleted file mode 100644 index 9e7659f..0000000 Binary files a/src/assets/images/file/icon_talk_ppt.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_ppt_d.png b/src/assets/images/file/icon_talk_ppt_d.png deleted file mode 100644 index 1a9b34c..0000000 Binary files a/src/assets/images/file/icon_talk_ppt_d.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_video.png b/src/assets/images/file/icon_talk_video.png deleted file mode 100644 index d78a485..0000000 Binary files a/src/assets/images/file/icon_talk_video.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_video_d.png b/src/assets/images/file/icon_talk_video_d.png deleted file mode 100644 index 8598815..0000000 Binary files a/src/assets/images/file/icon_talk_video_d.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_xls.png b/src/assets/images/file/icon_talk_xls.png deleted file mode 100644 index dca1bae..0000000 Binary files a/src/assets/images/file/icon_talk_xls.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_xls_d.png b/src/assets/images/file/icon_talk_xls_d.png deleted file mode 100644 index 5b5d734..0000000 Binary files a/src/assets/images/file/icon_talk_xls_d.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_zip.png b/src/assets/images/file/icon_talk_zip.png deleted file mode 100644 index bd0ebcf..0000000 Binary files a/src/assets/images/file/icon_talk_zip.png and /dev/null differ diff --git a/src/assets/images/file/icon_talk_zip_d.png b/src/assets/images/file/icon_talk_zip_d.png deleted file mode 100644 index 2e51067..0000000 Binary files a/src/assets/images/file/icon_talk_zip_d.png and /dev/null differ diff --git a/src/assets/images/ico/btn_checked_w72.svg b/src/assets/images/ico/btn_checked_w72.svg new file mode 100644 index 0000000..c10aa70 --- /dev/null +++ b/src/assets/images/ico/btn_checked_w72.svg @@ -0,0 +1,24 @@ + +svg + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + enable-background="new 0 0 24 24" + id="Layer_1" + version="1.0" + viewBox="0 0 24 24" + xml:space="preserve" + > + + diff --git a/src/assets/images/ico/btn_icon_chat_capy_g18.svg b/src/assets/images/ico/btn_icon_chat_capy_g18.svg new file mode 100644 index 0000000..0b60675 --- /dev/null +++ b/src/assets/images/ico/btn_icon_chat_capy_g18.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/assets/images/ico/btn_icon_chat_delete_g18.svg b/src/assets/images/ico/btn_icon_chat_delete_g18.svg new file mode 100644 index 0000000..a160dc4 --- /dev/null +++ b/src/assets/images/ico/btn_icon_chat_delete_g18.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/src/assets/images/ico/btn_icon_chat_me_g18.svg b/src/assets/images/ico/btn_icon_chat_me_g18.svg new file mode 100644 index 0000000..373982f --- /dev/null +++ b/src/assets/images/ico/btn_icon_chat_me_g18.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + m + + + e + + diff --git a/src/assets/images/ico/btn_icon_chat_relay0_g18.svg b/src/assets/images/ico/btn_icon_chat_relay0_g18.svg new file mode 100644 index 0000000..19a5267 --- /dev/null +++ b/src/assets/images/ico/btn_icon_chat_relay0_g18.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/ico/btn_icon_chat_relay_g18.svg b/src/assets/images/ico/btn_icon_chat_relay_g18.svg new file mode 100644 index 0000000..baf9a0a --- /dev/null +++ b/src/assets/images/ico/btn_icon_chat_relay_g18.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/ico/btn_icon_chat_undo_g18.svg b/src/assets/images/ico/btn_icon_chat_undo_g18.svg new file mode 100644 index 0000000..e6fbfbc --- /dev/null +++ b/src/assets/images/ico/btn_icon_chat_undo_g18.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/images/ico/btn_list_exit_a24.svg b/src/assets/images/ico/btn_list_exit_a24.svg new file mode 100644 index 0000000..eec0e86 --- /dev/null +++ b/src/assets/images/ico/btn_list_exit_a24.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/src/assets/images/ico/btn_list_profile_set_a24.svg b/src/assets/images/ico/btn_list_profile_set_a24.svg new file mode 100644 index 0000000..8dfcf7e --- /dev/null +++ b/src/assets/images/ico/btn_list_profile_set_a24.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/images/ico/btn_list_setting_a24.svg b/src/assets/images/ico/btn_list_setting_a24.svg new file mode 100644 index 0000000..703d423 --- /dev/null +++ b/src/assets/images/ico/btn_list_setting_a24.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/assets/images/ico/icon_alert_info_a50.svg b/src/assets/images/ico/icon_alert_info_a50.svg new file mode 100644 index 0000000..831e2b6 --- /dev/null +++ b/src/assets/images/ico/icon_alert_info_a50.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/src/assets/images/ico/icon_normal_chat_g60.svg b/src/assets/images/ico/icon_normal_chat_g60.svg new file mode 100644 index 0000000..52b2c08 --- /dev/null +++ b/src/assets/images/ico/icon_normal_chat_g60.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/ico/icon_timer_chat_g60.svg b/src/assets/images/ico/icon_timer_chat_g60.svg new file mode 100644 index 0000000..d0ba60e --- /dev/null +++ b/src/assets/images/ico/icon_timer_chat_g60.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/ico/img_coming_soon.png b/src/assets/images/ico/img_coming_soon.png new file mode 100644 index 0000000..80ee6e6 Binary files /dev/null and b/src/assets/images/ico/img_coming_soon.png differ diff --git a/src/assets/images/ico/img_coming_soon.svg b/src/assets/images/ico/img_coming_soon.svg new file mode 100644 index 0000000..629ac41 --- /dev/null +++ b/src/assets/images/ico/img_coming_soon.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/ico/img_nophoto.svg b/src/assets/images/ico/img_nophoto.svg index d90ebec..396deb3 100644 --- a/src/assets/images/ico/img_nophoto.svg +++ b/src/assets/images/ico/img_nophoto.svg @@ -4,26 +4,26 @@ - + - + - + - - - + + + diff --git a/src/assets/images/ico/img_nophoto_multiple.svg b/src/assets/images/ico/img_nophoto_multiple.svg new file mode 100644 index 0000000..10e233d --- /dev/null +++ b/src/assets/images/ico/img_nophoto_multiple.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/img_banner01.png b/src/assets/images/img_banner01.png new file mode 100644 index 0000000..99b5b63 Binary files /dev/null and b/src/assets/images/img_banner01.png differ diff --git a/src/assets/images/img_banner01_2x.png b/src/assets/images/img_banner01_2x.png new file mode 100644 index 0000000..7208f63 Binary files /dev/null and b/src/assets/images/img_banner01_2x.png differ diff --git a/src/assets/images/img_groupphoto_36.png b/src/assets/images/img_groupphoto_36.png deleted file mode 100644 index 1b98d1e..0000000 Binary files a/src/assets/images/img_groupphoto_36.png and /dev/null differ diff --git a/src/assets/images/img_groupphoto_80.png b/src/assets/images/img_groupphoto_80.png deleted file mode 100644 index 2f6e938..0000000 Binary files a/src/assets/images/img_groupphoto_80.png and /dev/null differ diff --git a/src/assets/images/img_nophoto_30.png b/src/assets/images/img_nophoto_30.png deleted file mode 100644 index 0db06d2..0000000 Binary files a/src/assets/images/img_nophoto_30.png and /dev/null differ diff --git a/src/assets/images/img_nophoto_36.png b/src/assets/images/img_nophoto_36.png deleted file mode 100644 index f4feed1..0000000 Binary files a/src/assets/images/img_nophoto_36.png and /dev/null differ diff --git a/src/assets/images/img_nophoto_50.png b/src/assets/images/img_nophoto_50.png deleted file mode 100644 index 029f81b..0000000 Binary files a/src/assets/images/img_nophoto_50.png and /dev/null differ diff --git a/src/assets/images/img_nophoto_70.png b/src/assets/images/img_nophoto_70.png deleted file mode 100644 index 2840dd9..0000000 Binary files a/src/assets/images/img_nophoto_70.png and /dev/null differ diff --git a/src/assets/images/img_nophoto_88.png b/src/assets/images/img_nophoto_88.png deleted file mode 100644 index 73205f6..0000000 Binary files a/src/assets/images/img_nophoto_88.png and /dev/null differ diff --git a/src/assets/images/login/bg_login01.png b/src/assets/images/login/bg_login01.png deleted file mode 100644 index 6f1db65..0000000 Binary files a/src/assets/images/login/bg_login01.png and /dev/null differ diff --git a/src/assets/images/login/bg_login02.png b/src/assets/images/login/bg_login02.png deleted file mode 100644 index 3a47579..0000000 Binary files a/src/assets/images/login/bg_login02.png and /dev/null differ diff --git a/src/assets/images/login/bg_login03.png b/src/assets/images/login/bg_login03.png deleted file mode 100644 index 1dad5f2..0000000 Binary files a/src/assets/images/login/bg_login03.png and /dev/null differ diff --git a/src/assets/images/logo/bg_logo_intro.png b/src/assets/images/logo/bg_logo_intro.png deleted file mode 100644 index 29291a2..0000000 Binary files a/src/assets/images/logo/bg_logo_intro.png and /dev/null differ diff --git a/src/assets/images/logo/bg_logo_login.png b/src/assets/images/logo/bg_logo_login.png deleted file mode 100644 index f180364..0000000 Binary files a/src/assets/images/logo/bg_logo_login.png and /dev/null differ diff --git a/src/assets/images/logo/logo_topbar.png b/src/assets/images/logo/logo_topbar.png deleted file mode 100644 index 001c8e3..0000000 Binary files a/src/assets/images/logo/logo_topbar.png and /dev/null differ diff --git a/src/assets/images/message/thumb_default.png b/src/assets/images/message/thumb_default.png deleted file mode 100644 index cde8145..0000000 Binary files a/src/assets/images/message/thumb_default.png and /dev/null differ diff --git a/src/assets/images/theme/theme-default.png b/src/assets/images/theme/theme-default.png deleted file mode 100644 index 2353d3d..0000000 Binary files a/src/assets/images/theme/theme-default.png and /dev/null differ diff --git a/src/assets/images/theme/theme-lgRed.png b/src/assets/images/theme/theme-lgRed.png deleted file mode 100644 index d227a5f..0000000 Binary files a/src/assets/images/theme/theme-lgRed.png and /dev/null differ diff --git a/src/assets/scss/_fonts.scss b/src/assets/scss/_fonts.scss index cc1ed45..5b593af 100644 --- a/src/assets/scss/_fonts.scss +++ b/src/assets/scss/_fonts.scss @@ -49,3 +49,59 @@ font-weight: normal; font-style: normal; } + +//Material Icons Outlined +// @font-face { +// font-family: 'Material Icons Outlined'; +// font-style: normal; +// font-weight: 400; +// src: url(https://fonts.gstatic.com/s/materialiconsoutlined/v19/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUce.woff2) +// format('woff2'); +// } + +/* fallback */ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url('assets/fonts/materialicons/materialicons-v52.woff2') format('woff2'), + url('assets/fonts/materialicons/materialicons-v52.woff') format('woff'); +} +/* fallback */ +@font-face { + font-family: 'Material Icons Outlined'; + font-style: normal; + font-weight: 400; + src: url('assets/fonts/materialicons/materialiconsoutlined-v21.woff2') + format('woff2'), + url('assets/fonts/materialicons/materialiconsoutlined-v21.woff') + format('woff'); +} +/* fallback */ +@font-face { + font-family: 'Material Icons Round'; + font-style: normal; + font-weight: 400; + src: url('assets/fonts/materialicons/materialiconsround-v21.woff2') + format('woff2'), + url('assets/fonts/materialicons/materialiconsround-v21.woff') format('woff'); +} +/* fallback */ +@font-face { + font-family: 'Material Icons Sharp'; + font-style: normal; + font-weight: 400; + src: url('assets/fonts/materialicons/materialiconssharp-v22.woff2') + format('woff2'), + url('assets/fonts/materialicons/materialiconssharp-v22.woff') format('woff'); +} +/* fallback */ +@font-face { + font-family: 'Material Icons Two Tone'; + font-style: normal; + font-weight: 400; + src: url('assets/fonts/materialicons/materialiconstwotone-v20.woff2') + format('woff2'), + url('assets/fonts/materialicons/materialiconstwotone-v20.woff') + format('woff'); +} diff --git a/src/assets/scss/components.scss b/src/assets/scss/components.scss deleted file mode 100644 index f11d7c4..0000000 --- a/src/assets/scss/components.scss +++ /dev/null @@ -1,11 +0,0 @@ -// Material theming tools -@import '~@angular/material/theming'; - -// Include core Angular Material styles -@include mat-core(); - -// Partials -@import 'partials/material-ui'; - -//creative -@import 'global/default'; diff --git a/src/assets/scss/global/_default.scss b/src/assets/scss/global/_default.scss index 4acfcd8..0a8a97e 100644 --- a/src/assets/scss/global/_default.scss +++ b/src/assets/scss/global/_default.scss @@ -32,6 +32,10 @@ dd { box-sizing: border-box; } +em { + font-style: normal; +} + .blind { font: 0/0 '돋움', dotum; } @@ -84,3 +88,254 @@ body { display: none; } } + +// Cursor Extend +%ucapCursorDefault { + cursor: default; +} +%ucapCursorPointer { + cursor: pointer; +} +%ucapCursorHelp { + cursor: help; +} +%ucapCursorNo { + cursor: no-drop; +} +%ucapCursorNot { + cursor: not-allowed; +} +%ucapCursorGress { + cursor: progress; +} +%ucapCursorEresize { + cursor: e-resize; +} + +// Sub title +.subtitle2 { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + height: 34px; + @include font-family(LG Smart, normal); + font-size: 1.2em; + font-weight: 600; + line-height: 24px; + color: #f83772; + &.not-first-subtit { + margin-top: 48px; + } + &::before { + content: ''; + width: 20px; + height: 3px; + display: flex; + background-color: $lipstick; + } +} + +//top toolbar +.ucap-title-bar-container { + display: flex; + .ucap-title-bar-controls { + flex-grow: 0; + } + .ucap-title-bar-controls-windows { + display: flex; + flex-direction: row; + align-items: center; + padding-right: 10px; + button { + & + button { + margin-left: 6px !important; + } + } + .ucap-title-bar-windows-button { + width: 30px !important; + height: 30px !important; + color: #999 !important; + opacity: 1 !important; + cursor: pointer; + svg { + width: 12px; + height: 12px; + } + &.ucap-title-bar-windows-button-minimize { + &:hover { + background-color: #ccc !important; + color: #fff !important; + } + svg { + vertical-align: text-bottom; + } + } + &.ucap-title-bar-windows-button-maximize { + &:hover { + background-color: #ccc !important; + color: #fff !important; + } + } + &.ucap-title-bar-windows-button-close { + &:hover { + background-color: $lipstick !important; + color: #fff !important; + &:active { + background-color: $lipstick !important; + color: #fff !important; + } + } + } + } + } + .ucap-title-bar-controls-mac { + position: fixed; + top: 0; + left: 0; + z-index: 100; + height: 40px !important; + display: flex; + flex-direction: row; + align-items: center; + &::before { + content: ''; + width: 59px; + height: 40px; + background-color: #f1f2f6; + position: fixed; + top: 0; + left: 0; + z-index: 0; + } + .ucap-title-bar-mac-button { + margin: 0 2.5px; + &.ucap-title-bar-mac-button-close { + margin-left: 7px !important; + } + } + } +} + +//Top toolbar Profile panel +.profile-menu-panel { + width: 300px; + //height: 450px; + border: 1px solid #999; + .mat-menu-content { + padding-bottom: 0 !important; + } +} + +//Setting panel +.settings-dialog-panel { + max-width: 100% !important; + height: 735px; + @include screen(xs) { + height: 100%; + max-width: 100% !important; + width: 100%; + } + .mat-tab-group { + height: 100%; + .mat-tab-header { + flex-basis: 50px; + } + .mat-tab-body-wrapper { + flex: 1 1 auto; + height: 100%; + overflow-y: auto; + overflow-x: hidden; + //대화 - 다운로드 폴더 + .sub-set-content { + .input-set-obj { + .mat-form-field-wrapper { + .mat-form-field-flex { + .mat-form-field-suffix { + align-self: center; + .mat-icon-button { + font-size: 24px; + width: 24px; + height: 24px; + line-height: 24px; + } + } + } + } + } + } + .btn-folder-first { + flex: 0 0 108px; + margin-left: 10px; + align-self: baseline; + @include screen(xs) { + flex: 0 0 30px; + align-self: flex-end; + } + } + } + } +} + +//Viewer +.ucap-file-viewer-container { + .ucap-binary-viewer-container { + .ucap-binary-viewer-header { + .ucap-binary-viewer-icon { + flex: 0 0 20px; + } + .ucap-binary-viewer-title { + @include wordBreak(); + @include ellipsis-column(1); + line-break: anywhere; + } + } + .ucap-binary-viewer-body { + .ucap-binary-viewer-content-wrapper { + @include screen(mid) { + flex-direction: column !important; + } + @include screen(xl) { + flex-direction: row !important; + } + } + } + } + .mat-icon-button { + color: #999; + } +} +.ucap-media-viewer-container { + .ucap-image-viewer-container { + .ucap-image-viewer-header { + .ucap-image-viewer-icon { + flex: 0 0 21px; + } + .ucap-image-viewer-title { + @include wordBreak(); + @include ellipsis-column(1); + line-break: anywhere; + } + } + .ucap-image-viewer-body { + .ucap-image-viewer-image-wrapper { + background-color: rgba(0, 0, 0, 0) !important; + } + } + } + .ucap-video-viewer-container { + .ucap-video-viewer-header { + .ucap-video-viewer-icon { + flex: 0 0 24px; + } + .ucap-video-viewer-title { + @include wordBreak(); + @include ellipsis-column(1); + line-break: anywhere; + } + } + } + .mat-icon-button { + color: #999; + } +} diff --git a/src/assets/scss/global/_material-ui.scss b/src/assets/scss/global/_material-ui.scss index ec6a358..182d02e 100644 --- a/src/assets/scss/global/_material-ui.scss +++ b/src/assets/scss/global/_material-ui.scss @@ -13,106 +13,167 @@ $background: map-get($theme, background); $foreground: map-get($theme, foreground); - $gradient-darkest: mat-color($accent, G900); - $gradient-light: mat-color($accent, G100); + //$gradient-darkest: mat-color($accent, G900); + //$gradient-light: mat-color($accent, G100); - //basic - .bg-primary-darkest { - background: mat-color($primary, 900); - color: mat-color($primary, default-contrast); - } - .bg-primary-dark { - background: mat-color($primary, 900); - color: mat-color($primary, default-contrast); - } - .bg-primary-light { - background: mat-color($primary, 300); - color: mat-color($primary, default-contrast); - } - .bg-primary-color { - background: mat-color($primary); - color: mat-color($primary, default-contrast); - } - .bg-accent-darkest { - background: mat-color($accent, 800); - color: mat-color($primary, default-contrast); - } - .bg-accent-dark { - background: mat-color($accent, B200); - color: mat-color($primary, default-contrast); - } - .bg-accent-light { - background: mat-color($accent, 300); - color: mat-color($primary, default-contrast); - } - .bg-accent-bright { - background-color: mat-color($accent, 100); - } - .bg-accent-brightest { - background: mat-color($accent, 50); - color: mat-color($primary, $dark-primary-text); - } - .bg-accent-color { - background: mat-color($accent); - color: mat-color($accent, default-contrast); - } - .bg-warn-color { - background-color: mat-color($warn, 300); - } - .bg-warn-darkest { - background-color: mat-color($warn, 600); - } - .text-primary-light { - color: mat-color($primary, 500); - } - .text-primary-color { - color: mat-color($primary); - } - .text-accent-dark { - color: mat-color($accent, 600); - } - .text-accent-darkest { - color: mat-color($accent, 800); - } - .text-accent-color { - color: mat-color($accent); - } - .text-warn-color { - color: mat-color($warn, 800); - } - .border-primary-color { - border-color: 1px solid mat-color($primary); - } - .border-accent-bright { - border-color: mat-color($accent, 300); - } - .border-accent-color { - border-color: 1px solid mat-color($accent); - } - .stroke-accent-darkest { - stroke: mat-color($accent, 800); - } - .border-warn-color { - border: mat-color($warn); - } - .stroke-warn-color { - stroke: mat-color($warn, 900); + html.cdk-global-scrollblock { + overflow: hidden !important; } + // sass 정의 .mat-toolbar { background-color: mat-color($accent, B100); } + ///// gnb .gnb .mat-toolbar { - background-color: $gray-ref0; + background-color: #f1f2f6; } - + /* .main-container { border-color: mat-color($accent, B100); } .global-menu { background-color: mat-color($accent, B100); } + */ + //[start] global-menu + + .global-menu { + //display: flex; + //flex-direction: row; + .mat-tab-header { + border-bottom: none !important; + width: 100%; + } + .mat-tab-label-container { + .mat-tab-list { + .mat-tab-labels { + display: flex; + flex-flow: column; + justify-content: space-around; + height: 272px; + border-bottom: none; + + .mat-tab-label { + width: 100%; + height: 32px; + padding: 0; + min-width: 0 !important; + .mat-tab-label-content { + .icon-item { + display: inline-flex; + width: 32px; + height: 32px; + border-radius: 50%; + justify-content: center; + align-items: center; + //transform: scale(0.9); + transition: transform 0.3s cubic-bezier(0.4, 0, 0, 1); + + svg { + //width: 24px; + //height: 24px; + stroke: $gray-re9; + stroke-width: 2; + stroke-linecap: square; + stroke-linejoin: miter; + fill: none; + g { + &#icon_gnb_organiztion_g32 { + .prefix__cls-1, + .prefix__cls-4 { + fill: none; + } + .prefix__cls-1 { + stroke: #999; + stroke-width: 2px; + } + .prefix__cls-3 { + stroke: none; + } + } + &#icon_gnb_message_g32 { + .prefix__cls-1 { + fill: none; + stroke: #999; + stroke-width: 2px; + stroke-linejoin: round; + } + } + } + } + .mat-badge-content { + right: -9px !important; + border: 1px solid #ffbf2a; + //width: 24px; + //height: 24px; + box-sizing: content-box; + top: -10px !important; + } + } + } + &.mat-tab-label-active { + opacity: 0; + svg { + stroke: #fff !important; + g { + &#prefix_23, + &#icon_gnb_chat_g32, + &#icon_gnb_call_g32 { + path { + &:nth-child(2) { + fill: #fff !important; + } + } + } + &#icon_gnb_organiztion_g32 { + .prefix__cls-1 { + stroke: #fff !important; + } + path { + &:nth-last-of-type(2) { + stroke: #fff !important; + } + } + } + &#icon_gnb_message_g32 { + .prefix__cls-1 { + stroke: #fff !important; + } + path { + &:nth-child(3) { + stroke: #fff !important; + } + } + } + } + } + } + &[aria-selected='true'] { + opacity: 1; + .mat-tab-label-content { + .icon-item { + transform: scale(1); + } + } + } + } + } + .mat-ink-bar { + opacity: 0; + } + } + } + .mat-tab-body-wrapper { + .mat-tab-body { + height: 100%; + width: 100%; + } + } + } + + //[end] .global-menu .ucap-clickable { cursor: pointer; @@ -144,7 +205,8 @@ } } } - + // mat field, hint + .mat-form-field, .mat-form-field-appearance-legacy { .mat-form-field-label { color: mat-color($primary); @@ -152,6 +214,11 @@ .mat-hint { color: mat-color($primary); + em { + font-style: normal; + color: $lipstick; + font-weight: 600; + } } .mat-form-field-underline { @@ -163,34 +230,13 @@ padding: $pixels-amount; border-top: $pixels-amount; } - //login input... //////////////////// - .idpass-type { - .mat-form-field-empty { - &.mat-form-field-label { - top: 11px; - } - } - .mat-form-field-can-float { - &.mat-form-field-should-float { - .mat-form-field-label { - top: 11px; - width: 0; - } - } - } - .mat-form-field { - &.mat-focused { - .mat-form-field-label { - opacity: 0; - } - } - } - } + // Input... //////////////////// // input .ucap-mat-input-container { .mat-form-field-empty { &.mat-form-field-label { top: 11px; + color: #999999; } } .mat-form-field-can-float { @@ -208,37 +254,28 @@ } } .mat-form-field-wrapper { + display: flex; + height: 100%; padding-bottom: 0; .mat-form-field-infix { align-self: center; width: inherit; padding: 0; + .mat-form-field-label-wrapper { + .mat-form-field-label { + transition: none; + } + } } } } } - //////////////////// login input...// + //////////////////// Input...// //panel ///////////////////////////// .panelType { @include ucapMatExpansionPanel(60px, 0 0 0 0, 1, 0); } ///////////////////////////// panel// - //btn ////////////////////////////// - .btnType { - .mat-fab .mat-button-wrapper { - padding: 16px 0; - display: inline-block; - line-height: 24px; - .mat-icon { - background-repeat: no-repeat; - display: inline-block; - fill: currentColor; - height: 24px; - width: 24px; - } - } - } - ////////////////////////////// btn// .mat-button-toggle-appearance-standard .mat-button-toggle-label-content { height: 34px; @@ -254,6 +291,7 @@ border-radius: 0px; } + /* .current-head { display: flex; justify-content: center; @@ -268,6 +306,7 @@ background: linear-gradient(to right, $gradient-darkest, $gradient-light); color: #ffffff; } + */ //[S]탭개수에 의한 width 정의 .mat-tab-group { @@ -294,6 +333,7 @@ //[E]탭개수에 의한 width 정의 //[S]쪽지 라디오 버튼 정렬 + /* .message-box { .mat-radio-label { .mat-radio-label-content { @@ -301,6 +341,7 @@ } } } + */ //[E]쪽지 라디오 버튼 정렬 //[S]mat-tab-group @@ -371,7 +412,7 @@ } } //[E]mat-tab-group - + /* nav[mat-tab-nav-bar][vertical] { display: flex; flex-direction: row !important; @@ -402,8 +443,10 @@ background-color: mat-color($accent, B100); } } + */ //[S]대화 말풍선 global 적용 + /* .message-row { .message-main { .bubble { @@ -433,16 +476,20 @@ } } } + */ //[E]대화 말풍선 global 적용 //[S]스티커 + /* .sticker-selector { .mat-tab-header { } } + */ //[E]스티커 //[S]번역 + /* .translationForm { background-color: mat-color($accent, 200, 0.4); .select-language { @@ -461,6 +508,7 @@ .translation-preview { background-color: mat-color($accent, 900, 0.8); } + */ //[E] 번역 .mat-calendar { @@ -495,11 +543,50 @@ } } - /////////2020 + .search-container { + .ucap-organization-search-for-tenant-container { + align-items: center; + .company-container { + height: 100%; + } + .searchword-container { + .mat-form-field { + &-suffix { + button { + color: mat-color($accent, 400); + } + } + } + } + .btn-search-container { + height: 100%; + } + } + } + + /////////2020 Mat + //Layout (drawer) + .mat-drawer-container { + .mat-drawer { + &.mat-drawer-end { + @include screen(custom, max, 1240) { + left: 0; + width: 100%; + //transition: all 0.3s linear; + } + } + } + } + //color + .mat-icon-button { + color: #666666; + } + .color-white { color: $white; } + //mat chip .mat-chip { &.mat-standard-chip { @@ -510,6 +597,9 @@ color: mat-color($primary, default-contrast); } } + .chip-text-content { + @include ellipsis-column(1); + } } .mat-chip.mat-standard-chip.mat-chip-selected.mat-primary { background-color: mat-color($accent, 700); @@ -544,7 +634,7 @@ .noti-sum { .mat-badge-content { right: 16px !important; - bottom: 12px !important; + bottom: 9px !important; min-width: 22px; width: auto; line-height: 20px; @@ -616,9 +706,9 @@ //mat ico btn (Mini Fab(36*36) Buttons) .mat-mini36-fab { - line-height: 32px; - width: 36px; - height: 36px; + line-height: 32px !important; + width: 36px !important; + height: 36px !important; .mat-button-wrapper { line-height: 20px !important; .mat-icon { @@ -631,6 +721,10 @@ } } + .mat-button.bg-primary-darkest[ng-reflect-disabled='true'] { + background-color: #dddddd; + } + //mat tree .tree-has-child { display: flex; @@ -691,11 +785,11 @@ &.mat-checked { .mat-slide-toggle-bar { background-color: #fff; - border: 1px solid #fd578a; + border: 1px solid mat-color($accent, 500); .mat-slide-toggle-thumb-container { transform: translate3d(14px, 0, 0); .mat-slide-toggle-thumb { - background-color: #e42f66; + background-color: mat-color($accent, 700); } } } @@ -704,8 +798,8 @@ // Btn Float .btn-main-float { - right: 22px !important; - bottom: 60px !important; + right: 30px !important; + bottom: 30px !important; .bg-accent-dark { //background: mat-color($accent, 600); color: mat-color($primary, default-contrast); @@ -730,4 +824,153 @@ } } } + + // Organization Select Box + .selection-expansion { + .selection-header { + &[aria-disabled='true'] { + .selection-header-title { + color: rgba(0, 0, 0, 0.87); + } + } + .mat-expansion-panel-header-description { + flex-grow: 0; + margin-right: 0; + } + } + .mat-expansion-panel-content { + display: flex; + flex-direction: column; + overflow: auto; + flex-grow: 1; + flex-basis: 0; + justify-content: space-between; + .mat-expansion-panel-body { + overflow: auto; + } + } + } + + // Mat Mixin background-color:$accent, 700, class name + @include colorMix-background($accent, A200, '.bg-accent-a200'); + @include colorMix-background($warn, 50, '.bg-warn-chat', '.message-area'); + + // Mat Mixin text-color:$accent, 700, class name + @include colorMix-text($accent, 700, '.txt-accent', '.text-accent-color'); + @include colorMix-text($accent, A700, '.text-accent-chat'); + @include colorMix-text($warn, 300, '.text-warn-chat'); + @include colorMix-text($warn, 800, '.text-warn-color'); + + // Mat Mixin bg+text-color: ex)$accent, 700, $accent, B200 class name or tag name + @include colorMix-backgroundText( + $primary, + default-contrast, + $primary, + 900, + '.bg-primary-dark', + '.bg-primary-darkest' + ); + @include colorMix-backgroundText( + $primary, + A700, + $primary, + A200, + '.selector-title', + '.ucap-organization-profile-selection-01-header' + ); + @include colorMix-backgroundText( + $primary, + A700, + $accent, + A100, + '.file-info-box', + '.video-info-box' + ); + @include colorMix-backgroundText( + $primary, + default-contrast, + $primary, + 500, + '.bg-primary-chat' + ); + @include colorMix-backgroundText($warn, A700, $warn, 200, '.bg-warn-chat2'); + @include colorMix-backgroundText( + $primary, + default-contrast, + $accent, + A400, + '.bg-accent-chat' + ); + @include colorMix-backgroundText( + $primary, + default-contrast, + $accent, + A700, + '.ico-timer-unit' + ); + @include colorMix-backgroundText( + $primary, + default-contrast, + $accent, + 500, + '.current' + ); + + //create-dialog min사이즈 + .max-create-dialog { + width: 80%; + height: 80vh; + max-width: 1000px !important; + min-width: 420px; + min-height: 640px; + } + .mid-create-dialog { + width: 80%; + height: 80vh; + max-width: 700px !important; + min-width: 420px; + min-height: 640px; + } + .max-create-dialog, + .mid-create-dialog { + @include screen(xs) { + width: 100%; + min-width: 100%; + height: 100vh; + } + .dialog-container { + @include screen(xs) { + border-radius: 0 !important; + } + } + } + .min-create-dialog { + width: 60%; + height: auto; + max-width: 500px !important; + max-height: 420px; + min-width: 300px; + min-height: 240px; + @include screen(xs) { + width: 80%; + min-width: 100%; + max-height: 400px; + } + .mat-dialog-container { + ucap-confirm-dialog, + ucap-alert-dialog { + .mat-card { + .mat-card-content { + min-height: 50px; + padding-left: 70px; + background-image: url(assets/images/ico/icon_alert_info_a50.svg); + background-repeat: no-repeat; + } + .mat-card-actions { + padding: 0 16px; + } + } + } + } + } } diff --git a/src/assets/scss/global/_ucap-ui.scss b/src/assets/scss/global/_ucap-ui.scss index 0b87a95..eeaf4a0 100644 --- a/src/assets/scss/global/_ucap-ui.scss +++ b/src/assets/scss/global/_ucap-ui.scss @@ -3,4 +3,5 @@ @import 'ucap/group'; @import 'ucap/call'; @import 'ucap/message'; -@import 'ucap/call'; +@import 'ucap/chat'; +@import 'ucap/dialog'; diff --git a/src/assets/scss/global/ucap/_authentication.scss b/src/assets/scss/global/ucap/_authentication.scss index 3f97ffc..f0b63ce 100644 --- a/src/assets/scss/global/ucap/_authentication.scss +++ b/src/assets/scss/global/ucap/_authentication.scss @@ -40,6 +40,13 @@ } .mat-form-field { .mat-form-field-wrapper { + .mat-form-field-flex { + .mat-form-field-infix { + .mat-input-element { + padding-right: 16px; + } + } + } .mat-form-field-underline { height: 0 !important; } @@ -64,12 +71,34 @@ width: 420px; .mat-form-field { @include ucapMatSelect(60px, 0 16px); + .mat-form-field-wrapper { + .mat-form-field-flex { + .mat-form-field-infix { + .mat-form-field-label-wrapper { + .mat-form-field-label { + top: 33px !important; + } + } + } + } + } } @include screen(mid) { margin-top: 23px !important; width: 350px; .mat-form-field { @include ucapMatSelect(50px, 0 16px); + .mat-form-field-wrapper { + .mat-form-field-flex { + .mat-form-field-infix { + .mat-form-field-label-wrapper { + .mat-form-field-label { + top: 27px !important; + } + } + } + } + } } } @include screen(xs) { @@ -77,6 +106,17 @@ width: 300px; .mat-form-field { @include ucapMatSelect(42px, 0 16px); + .mat-form-field-wrapper { + .mat-form-field-flex { + .mat-form-field-infix { + .mat-form-field-label-wrapper { + .mat-form-field-label { + top: 23px !important; + } + } + } + } + } } } } diff --git a/src/assets/scss/global/ucap/_chat.scss b/src/assets/scss/global/ucap/_chat.scss index e69de29..00f5c81 100644 --- a/src/assets/scss/global/ucap/_chat.scss +++ b/src/assets/scss/global/ucap/_chat.scss @@ -0,0 +1,1515 @@ +.ucap-chat-room-list-item1 { + cursor: pointer; + @include wordBreak(); + .ucap-chat-room-list-item1-profile-image { + height: 38px; + @include profile-avatar-default( + 0, + 14, + $warm-pink, + 18px + ); //오른 아래 공간, 모바일 온라인 아이콘 크기, 모바일 아이콘 색, 모바일 아이콘 bg크기 + flex-basis: 36px; + .profile-image { + @include avatar-img(36px, 0); //아바타 크기, 왼쪽공간 + background-color: #ffe8cb; + } + } + .ucap-chat-room-list-item1-info { + min-height: 60px; + flex-grow: 1; + padding-left: 12px; + .roomName { + flex: 1 0 auto; + max-height: 23px; + line-height: 23px; + height: 23px; + font: { + size: 1em; + weight: 600; + } + color: #333; + line-height: 21px; + height: 21px; + //@include ellipsis-column(1); + .timer-icon { + padding-right: 8px; + .ico-timer-unit { + width: 16px !important; + height: 16px !important; + line-height: 16px; + font-size: 10px; + text-align: center; + border-radius: 50%; + } + } + .chat-subject { + @include ellipsis-column(1); + line-break: anywhere; + flex: 0 1 auto; + } + .chat-amount { + flex: 0 0 auto; + white-space: nowrap; + color: $lipstick; + @include ellipsis-column(1); + strong { + margin-left: 2px; + } + } + .ico-chat-list { + margin-left: 6px; + align-self: center; + width: 16px; + height: 19px; + font-size: 16px; + } + } + .lastMessage { + font: { + size: 0.857em; + } + color: $gray-re70; + line-height: 19px; + margin-top: 2px; + height: 20px; + @include ellipsis-column(1); + line-break: anywhere; + } + } + .date { + flex: 0 1 auto; + color: $gray-re9; + line-height: 15px; + //align-self: flex-start; + padding: 13px 16px 0 10px; + //flex-basis: 136px; + font-size: 0.857em; + text-align: right; + white-space: nowrap; + } + .room-menu-more { + align-self: center; + position: absolute; + z-index: 5; + right: 6px; + background-color: rgba(255, 255, 255, 0.8); + visibility: hidden; + transition: all 0.2s; + opacity: 0; + } + &:hover { + transition: all 0.2s; + background-color: #fefaf6; + .room-menu-more { + visibility: visible; + transition: all 0.3s; + opacity: 1; + } + } + .group-check { + align-self: center; + padding: 0 16px 0 25px; + } + &.current-chat { + background-color: #fefaf6; + /*Background 변경시 적용 내용 + background-color: rgba(170, 164, 166, 0.8); + .roomName { + color: #fff; + .chat-subject { + color: #fff; + } + .chat-amount { + color: #fff; + } + .mat-icon { + .ico-off-false { + color: #fff; + } + } + } + .lastMessage { + color: #fff; + } + .date { + color: #fff; + } + */ + .ucap-chat-room-list-item1-profile-image { + .profile-image { + background-color: #d1f6ff; + } + } + } +} + +//mat-menu +.manu-title { + @include menu-title(); +} +.mat-menu-chat-room-event { + .ico-exit-app { + -moz-transform: scale(-1, 1); + -webkit-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); + } +} + +.chat-menu-box { + .mat-menu-content { + &:not(:empty) { + padding-top: 0; + padding-bottom: 0; + } + .menu-title { + @include menu-title() { + background-color: #584f52; + color: #fff; + padding: 12px; + border-bottom: none; + @include ellipsis(1); + } + } + mat-divider { + border-top: none; + } + .chat-menu-box-body { + border: 1px solid #999999; + border-top: none; + border-radius: 0 0 4px 4px; + .data { + display: flex; + flex-direction: column; + align-items: center; + padding-bottom: 10px; + .sub-title { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + height: 40px; + line-height: 40px; + width: calc(100% - 28px); + //padding: 0 14px; + .material-icons-outlined { + flex-basis: 40px; + color: rgba(0, 0, 0, 0.54); + } + span { + flex-grow: 1; + } + } + + .btn { + margin: 3px 14px 2px 42px; + height: 34px; + line-height: 34px; + border-radius: 4px; + background-color: #f1f2f6; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + button { + height: 34px; + line-height: 34px; + position: relative; + &:before { + content: ''; + width: 1px; + height: 14px; + background-color: #ccc; + position: absolute; + left: 0; + top: 11px; + } + &:first-of-type { + &::before { + display: none; + } + } + } + } + } + button { + height: 40px; + line-height: 40px; + &.btn-exit { + text-align: center; + font-weight: 600; + border-radius: 0 0 4px 4px; + border-top: 1px solid #999999; + } + } + } + } +} + +// Tree list +.ucap-chat-room-expansion-container { + .mat-tree { + .mat-tree-node { + li { + width: 100%; + .ucap-chat-room-list-item1 { + max-height: 59px; + border-bottom: 1px solid #eee; + padding-left: 16px; + } + } + &.ucap-clickable { + border-bottom: 1px solid $gray-rec; + li { + .path { + .group-info { + .header-buddy { + font-size: 0.9em; + @include ellipsis-column(1); + line-break: anywhere; + @include wordBreak(); + } + } + } + } + } + } + } +} + +//Chat member list Dialog +.ucap-chat-member-list-dialog { + .ucap-organization-profile-list-container { + .ucap-virtual-scroll-viewport { + overflow-x: hidden; + .ps__rail-x { + display: none; + } + .ucap-virtual-scroll-content-wrapper { + padding: 0 16px; + // overflow: hidden; + } + // .ucap-virtual-scroll-spacer { + // height: auto !important; + // } + } + .profile-list-container { + overflow: hidden; + .ucap-organization-profile-list-item-02-container { + height: 60px; + .user-profile-info { + flex-basis: 100%; + .user-info { + .user-n-g { + .user-grade { + white-space: pre-wrap !important; + } + } + } + } + .user-intro { + flex-basis: 0; + } + .action-content { + height: 59px; + flex-basis: 0; + } + } + } + } + .ucap-chat-item-list-container { + padding: 0 16px; + .profile-list-container { + padding-left: 5px; + } + } +} + +//Chat member list Drawer +.ucap-add-users-chat-drawer { + max-width: 400px; + @include screen(custom, max, 1240) { + max-width: 100%; + } + .drawer-body { + .ucap-chat-member-drawer { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + app-group-select-user { + flex: 1 1 auto; + height: 100%; + .select-user-section-container { + .ucap-dialog-search { + margin: 0; + } + } + } + ucap-organization-profile-selection-01 { + flex: 1 1 200px; + height: 100%; + .ucap-organization-profile-selection-01-container { + border-top: 1px solid #d4d4d4; + .ucap-organization-profile-selection-01-header { + flex: 0 0 40px !important; + max-height: 40px !important; + min-height: 40px !important; + padding: 0 16px; + display: flex; + align-items: center; + } + .ucap-organization-profile-selection-01-body { + padding: 10px 16px; + width: 100%; + height: 120px; + overflow: auto; + @include screen(xs) { + height: 90px; + } + } + } + } + .select-user-section-container { + .select-user-section-content { + .mat-tab-group { + margin: 0 16px; + .mat-tab-header { + } + .mat-tab-body-wrapper { + //padding: 0 16px; + .mat-tab-body { + .mat-tab-body-content { + .ucap-group-expansion-container { + .ucap-virtual-scroll-viewport { + } + } + .select-tap { + .organization-tree-simple { + padding: 0 !important; + justify-content: center; + } + } + } + } + } + } + } + .ucap-dialog-search-result { + padding: 0 16px; + .search-result-total-content { + .number { + margin-left: 4px; + color: $lipstick; + font-weight: 600; + } + } + } + } + } + } +} +//Chat Data Room Image +.dataroom-contents { + .ucap-chat-image-list-container { + height: 100%; + justify-content: center; + padding: 6px 8px; + background-color: #f7f8fa; + ucap-chat-image-list-item-01 { + float: left; + width: calc(33.3% - 4px); + height: 100px; + position: relative; + margin: 4px 2px; + background-color: $white; + .ucap-chat-image-list-item-01-container { + .contents { + justify-content: center; + img { + max-width: 100%; + max-height: 100%; + } + .mat-checkbox { + z-index: 4; + right: 10px; + top: 5px; + .mat-checkbox-layout { + .mat-checkbox-inner-container { + border-radius: 2px; + } + } + } + } + .btn-box { + height: 0; + transition: all 0.4s; + -webkit-transition: all 0.4s; + visibility: hidden; + ul { + z-index: 4; + bottom: 10px; + justify-content: space-around; + li { + button { + height: 20px; + width: 20px; + .mat-button-wrapper { + .mat-icon { + font-size: 8px; + line-height: 8px; + width: 8px; + height: 8px; + color: transparent; + } + } + } + } + } + &.over { + height: 100% !important; + visibility: visible; + transition: all 0.5s; + -webkit-transition: all 0.5s; + background-color: rgba(0, 0, 0, 0.5); + ul { + li { + button { + height: 30px; + width: 30px; + .mat-button-wrapper { + .mat-icon { + font-size: 18px; + line-height: 18px; + width: 18px; + height: 18px; + color: $white; + } + } + } + } + } + } + } + .progress { + visibility: hidden; + height: 0; + transition: all 0.1s; + &.over { + transition: all 0.2s; + visibility: visible; + position: absolute; + z-index: 5; + width: 100%; + height: 100%; + left: 0; + top: 0; + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: center; + color: #fff; + padding: 0 8px; + background-color: rgba(0, 0, 0, 0.5); + } + } + } + } + } + //File + .ucap-chat-file-list-item-01-container { + border-bottom: 1px solid #ccc; + padding: 0 6px 0 0; + min-height: 70px; + //max-width: calc(100% - 6px); + display: block !important; + position: relative; + .contents { + display: flex; + flex-direction: row; + align-items: center; + .file-info { + li { + &.file-name { + flex: 0 0 40%; + .text-filename { + @include ellipsis-column(1); + color: #333; + font-weight: 600; + } + .text-username { + @include ellipsis-column(1); + color: #333; + font-size: 0.857em; + } + } + &.file-bytes-date { + flex: 0 0 25%; + padding-right: 5px; + .amount-bytes { + text-align: right; + white-space: nowrap; + color: #333; + font-size: 0.857em; + } + .text-date { + text-align: right; + color: #333; + font-size: 0.857em; + em { + font-style: normal; + } + } + } + &.file-list-amount { + flex-direction: row !important; + flex: 0 0 25%; + justify-content: center; + color: #333; + font-weight: 600; + strong { + color: #f92465; + } + } + &.file-check-box { + flex: 0 0 10%; + } + } + } + } + .progress { + visibility: hidden; + height: 0; + transition: all 0.1s; + &.over { + transition: all 0.2s; + visibility: visible; + position: absolute; + z-index: 5; + width: 100%; + height: 100%; + left: 0; + top: 0; + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: center; + color: #fff; + padding: 0 8px; + background-color: rgba(0, 0, 0, 0.5); + } + } + } +} + +// Recent receive message +.app-chat-recent-message-container { + padding: 22px 30px; + @include screen(xs) { + padding: 22px 15px; + } + .user-profile-thumb { + display: flex; + flex-direction: row; + align-items: flex-start; + width: 100%; + .profile-image { + flex: 0 0 36px; + @include avatar-img(36px, 0); //아바타 크기, 왼쪽공간 + background-color: rgba(254, 229, 229, 0.9); + @extend %ucapCursorPointer; + } + .sender-info { + flex: 0 0 auto; + max-width: 25%; + padding-left: 10px; + font-size: 0.929em; + display: inline-flex; + flex-direction: row; + .name { + flex: 1 1 auto; + //max-width: 70%; + @include ellipsis-column(1); + line-break: anywhere; + } + .grade { + flex: 0.8 1 auto; + //max-width: 30%; + @include ellipsis-column(1); + line-break: anywhere; + } + } + .contents { + flex-grow: 1; + max-width: 50%; + padding-left: 50px; + font-size: 0.929em; + line-height: 1.54; + @include ellipsis-column(2); + @include wordBreak; + @include screen(xs) { + padding-left: 20px; + } + } + .btn-area { + flex: 1 0 auto; + display: inline-flex; + flex-direction: row; + justify-content: flex-end; + align-self: center; + cursor: default; + button { + min-width: 34px; + padding: 0; + height: 34px; + background-color: #fff; + border-radius: 6px; + border: 1px solid #e42f66; + } + } + } +} + +// Message Input Box +.ucap-chat-input-container { + height: 100%; +} + +// Message Box ////////////////// +.chat-row { + ucap-chat-message-box-information { + width: 100%; + } +} + +//Chat Message Bubble Disable +.disable-bubble { + .chat-bubble-area { + .file-info-box { + color: #999 !important; + background: #f1f2f6 !important; + } + .video-info-box { + color: #999 !important; + background: #f1f2f6 !important; + .file-thumbimg { + position: relative; + &::after { + content: ''; + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + background-color: rgba(255, 255, 255, 0.8); + } + } + } + .event-info, + .sms-info, + .conference-info li, + .label, + .place, + .date { + color: #999 !important; + } + .event-content, + .contents, + content { + color: #666 !important; + } + .event-header, + .sms-header, + .conference-header { + background-color: #e2e2e2 !important; + } + .ucap-chat-message-box-image-bubble { + position: relative; + &::after { + content: ''; + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + background-color: rgba(255, 255, 255, 0.8); + } + } + .btn-box { + border-top: 1px solid #ccc !important; + background-color: #e2e2e2 !important; + ul { + li { + border-left: 1px solid #ccc !important; + } + } + .mat-button { + color: #999 !important; + } + } + } +} + +//Date splitter +.ucap-chat-message-box-date-splitter { + height: 30px; + margin: 20px 0; + &:before { + content: ''; + height: 1px; + background-color: $gray-re3; + top: 50%; + } + .date { + font-size: 0.979em; + text-align: center; + font-weight: 600; + color: $gray-re3; + padding: 0 36px; + @include screen(xs) { + padding: 0 16px; + } + align-self: center; + } +} +//Attach +.ucap-chat-message-box-attach-file-bubble { + border-radius: 2px; + cursor: pointer; + position: relative; + margin: 0 16px 16px; + overflow: hidden; + min-width: 160px; + @include screen(custom, max, 414) { + margin: 0; + } + .file-info-box { + align-items: center; + padding: 6px 12px 0 0; + border-radius: 2px; + width: 100%; + cursor: pointer; + .file-info { + border-radius: 2px; + .file-name { + line-height: 1.2; + font-size: 0.929em; + @include ellipsis-column(2); + line-break: anywhere; + @include wordBreak(); + } + .date { + color: #c92b5c; + font-size: 0.875em; + margin-top: 4px; + } + } + } + .btn-box { + transition: all 0.4s; + display: flex !important; + visibility: hidden; + border-radius: 2px; + height: 0 !important; + position: absolute; + &.over { + transition: all 0.6s; + visibility: visible; + background-color: rgba(0, 0, 0, 0.6); + height: 100% !important; + } + ul { + li { + padding-left: 16px; + &:first-child { + } + .btn-file-ctrl { + @include ucapMatButton(36px, 36px, 50%, 36px); + } + } + } + } + .progress { + display: flex; + background-color: rgba(0, 0, 0, 0.6); + cursor: default; + &:before { + content: ''; + width: 16px; + height: 16px; + right: 22px; + top: calc(50% - 25px); + background-color: $white; + border-radius: 50%; + } + &.over { + } + .btn-close { + @include ucapMatButton(20px, 20px, 0, 20px); + width: 20px; + margin-bottom: 6px; + } + .progress-info-area { + color: $white; + font-size: 0.875em; + margin-top: 4px; + font-weight: 600; + } + } + + .desibled { + .ucap-file-bubble { + .ucap-chat-message-box-attach-file { + color: #999 !important; + background: #f1f2f6 !important; + } + } + } +} +.me { + .ucap-chat-message-box-attach-file-bubble { + .file-info-box { + background-color: #fef6ec; + .file-info { + .date { + color: #ff6c20; + } + } + } + .btn-box { + ul { + li { + .btn-file-ctrl { + background-color: #f69532; + } + } + } + } + } +} +//Text +.ucap-chat-message-box-text-bubble { + padding: 0 16px 16px; + span { + font-size: 13px; + line-height: 20px; + color: #333; + a { + color: #349adf !important; + } + } +} +//Information +.ucap-chat-message-box-information { + @include wordBreak(); + width: 100%; + .info { + line-height: 1.67; + min-height: 30px; + padding: 5px 8px; + font-size: 0.875em; + border-radius: 2px; + text-align: center; + margin-top: 20px; + strong { + color: #ffda9a; + font-weight: 600; + strong { + color: $white; + } + } + } + .chat-event { + line-height: 1.67; + min-height: 30px; + padding: 5px 8px; + font-size: 0.875em; + border-radius: 2px; + text-align: center; + margin-top: 20px; + font-weight: 600; + } +} +//Sticker +.ucap-chat-message-box-sticker-bubble { + padding: 0 16px 16px; + li { + @include wordBreak(); + } +} +//Mass translation +.ucap-chat-message-box-mass-translation-bubble { + .content { + padding: 0 16px 16px; + font-size: 0.929em; + color: #333; + line-height: 1.54; + @include wordBreak(); + } + .contents { + padding: 16px 16px 16px 42px; + font-size: 0.929em; + color: #333; + line-height: 1.54; + background-color: #fce7ed; + @include wordBreak(); + .language { + padding: 3px 8px; + background-color: #e86773; + border-radius: 2px; + font-size: 0.786em; + color: #fff; + margin-right: 4px; + line-height: 1.3; + transform: translateY(-2px); + margin-top: 3px; + margin-left: -26px; + } + } + + .btn-box { + height: 40px; + border-top: 1px solid #f998a0; + background-color: #fff2f3; + ul { + height: 100%; + li { + font-size: 0.979em; + border-left: 1px solid #f998a0; + &:first-child { + border-left: none; + } + .mat-button { + padding: 0 6px; + height: 40px; + border-radius: 0; + color: #584f52; + } + .language { + padding: 3px 8px; + background-color: #e86773; + border-radius: 2px; + font-size: 0.786em; + color: #fff; + margin-right: 4px; + line-height: 1.2; + transform: translateY(-2px); + margin-top: 3px; + } + } + } + } +} +.me { + .ucap-chat-message-box-mass-translation-bubble { + .btn-box { + border-top: 1px solid #f69532; + background-color: #fcb954; + ul { + li { + border-left: 1px solid #f69532; + .mat-button { + color: #fff; + } + .language { + background-color: #ff7f3d; + } + } + } + } + } +} + +//Mass +.ucap-chat-message-box-mass-bubble { + .content { + padding: 0 16px 16px; + font-size: 0.929em; + color: #333; + line-height: 1.54; + display: block; + @include wordBreak(); + a { + color: #349adf !important; + } + } + + .btn-box { + border-top: 1px solid #f998a0; + height: 40px; + background-color: #fff2f3; + .mat-button { + height: 40px; + border-radius: 0; + color: #584f52; + } + } +} +.me { + .ucap-chat-message-box-mass-bubble { + .btn-box { + border-top: 1px solid #f69532; + background-color: #fcb954; + .mat-button { + color: #fff; + } + } + } +} + +//Read +.ucap-chat-message-box-read-here { + background-color: rgba(0, 0, 0, 0.4); + line-height: 30px; + min-height: 30px; + padding: 0 8px; + border-radius: 2px; + margin: 20px 0 0; + font-size: 12px; + font-weight: 600; +} +//Recall +.ucap-chat-message-box-recall-bubble { + padding: 0 16px 16px; + .recall-msg { + color: #999999; + margin-left: 10px; + line-height: 1.8; + } +} +//Reply +.ucap-chat-message-box-reply-bubble { + .reply-header { + padding: 10px 16px; + background-color: #f7f8fa; + border-top: 1px solid #f998a0; + border-bottom: 1px solid #f998a0; + dl { + margin: 0; + padding: 5px 0 8px; + dt { + color: #ec5361; + margin: 0; + } + dd { + color: #666; + font-size: 0.979em; + margin: 5px 0 0; + } + } + } + .content { + padding: 12px 16px 16px; + .content-inner { + .icon-reply { + background-color: #e86773; + width: 30px; + height: 18px; + border-radius: 2px; + margin-right: 8px; + .ico-reply { + color: #fff; + font-size: 16px; + } + } + .reply-text { + line-height: 18px; + font-size: 0.979em; + color: #333; + } + } + } +} +.me { + .ucap-chat-message-box-reply-bubble { + .reply-header { + background-color: #fef6ec; + border-top: 1px solid #f69532; + border-bottom: 1px solid #f69532; + dl { + dt { + color: #ff7f3d; + } + } + } + .content { + .content-inner { + .icon-reply { + background-color: #ffe8cb; + } + } + } + } +} + +//Schedule +.ucap-chat-message-box-schedule-bubble { + .event-box { + padding: 0 16px; + .event-header { + background-color: rgba(232, 103, 115, 0.9); + border-radius: 4px; + min-height: 36px; + color: #fff; + font-weight: 600; + @include wordBreak(); + } + .event-info { + .place { + min-height: 32px; + font-size: 0.929em; + color: #666; + font-weight: 600; + padding: 6px 0; + @include wordBreak(); + } + .date { + min-height: 32px; + font-size: 0.929em; + color: #c92b5c; + padding: 6px 0; + border-top: 1px dashed #ccc; + @include wordBreak(); + } + .event-content { + min-height: 32px; + font-size: 0.929em; + color: #333; + padding: 8px 0; + @include wordBreak(); + } + } + } + .btn-box { + height: 40px; + border-top: 1px solid #f998a0; + background-color: #fff2f3; + .mat-button { + height: 40px; + border-radius: 0; + color: #584f52; + } + } +} +.me { + .ucap-chat-message-box-schedule-bubble { + .event-box { + .event-header { + background-color: rgba(255, 127, 61, 0.9); + } + .event-info { + .date { + color: #ff6c20; + } + } + } + .btn-box { + border-top: 1px solid #f69532; + background-color: #fcb954; + .mat-button { + color: #fff; + } + } + } +} +//SMS +.ucap-chat-message-box-sms-bubble { + .sms-box { + padding: 0 16px 16px; + .sms-header { + background-color: rgba(232, 103, 115, 0.9); + border-radius: 4px; + min-height: 36px; + color: #fff; + font-weight: 600; + @include wordBreak(); + } + .content { + min-height: 32px; + font-size: 0.857em; + color: #333; + padding: 8px 0 0; + @include wordBreak(); + } + } +} +.me { + .ucap-chat-message-box-sms-bubble { + .sms-box { + .sms-header { + background-color: rgba(255, 127, 61, 0.9); + } + } + } +} +//Translation +.ucap-chat-message-box-translation-bubble { + .translation-original-box { + padding: 0 16px; + .sticker { + } + .original { + font-size: 0.929em; + line-height: 1.47; + padding-bottom: 10px; + @include wordBreak(); + } + } + .translation-box { + border-top: 1px solid #f998a0; + background-color: #fff2f3; + padding: 10px 16px; + font-size: 0.929em; + line-height: 1.4; + color: #333; + span { + @include wordBreak(); + } + .language { + padding: 2px 8px; + background-color: #e86773; + border-radius: 2px; + font-size: 0.9em; + color: #fff; + margin-right: 8px; + line-height: 1; + transform: translateY(-2px); + } + } +} +.me { + .ucap-chat-message-box-translation-bubble { + .translation-box { + border-top: 1px solid #f69532; + background-color: #fef6ec; + .language { + background-color: #ff7f3d; + } + } + } +} +//Video Conference +.ucap-chat-message-box-video-conference-bubble { + .conference-box { + padding: 0 16px; + .conference-header { + background-color: rgba(232, 103, 115, 0.9); + border-radius: 4px; + height: 36px; + color: #fff; + font-weight: 600; + } + .conference-info { + li { + min-height: 32px; + font-size: 0.929em; + color: #2d2a2c; + padding: 6px 0; + border-top: 1px dashed #ccc; + .label { + font-weight: 600; + } + &:first-child { + border-top: none; + } + &.date { + color: #c92b5c; + } + } + } + } + .btn-box { + height: 40px; + border-top: 1px solid #f998a0; + background-color: #fff2f3; + ul { + li { + width: 50%; + font-size: 0.979em; + border-left: 1px solid #f998a0; + .mat-button { + padding: 0 6px; + height: 40px; + border-radius: 0; + color: #584f52; + } + } + } + } +} +.me { + .ucap-chat-message-box-video-conference-bubble { + .conference-box { + .conference-header { + background-color: rgba(255, 127, 61, 0.9); + } + .conference-info { + .date { + color: #ff6c20; + } + } + } + .btn-box { + border-top: 1px solid #f69532; + background-color: #fcb954; + .mat-button { + color: #fff; + } + } + } +} + +//Video +.ucap-chat-message-box-video-bubble { + display: flex; + flex-direction: row; + align-items: center; + border-radius: 2px; + min-width: 200px; + cursor: pointer; + position: relative; + margin: 0 16px 16px; + overflow: hidden; + @include screen(custom, max, 414) { + margin: 0; + } + .video-info-box { + align-items: center; + padding: 10px; + border-radius: 2px; + width: 100%; + .file-thumbimg { + flex: 0 0 100px; + min-height: 100px; + max-height: 100px; + display: flex; + align-items: center; + justify-content: center; + background-color: #fff; + img { + max-height: 100px; + } + } + .video-info { + padding-left: 10px; + border-radius: 2px; + .video-subject { + line-height: 1.2; + font-size: 0.929em; + @include ellipsis-column(4); + line-break: anywhere; + @include wordBreak(); + } + .date { + color: #c92b5c; + font-size: 0.875em; + margin-top: 4px; + } + } + } + + .btn-box { + transition: all 0.4s; + display: flex !important; + visibility: visible; + border-radius: 2px; + height: 0 !important; + position: absolute; + &.over { + transition: all 0.6s; + visibility: visible; + background-color: rgba(0, 0, 0, 0.6); + height: 100% !important; + } + ul { + li { + .btn-file-ctrl { + @include ucapMatButton(36px, 36px, 50%, 36px); + width: 36px; + } + } + } + } + .progress { + display: flex; + border-radius: 2px; + background-color: rgba(0, 0, 0, 0.6); + padding: 0 20px; + &:before { + width: 16px; + height: 16px; + right: 22px; + top: calc(50% - 25px); + background-color: $white; + border-radius: 50%; + } + .btn-close { + @include ucapMatButton(20px, 20px, 0, 20px); + width: 20px; + margin-bottom: 6px; + } + .progress-info-area { + color: $white; + font-size: 0.875em; + margin-top: 4px; + font-weight: 600; + } + } +} +.me { + .ucap-chat-message-box-video-bubble { + .video-info-box { + background-color: #fef6ec; + .video-info { + .date { + color: #ff6c20; + } + } + } + .btn-box { + ul { + li { + .btn-file-ctrl { + background-color: #f69532; + } + } + } + } + } +} + +//Image +.ucap-chat-message-box-image-bubble { + text-align: center; + padding: 0 12px 12px; + @include screen(custom, max, 414) { + padding: 0; + } + img { + border-radius: 5px; + } +} + +.ucap-chat-room-add-group-body { + .ucap-dialog-select-group-container { + .ucap-dialog-select-group-contnet { + height: 100% !important; + .ucap-dialog-app-group-name-input { + display: flex; + width: 100%; + height: 80px !important; + margin: 20px 0; + .new-group-add { + display: flex; + align-items: unset !important; + flex-direction: column; + .group-name-input { + padding: 0 16px; + } + } + } + } + } +} diff --git a/src/assets/scss/global/ucap/_dialog.scss b/src/assets/scss/global/ucap/_dialog.scss new file mode 100644 index 0000000..3d2cd03 --- /dev/null +++ b/src/assets/scss/global/ucap/_dialog.scss @@ -0,0 +1,320 @@ +.dialog-container { + width: 100%; + height: 100%; + .layout-container { + .layout-header { + border-bottom: none !important; + @include screen(xs) { + margin: 0 !important; + } + .sub-header-tit { + padding-left: 20px; + font-size: 1em; + color: #666; + font-weight: 400; + } + } + } + .dialog-body { + padding: $default-space; + @include wordBreak(); + @include screen(xs) { + padding: $default-space 0; + } + .mat-stepper-horizontal { + overflow: hidden; + .mat-horizontal-stepper-header-container { + display: none; + } + .mat-horizontal-content-container { + position: relative; + height: 100%; + padding: 0; + overflow: hidden; + .mat-horizontal-stepper-content { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + display: flex; + flex-direction: row; + justify-content: center; + @include screen(xs) { + flex-direction: column; + } + + &[aria-expanded='false'] { + left: 100%; + } + } + } + } + .app-group-profile-list-item-02 { + } + .profile-container { + overflow: auto; + @include screen(xs) { + .ucap-organization-profile-01-container { + .profile-card-content { + padding: 58px 5.7% 25px; + margin-top: 0; + height: 100%; + .btn-profile-option-area { + top: 28px; + } + } + } + } + } + } + .btn-box { + padding: 0 $default-space; + button { + margin-left: 4px; + } + @include screen(xs) { + padding: 0; + } + } + .sub-title { + font-size: 14px; + font-weight: bold; + color: #474244; + .label-text { + @include screen(sm) { + display: none; + } + @include screen(xs) { + display: none; + } + } + mat-icon { + @include screen(sm) { + display: none; + min-width: 0; + } + @include screen(xs) { + display: none; + min-width: 0; + } + } + .number { + color: #e42f66; + } + } + .forwrad-dialog-container { + .forwrad-dialog-content { + .tap-container { + .ucap-dialog-chat-tap { + .ucap-chat-room-list-item1 { + padding-left: 16px; + border-bottom: 1px solid #cccccc; + .date { + display: none; + } + } + perfect-scrollbar { + .ps { + .ps-content { + .profile-list-container { + .ucap-chat-item-list-container { + .ucap-virtual-scroll-orientation-vertical { + .ps__rail-y { + right: 0; + left: initial !important; + } + } + } + } + } + } + } + } + } + } + } +} + +.select-user-tap-group-expansion { + .expansion-01-container, + .expansion-02-container { + .ucap-group-expansion-container { + .mat-tree > .mat-tree-node { + li.tree-node-header { + mat-checkbox { + padding: 0 16px 0 10px; + } + } + li { + .profile-list-container { + height: 60px; + background-color: #fff; + } + } + .profile-list-container { + &:hover { + transition: all 0.2s; + background-color: #f7f8fa; + } + } + } + .ucap-selected-groupt-header { + background-color: #f7f8fa !important; + height: 100%; + } + } + } +} + +//선택한 사용자 +.ucap-dialog-organization-profile-selection { + .ucap-organization-profile-selection-01-header { + display: flex; + align-items: center; + background-color: #f1f2f6; + padding: 0 10px; + flex-direction: row; + p { + flex: 0 1 auto; + @include ellipsis-column(1); + line-break: anywhere; + } + span { + flex: 1 0 auto; + em { + font-style: normal; + } + } + .number { + color: #e42f66; + } + } + .ucap-organization-profile-selection-01-body { + padding: 10px; + overflow: auto; + } +} + +//새그룹명 추가 +.ucap-dialog-select-group-container { + .ucap-dialog-app-group-name-input { + .name-input-container { + .img-group-add { + display: none; + } + .name-input-form { + .mat-form-field { + width: 100% !important; + } + } + } + } +} +.name-input-container { + .name-input-form { + .mat-form-field { + &.mat-form-field-appearance-legacy.mat-form-field-can-float.mat-form-field-should-float + .mat-form-field-label, + .mat-form-field-appearance-legacy.mat-form-field-can-float + .mat-input-server:focus + + .mat-form-field-label-wrapper + .mat-form-field-label { + font-size: 0; + transform: translateY(0) scale(0); + } + &.mat-form-field-appearance-legacy { + .mat-form-field-label { + color: #999999; + } + } + } + } +} + +//그룹맴버 관리 리스트 +.dialog-container { + .ucap-organization-profile-list-item-02-container { + height: 60px !important; + .action-content { + display: none; + } + } +} + +//검색창 +.ucap-dialog-search { + margin-left: -16px; + margin-right: -16px; +} + +//장문보기 +.ucap-dialog-chat-text-detail-container { + .layout-container { + .layout-action { + display: none !important; + } + } +} + +//dialog 검색결과 +.ucap-dialog-search-result { + display: flex; + flex-direction: column; + height: calc(100% - 50px); + .sub-title { + display: flex; + flex: 0 0 40px; + align-items: center; + height: 40px; + border-bottom: 1px solid #999999; + mat-checkbox { + display: inline-flex; + margin-left: auto; + margin-right: 16px; + } + } + .search-result-list { + display: flex; + flex: 1 1 auto; + .ucap-dialog-search-result-container { + .profile-list-item-container { + .ucap-organization-profile-list-item-02-container { + height: 60px; + .action-content { + display: none; + } + } + } + } + } +} + +.expansion-container .header-buddy .group-info + .group-check { + width: 40px; + margin-left: 10px; +} + +.ucap-group-name-input-form { + .mat-form-field { + .mat-form-field-wrapper { + display: flex; + height: 100%; + padding-bottom: 0; + .mat-form-field-infix { + align-self: center; + width: inherit; + padding: 0; + .mat-input-element { + min-height: 30px; + } + .mat-form-field-label-wrapper { + .mat-form-field-label { + transition-property: opacity; + transition-duration: 1s; + } + } + } + } + } +} diff --git a/src/assets/scss/global/ucap/_group.scss b/src/assets/scss/global/ucap/_group.scss index e69de29..77eb312 100644 --- a/src/assets/scss/global/ucap/_group.scss +++ b/src/assets/scss/global/ucap/_group.scss @@ -0,0 +1,124 @@ +.chatlist { + .ucap-chat-room-list-item1 { + margin-top: 10px; + position: relative; + min-height: 60px; + height: auto !important; + &:after { + content: ''; + width: calc(100% - 50px); + height: 1px; + background-color: $gray-rec; + position: absolute; + bottom: 0; + right: 0; + @include screen(xs) { + width: 100%; + } + } + &:hover { + background-color: transparent !important; + } + } + @include screen(xs) { + ucap-chat-room-list-item-01 { + &:last-of-type { + .ucap-chat-room-list-item1 { + &:after { + display: none; + } + } + } + } + } +} + +//Dialog +.ucap-edit-group-name-dialog { + .mat-dialog-container { + padding: 10px 15px 25px; + position: relative; + .dialog-container { + .form-eidt { + span { + .mat-hint { + position: absolute; + bottom: 4px; + right: 20px; + } + } + .edit-actions { + display: flex; + .mat-icon-button { + .mat-button-wrapper { + .mdi-check { + &::before { + color: #fd578a; + } + } + .mdi-close { + &::before { + //color: #333333; + } + } + } + } + } + } + } + } +} + +app-group-expansion { + .expansion-container { + .ucap-group-expansion-container { + .ucap-virtual-scroll-content-wrapper { + width: 100%; + max-width: 100%; + .group-info { + width: calc(100% - 100px); + justify-self: self-start; + flex-grow: 1; + font-size: 0.9em; + } + } + } + } +} + +.sidenav-container.group { + .menu-btn { + justify-self: end; + padding-right: 6px; + .mat-icon-button { + width: 28px; + } + .mat-button-ripple.mat-ripple { + top: 6px; + width: 28px; + height: 28px; + } + } +} + +.sidenav-container { + .search-container { + .ucap-organization-search-for-tenant-container { + .company-container { + flex: 0 0 110px !important; + max-width: 110px !important; + min-width: 110px !important; + } + } + } +} + +.sidenav-container.group { + .ucap-mat-input-container { + .mat-form-field-empty { + &.mat-form-field-label { + letter-spacing: -0.04em; + } + } + } +} diff --git a/src/assets/scss/global/ucap/_organization.scss b/src/assets/scss/global/ucap/_organization.scss index 2e01088..23e8b12 100644 --- a/src/assets/scss/global/ucap/_organization.scss +++ b/src/assets/scss/global/ucap/_organization.scss @@ -1,7 +1,1420 @@ -.ucap-organization-profile-image-01-image-container { - border-radius: 50%; - overflow: hidden; - width: 36px; - height: 36px; - margin-left: 2px; +ucap-organization-profile-image-01 { + display: flex; +} + +.ucap-organization-profile-image-01-container { + height: 38px; + @include profile-avatar-default( + 0, + 14, + $green, + 18px, + '.ucap-organization-presence-mobile-online', + '.ucap-organization-presence-mobile-offline', + '.ucap-organization-presence-mobile-busy', + '.ucap-organization-presence-mobile-away', + '.ucap-organization-presence-mobile-away2', + '.ucap-organization-presence-mobile-none' + ); + &.ucap-organization-presence-mobile-online { + &:after { + color: $green; + } + } + &.ucap-organization-presence-mobile-offline { + &:after { + color: $gray-rec !important; + } + } + &.ucap-organization-presence-mobile-busy { + &:after { + color: $yellow-other !important; + } + } + &.ucap-organization-presence-mobile-away { + &:after { + color: $red-absence !important; + } + } + &.ucap-organization-presence-mobile-away2 { + &:after { + color: $red-absence !important; + } + } + &.ucap-organization-presence-mobile-none { + &:after { + color: transparent !important; + } + } +} + +.ucap-organization-presence { + @include presence-state(10px); + &-none { + @include presence-state(10px); + } + &-pc-online { + @include presence-state(10px, $green); + } + &-pc-offline { + @include presence-state(10px, $gray-rec); + } + &-pc-busy { + @include presence-state(10px, $yellow-other); + } + &-pc-away { + @include presence-state(10px, $red-absence); + } + &-pc-away2 { + @include presence-state(10px, $red-absence); + } + &-pc-none { + @include presence-state(10px); + } +} + +//avatar 01 +.ucap-organization-profile-image-01-image-container { + @include avatar-img(36px, 0) { + background-color: #ffe8cb; + } +} +//avatar 02 +.ucap-organization-profile-image-02-image-container { + @include avatar-img(36px, 0) { + background-color: #ffe8cb; + } +} + +//Profile +.ucap-organization-profile-01-container { + min-width: 400px !important; + @include screen(xs) { + min-width: 100% !important; + height: calc(100% - 77px); + display: flex; + } + .profile-card-content { + padding: 60px 8.7%; + width: 100%; + position: relative; + @include screen(xs) { + padding: 30px 5.7% 28px; + margin-top: 50px; + height: calc(100% - 50px); + overflow: auto; + flex: 1 0 auto; + } + .btn-profile-option-area { + z-index: 5; + top: 30px; + right: 30px !important; + @include screen(xs) { + top: 0; + right: 20px !important; + } + button { + margin: 0 2px; + &.btn-star-add { + line-height: 24px !important; + color: $white; + &.btn-star-chk { + &:before { + content: 'star'; + @include font-family-ico('Material Icons', 14, center, #fdd73f); + position: absolute; + margin: 8px 0 0 -7px; + } + } + } + &.btn-person-add { + line-height: 24px !important; + color: $white; + } + } + } + .user-profile-info-area { + // Profile thumb////////////////// + .user-profile-thumb { + .ucap-organization-profile-image-01-container { + height: 138px; + @include profile-avatar-default( + 0, + 20, + $green, + 36px, + '.ucap-organization-presence-mobile-online', + '.ucap-organization-presence-mobile-offline', + '.ucap-organization-presence-mobile-busy', + '.ucap-organization-presence-mobile-away', + '.ucap-organization-presence-mobile-away2', + '.ucap-organization-presence-mobile-none' + ); + &.ucap-organization-presence-mobile-online, + &.ucap-organization-presence-mobile-offline, + &.ucap-organization-presence-mobile-busy, + &.ucap-organization-presence-mobile-away, + &.ucap-organization-presence-mobile-away2, + &.ucap-organization-presence-mobile-none { + padding: 10px 0 0; + } //오른 아래 공간, 모바일 온라인 아이콘 크기, 모바일 아이콘 색, 모바일 아이콘 bg크기 + padding: 10px 0 0; + align-self: start; + &:after { + margin-left: -36px !important; + } + .ucap-organization-presence { + @include presence-state(14px); + margin-top: -10px; + &-none { + @include presence-state(14px); + margin-top: -10px; + } + &-pc-online { + @include presence-state(14px, $green); + margin-top: -10px; + } + &-pc-offline { + @include presence-state(14px, $gray-rec); + margin-top: -10px; + } + &-pc-busy { + @include presence-state(14px, $yellow-other); + margin-top: -10px; + } + &-pc-away { + @include presence-state(14px, $red-absence); + margin-top: -10px; + } + &-pc-away2 { + @include presence-state(14px, $red-absence); + margin-top: -10px; + } + &-pc-none { + @include presence-state(14px); + margin-top: -10px; + } + } + .ucap-organization-profile-image-01-image-container { + @include avatar-img(128px, 0) { + background-color: #d1f6ff; + } + margin-left: -3px; + border: 3px solid $white; + img { + width: 122px; + } + } + } + .btn-profile-ctrl { + align-self: flex-end; + margin-left: -36px; + .mat-mini-fab.mat-accent { + background-color: #fd578a; + } + } + @include screen(xs) { + .ucap-organization-profile-image-01-container { + height: 82px; + @include profile-avatar-default( + 0, + 14, + $green, + 24px, + '.ucap-organization-presence-mobile-online', + '.ucap-organization-presence-mobile-offline', + '.ucap-organization-presence-mobile-busy', + '.ucap-organization-presence-mobile-away', + '.ucap-organization-presence-mobile-away2', + '.ucap-organization-presence-mobile-none' + ); + &.ucap-organization-presence-mobile-online, + &.ucap-organization-presence-mobile-offline, + &.ucap-organization-presence-mobile-busy, + &.ucap-organization-presence-mobile-away, + &.ucap-organization-presence-mobile-away2, + &.ucap-organization-presence-mobile-none { + padding: 10px 0 0; + } + &:after { + margin-left: -24px !important; + } + .ucap-organization-presence { + @include presence-state(12px); + margin-top: -10px; + &-none { + @include presence-state(12px); + margin-top: -10px; + } + &-pc-online { + @include presence-state(12px, $green); + margin-top: -10px; + } + &-pc-offline { + @include presence-state(12px, $gray-rec); + margin-top: -10px; + } + &-pc-busy { + @include presence-state(12px, $yellow-other); + margin-top: -10px; + } + &-pc-away { + @include presence-state(12px, $red-absence); + margin-top: -10px; + } + &-pc-away2 { + @include presence-state(12px, $red-absence); + margin-top: -10px; + } + &-pc-none { + @include presence-state(12px); + margin-top: -10px; + } + } + .ucap-organization-profile-image-01-image-container { + @include avatar-img(72px, 0); + margin-left: -2px; + border: 2px solid $white; + img { + width: 68px; + } + } + } + .btn-profile-ctrl { + margin-left: -24px; + button { + width: 24px !important; + height: 24px !important; + .mat-icon { + font-size: 14px; + height: 14px; + width: 14px; + line-height: 14px; + } + } + } + } + } + //////////////////Profile thumb // + .user-info { + padding-left: 38px; + .user-n-g { + height: 44px; + .name { + font: { + size: 1.857em; + weight: 600; + } + color: rgba(0, 0, 0, 0.8); + order: 1; + line-height: 1.2; + @include ellipsis-column(1); + @include wordBreak(); + line-break: anywhere; + flex: 1 1 auto; + } + .grade { + font: { + size: 1.286em; + } + color: rgba(255, 255, 255, 0.9); + margin-left: 6px; + order: 0; + -ms-flex-order: 0; + @include ellipsis-column(1); + @include wordBreak(); + line-break: anywhere; + flex: 1 0 auto; + } + & + .deptName { + margin-top: 8px; + min-height: 25px; + @include ellipsis-column(1); + @include wordBreak(); + } + } + .deptName { + font-size: 1.4em; + color: $white; + line-height: 25px; + font-weight: 600; + @include ellipsis(1); + //@include wordBreak(); + } + .nickname-area { + margin-top: 20px; + align-items: center; + min-height: 53px; + .nickname-info { + align-self: baseline; + .mat-form-field { + width: 100%; + height: 100%; + .mat-form-field-wrapper { + height: 100%; + padding-bottom: 0; + overflow: hidden; + .mat-form-field-flex { + height: 100%; + .mat-form-field-infix { + height: 100%; + width: 100%; + border-top: 0; + .mat-input-element { + color: #333333; + padding: 0 16px; + height: 30px; + line-height: 30px; + border-radius: 15px; + border: solid 1px #fc5182; + background-color: rgba(255, 255, 255, 0.95); + color: $gray-re9; + font-size: 1em; + width: 80%; + margin: 1px; + } + .mat-icon-button { + width: 30px; + height: 30px; + } + .mat-form-field-label { + line-height: 1.2em; + padding-left: 16px; + color: #999999; + } + } + } + } + .mat-form-field-subscript-wrapper { + position: relative; + margin-top: 4px; + padding-right: 40px; + } + } + } + } + @include screen(xs) { + padding-left: 20px; + .user-n-g { + height: 30px; + .name { + font: { + size: 1.286em; + weight: 600; + } + } + .grade { + font: { + size: 1em; + } + } + & + .deptName { + margin-top: 4px; + min-height: 20px; + } + } + .deptName { + line-height: 20px; + font-size: 0.929em; + font-weight: 400; + } + .nickname-area { + margin-top: 10px; + } + } + } + } + .address-txt { + font-size: 1.143em; + line-height: 21px; + min-height: 21px; + color: $gray-re3; + margin-top: 50px; + white-space: nowrap; + @include ellipsis-column(1); + display: block; + @include screen(xs) { + margin-top: 10px; + } + } + .btn-link-area { + cursor: default; + .btn-partner-set, + .btn-my-set { + display: flex; + align-items: center; + justify-content: space-between; + cursor: default; + padding: 10px 25px 0; + height: 90px; + margin-top: 11px; + border-top: 1px solid rgba(255, 255, 255, 0.8); + border-bottom: 1px solid rgba(255, 255, 255, 0.8); + .mat-icon-button { + &[disabled='true'] { + .mat-button-wrapper { + img { + opacity: 0.5; + } + } + } + } + img { + vertical-align: top; + } + } + .btn-my-set { + margin-top: -1px; + justify-content: space-around; + .mat-button { + padding: 0; + .mat-button-wrapper { + img { + width: 40px; + margin-right: 10px; + @include screen(xs) { + margin-right: 0; + } + } + .btn-label { + font-size: 1.1em; + color: #ffffff; + } + } + .mat-ripple { + border-radius: 50%; + width: 40px; + height: 40px; + @include screen(xs) { + width: 36px; + height: 36px; + margin-left: 2px; + } + } + .mat-button-focus-overlay { + background-color: transparent !important; + } + } + } + @include screen(xs) { + .btn-partner-set, + .btn-my-set { + padding: 10px 25px 0; + height: 65px; + img { + vertical-align: top; + width: 30px; + height: 30px; + } + } + } + } + .my-input { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + margin: 0; + width: 100%; + margin-top: 38px !important; + min-height: 55px; + @include screen(xs) { + margin-top: 23px !important; + } + .my-in-input { + font-size: 1.143em; + flex-grow: 1; + height: 24px; + line-height: 24px; + margin-top: 8px; + .mat-form-field-wrapper { + .mat-form-field-subscript-wrapper { + margin-top: 0; + } + } + } + button { + margin-bottom: 5px; + } + } + .user-profile-info-list { + margin-top: 50px; + ul { + display: flex; + flex-direction: column; + justify-content: space-between; + height: 250px; + li { + font-size: 1.143em; + font-weight: 600; + color: #584f52; + @include ellipsis(1); + span { + width: 100px; + height: 30px; + border-radius: 15px; + border: rgba(255, 255, 255, 0.6); + background-color: rgba(255, 255, 255, 0.4); + font-size: 0.9em; + font-weight: 600; + display: inline-flex; + align-items: center; + justify-content: center; + color: rgba(0, 0, 0, 0.6); + margin-right: 40px; + } + } + } + @include screen(xs) { + margin-top: 30px; + ul { + height: 200px; + li { + span { + width: 80px; + height: 30px; + margin-right: 10px; + line-height: 26px; + } + } + } + } + } + } +} + +//Profile Item 01 +.ucap-organization-profile-list-item-01-container { + min-height: 60px; + border-top: 1px solid $gray-rec; + border-color: $gray-rec !important; + padding-right: 16px !important; + padding-left: 5px !important; + position: relative; + &:hover { + transition: all 0.2s; + background-color: #f1f2f6; + } + @include screen(xs) { + padding-left: 20px; + } + .user-profile-info-content { + flex: 0 0 36%; + //padding-right: 10px; + @include screen(xs) { + padding-left: 16px; + flex-basis: 88%; + } + .profile-image { + height: 38px !important; + img { + @extend %ucapCursorPointer; + } + } + .user-info { + .user-n-g { + .user-name { + } + .user-grade { + } + } + .dept-name { + } + } + } + .company-info { + flex: 0 0 34%; + position: relative; + padding-left: 12px; + min-height: 60px; + justify-content: center; + display: flex !important; + flex-direction: column; + @include ellipsis(1); + @include screen(xs) { + display: none !important; + } + &::before { + content: ''; + width: 1px; + height: 36px; + background-color: $gray-rec; + position: absolute; + left: 0; + top: calc(50% - 18px); + } + .companyName { + @include ellipsis(1); + } + .email { + @include ellipsis(1); + } + } + .contact-info { + flex: 0 0 23%; + @include screen(custom, max, 1380) { + display: none; + } + .mobileNumber { + } + .officNumber { + } + } + .action-content { + position: absolute; + z-index: 5; + transition: all 0.2s linear; + right: calc(4% + 41px - 18px); + @include screen(xs) { + right: calc(4% + 56px - 9px); + } + bottom: 1px; + height: 58px; + width: 4px; + overflow: hidden; + background-color: rgba(255, 255, 255, 0.8); + display: flex; + flex-direction: row; + align-items: center; + flex-flow: nowrap; + padding: 0 12px 0 10px; + border-radius: 34px; + opacity: 0; + visibility: hidden; + button { + margin: 0 -4.5px; + img { + width: 24px; + } + } + &.visible-profile-menu { + visibility: visible !important; + transition: all 0.4s linear; + opacity: 1; + width: auto; + } + } + .checkbox-area { + flex: 0 0 4% !important; + position: relative; + padding-left: 20px; + &::before { + content: ''; + width: 1px; + height: 36px; + background-color: $gray-rec; + position: absolute; + left: 0; + top: calc(50% - 18px); + } + @include screen(xs) { + flex-basis: 12% !important; + padding-left: 12px; + &::before { + //display: none; + } + } + .mat-checkbox { + line-height: 1; + } + } +} + +//조직 선택 +.list-selection-container { + .mat-accordion { + } + .mat-expansion-panel-body { + padding: 10px 24px 16px; + } +} + +//Profile Item 02 +.ucap-organization-profile-list-item-02-container { + height: 70px; + .user-profile-info { + align-items: center; + overflow: hidden; + flex: 1 1 auto !important; + .profile-image { + min-width: 52px; + width: auto !important; + height: 38px !important; + flex-grow: 0; + flex-basis: auto; + } + .user-info { + padding-left: 16px; + flex: 1 1 auto; + max-width: calc(100% - 54px); + .user-n-g { + height: 22px; + flex: 1 1 auto; + .user-name { + flex: 0 0.9 auto; + height: 22px; + color: #212121; + @include ellipsis(1); + @include wordBreak(); + font: { + size: 1em; + weight: 600; + } + white-space: nowrap !important; + line-break: anywhere; + } + .user-grade { + flex: 0 1 auto; + @include ellipsis(1); + @include wordBreak(); + font: { + size: 0.929em; + } + color: #707070; + margin-left: 4px; + white-space: nowrap !important; + } + } + .dept-name { + flex: 1 0 auto; + @include ellipsis(1); + @include wordBreak(); + font: { + size: 0.857em; + } + color: #666666; + line-height: 16px; + white-space: nowrap !important; + } + } + } + .user-intro { + flex: 0 0 auto; + display: flex; + flex-direction: row-reverse; + overflow: hidden; + max-width: 35%; + .intro-content { + display: flex; + flex-direction: row; + justify-content: flex-end; + flex: 1 0 auto; + max-width: 100%; + .intro { + display: inline-flex; + flex-flow: row nowrap; + flex-grow: 0; + align-items: baseline; + p { + font-size: 0.786em; + line-height: 1.4; + @include ellipsis(2); + @include wordBreak(); + max-height: 30px; + } + &:before { + content: 'chat'; + @include font-family-ico($font-ico-default, 12, center, $lipstick); + flex-direction: row; + align-items: flex-start; + width: 12px; + height: 12px; + line-height: 12px; + margin-right: 4.8px; + position: relative; + top: 2px; + } + } + .intro-name { + max-width: 100%; + p { + text-align: center; + width: 100%; + height: 20px; + line-height: 18px; + color: $gray-re70; + font-size: 0.857em; + padding: 0 5px; + border-radius: 30px; + border: solid 1px $warm-pink; + background-color: #ffffff; + @include ellipsis-column(1); + @include wordBreak(); + line-break: anywhere; + } + } + } + } + .action-content { + position: absolute; + z-index: 5; + transition: all 0.1s linear; + right: -200px; + bottom: 0; + height: 59px; + background-color: rgba(255, 255, 255, 0.8); + display: flex; + flex-direction: row; + align-items: center; + flex-flow: nowrap; + padding: 0 12px 0 10px; + border-radius: 35px 0 0 34px; + visibility: hidden; + button { + margin: 0 -4.5px; + img { + width: 24px; + } + } + &.visible-profile-menu { + visibility: visible !important; + transition: all 0.2s linear; + right: 0; + } + } + + .mat-checkbox { + margin-left: 15px; + } +} + +//Organization List +.ucap-organization-profile-list-container { + .ucap-virtual-scroll-viewport { + .ucap-virtual-scroll-content-wrapper { + width: 100%; + div { + &:first-of-type { + .ucap-organization-profile-list-item-01-container { + border-top: none; + } + } + } + } + } +} + +//Tree /////////////////////////// +//Group Tree +.ucap-group-expansion-container { + .mat-tree { + .mat-tree-node { + li { + width: 100%; + &:hover { + .profile-list-container { + background-color: #f7f8fa; + } + } + .current-user { + .profile-list-container { + background-color: #f7f8fa; + .ucap-organization-profile-image-01-image-container { + background-color: #d1f6ff; + } + } + } + } + &:first-of-type { + &[node-type='Profile'] { + .profile-list-container { + border-top: none; + background-color: #f7f8fa; + .ucap-organization-profile-image-01-image-container { + background-color: #d1f6ff; + } + } + } + } + } + } +} + +// Organization Tree +.ucap-organization-tree-container { + .ucap-virtual-scroll-viewport { + .ucap-virtual-scroll-content-wrapper { + &:before { + content: ''; + width: 21px; + height: 100%; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + position: absolute; + z-index: -1; + left: 90px; + bottom: 0; + } + } + } + .mat-tree { + &:before { + width: 21px !important; + } + &:after { + width: 21px !important; + } + .tree-has-child, + .tree-no-child { + li { + .tree-node-body { + cursor: pointer; + } + .current { + background-color: #fd578a !important; + color: #fff !important; + font-weight: 600; + border-width: 0 !important; + .mat-icon-button, + .dept-name { + color: #fff !important; + } + } + } + } + + mat-tree-node[aria-expanded='true'] { + &.tree-has-child, + &.tree-no-child { + &::before { + top: -6px; + } + &:after { + //margin-top: -9px !important; + //height: 56px !important; + height: 73px !important; + } + } + &:last-of-type { + &.tree-has-child, + &.tree-no-child { + &:after { + margin-top: 0 !important; + height: 56px !important; + } + } + } + } + mat-tree-node[aria-expanded='false'] { + &::before { + top: -6px; + } + &.tree-has-child, + &.tree-no-child { + &:after { + //margin-top: -9px !important; + //height: 56px !important; + height: 73px !important; + } + } + &:last-of-type { + &.tree-has-child, + &.tree-no-child { + &:after { + margin-top: 0 !important; + height: 56px !important; + } + } + } + } + } +} + +//Search +.ucap-organization-search-for-tenant-container { + .company-container { + flex: 0 0 130px !important; + max-width: 130px !important; + min-width: 130px !important; + padding: 0 8px 0 12px; + } + max-width: 900px; + margin: 0 auto; +} +.index-page-container { + .search-container { + padding: 0 30px 10px !important; + @include screen(xs) { + padding: 0 16px 10px !important; + } + } +} +.search-wrpper { + background-color: #fff; + .profile-list-item-container { + .ucap-organization-profile-list-container { + .ucap-virtual-scroll-viewport { + .profile-list-container { + &:hover { + transition: all 0.3s; + background-color: #f7f8fa; + } + } + .ps__rail-x { + display: none; + } + } + } + } +} + +//Top Toolbar +.my-profile { + cursor: pointer; + .ucap-organization-profile-image-01-container { + height: 30px; + span { + position: relative; + align-self: flex-end; + margin-left: -10px; + order: 2; + width: 10px !important; + height: 10px !important; + } + .ucap-organization-profile-image-01-image-container { + @include avatar-img(30px, 0) { + background-color: #d1f6ff; + } + } + &::after { + display: none !important; + } + } +} +//Profile Menu +.ucap-organization-profile-menu-01 { + .ucap-organization-profile-menu-01-container { + //display: flex; + //flex-direction: column; + ucap-inline-edit-input { + display: flex; + flex-direction: row; + flex-grow: 1; + span { + &[ucapinlineeditinput='view'] { + flex: 0 1 auto; + @include ellipsis-column(1); + @include wordBreak(); + line-break: anywhere; + height: 30px; + line-height: 30px; + } + &[ucapinlineeditinput='edit'] { + flex: 1 1 auto; + @include ellipsis-column(1); + @include wordBreak(); + line-break: anywhere; + height: 30px; + line-height: 30px; + border-bottom: solid 1px #999999; + } + } + .view-actions { + flex: 0 0 auto; + height: 30px; + .mat-icon-button { + width: 30px; + height: 30px; + line-height: 30px; + cursor: ew-resize; + } + } + .edit-actions { + flex: 0 0 auto; + height: 30px; + border-bottom: solid 1px #999999; + .mat-icon-button { + width: 30px; + height: 30px; + line-height: 30px; + color: $lipstick; + } + } + } + + .ucap-organization-profile-menu-01-profile { + //profile + padding: 12px 20px 0; + .ucap-organization-profile-list-item-02-container { + .user-profile-info { + .profile-image { + height: 46px !important; + .profile-image-container { + .ucap-organization-profile-image-01-container { + height: 46px; + span { + position: relative; + align-self: flex-end; + margin-left: -12px; + order: 2; + width: 16px !important; + height: 16px !important; + border: 2px solid #fff; + } + .ucap-organization-profile-image-01-image-container { + @include avatar-img(46px, 0) { + background-color: #d1f6ff; + } + } + &::after { + display: none !important; + } + } + } + } + .user-info { + .user-n-g { + .user-name { + font-size: 1.143em; + } + .user-grade { + font-size: 1em; + } + } + .dept-name { + } + } + } + } + } + .ucap-organization-profile-menu-01-intro { + //profile intro + padding: 13px 20px 0; + } + .ucap-organization-profile-menu-01-intro-below { + //display: flex; + //flex-direction: row; + padding-top: 18px; + margin: 0 20px; + height: 80px; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #ccc; + .btn-profile-obj { + width: 33.3%; + height: 63px; + position: relative; + &::before { + content: ''; + width: 1px; + height: 30px; + background-color: #ccc; + position: absolute; + z-index: 5; + left: 0; + top: 13px; + } + &:first-of-type { + &::before { + display: none; + } + } + .mat-button { + width: 100%; + height: 63px; + .mat-button-wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + .mat-icon { + font-size: 28px; + width: 28px; + height: 28px; + line-height: 28px; + color: #666; + } + strong { + font-size: 0.857em; + color: #666; + } + } + } + } + } + .ucap-organization-profile-menu-01-status-message { + //display: flex; + //flex-direction: column; + padding: 7px 20px 0; + .status-message-online { + display: flex; + flex-direction: row; + align-items: center; + height: 30px; + @extend %ucapCursorPointer; + &::before { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background-color: $green; + margin-right: 8px; + flex: 0 0 10px; + } + &:hover { + color: $green; + } + } + .status-message-away { + display: flex; + flex-direction: row; + align-items: center; + height: 30px; + @extend %ucapCursorPointer; + &::before { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background-color: $red-absence; + margin-right: 8px; + flex: 0 0 10px; + } + &:hover { + color: $red-absence; + } + .mat-radio-group { + flex: 1 0 auto; + text-align: right; + .mat-radio-button { + margin-left: 6px; + .mat-radio-label { + .mat-radio-container { + .mat-radio-ripple { + left: calc(50% - 12px); + top: calc(50% - 12px); + height: 24px; + width: 24px; + } + } + .mat-radio-label-content { + padding-left: 2px; + } + } + } + } + } + .status-message-busy1 { + display: flex; + flex-direction: row; + align-items: center; + @extend %ucapCursorPointer; + &::before { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background-color: $yellow-other; + margin-right: 8px; + flex: 0 0 10px; + } + } + .status-message-busy2 { + display: flex; + flex-direction: row; + align-items: center; + @extend %ucapCursorPointer; + &::before { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background-color: $yellow-other; + margin-right: 8px; + flex: 0 0 10px; + } + } + .status-message-busy3 { + display: flex; + flex-direction: row; + align-items: center; + @extend %ucapCursorPointer; + &::before { + content: ''; + width: 10px; + height: 10px; + border-radius: 50%; + background-color: $yellow-other; + margin-right: 8px; + flex: 0 0 10px; + } + } + .status-message-busy1, + .status-message-busy2, + .status-message-busy3 { + span { + &[ucapinlineeditinput='view'] { + &:hover { + color: $yellow-other; + } + } + } + } + } + .ucap-organization-profile-menu-01-btn-area { + //display: flex; + //flex-direction: row; + align-items: center; + margin: 10px 0 0; + width: 100%; + border-top: 1px solid #ccc; + height: 45px; + .btn-profile-menu1 { + flex: 0 0 50%; + text-align: center; + .mat-button { + width: 100%; + } + } + .btn-profile-menu2 { + flex: 0 0 50%; + text-align: center; + position: relative; + &::before { + content: ''; + width: 1px; + height: 16px; + background-color: #ccc; + position: absolute; + z-index: 5; + left: 0; + top: 11px; + } + .mat-button { + width: 100%; + } + } + } + } +} + +//Line Tree +.ucap-breadcrumb { + background-color: #f1f2f6; + .ucap-breadcrumb-container { + .mat-tab-header-pagination-before { + background-color: #fff; + box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); + } + .mat-tab-header-pagination-after { + background-color: #fff; + box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12); + } + .ucap-breadcrumb-list { + .ucap-breadcrumb-labels { + .organization-tree-simple { + .organization-dept { + button { + &.ucap-breadcrumb-node { + margin: 0 5px; + } + } + } + } + } + } + } } diff --git a/src/assets/scss/lg.scss b/src/assets/scss/lg.scss deleted file mode 100644 index 6360582..0000000 --- a/src/assets/scss/lg.scss +++ /dev/null @@ -1,4 +0,0 @@ -// Material theming tools -@import '~@angular/material/theming'; - -@import 'setting/variables'; diff --git a/src/assets/scss/mixins/_dom.scss b/src/assets/scss/mixins/_dom.scss index 9f19570..1d730c5 100644 --- a/src/assets/scss/mixins/_dom.scss +++ b/src/assets/scss/mixins/_dom.scss @@ -56,3 +56,73 @@ -webkit-line-clamp: $column; -webkit-box-orient: vertical; } + +//Button +//Bottom button type +@mixin ucap-button-flat-stroked($button-width: auto) { + border-radius: 2px; + font-size: 0.929em; + & + button { + margin-left: 4px; + } + width: $button-width; + @content; +} + +//Word Break +@mixin wordBreak() { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-word; +} + +//Mat Menu +@mixin menu-title($fontSize: 1em, $fontWeight: 600, $fontColor: #5c444b) { + font-size: $fontSize; + font-weight: $fontWeight; + color: $fontColor; + width: 100%; + line-height: 20px; + padding: 2px 16px 10px; + border-bottom: 1px solid #ccc; + display: block; + @include wordBreak(); + @content; +} + +////////// +// Mat Mixin background-color: ex)$accent, 700, class name or tag name +@mixin colorMix-background($theme-type1, $theme-type2, $class-Name...) { + @for $i from 0 to length($class-Name) { + #{nth($class-Name, $i + 1)} { + background-color: mat-color($theme-type1, $theme-type2); + } + } +} + +// Mat Mixin text-color: ex)$accent, 700, class name or tag name +@mixin colorMix-text($theme-type1, $theme-type2, $class-Name...) { + @for $i from 0 to length($class-Name) { + #{nth($class-Name, $i + 1)} { + color: mat-color($theme-type1, $theme-type2); + } + } +} +// Mat Mixin bg+text-color: ex)$accent, 700, $accent, B200 class name or tag name +@mixin colorMix-backgroundText( + $themeT-type1, + $themeT-type2, + $bg-theme-type, + $numbering, + $class-Name-bg... +) { + @for $i from 0 to length($class-Name-bg) { + #{nth($class-Name-bg, $i + 1)} { + color: mat-color($themeT-type1, $themeT-type2); + background: mat-color($bg-theme-type, $numbering); + + //@if str-slice(#{$class-Name-bg}, -5, -1) == 'title' { + //} + } + } +} diff --git a/src/assets/scss/mixins/_ucap-organization.scss b/src/assets/scss/mixins/_ucap-organization.scss index 5282b13..6c45364 100644 --- a/src/assets/scss/mixins/_ucap-organization.scss +++ b/src/assets/scss/mixins/_ucap-organization.scss @@ -1,22 +1,23 @@ -@mixin presence-state($circle-size: 8px) { +@mixin presence-state($circle-size: 8px, $bg-color: transparent) { //pc상태설정 display: flex; align-items: flex-start; width: $circle-size; height: $circle-size; border-radius: 50%; - background-color: $green; + background-color: $bg-color; transform: none; + margin: 0; @extend %blind; - &.offline { - background-color: $gray-rec; - } - &.absence { - background-color: $red-absence; - } - &.other-business { - background-color: $yellow-other; - } + //&.offline { + // background-color: $gray-rec; + //} + //&.absence { + // background-color: $red-absence; + //} + //&.other-business { + // background-color: $yellow-other; + //} @content; } @mixin avatar-img($avatar-size: 36px, $avatar-left: 0) { @@ -31,33 +32,38 @@ //profile avatar @mixin profile-avatar-default( - $mobile-on: 0 5px 5px 0, - $mico-size: 8, + $mobile-on: 0, + $mico-size: 14, $mico-color: $green, - $mico-bg: 18px + $mico-bg: 18px, + $class-name... ) { //기본설정 display: inline-flex; - position: relative; - &.mobile-ing { - padding: $mobile-on; - &:after { - @include font-family-ico( - $font-ico-default, - $mico-size, - center, - $mico-color - ); - content: 'smartphone'; - display: block; - width: $mico-bg; - height: $mico-bg; - line-height: $mico-bg; - border-radius: 50%; - background-color: $white; - position: absolute; - bottom: 0; - right: 0; + //position: relative; + @for $i from 0 to length($class-name) { + &#{nth($class-name, $i + 1)} { + padding: $mobile-on; + &:after { + @include font-family-ico( + $font-ico-default, + $mico-size, + center, + $mico-color + ); + content: 'smartphone'; + display: block; + width: $mico-bg; + height: $mico-bg; + line-height: $mico-bg; + border-radius: 50%; + background-color: $white; + //position: absolute; + //bottom: 0; + //right: 0; + align-self: flex-end; + margin-left: -10px; + } } } @content; diff --git a/src/assets/scss/partials/_material-ui.scss b/src/assets/scss/partials/_material-ui.scss deleted file mode 100644 index 59557c5..0000000 --- a/src/assets/scss/partials/_material-ui.scss +++ /dev/null @@ -1,3 +0,0 @@ -.cdk-global-scrollblock { - overflow: hidden !important; -} diff --git a/src/assets/scss/setting/_variables.scss b/src/assets/scss/setting/_variables.scss index 483b2f9..38f5524 100644 --- a/src/assets/scss/setting/_variables.scss +++ b/src/assets/scss/setting/_variables.scss @@ -50,6 +50,9 @@ $txt-color01: $gray-re6; // Line Color $line-color-gray01: #d4d4d4; +$line-color-gray02: #cccccc; +$line-color-gray03: #999999; +$line-color-gray04: #666666; // Bg Color $body-bg: $white !default; @@ -190,6 +193,8 @@ $ucap-color-warn: ( ); /////////////////////////////Theming color set // +// @media///////////////////////////// + $ucap-screen-max-xs: 575; $ucap-screen-min-sm: 576; $ucap-screen-max-sm: 767; @@ -198,3 +203,24 @@ $ucap-screen-max-md: 991; $ucap-screen-min-lg: 992; $ucap-screen-max-lg: 1199; $ucap-screen-min-xl: 1200; + +/////////////////////////////@media // + +// padding///////////////////////////// + +$default-space: 16px; + +/////////////////////////////padding // + +// line-color ///////////////////////////// + +/////////////////////////////line-color // + +//virtual-scroll 사이즈 조정 +.ps__thumb-y { + width: 4px !important; +} + +.ps__rail-y { + width: 4px !important; +} diff --git a/src/assets/scss/ucap.scss b/src/assets/scss/ucap.scss deleted file mode 100644 index f11d7c4..0000000 --- a/src/assets/scss/ucap.scss +++ /dev/null @@ -1,11 +0,0 @@ -// Material theming tools -@import '~@angular/material/theming'; - -// Include core Angular Material styles -@include mat-core(); - -// Partials -@import 'partials/material-ui'; - -//creative -@import 'global/default'; diff --git a/src/environments/environment.hmr.ts b/src/environments/environment.hmr.ts index 0057030..b38b0bc 100644 --- a/src/environments/environment.hmr.ts +++ b/src/environments/environment.hmr.ts @@ -1,5 +1,5 @@ import { DeviceType, DesktopType, NotificationMethod } from '@ucap/core'; -import { BrowserNativeService } from '@ucap/ng-native-browser'; +import { NativeService, I18NEXT } from './native/native'; import { Environment, @@ -21,9 +21,32 @@ export const environment: Environment = { productConfig: { productId: 'PRO_000482', - productName: 'EZMessenger', + productName: 'MMessenger', localEncriptionKey: '!@#$LG%^&*', - nativeServiceClass: BrowserNativeService, + nativeServiceClass: NativeService, + i18next: { + options: { + whitelist: ['ko', 'en'], + fallbackLng: 'en', + debug: true, + returnEmptyString: false, + ns: [ + 'common', + 'organization', + 'authentication', + 'group', + 'locale', + 'chat', + 'call', + 'message' + ], + backend: { + loadPath: `${I18NEXT.loadPathRoot}assets/i18n/{{lng}}/{{ns}}.json` + } + }, + useBackends: I18NEXT.backends + }, + supportedLanguages: ['ko', 'en'], updateCheck: { deviceType: DeviceType.Renderer, devicePlatformType: DesktopType.Windows, @@ -58,7 +81,7 @@ export const environment: Environment = { } }, file: { - defaultDownloadFolder: 'LF Talk2 Download', + defaultDownloadFolder: 'MMessenger Download', defaultFileAllowSize: 100 }, authentication: { @@ -72,7 +95,8 @@ export const environment: Environment = { editableProfileImage: false }, organization: { - displayRoot: false + displayRoot: false, + supportedLanguages: ['ko', 'en'] }, group: { useMyDeptGroup: false, @@ -104,7 +128,7 @@ export const environment: Environment = { hostConfig: { protocol: 'http', domain: '13.124.88.127', - port: 8033 + port: 8011 }, urls: commonApiUrls, acceptableFileExtensions: commonApiacceptableFileExtensions, diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index c666ce1..94bb8bb 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,5 +1,5 @@ import { DeviceType, DesktopType, NotificationMethod } from '@ucap/core'; -import { BrowserNativeService } from '@ucap/ng-native-browser'; +import { NativeService, I18NEXT } from './native/native'; import { Environment, @@ -21,9 +21,32 @@ export const environment: Environment = { productConfig: { productId: 'PRO_000482', - productName: 'EZMessenger', + productName: 'MMessenger', localEncriptionKey: '!@#$LG%^&*', - nativeServiceClass: BrowserNativeService, + nativeServiceClass: NativeService, + i18next: { + options: { + whitelist: ['ko', 'en'], + fallbackLng: 'en', + debug: true, + returnEmptyString: false, + ns: [ + 'common', + 'organization', + 'authentication', + 'group', + 'locale', + 'chat', + 'call', + 'message' + ], + backend: { + loadPath: `${I18NEXT.loadPathRoot}assets/i18n/{{lng}}/{{ns}}.json` + } + }, + useBackends: I18NEXT.backends + }, + supportedLanguages: ['ko', 'en'], updateCheck: { deviceType: DeviceType.Renderer, devicePlatformType: DesktopType.Windows, @@ -58,7 +81,7 @@ export const environment: Environment = { } }, file: { - defaultDownloadFolder: 'LF Talk2 Download', + defaultDownloadFolder: 'MMessenger Download', defaultFileAllowSize: 100 }, authentication: { @@ -72,7 +95,8 @@ export const environment: Environment = { editableProfileImage: false }, organization: { - displayRoot: false + displayRoot: false, + supportedLanguages: ['ko', 'en'] }, group: { useMyDeptGroup: false, @@ -104,7 +128,7 @@ export const environment: Environment = { hostConfig: { protocol: 'http', domain: '13.124.88.127', - port: 8033 + port: 8011 }, urls: commonApiUrls, acceptableFileExtensions: commonApiacceptableFileExtensions, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index fa52cfa..9c93591 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -2,7 +2,7 @@ // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // The list of file replacements can be found in `angular.json`. import { DeviceType, DesktopType, NotificationMethod } from '@ucap/core'; -import { BrowserNativeService } from '@ucap/ng-native-browser'; +import { NativeService, I18NEXT } from './native/native'; import { Environment, @@ -24,9 +24,32 @@ export const environment: Environment = { productConfig: { productId: 'PRO_000482', - productName: 'EZMessenger', + productName: 'MMessenger', localEncriptionKey: '!@#$LG%^&*', - nativeServiceClass: BrowserNativeService, + nativeServiceClass: NativeService, + i18next: { + options: { + whitelist: ['ko', 'en'], + fallbackLng: 'en', + debug: true, + returnEmptyString: false, + ns: [ + 'common', + 'organization', + 'authentication', + 'group', + 'locale', + 'chat', + 'call', + 'message' + ], + backend: { + loadPath: `${I18NEXT.loadPathRoot}assets/i18n/{{lng}}/{{ns}}.json` + } + }, + useBackends: I18NEXT.backends + }, + supportedLanguages: ['ko', 'en'], updateCheck: { deviceType: DeviceType.Renderer, devicePlatformType: DesktopType.Windows, @@ -61,7 +84,7 @@ export const environment: Environment = { } }, file: { - defaultDownloadFolder: 'LF Talk2 Download', + defaultDownloadFolder: 'MMessenger Download', defaultFileAllowSize: 100 }, authentication: { @@ -75,7 +98,8 @@ export const environment: Environment = { editableProfileImage: false }, organization: { - displayRoot: false + displayRoot: false, + supportedLanguages: ['ko', 'en'] }, group: { useMyDeptGroup: false, diff --git a/src/environments/environment.type.ts b/src/environments/environment.type.ts index b638a3c..b079b1e 100644 --- a/src/environments/environment.type.ts +++ b/src/environments/environment.type.ts @@ -49,6 +49,18 @@ export interface Environment { productName: string; localEncriptionKey: string; nativeServiceClass: Type; + i18next: { + options: { + whitelist?: string[]; + fallbackLng?: string; + debug?: boolean; + returnEmptyString?: boolean; + ns?: string[]; + backend?: any; + }; + useBackends?: any[]; + }; + supportedLanguages: string[]; updateCheck: { deviceType: DeviceType; devicePlatformType: DesktopType; @@ -75,6 +87,7 @@ export interface Environment { organization: { /** 최상위 노드 출력 여부 */ displayRoot: boolean; + supportedLanguages: string[]; }; group: { /** 소속부서(내부서) 그룹핑 사용여부 */ diff --git a/src/environments/native/native.browser.prod.ts b/src/environments/native/native.browser.prod.ts new file mode 100644 index 0000000..4dd374c --- /dev/null +++ b/src/environments/native/native.browser.prod.ts @@ -0,0 +1,8 @@ +export { BrowserNativeService as NativeService } from '@ucap/ng-native'; +import xhr from 'i18next-xhr-backend'; +import languageDetector from 'i18next-browser-languagedetector'; + +export const I18NEXT = { + backends: [xhr, languageDetector], + loadPathRoot: '' +}; diff --git a/src/environments/native/native.browser.ts b/src/environments/native/native.browser.ts new file mode 100644 index 0000000..4dd374c --- /dev/null +++ b/src/environments/native/native.browser.ts @@ -0,0 +1,8 @@ +export { BrowserNativeService as NativeService } from '@ucap/ng-native'; +import xhr from 'i18next-xhr-backend'; +import languageDetector from 'i18next-browser-languagedetector'; + +export const I18NEXT = { + backends: [xhr, languageDetector], + loadPathRoot: '' +}; diff --git a/src/environments/native/native.renderer.prod.ts b/src/environments/native/native.renderer.prod.ts new file mode 100644 index 0000000..54c3782 --- /dev/null +++ b/src/environments/native/native.renderer.prod.ts @@ -0,0 +1,8 @@ +export { ElectronNativeService as NativeService } from '@ucap/electron-native'; +import fs from 'i18next-fs-backend'; +import languageDetector from 'i18next-browser-languagedetector'; + +export const I18NEXT = { + backends: [fs, languageDetector], + loadPathRoot: 'resources/app.asar/dist/' +}; diff --git a/src/environments/native/native.renderer.ts b/src/environments/native/native.renderer.ts new file mode 100644 index 0000000..98cc485 --- /dev/null +++ b/src/environments/native/native.renderer.ts @@ -0,0 +1,8 @@ +export { ElectronNativeService as NativeService } from '@ucap/electron-native'; +import xhr from 'i18next-xhr-backend'; +import languageDetector from 'i18next-browser-languagedetector'; + +export const I18NEXT = { + backends: [xhr, languageDetector], + loadPathRoot: '' +}; diff --git a/src/environments/native/native.ts b/src/environments/native/native.ts new file mode 100644 index 0000000..4dd374c --- /dev/null +++ b/src/environments/native/native.ts @@ -0,0 +1,8 @@ +export { BrowserNativeService as NativeService } from '@ucap/ng-native'; +import xhr from 'i18next-xhr-backend'; +import languageDetector from 'i18next-browser-languagedetector'; + +export const I18NEXT = { + backends: [xhr, languageDetector], + loadPathRoot: '' +}; diff --git a/src/index.html b/src/index.html index acbaf06..a06b14a 100644 --- a/src/index.html +++ b/src/index.html @@ -6,8 +6,467 @@ + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    Welcome to Messenger

    +
    diff --git a/tsconfig.app.renderer.json b/tsconfig.app.renderer.json new file mode 100644 index 0000000..86b53b7 --- /dev/null +++ b/tsconfig.app.renderer.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "target": "ES5", + "types": [] + }, + "files": ["src/main.ts", "src/polyfills.ts"], + "include": ["src/**/*.d.ts"] +}