0710 sync
This commit is contained in:
parent
cbe1ddbd29
commit
bf63e11021
38
angular.json
38
angular.json
|
@ -1074,44 +1074,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"native-browser": {
|
||||
"projectType": "library",
|
||||
"root": "projects/native-browser",
|
||||
"sourceRoot": "projects/native-browser/src",
|
||||
"prefix": "lib",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-ng-packagr:build",
|
||||
"options": {
|
||||
"tsConfig": "projects/native-browser/tsconfig.lib.json",
|
||||
"project": "projects/native-browser/ng-package.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"tsConfig": "projects/native-browser/tsconfig.lib.prod.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "projects/native-browser/src/test.ts",
|
||||
"tsConfig": "projects/native-browser/tsconfig.spec.json",
|
||||
"karmaConfig": "projects/native-browser/karma.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"projects/native-browser/tsconfig.lib.json",
|
||||
"projects/native-browser/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": ["**/node_modules/**"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"store-authentication": {
|
||||
"projectType": "library",
|
||||
"root": "projects/store-authentication",
|
||||
|
|
760
package-lock.json
generated
760
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
94
package.json
94
package.json
|
@ -16,9 +16,8 @@
|
|||
"build:pi": "node ./scripts/build.js pi",
|
||||
"build:core": "node ./scripts/build.js core",
|
||||
"build:logger": "node ./scripts/build.js logger",
|
||||
"build:native:all": "npm-run-all -s build:native build:native-browser",
|
||||
"build:native:all": "npm-run-all -s build:native",
|
||||
"build:native": "node ./scripts/build.js native",
|
||||
"build:native-browser": "node ./scripts/build.js native-browser",
|
||||
"build:protocol:all": "npm-run-all -s build:protocol build:protocol-authentication build:protocol-buddy build:protocol-event build:protocol-file build:protocol-group build:protocol-status build:protocol-info build:protocol-inner build:protocol-option build:protocol-ping build:protocol-query build:protocol-room build:protocol-service build:protocol-sync build:protocol-umg",
|
||||
"build:protocol": "node ./scripts/build.js protocol",
|
||||
"build:protocol-authentication": "node ./scripts/build.js protocol-authentication",
|
||||
|
@ -65,9 +64,8 @@
|
|||
"publish:pi": "cd ./dist/pi && npm publish",
|
||||
"publish:core": "cd ./dist/core && npm publish",
|
||||
"publish:logger": "cd ./dist/logger && npm publish",
|
||||
"publish:native:all": "npm-run-all -s publish:native publish:native-browser",
|
||||
"publish:native:all": "npm-run-all -s publish:native",
|
||||
"publish:native": "cd ./dist/native && npm publish",
|
||||
"publish:native-browser": "cd ./dist/native-browser && npm publish",
|
||||
"publish:protocol:all": "npm-run-all -s publish:protocol publish:protocol-authentication publish:protocol-buddy publish:protocol-event publish:protocol-file publish:protocol-group publish:protocol-status publish:protocol-info publish:protocol-inner publish:protocol-option publish:protocol-ping publish:protocol-query publish:protocol-room publish:protocol-service publish:protocol-sync publish:protocol-umg",
|
||||
"publish:protocol": "cd ./dist/protocol && npm publish",
|
||||
"publish:protocol-authentication": "cd ./dist/protocol-authentication && npm publish",
|
||||
|
@ -110,35 +108,33 @@
|
|||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addon-knobs": "^5.3.18",
|
||||
"tslib": "^1.10.0"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^0.900.6",
|
||||
"@angular-devkit/build-ng-packagr": "^0.900.6",
|
||||
"@angular/animations": "^9.0.6",
|
||||
"@angular/cdk": "^9.1.2",
|
||||
"@angular/cli": "^9.0.6",
|
||||
"@angular/common": "^9.0.6",
|
||||
"@angular/compiler": "^9.0.6",
|
||||
"@angular/compiler-cli": "^9.0.6",
|
||||
"@angular/core": "^9.0.6",
|
||||
"@angular/flex-layout": "^9.0.0-beta.29",
|
||||
"@angular/forms": "^9.0.6",
|
||||
"@angular/language-service": "^9.0.6",
|
||||
"@angular/material": "^9.1.2",
|
||||
"@angular/material-moment-adapter": "^9.1.2",
|
||||
"@angular/platform-browser": "^9.0.6",
|
||||
"@angular/platform-browser-dynamic": "^9.0.6",
|
||||
"@angular/router": "^9.0.6",
|
||||
"@angular/animations": "^9.1.11",
|
||||
"@angular/cdk": "^9.2.4",
|
||||
"@angular/cli": "^9.1.9",
|
||||
"@angular/common": "^9.1.11",
|
||||
"@angular/compiler": "^9.1.11",
|
||||
"@angular/compiler-cli": "^9.1.11",
|
||||
"@angular/core": "^9.1.11",
|
||||
"@angular/flex-layout": "^9.0.0-beta.31",
|
||||
"@angular/forms": "^9.1.11",
|
||||
"@angular/language-service": "^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",
|
||||
"@babel/core": "^7.9.0",
|
||||
"@ngrx/effects": "^9.0.0",
|
||||
"@ngrx/entity": "^9.0.0",
|
||||
"@ngrx/router-store": "^9.0.0",
|
||||
"@ngrx/store": "^9.0.0",
|
||||
"@ngrx/store-devtools": "^9.0.0",
|
||||
"@ngrx/effects": "^9.2.0",
|
||||
"@ngrx/entity": "^9.2.0",
|
||||
"@ngrx/router-store": "^9.2.0",
|
||||
"@ngrx/store": "^9.2.0",
|
||||
"@ngrx/store-devtools": "^9.2.0",
|
||||
"@storybook/addon-actions": "^5.3.18",
|
||||
"@storybook/addon-knobs": "^5.3.18",
|
||||
"@storybook/addon-links": "^5.3.18",
|
||||
"@storybook/addon-notes": "^5.3.18",
|
||||
"@storybook/addons": "^5.3.18",
|
||||
|
@ -148,15 +144,14 @@
|
|||
"@types/moment-timezone": "^0.5.12",
|
||||
"@types/node": "^12.12.30",
|
||||
"@ucap/api": "~0.0.1",
|
||||
"@ucap/api-common": "~0.0.5",
|
||||
"@ucap/api-common": "~0.0.7",
|
||||
"@ucap/api-external": "~0.0.2",
|
||||
"@ucap/api-message": "~0.0.1",
|
||||
"@ucap/api-prompt": "~0.0.1",
|
||||
"@ucap/api-public": "~0.0.1",
|
||||
"@ucap/core": "~0.0.6",
|
||||
"@ucap/core": "~0.0.14",
|
||||
"@ucap/logger": "~0.0.12",
|
||||
"@ucap/native": "~0.0.1",
|
||||
"@ucap/native-browser": "~0.0.1",
|
||||
"@ucap/native": "~0.0.19",
|
||||
"@ucap/ng-api-common": "file:pack/ucap-ng-api-common-0.0.1.tgz",
|
||||
"@ucap/ng-api-external": "file:pack/ucap-ng-api-external-0.0.1.tgz",
|
||||
"@ucap/ng-api-message": "file:pack/ucap-ng-api-message-0.0.1.tgz",
|
||||
|
@ -165,8 +160,7 @@
|
|||
"@ucap/ng-core": "file:pack/ucap-ng-core-0.0.7.tgz",
|
||||
"@ucap/ng-i18n": "file:pack/ucap-ng-i18n-0.0.6.tgz",
|
||||
"@ucap/ng-logger": "file:pack/ucap-ng-logger-0.0.2.tgz",
|
||||
"@ucap/ng-native": "file:pack/ucap-ng-native-0.0.1.tgz",
|
||||
"@ucap/ng-native-browser": "file:pack/ucap-ng-native-browser-0.0.1.tgz",
|
||||
"@ucap/ng-native": "file:pack/ucap-ng-native-0.0.5.tgz",
|
||||
"@ucap/ng-pi": "file:pack/ucap-ng-pi-0.0.1.tgz",
|
||||
"@ucap/ng-protocol": "file:pack/ucap-ng-protocol-0.0.3.tgz",
|
||||
"@ucap/ng-protocol-authentication": "file:pack/ucap-ng-protocol-authentication-0.0.3.tgz",
|
||||
|
@ -184,37 +178,37 @@
|
|||
"@ucap/ng-protocol-status": "file:pack/ucap-ng-protocol-status-0.0.3.tgz",
|
||||
"@ucap/ng-protocol-sync": "file:pack/ucap-ng-protocol-sync-0.0.3.tgz",
|
||||
"@ucap/ng-protocol-umg": "file:pack/ucap-ng-protocol-umg-0.0.3.tgz",
|
||||
"@ucap/ng-store-authentication": "file:pack/ucap-ng-store-authentication-0.0.11.tgz",
|
||||
"@ucap/ng-store-chat": "file:pack/ucap-ng-store-chat-0.0.19.tgz",
|
||||
"@ucap/ng-store-group": "file:pack/ucap-ng-store-group-0.0.14.tgz",
|
||||
"@ucap/ng-store-organization": "file:pack/ucap-ng-store-organization-0.0.8.tgz",
|
||||
"@ucap/ng-ui": "file:pack/ucap-ng-ui-0.0.24.tgz",
|
||||
"@ucap/ng-ui-authentication": "file:pack/ucap-ng-ui-authentication-0.0.25.tgz",
|
||||
"@ucap/ng-ui-chat": "file:pack/ucap-ng-ui-chat-0.0.13.tgz",
|
||||
"@ucap/ng-ui-group": "file:pack/ucap-ng-ui-group-0.0.33.tgz",
|
||||
"@ucap/ng-store-authentication": "file:pack/ucap-ng-store-authentication-0.0.14.tgz",
|
||||
"@ucap/ng-store-chat": "file:pack/ucap-ng-store-chat-0.0.66.tgz",
|
||||
"@ucap/ng-store-group": "file:pack/ucap-ng-store-group-0.0.22.tgz",
|
||||
"@ucap/ng-store-organization": "file:pack/ucap-ng-store-organization-0.0.20.tgz",
|
||||
"@ucap/ng-ui": "file:pack/ucap-ng-ui-0.0.97.tgz",
|
||||
"@ucap/ng-ui-authentication": "file:pack/ucap-ng-ui-authentication-0.0.29.tgz",
|
||||
"@ucap/ng-ui-chat": "file:pack/ucap-ng-ui-chat-0.0.72.tgz",
|
||||
"@ucap/ng-ui-group": "file:pack/ucap-ng-ui-group-0.0.78.tgz",
|
||||
"@ucap/ng-ui-material": "file:pack/ucap-ng-ui-material-0.0.4.tgz",
|
||||
"@ucap/ng-ui-organization": "file:pack/ucap-ng-ui-organization-0.0.84.tgz",
|
||||
"@ucap/ng-ui-organization": "file:pack/ucap-ng-ui-organization-0.0.202.tgz",
|
||||
"@ucap/ng-ui-skin-default": "file:pack/ucap-ng-ui-skin-default-0.0.1.tgz",
|
||||
"@ucap/ng-web-socket": "file:pack/ucap-ng-web-socket-0.0.2.tgz",
|
||||
"@ucap/ng-web-storage": "file:pack/ucap-ng-web-storage-0.0.3.tgz",
|
||||
"@ucap/pi": "~0.0.5",
|
||||
"@ucap/pi": "~0.0.8",
|
||||
"@ucap/protocol": "~0.0.11",
|
||||
"@ucap/protocol-authentication": "~0.0.5",
|
||||
"@ucap/protocol-buddy": "~0.0.5",
|
||||
"@ucap/protocol-event": "~0.0.5",
|
||||
"@ucap/protocol-file": "~0.0.5",
|
||||
"@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.4",
|
||||
"@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.3",
|
||||
"@ucap/ui-scss": "~0.0.5",
|
||||
"@ucap/web-socket": "~0.0.5",
|
||||
"@ucap/web-storage": "~0.0.5",
|
||||
"autolinker": "^3.13.0",
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
# NativeBrowser
|
||||
|
||||
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.0.2.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name --project native-browser` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project native-browser`.
|
||||
> Note: Don't forget to add `--project native-browser` or else it will be added to the default project in your `angular.json` file.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build native-browser` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Publishing
|
||||
|
||||
After building your library with `ng build native-browser`, go to the dist folder `cd dist/native-browser` and run `npm publish`.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test native-browser` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
|
@ -1,32 +0,0 @@
|
|||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, '../../coverage/native-browser'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../dist/native-browser",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts",
|
||||
"umdModuleIds": {
|
||||
"@ucap/native-browser": "@ucap/native-browser",
|
||||
"@ucap/ng-core": "@ucap/ng-core"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"name": "@ucap/ng-native-browser",
|
||||
"version": "0.0.1",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^9.0.2",
|
||||
"@angular/core": "^9.0.2",
|
||||
"@ucap/native-browser": "~0.0.1",
|
||||
"@ucap/ng-core": "~0.0.1",
|
||||
"axios": "^0.19.2",
|
||||
"rxjs": "~6.5.4",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import { Injectable, Inject } from '@angular/core';
|
||||
|
||||
import { AxiosInstance } from 'axios';
|
||||
|
||||
import { BrowserNativeService as UcapBrowserNativeService } from '@ucap/native-browser';
|
||||
|
||||
import { AXIOS_INSTANCE } from '@ucap/ng-core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class BrowserNativeService extends UcapBrowserNativeService {
|
||||
constructor(@Inject(AXIOS_INSTANCE) axios: AxiosInstance) {
|
||||
super(axios);
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
/*
|
||||
* Public API Surface of native-browser
|
||||
*/
|
||||
|
||||
export * from './lib/services/browser-native.service';
|
|
@ -1,26 +0,0 @@
|
|||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/dist/zone';
|
||||
import 'zone.js/dist/zone-testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(path: string, deep?: boolean, filter?: RegExp): {
|
||||
keys(): string[];
|
||||
<T>(id: string): T;
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting()
|
||||
);
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../out-tsc/lib",
|
||||
"target": "es2015",
|
||||
"declaration": true,
|
||||
"inlineSources": true,
|
||||
"types": [],
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2018"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"skipTemplateCodegen": true,
|
||||
"strictMetadataEmit": true,
|
||||
"enableResourceInlining": true
|
||||
},
|
||||
"exclude": [
|
||||
"src/test.ts",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.lib.json",
|
||||
"angularCompilerOptions": {
|
||||
"enableIvy": false
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts"
|
||||
],
|
||||
"include": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"extends": "../../tslint.json",
|
||||
"rules": {
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"lib",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"lib",
|
||||
"kebab-case"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -2,6 +2,11 @@
|
|||
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
||||
"dest": "../../dist/native",
|
||||
"lib": {
|
||||
"entryFile": "src/public-api.ts"
|
||||
"entryFile": "src/public-api.ts",
|
||||
"umdModuleIds": {
|
||||
"@ucap/core": "@ucap/core",
|
||||
"@ucap/native": "@ucap/native",
|
||||
"@ucap/ng-core": "@ucap/ng-core"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
{
|
||||
"name": "@ucap/ng-native",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.5",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^9.0.2",
|
||||
"@angular/core": "^9.0.2",
|
||||
"@ucap/core": "~0.0.1",
|
||||
"@ucap/native": "~0.0.1",
|
||||
"axios": "^0.19.2",
|
||||
"rxjs": "~6.5.4",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
|
|
666
projects/native/src/lib/services/browser-native.service.ts
Normal file
666
projects/native/src/lib/services/browser-native.service.ts
Normal file
|
@ -0,0 +1,666 @@
|
|||
import { Subject, BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { AxiosInstance } from 'axios';
|
||||
|
||||
import { StatusCode, FileUtil } from '@ucap/core';
|
||||
import {
|
||||
NativeService,
|
||||
UpdateCheckConfig,
|
||||
UpdateInfo,
|
||||
NativeType,
|
||||
WindowState,
|
||||
WindowIdle,
|
||||
NotificationRequest,
|
||||
NotificationType
|
||||
} from '@ucap/native';
|
||||
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
export class BrowserNativeService extends NativeService {
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _notificationService: NotificationService;
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _idle_checker: WindowIdleChecker;
|
||||
|
||||
constructor(private axios: AxiosInstance) {
|
||||
super();
|
||||
|
||||
this._notificationService = new NotificationService();
|
||||
}
|
||||
|
||||
platform_nativeType(): Promise<NativeType> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
try {
|
||||
resolve(NativeType.Browser);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
platform_networkInfo(): Promise<any> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
try {
|
||||
resolve([{ ip: 'Browser', mac: 'browser' }]);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
platform_execute(executableName: string): Promise<number> {
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
try {
|
||||
resolve(-1);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
platform_openDefaultBrowser(
|
||||
url: string,
|
||||
options?: {
|
||||
name?: string;
|
||||
features?: string;
|
||||
replace?: boolean;
|
||||
}
|
||||
): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
const name = !!options && !!options.name ? options.name : null;
|
||||
const features =
|
||||
!!options && !!options.features ? options.features : null;
|
||||
window.open(url, name, features);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
platform_readFromClipboard(): Promise<{
|
||||
text?: string;
|
||||
rtf?: string;
|
||||
html?: string;
|
||||
image?: Buffer;
|
||||
imageDataUrl?: string;
|
||||
}> {
|
||||
return new Promise<{
|
||||
text?: string;
|
||||
rtf?: string;
|
||||
html?: string;
|
||||
image?: Buffer;
|
||||
imageDataUrl?: string;
|
||||
}>((resolve, reject) => {
|
||||
try {
|
||||
navigator.permissions
|
||||
.query({ name: 'clipboard-read' as PermissionName })
|
||||
.then((result) => {
|
||||
if ('granted' === result.state || 'prompt' === result.state) {
|
||||
navigator.clipboard
|
||||
.readText()
|
||||
.then((value) => {
|
||||
resolve({ text: value });
|
||||
})
|
||||
.catch((reason) => {
|
||||
reject(reason);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((reason) => {
|
||||
reject(reason);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
file_save(
|
||||
buffer: Buffer,
|
||||
fileName: string,
|
||||
mimeType: string,
|
||||
path?: string
|
||||
): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
try {
|
||||
FileUtil.save(buffer, fileName, mimeType)
|
||||
.then((fn) => {
|
||||
resolve(fn);
|
||||
})
|
||||
.catch((reason) => {
|
||||
reject(reason);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
file_read(path: string): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
try {
|
||||
this.axios
|
||||
.get(path, { responseType: 'arraybuffer' })
|
||||
.then((res) => {
|
||||
resolve(Buffer.from(res.data));
|
||||
})
|
||||
.catch((reason) => reject(reason));
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
file_openFolder(folderPath?: string, make?: boolean): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
try {
|
||||
resolve(false);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
file_openItem(filePath?: string): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
try {
|
||||
resolve(false);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
file_path(
|
||||
name: 'home' | 'documents' | 'downloads' | 'music' | 'pictures' | 'videos',
|
||||
...appendPaths: string[]
|
||||
): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
try {
|
||||
resolve(undefined);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
file_selectForOpen(option: {
|
||||
title?: string;
|
||||
defaultPath?: string;
|
||||
filters?: {
|
||||
extensions: string[];
|
||||
name: string;
|
||||
}[];
|
||||
properties?: Array<
|
||||
| 'openFile'
|
||||
| 'openDirectory'
|
||||
| 'multiSelections'
|
||||
| 'showHiddenFiles'
|
||||
| 'createDirectory'
|
||||
| 'promptToCreate'
|
||||
| 'noResolveAliases'
|
||||
| 'treatPackageAsDirectory'
|
||||
>;
|
||||
message?: string;
|
||||
}): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
try {
|
||||
resolve(undefined);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
file_selectForSave(option: {
|
||||
title?: string;
|
||||
defaultPath?: string;
|
||||
filters?: {
|
||||
extensions: string[];
|
||||
name: string;
|
||||
}[];
|
||||
message?: string;
|
||||
}): Promise<{
|
||||
canceled: boolean;
|
||||
filePath: string;
|
||||
}> {
|
||||
return new Promise<{
|
||||
canceled: boolean;
|
||||
filePath: string;
|
||||
}>((resolve, reject) => {
|
||||
try {
|
||||
resolve(undefined);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window_onState$(): BehaviorSubject<WindowState> {
|
||||
return super.window_onState$();
|
||||
}
|
||||
window_onFocus$(): BehaviorSubject<boolean> {
|
||||
if (!this._window_onFocusSubject) {
|
||||
// tslint:disable-next-line: variable-name
|
||||
const __this = this;
|
||||
const onFocus = (event: Event) => {
|
||||
__this._window_onFocusSubject.next(true);
|
||||
};
|
||||
const onBlur = (event: Event) => {
|
||||
__this._window_onFocusSubject.next(false);
|
||||
};
|
||||
window.addEventListener('focus', onFocus);
|
||||
window.addEventListener('blur', onBlur);
|
||||
|
||||
super.window_onFocus$().subscribe(
|
||||
(focus) => {},
|
||||
(error) => {},
|
||||
() => {
|
||||
window.removeEventListener('focus', onFocus);
|
||||
window.removeEventListener('blur', onBlur);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return super.window_onFocus$();
|
||||
}
|
||||
window_close(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
window_minimize(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
window_maximize(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
idle_startCheck(limitTime: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
if (!!this._idle_onStateSubject) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._idle_checker = new WindowIdleChecker({
|
||||
limitTime,
|
||||
onIdle: () => {
|
||||
if (!!this._idle_onStateSubject) {
|
||||
this._idle_onStateSubject.next(WindowIdle.Idle);
|
||||
}
|
||||
},
|
||||
onActive: () => {
|
||||
if (!!this._idle_onStateSubject) {
|
||||
this._idle_onStateSubject.next(WindowIdle.Active);
|
||||
}
|
||||
},
|
||||
onHide: () => {
|
||||
if (!!this._idle_onStateSubject) {
|
||||
this._idle_onStateSubject.next(WindowIdle.Idle);
|
||||
}
|
||||
},
|
||||
onShow: () => {
|
||||
if (!!this._idle_onStateSubject) {
|
||||
this._idle_onStateSubject.next(WindowIdle.Active);
|
||||
}
|
||||
}
|
||||
});
|
||||
this._idle_checker.start();
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
idle_onState$(): BehaviorSubject<WindowIdle> {
|
||||
return super.idle_onState$();
|
||||
}
|
||||
idle_stopCheck(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
if (!!this._idle_checker) {
|
||||
this._idle_checker.stop();
|
||||
this._idle_checker = undefined;
|
||||
}
|
||||
if (!!this._idle_onStateSubject) {
|
||||
this._idle_onStateSubject.complete();
|
||||
this._idle_onStateSubject = undefined;
|
||||
}
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
idle_changeLimitTime(limitTime: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
if (!this._idle_checker) {
|
||||
return;
|
||||
}
|
||||
this._idle_checker.changeLimitTime(limitTime);
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app_version(): Promise<string> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
try {
|
||||
resolve('');
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_postInit(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_postLogin(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
this._notificationService.requestPermission();
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_postLogout(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_postDestroy(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_changeAutoLaunch(autoLaunch: boolean): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
try {
|
||||
resolve(true);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_showNotify(req: NotificationRequest): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
this._notificationService.notify(req, () => {
|
||||
window.focus();
|
||||
|
||||
switch (req.type) {
|
||||
case NotificationType.Event:
|
||||
if (!!this._chat_onOpenSubject) {
|
||||
this._chat_onOpenSubject.next(req.seq);
|
||||
}
|
||||
break;
|
||||
case NotificationType.Message:
|
||||
if (!!this._message_onOpenSubject) {
|
||||
this._message_onOpenSubject.next(req.seq);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_closeAllNotify(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_checkForUpdates(currentVersion: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_startCheckForUpdate(config: UpdateCheckConfig): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_onUpdate$(): BehaviorSubject<UpdateInfo> {
|
||||
return super.app_onUpdate$();
|
||||
}
|
||||
app_stopCheckForUpdate(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_applyInstantUpdates(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
app_exit(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
window.close();
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app_onLogout$(): Subject<void> {
|
||||
return super.app_onLogout$();
|
||||
}
|
||||
app_onStatus$(): BehaviorSubject<StatusCode> {
|
||||
return super.app_onStatus$();
|
||||
}
|
||||
app_onShowSetting$(): Subject<void> {
|
||||
return super.app_onShowSetting$();
|
||||
}
|
||||
|
||||
chat_onOpen$(): BehaviorSubject<string> {
|
||||
return super.chat_onOpen$();
|
||||
}
|
||||
|
||||
message_onOpen$(): BehaviorSubject<string> {
|
||||
return super.message_onOpen$();
|
||||
}
|
||||
}
|
||||
|
||||
interface WindowIdleCheckerConfig {
|
||||
limitTime?: number;
|
||||
events?: string[];
|
||||
onIdle?: () => void;
|
||||
onActive?: () => void;
|
||||
visibilityEvents?: string[];
|
||||
onHide?: () => void;
|
||||
onShow?: () => void;
|
||||
}
|
||||
|
||||
const defaultWindowIdleCheckerConfig: WindowIdleCheckerConfig = {
|
||||
limitTime: 5 * 60 * 1000,
|
||||
events: ['mousemove', 'keydown', 'mousedown', 'touchstart'],
|
||||
onIdle: () => {},
|
||||
onActive: () => {},
|
||||
onHide: () => {},
|
||||
onShow: () => {},
|
||||
visibilityEvents: [
|
||||
'visibilitychange',
|
||||
'webkitvisibilitychange',
|
||||
'mozvisibilitychange',
|
||||
'msvisibilitychange'
|
||||
]
|
||||
};
|
||||
|
||||
const WEBKIT_HIDDEN = 'webkitHidden';
|
||||
const MOZ_HIDDEN = 'mozHidden';
|
||||
const MS_HIDDEN = 'msHidden';
|
||||
|
||||
class WindowIdleChecker {
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _timerId: any;
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _status: WindowIdle;
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _visible: boolean;
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _started: boolean;
|
||||
|
||||
constructor(
|
||||
// tslint:disable-next-line: variable-name
|
||||
private _config: WindowIdleCheckerConfig
|
||||
) {
|
||||
this._config = Object.assign({}, defaultWindowIdleCheckerConfig, _config);
|
||||
this._status = WindowIdle.Active;
|
||||
this._visible = true;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (!!this._timerId) {
|
||||
clearInterval(this._timerId);
|
||||
}
|
||||
|
||||
this._timerId = setInterval(() => {
|
||||
if (WindowIdle.Active === this._status) {
|
||||
this._status = WindowIdle.Idle;
|
||||
this._config.onIdle();
|
||||
}
|
||||
}, this._config.limitTime);
|
||||
|
||||
if (!this._started) {
|
||||
this._started = true;
|
||||
}
|
||||
|
||||
this._addEventListerner(this._config.events, this._onActive);
|
||||
|
||||
if (!!this._config.onShow || !!this._config.onHide) {
|
||||
this._addEventListerner(
|
||||
this._config.visibilityEvents,
|
||||
this._onVisibility
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (!!this._timerId) {
|
||||
clearInterval(this._timerId);
|
||||
this._timerId = undefined;
|
||||
}
|
||||
|
||||
if (!!this._started) {
|
||||
this._removeEventListerner(this._config.events, this._onActive);
|
||||
|
||||
if (!!this._config.onShow || !!this._config.onHide) {
|
||||
this._removeEventListerner(
|
||||
this._config.visibilityEvents,
|
||||
this._onVisibility
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeLimitTime(limitTime: number) {
|
||||
this._config = {
|
||||
...this._config,
|
||||
limitTime
|
||||
};
|
||||
this.start();
|
||||
}
|
||||
|
||||
private _onActive(event: Event) {
|
||||
if (WindowIdle.Idle === this._status) {
|
||||
this._status = WindowIdle.Active;
|
||||
this._config.onActive();
|
||||
}
|
||||
}
|
||||
|
||||
private _onVisibility(event: Event) {
|
||||
if (
|
||||
!!document.hidden ||
|
||||
!!document[WEBKIT_HIDDEN] ||
|
||||
!!document[MOZ_HIDDEN] ||
|
||||
!!document[MS_HIDDEN]
|
||||
) {
|
||||
if (this._visible) {
|
||||
this._visible = false;
|
||||
this._config.onHide();
|
||||
}
|
||||
} else {
|
||||
if (!this._visible) {
|
||||
this._visible = true;
|
||||
this._config.onShow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _addEventListerner(
|
||||
events: string[],
|
||||
callback: (event: Event) => void
|
||||
) {
|
||||
events.forEach((e) => {
|
||||
window.addEventListener(e, callback.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
private _removeEventListerner(
|
||||
events: string[],
|
||||
callback: (event: Event) => void
|
||||
) {
|
||||
events.forEach((e) => {
|
||||
window.removeEventListener(e, callback.bind(this));
|
||||
});
|
||||
}
|
||||
}
|
44
projects/native/src/lib/services/notification.service.ts
Normal file
44
projects/native/src/lib/services/notification.service.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { NotificationRequest } from '@ucap/native';
|
||||
|
||||
export class NotificationService {
|
||||
notificationPermission: NotificationPermission;
|
||||
|
||||
constructor() {
|
||||
this.notificationPermission = this.isSupported() ? 'default' : 'denied';
|
||||
}
|
||||
|
||||
public isSupported(): boolean {
|
||||
return 'Notification' in window;
|
||||
}
|
||||
|
||||
requestPermission(): void {
|
||||
const self = this;
|
||||
if ('Notification' in window) {
|
||||
Notification.requestPermission().then((result) => {
|
||||
self.notificationPermission = result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
notify(noti: NotificationRequest, click?: () => void) {
|
||||
if (!this.isSupported()) {
|
||||
return;
|
||||
}
|
||||
const notification = new Notification(noti.title, {
|
||||
body: noti.contents,
|
||||
icon: noti.image || 'assets/images/img_nophoto_50.png'
|
||||
});
|
||||
notification.onclick = (e) => {
|
||||
console.log('notification.onclick');
|
||||
if (!!click) {
|
||||
click();
|
||||
}
|
||||
};
|
||||
notification.onclose = (e) => {
|
||||
console.log('notification.onclose');
|
||||
};
|
||||
notification.onerror = (e) => {
|
||||
console.log('notification.onerror');
|
||||
};
|
||||
}
|
||||
}
|
|
@ -2,4 +2,7 @@
|
|||
* Public API Surface of native
|
||||
*/
|
||||
|
||||
export * from './lib/services/notification.service';
|
||||
export * from './lib/services/browser-native.service';
|
||||
|
||||
export * from './lib/types/token';
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
"@ucap/ng-protocol-authentication": "@ucap/ng-protocol-authentication",
|
||||
"@ucap/ng-protocol-info": "@ucap/ng-protocol-info",
|
||||
"@ucap/ng-protocol-option": "@ucap/ng-protocol-option",
|
||||
"@ucap/ng-protocol-query": "@ucap/ng-protocol-query"
|
||||
"@ucap/ng-protocol-query": "@ucap/ng-protocol-query",
|
||||
"@ucap/ng-store-organization": "@ucap/ng-store-organization"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@ucap/ng-store-authentication",
|
||||
"version": "0.0.11",
|
||||
"version": "0.0.14",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
|
@ -23,6 +23,7 @@
|
|||
"@ucap/ng-protocol-option": "~0.0.1",
|
||||
"@ucap/ng-protocol-query": "~0.0.1",
|
||||
"@ucap/ng-protocol-info": "~0.0.1",
|
||||
"@ucap/ng-store-organization": "~0.0.1",
|
||||
"rxjs": "~6.5.4",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
LoginResponse,
|
||||
LogoutResponse
|
||||
} from '@ucap/protocol-authentication';
|
||||
import { UserResponse, UserRequest } from '@ucap/protocol-info';
|
||||
|
||||
/**
|
||||
* request of web login
|
||||
|
@ -107,29 +106,3 @@ export const sessionDestroyed = createAction(
|
|||
'[ucap::authentication::login] session Destroyed',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
|
||||
/**
|
||||
* info user request
|
||||
*/
|
||||
export const infoUser = createAction(
|
||||
'[ucap::authentication::login] Info User',
|
||||
props<{ req: UserRequest }>()
|
||||
);
|
||||
|
||||
/**
|
||||
* Success of info user request
|
||||
*/
|
||||
export const infoUserSuccess = createAction(
|
||||
'[ucap::authentication::login] Info User Success',
|
||||
props<{
|
||||
res: UserResponse;
|
||||
}>()
|
||||
);
|
||||
|
||||
/**
|
||||
* Failure of info user request
|
||||
*/
|
||||
export const infoUserFailure = createAction(
|
||||
'[ucap::authentication::login] Info User Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
|
|
|
@ -1,22 +1,16 @@
|
|||
import { of } from 'rxjs';
|
||||
import { catchError, map, switchMap, exhaustMap } from 'rxjs/operators';
|
||||
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
|
||||
import { PiService } from '@ucap/ng-pi';
|
||||
import { AuthenticationProtocolService } from '@ucap/ng-protocol-authentication';
|
||||
import { InfoProtocolService } from '@ucap/ng-protocol-info';
|
||||
|
||||
import {
|
||||
logout,
|
||||
logoutSuccess,
|
||||
infoUser,
|
||||
infoUserSuccess,
|
||||
infoUserFailure
|
||||
} from './actions';
|
||||
import { UserResponse } from '@ucap/protocol-info';
|
||||
import { UserActions } from '@ucap/ng-store-organization';
|
||||
|
||||
import { logout, logoutSuccess, loginSuccess } from './actions';
|
||||
|
||||
@Injectable()
|
||||
export class Effects {
|
||||
|
@ -32,27 +26,37 @@ export class Effects {
|
|||
);
|
||||
});
|
||||
|
||||
infoUser$ = createEffect(() =>
|
||||
loginSuccessForOrganizationUserInit$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(infoUser),
|
||||
map((action) => action.req),
|
||||
exhaustMap((req) =>
|
||||
this.infoProtocolService.user(req).pipe(
|
||||
map((res: UserResponse) => {
|
||||
return infoUserSuccess({
|
||||
res
|
||||
});
|
||||
}),
|
||||
catchError((error) => of(infoUserFailure({ error })))
|
||||
)
|
||||
)
|
||||
)
|
||||
ofType(loginSuccess),
|
||||
map((params) => params.res),
|
||||
tap((loginRes) => {
|
||||
this.store.dispatch(
|
||||
UserActions.init({
|
||||
user: {
|
||||
info: loginRes.userInfo,
|
||||
companyCode: loginRes.companyCode,
|
||||
departmentCode: loginRes.departmentCode,
|
||||
statusMessage1: loginRes.statusMessage1,
|
||||
statusMessage2: loginRes.statusMessage2,
|
||||
statusMessage3: loginRes.statusMessage3,
|
||||
madn: loginRes.madn,
|
||||
hardPhoneSadn: loginRes.hardPhoneSadn,
|
||||
fmcSadn: loginRes.fmcSadn,
|
||||
pbxIndex: loginRes.pbxIndex,
|
||||
talkWithMeBotSeq: loginRes.talkWithMeBotSeq
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private piService: PiService,
|
||||
private authenticationProtocolService: AuthenticationProtocolService,
|
||||
private infoProtocolService: InfoProtocolService
|
||||
private store: Store<any>,
|
||||
private authenticationProtocolService: AuthenticationProtocolService
|
||||
) {}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { createReducer, on } from '@ngrx/store';
|
||||
|
||||
import { initialState } from './state';
|
||||
import { loginSuccess, logoutSuccess, infoUserSuccess } from './actions';
|
||||
import { UserInfoUpdateType } from '@ucap/protocol-info';
|
||||
import { loginSuccess, logoutSuccess } from './actions';
|
||||
|
||||
export const reducer = createReducer(
|
||||
initialState,
|
||||
|
@ -17,41 +16,5 @@ export const reducer = createReducer(
|
|||
return {
|
||||
...initialState
|
||||
};
|
||||
}),
|
||||
|
||||
on(infoUserSuccess, (state, action) => {
|
||||
let loginRes = {
|
||||
...state.loginRes
|
||||
};
|
||||
|
||||
switch (action.res.type) {
|
||||
case UserInfoUpdateType.Image:
|
||||
loginRes = {
|
||||
...loginRes
|
||||
};
|
||||
break;
|
||||
case UserInfoUpdateType.Intro:
|
||||
loginRes = {
|
||||
...loginRes,
|
||||
userInfo: {
|
||||
...loginRes.userInfo,
|
||||
intro: action.res.info
|
||||
}
|
||||
};
|
||||
break;
|
||||
case UserInfoUpdateType.TelephoneVisible:
|
||||
loginRes = {
|
||||
...loginRes
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
loginRes: {
|
||||
...loginRes
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
|
|
|
@ -8,13 +8,20 @@
|
|||
"@ngrx/store": "@ngrx/store",
|
||||
"@ngrx/entity": "@ngrx/entity",
|
||||
"@ngrx/effects": "@ngrx/effects",
|
||||
"@ucap/api": "@ucap/api",
|
||||
"@ucap/pi": "@ucap/pi",
|
||||
"@ucap/protocol-event": "@ucap/protocol-event",
|
||||
"@ucap/protocol-file": "@ucap/protocol-file",
|
||||
"@ucap/protocol-info": "@ucap/protocol-info",
|
||||
"@ucap/protocol-room": "@ucap/protocol-room",
|
||||
"@ucap/protocol-sync": "@ucap/protocol-sync",
|
||||
"@ucap/ng-i18n": "@ucap/ng-i18n",
|
||||
"@ucap/ng-api-common": "@ucap/ng-api-common",
|
||||
"@ucap/ng-protocol-room": "@ucap/ng-protocol-room",
|
||||
"@ucap/ng-protocol-event": "@ucap/ng-protocol-event",
|
||||
"@ucap/ng-protocol-file": "@ucap/ng-protocol-file",
|
||||
"@ucap/ng-protocol-sync": "@ucap/ng-protocol-sync",
|
||||
"@ucap/ng-store-organization": "@ucap/ng-store-organization",
|
||||
"@ucap/ng-store-authentication": "@ucap/ng-store-authentication"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@ucap/ng-store-chat",
|
||||
"version": "0.0.19",
|
||||
"version": "0.0.66",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { createAction, props } from '@ngrx/store';
|
||||
|
||||
import { DeviceType } from '@ucap/core';
|
||||
|
||||
import {
|
||||
Info,
|
||||
InfoRequest as EventInfoRequest,
|
||||
|
@ -16,14 +18,17 @@ import {
|
|||
CancelNotification,
|
||||
DelNotification,
|
||||
EventJson,
|
||||
ReadResponse
|
||||
ReadResponse,
|
||||
SendRequest
|
||||
} from '@ucap/protocol-event';
|
||||
|
||||
import {
|
||||
InfoRequest as FileInfoRequest,
|
||||
InfoResponse as FileInfoResponse,
|
||||
FileDownloadInfo,
|
||||
FileInfo
|
||||
FileInfo,
|
||||
DownCheckRequest,
|
||||
DownCheckResponse
|
||||
} from '@ucap/protocol-file';
|
||||
|
||||
/**
|
||||
|
@ -51,6 +56,13 @@ export const eventsFailure = createAction(
|
|||
'[ucap::chat::chatting] events Failure',
|
||||
props<{ roomId: string; error: any }>()
|
||||
);
|
||||
/**
|
||||
* retrieve list of event
|
||||
*/
|
||||
export const moreEvents = createAction(
|
||||
'[ucap::chat::chatting] events more',
|
||||
props<{ roomId: string }>()
|
||||
);
|
||||
|
||||
/**
|
||||
* retrieve list of file information
|
||||
|
@ -78,6 +90,19 @@ export const fileInfosFailure = createAction(
|
|||
props<{ roomId: string; error: any }>()
|
||||
);
|
||||
|
||||
export const fileDownCheck = createAction(
|
||||
'[ucap::chat::chatting] fileDownCheck',
|
||||
props<{ req: DownCheckRequest }>()
|
||||
);
|
||||
export const fileDownCheckSuccess = createAction(
|
||||
'[ucap::chat::chatting] fileDownCheck Success',
|
||||
props<{ res: DownCheckResponse }>()
|
||||
);
|
||||
export const fileDownCheckFailure = createAction(
|
||||
'[ucap::chat::chatting] fileDownCheck Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
|
||||
/**
|
||||
* add new event
|
||||
*/
|
||||
|
@ -143,3 +168,109 @@ export const sendNotification = createAction(
|
|||
'[ucap::chat::chatting] Send Notification',
|
||||
props<{ noti: SendNotification }>()
|
||||
);
|
||||
|
||||
/** 대화 삭제 */
|
||||
export const del = createAction(
|
||||
'[ucap::chat::chatting] Delete',
|
||||
props<DelRequest>()
|
||||
);
|
||||
export const delFailure = createAction(
|
||||
'[ucap::chat::chatting] Delete Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
export const delNotification = createAction(
|
||||
'[ucap::chat::chatting] Delete Notification || Response',
|
||||
props<{ noti: DelNotification | DelResponse }>()
|
||||
);
|
||||
/** 대화 삭제시 열린 대화방의 대화 내용 갱신 */
|
||||
export const delEventList = createAction(
|
||||
'[ucap::chat::chatting] Delete InfoList',
|
||||
props<{
|
||||
roomId: string;
|
||||
eventSeqs: number[];
|
||||
}>()
|
||||
);
|
||||
|
||||
/** Clear event for TimerRoom */
|
||||
export const intervalClearEvent = createAction(
|
||||
'[ucap::chat::chatting] Clear events interval',
|
||||
props<{
|
||||
roomId: string;
|
||||
}>()
|
||||
);
|
||||
|
||||
/** forward */
|
||||
export const forward = createAction(
|
||||
'[ucap::chat::chatting] Forward',
|
||||
props<{
|
||||
senderSeq: string;
|
||||
deviceType: DeviceType;
|
||||
req: SendRequest;
|
||||
trgtUserSeqs?: string[];
|
||||
trgtRoomId?: string;
|
||||
}>()
|
||||
);
|
||||
/** chat forward failure */
|
||||
export const forwardFailure = createAction(
|
||||
'[ucap::chat::chatting] Forward failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
|
||||
export const forwardAfterRoomOpen = createAction(
|
||||
'[ucap::chat::chatting] Forward after room open',
|
||||
props<{
|
||||
senderSeq: string;
|
||||
deviceType: DeviceType;
|
||||
req: SendRequest;
|
||||
trgtUserSeqs?: string[];
|
||||
trgtRoomId?: string;
|
||||
}>()
|
||||
);
|
||||
|
||||
export const roomOpenAfterForward = createAction(
|
||||
'[ucap::chat::chatting] Room open after forward',
|
||||
props<{
|
||||
senderSeq: string;
|
||||
req: SendRequest;
|
||||
trgtUserSeqs?: string[];
|
||||
trgtRoomId?: string;
|
||||
}>()
|
||||
);
|
||||
|
||||
export const forwarForFileEvent = createAction(
|
||||
'[ucap::chat::chatting] Forward for file event',
|
||||
props<{
|
||||
forwardType: string;
|
||||
deviceType: DeviceType;
|
||||
trgtRoomId?: string;
|
||||
sendReq: SendRequest;
|
||||
}>()
|
||||
);
|
||||
|
||||
/** 대화 회수 */
|
||||
export const cancel = createAction(
|
||||
'[ucap::chat::chatting] Cancel',
|
||||
props<CancelRequest>()
|
||||
);
|
||||
export const cancelFailure = createAction(
|
||||
'[ucap::chat::chatting] Cancel Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
export const cancelNotification = createAction(
|
||||
'[ucap::chat::chatting] Cancel Notification || Response',
|
||||
props<{ noti: CancelNotification | CancelResponse }>()
|
||||
);
|
||||
/** 대화 회수시 열린 대화방의 대화 내용 갱신 */
|
||||
export const updateEventList = createAction(
|
||||
'[ucap::chat::chatting] Update InfoList',
|
||||
props<{
|
||||
roomId: string;
|
||||
eventSeq: number;
|
||||
sentMessage: string;
|
||||
}>()
|
||||
);
|
||||
|
||||
export const clearActiveRoomId = createAction(
|
||||
'[ucap::chat::chatting] Clear activeRoomId',
|
||||
props()
|
||||
);
|
||||
|
|
|
@ -7,7 +7,9 @@ import {
|
|||
exhaustMap,
|
||||
concatMap,
|
||||
withLatestFrom,
|
||||
debounceTime
|
||||
debounceTime,
|
||||
mergeMap,
|
||||
take
|
||||
} from 'rxjs/operators';
|
||||
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
|
@ -16,11 +18,46 @@ import { Store, select } from '@ngrx/store';
|
|||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
import { Dictionary } from '@ngrx/entity';
|
||||
|
||||
import {
|
||||
RoomType,
|
||||
OpenResponse as CreateResponse,
|
||||
Open3Response as CreateTimerResponse,
|
||||
ExitResponse as DeleteResponse,
|
||||
ExitAllResponse as DeleteMultiResponse
|
||||
} from '@ucap/protocol-room';
|
||||
import {
|
||||
InfoRequest,
|
||||
ReadResponse,
|
||||
FileType,
|
||||
EventType,
|
||||
DelResponse,
|
||||
SendResponse,
|
||||
CancelResponse,
|
||||
FileEventJson,
|
||||
decodeFileEventJson,
|
||||
Info,
|
||||
EventJson
|
||||
} from '@ucap/protocol-event';
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
import { LocaleCode } from '@ucap/core';
|
||||
import { StatusCode } from '@ucap/api';
|
||||
import { FileTalkShareRequest, FileTalkShareResponse } from '@ucap/api-common';
|
||||
|
||||
import { I18nService } from '@ucap/ng-i18n';
|
||||
import { CommonApiService } from '@ucap/ng-api-common';
|
||||
import { EventProtocolService } from '@ucap/ng-protocol-event';
|
||||
import { RoomProtocolService } from '@ucap/ng-protocol-room';
|
||||
import { FileProtocolService } from '@ucap/ng-protocol-file';
|
||||
import { UserSelector } from '@ucap/ng-store-organization';
|
||||
import { LoginSelector } from '@ucap/ng-store-authentication';
|
||||
|
||||
import { ModuleConfig } from '../../config/module-config';
|
||||
import { _MODULE_CONFIG } from '../../config/token';
|
||||
import { ChattingSelector, RoomSelector } from '../state';
|
||||
import * as RoomActions from '../room/actions';
|
||||
|
||||
import {
|
||||
events,
|
||||
eventsFailure,
|
||||
|
@ -35,18 +72,26 @@ import {
|
|||
sendSuccess,
|
||||
sendFailure,
|
||||
addEvent,
|
||||
addEventSuccess
|
||||
addEventSuccess,
|
||||
del,
|
||||
delNotification,
|
||||
delFailure,
|
||||
delEventList,
|
||||
moreEvents,
|
||||
forward,
|
||||
forwardFailure,
|
||||
forwardAfterRoomOpen,
|
||||
roomOpenAfterForward,
|
||||
forwarForFileEvent,
|
||||
cancel,
|
||||
cancelFailure,
|
||||
cancelNotification,
|
||||
updateEventList,
|
||||
intervalClearEvent,
|
||||
fileDownCheck,
|
||||
fileDownCheckSuccess,
|
||||
fileDownCheckFailure
|
||||
} from './actions';
|
||||
|
||||
import {
|
||||
InfoRequest,
|
||||
ReadResponse,
|
||||
FileType,
|
||||
EventType
|
||||
} from '@ucap/protocol-event';
|
||||
|
||||
import { ModuleConfig } from '../../config/module-config';
|
||||
import { _MODULE_CONFIG } from '../../config/token';
|
||||
import { Chatting } from './state';
|
||||
|
||||
@Injectable()
|
||||
|
@ -110,6 +155,40 @@ export class Effects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
moreEvents$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(moreEvents),
|
||||
mergeMap(
|
||||
(action) =>
|
||||
of(action).pipe(
|
||||
withLatestFrom(
|
||||
this.store.pipe(
|
||||
select(ChattingSelector.eventList, action.roomId)
|
||||
)
|
||||
)
|
||||
),
|
||||
(action, latestStoreData) => latestStoreData
|
||||
),
|
||||
tap(([req, eventList]) => {
|
||||
if (!!eventList && eventList.length > 0) {
|
||||
this.store.dispatch(
|
||||
events({
|
||||
req: {
|
||||
roomId: req.roomId,
|
||||
baseSeq: eventList.sort((a, b) => a.seq - b.seq)[0].seq,
|
||||
requestCount:
|
||||
this.moduleConfig?.eventRequestDefaultCount || 50
|
||||
} as InfoRequest
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
read$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
|
@ -117,7 +196,14 @@ export class Effects {
|
|||
exhaustMap((req) =>
|
||||
this.eventProtocolService.read(req).pipe(
|
||||
map((res: ReadResponse) => {
|
||||
// room user lastReadEventSeq reset.
|
||||
this.store.dispatch(readSuccess(res));
|
||||
// room noReadCount reset.
|
||||
this.store.dispatch(
|
||||
RoomActions.updateUnreadCount({
|
||||
roomId: res.roomId
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError((error) => of(readFailure({ error })))
|
||||
)
|
||||
|
@ -152,6 +238,23 @@ export class Effects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
fileDownCheck$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(fileDownCheck),
|
||||
switchMap((action) => {
|
||||
return this.fileProtocolService.downCheck(action.req).pipe(
|
||||
map((res) => {
|
||||
this.store.dispatch(fileDownCheckSuccess({ res }));
|
||||
}),
|
||||
catchError((error) => of(fileDownCheckFailure({ error })))
|
||||
);
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
addEvent$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
|
@ -234,6 +337,352 @@ export class Effects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
del$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(del),
|
||||
exhaustMap((req) =>
|
||||
this.eventProtocolService.del(req).pipe(
|
||||
map((res: DelResponse) => {
|
||||
return delNotification({ noti: res });
|
||||
}),
|
||||
catchError((error) => of(delFailure({ error })))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
delNotification$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(delNotification),
|
||||
map((action) => action.noti),
|
||||
withLatestFrom(this.store.pipe(select(RoomSelector.rooms))),
|
||||
tap(([noti, rooms]) => {
|
||||
const notiRoomId = noti.roomId;
|
||||
|
||||
if (!!rooms && rooms.length > 0) {
|
||||
// 현재 방이 오픈되어 있으면 방내용 갱신
|
||||
const idx = rooms.findIndex(
|
||||
(roomInfo) => roomInfo.roomId === notiRoomId
|
||||
);
|
||||
|
||||
if (idx > -1) {
|
||||
this.store.dispatch(
|
||||
delEventList({ roomId: notiRoomId, eventSeqs: [noti.eventSeq] })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 대화 > 리스트의 항목 갱신
|
||||
this.store.dispatch(
|
||||
RoomActions.room({
|
||||
req: {
|
||||
roomId: notiRoomId,
|
||||
isDetail: false,
|
||||
localeCode: this.i18nService.currentLng.toUpperCase() as LocaleCode
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
intervalClearEvent$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(intervalClearEvent),
|
||||
withLatestFrom(
|
||||
this.store.pipe(select(RoomSelector.rooms)),
|
||||
this.store.pipe(select(ChattingSelector.chattings))
|
||||
),
|
||||
tap(([action, rooms, chattings]) => {
|
||||
const roomId = action.roomId;
|
||||
if (
|
||||
!!rooms &&
|
||||
rooms.length > 0 &&
|
||||
!!chattings &&
|
||||
chattings.length > 0
|
||||
) {
|
||||
const roomInfo = rooms.find((item) => item.roomId === roomId);
|
||||
const chatting = chattings.find((item) => item.roomId === roomId);
|
||||
|
||||
if (
|
||||
!!roomInfo &&
|
||||
!!roomInfo.timeRoomInterval &&
|
||||
!!chatting &&
|
||||
!!chatting.eventList
|
||||
) {
|
||||
const eventList = chatting.eventList;
|
||||
const delEventSeq: number[] = [];
|
||||
|
||||
eventList.ids.forEach((id) => {
|
||||
const event: Info<EventJson> = eventList.entities[id];
|
||||
|
||||
if (
|
||||
!event ||
|
||||
event.type === EventType.NotificationForTimerRoom
|
||||
) {
|
||||
//ignore..
|
||||
} else {
|
||||
if (
|
||||
new Date().getTime() -
|
||||
moment(event.sendDate).toDate().getTime() >=
|
||||
roomInfo.timeRoomInterval * 1000
|
||||
) {
|
||||
delEventSeq.push(event.seq);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (delEventSeq.length > 0) {
|
||||
this.store.dispatch(
|
||||
delEventList({
|
||||
roomId,
|
||||
eventSeqs: delEventSeq
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
forward$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(forward),
|
||||
tap((action) => {
|
||||
if (!!action.trgtRoomId) {
|
||||
// 대화전달 후 방오픈. Exist roomSeq.
|
||||
if (action.req.eventType === EventType.File) {
|
||||
// file share request action
|
||||
this.store.dispatch(
|
||||
forwarForFileEvent({
|
||||
forwardType: 'F',
|
||||
deviceType: action.deviceType,
|
||||
sendReq: action.req,
|
||||
trgtRoomId: action.trgtRoomId
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.store.dispatch(roomOpenAfterForward(action));
|
||||
}
|
||||
} else if (!!action.trgtUserSeqs && action.trgtUserSeqs.length > 0) {
|
||||
// 방오픈 후 대화전달.
|
||||
this.store.dispatch(forwardAfterRoomOpen(action));
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
forwarForFileEvent$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(forwarForFileEvent),
|
||||
withLatestFrom(
|
||||
this.store.pipe(select(UserSelector.user)),
|
||||
this.store.pipe(select(LoginSelector.loginRes))
|
||||
),
|
||||
tap(([actionReq, user, loginRes]) => {
|
||||
const fileEventJson: FileEventJson = decodeFileEventJson(
|
||||
actionReq.sendReq.sentMessage
|
||||
);
|
||||
|
||||
const req: FileTalkShareRequest = {
|
||||
userSeq: String(user.info.seq),
|
||||
deviceType: actionReq.deviceType,
|
||||
token: loginRes.tokenString,
|
||||
attachmentsSeq: fileEventJson.attachmentSeq.toString(),
|
||||
roomId: actionReq.trgtRoomId,
|
||||
synapKey: ''
|
||||
};
|
||||
|
||||
return this.commonApiService
|
||||
.fileTalkShare(req)
|
||||
.pipe(
|
||||
take(1),
|
||||
map((res: FileTalkShareResponse) => {
|
||||
const sedRequest = {
|
||||
senderSeq: String(user.info.seq),
|
||||
req: {
|
||||
roomId: actionReq.trgtRoomId,
|
||||
eventType: actionReq.sendReq.eventType,
|
||||
sentMessage: res.returnJson
|
||||
}
|
||||
};
|
||||
if (actionReq.forwardType === 'O') {
|
||||
if (res.statusCode === StatusCode.Success) {
|
||||
this.store.dispatch(send(sedRequest));
|
||||
}
|
||||
} else if (actionReq.forwardType === 'F') {
|
||||
if (res.statusCode === StatusCode.Success) {
|
||||
this.store.dispatch(
|
||||
roomOpenAfterForward({
|
||||
...sedRequest,
|
||||
trgtRoomId: actionReq.trgtRoomId
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}),
|
||||
catchError((error) => of(forwardFailure({ error })))
|
||||
)
|
||||
.subscribe();
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
forwardAfterRoomOpen$ = createEffect(
|
||||
() => {
|
||||
let createRes: CreateResponse;
|
||||
return this.actions$.pipe(
|
||||
ofType(forwardAfterRoomOpen),
|
||||
exhaustMap((actionReq) => {
|
||||
return this.roomProtocolService
|
||||
.open({ divCd: 'forwardOpen', userSeqs: actionReq.trgtUserSeqs })
|
||||
.pipe(
|
||||
map((res: CreateResponse) => {
|
||||
createRes = res;
|
||||
this.store.dispatch(
|
||||
RoomActions.createRoomSuccess({ res, isForward: true })
|
||||
);
|
||||
}),
|
||||
map(() => {
|
||||
if (actionReq.req.eventType === EventType.File) {
|
||||
// file share request action
|
||||
this.store.dispatch(
|
||||
forwarForFileEvent({
|
||||
forwardType: 'O',
|
||||
deviceType: actionReq.deviceType,
|
||||
sendReq: actionReq.req,
|
||||
trgtRoomId: createRes.roomId
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.store.dispatch(
|
||||
send({
|
||||
senderSeq: actionReq.senderSeq,
|
||||
req: {
|
||||
roomId: createRes.roomId,
|
||||
eventType: actionReq.req.eventType,
|
||||
sentMessage: actionReq.req.sentMessage
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}),
|
||||
catchError((error) =>
|
||||
of(RoomActions.createRoomFailure({ error }))
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
roomOpenAfterForward$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(roomOpenAfterForward),
|
||||
exhaustMap((action) => {
|
||||
return this.eventProtocolService
|
||||
.send({
|
||||
roomId: action.trgtRoomId,
|
||||
eventType: action.req.eventType,
|
||||
sentMessage: action.req.sentMessage
|
||||
})
|
||||
.pipe(
|
||||
map((res: SendResponse) => {
|
||||
this.store.dispatch(
|
||||
addEvent({
|
||||
roomId: res.roomId,
|
||||
info: res.info,
|
||||
SVC_TYPE: res.SVC_TYPE,
|
||||
SSVC_TYPE: res.SSVC_TYPE
|
||||
})
|
||||
);
|
||||
this.store.dispatch(
|
||||
RoomActions.selectedRoom({
|
||||
roomId: res.roomId,
|
||||
localeCode: this.i18nService.currentLng.toUpperCase() as LocaleCode
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError((error) =>
|
||||
of(RoomActions.createRoomFailure({ error }))
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
cancel$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(cancel),
|
||||
exhaustMap((req) =>
|
||||
this.eventProtocolService.cancel(req).pipe(
|
||||
map((res: CancelResponse) => {
|
||||
return cancelNotification({ noti: res });
|
||||
}),
|
||||
catchError((error) => of(delFailure({ error })))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
cancelNotification$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(cancelNotification),
|
||||
withLatestFrom(this.store.pipe(select(RoomSelector.rooms))),
|
||||
tap(([action, rooms]) => {
|
||||
const notiRoomId = action.noti.roomId;
|
||||
|
||||
if (!!rooms && rooms.length > 0) {
|
||||
// 현재 방이 오픈되어 있으면 방내용 갱신
|
||||
const idx = rooms.findIndex(
|
||||
(roomInfo) => roomInfo.roomId === notiRoomId
|
||||
);
|
||||
|
||||
if (idx > -1) {
|
||||
this.store.dispatch(
|
||||
updateEventList({
|
||||
roomId: notiRoomId,
|
||||
eventSeq: action.noti.eventSeq,
|
||||
sentMessage: this.i18nService.t('event.recalled')
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 대화 > 리스트의 항목 갱신
|
||||
this.store.dispatch(
|
||||
RoomActions.room({
|
||||
req: {
|
||||
roomId: action.noti.roomId,
|
||||
isDetail: false,
|
||||
localeCode: this.i18nService.currentLng.toUpperCase() as LocaleCode
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
/*******************************************************************
|
||||
* [Room Action watching.]
|
||||
*******************************************************************/
|
||||
|
@ -263,6 +712,10 @@ export class Effects {
|
|||
@Inject(_MODULE_CONFIG) private moduleConfig: ModuleConfig,
|
||||
private roomProtocolService: RoomProtocolService,
|
||||
private eventProtocolService: EventProtocolService,
|
||||
private fileProtocolService: FileProtocolService
|
||||
) {}
|
||||
private fileProtocolService: FileProtocolService,
|
||||
private i18nService: I18nService,
|
||||
private commonApiService: CommonApiService
|
||||
) {
|
||||
this.i18nService.setDefaultNamespace('chat');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,28 @@
|
|||
import { createReducer, on } from '@ngrx/store';
|
||||
|
||||
import { FileInfo } from '@ucap/protocol-file';
|
||||
import { Info, EventJson, EventType } from '@ucap/protocol-event';
|
||||
|
||||
import * as RoomActions from '../room/actions';
|
||||
|
||||
import {
|
||||
initialState,
|
||||
adapterChatting,
|
||||
adapterEventList,
|
||||
Chatting,
|
||||
adapterFileInfoList,
|
||||
adapterFileInfoCheckList
|
||||
adapterFileInfoList
|
||||
// adapterFileInfoCheckList
|
||||
} from './state';
|
||||
|
||||
import * as RoomActions from '../room/actions';
|
||||
import {
|
||||
eventsSuccess,
|
||||
eventsFailure,
|
||||
fileInfosSuccess,
|
||||
fileInfosFailure,
|
||||
addEvent
|
||||
addEvent,
|
||||
delEventList,
|
||||
updateEventList,
|
||||
clearActiveRoomId
|
||||
} from './actions';
|
||||
|
||||
export const reducer = createReducer(
|
||||
|
@ -31,22 +38,53 @@ export const reducer = createReducer(
|
|||
eventListProcessing: false,
|
||||
eventList: adapterEventList.getInitialState(),
|
||||
eventStatus: null,
|
||||
remainEvent: false,
|
||||
remainEvent: null,
|
||||
|
||||
fileInfoListProcessing: false,
|
||||
fileInfoList: adapterFileInfoList.getInitialState(),
|
||||
fileInfoCheckList: adapterFileInfoCheckList.getInitialState(),
|
||||
// fileInfoCheckList: adapterFileInfoCheckList.getInitialState(),
|
||||
fileInfoCheckList: [],
|
||||
fileInfoSyncDate: '',
|
||||
...chatting
|
||||
};
|
||||
|
||||
// dupliaction event process
|
||||
const trgtEventInfoList: Info<EventJson>[] = [];
|
||||
if (!!chatting && !!chatting.eventList && !!chatting.eventList.entities) {
|
||||
const filteredList = action.eventInfoList.filter((item) => {
|
||||
let notExistOrDiff = true; // added target flag.
|
||||
|
||||
// tslint:disable-next-line: forin
|
||||
for (const key in chatting.eventList.entities) {
|
||||
const event = chatting.eventList.entities[key];
|
||||
if (
|
||||
item.seq === event.seq &&
|
||||
item.type === event.type &&
|
||||
item.sentMessage === event.sentMessage
|
||||
) {
|
||||
notExistOrDiff = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return notExistOrDiff;
|
||||
});
|
||||
|
||||
if (!!filteredList && filteredList.length > 0) {
|
||||
trgtEventInfoList.push(...filteredList);
|
||||
}
|
||||
} else {
|
||||
trgtEventInfoList.push(...action.eventInfoList);
|
||||
}
|
||||
|
||||
trgtChatting = {
|
||||
...trgtChatting,
|
||||
eventList: adapterEventList.upsertMany(action.eventInfoList, {
|
||||
eventList: adapterEventList.upsertMany(trgtEventInfoList, {
|
||||
...trgtChatting.eventList
|
||||
}),
|
||||
eventStatus: action.res,
|
||||
remainEvent: action.remainEvent,
|
||||
remainEvent:
|
||||
trgtChatting.remainEvent === false ? false : action.remainEvent, // 재조회를 위한 처리.
|
||||
eventListProcessing: false
|
||||
};
|
||||
|
||||
|
@ -92,11 +130,12 @@ export const reducer = createReducer(
|
|||
eventListProcessing: false,
|
||||
eventList: adapterEventList.getInitialState(),
|
||||
eventStatus: null,
|
||||
remainEvent: false,
|
||||
remainEvent: null,
|
||||
|
||||
fileInfoListProcessing: false,
|
||||
fileInfoList: adapterFileInfoList.getInitialState(),
|
||||
fileInfoCheckList: adapterFileInfoCheckList.getInitialState(),
|
||||
// fileInfoCheckList: adapterFileInfoCheckList.getInitialState(),
|
||||
fileInfoCheckList: [],
|
||||
fileInfoSyncDate: '',
|
||||
...chatting
|
||||
};
|
||||
|
@ -111,11 +150,12 @@ export const reducer = createReducer(
|
|||
...trgtChatting.fileInfoList
|
||||
})
|
||||
: trgtChatting.fileInfoList,
|
||||
fileInfoCheckList: !!fileInfoCheckList
|
||||
? adapterFileInfoCheckList.upsertMany(fileInfoCheckList, {
|
||||
...trgtChatting.fileInfoCheckList
|
||||
})
|
||||
: trgtChatting.fileInfoCheckList,
|
||||
// fileInfoCheckList: !!fileInfoCheckList
|
||||
// ? adapterFileInfoCheckList.upsertMany(fileInfoCheckList, {
|
||||
// ...trgtChatting.fileInfoCheckList
|
||||
// })
|
||||
// : trgtChatting.fileInfoCheckList,
|
||||
fileInfoCheckList,
|
||||
fileInfoListProcessing: false
|
||||
};
|
||||
|
||||
|
@ -171,6 +211,117 @@ export const reducer = createReducer(
|
|||
}
|
||||
}),
|
||||
|
||||
on(delEventList, (state, action) => {
|
||||
const roomId = action.roomId;
|
||||
const chatting = state.chattings.entities[roomId];
|
||||
|
||||
// checked Valid array
|
||||
if (
|
||||
!chatting ||
|
||||
!chatting.eventList ||
|
||||
chatting.eventList.ids.length === 0
|
||||
) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const fileInfoList = chatting.fileInfoList;
|
||||
const trgtDelFileInfoSeqs = [];
|
||||
if (!!fileInfoList) {
|
||||
fileInfoList.ids.forEach((id) => {
|
||||
const fileInfo: FileInfo = fileInfoList.entities[id];
|
||||
if (action.eventSeqs.indexOf(fileInfo.eventSeq) > -1) {
|
||||
trgtDelFileInfoSeqs.push(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
chattings: adapterChatting.upsertOne(
|
||||
{
|
||||
...chatting,
|
||||
eventList: adapterEventList.removeMany(action.eventSeqs, {
|
||||
...chatting.eventList
|
||||
}),
|
||||
fileInfoList:
|
||||
trgtDelFileInfoSeqs.length > 0
|
||||
? adapterFileInfoList.removeMany(trgtDelFileInfoSeqs, {
|
||||
...chatting.fileInfoList
|
||||
})
|
||||
: chatting.fileInfoList
|
||||
},
|
||||
{ ...state.chattings }
|
||||
)
|
||||
};
|
||||
}),
|
||||
|
||||
on(updateEventList, (state, action) => {
|
||||
const roomId = action.roomId;
|
||||
const eventSeq = action.eventSeq;
|
||||
const sentMessage = action.sentMessage;
|
||||
const chatting = state.chattings.entities[roomId];
|
||||
let fileInfoSeq;
|
||||
|
||||
// checked Valid array
|
||||
if (
|
||||
!chatting ||
|
||||
!chatting.eventList ||
|
||||
chatting.eventList.ids.length === 0
|
||||
) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const statusEventInfo: Info<EventJson> = {
|
||||
...chatting.eventList[eventSeq],
|
||||
type: EventType.RecalledMessage,
|
||||
sentMessage
|
||||
};
|
||||
|
||||
// 파일이 회수되었을 경우 fileInfoList 에서도 삭제 한다.
|
||||
if (chatting.eventList.entities[eventSeq].type === EventType.File) {
|
||||
const fileInfoList = chatting.fileInfoList;
|
||||
if (!!chatting && !!fileInfoList) {
|
||||
fileInfoList.ids.forEach((id) => {
|
||||
const fileInfo: FileInfo = fileInfoList.entities[id];
|
||||
if (action.eventSeq === fileInfo.eventSeq) {
|
||||
fileInfoSeq = id;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
chattings: adapterChatting.upsertOne(
|
||||
{
|
||||
...chatting,
|
||||
eventList: adapterEventList.updateOne(
|
||||
{
|
||||
id: eventSeq,
|
||||
changes: statusEventInfo
|
||||
},
|
||||
{
|
||||
...chatting.eventList
|
||||
}
|
||||
),
|
||||
fileInfoList: !!fileInfoSeq
|
||||
? adapterFileInfoList.removeOne(fileInfoSeq, {
|
||||
...chatting.fileInfoList
|
||||
})
|
||||
: chatting.fileInfoList
|
||||
},
|
||||
{ ...state.chattings }
|
||||
)
|
||||
};
|
||||
}),
|
||||
|
||||
on(clearActiveRoomId, (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
activeRoomId: null
|
||||
};
|
||||
}),
|
||||
|
||||
/*******************************************************************
|
||||
* [Room Action watching.]
|
||||
*******************************************************************/
|
||||
|
@ -197,7 +348,9 @@ export const reducer = createReducer(
|
|||
|
||||
return {
|
||||
...state,
|
||||
chattings: adapterChatting.removeMany(roomIds, { ...state.chattings })
|
||||
chattings: adapterChatting.removeMany(roomIds, {
|
||||
...state.chattings
|
||||
})
|
||||
};
|
||||
})
|
||||
);
|
||||
|
|
|
@ -2,12 +2,11 @@ import { Selector, createSelector } from '@ngrx/store';
|
|||
import { EntityState, createEntityAdapter, Dictionary } from '@ngrx/entity';
|
||||
|
||||
import { InfoResponse, Info, EventJson } from '@ucap/protocol-event';
|
||||
|
||||
import { FileInfo, FileDownloadInfo } from '@ucap/protocol-file';
|
||||
|
||||
export interface EventListState extends EntityState<Info<EventJson>> {}
|
||||
export interface FileInfoListState extends EntityState<FileInfo> {}
|
||||
export interface FileInfoCheckListState extends EntityState<FileDownloadInfo> {}
|
||||
// export interface FileInfoCheckListState extends EntityState<FileDownloadInfo> {}
|
||||
|
||||
export const adapterEventList = createEntityAdapter<Info<EventJson>>({
|
||||
selectId: (info) => info.seq,
|
||||
|
@ -21,12 +20,12 @@ export const adapterFileInfoList = createEntityAdapter<FileInfo>({
|
|||
return b.seq - a.seq;
|
||||
}
|
||||
});
|
||||
export const adapterFileInfoCheckList = createEntityAdapter<FileDownloadInfo>({
|
||||
selectId: (info) => info.seq,
|
||||
sortComparer: (a, b) => {
|
||||
return b.seq - a.seq;
|
||||
}
|
||||
});
|
||||
// export const adapterFileInfoCheckList = createEntityAdapter<FileDownloadInfo>({
|
||||
// selectId: (info) => info.seq,
|
||||
// sortComparer: (a, b) => {
|
||||
// return b.seq - a.seq;
|
||||
// }
|
||||
// });
|
||||
|
||||
const eventListInitialState: EventListState = adapterEventList.getInitialState(
|
||||
{}
|
||||
|
@ -34,9 +33,9 @@ const eventListInitialState: EventListState = adapterEventList.getInitialState(
|
|||
const fileInfoListInitialState: FileInfoListState = adapterFileInfoList.getInitialState(
|
||||
{}
|
||||
);
|
||||
const fileInfoCheckListInitialState: FileInfoCheckListState = adapterFileInfoCheckList.getInitialState(
|
||||
{}
|
||||
);
|
||||
// const fileInfoCheckListInitialState: FileInfoCheckListState = adapterFileInfoCheckList.getInitialState(
|
||||
// {}
|
||||
// );
|
||||
|
||||
const {
|
||||
selectAll: selectAllForEventList,
|
||||
|
@ -52,12 +51,12 @@ const {
|
|||
selectTotal: selectTotalForFileInfoList
|
||||
} = adapterFileInfoList.getSelectors();
|
||||
|
||||
const {
|
||||
selectAll: selectAllForFileInfoCheckList,
|
||||
selectEntities: selectEntitiesForFileInfoCheckList,
|
||||
selectIds: selectIdsForFileInfoCheckList,
|
||||
selectTotal: selectTotalForFileInfoCheckList
|
||||
} = adapterFileInfoCheckList.getSelectors();
|
||||
// const {
|
||||
// selectAll: selectAllForFileInfoCheckList,
|
||||
// selectEntities: selectEntitiesForFileInfoCheckList,
|
||||
// selectIds: selectIdsForFileInfoCheckList,
|
||||
// selectTotal: selectTotalForFileInfoCheckList
|
||||
// } = adapterFileInfoCheckList.getSelectors();
|
||||
|
||||
export interface Chatting {
|
||||
roomId?: string;
|
||||
|
@ -65,11 +64,12 @@ export interface Chatting {
|
|||
eventListProcessing?: boolean;
|
||||
eventList?: EventListState;
|
||||
eventStatus?: InfoResponse | null;
|
||||
remainEvent?: boolean;
|
||||
remainEvent?: boolean | null;
|
||||
|
||||
fileInfoListProcessing?: boolean;
|
||||
fileInfoList?: FileInfoListState;
|
||||
fileInfoCheckList?: FileInfoCheckListState;
|
||||
// fileInfoCheckList?: FileInfoCheckListState;
|
||||
fileInfoCheckList?: FileDownloadInfo[];
|
||||
fileInfoSyncDate?: string;
|
||||
}
|
||||
|
||||
|
@ -139,12 +139,37 @@ export function selectors<S>(selector: Selector<any, State>) {
|
|||
(state) => state.fileInfoListProcessing
|
||||
);
|
||||
const selectChattingFileInfoList = createSelector(
|
||||
selectChatting,
|
||||
(state) => state.fileInfoList
|
||||
selectChattings,
|
||||
(state: ChattingState, roomId: string) => {
|
||||
const chatting = state.entities && state.entities[roomId];
|
||||
if (!!chatting) {
|
||||
return chatting?.fileInfoList;
|
||||
} else {
|
||||
return adapterFileInfoList.getInitialState();
|
||||
}
|
||||
}
|
||||
);
|
||||
// const selectChattingFileInfoCheckList = createSelector(
|
||||
// selectChattings,
|
||||
// (state: ChattingState, roomId: string) => {
|
||||
// const chatting = state.entities && state.entities[roomId];
|
||||
// if (!!chatting) {
|
||||
// return chatting?.fileInfoCheckList;
|
||||
// } else {
|
||||
// return adapterFileInfoCheckList.getInitialState();
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
const selectChattingFileInfoCheckList = createSelector(
|
||||
selectChatting,
|
||||
(state) => state.fileInfoCheckList
|
||||
selectChattings,
|
||||
(state: ChattingState, roomId: string) => {
|
||||
const chatting = state.entities && state.entities[roomId];
|
||||
if (!!chatting) {
|
||||
return chatting?.fileInfoCheckList;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
);
|
||||
const selectChattingFileInfoSyncDate = createSelector(
|
||||
selectChatting,
|
||||
|
@ -167,10 +192,11 @@ export function selectors<S>(selector: Selector<any, State>) {
|
|||
selectChattingFileInfoList,
|
||||
selectAllForFileInfoList
|
||||
),
|
||||
fileInfoCheckList: createSelector(
|
||||
selectChattingFileInfoCheckList,
|
||||
selectAllForFileInfoCheckList
|
||||
),
|
||||
// fileInfoCheckList: createSelector(
|
||||
// selectChattingFileInfoCheckList,
|
||||
// selectAllForFileInfoCheckList
|
||||
// ),
|
||||
fileInfoCheckList: selectChattingFileInfoCheckList,
|
||||
fileInfoSyncDate: selectChattingFileInfoSyncDate
|
||||
};
|
||||
}
|
||||
|
|
|
@ -160,21 +160,6 @@ export const create = createAction(
|
|||
'[ucap::chat::room] create',
|
||||
props<{ req: CreateRequest }>()
|
||||
);
|
||||
/**
|
||||
* Success of create request
|
||||
*/
|
||||
export const createSuccess = createAction(
|
||||
'[ucap::chat::room] create Success',
|
||||
props<{ res: CreateResponse }>()
|
||||
);
|
||||
/**
|
||||
* Failure of create request
|
||||
*/
|
||||
export const createFailure = createAction(
|
||||
'[ucap::chat::room] create Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
|
||||
/**
|
||||
* create timer room
|
||||
*/
|
||||
|
@ -183,17 +168,17 @@ export const createTimer = createAction(
|
|||
props<{ req: CreateTimerRequest }>()
|
||||
);
|
||||
/**
|
||||
* Success of openTimer request
|
||||
* Success of create room / timer room request
|
||||
*/
|
||||
export const createTimerSuccess = createAction(
|
||||
'[ucap::chat::room] createTimer Success',
|
||||
props<{ res: CreateTimerResponse }>()
|
||||
export const createRoomSuccess = createAction(
|
||||
'[ucap::chat::room] create room Success',
|
||||
props<{ res: CreateResponse; isForward?: boolean }>()
|
||||
);
|
||||
/**
|
||||
* Failure of createTimer request
|
||||
* Failure of create room / timer room request
|
||||
*/
|
||||
export const createTimerFailure = createAction(
|
||||
'[ucap::chat::room] createTimer Failure',
|
||||
export const createRoomFailure = createAction(
|
||||
'[ucap::chat::room] create room Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
|
||||
|
@ -264,6 +249,15 @@ export const updateFailure = createAction(
|
|||
'[ucap::chat::room] update Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
/**
|
||||
* Success of update RoomName
|
||||
*/
|
||||
export const updateRoomName = createAction(
|
||||
'[ucap::chat::room] update Roomname for Notification',
|
||||
props<{
|
||||
res: UpdateResponse;
|
||||
}>()
|
||||
);
|
||||
|
||||
/**
|
||||
* update isJoinRoom of user information from true to false
|
||||
|
@ -331,18 +325,6 @@ export const closeFailure = createAction(
|
|||
props<{ error: any }>()
|
||||
);
|
||||
|
||||
/**
|
||||
* Invite conversation partner to room or create room
|
||||
*/
|
||||
export const inviteOrCreate = createAction(
|
||||
'[ucap::chat::room] inviteOrCreate',
|
||||
props<{
|
||||
roomInfo: RoomInfo;
|
||||
localeCode: LocaleCode;
|
||||
req: CreateRequest;
|
||||
}>()
|
||||
);
|
||||
|
||||
/**
|
||||
* Invite conversation partner to room
|
||||
*/
|
||||
|
@ -386,6 +368,13 @@ export const expelFailure = createAction(
|
|||
'[ucap::chat::room] expel Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
/**
|
||||
* expel notification
|
||||
*/
|
||||
export const expelNotification = createAction(
|
||||
'[ucap::chat::room] expel Notification',
|
||||
props<{ res: ExitForcingResponse }>()
|
||||
);
|
||||
|
||||
/**
|
||||
* update interval of timer room
|
||||
|
@ -422,10 +411,24 @@ export const inviteNotification = createAction(
|
|||
*/
|
||||
export const exitNotification = createAction(
|
||||
'[ucap::chat::room] Exit Notification',
|
||||
props<{ roomId: string; userSeq: string; senderSeq: string }>()
|
||||
props<{ roomId: string; senderSeq: string }>()
|
||||
);
|
||||
|
||||
export const updateUnreadCount = createAction(
|
||||
'[ucap::chat::room] Update unread count',
|
||||
props<{ roomId: string; noReadCnt?: number }>()
|
||||
);
|
||||
|
||||
/**
|
||||
* room user info update by Notification
|
||||
*/
|
||||
// export const userInfoListUpdate = createAction(
|
||||
// '[ucap::chat::room] user info update',
|
||||
// props<{
|
||||
// roomInfo: RoomInfo;
|
||||
// roomUserInfo: {
|
||||
// userInfoShortList: UserInfoShort[];
|
||||
// userInfoList: RoomUserInfo[];
|
||||
// };
|
||||
// }>()
|
||||
// );
|
||||
|
|
|
@ -14,8 +14,8 @@ import { Injectable } from '@angular/core';
|
|||
import { Store, select } from '@ngrx/store';
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
|
||||
import { LocaleCode } from '@ucap/core';
|
||||
import {
|
||||
RoomType,
|
||||
OpenResponse as CreateResponse,
|
||||
Open3Response as CreateTimerResponse,
|
||||
ExitResponse as DeleteResponse,
|
||||
|
@ -24,22 +24,30 @@ import {
|
|||
InviteResponse,
|
||||
ExitForcingResponse,
|
||||
UpdateTimerSetResponse,
|
||||
InfoRequest
|
||||
InfoRequest,
|
||||
UserInfoShort,
|
||||
UserInfo,
|
||||
RoomInfo
|
||||
} from '@ucap/protocol-room';
|
||||
import { UserNotification, UserInfoUpdateType } from '@ucap/protocol-info';
|
||||
|
||||
import { I18nService } from '@ucap/ng-i18n';
|
||||
import { RoomProtocolService } from '@ucap/ng-protocol-room';
|
||||
import { SyncProtocolService } from '@ucap/ng-protocol-sync';
|
||||
|
||||
import {
|
||||
PresenceActions,
|
||||
CommonActions,
|
||||
UserSelector
|
||||
} from '@ucap/ng-store-organization';
|
||||
import { LoginActions } from '@ucap/ng-store-authentication';
|
||||
import * as ChattingAction from '../Chatting/actions';
|
||||
import { RoomSelector } from '../state';
|
||||
|
||||
import * as ChattingAction from '../chatting/actions';
|
||||
import { RoomSelector, ChattingSelector } from '../state';
|
||||
|
||||
import {
|
||||
rooms,
|
||||
roomsFailure,
|
||||
roomsSuccess,
|
||||
room,
|
||||
roomSuccess,
|
||||
roomFailure,
|
||||
inviteNotification,
|
||||
exitNotification,
|
||||
|
@ -48,11 +56,9 @@ import {
|
|||
close,
|
||||
delSuccess,
|
||||
create,
|
||||
createSuccess,
|
||||
createFailure,
|
||||
createTimer,
|
||||
createTimerSuccess,
|
||||
createTimerFailure,
|
||||
createRoomSuccess,
|
||||
createRoomFailure,
|
||||
del,
|
||||
delFailure,
|
||||
update,
|
||||
|
@ -61,7 +67,6 @@ import {
|
|||
open,
|
||||
openSuccess,
|
||||
closeSuccess,
|
||||
inviteOrCreate,
|
||||
invite,
|
||||
inviteSuccess,
|
||||
inviteFailure,
|
||||
|
@ -79,10 +84,9 @@ import {
|
|||
selectedRoom,
|
||||
room2Success,
|
||||
selectedRoomSuccess,
|
||||
clearSelectedRoom
|
||||
clearSelectedRoom,
|
||||
expelNotification
|
||||
} from './actions';
|
||||
import { Router } from '@angular/router';
|
||||
import { LocaleCode } from '@ucap/core';
|
||||
|
||||
@Injectable()
|
||||
export class Effects {
|
||||
|
@ -121,14 +125,25 @@ export class Effects {
|
|||
roomInfo2Res: res
|
||||
})
|
||||
);
|
||||
|
||||
// Buddy Presence
|
||||
const targetUserInfos = req.isDetail
|
||||
? res.roomUserInfo.userInfoList.map(
|
||||
(userInfo) => userInfo.seq + ''
|
||||
)
|
||||
: res.roomUserInfo.userInfoShortList.map(
|
||||
(userInfo) => userInfo.seq + ''
|
||||
);
|
||||
if (!!targetUserInfos && targetUserInfos.length > 0) {
|
||||
this.store.dispatch(
|
||||
PresenceActions.bulkInfo({
|
||||
divCd: 'roomBulk',
|
||||
userSeqs: targetUserInfos
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// is not join room. so, redirect chat main.
|
||||
this.router.navigate([
|
||||
'chat',
|
||||
{
|
||||
outlets: { content: 'index' }
|
||||
}
|
||||
]);
|
||||
this.store.dispatch(
|
||||
clearSelectedRoom({ roomId: req.roomId })
|
||||
);
|
||||
|
@ -237,27 +252,9 @@ export class Effects {
|
|||
exhaustMap((req) => {
|
||||
return this.roomProtocolService.open(req).pipe(
|
||||
map((res: CreateResponse) => {
|
||||
console.log('CreateResponse', res);
|
||||
|
||||
this.store.dispatch(createSuccess({ res }));
|
||||
|
||||
this.router.navigate(
|
||||
[
|
||||
'chat',
|
||||
{
|
||||
outlets: { content: 'chatroom' }
|
||||
}
|
||||
],
|
||||
{
|
||||
queryParams: { roomId: res.roomId }
|
||||
}
|
||||
);
|
||||
|
||||
// if (!res.newRoom) {
|
||||
// } else {
|
||||
// }
|
||||
this.store.dispatch(createRoomSuccess({ res }));
|
||||
}),
|
||||
catchError((error) => of(createFailure({ error })))
|
||||
catchError((error) => of(createRoomFailure({ error })))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
@ -265,33 +262,53 @@ export class Effects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
createTimer$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
createTimer$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(createTimer),
|
||||
map((action) => action.req),
|
||||
exhaustMap((req) => {
|
||||
return this.roomProtocolService.open3(req).pipe(
|
||||
map((res: CreateTimerResponse) => {
|
||||
return createTimerSuccess({ res });
|
||||
// return createTimerSuccess({ res });
|
||||
this.store.dispatch(createRoomSuccess({ res }));
|
||||
}),
|
||||
catchError((error) => of(createTimerFailure({ error })))
|
||||
catchError((error) => of(createRoomFailure({ error })))
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
del$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(del),
|
||||
map((action) => action.req),
|
||||
exhaustMap((req) => {
|
||||
withLatestFrom(this.store.pipe(select(ChattingSelector.activeRoomId))),
|
||||
exhaustMap(([req, activeRoomId]) => {
|
||||
const existActiveRoomId = req.roomId === activeRoomId;
|
||||
|
||||
return this.roomProtocolService.exit(req).pipe(
|
||||
switchMap((res: DeleteResponse) => [
|
||||
switchMap((res: DeleteResponse) => {
|
||||
if (!!existActiveRoomId) {
|
||||
return [
|
||||
// clear activeRoomId
|
||||
clearSelectedRoom({ roomId: res.roomId }),
|
||||
// close room, clear chatting
|
||||
close({ roomIds: [res.roomId] }),
|
||||
// clear room in rooms.
|
||||
delSuccess({ res })
|
||||
]),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
// close room, clear chatting
|
||||
close({ roomIds: [res.roomId] }),
|
||||
// clear room in rooms.
|
||||
delSuccess({ res })
|
||||
];
|
||||
}
|
||||
}),
|
||||
catchError((error) => of(delFailure({ error })))
|
||||
);
|
||||
})
|
||||
|
@ -302,14 +319,32 @@ export class Effects {
|
|||
this.actions$.pipe(
|
||||
ofType(delMulti),
|
||||
map((action) => action.req),
|
||||
exhaustMap((req) => {
|
||||
withLatestFrom(this.store.pipe(select(ChattingSelector.activeRoomId))),
|
||||
exhaustMap(([req, activeRoomId]) => {
|
||||
const existActiveRoomId = req.roomIds.find(
|
||||
(roomId) => roomId === activeRoomId
|
||||
);
|
||||
|
||||
return this.roomProtocolService.exitAll(req).pipe(
|
||||
switchMap((res: DeleteMultiResponse) => [
|
||||
switchMap((res: DeleteMultiResponse) => {
|
||||
if (!!existActiveRoomId) {
|
||||
return [
|
||||
// clear selected room
|
||||
clearSelectedRoom({ roomId: existActiveRoomId }),
|
||||
// close room, clear chatting
|
||||
close({ roomIds: res.roomIds }),
|
||||
// clear room in rooms.
|
||||
delMultiSuccess({ res })
|
||||
]),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
// close room, clear chatting
|
||||
close({ roomIds: res.roomIds }),
|
||||
// clear room in rooms.
|
||||
delMultiSuccess({ res })
|
||||
];
|
||||
}
|
||||
}),
|
||||
catchError((error) => of(delMultiFailure({ error })))
|
||||
);
|
||||
})
|
||||
|
@ -357,33 +392,6 @@ export class Effects {
|
|||
);
|
||||
});
|
||||
|
||||
inviteOrCreate$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(inviteOrCreate),
|
||||
map((action) => {
|
||||
const roomInfo = action.roomInfo;
|
||||
const localeCode = action.localeCode;
|
||||
|
||||
switch (roomInfo.roomType) {
|
||||
case RoomType.Single:
|
||||
return create({ req: action.req });
|
||||
case RoomType.Multi:
|
||||
return invite({
|
||||
req: {
|
||||
roomId: roomInfo.roomId,
|
||||
inviteUserSeqs: [...action.req.userSeqs]
|
||||
},
|
||||
localeCode
|
||||
});
|
||||
default:
|
||||
return inviteFailure({
|
||||
error: `type[${roomInfo.roomType}] of room is not valid`
|
||||
});
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
invite$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(invite),
|
||||
|
@ -398,7 +406,7 @@ export class Effects {
|
|||
room({
|
||||
req: {
|
||||
roomId: req.roomId,
|
||||
isDetail: true,
|
||||
isDetail: false,
|
||||
localeCode
|
||||
}
|
||||
})
|
||||
|
@ -425,6 +433,47 @@ export class Effects {
|
|||
)
|
||||
);
|
||||
|
||||
expelSuccess$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(expelSuccess),
|
||||
map((action) => action.res),
|
||||
tap((res) => {
|
||||
this.store.dispatch(
|
||||
excludeUser({ roomId: res.roomId, userSeqs: res.userSeqs })
|
||||
);
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
expelNotification$ = createEffect(() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(expelNotification),
|
||||
withLatestFrom(this.store.pipe(select(UserSelector.user))),
|
||||
exhaustMap(([action, user]) => {
|
||||
const roomId = action.res.roomId;
|
||||
const userSeqs = action.res.userSeqs;
|
||||
const existMe = userSeqs.indexOf(String(user.info.seq)) > -1;
|
||||
|
||||
// 내가 강퇴 대상에 포함되어 있으면 우선 처리.
|
||||
if (!!existMe) {
|
||||
return [
|
||||
close({ roomIds: [roomId] }),
|
||||
clearSelectedRoom({ roomId }),
|
||||
delSuccess({ res: { roomId } })
|
||||
];
|
||||
} else {
|
||||
// 나를 제외한 강퇴 인원 처리.
|
||||
if (!!userSeqs && userSeqs.length > 0) {
|
||||
return [excludeUser({ roomId, userSeqs })];
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
updateTimeRoomInterval$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(updateTimeRoomInterval),
|
||||
|
@ -440,28 +489,48 @@ export class Effects {
|
|||
)
|
||||
);
|
||||
|
||||
inviteNotification$ = createEffect(() => {
|
||||
/**
|
||||
* @discription Call by notifications case in SSVC_TYPE_ROOM_INVITE_RES, SSVC_TYPE_ROOM_INVITE_NOTI
|
||||
* 1. roomlist 를 체크하여 없을경우 내가 초대된 경우라 간주하고 방 조회하여 갱신하지 않도록 한다.(첫 대화가 들어오면 그때 조회.)
|
||||
* 2. roomlist 를 체크하여 있을 경우 기존방에 다른 인원이 추가되었을 경우이므로 방 조회하여 갱신한다.
|
||||
*/
|
||||
inviteNotification$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(inviteNotification),
|
||||
map((action) =>
|
||||
withLatestFrom(this.store.pipe(select(RoomSelector.rooms))),
|
||||
map(([action, roomList]) => {
|
||||
const roomId = action.noti.roomId;
|
||||
if (!!roomList && roomList.length > 0) {
|
||||
if (roomList.some((roomInfo) => roomId === roomInfo.roomId)) {
|
||||
this.store.dispatch(
|
||||
room({
|
||||
req: {
|
||||
roomId: action.noti.roomId,
|
||||
isDetail: true,
|
||||
roomId,
|
||||
isDetail: false,
|
||||
localeCode: action.localeCode
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
{
|
||||
dispatch: false
|
||||
}
|
||||
);
|
||||
|
||||
exitNotification$ = createEffect(() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(exitNotification),
|
||||
switchMap((action) => {
|
||||
if (action.userSeq === action.senderSeq) {
|
||||
withLatestFrom(this.store.pipe(select(UserSelector.user))),
|
||||
switchMap(([action, user]) => {
|
||||
if (String(user.info.seq) === String(action.senderSeq)) {
|
||||
return [
|
||||
close({ roomIds: [action.roomId] }),
|
||||
clearSelectedRoom({ roomId: action.roomId }),
|
||||
delSuccess({
|
||||
res: { roomId: action.roomId }
|
||||
})
|
||||
|
@ -490,7 +559,134 @@ export class Effects {
|
|||
const roomId = action.roomId;
|
||||
|
||||
if (!roomList.find((roomInfo) => roomInfo.roomId === roomId)) {
|
||||
this.store.dispatch(rooms({ localeCode: LocaleCode.Korean }));
|
||||
this.store.dispatch(
|
||||
rooms({
|
||||
localeCode: this.i18nService.currentLng.toUpperCase() as LocaleCode
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
userNotificationForRoom$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(CommonActions.userNotification),
|
||||
withLatestFrom(
|
||||
this.store.pipe(select(UserSelector.user)),
|
||||
this.store.pipe(select(RoomSelector.rooms)),
|
||||
this.store.pipe(select(RoomSelector.roomUsers)),
|
||||
this.store.pipe(select(RoomSelector.roomUsersShort))
|
||||
),
|
||||
tap(([action, user, roomList, roomUsers, roomUsersShort]) => {
|
||||
const noti = action.noti as UserNotification;
|
||||
|
||||
if (
|
||||
Number(action.noti.SENDER_SEQ) !== Number(user.info.seq) &&
|
||||
noti.type === UserInfoUpdateType.Image
|
||||
) {
|
||||
roomUsers = (roomUsers || []).filter(
|
||||
(userMap) =>
|
||||
roomList.findIndex((rInfo) => rInfo.roomId === userMap.roomId) >
|
||||
-1
|
||||
);
|
||||
|
||||
roomUsersShort = (roomUsersShort || []).filter(
|
||||
(userMap) =>
|
||||
roomList.findIndex((rInfo) => rInfo.roomId === userMap.roomId) >
|
||||
-1
|
||||
);
|
||||
|
||||
const tempRoomList: {
|
||||
roomInfo: RoomInfo;
|
||||
uInfos?: UserInfo[];
|
||||
userInfoS?: UserInfoShort[];
|
||||
}[] = [];
|
||||
|
||||
const findIdx = noti.info.indexOf('ProfileImage');
|
||||
let imgInfo: string = noti.info;
|
||||
|
||||
if (findIdx > -1) {
|
||||
const startIdx = noti.info.indexOf('/', findIdx);
|
||||
imgInfo = noti.info.substring(startIdx);
|
||||
}
|
||||
|
||||
for (const ru of roomUsers) {
|
||||
ru.userInfos.every((u) => {
|
||||
if (Number(u.seq) === Number(noti.SENDER_SEQ)) {
|
||||
const tempInfos: UserInfo[] = [];
|
||||
const roomInfo = roomList.filter(
|
||||
(r) => r.roomId === ru.roomId
|
||||
)[0];
|
||||
const cUser = {
|
||||
...u,
|
||||
profileImageFile: imgInfo
|
||||
};
|
||||
|
||||
ru.userInfos.map((u2) => {
|
||||
if (Number(u2.seq) === Number(u.seq)) {
|
||||
tempInfos.push(cUser);
|
||||
} else {
|
||||
tempInfos.push(u2);
|
||||
}
|
||||
});
|
||||
|
||||
tempRoomList.push({
|
||||
roomInfo,
|
||||
uInfos: tempInfos
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
for (const ru of roomUsersShort) {
|
||||
ru.userInfos.every((u) => {
|
||||
if (Number(u.seq) === Number(noti.SENDER_SEQ)) {
|
||||
const tempShorts: UserInfoShort[] = [];
|
||||
const roomInfo = roomList.filter(
|
||||
(r) => r.roomId === ru.roomId
|
||||
)[0];
|
||||
|
||||
const cUser = {
|
||||
...u,
|
||||
profileImageFile: imgInfo
|
||||
};
|
||||
ru.userInfos.map((u2) => {
|
||||
if (Number(u2.seq) === Number(u.seq)) {
|
||||
tempShorts.push(cUser);
|
||||
} else {
|
||||
tempShorts.push(u2);
|
||||
}
|
||||
});
|
||||
|
||||
tempRoomList.push({
|
||||
roomInfo,
|
||||
userInfoS: tempShorts
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
tempRoomList.map((obj) => {
|
||||
this.store.dispatch(
|
||||
room2Success({
|
||||
roomInfo: obj.roomInfo,
|
||||
roomUserInfo: {
|
||||
userInfoList: obj?.uInfos,
|
||||
userInfoShortList: obj?.userInfoS
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
@ -501,8 +697,8 @@ export class Effects {
|
|||
constructor(
|
||||
private actions$: Actions,
|
||||
private store: Store<any>,
|
||||
private router: Router,
|
||||
private syncProtocolService: SyncProtocolService,
|
||||
private roomProtocolService: RoomProtocolService
|
||||
private roomProtocolService: RoomProtocolService,
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { createReducer, on } from '@ngrx/store';
|
||||
|
||||
import {
|
||||
UserInfo as RoomUserInfo,
|
||||
UserInfoShort as RoomUserInfoShort,
|
||||
RoomInfo
|
||||
} from '@ucap/protocol-room';
|
||||
import { RoomInfo } from '@ucap/protocol-room';
|
||||
import { Info, EventJson } from '@ucap/protocol-event';
|
||||
|
||||
import * as chattingActions from '../chatting/actions';
|
||||
import * as ChattingActions from '../chatting/actions';
|
||||
import { ChatUtil } from '../../utils/chat.util';
|
||||
|
||||
import {
|
||||
initialState,
|
||||
|
@ -19,18 +17,17 @@ import {
|
|||
import {
|
||||
roomsSuccess,
|
||||
roomSuccess,
|
||||
excludeUser,
|
||||
excludeUserSuccess,
|
||||
delSuccess,
|
||||
rooms2Success,
|
||||
delMultiSuccess,
|
||||
updateSuccess,
|
||||
room2Success,
|
||||
createSuccess,
|
||||
updateUnreadCount
|
||||
createRoomSuccess,
|
||||
updateUnreadCount,
|
||||
updateRoomName,
|
||||
updateTimeRoomIntervalSuccess
|
||||
} from './actions';
|
||||
import { Info, EventJson } from '@ucap/protocol-event';
|
||||
import { ChatUtil } from '../../utils/chat.util';
|
||||
|
||||
export const reducer = createReducer(
|
||||
initialState,
|
||||
|
@ -184,63 +181,88 @@ export const reducer = createReducer(
|
|||
|
||||
on(excludeUserSuccess, (state, action) => {
|
||||
const roomId = action.roomId;
|
||||
const roomUserMap = state.roomUsers.entities[roomId];
|
||||
const roomUserMapShort = state.roomUsersShort.entities[roomId];
|
||||
|
||||
const userInfos: RoomUserInfo[] = [...roomUserMap.userInfos];
|
||||
const userInfosShort: RoomUserInfoShort[] = [...roomUserMapShort.userInfos];
|
||||
|
||||
action.userSeqs.forEach((userSeq) => {
|
||||
const userInfo: RoomUserInfo = userInfos.find(
|
||||
(u) => userSeq === String(u.seq)
|
||||
);
|
||||
|
||||
if (!!userInfo && !!userInfo.seq) {
|
||||
userInfo.isJoinRoom = false;
|
||||
}
|
||||
|
||||
const userInfoShort: RoomUserInfoShort = userInfosShort.find(
|
||||
(u) => userSeq === String(u.seq)
|
||||
);
|
||||
|
||||
if (!!userInfoShort && !!userInfoShort.seq) {
|
||||
userInfoShort.isJoinRoom = false;
|
||||
}
|
||||
});
|
||||
|
||||
let roomUser: RoomUserMap = state.roomUsers.entities[roomId];
|
||||
if (!!roomUser) {
|
||||
roomUser = {
|
||||
...roomUser,
|
||||
userInfos: state.roomUsers.entities[roomId].userInfos.map(
|
||||
(roomUserInfo) => {
|
||||
if (
|
||||
action.userSeqs.some((seq) => seq + '' === roomUserInfo.seq + '')
|
||||
) {
|
||||
return {
|
||||
...state,
|
||||
roomUsers: adapterRoomUser.updateOne(
|
||||
{
|
||||
id: roomId,
|
||||
changes: {
|
||||
roomId,
|
||||
userInfos
|
||||
...roomUserInfo,
|
||||
isJoinRoom: false
|
||||
};
|
||||
} else {
|
||||
return roomUserInfo;
|
||||
}
|
||||
},
|
||||
{
|
||||
...state.roomUsers
|
||||
}
|
||||
),
|
||||
roomUsersShort: adapterRoomUserShort.updateOne(
|
||||
{
|
||||
id: roomId,
|
||||
changes: {
|
||||
roomId,
|
||||
userInfos: userInfosShort
|
||||
}
|
||||
},
|
||||
{
|
||||
...state.roomUsersShort
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
let roomUserShort: RoomUserShortMap = state.roomUsersShort.entities[roomId];
|
||||
if (!!roomUserShort) {
|
||||
roomUserShort = {
|
||||
...roomUserShort,
|
||||
userInfos: state.roomUsersShort.entities[roomId].userInfos.map(
|
||||
(roomUserInfo) => {
|
||||
if (
|
||||
action.userSeqs.some((seq) => seq + '' === roomUserInfo.seq + '')
|
||||
) {
|
||||
return {
|
||||
...roomUserInfo,
|
||||
isJoinRoom: false
|
||||
};
|
||||
} else {
|
||||
return roomUserInfo;
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
const currentRoomInfo = state.rooms.entities[roomId];
|
||||
let roomInfo: RoomInfo;
|
||||
if (!!currentRoomInfo) {
|
||||
roomInfo = {
|
||||
...currentRoomInfo,
|
||||
joinUserCount: !!roomUser
|
||||
? roomUser.userInfos.filter((item) => !!item.isJoinRoom).length
|
||||
: !!roomUserShort
|
||||
? roomUserShort.userInfos.filter((item) => !!item.isJoinRoom).length
|
||||
: currentRoomInfo.joinUserCount
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
rooms: !!roomInfo
|
||||
? adapterRoom.upsertOne(roomInfo, { ...state.rooms })
|
||||
: state.rooms,
|
||||
roomUsers: !!roomUser
|
||||
? adapterRoomUser.upsertOne(roomUser, {
|
||||
...state.roomUsers
|
||||
})
|
||||
: state.roomUsers,
|
||||
roomUsersShort: !!roomUserShort
|
||||
? adapterRoomUserShort.upsertOne(roomUserShort, {
|
||||
...state.roomUsersShort
|
||||
})
|
||||
: state.roomUsersShort
|
||||
};
|
||||
}),
|
||||
|
||||
on(createSuccess, (state, action) => {
|
||||
on(createRoomSuccess, (state, action) => {
|
||||
const standby = state.standbyRooms;
|
||||
const curRoomId = action.res.roomId;
|
||||
if (standby.findIndex((roomId) => roomId === curRoomId) > -1) {
|
||||
|
||||
if (
|
||||
!!action.isForward ||
|
||||
standby.findIndex((roomId) => roomId === curRoomId) > -1 ||
|
||||
!!state.rooms.entities[curRoomId]
|
||||
) {
|
||||
// Exist.
|
||||
return state;
|
||||
} else {
|
||||
|
@ -252,26 +274,6 @@ export const reducer = createReducer(
|
|||
}
|
||||
}),
|
||||
|
||||
on(delSuccess, (state, action) => {
|
||||
const roomId = action.res.roomId;
|
||||
const room = state.rooms.entities[roomId];
|
||||
|
||||
if (!room) {
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
rooms: adapterRoom.removeOne(roomId, { ...state.rooms }),
|
||||
roomUsers: adapterRoomUser.removeOne(roomId, {
|
||||
...state.roomUsers
|
||||
}),
|
||||
roomUsersShort: adapterRoomUserShort.removeOne(roomId, {
|
||||
...state.roomUsersShort
|
||||
})
|
||||
};
|
||||
}),
|
||||
|
||||
on(updateSuccess, (state, action) => {
|
||||
const roomInfo = {
|
||||
...state.rooms.entities[action.res.roomId],
|
||||
|
@ -285,6 +287,30 @@ export const reducer = createReducer(
|
|||
};
|
||||
}),
|
||||
|
||||
on(updateTimeRoomIntervalSuccess, (state, action) => {
|
||||
const roomInfo = {
|
||||
...state.rooms.entities[action.res.roomId],
|
||||
timeRoomInterval: action.res.timerInterval
|
||||
} as RoomInfo;
|
||||
|
||||
return {
|
||||
...state,
|
||||
rooms: adapterRoom.upsertOne(roomInfo, { ...state.rooms })
|
||||
};
|
||||
}),
|
||||
|
||||
on(updateRoomName, (state, action) => {
|
||||
const roomInfo = {
|
||||
...state.rooms.entities[action.res.roomId],
|
||||
roomName: action.res.roomName
|
||||
} as RoomInfo;
|
||||
|
||||
return {
|
||||
...state,
|
||||
rooms: adapterRoom.upsertOne(roomInfo, { ...state.rooms })
|
||||
};
|
||||
}),
|
||||
|
||||
on(delSuccess, (state, action) => {
|
||||
const roomId = action.res.roomId;
|
||||
const room = state.rooms.entities[roomId];
|
||||
|
@ -353,7 +379,7 @@ export const reducer = createReducer(
|
|||
/*******************************************************************
|
||||
* [Chatting Action watching.]
|
||||
*******************************************************************/
|
||||
on(chattingActions.readSuccess, (state, action) => {
|
||||
on(ChattingActions.readSuccess, (state, action) => {
|
||||
const roomId = action.roomId;
|
||||
const trgtUserSeq = action.SENDER_SEQ;
|
||||
|
||||
|
@ -413,7 +439,7 @@ export const reducer = createReducer(
|
|||
};
|
||||
}),
|
||||
|
||||
on(chattingActions.addEventSuccess, (state, action) => {
|
||||
on(ChattingActions.addEventSuccess, (state, action) => {
|
||||
const roomId = action.roomId;
|
||||
const info: Info<EventJson> = action.info;
|
||||
|
||||
|
@ -434,6 +460,12 @@ export const reducer = createReducer(
|
|||
action.info.sentMessageJson || action.info.sentMessage
|
||||
);
|
||||
|
||||
if (!finalEventMessage) {
|
||||
/**
|
||||
* 해당 타입은 메시지를 갱신하지 않는다.
|
||||
*/
|
||||
return state;
|
||||
} else {
|
||||
const roomInfo = {
|
||||
...currentRoomInfo,
|
||||
finalEventType: info.type,
|
||||
|
@ -447,6 +479,7 @@ export const reducer = createReducer(
|
|||
rooms: adapterRoom.upsertOne(roomInfo, { ...state.rooms }),
|
||||
standbyRooms: fixedStandByRooms
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import moment from 'moment';
|
||||
|
||||
import { Selector, createSelector } from '@ngrx/store';
|
||||
import { EntityState, createEntityAdapter, Dictionary } from '@ngrx/entity';
|
||||
import { EntityState, createEntityAdapter } from '@ngrx/entity';
|
||||
|
||||
import { RoomUserDetailData, RoomUserData } from '@ucap/protocol-sync';
|
||||
import {
|
||||
RoomInfo,
|
||||
UserInfo as RoomUserInfo,
|
||||
|
@ -134,16 +133,23 @@ export function selectors<S>(selector: Selector<any, State>) {
|
|||
selector,
|
||||
(state: State) => state.standbyRooms
|
||||
),
|
||||
unreadTotal: createSelector(
|
||||
selectRooms,
|
||||
selectAllForRoom,
|
||||
(roomState: RoomState, rooms: RoomInfo[]) => {
|
||||
unreadTotal: createSelector(selectRooms, (roomState: RoomState) => {
|
||||
let unreadTotal = 0;
|
||||
for (const room of rooms) {
|
||||
|
||||
// tslint:disable-next-line: forin
|
||||
for (const key in roomState.ids) {
|
||||
const roomId = roomState.ids[key];
|
||||
if (roomId === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (roomState.entities.hasOwnProperty(roomId)) {
|
||||
const room = roomState.entities[roomId];
|
||||
unreadTotal += room.noReadCnt;
|
||||
}
|
||||
return unreadTotal;
|
||||
}
|
||||
)
|
||||
|
||||
return unreadTotal;
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -128,8 +128,13 @@ export class ChatUtil {
|
|||
default:
|
||||
{
|
||||
const m = finalEventMessage as string;
|
||||
|
||||
if (eventType === EventType.Character && m.trim().length === 0) {
|
||||
eventMessage = '최근 대화 없음';
|
||||
} else {
|
||||
eventMessage = m;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,9 @@ export * from './lib/config/module-config';
|
|||
|
||||
export { CommonActions, RoomActions, ChattingActions };
|
||||
|
||||
export * from './lib/utils/chat.util';
|
||||
export * from './lib/store/state';
|
||||
export { Chatting } from './lib/store/chatting/state';
|
||||
export { RoomUserMap, RoomUserShortMap } from './lib/store/room/state';
|
||||
|
||||
export * from './lib/chat-store.module';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@ucap/ng-store-group",
|
||||
"version": "0.0.14",
|
||||
"version": "0.0.22",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createAction, props } from '@ngrx/store';
|
||||
import { UserInfo } from '@ucap/protocol-sync';
|
||||
|
||||
import { UserInfo } from '@ucap/protocol-sync';
|
||||
import {
|
||||
AddRequest as BuddyAddRequest,
|
||||
AddResponse as BuddyAddResponse,
|
||||
|
@ -11,7 +11,8 @@ import {
|
|||
} from '@ucap/protocol-buddy';
|
||||
import {
|
||||
UserNicknameRequest as NicknameRequest,
|
||||
UserNicknameResponse as NicknameResponse
|
||||
UserNicknameResponse as NicknameResponse,
|
||||
UserNotification
|
||||
} from '@ucap/protocol-info';
|
||||
|
||||
/**
|
||||
|
@ -142,3 +143,10 @@ export const nicknameFailure = createAction(
|
|||
'[ucap::group::buddy] user nickname Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
/**
|
||||
* buddy info update by Notification
|
||||
*/
|
||||
export const buddyInfoUpdate = createAction(
|
||||
'[ucap::group::buddy] buddy info update notification',
|
||||
props<{ noti: UserNotification }>()
|
||||
);
|
||||
|
|
|
@ -30,8 +30,11 @@ import { BuddyProtocolService } from '@ucap/ng-protocol-buddy';
|
|||
|
||||
import {
|
||||
DepartmentSelector,
|
||||
PresenceActions
|
||||
PresenceActions,
|
||||
CommonActions,
|
||||
UserSelector
|
||||
} from '@ucap/ng-store-organization';
|
||||
|
||||
import { LoginActions } from '@ucap/ng-store-authentication';
|
||||
|
||||
import * as groupActions from '../group/actions';
|
||||
|
@ -51,7 +54,8 @@ import {
|
|||
nickname,
|
||||
nicknameSuccess,
|
||||
nicknameFailure,
|
||||
delAndClear
|
||||
delAndClear,
|
||||
buddyInfoUpdate
|
||||
} from './actions';
|
||||
|
||||
import { BuddySelector, GroupSelector } from '../state';
|
||||
|
@ -135,7 +139,7 @@ export class Effects {
|
|||
),
|
||||
tap(([req, groupList, myDeptUserList]) => {
|
||||
for (const group of groupList) {
|
||||
if (group.userSeqs.indexOf(req.seq as any) > -1) {
|
||||
if (group.userSeqs.indexOf(String(req.seq)) > -1) {
|
||||
// 소속부서(내부서) 고정그룹 사용시 소속부서원을 삭제하지 않는다.
|
||||
if (
|
||||
!!this.moduleConfig.useMyDeptGroup &&
|
||||
|
@ -154,7 +158,7 @@ export class Effects {
|
|||
groupSeq: group.seq,
|
||||
groupName: group.name,
|
||||
userSeqs: group.userSeqs.filter(
|
||||
(userSeq) => userSeq !== (req.seq as any)
|
||||
(userSeq) => userSeq !== String(req.seq)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
@ -169,7 +173,7 @@ export class Effects {
|
|||
this.moduleConfig.useMyDeptGroup &&
|
||||
!!myDeptUserList &&
|
||||
myDeptUserList.filter(
|
||||
(deptUser) => deptUser.seq === (req.seq as any)
|
||||
(deptUser) => deptUser.seq === String(req.seq)
|
||||
).length > 0
|
||||
) {
|
||||
// skip;;
|
||||
|
@ -268,25 +272,42 @@ export class Effects {
|
|||
const userSeqsForDelete = action.userSeqsForDelete;
|
||||
|
||||
if (!!targetUserSeqs && 0 < targetUserSeqs.length) {
|
||||
const addBuddyList: string[] = [];
|
||||
targetUserSeqs.forEach((userSeq) => {
|
||||
if (!buddyList) {
|
||||
addBuddyList.push(userSeq);
|
||||
return;
|
||||
}
|
||||
// Add Buddy
|
||||
const userSeqsForAdd: string[] = [];
|
||||
|
||||
const index = buddyList.findIndex(
|
||||
(b) => b.seq === Number(userSeq)
|
||||
targetUserSeqs.map((seq) => {
|
||||
const findBuddy = buddyList.filter(
|
||||
(user) => Number(user.seq) === Number(seq)
|
||||
);
|
||||
if (-1 < index) {
|
||||
addBuddyList.push(userSeq);
|
||||
return;
|
||||
|
||||
if (!!findBuddy && findBuddy.length === 0) {
|
||||
userSeqsForAdd.push(seq);
|
||||
}
|
||||
});
|
||||
|
||||
if (addBuddyList.length > 0) {
|
||||
this.store.dispatch(add({ req: { userSeqs: addBuddyList } }));
|
||||
if (userSeqsForAdd.length > 0) {
|
||||
this.store.dispatch(add({ req: { userSeqs: userSeqsForAdd } }));
|
||||
}
|
||||
|
||||
// const addBuddyList: string[] = [];
|
||||
// targetUserSeqs.forEach((userSeq) => {
|
||||
// if (!buddyList) {
|
||||
// addBuddyList.push(userSeq);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const index = buddyList.findIndex(
|
||||
// (b) => b.seq === Number(userSeq)
|
||||
// );
|
||||
// if (-1 < index) {
|
||||
// addBuddyList.push(userSeq);
|
||||
// return;
|
||||
// }
|
||||
// });
|
||||
|
||||
// if (addBuddyList.length > 0) {
|
||||
// this.store.dispatch(add({ req: { userSeqs: addBuddyList } }));
|
||||
// }
|
||||
}
|
||||
|
||||
if (!!userSeqsForDelete && 0 < userSeqsForDelete.length) {
|
||||
|
@ -304,6 +325,21 @@ export class Effects {
|
|||
{ dispatch: false }
|
||||
);
|
||||
|
||||
userNotificationForBuddy$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(CommonActions.userNotification),
|
||||
withLatestFrom(this.store.pipe(select(UserSelector.user))),
|
||||
tap(([action, user]) => {
|
||||
if (Number(action.noti.SENDER_SEQ) !== Number(user.info.seq)) {
|
||||
this.store.dispatch(buddyInfoUpdate({ noti: action.noti }));
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private store: Store<any>,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { createReducer, on } from '@ngrx/store';
|
||||
|
||||
import { UserInfoUpdateType } from '@ucap/protocol-info';
|
||||
import { UserInfo } from '@ucap/protocol-sync';
|
||||
|
||||
import { initialState, adapterBuddy } from './state';
|
||||
|
@ -7,7 +8,8 @@ import {
|
|||
buddy2Success,
|
||||
delSuccess,
|
||||
updateSuccess,
|
||||
nicknameSuccess
|
||||
nicknameSuccess,
|
||||
buddyInfoUpdate
|
||||
} from './actions';
|
||||
|
||||
export const reducer = createReducer(
|
||||
|
@ -56,5 +58,49 @@ export const reducer = createReducer(
|
|||
...state,
|
||||
buddies: adapterBuddy.upsertOne(userInfo, { ...state.buddies })
|
||||
};
|
||||
}),
|
||||
|
||||
on(buddyInfoUpdate, (state, action) => {
|
||||
const noti = action.noti;
|
||||
let buddyInfo: UserInfo;
|
||||
|
||||
switch (noti.type) {
|
||||
case UserInfoUpdateType.Image:
|
||||
{
|
||||
const findIdx = noti.info.indexOf('ProfileImage');
|
||||
let imgInfo: string = noti.info;
|
||||
|
||||
if (findIdx > -1) {
|
||||
const startIdx = noti.info.indexOf('/', findIdx);
|
||||
imgInfo = noti.info.substring(startIdx);
|
||||
}
|
||||
|
||||
buddyInfo = {
|
||||
...state.buddies.entities[noti.SENDER_SEQ],
|
||||
profileImageFile: imgInfo
|
||||
};
|
||||
}
|
||||
break;
|
||||
case UserInfoUpdateType.Intro:
|
||||
{
|
||||
buddyInfo = {
|
||||
...state.buddies.entities[noti.SENDER_SEQ],
|
||||
intro: noti.info
|
||||
};
|
||||
}
|
||||
break;
|
||||
case UserInfoUpdateType.TelephoneVisible:
|
||||
{
|
||||
buddyInfo = {
|
||||
...state.buddies.entities[noti.SENDER_SEQ]
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
buddies: adapterBuddy.upsertOne(buddyInfo, { ...state.buddies })
|
||||
};
|
||||
})
|
||||
);
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { Selector, createSelector } from '@ngrx/store';
|
||||
import { EntityState, createEntityAdapter } from '@ngrx/entity';
|
||||
|
||||
import { UserInfo } from '@ucap/protocol-sync';
|
||||
|
||||
export interface BuddyState extends EntityState<UserInfo> {
|
||||
syncDate: string;
|
||||
}
|
||||
export const adapterBuddy = createEntityAdapter<UserInfo>({
|
||||
selectId: userInfo => userInfo.seq
|
||||
selectId: (userInfo) => userInfo.seq
|
||||
});
|
||||
|
||||
export interface State {
|
||||
|
@ -38,7 +39,7 @@ export function selectors<S>(selector: Selector<any, State>) {
|
|||
buddies: createSelector(selectBuddies, selectAllForBuddy),
|
||||
buddySyncDate: createSelector(
|
||||
selectBuddies,
|
||||
buddyState => buddyState.syncDate
|
||||
(buddyState) => buddyState.syncDate
|
||||
)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ import {
|
|||
} from '@ucap/protocol-group';
|
||||
|
||||
import { GroupProtocolService } from '@ucap/ng-protocol-group';
|
||||
|
||||
import { SyncProtocolService } from '@ucap/ng-protocol-sync';
|
||||
|
||||
import { DepartmentSelector } from '@ucap/ng-store-organization';
|
||||
|
@ -132,17 +131,20 @@ export class Effects {
|
|||
return this.actions$.pipe(
|
||||
ofType(updateMember),
|
||||
withLatestFrom(
|
||||
this.store.pipe(select(BuddySelector.buddies)),
|
||||
this.store.pipe(select(GroupSelector.groups)),
|
||||
this.store.pipe(select(DepartmentSelector.myDepartmentUserInfoList)) // 내 부서원 비교.
|
||||
),
|
||||
switchMap(([action, groupList, myDeptUserList]) => {
|
||||
switchMap(([action, buddyList, groupList, myDeptUserList]) => {
|
||||
const targetGroup = action.targetGroup;
|
||||
const targetUserSeqs = action.targetUserSeqs as any;
|
||||
const targetUserSeqs = action.targetUserSeqs;
|
||||
|
||||
// Del Buddy
|
||||
let userSeqsForDelete: string[] = targetGroup.userSeqs.filter(
|
||||
(v) => targetUserSeqs.indexOf(v + '') < 0
|
||||
);
|
||||
let userSeqsForDelete: string[] = targetGroup.userSeqs.filter((v) => {
|
||||
if (!targetUserSeqs.includes(v)) {
|
||||
return v;
|
||||
}
|
||||
});
|
||||
|
||||
// 소속부서(내부서) 고정그룹 사용시 소속부서원을 삭제하지 않는다.
|
||||
if (
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
"@ucap/protocol-query": "@ucap/protocol-query",
|
||||
"@ucap/protocol-status": "@ucap/protocol-status",
|
||||
"@ucap/ng-api-external": "@ucap/ng-api-external",
|
||||
"@ucap/ng-protocol-info": "@ucap/ng-protocol-info",
|
||||
"@ucap/ng-protocol-query": "@ucap/ng-protocol-query",
|
||||
"@ucap/ng-protocol-status": "@ucap/ng-protocol-status",
|
||||
"@ucap/ng-store-authentication": "@ucap/ng-store-authentication"
|
||||
"@ucap/ng-protocol-status": "@ucap/ng-protocol-status"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@ucap/ng-store-organization",
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.20",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
|
@ -10,8 +10,8 @@
|
|||
"@ucap/core": "~0.0.1",
|
||||
"@ucap/protocol-query": "~0.0.1",
|
||||
"@ucap/ng-api-external": "~0.0.1",
|
||||
"@ucap/ng-protocol-info": "~0.0.1",
|
||||
"@ucap/ng-protocol-query": "~0.0.1",
|
||||
"@ucap/ng-store-authentication": "~0.0.1",
|
||||
"tslib": "^1.10.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
import { createAction } from '@ngrx/store';
|
||||
import { createAction, props } from '@ngrx/store';
|
||||
|
||||
import { UserNotification } from '@ucap/protocol-info';
|
||||
|
||||
export const init = createAction('[ucap::organization::common] init');
|
||||
|
||||
export const userNotification = createAction(
|
||||
'[ucap::organization::common] user Notification',
|
||||
props<{ noti: UserNotification }>()
|
||||
);
|
||||
|
|
|
@ -1,8 +1,49 @@
|
|||
import { withLatestFrom, tap } from 'rxjs/operators';
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Actions } from '@ngrx/effects';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
|
||||
import { modifyInfoSuccess } from '../user/actions';
|
||||
import { UserSelector } from '../state';
|
||||
|
||||
import { userNotification } from './actions';
|
||||
|
||||
@Injectable()
|
||||
export class Effects {
|
||||
constructor(private actions$: Actions) {}
|
||||
userNotification$ = createEffect(
|
||||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(userNotification),
|
||||
withLatestFrom(this.store.pipe(select(UserSelector.user))),
|
||||
tap(([action, user]) => {
|
||||
if (Number(action.noti.SENDER_SEQ) === Number(user.info.seq)) {
|
||||
// my
|
||||
const noti = action.noti;
|
||||
|
||||
const findIdx = noti.info.indexOf('ProfileImage');
|
||||
let notInfo: string = noti.info;
|
||||
|
||||
if (findIdx > -1) {
|
||||
const startIdx = noti.info.indexOf('/', findIdx);
|
||||
notInfo = noti.info.substring(startIdx);
|
||||
}
|
||||
this.store.dispatch(
|
||||
modifyInfoSuccess({
|
||||
res: {
|
||||
SENDER_SEQ: noti.SENDER_SEQ,
|
||||
info: notInfo,
|
||||
type: noti.type
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
constructor(private actions$: Actions, private store: Store<any>) {}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Selector, createSelector } from '@ngrx/store';
|
||||
import { DeptInfo, UserInfoSS } from '@ucap/protocol-query';
|
||||
|
||||
import { Company } from '@ucap/api-external';
|
||||
|
||||
export interface State {
|
||||
|
|
|
@ -7,7 +7,8 @@ import { Store } from '@ngrx/store';
|
|||
import { Actions, ofType, createEffect } from '@ngrx/effects';
|
||||
|
||||
import { QueryProtocolService } from '@ucap/ng-protocol-query';
|
||||
import { LoginActions } from '@ucap/ng-store-authentication';
|
||||
|
||||
import { init as userInit } from '../user/actions';
|
||||
|
||||
import {
|
||||
dept,
|
||||
|
@ -23,12 +24,12 @@ import { DeptDivisionCode } from '@ucap/protocol-query';
|
|||
export class Effects {
|
||||
sessionCreatedForGroups$ = createEffect(() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(LoginActions.sessionCreated),
|
||||
map(action =>
|
||||
ofType(userInit),
|
||||
map((action) =>
|
||||
dept({
|
||||
req: {
|
||||
divCd: DeptDivisionCode.Organization,
|
||||
companyCode: action.loginSession.companyCode
|
||||
companyCode: action.user.companyCode
|
||||
}
|
||||
})
|
||||
)
|
||||
|
@ -38,17 +39,13 @@ export class Effects {
|
|||
dept$ = createEffect(() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(dept),
|
||||
map(action => action.req),
|
||||
switchMap(req => {
|
||||
map((action) => action.req),
|
||||
switchMap((req) => {
|
||||
return this.queryProtocolService.dept(req).pipe(
|
||||
map(res => {
|
||||
const departmentInfoList = res.departmentInfoList.sort((a, b) =>
|
||||
a.order < b.order ? -1 : a.order > b.order ? 1 : 0
|
||||
);
|
||||
|
||||
return deptSuccess({ departmentInfoList });
|
||||
map((res) => {
|
||||
return deptSuccess({ departmentInfoList: res.departmentInfoList });
|
||||
}),
|
||||
catchError(error => of(deptFailure({ error })))
|
||||
catchError((error) => of(deptFailure({ error })))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
@ -57,24 +54,13 @@ export class Effects {
|
|||
myDeptUser$ = createEffect(() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(myDeptUser),
|
||||
map(action => action.req),
|
||||
switchMap(req => {
|
||||
map((action) => action.req),
|
||||
switchMap((req) => {
|
||||
return this.queryProtocolService.deptUser(req).pipe(
|
||||
map(res => {
|
||||
const userInfos = res.userInfos.sort((a, b) =>
|
||||
a.order < b.order
|
||||
? -1
|
||||
: a.order > b.order
|
||||
? 1
|
||||
: a.name < b.name
|
||||
? -1
|
||||
: a.name > b.name
|
||||
? 1
|
||||
: 0
|
||||
);
|
||||
return myDeptUserSuccess({ userInfos });
|
||||
map((res) => {
|
||||
return myDeptUserSuccess({ userInfos: res.userInfos });
|
||||
}),
|
||||
catchError(error => of(myDeptUserFailure({ error })))
|
||||
catchError((error) => of(myDeptUserFailure({ error })))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
|
|
@ -4,10 +4,12 @@ import { Effects as CommonEffects } from './common/effects';
|
|||
import { Effects as CompanyEffects } from './company/effects';
|
||||
import { Effects as DepartmentEffects } from './department/effects';
|
||||
import { Effects as PresenceEffects } from './presence/effects';
|
||||
import { Effects as UserEffects } from './user/effects';
|
||||
|
||||
export const effects: Type<any>[] = [
|
||||
CommonEffects,
|
||||
CompanyEffects,
|
||||
DepartmentEffects,
|
||||
PresenceEffects
|
||||
PresenceEffects,
|
||||
UserEffects
|
||||
];
|
||||
|
|
|
@ -5,54 +5,59 @@ import {
|
|||
StatusNotification,
|
||||
StatusRequest,
|
||||
StatusResponse,
|
||||
MessageUpdateRequest
|
||||
MessageUpdateRequest,
|
||||
MessageUpdateResponse
|
||||
} from '@ucap/protocol-status';
|
||||
|
||||
export const bulkInfo = createAction(
|
||||
'[ucap::organization::presence] Bulk Info',
|
||||
'[ucap::organization::presence] bulkInfo',
|
||||
props<BulkInfoRequest>()
|
||||
);
|
||||
|
||||
export const bulkInfoSuccess = createAction(
|
||||
'[ucap::organization::presence] Bulk Info Success',
|
||||
'[ucap::organization::presence] bulkInfo Success',
|
||||
props<{ statusBulkInfoList: StatusBulkInfo[] }>()
|
||||
);
|
||||
|
||||
export const bulkInfoFailure = createAction(
|
||||
'[ucap::organization::presence] Bulk Info Failure',
|
||||
'[ucap::organization::presence] bulkInfo Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
|
||||
export const statusNotification = createAction(
|
||||
'[ucap::organization::presence] Status Notification',
|
||||
'[ucap::organization::presence] statusNotification',
|
||||
props<{ noti: StatusNotification }>()
|
||||
);
|
||||
|
||||
export const status = createAction(
|
||||
'[ucap::organization::presence] Status',
|
||||
'[ucap::organization::presence] status',
|
||||
props<{ req: StatusRequest }>()
|
||||
);
|
||||
export const statusSuccess = createAction(
|
||||
'[ucap::organization::presence] Status Success',
|
||||
'[ucap::organization::presence] statusSuccess',
|
||||
props<{
|
||||
res: StatusResponse;
|
||||
}>()
|
||||
);
|
||||
export const statusFailure = createAction(
|
||||
'[ucap::organization::presence] Status Failure',
|
||||
'[ucap::organization::presence] statusFailure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
|
||||
export const changeMyIdleCheckTime = createAction(
|
||||
'[ucap::organization::presence] Change MyIdleCheckTime',
|
||||
'[ucap::organization::presence] changeMyIdleCheckTime',
|
||||
props<{ checkTime: number }>()
|
||||
);
|
||||
|
||||
export const messageUpdate = createAction(
|
||||
'[ucap::organization::presence] status message update of Others',
|
||||
'[ucap::organization::presence] messageUpdate',
|
||||
props<{ req: MessageUpdateRequest }>()
|
||||
);
|
||||
export const messageUpdateSuccess = createAction(
|
||||
'[ucap::organization::presence] messageUpdate Success',
|
||||
props<{ res: MessageUpdateResponse }>()
|
||||
);
|
||||
export const messageUpdateFailure = createAction(
|
||||
'[ucap::organization::presence] status message update of Others Failure',
|
||||
'[ucap::organization::presence] messageUpdate Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { of } from 'rxjs';
|
||||
import { map, exhaustMap, catchError, tap, switchMap } from 'rxjs/operators';
|
||||
import { map, exhaustMap, catchError, concatMap } from 'rxjs/operators';
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
|
@ -12,10 +12,12 @@ import {
|
|||
statusFailure,
|
||||
bulkInfo,
|
||||
bulkInfoSuccess,
|
||||
bulkInfoFailure
|
||||
bulkInfoFailure,
|
||||
messageUpdate,
|
||||
messageUpdateSuccess,
|
||||
messageUpdateFailure
|
||||
} from './actions';
|
||||
import { StatusProtocolService } from '@ucap/ng-protocol-status';
|
||||
import { StatusBulkInfo } from '@ucap/protocol-status';
|
||||
|
||||
@Injectable()
|
||||
export class Effects {
|
||||
|
@ -23,7 +25,7 @@ export class Effects {
|
|||
() => {
|
||||
return this.actions$.pipe(
|
||||
ofType(bulkInfo),
|
||||
switchMap((req) => {
|
||||
concatMap((req) => {
|
||||
return this.statusProtocolService.bulkInfo(req).pipe(
|
||||
map((res) => {
|
||||
this.store.dispatch(
|
||||
|
@ -55,6 +57,21 @@ export class Effects {
|
|||
)
|
||||
);
|
||||
|
||||
messageUpdate$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(messageUpdate),
|
||||
map((action) => action.req),
|
||||
exhaustMap((req) => {
|
||||
return this.statusProtocolService.messageUpdate(req).pipe(
|
||||
map((res) => {
|
||||
return messageUpdateSuccess({ res });
|
||||
}),
|
||||
catchError((error) => of(messageUpdateFailure({ error })))
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private store: Store<any>,
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { createReducer, on } from '@ngrx/store';
|
||||
|
||||
import {
|
||||
StatusBulkInfo,
|
||||
TerminalStatusInfo,
|
||||
TerminalStatusNumber
|
||||
} from '@ucap/protocol-status';
|
||||
|
||||
import { initialState, State, adapterStatusBulkInfo } from './state';
|
||||
import {
|
||||
bulkInfoSuccess,
|
||||
|
@ -6,11 +13,6 @@ import {
|
|||
statusSuccess,
|
||||
bulkInfo
|
||||
} from './actions';
|
||||
import {
|
||||
StatusBulkInfo,
|
||||
TerminalStatusInfo,
|
||||
TerminalStatusNumber
|
||||
} from '@ucap/protocol-status';
|
||||
|
||||
export const reducer = createReducer(
|
||||
initialState,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Selector, createSelector } from '@ngrx/store';
|
||||
import { EntityState, createEntityAdapter } from '@ngrx/entity';
|
||||
|
||||
import { StatusBulkInfo } from '@ucap/protocol-status';
|
||||
|
||||
export interface StatusBulkInfoState extends EntityState<StatusBulkInfo> {}
|
||||
|
@ -42,11 +43,13 @@ export function selectors<S>(selector: Selector<any, State>) {
|
|||
selectStatusBulkInfo,
|
||||
ngeSelectEntitiesStatusBulkInfo
|
||||
),
|
||||
selectStatusBulkInfo: (userSeq: number) =>
|
||||
createSelector(
|
||||
selectStatusBulkInfo: createSelector(
|
||||
selectStatusBulkInfo,
|
||||
ngeSelectEntitiesStatusBulkInfo,
|
||||
(_, entities) => (!!entities ? entities[userSeq] : undefined)
|
||||
(statusBulkInfoState: StatusBulkInfoState, userSeq: number) => {
|
||||
return (
|
||||
statusBulkInfoState.entities && statusBulkInfoState.entities[userSeq]
|
||||
);
|
||||
}
|
||||
),
|
||||
statusBulkInfoProcessing: createSelector(
|
||||
selector,
|
||||
|
|
|
@ -4,12 +4,14 @@ import { reducer as CommonReducer } from './common/reducers';
|
|||
import { reducer as CompanyReducer } from './company/reducers';
|
||||
import { reducer as DepartmentReducer } from './department/reducers';
|
||||
import { reducer as PresenceReducer } from './presence/reducers';
|
||||
import { reducer as UserReducer } from './user/reducers';
|
||||
|
||||
export function reducers(state: any | undefined, action: Action) {
|
||||
return combineReducers({
|
||||
common: CommonReducer,
|
||||
company: CompanyReducer,
|
||||
department: DepartmentReducer,
|
||||
presence: PresenceReducer
|
||||
presence: PresenceReducer,
|
||||
user: UserReducer
|
||||
})(state, action);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import * as CommonState from './common/state';
|
|||
import * as CompanyState from './company/state';
|
||||
import * as DepartmentState from './department/state';
|
||||
import * as PresenceState from './presence/state';
|
||||
import * as UserState from './user/state';
|
||||
|
||||
export const KEY_FEATURE = 'organization';
|
||||
|
||||
|
@ -12,6 +13,7 @@ export interface State {
|
|||
company: CompanyState.State;
|
||||
department: DepartmentState.State;
|
||||
presence: PresenceState.State;
|
||||
user: UserState.State;
|
||||
}
|
||||
|
||||
export const Selector = createFeatureSelector<State>(KEY_FEATURE);
|
||||
|
@ -31,3 +33,7 @@ export const DepartmentSelector = DepartmentState.selectors(
|
|||
export const PresenceSelector = PresenceState.selectors(
|
||||
createSelector(Selector, (state: State) => state.presence)
|
||||
);
|
||||
|
||||
export const UserSelector = UserState.selectors(
|
||||
createSelector(Selector, (state: State) => state.user)
|
||||
);
|
||||
|
|
37
projects/store-organization/src/lib/store/user/actions.ts
Normal file
37
projects/store-organization/src/lib/store/user/actions.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { createAction, props } from '@ngrx/store';
|
||||
|
||||
import { UserResponse, UserRequest, User } from '@ucap/protocol-info';
|
||||
|
||||
/**
|
||||
* info user request
|
||||
*/
|
||||
export const init = createAction(
|
||||
'[ucap::organization::user] init',
|
||||
props<{ user: User }>()
|
||||
);
|
||||
|
||||
/**
|
||||
* modifyInfo user request
|
||||
*/
|
||||
export const modifyInfo = createAction(
|
||||
'[ucap::organization::user] modifyInfo',
|
||||
props<{ req: UserRequest }>()
|
||||
);
|
||||
|
||||
/**
|
||||
* Success of modifyInfo request
|
||||
*/
|
||||
export const modifyInfoSuccess = createAction(
|
||||
'[ucap::organization::user] modifyInfo Success',
|
||||
props<{
|
||||
res: UserResponse;
|
||||
}>()
|
||||
);
|
||||
|
||||
/**
|
||||
* Failure of modifyInfo request
|
||||
*/
|
||||
export const modifyInfoFailure = createAction(
|
||||
'[ucap::organization::user] modifyInfo Failure',
|
||||
props<{ error: any }>()
|
||||
);
|
37
projects/store-organization/src/lib/store/user/effects.ts
Normal file
37
projects/store-organization/src/lib/store/user/effects.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { of } from 'rxjs';
|
||||
import { map, exhaustMap, catchError } from 'rxjs/operators';
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||
|
||||
import { UserResponse } from '@ucap/protocol-info';
|
||||
|
||||
import { InfoProtocolService } from '@ucap/ng-protocol-info';
|
||||
|
||||
import { modifyInfo, modifyInfoSuccess, modifyInfoFailure } from './actions';
|
||||
|
||||
@Injectable()
|
||||
export class Effects {
|
||||
infoUser$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(modifyInfo),
|
||||
map((action) => action.req),
|
||||
exhaustMap((req) =>
|
||||
this.infoProtocolService.user(req).pipe(
|
||||
map((res: UserResponse) => {
|
||||
return modifyInfoSuccess({
|
||||
res
|
||||
});
|
||||
}),
|
||||
catchError((error) => of(modifyInfoFailure({ error })))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private infoProtocolService: InfoProtocolService
|
||||
) {}
|
||||
}
|
89
projects/store-organization/src/lib/store/user/reducers.ts
Normal file
89
projects/store-organization/src/lib/store/user/reducers.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
import { createReducer, on } from '@ngrx/store';
|
||||
|
||||
import { UserInfoUpdateType } from '@ucap/protocol-info';
|
||||
import { MessageIndexType } from '@ucap/protocol-status';
|
||||
|
||||
import { messageUpdateSuccess } from '../presence/actions';
|
||||
|
||||
import { init, modifyInfoSuccess } from './actions';
|
||||
import { State, initialState } from './state';
|
||||
|
||||
export const reducer = createReducer(
|
||||
initialState,
|
||||
on(init, (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
user: action.user
|
||||
} as State;
|
||||
}),
|
||||
|
||||
on(modifyInfoSuccess, (state, action) => {
|
||||
let user = {
|
||||
...state.user
|
||||
};
|
||||
|
||||
switch (action.res.type) {
|
||||
case UserInfoUpdateType.Image:
|
||||
user = {
|
||||
...user,
|
||||
info: {
|
||||
...user.info,
|
||||
profileImageFile: action.res.info
|
||||
}
|
||||
};
|
||||
break;
|
||||
case UserInfoUpdateType.Intro:
|
||||
user = {
|
||||
...user,
|
||||
info: {
|
||||
...user.info,
|
||||
intro: action.res.info
|
||||
}
|
||||
};
|
||||
break;
|
||||
case UserInfoUpdateType.TelephoneVisible:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
user
|
||||
};
|
||||
}),
|
||||
|
||||
on(messageUpdateSuccess, (state, action) => {
|
||||
let user = {
|
||||
...state.user
|
||||
};
|
||||
|
||||
switch (action.res.index) {
|
||||
case MessageIndexType.First:
|
||||
user = {
|
||||
...user,
|
||||
statusMessage1: action.res.statusMessage
|
||||
};
|
||||
break;
|
||||
case MessageIndexType.Second:
|
||||
user = {
|
||||
...user,
|
||||
statusMessage2: action.res.statusMessage
|
||||
};
|
||||
break;
|
||||
case MessageIndexType.Third:
|
||||
user = {
|
||||
...user,
|
||||
statusMessage3: action.res.statusMessage
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
user
|
||||
} as State;
|
||||
})
|
||||
);
|
20
projects/store-organization/src/lib/store/user/state.ts
Normal file
20
projects/store-organization/src/lib/store/user/state.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { Selector, createSelector } from '@ngrx/store';
|
||||
import { EntityState } from '@ngrx/entity';
|
||||
import { StatusBulkInfo } from '@ucap/protocol-status';
|
||||
import { User } from '@ucap/protocol-info';
|
||||
|
||||
export interface StatusBulkInfoState extends EntityState<StatusBulkInfo> {}
|
||||
|
||||
export interface State {
|
||||
user: User | undefined;
|
||||
}
|
||||
|
||||
export const initialState: State = {
|
||||
user: undefined
|
||||
};
|
||||
|
||||
export function selectors<S>(selector: Selector<any, State>) {
|
||||
return {
|
||||
user: createSelector(selector, (state: State) => state.user)
|
||||
};
|
||||
}
|
|
@ -6,10 +6,17 @@ import * as CommonActions from './lib/store/common/actions';
|
|||
import * as CompanyActions from './lib/store/company/actions';
|
||||
import * as DepartmentActions from './lib/store/department/actions';
|
||||
import * as PresenceActions from './lib/store/presence/actions';
|
||||
import * as UserActions from './lib/store/user/actions';
|
||||
|
||||
export * from './lib/config/module-config';
|
||||
|
||||
export { CommonActions, CompanyActions, DepartmentActions, PresenceActions };
|
||||
export {
|
||||
CommonActions,
|
||||
CompanyActions,
|
||||
DepartmentActions,
|
||||
PresenceActions,
|
||||
UserActions
|
||||
};
|
||||
|
||||
export * from './lib/store/state';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@ucap/ng-ui-authentication",
|
||||
"version": "0.0.25",
|
||||
"version": "0.0.29",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="change-password-form">
|
||||
<form name="changePasswordForm" [formGroup]="changePasswordForm" novalidate>
|
||||
<mat-form-field>
|
||||
<mat-form-field floatLabel="never" appearance="none">
|
||||
<mat-label>{{ 'accounts.fieldCurrentPassword' | ucapI18n }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
|
@ -8,11 +8,11 @@
|
|||
[formControl]="currentLoginPwFormControl"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-form-field floatLabel="never" appearance="none">
|
||||
<mat-label>{{ 'accounts.fieldNewPassword' | ucapI18n }}</mat-label>
|
||||
<input matInput type="password" [formControl]="newLoginPwFormControl" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-form-field floatLabel="never" appearance="none">
|
||||
<mat-label>{{ 'accounts.fieldNewPasswordConfirm' | ucapI18n }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
|
|
|
@ -7,11 +7,15 @@
|
|||
[style.display]="!!fixedCompanyCode ? 'none' : 'block'"
|
||||
>
|
||||
<mat-form-field color="accent">
|
||||
<mat-label>{{ 'login.fields.company' | ucapI18n }}</mat-label>
|
||||
<mat-label>{{
|
||||
'authentication:login.fields.company' | ucapI18n
|
||||
}}</mat-label>
|
||||
<mat-select
|
||||
[formControl]="companyCodeFormControl"
|
||||
[value]="companyCode || fixedCompanyCode"
|
||||
placeholder="{{ 'login.labels.selectCompany' | ucapI18n }}"
|
||||
placeholder="{{
|
||||
'authentication:login.labels.selectCompany' | ucapI18n
|
||||
}}"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let company of companyList"
|
||||
|
@ -49,8 +53,10 @@
|
|||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'login.fields.loginId' | ucapI18n }}</mat-label>
|
||||
<mat-form-field floatLabel="never" appearance="none">
|
||||
<mat-label>{{
|
||||
'authentication:login.fields.loginId' | ucapI18n
|
||||
}}</mat-label>
|
||||
<input matInput [formControl]="loginIdFormControl" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
@ -95,8 +101,10 @@
|
|||
</g>
|
||||
</svg>
|
||||
</span>
|
||||
<mat-form-field>
|
||||
<mat-label>{{ 'login.fields.loginPw' | ucapI18n }}</mat-label>
|
||||
<mat-form-field floatLabel="never" appearance="none">
|
||||
<mat-label>{{
|
||||
'authentication:login.fields.loginPw' | ucapI18n
|
||||
}}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="password"
|
||||
|
@ -115,7 +123,7 @@
|
|||
"
|
||||
class="ucap-authentication-login-error"
|
||||
>
|
||||
{{ 'login.errors.requireCompany' | ucapI18n }}
|
||||
{{ 'authentication:login.errors.requireCompany' | ucapI18n }}
|
||||
</mat-error>
|
||||
<mat-error
|
||||
*ngIf="
|
||||
|
@ -125,7 +133,7 @@
|
|||
"
|
||||
class="ucap-authentication-login-error"
|
||||
>
|
||||
{{ 'login.errors.requireLoginId' | ucapI18n }}
|
||||
{{ 'authentication:login.errors.requireLoginId' | ucapI18n }}
|
||||
</mat-error>
|
||||
<mat-error
|
||||
*ngIf="
|
||||
|
@ -135,10 +143,10 @@
|
|||
"
|
||||
class="ucap-authentication-login-error"
|
||||
>
|
||||
{{ 'login.errors.requireLoginPw' | ucapI18n }}
|
||||
{{ 'authentication:login.errors.requireLoginPw' | ucapI18n }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="loginFailed" class="ucap-authentication-login-error">
|
||||
{{ 'login.errors.failed' | ucapI18n }}
|
||||
{{ 'authentication:login.errors.failed' | ucapI18n }}
|
||||
</mat-error>
|
||||
</div>
|
||||
|
||||
|
@ -156,11 +164,11 @@
|
|||
<ng-container
|
||||
*ngIf="!processing && (!loginTry || !loginTry.remainTimeForNextTry)"
|
||||
>
|
||||
{{ 'login.labels.doLogin' | ucapI18n }}
|
||||
{{ 'authentication:login.labels.doLogin' | ucapI18n }}
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!!loginTry && !!loginTry.remainTimeForNextTry">
|
||||
{{ 'login.errors.attemptsExceeded' | ucapI18n }}
|
||||
{{ 'authentication:login.errors.attemptsExceeded' | ucapI18n }}
|
||||
(
|
||||
{{
|
||||
moment
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
* Public API Surface of ui-authentication
|
||||
*/
|
||||
|
||||
export * from './lib/components/change-password.component';
|
||||
export * from './lib/components/login.component';
|
||||
|
||||
export * from './lib/config/module-config';
|
||||
export * from './lib/config/token';
|
||||
|
||||
export * from './lib/authentication-ui.module';
|
||||
|
|
|
@ -5,12 +5,16 @@
|
|||
"entryFile": "src/public-api.ts",
|
||||
"styleIncludePaths": ["./src/assets/scss"],
|
||||
"umdModuleIds": {
|
||||
"moment": "moment",
|
||||
"ngx-perfect-scrollbar": "ngx-perfect-scrollbar",
|
||||
"@ucap/core": "@ucap/core",
|
||||
"@ucap/api": "@ucap/api",
|
||||
"@ucap/protocol-event": "@ucap/protocol-event",
|
||||
"@ucap/protocol-room": "@ucap/protocol-room",
|
||||
"@ucap/ng-logger": "@ucap/ng-logger",
|
||||
"@ucap/ng-i18n": "@ucap/ng-i18n",
|
||||
"@ucap/ng-ui": "@ucap/ng-ui"
|
||||
"@ucap/ng-ui": "@ucap/ng-ui",
|
||||
"@ucap/ng-ui-organization": "@ucap/ng-ui-organization"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@ucap/ng-ui-chat",
|
||||
"version": "0.0.13",
|
||||
"version": "0.0.72",
|
||||
"publishConfig": {
|
||||
"registry": "https://nexus.loafle.net/repository/npm-ucap/"
|
||||
},
|
||||
|
|
|
@ -3,25 +3,36 @@ import { CommonModule } from '@angular/common';
|
|||
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
||||
import { ScrollingModule } from '@angular/cdk/scrolling';
|
||||
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import { MatTreeModule } from '@angular/material/tree';
|
||||
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
|
||||
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
|
||||
|
||||
import { UiModule } from '@ucap/ng-ui';
|
||||
import { OrganizationUiModule } from '@ucap/ng-ui-organization';
|
||||
|
||||
import { I18nModule, UCAP_I18N_NAMESPACE } from '@ucap/ng-i18n';
|
||||
|
||||
import { ModuleConfig } from './config/module-config';
|
||||
import { _MODULE_CONFIG } from './config/token';
|
||||
|
||||
import {
|
||||
ImageListComponent,
|
||||
ImageListNodeDirective
|
||||
} from './components/image-list.component';
|
||||
import {
|
||||
FileListComponent,
|
||||
FileListNodeDirective
|
||||
} from './components/file-list.component';
|
||||
import { ImageListItem01Component } from './components/image-list-item-01.component';
|
||||
import { FileListItem01Component } from './components/file-list-item-01.component';
|
||||
import {
|
||||
RoomExpansionComponent,
|
||||
RoomExpansionHeaderDirective,
|
||||
|
@ -45,10 +56,19 @@ import { ReadHereComponent } from './components/message-box/read-here.component'
|
|||
import { DateSplitterComponent } from './components/message-box/date-splitter.component';
|
||||
import { SmsComponent } from './components/message-box/sms.component';
|
||||
import { ReplyComponent } from './components/message-box/reply.component';
|
||||
import {
|
||||
ChatItemListComponent,
|
||||
ChatItemListNodeDirective
|
||||
} from './components/item-list.component';
|
||||
|
||||
const COMPONENTS = [
|
||||
ImageListItem01Component,
|
||||
FileListItem01Component,
|
||||
FileListComponent,
|
||||
ImageListComponent,
|
||||
RoomExpansionComponent,
|
||||
RoomListItem01Component,
|
||||
ChatItemListComponent,
|
||||
|
||||
InformationComponent,
|
||||
TextComponent,
|
||||
|
@ -72,7 +92,13 @@ const COMPONENTS = [
|
|||
];
|
||||
const DIALOGS = [];
|
||||
const PIPES = [];
|
||||
const DIRECTIVES = [RoomExpansionHeaderDirective, RoomExpansionNodeDirective];
|
||||
const DIRECTIVES = [
|
||||
RoomExpansionHeaderDirective,
|
||||
RoomExpansionNodeDirective,
|
||||
FileListNodeDirective,
|
||||
ImageListNodeDirective,
|
||||
ChatItemListNodeDirective
|
||||
];
|
||||
const SERVICES = [];
|
||||
|
||||
@NgModule({
|
||||
|
@ -86,7 +112,6 @@ export class ChatUiRootModule {}
|
|||
imports: [
|
||||
CommonModule,
|
||||
FlexLayoutModule,
|
||||
ScrollingModule,
|
||||
|
||||
MatBadgeModule,
|
||||
MatButtonModule,
|
||||
|
@ -95,10 +120,13 @@ export class ChatUiRootModule {}
|
|||
MatRippleModule,
|
||||
MatTreeModule,
|
||||
MatMenuModule,
|
||||
MatProgressBarModule,
|
||||
MatTooltipModule,
|
||||
|
||||
PerfectScrollbarModule,
|
||||
|
||||
I18nModule,
|
||||
OrganizationUiModule,
|
||||
UiModule
|
||||
],
|
||||
exports: [...COMPONENTS, ...DIRECTIVES, ...PIPES],
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
<div class="ucap-chat-file-list-item-01-container">
|
||||
<div class="contents">
|
||||
<!--파일명에 따라 doc exe hwp ppt xls zip 으로 추가되고 나머지 파일 명은 file로 기간이 만료된 파일은 그뒤에 disable도 추가-->
|
||||
<!-- <div class="file-img" [ngClass]="fileInfo.FileExt"></div> -->
|
||||
<div
|
||||
[ngClass]="[
|
||||
'mime-icon',
|
||||
'light',
|
||||
'ico-' + fileInfo.sentMessageJson.fileExt
|
||||
]"
|
||||
>
|
||||
<div class="ico"></div>
|
||||
</div>
|
||||
<ul class="file-info">
|
||||
<li class="file-name">
|
||||
<span class="text-filename">{{
|
||||
fileInfo.sentMessageJson.fileName
|
||||
}}</span>
|
||||
<span class="text-username">{{
|
||||
senderInfo | ucapOrganizationTranslate: 'name'
|
||||
}}</span>
|
||||
</li>
|
||||
<li class="file-bytes-date">
|
||||
<span class="amount-bytes">{{
|
||||
fileInfo.sentMessageJson.attachmentSize | ucapBytes
|
||||
}}</span>
|
||||
<span class="text-date">
|
||||
<em>{{
|
||||
fileInfo.sentMessageJson.attachmentRegDate | ucapDate: 'MM.DD'
|
||||
}}</em>
|
||||
<em *ngIf="!!fileRetentionPeriod && fileRetentionPeriod > 0">
|
||||
~
|
||||
{{
|
||||
fileInfo.sentMessageJson.attachmentRegDate
|
||||
| ucapDate: 'MM.DD':fileRetentionPeriodOptions
|
||||
}}
|
||||
</em>
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
*ngIf="!!downloadTotalCount && !!downloadedCount"
|
||||
class="file-list-amount"
|
||||
(click)="onClickShowDownCheck()"
|
||||
>
|
||||
<strong>{{ downloadedCount }}</strong
|
||||
>/{{ downloadTotalCount }}
|
||||
</li>
|
||||
<li class="file-check-box">
|
||||
<mat-checkbox
|
||||
*ngIf="!!checkable"
|
||||
#checkbox
|
||||
aria-label="check"
|
||||
[checked]="checked"
|
||||
(change)="onChangeCheck(checkbox.checked)"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
</mat-checkbox>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="progress"
|
||||
*ngIf="!!fileDownloadItem"
|
||||
[ngClass]="
|
||||
!!fileDownloadItem &&
|
||||
!!fileDownloadItem.downloadingProgress$ &&
|
||||
(fileDownloadItem.downloadingProgress$ | async) > 0 &&
|
||||
(fileDownloadItem.downloadingProgress$ | async) < 100
|
||||
? 'over'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="close"
|
||||
class="btn-close"
|
||||
color="accent"
|
||||
style="display: none;"
|
||||
>
|
||||
<mat-icon color="accent">cancel</mat-icon>
|
||||
</button>
|
||||
<mat-progress-bar
|
||||
color="accent"
|
||||
mode="determinate"
|
||||
[value]="fileDownloadItem.downloadingProgress$ | async"
|
||||
></mat-progress-bar>
|
||||
|
||||
<div class="progress-info-area">
|
||||
<span style="display: none;">{{
|
||||
'common:file.downloading' | ucapI18n
|
||||
}}</span>
|
||||
<strong>{{ fileDownloadItem.downloadingProgress$ | async }}%</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,21 @@
|
|||
@import '~@ucap/ng-ui-material/material';
|
||||
|
||||
.ucap-chat-file-list-item-01-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
.mime-icon {
|
||||
flex: 0 0 60px;
|
||||
}
|
||||
ul {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
li {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import { DebugElement } from '@angular/core';
|
|||
|
||||
import { ProfileListItemComponent } from './profile-list-item.component';
|
||||
|
||||
describe('ucap::ui-organization::ProfileListItemComponent', () => {
|
||||
describe('ucap::ucap::organization::ProfileListItemComponent', () => {
|
||||
let component: ProfileListItemComponent;
|
||||
let fixture: ComponentFixture<ProfileListItemComponent>;
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input,
|
||||
EventEmitter,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
|
||||
import { Subject } from 'rxjs';
|
||||
import {
|
||||
UserInfo as RoomUserInfo,
|
||||
UserInfoShort as RoomUserInfoShort
|
||||
} from '@ucap/protocol-room';
|
||||
import { FileInfo } from '@ucap/protocol-file';
|
||||
import { DateOptions } from '@ucap/ng-ui';
|
||||
import { FileDownloadItem } from '@ucap/api';
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-chat-file-list-item-01',
|
||||
templateUrl: './file-list-item-01.component.html',
|
||||
styleUrls: ['./file-list-item-01.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FileListItem01Component implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
fileInfo: FileInfo;
|
||||
|
||||
@Input()
|
||||
senderInfo: RoomUserInfo | RoomUserInfoShort;
|
||||
|
||||
@Input()
|
||||
downloadTotalCount: number;
|
||||
@Input()
|
||||
downloadedCount: number;
|
||||
|
||||
@Input()
|
||||
fileRetentionPeriod: number;
|
||||
|
||||
@Input()
|
||||
expired = false;
|
||||
|
||||
@Input()
|
||||
checked = false;
|
||||
|
||||
@Input()
|
||||
checkable = true;
|
||||
|
||||
@Input()
|
||||
fileDownloadItem: FileDownloadItem;
|
||||
|
||||
@Output()
|
||||
changeCheck = new EventEmitter<{
|
||||
checked: boolean;
|
||||
fileInfo: FileInfo;
|
||||
fileDownloadItem: FileDownloadItem;
|
||||
}>();
|
||||
|
||||
@Output()
|
||||
showDownCheck = new EventEmitter<FileInfo>();
|
||||
|
||||
private ngOnDestroySubject: Subject<void> = new Subject();
|
||||
constructor(private changeDetectorRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.next();
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
|
||||
onClickShowDownCheck() {
|
||||
this.showDownCheck.emit(this.fileInfo);
|
||||
}
|
||||
|
||||
onChangeCheck(checked: boolean) {
|
||||
this.changeCheck.emit({
|
||||
checked,
|
||||
fileInfo: this.fileInfo,
|
||||
fileDownloadItem: this.fileDownloadItem
|
||||
});
|
||||
}
|
||||
|
||||
get fileRetentionPeriodOptions(): DateOptions {
|
||||
return {
|
||||
manipulate: { amount: this.fileRetentionPeriod, unit: 'days' }
|
||||
};
|
||||
}
|
||||
}
|
15
projects/ui-chat/src/lib/components/file-list.component.html
Normal file
15
projects/ui-chat/src/lib/components/file-list.component.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<div class="ucap-chat-file-list-container" fxFlexFill>
|
||||
<ucap-virtual-scroll-viewport
|
||||
#cvsvList
|
||||
perfectScrollbar
|
||||
fxFlexFill
|
||||
[itemSize]="vsItemSize"
|
||||
>
|
||||
<ng-container *ucapVirtualFor="let fileInfo of fileInfos">
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="nodeTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: fileInfo }"
|
||||
></ng-container>
|
||||
</ng-container>
|
||||
</ucap-virtual-scroll-viewport>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
@import '~@ucap/ng-ui-material/material';
|
||||
|
||||
.ucap-chat-file-list-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { FileListComponent } from './file-list.component';
|
||||
|
||||
describe('ucap::chat::FileListComponent', () => {
|
||||
let component: FileListComponent;
|
||||
let fixture: ComponentFixture<FileListComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [FileListComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FileListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
import { moduleMetadata } from '@storybook/angular';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { ChatUiModule } from '../chat-ui.module';
|
||||
|
||||
import { FileListComponent } from './file-list.component';
|
||||
|
||||
export default {
|
||||
title: 'ui-chat::FileListComponent',
|
||||
component: FileListComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [BrowserModule, BrowserAnimationsModule, ChatUiModule],
|
||||
providers: []
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
export const Default = () => ({
|
||||
component: FileListComponent,
|
||||
props: {}
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
@import '~@ucap/ng-ui-material/material';
|
||||
|
||||
@mixin ucap-organization-profile-list-theme($theme) {
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.ucap-organization-profile-list-container {
|
||||
border-color: mat-color($foreground, secondary-text);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin ucap-organization-profile-list-typography($config) {
|
||||
.ucap-organization-profile-list-container {
|
||||
.searchword-container {
|
||||
.form-field-input-searchword {
|
||||
font-family: mat-font-family($config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
projects/ui-chat/src/lib/components/file-list.component.ts
Normal file
55
projects/ui-chat/src/lib/components/file-list.component.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { Subject } from 'rxjs';
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input,
|
||||
Directive,
|
||||
ContentChild,
|
||||
TemplateRef
|
||||
} from '@angular/core';
|
||||
|
||||
import { FileInfo } from '@ucap/protocol-file';
|
||||
|
||||
@Directive({
|
||||
selector: '[ucapChatFileListNode]'
|
||||
})
|
||||
export class FileListNodeDirective {}
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-chat-file-list',
|
||||
templateUrl: './file-list.component.html',
|
||||
styleUrls: ['./file-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class FileListComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
fileInfos: FileInfo[];
|
||||
|
||||
@Input()
|
||||
vsItemSize: number;
|
||||
|
||||
@ContentChild(FileListNodeDirective, {
|
||||
read: TemplateRef,
|
||||
static: false
|
||||
})
|
||||
nodeTemplate: TemplateRef<FileListNodeDirective>;
|
||||
|
||||
private ngOnDestroySubject: Subject<void>;
|
||||
|
||||
constructor(private changeDetectorRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.ngOnDestroySubject = new Subject();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.next();
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<div
|
||||
class="ucap-chat-image-list-item-01-container"
|
||||
(mouseover)="onMouseover($event)"
|
||||
(mouseleave)="onMouseleave($event)"
|
||||
>
|
||||
<div class="contents">
|
||||
<img
|
||||
[src]="fileInfo.sentMessageJson.thumbUrl"
|
||||
onerror="this.src='assets/sticker/sticker_default.png'"
|
||||
/>
|
||||
<mat-checkbox
|
||||
#checkbox
|
||||
*ngIf="!!checkable"
|
||||
aria-label="check"
|
||||
[checked]="checked"
|
||||
(change)="onChangeCheck(checkbox.checked)"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div class="btn-box" [ngClass]="!!showOverButtons ? 'over' : ''">
|
||||
<ul>
|
||||
<li>
|
||||
<button
|
||||
mat-icon-button
|
||||
class="ico-btn-obj"
|
||||
aria-label="저장"
|
||||
[matTooltip]="'common:file.save' | ucapI18n"
|
||||
(click)="onClickSave($event)"
|
||||
>
|
||||
<mat-icon>save_alt</mat-icon>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
mat-icon-button
|
||||
class="ico-btn-obj"
|
||||
aria-label="뷰어로열기"
|
||||
[matTooltip]="'common:file.fileOpen' | ucapI18n"
|
||||
(click)="onClickOpenViewer($event)"
|
||||
>
|
||||
<mat-icon>launch</mat-icon>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
mat-icon-button
|
||||
class="ico-btn-obj"
|
||||
aria-label="삭제"
|
||||
[matTooltip]="'common:file.delete' | ucapI18n"
|
||||
(click)="onClickDelete($event)"
|
||||
>
|
||||
<mat-icon class="material-icons-outlined">delete_outline</mat-icon>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="progress"
|
||||
*ngIf="!!fileDownloadItem"
|
||||
[ngClass]="
|
||||
!!fileDownloadItem &&
|
||||
!!fileDownloadItem.downloadingProgress$ &&
|
||||
(fileDownloadItem.downloadingProgress$ | async) > 0 &&
|
||||
(fileDownloadItem.downloadingProgress$ | async) < 100
|
||||
? 'over'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="close"
|
||||
class="btn-close"
|
||||
color="accent"
|
||||
style="display: none;"
|
||||
>
|
||||
<mat-icon color="accent">cancel</mat-icon>
|
||||
</button>
|
||||
<mat-progress-bar
|
||||
color="accent"
|
||||
mode="determinate"
|
||||
[value]="fileDownloadItem.downloadingProgress$ | async"
|
||||
></mat-progress-bar>
|
||||
|
||||
<div class="progress-info-area">
|
||||
<span style="display: none;">{{
|
||||
'common:file.downloading' | ucapI18n
|
||||
}}</span>
|
||||
<strong>{{ fileDownloadItem.downloadingProgress$ | async }}%</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,44 @@
|
|||
@import '~@ucap/ng-ui-material/material';
|
||||
|
||||
.ucap-chat-image-list-item-01-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
//padding-left: 4px;
|
||||
//margin-left: -4px;
|
||||
.contents {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
img {
|
||||
}
|
||||
.mat-checkbox {
|
||||
position: absolute;
|
||||
//right: 0;
|
||||
//top: 0;
|
||||
}
|
||||
}
|
||||
.btn-box {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
bottom: 0;
|
||||
//background-color: rgba(0, 0, 0, 0.5);
|
||||
ul {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
//bottom: 0;
|
||||
li {
|
||||
button {
|
||||
//width: 30%;
|
||||
//height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
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';
|
||||
|
||||
describe('ucap::ucap::organization::ProfileListItemComponent', () => {
|
||||
let component: ProfileListItemComponent;
|
||||
let fixture: ComponentFixture<ProfileListItemComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProfileListItemComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProfileListItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,122 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input,
|
||||
EventEmitter,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
|
||||
import { Subject } from 'rxjs';
|
||||
import { FileInfo } from '@ucap/protocol-file';
|
||||
import { FileDownloadItem } from '@ucap/api';
|
||||
import { FileEventJson } from '@ucap/protocol-event';
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-chat-image-list-item-01',
|
||||
templateUrl: './image-list-item-01.component.html',
|
||||
styleUrls: ['./image-list-item-01.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ImageListItem01Component implements OnInit, OnDestroy {
|
||||
private ngOnDestroySubject: Subject<void> = new Subject();
|
||||
|
||||
@Input()
|
||||
fileInfo: FileInfo;
|
||||
|
||||
@Input()
|
||||
expired = false;
|
||||
|
||||
@Input()
|
||||
checked = false;
|
||||
|
||||
@Input()
|
||||
checkable = true;
|
||||
|
||||
@Input()
|
||||
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<FileInfo>();
|
||||
|
||||
@Output()
|
||||
delete = new EventEmitter<FileInfo>();
|
||||
|
||||
showOverButtons = false;
|
||||
|
||||
constructor(private changeDetectorRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.next();
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
|
||||
onMouseover(event: MouseEvent): void {
|
||||
if (!this.expired) {
|
||||
this.showOverButtons = true;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
onMouseleave(event: MouseEvent): void {
|
||||
this.showOverButtons = false;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
onChangeCheck(checked: boolean) {
|
||||
this.changeCheck.emit({
|
||||
checked,
|
||||
fileInfo: this.fileInfo,
|
||||
fileDownloadItem: this.fileDownloadItem
|
||||
});
|
||||
}
|
||||
|
||||
onClickSave(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
const param = {
|
||||
fileInfo: {
|
||||
// require parameters.
|
||||
fileName: this.fileInfo.name,
|
||||
attachmentSeq: this.fileInfo.seq
|
||||
} as FileEventJson,
|
||||
fileDownloadItem: this.fileDownloadItem,
|
||||
type: 'save'
|
||||
};
|
||||
this.save.emit(param);
|
||||
}
|
||||
onClickOpenViewer(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.openViewer.emit(this.fileInfo);
|
||||
}
|
||||
onClickDelete(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
this.delete.emit(this.fileInfo);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<div class="ucap-chat-image-list-container" fxFlexFill>
|
||||
<ucap-virtual-scroll-viewport
|
||||
#cvsvList
|
||||
perfectScrollbar
|
||||
fxFlexFill
|
||||
[itemSize]="vsItemSize"
|
||||
>
|
||||
<ng-container *ucapVirtualFor="let fileInfo of fileInfos">
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="nodeTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: fileInfo }"
|
||||
></ng-container>
|
||||
</ng-container>
|
||||
</ucap-virtual-scroll-viewport>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
@import '~@ucap/ng-ui-material/material';
|
||||
|
||||
.ucap-chat-image-list-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { ImageListComponent } from './image-list.component';
|
||||
|
||||
describe('ucap::chat::ImageListComponent', () => {
|
||||
let component: ImageListComponent;
|
||||
let fixture: ComponentFixture<ImageListComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ImageListComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ImageListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
import { moduleMetadata } from '@storybook/angular';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { ChatUiModule } from '../chat-ui.module';
|
||||
|
||||
import { ImageListComponent } from './image-list.component';
|
||||
|
||||
export default {
|
||||
title: 'ui-chat::ImageListComponent',
|
||||
component: ImageListComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [BrowserModule, BrowserAnimationsModule, ChatUiModule],
|
||||
providers: []
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
export const Default = () => ({
|
||||
component: ImageListComponent,
|
||||
props: {}
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
@import '~@ucap/ng-ui-material/material';
|
||||
|
||||
@mixin ucap-organization-profile-list-theme($theme) {
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.ucap-organization-profile-list-container {
|
||||
border-color: mat-color($foreground, secondary-text);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin ucap-organization-profile-list-typography($config) {
|
||||
.ucap-organization-profile-list-container {
|
||||
.searchword-container {
|
||||
.form-field-input-searchword {
|
||||
font-family: mat-font-family($config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
projects/ui-chat/src/lib/components/image-list.component.ts
Normal file
55
projects/ui-chat/src/lib/components/image-list.component.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { Subject } from 'rxjs';
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input,
|
||||
Directive,
|
||||
ContentChild,
|
||||
TemplateRef
|
||||
} from '@angular/core';
|
||||
|
||||
import { FileInfo } from '@ucap/protocol-file';
|
||||
|
||||
@Directive({
|
||||
selector: '[ucapChatImageListNode]'
|
||||
})
|
||||
export class ImageListNodeDirective {}
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-chat-image-list',
|
||||
templateUrl: './image-list.component.html',
|
||||
styleUrls: ['./image-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ImageListComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
fileInfos: FileInfo[];
|
||||
|
||||
@Input()
|
||||
vsItemSize: number;
|
||||
|
||||
@ContentChild(ImageListNodeDirective, {
|
||||
read: TemplateRef,
|
||||
static: false
|
||||
})
|
||||
nodeTemplate: TemplateRef<ImageListNodeDirective>;
|
||||
|
||||
private ngOnDestroySubject: Subject<void>;
|
||||
|
||||
constructor(private changeDetectorRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.ngOnDestroySubject = new Subject();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.next();
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
}
|
17
projects/ui-chat/src/lib/components/item-list.component.html
Normal file
17
projects/ui-chat/src/lib/components/item-list.component.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<div class="ucap-chat-item-list-container" #container fxFlexFill>
|
||||
<ucap-virtual-scroll-viewport
|
||||
#cvsvList
|
||||
fxFlexFill
|
||||
perfectScrollbar
|
||||
[itemSize]="vsItemSize"
|
||||
>
|
||||
<ng-container *ucapVirtualFor="let itemInfo of itemInfos">
|
||||
<div [style.height.px]="vsItemSize">
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="nodeTemplate"
|
||||
[ngTemplateOutletContext]="{ $implicit: itemInfo }"
|
||||
></ng-template>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ucap-virtual-scroll-viewport>
|
||||
</div>
|
11
projects/ui-chat/src/lib/components/item-list.component.scss
Normal file
11
projects/ui-chat/src/lib/components/item-list.component.scss
Normal file
|
@ -0,0 +1,11 @@
|
|||
@import '~@ucap/ng-ui-material/material';
|
||||
|
||||
.ucap-chat-item-list-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
ucap-virtual-scroll-viewport {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { ChatItemListComponent } from './item-list.component';
|
||||
|
||||
describe('ucap::organization::ChatItemListComponent', () => {
|
||||
let component: ChatItemListComponent;
|
||||
let fixture: ComponentFixture<ChatItemListComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ChatItemListComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChatItemListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
import { moduleMetadata } from '@storybook/angular';
|
||||
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { ChatItemListComponent } from './item-list.component';
|
||||
|
||||
export default {
|
||||
title: 'ui-chat::ChatItemListComponent',
|
||||
component: ChatItemListComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [BrowserModule, BrowserAnimationsModule],
|
||||
providers: []
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
export const Default = () => ({
|
||||
component: ChatItemListComponent,
|
||||
props: {}
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
@import '~@ucap/ng-ui-material/material';
|
||||
|
||||
@mixin ucap-chat-item-list-theme($theme) {
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.ucap-chat-item-list-container {
|
||||
border-color: mat-color($foreground, secondary-text);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin ucap-chat-item-list-typography($config) {
|
||||
.ucap-chat-item-list-container {
|
||||
.searchword-container {
|
||||
.form-field-input-searchword {
|
||||
font-family: mat-font-family($config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
71
projects/ui-chat/src/lib/components/item-list.component.ts
Normal file
71
projects/ui-chat/src/lib/components/item-list.component.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { Subject } from 'rxjs';
|
||||
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Input,
|
||||
Directive,
|
||||
ContentChild,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
ElementRef
|
||||
} from '@angular/core';
|
||||
|
||||
import { ObjectUtil } from '@ucap/core';
|
||||
|
||||
import { UserInfo, UserInfoShort, RoomInfo } from '@ucap/protocol-room';
|
||||
|
||||
import { VirtualScrollViewportComponent } from '@ucap/ng-ui';
|
||||
|
||||
export type ItemInfoTypes = UserInfo | UserInfoShort | RoomInfo;
|
||||
|
||||
@Directive({
|
||||
selector: '[ucapChatItemListNode]'
|
||||
})
|
||||
export class ChatItemListNodeDirective {}
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-chat-item-list',
|
||||
templateUrl: './item-list.component.html',
|
||||
styleUrls: ['./item-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ChatItemListComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
itemInfos: ItemInfoTypes[];
|
||||
|
||||
@Input()
|
||||
vsItemSize: number;
|
||||
|
||||
@ContentChild(ChatItemListNodeDirective, {
|
||||
read: TemplateRef,
|
||||
static: false
|
||||
})
|
||||
nodeTemplate: TemplateRef<ChatItemListNodeDirective>;
|
||||
|
||||
@ViewChild('container', { static: true })
|
||||
container: ElementRef<HTMLElement>;
|
||||
|
||||
@ViewChild('cvsvList', { static: true })
|
||||
cvsvList: VirtualScrollViewportComponent;
|
||||
|
||||
ObjectUtil = ObjectUtil;
|
||||
|
||||
private ngOnDestroySubject: Subject<void>;
|
||||
|
||||
constructor(private changeDetectorRef: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.ngOnDestroySubject = new Subject();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (!!this.ngOnDestroySubject) {
|
||||
this.ngOnDestroySubject.next();
|
||||
this.ngOnDestroySubject.complete();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,10 @@
|
|||
<div class="ucap-chat-message-box-attach-file" (click)="onClickOpenViewer()">
|
||||
<div class="chat-bubble-area">
|
||||
<div
|
||||
class="ucap-chat-message-box-attach-file-bubble"
|
||||
(mouseenter)="mouseEnter($event)"
|
||||
(mouseleave)="mouseLeave($event)"
|
||||
>
|
||||
<div class="file-info-box" (click)="onClickOpenViewer()">
|
||||
<!--파일명에 따라 doc exe hwp ppt xls zip 으로 추가되고 나머지 파일 명은 file로 기간이 만료된 파일은 그뒤에 disable도 추가-->
|
||||
<!-- <div class="file-img" [ngClass]="fileInfo.FileExt"></div> -->
|
||||
<div [ngClass]="['mime-icon', 'light', 'ico-' + fileInfo.fileExt]">
|
||||
|
@ -10,28 +16,113 @@
|
|||
</li>
|
||||
<li class="date">
|
||||
{{ fileInfo.attachmentRegDate | ucapDate }}
|
||||
<span *ngIf="!!fileRetentionPeriod && fileRetentionPeriod > 0">
|
||||
~
|
||||
{{
|
||||
fileInfo.attachmentRegDate
|
||||
| ucapDate: 'YYYY.MM.DD':fileRetentionPeriodOptions
|
||||
}}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="btn-box over">
|
||||
<ul *ngIf="!expired">
|
||||
<li>
|
||||
<button mat-button (click)="onClickSave()">
|
||||
저장
|
||||
<div class="btn-box" [ngClass]="!!showOverButtons ? 'over' : ''">
|
||||
<ul>
|
||||
<li *ngIf="!existFile">
|
||||
<button
|
||||
mat-flat-button
|
||||
color="accent"
|
||||
class="btn-file-ctrl"
|
||||
(click)="onClickSave()"
|
||||
aria-label="저장"
|
||||
[matTooltip]="'common:file.save' | ucapI18n"
|
||||
>
|
||||
<mat-icon>save_alt</mat-icon>
|
||||
</button>
|
||||
</li>
|
||||
<li *ngIf="!existFile">
|
||||
<button
|
||||
mat-flat-button
|
||||
*ngIf="deviceType !== DeviceType.Web"
|
||||
color="accent"
|
||||
class="btn-file-ctrl"
|
||||
(click)="onClickSaveAs()"
|
||||
aria-label="다른 이름 저장"
|
||||
[matTooltip]="'common:file.saveAs' | ucapI18n"
|
||||
>
|
||||
<mat-icon>save</mat-icon>
|
||||
</button>
|
||||
</li>
|
||||
<li *ngIf="!!existFile">
|
||||
<button
|
||||
mat-flat-button
|
||||
*ngIf="deviceType !== DeviceType.Web"
|
||||
color="accent"
|
||||
class="btn-file-ctrl"
|
||||
(click)="onClickOpenFile()"
|
||||
aria-label="파일열기"
|
||||
[matTooltip]="'common:file.fileOpen' | ucapI18n"
|
||||
>
|
||||
<mat-icon class="material-icons-outlined">text_snippet</mat-icon>
|
||||
</button>
|
||||
</li>
|
||||
<li *ngIf="!!existFile">
|
||||
<button
|
||||
mat-flat-button
|
||||
*ngIf="deviceType !== DeviceType.Web"
|
||||
color="accent"
|
||||
class="btn-file-ctrl"
|
||||
(click)="onClickOpenFolder()"
|
||||
aria-label="폴더열기"
|
||||
[matTooltip]="'common:file.folderOpen' | ucapI18n"
|
||||
>
|
||||
<mat-icon class="material-icons-outlined">folder</mat-icon>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button mat-button (click)="onClickSave()">
|
||||
폴더열기
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button mat-button (click)="onClickSave()">
|
||||
???
|
||||
<button
|
||||
mat-flat-button
|
||||
color="accent"
|
||||
class="btn-file-ctrl"
|
||||
(click)="onClickOpenViewer()"
|
||||
aria-label="뷰어 열기"
|
||||
[matTooltip]="'label.openViewer' | ucapI18n"
|
||||
>
|
||||
<mat-icon>open_in_new</mat-icon>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="progress over">
|
||||
프로그레스 영역
|
||||
<div
|
||||
class="progress"
|
||||
[ngClass]="
|
||||
!!fileDownloadItem &&
|
||||
!!fileDownloadItem.downloadingProgress$ &&
|
||||
(fileDownloadItem.downloadingProgress$ | async) > 0 &&
|
||||
(fileDownloadItem.downloadingProgress$ | async) < 100
|
||||
? 'over'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<button
|
||||
mat-icon-button
|
||||
aria-label="close"
|
||||
class="btn-close"
|
||||
color="accent"
|
||||
style="display: none;"
|
||||
>
|
||||
<mat-icon color="accent">cancel</mat-icon>
|
||||
</button>
|
||||
<mat-progress-bar
|
||||
color="accent"
|
||||
mode="determinate"
|
||||
[value]="fileDownloadItem.downloadingProgress$ | async"
|
||||
></mat-progress-bar>
|
||||
|
||||
<div class="progress-info-area">
|
||||
<span>{{ 'common:file.downloading' | ucapI18n }}</span
|
||||
><strong>{{ fileDownloadItem.downloadingProgress$ | async }}%</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,126 +1,124 @@
|
|||
// $tablet-s-width: 768px;
|
||||
$tablet-s-width: 768px;
|
||||
|
||||
// .bubble-main {
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// padding: 14px;
|
||||
// min-width: 300px;
|
||||
// .file-img {
|
||||
// display: inline-flex;
|
||||
// width: 50px;
|
||||
// height: 50px;
|
||||
// float: left;
|
||||
// margin-right: 14px;
|
||||
// background-repeat: no-repeat;
|
||||
// &.doc {
|
||||
// background-image: url(/assets/images/file/icon_talk_doc.png);
|
||||
// &.disable {
|
||||
// background-image: url(/assets/images/file/icon_talk_doc_d.png);
|
||||
// }
|
||||
// }
|
||||
// &.exe {
|
||||
// background-image: url(/assets/images/file/icon_talk_exe.png);
|
||||
// &.disable {
|
||||
// background-image: url(/assets/images/file/icon_talk_exe_d.png);
|
||||
// }
|
||||
// }
|
||||
// &.file {
|
||||
// background-image: url(/assets/images/file/icon_talk_file.png);
|
||||
// &.disable {
|
||||
// background-image: url(/assets/images/file/icon_talk_file_d.png);
|
||||
// }
|
||||
// }
|
||||
// &.hwp {
|
||||
// background-image: url(/assets/images/file/icon_talk_hwp.png);
|
||||
// &.disable {
|
||||
// background-image: url(/assets/images/file/icon_talk_hwp_d.png);
|
||||
// }
|
||||
// }
|
||||
// &.ppt,
|
||||
// &.pptx {
|
||||
// background-image: url(/assets/images/file/icon_talk_ppt.png);
|
||||
// &.disable {
|
||||
// background-image: url(/assets/images/file/icon_talk_ppt_d.png);
|
||||
// }
|
||||
// }
|
||||
// &.xls,
|
||||
// &.xlsm,
|
||||
// &.xlsx {
|
||||
// background-image: url(/assets/images/file/icon_talk_xls.png);
|
||||
// &.disable {
|
||||
// background-image: url(/assets/images/file/icon_talk_xls_d.png);
|
||||
// }
|
||||
// }
|
||||
// &.zip {
|
||||
// background-image: url(/assets/images/file/icon_talk_zip.png);
|
||||
// &.disable {
|
||||
// background-image: url(/assets/images/file/icon_talk_doc_d.png);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .file-info {
|
||||
// display: inline-flex;
|
||||
// flex-direction: column;
|
||||
// text-align: left;
|
||||
// float: left;
|
||||
// line-height: 1.6em;
|
||||
// .file-name {
|
||||
// font-size: 1em;
|
||||
// font-weight: bold;
|
||||
// display: inline-flex;
|
||||
// }
|
||||
// .file-size {
|
||||
// display: inline-flex;
|
||||
// font-size: 11px;
|
||||
// color: #666666;
|
||||
// }
|
||||
// .file-ext {
|
||||
// font-size: 12px;
|
||||
// color: #848d95;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .btn-box {
|
||||
// width: 100%;
|
||||
// height: 40px;
|
||||
// border-top: 1px solid #dddddd;
|
||||
// display: flex;
|
||||
// width: 100%;
|
||||
// text-align: center;
|
||||
// font-size: 1em;
|
||||
// ul {
|
||||
// width: 100%;
|
||||
// li {
|
||||
// width: 50%;
|
||||
// height: 100%;
|
||||
// display: inline-block;
|
||||
// text-align: center;
|
||||
.ucap-chat-message-box-attach-file-bubble {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
//padding: 6px 12px 0 0;
|
||||
//border-radius: 2px;
|
||||
cursor: pointer;
|
||||
.file-info-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
//align-items: center;
|
||||
// border-right: 1px solid #dddddd;
|
||||
// @media screen and (max-width: #{$tablet-s-width}) {
|
||||
// width: 30%;
|
||||
// }
|
||||
// &:last-child {
|
||||
// border-right: none;
|
||||
// @media screen and (max-width: #{$tablet-s-width}) {
|
||||
// width: 70%;
|
||||
// }
|
||||
// }
|
||||
// .mat-button {
|
||||
// width: 100%;
|
||||
// display: block;
|
||||
// height: 100%;
|
||||
// font-size: 1em;
|
||||
// }
|
||||
// }
|
||||
// &.expired {
|
||||
// li {
|
||||
// width: 100%;
|
||||
// white-space: nowrap;
|
||||
// color: #999999;
|
||||
// align-items: center;
|
||||
// line-height: 40px;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//padding: 6px 12px 0 0;
|
||||
//border-radius: 2px;
|
||||
cursor: pointer;
|
||||
.file-info {
|
||||
//border-radius: 2px;
|
||||
.file-name {
|
||||
font-weight: 600;
|
||||
//line-height: 1.2;
|
||||
//font-size: 0.929em;
|
||||
}
|
||||
.date {
|
||||
//color: #c92b5c;
|
||||
//font-size: 0.875em;
|
||||
//margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
//background-color: rgba(0, 0, 0, 0.6);
|
||||
display: none;
|
||||
//transition: all 0.2s linear;
|
||||
//opacity: 0;
|
||||
//border-radius: 2px;
|
||||
cursor: default;
|
||||
&.over {
|
||||
position: absolute;
|
||||
display: block;
|
||||
z-index: 5;
|
||||
top: 0;
|
||||
left: 0;
|
||||
//transition: all 0.3s linear;
|
||||
opacity: 1;
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
li {
|
||||
//padding-left: 16px;
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
.btn-file-ctrl {
|
||||
//@include matbtnCtrl(36px, 36px, 50%, 36px);
|
||||
width: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.progress {
|
||||
//display: flex;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
//background-color: rgba(0, 0, 0, 0.6);
|
||||
padding: 0 20px;
|
||||
border-radius: 2px;
|
||||
cursor: default;
|
||||
&:before {
|
||||
content: '';
|
||||
//width: 16px;
|
||||
// height: 16px;
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
//right: 22px;
|
||||
//top: calc(50% - 25px);
|
||||
//background-color: $white;
|
||||
display: inline-block;
|
||||
//border-radius: 50%;
|
||||
}
|
||||
&.over {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.btn-close {
|
||||
align-self: flex-end;
|
||||
//@include matbtnCtrl(20px, 20px, 0, 20px);
|
||||
// width: 20px;
|
||||
//margin-bottom: 6px;
|
||||
}
|
||||
.progress-info-area {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
//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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { FileEventJson } from '@ucap/protocol-event';
|
||||
import { DateOptions } from '@ucap/ng-ui';
|
||||
import { FileDownloadItem } from '@ucap/api';
|
||||
import { DeviceType } from '@ucap/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-chat-message-box-attach-file',
|
||||
|
@ -10,26 +13,83 @@ export class AttachFileComponent implements OnInit {
|
|||
@Input()
|
||||
fileInfo: FileEventJson;
|
||||
|
||||
@Input()
|
||||
fileDownloadItem: FileDownloadItem;
|
||||
|
||||
@Input()
|
||||
existFile = false;
|
||||
|
||||
@Input()
|
||||
deviceType: DeviceType;
|
||||
|
||||
@Input()
|
||||
expired = false;
|
||||
|
||||
@Input()
|
||||
fileRetentionPeriod?: number;
|
||||
|
||||
@Output()
|
||||
save = new EventEmitter<string>();
|
||||
|
||||
@Output()
|
||||
openFile = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
openFolder = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
openViewer = new EventEmitter();
|
||||
|
||||
showOverButtons = false;
|
||||
|
||||
DeviceType = DeviceType;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
onClickSave() {
|
||||
if (!this.expired) {
|
||||
this.save.emit('save');
|
||||
}
|
||||
}
|
||||
onClickSaveAs() {
|
||||
if (!this.expired) {
|
||||
this.save.emit('saveAs');
|
||||
}
|
||||
}
|
||||
onClickOpenFile() {
|
||||
if (!this.expired) {
|
||||
this.openFile.emit();
|
||||
}
|
||||
}
|
||||
onClickOpenFolder() {
|
||||
if (!this.expired) {
|
||||
this.openFolder.emit();
|
||||
}
|
||||
}
|
||||
onClickOpenViewer() {
|
||||
if (!this.expired) {
|
||||
this.openViewer.emit();
|
||||
}
|
||||
}
|
||||
|
||||
mouseEnter(event: MouseEvent): void {
|
||||
if (!this.expired) {
|
||||
this.showOverButtons = true;
|
||||
}
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
mouseLeave(event: MouseEvent): void {
|
||||
this.showOverButtons = false;
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
get fileRetentionPeriodOptions(): DateOptions {
|
||||
return {
|
||||
manipulate: { amount: this.fileRetentionPeriod, unit: 'days' }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
@import '~@ucap/ng-ui-material/material';
|
||||
|
||||
.ucap-chat-message-box-date-splitter {
|
||||
display: flex;
|
||||
align-content: center;
|
||||
flex-flow: row;
|
||||
//height: 30px;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
//margin: 20px 0;
|
||||
&:before {
|
||||
content: '';
|
||||
// height: 1px;
|
||||
// background-color: $gray-re3;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
// top: 50%;
|
||||
}
|
||||
.date {
|
||||
min-width: 160px;
|
||||
// font-size: 0.979em;
|
||||
// text-align: center;
|
||||
// font-weight: 600;
|
||||
// color: $gray-re3;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
// padding: 0 36px;
|
||||
// align-self: center;
|
||||
}
|
||||
}
|
|
@ -6,8 +6,14 @@
|
|||
<ucap-chat-message-box-attach-file
|
||||
*ngSwitchCase="FileType.File"
|
||||
[fileInfo]="fileInfo"
|
||||
[fileDownloadItem]="fileDownloadItem"
|
||||
[expired]="getExpiredFile()"
|
||||
[deviceType]="deviceType"
|
||||
[existFile]="existFile"
|
||||
[fileRetentionPeriod]="fileRetentionPeriod"
|
||||
(openViewer)="onClickFileViewer(fileInfo)"
|
||||
(openFile)="onClickOpenFile(fileInfo)"
|
||||
(openFolder)="onClickOpenFolder(fileInfo)"
|
||||
(save)="onSave($event)"
|
||||
>
|
||||
</ucap-chat-message-box-attach-file>
|
||||
|
@ -15,8 +21,14 @@
|
|||
<ucap-chat-message-box-attach-file
|
||||
*ngSwitchCase="FileType.Sound"
|
||||
[fileInfo]="fileInfo"
|
||||
[fileDownloadItem]="fileDownloadItem"
|
||||
[expired]="getExpiredFile()"
|
||||
[deviceType]="deviceType"
|
||||
[existFile]="existFile"
|
||||
[fileRetentionPeriod]="fileRetentionPeriod"
|
||||
(openViewer)="onClickFileViewer(fileInfo)"
|
||||
(openFile)="onClickOpenFile(fileInfo)"
|
||||
(openFolder)="onClickOpenFolder(fileInfo)"
|
||||
(save)="onSave($event)"
|
||||
>
|
||||
</ucap-chat-message-box-attach-file>
|
||||
|
@ -25,6 +37,7 @@
|
|||
*ngSwitchCase="FileType.Image"
|
||||
[fileInfo]="fileInfo"
|
||||
[expired]="getExpiredFile()"
|
||||
[fileRetentionPeriod]="fileRetentionPeriod"
|
||||
(openViewer)="onClickFileViewer(fileInfo)"
|
||||
(save)="onSave($event)"
|
||||
></ucap-chat-message-box-image>
|
||||
|
@ -32,8 +45,14 @@
|
|||
<ucap-chat-message-box-video
|
||||
*ngSwitchCase="FileType.Video"
|
||||
[fileInfo]="fileInfo"
|
||||
[fileDownloadItem]="fileDownloadItem"
|
||||
[expired]="getExpiredFile()"
|
||||
[deviceType]="deviceType"
|
||||
[existFile]="existFile"
|
||||
[fileRetentionPeriod]="fileRetentionPeriod"
|
||||
(openViewer)="onClickFileViewer(fileInfo)"
|
||||
(openFile)="onClickOpenFile(fileInfo)"
|
||||
(openFolder)="onClickOpenFolder(fileInfo)"
|
||||
(save)="onSave($event)"
|
||||
></ucap-chat-message-box-video>
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import { Component, OnInit, Output, Input, EventEmitter } from '@angular/core';
|
|||
import { FileType, Info, FileEventJson } from '@ucap/protocol-event';
|
||||
import { RoomInfo } from '@ucap/protocol-room';
|
||||
import { FileDownloadItem, StatusCode } from '@ucap/api';
|
||||
import { SelectFileInfo } from '@ucap/ng-ui';
|
||||
import { DeviceType } from '@ucap/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ucap-chat-message-box-file',
|
||||
|
@ -15,14 +17,30 @@ export class FileComponent implements OnInit {
|
|||
@Input()
|
||||
roomInfo: RoomInfo;
|
||||
|
||||
@Input()
|
||||
existFile = false;
|
||||
|
||||
@Input()
|
||||
deviceType?: DeviceType;
|
||||
|
||||
@Input()
|
||||
fileRetentionPeriod?: number;
|
||||
|
||||
@Output()
|
||||
save = new EventEmitter<{
|
||||
fileInfo: FileEventJson;
|
||||
fileDownloadItem: FileDownloadItem;
|
||||
type: string;
|
||||
}>();
|
||||
|
||||
@Output()
|
||||
fileViewer = new EventEmitter<FileEventJson>();
|
||||
fileViewer = new EventEmitter<SelectFileInfo>();
|
||||
|
||||
@Output()
|
||||
openFile = new EventEmitter<FileEventJson>();
|
||||
|
||||
@Output()
|
||||
openFolder = new EventEmitter<FileEventJson>();
|
||||
|
||||
fileInfo?: FileEventJson;
|
||||
fileDownloadItem: FileDownloadItem;
|
||||
|
@ -55,7 +73,7 @@ export class FileComponent implements OnInit {
|
|||
|
||||
onClickFileViewer(fileInfo: FileEventJson) {
|
||||
if (!this.getExpiredFile()) {
|
||||
this.fileViewer.emit(fileInfo);
|
||||
this.fileViewer.emit({ attachmentSeq: this.fileInfo.attachmentSeq });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,4 +86,14 @@ export class FileComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
}
|
||||
onClickOpenFile(fileInfo: FileEventJson): void {
|
||||
if (!this.getExpiredFile()) {
|
||||
this.openFile.emit(fileInfo);
|
||||
}
|
||||
}
|
||||
onClickOpenFolder(fileInfo: FileEventJson): void {
|
||||
if (!this.getExpiredFile()) {
|
||||
this.openFolder.emit(fileInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user