@things-factory/notification 8.0.0-beta.9 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/actions/notification-fcm.ts +148 -0
- package/client/bootstrap.ts +135 -0
- package/client/index.ts +6 -0
- package/client/pages/notification/notification-list-page.ts +258 -0
- package/client/pages/notification-rule/notification-rule-importer.ts +87 -0
- package/client/pages/notification-rule/notification-rule-list-page.ts +386 -0
- package/client/reducers/notification.ts +27 -0
- package/client/route.ts +10 -0
- package/client/tsconfig.json +13 -0
- package/client/viewparts/notification-badge.ts +54 -0
- package/client/viewparts/notification-item.ts +246 -0
- package/client/viewparts/notification-list.ts +160 -0
- package/client/viewparts/notification-sender.ts +142 -0
- package/client/viewparts/notification-setting-let.ts +222 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/docs/images/config-app-1.png +0 -0
- package/docs/images/config-app-2.png +0 -0
- package/docs/images/config-server-key.png +0 -0
- package/docs/images/config-service-account.png +0 -0
- package/docs/images/config-vapidkey-1.png +0 -0
- package/docs/images/config-vapidkey-2-get-public-key.png +0 -0
- package/docs/images/config-vapidkey-3-get-private-key.png +0 -0
- package/docs/images/element-notification-badge.png +0 -0
- package/docs/images/element-notification-list.png +0 -0
- package/docs/images/element-notification-setting-let.png +0 -0
- package/docs/images/push-test-on-chrome-1.png +0 -0
- package/docs/images/push-test-on-chrome-2.png +0 -0
- package/docs/images/push-test-on-firebase-1.png +0 -0
- package/docs/images/push-test-on-firebase-2.png +0 -0
- package/docs/images/push-test-on-firebase-3.png +0 -0
- package/docs/images/push-test-on-firebase-4.png +0 -0
- package/package.json +9 -9
- package/server/controllers/fcm.ts +214 -0
- package/server/controllers/index.ts +1 -0
- package/server/index.ts +5 -0
- package/server/middlewares/index.ts +5 -0
- package/server/middlewares/notification-middleware.ts +73 -0
- package/server/routers/notification-router.ts +67 -0
- package/server/routes.ts +11 -0
- package/server/service/index.ts +42 -0
- package/server/service/notification/directive-notification.ts +71 -0
- package/server/service/notification/index.ts +14 -0
- package/server/service/notification/notification-mutation.ts +119 -0
- package/server/service/notification/notification-query.ts +76 -0
- package/server/service/notification/notification-subscription.ts +44 -0
- package/server/service/notification/notification-type.ts +55 -0
- package/server/service/notification/notification.ts +105 -0
- package/server/service/notification-rule/event-subscriber.ts +20 -0
- package/server/service/notification-rule/index.ts +9 -0
- package/server/service/notification-rule/notification-rule-history.ts +136 -0
- package/server/service/notification-rule/notification-rule-mutation.ts +203 -0
- package/server/service/notification-rule/notification-rule-query.ts +65 -0
- package/server/service/notification-rule/notification-rule-type.ts +71 -0
- package/server/service/notification-rule/notification-rule.ts +125 -0
- package/server/tsconfig.json +9 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
export const UPDATE_NOTIFICATION = 'UPDATE_NOTIFICATION'
|
|
2
|
+
export const CONFIRM_NOTIFICATION = 'CONFIRM_NOTIFICATION'
|
|
3
|
+
|
|
4
|
+
import { initializeApp } from 'firebase/app'
|
|
5
|
+
import { getMessaging, getToken, deleteToken } from 'firebase/messaging'
|
|
6
|
+
|
|
7
|
+
import { synchronize } from '@things-factory/utils'
|
|
8
|
+
|
|
9
|
+
var vapidKey
|
|
10
|
+
var appConfig
|
|
11
|
+
var messaging
|
|
12
|
+
|
|
13
|
+
async function _getVapidKey() {
|
|
14
|
+
if (!vapidKey || !appConfig) {
|
|
15
|
+
if (Notification.permission === 'denied') {
|
|
16
|
+
console.warn('Notification permission denied')
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const response = await fetch('/notification/config', {
|
|
21
|
+
headers: {
|
|
22
|
+
Accept: 'application/json'
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error(`fail to get notification configuration: ${response.status}`)
|
|
27
|
+
}
|
|
28
|
+
const conf = await response.json()
|
|
29
|
+
|
|
30
|
+
if (!conf || !conf.vapidPublicKey || !conf.appConfig) {
|
|
31
|
+
throw new Error('correct notification configuration required.')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const app = await initializeApp(conf.appConfig)
|
|
36
|
+
messaging = getMessaging(app)
|
|
37
|
+
|
|
38
|
+
// messaging = _messaging.isSupported() ? _messaging() : null
|
|
39
|
+
if (!messaging) {
|
|
40
|
+
console.warn('###################################################')
|
|
41
|
+
console.warn('push notification is not supported in this browser.')
|
|
42
|
+
console.warn('###################################################')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
vapidKey = conf.vapidPublicKey
|
|
46
|
+
appConfig = conf.appConfig
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw new Error(`failed to initialize firebase app.`)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { vapidKey, messaging }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// to avoid race condition
|
|
56
|
+
const getVapidKey = synchronize(_getVapidKey) as any
|
|
57
|
+
|
|
58
|
+
export async function isSupportingMessaging() {
|
|
59
|
+
const { messaging } = (await getVapidKey()) || {}
|
|
60
|
+
return !!messaging
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function getSubscriptionToken(force = false) {
|
|
64
|
+
// const registration = await navigator.serviceWorker.getRegistration('/')
|
|
65
|
+
const registration = await navigator.serviceWorker?.ready
|
|
66
|
+
|
|
67
|
+
if (!registration) {
|
|
68
|
+
console.warn('service worker not registered.')
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!registration.pushManager || (!force && !(await registration.pushManager.getSubscription()))) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const { vapidKey, messaging } = (await getVapidKey()) || {}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
return (
|
|
80
|
+
vapidKey &&
|
|
81
|
+
(await getToken(messaging, {
|
|
82
|
+
vapidKey,
|
|
83
|
+
serviceWorkerRegistration: registration
|
|
84
|
+
}))
|
|
85
|
+
)
|
|
86
|
+
} catch (err) {
|
|
87
|
+
console.error(err)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function subscribe() {
|
|
92
|
+
const token = await getSubscriptionToken(true)
|
|
93
|
+
|
|
94
|
+
if (!token) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
await fetch('/notification/register', {
|
|
99
|
+
method: 'post',
|
|
100
|
+
headers: {
|
|
101
|
+
'Content-type': 'application/json',
|
|
102
|
+
Accept: 'application/json'
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({
|
|
105
|
+
subscription: token
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
return token
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function unsubscribe() {
|
|
113
|
+
const token = await getSubscriptionToken()
|
|
114
|
+
if (!token) {
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const { vapidKey, messaging } = (await getVapidKey()) || {}
|
|
120
|
+
vapidKey && (await deleteToken(messaging))
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error(err)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await fetch('/notification/unregister', {
|
|
126
|
+
method: 'post',
|
|
127
|
+
headers: {
|
|
128
|
+
'Content-type': 'application/json',
|
|
129
|
+
Accept: 'application/json'
|
|
130
|
+
},
|
|
131
|
+
body: JSON.stringify({
|
|
132
|
+
subscription: token
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function notify(options) {
|
|
138
|
+
const response = await fetch('/notification/notify', {
|
|
139
|
+
method: 'post',
|
|
140
|
+
headers: {
|
|
141
|
+
'Content-type': 'application/json',
|
|
142
|
+
Accept: 'application/json'
|
|
143
|
+
},
|
|
144
|
+
body: JSON.stringify(options)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return response.ok
|
|
148
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import gql from 'graphql-tag'
|
|
2
|
+
|
|
3
|
+
import { notify } from '@operato/layout'
|
|
4
|
+
import { auth } from '@things-factory/auth-base/dist-client/auth.js'
|
|
5
|
+
|
|
6
|
+
import { i18next } from '@operato/i18n'
|
|
7
|
+
import { notificationStore, route, store } from '@operato/shell'
|
|
8
|
+
import { subscribe as graphqlSubscribe } from '@operato/graphql'
|
|
9
|
+
|
|
10
|
+
import { subscribe, unsubscribe, UPDATE_NOTIFICATION } from './actions/notification-fcm'
|
|
11
|
+
import notification from './reducers/notification'
|
|
12
|
+
|
|
13
|
+
var graphqlSubscription: any
|
|
14
|
+
|
|
15
|
+
export default async function bootstrap() {
|
|
16
|
+
/* initialize reducers */
|
|
17
|
+
store.addReducers({ notification })
|
|
18
|
+
|
|
19
|
+
async function onnotification(notification?: any) {
|
|
20
|
+
store.dispatch((async dispatch => {
|
|
21
|
+
if (notification) {
|
|
22
|
+
await notificationStore.add(notification)
|
|
23
|
+
await notificationStore.limit()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const history = await notificationStore.getAll()
|
|
27
|
+
|
|
28
|
+
dispatch({
|
|
29
|
+
type: UPDATE_NOTIFICATION,
|
|
30
|
+
history: [...history]
|
|
31
|
+
})
|
|
32
|
+
}) as any)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
auth.on('presignout', async () => {
|
|
36
|
+
try {
|
|
37
|
+
await unsubscribe()
|
|
38
|
+
/* await */ notificationStore.clear()
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.warn(err)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
auth.on('profile', async ({ credential }) => {
|
|
45
|
+
try {
|
|
46
|
+
await subscribe()
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.warn(err)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* subscription graphql notifications
|
|
53
|
+
*/
|
|
54
|
+
var query = gql`
|
|
55
|
+
subscription notification($subjects: [String!]) {
|
|
56
|
+
notification(subjects: $subjects) {
|
|
57
|
+
subject
|
|
58
|
+
type
|
|
59
|
+
title
|
|
60
|
+
body
|
|
61
|
+
url
|
|
62
|
+
image
|
|
63
|
+
timestamp
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
`
|
|
67
|
+
|
|
68
|
+
graphqlSubscription = await graphqlSubscribe(
|
|
69
|
+
{
|
|
70
|
+
query,
|
|
71
|
+
variables: {
|
|
72
|
+
subjects: ['info', 'warn', 'error', 'reload']
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
next({ data }) {
|
|
77
|
+
const { notification } = data || {}
|
|
78
|
+
onnotification({
|
|
79
|
+
timestamp: Date.now(),
|
|
80
|
+
...notification
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
/* subscription web-push notification */
|
|
88
|
+
const channel = new BroadcastChannel('notification')
|
|
89
|
+
channel.addEventListener('message', async ({ data }) => {
|
|
90
|
+
const { type } = data || {}
|
|
91
|
+
|
|
92
|
+
switch (type) {
|
|
93
|
+
case 'notificationclick':
|
|
94
|
+
const { action, notification } = data
|
|
95
|
+
|
|
96
|
+
// TODO how to follow action with notification.data
|
|
97
|
+
notification.url && route(notification.url)
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
case 'show-reload-snackbar':
|
|
101
|
+
notify({
|
|
102
|
+
level: 'info',
|
|
103
|
+
message: i18next.t('text.the application has been updated'),
|
|
104
|
+
option: {
|
|
105
|
+
action: {
|
|
106
|
+
label: i18next.t('button.reload'),
|
|
107
|
+
callback: () => window.location.reload()
|
|
108
|
+
},
|
|
109
|
+
timer: -1
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
case 'notification':
|
|
115
|
+
default:
|
|
116
|
+
onnotification()
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
document.addEventListener('notify', (event: Event) => {
|
|
121
|
+
let { message, level, ex = '', option = {} } = (event as CustomEvent).detail
|
|
122
|
+
const title = message.split(' .\n')[0]
|
|
123
|
+
|
|
124
|
+
if (level === 'error') {
|
|
125
|
+
onnotification({
|
|
126
|
+
timestamp: Date.now(),
|
|
127
|
+
type: 'ERROR',
|
|
128
|
+
title,
|
|
129
|
+
body: ex ? ex.message : message
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
await onnotification()
|
|
135
|
+
}
|
package/client/index.ts
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import '@operato/data-grist'
|
|
2
|
+
|
|
3
|
+
import { CommonButtonStyles, CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
|
|
4
|
+
import { PageView, store } from '@operato/shell'
|
|
5
|
+
import { css, html } from 'lit'
|
|
6
|
+
import { customElement, property, query } from 'lit/decorators.js'
|
|
7
|
+
import { ScopedElementsMixin } from '@open-wc/scoped-elements'
|
|
8
|
+
import { ColumnConfig, DataGrist, FetchOption } from '@operato/data-grist'
|
|
9
|
+
import { client } from '@operato/graphql'
|
|
10
|
+
import { i18next, localize } from '@operato/i18n'
|
|
11
|
+
import { notify } from '@operato/layout'
|
|
12
|
+
import { isMobileDevice } from '@operato/utils'
|
|
13
|
+
|
|
14
|
+
import { connect } from 'pwa-helpers/connect-mixin'
|
|
15
|
+
import gql from 'graphql-tag'
|
|
16
|
+
|
|
17
|
+
@customElement('notification-list-page')
|
|
18
|
+
export class NotificationListPage extends connect(store)(localize(i18next)(ScopedElementsMixin(PageView))) {
|
|
19
|
+
static styles = [
|
|
20
|
+
ScrollbarStyles,
|
|
21
|
+
CommonGristStyles,
|
|
22
|
+
CommonHeaderStyles,
|
|
23
|
+
css`
|
|
24
|
+
:host {
|
|
25
|
+
display: flex;
|
|
26
|
+
|
|
27
|
+
width: 100%;
|
|
28
|
+
|
|
29
|
+
--grid-record-emphasized-background-color: #8b0000;
|
|
30
|
+
--grid-record-emphasized-color: #ff6b6b;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
ox-grist {
|
|
34
|
+
overflow-y: auto;
|
|
35
|
+
flex: 1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
ox-filters-form {
|
|
39
|
+
flex: 1;
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
@property({ type: Object }) gristConfig: any
|
|
45
|
+
@property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
|
|
46
|
+
|
|
47
|
+
@query('ox-grist') private grist!: DataGrist
|
|
48
|
+
|
|
49
|
+
get context() {
|
|
50
|
+
return {
|
|
51
|
+
title: i18next.t('title.notification list'),
|
|
52
|
+
search: {
|
|
53
|
+
handler: (search: string) => {
|
|
54
|
+
this.grist.searchText = search
|
|
55
|
+
},
|
|
56
|
+
value: this.grist?.searchText || ''
|
|
57
|
+
},
|
|
58
|
+
filter: {
|
|
59
|
+
handler: () => {
|
|
60
|
+
this.grist.toggleHeadroom()
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
help: 'notification/notification',
|
|
64
|
+
actions: [
|
|
65
|
+
{
|
|
66
|
+
title: i18next.t('button.save'),
|
|
67
|
+
action: this._updateNotification.bind(this),
|
|
68
|
+
...CommonButtonStyles.save
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: i18next.t('button.delete'),
|
|
72
|
+
action: this._deleteNotification.bind(this),
|
|
73
|
+
...CommonButtonStyles.delete
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
render() {
|
|
80
|
+
const mode = this.mode || (isMobileDevice() ? 'CARD' : 'GRID')
|
|
81
|
+
|
|
82
|
+
return html`
|
|
83
|
+
<ox-grist .mode=${mode} .config=${this.gristConfig} .fetchHandler=${this.fetchHandler.bind(this)}>
|
|
84
|
+
<div slot="headroom" class="header">
|
|
85
|
+
<div class="filters">
|
|
86
|
+
<ox-filters-form autofocus></ox-filters-form>
|
|
87
|
+
|
|
88
|
+
<div id="modes">
|
|
89
|
+
<md-icon @click=${() => (this.mode = 'GRID')} ?active=${mode == 'GRID'}>grid_on</md-icon>
|
|
90
|
+
<md-icon @click=${() => (this.mode = 'LIST')} ?active=${mode == 'LIST'}>format_list_bulleted</md-icon>
|
|
91
|
+
<md-icon @click=${() => (this.mode = 'CARD')} ?active=${mode == 'CARD'}>apps</md-icon>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</ox-grist>
|
|
96
|
+
`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async pageInitialized(lifecycle: any) {
|
|
100
|
+
this.gristConfig = {
|
|
101
|
+
list: {
|
|
102
|
+
fields: ['title', 'body'],
|
|
103
|
+
details: ['state', 'updatedAt']
|
|
104
|
+
},
|
|
105
|
+
columns: [
|
|
106
|
+
{ type: 'gutter', gutterName: 'sequence' },
|
|
107
|
+
{ type: 'gutter', gutterName: 'row-selector', multiple: true },
|
|
108
|
+
{
|
|
109
|
+
type: 'string',
|
|
110
|
+
name: 'title',
|
|
111
|
+
header: i18next.t('field.title'),
|
|
112
|
+
record: {
|
|
113
|
+
editable: false
|
|
114
|
+
},
|
|
115
|
+
filter: 'search',
|
|
116
|
+
sortable: true,
|
|
117
|
+
width: 150
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: 'string',
|
|
121
|
+
name: 'body',
|
|
122
|
+
header: i18next.t('field.body'),
|
|
123
|
+
record: {
|
|
124
|
+
editable: false
|
|
125
|
+
},
|
|
126
|
+
filter: 'search',
|
|
127
|
+
width: 200
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
type: 'checkbox',
|
|
131
|
+
name: 'state',
|
|
132
|
+
label: true,
|
|
133
|
+
header: i18next.t('field.state'),
|
|
134
|
+
record: {
|
|
135
|
+
editable: true
|
|
136
|
+
},
|
|
137
|
+
filter: true,
|
|
138
|
+
sortable: true,
|
|
139
|
+
width: 60
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: 'datetime',
|
|
143
|
+
name: 'createdAt',
|
|
144
|
+
header: i18next.t('field.created_at'),
|
|
145
|
+
record: {
|
|
146
|
+
editable: false
|
|
147
|
+
},
|
|
148
|
+
sortable: true,
|
|
149
|
+
width: 180
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
rows: {
|
|
153
|
+
selectable: {
|
|
154
|
+
multiple: true
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
sorters: [
|
|
158
|
+
{
|
|
159
|
+
name: 'createdAt',
|
|
160
|
+
desc: true
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async pageUpdated(changes: any, lifecycle: any) {
|
|
167
|
+
if (this.active) {
|
|
168
|
+
// do something here when this page just became as active
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {
|
|
173
|
+
const response = await client.query({
|
|
174
|
+
query: gql`
|
|
175
|
+
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
|
|
176
|
+
responses: notiBoxes(filters: $filters, pagination: $pagination, sortings: $sortings) {
|
|
177
|
+
items {
|
|
178
|
+
id
|
|
179
|
+
title
|
|
180
|
+
body
|
|
181
|
+
state
|
|
182
|
+
createdAt
|
|
183
|
+
}
|
|
184
|
+
total
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
`,
|
|
188
|
+
variables: {
|
|
189
|
+
filters,
|
|
190
|
+
pagination: { page, limit },
|
|
191
|
+
sortings
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
total: response.data.responses.total || 0,
|
|
197
|
+
records: response.data.responses.items || []
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async _deleteNotification() {
|
|
202
|
+
if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) {
|
|
203
|
+
const ids = this.grist.selected.map(record => record.id)
|
|
204
|
+
if (ids && ids.length > 0) {
|
|
205
|
+
const response = await client.mutate({
|
|
206
|
+
mutation: gql`
|
|
207
|
+
mutation ($ids: [String!]!) {
|
|
208
|
+
deleteNotificationes(ids: $ids)
|
|
209
|
+
}
|
|
210
|
+
`,
|
|
211
|
+
variables: {
|
|
212
|
+
ids
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
if (!response.errors) {
|
|
217
|
+
this.grist.fetch()
|
|
218
|
+
notify({
|
|
219
|
+
message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async _updateNotification() {
|
|
227
|
+
let patches = this.grist.dirtyRecords
|
|
228
|
+
if (patches && patches.length) {
|
|
229
|
+
patches = patches.map(patch => {
|
|
230
|
+
let patchField: any = patch.id ? { id: patch.id } : {}
|
|
231
|
+
const dirtyFields = patch.__dirtyfields__
|
|
232
|
+
for (let key in dirtyFields) {
|
|
233
|
+
patchField[key] = dirtyFields[key].after
|
|
234
|
+
}
|
|
235
|
+
patchField.cuFlag = patch.__dirty__
|
|
236
|
+
|
|
237
|
+
return patchField
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
const response = await client.mutate({
|
|
241
|
+
mutation: gql`
|
|
242
|
+
mutation ($patches: [NotificationPatch!]!) {
|
|
243
|
+
updateMultipleNotification(patches: $patches) {
|
|
244
|
+
name
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
`,
|
|
248
|
+
variables: {
|
|
249
|
+
patches
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
if (!response.errors) {
|
|
254
|
+
this.grist.fetch()
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js'
|
|
2
|
+
import '@operato/data-grist'
|
|
3
|
+
|
|
4
|
+
import gql from 'graphql-tag'
|
|
5
|
+
import { css, html, LitElement } from 'lit'
|
|
6
|
+
import { property } from 'lit/decorators.js'
|
|
7
|
+
|
|
8
|
+
import { client } from '@operato/graphql'
|
|
9
|
+
import { i18next } from '@operato/i18n'
|
|
10
|
+
import { isMobileDevice } from '@operato/utils'
|
|
11
|
+
import { CommonHeaderStyles } from '@operato/styles'
|
|
12
|
+
|
|
13
|
+
export class NotificationRuleImporter extends LitElement {
|
|
14
|
+
static styles = [
|
|
15
|
+
CommonHeaderStyles,
|
|
16
|
+
css`
|
|
17
|
+
:host {
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
|
|
21
|
+
background-color: var(--md-sys-color-surface);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
ox-grist {
|
|
25
|
+
flex: 1;
|
|
26
|
+
}
|
|
27
|
+
`
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
@property({ type: Array }) notificationRules: any[] = []
|
|
31
|
+
@property({ type: Object }) columns = {
|
|
32
|
+
list: { fields: ['name', 'description'] },
|
|
33
|
+
pagination: { infinite: true },
|
|
34
|
+
columns: [
|
|
35
|
+
{
|
|
36
|
+
type: 'string',
|
|
37
|
+
name: 'name',
|
|
38
|
+
header: i18next.t('field.name'),
|
|
39
|
+
width: 150
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: 'string',
|
|
43
|
+
name: 'description',
|
|
44
|
+
header: i18next.t('field.description'),
|
|
45
|
+
width: 200
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: 'checkbox',
|
|
49
|
+
name: 'active',
|
|
50
|
+
header: i18next.t('field.active'),
|
|
51
|
+
width: 60
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
render() {
|
|
57
|
+
return html`
|
|
58
|
+
<ox-grist
|
|
59
|
+
.mode=${isMobileDevice() ? 'LIST' : 'GRID'}
|
|
60
|
+
.config=${this.columns}
|
|
61
|
+
.data=${{
|
|
62
|
+
records: this.notificationRules
|
|
63
|
+
}}
|
|
64
|
+
></ox-grist>
|
|
65
|
+
|
|
66
|
+
<div class="footer">
|
|
67
|
+
<div filler></div>
|
|
68
|
+
<button @click=${this.save.bind(this)} done><md-icon>save</md-icon>${i18next.t('button.save')}</button>
|
|
69
|
+
</div>
|
|
70
|
+
`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async save() {
|
|
74
|
+
const response = await client.mutate({
|
|
75
|
+
mutation: gql`
|
|
76
|
+
mutation importNotificationRules($notificationRules: [NotificationRulePatch!]!) {
|
|
77
|
+
importNotificationRules(notificationRules: $notificationRules)
|
|
78
|
+
}
|
|
79
|
+
`,
|
|
80
|
+
variables: { notificationRules: this.notificationRules }
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
if (response.errors?.length) return
|
|
84
|
+
|
|
85
|
+
this.dispatchEvent(new CustomEvent('imported'))
|
|
86
|
+
}
|
|
87
|
+
}
|