@adaptabletools/adaptable-plugin-ipushpull 22.0.1 → 22.0.3
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/package.json +1 -3
- package/src/IPushPullApiImpl.d.ts +4 -3
- package/src/IPushPullApiImpl.js +1 -1
- package/src/IPushPullPluginOptions.d.ts +59 -0
- package/src/Module/PushPullModule.js +47 -49
- package/src/Utilities/Services/IPushPullService.d.ts +39 -0
- package/src/Utilities/Services/IPushPullService.js +202 -0
- package/src/Utilities/Services/Interface/{IPushPullService.d.ts → IIPushPullService.d.ts} +2 -2
- package/src/Utilities/Services/Interface/IIPushPullService.js +1 -0
- package/src/View/IPushPullAddPagePopup.js +19 -22
- package/src/View/IPushPullLoginPopup.js +15 -18
- package/src/View/IPushPullViewPanel.js +31 -47
- package/src/index.d.ts +6 -5
- package/src/index.js +29 -38
- package/src/ipushpull-client/IPushPullClient.d.ts +28 -0
- package/src/ipushpull-client/IPushPullClient.js +198 -0
- package/src/ipushpull-client/index.d.ts +4 -0
- package/src/ipushpull-client/index.js +2 -0
- package/src/ipushpull-client/themes.d.ts +8 -0
- package/src/ipushpull-client/themes.js +159 -0
- package/src/ipushpull-client/types.d.ts +67 -0
- package/src/ipushpull-client/types.js +1 -0
- package/src/Utilities/Services/PushPullService.d.ts +0 -26
- package/src/Utilities/Services/PushPullService.js +0 -300
- package/src/View/IPushPullPopup.d.ts +0 -2
- package/src/View/IPushPullPopup.js +0 -37
- /package/src/{Utilities/Services/Interface/IPushPullService.js → IPushPullPluginOptions.js} +0 -0
|
@@ -4,7 +4,7 @@ import * as React from 'react';
|
|
|
4
4
|
import ObjectFactory from '@adaptabletools/adaptable/src/Utilities/ObjectFactory';
|
|
5
5
|
import { StringExtensions } from '@adaptabletools/adaptable/src/Utilities/Extensions/StringExtensions';
|
|
6
6
|
import { Flex } from '@adaptabletools/adaptable/src/components/Flex';
|
|
7
|
-
import
|
|
7
|
+
import { Select } from '@adaptabletools/adaptable/src/components/Select';
|
|
8
8
|
import { ButtonExport } from '@adaptabletools/adaptable/src/View/Components/Buttons/ButtonExport';
|
|
9
9
|
import { ButtonPause } from '@adaptabletools/adaptable/src/View/Components/Buttons/ButtonPause';
|
|
10
10
|
import { ButtonPlay } from '@adaptabletools/adaptable/src/View/Components/Buttons/ButtonPlay';
|
|
@@ -62,58 +62,42 @@ const IPushPullViewPanelComponent = (props) => {
|
|
|
62
62
|
let allReports = systemReports
|
|
63
63
|
.filter((s) => props.api.exportApi.internalApi.isSystemReportActive(s))
|
|
64
64
|
.concat(props.Reports.map((r) => r.Name));
|
|
65
|
-
let availableReports = allReports.map((report) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
let
|
|
80
|
-
|
|
81
|
-
label: page,
|
|
82
|
-
value: page,
|
|
83
|
-
onClick: () => onPageChanged(page),
|
|
84
|
-
};
|
|
85
|
-
}) ?? [];
|
|
86
|
-
// this is clearly ridiculous!
|
|
87
|
-
// im getting tired...
|
|
88
|
-
let isCompletedReport = StringExtensions.IsNotNullOrEmpty(props.CurrentIPushPullReportName) &&
|
|
89
|
-
StringExtensions.IsNotNullOrEmpty(props.CurrentIPushPullFolder) &&
|
|
90
|
-
StringExtensions.IsNotNullOrEmpty(props.CurrentIPushPullPage);
|
|
65
|
+
let availableReports = allReports.map((report) => ({
|
|
66
|
+
label: report,
|
|
67
|
+
value: report,
|
|
68
|
+
}));
|
|
69
|
+
let availableFolders = props.IPushPullDomainsPages?.map((iPushPullDomain) => ({
|
|
70
|
+
label: iPushPullDomain.Name,
|
|
71
|
+
value: iPushPullDomain.Name,
|
|
72
|
+
})) ?? [];
|
|
73
|
+
let availablePages = props.CurrentIPushPullAvailablePages?.map((page) => ({
|
|
74
|
+
label: page,
|
|
75
|
+
value: page,
|
|
76
|
+
})) ?? [];
|
|
77
|
+
let hasReport = StringExtensions.IsNotNullOrEmpty(props.CurrentIPushPullReportName);
|
|
78
|
+
let hasFolder = StringExtensions.IsNotNullOrEmpty(props.CurrentIPushPullFolder);
|
|
79
|
+
let hasPage = StringExtensions.IsNotNullOrEmpty(props.CurrentIPushPullPage);
|
|
80
|
+
let isCompletedReport = hasReport && hasFolder && hasPage;
|
|
91
81
|
let isLiveIPushPullReport = isCompletedReport &&
|
|
92
82
|
props.CurrentLiveIPushPullReport &&
|
|
93
83
|
props.CurrentIPushPullReportName == props.CurrentLiveIPushPullReport.ReportName &&
|
|
94
84
|
props.CurrentIPushPullFolder == props.CurrentLiveIPushPullReport.Folder &&
|
|
95
85
|
props.CurrentIPushPullPage == props.CurrentLiveIPushPullReport.Page;
|
|
96
86
|
const elementType = props.viewType === 'Toolbar' ? 'DashboardToolbar' : 'ToolPanel';
|
|
97
|
-
return props.IsIPushPullRunning ? (React.createElement(Flex, { flexDirection: "row", className: `ab-${elementType}__IPushPull__wrap`, flexWrap: props.viewType === 'ToolPanel' ? 'wrap' : 'nowrap' },
|
|
98
|
-
React.createElement(Flex,
|
|
99
|
-
React.createElement(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
React.createElement(Flex,
|
|
103
|
-
React.createElement(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
:
|
|
110
|
-
|
|
111
|
-
React.createElement(ButtonExport, { className: `ab-${elementType}__IPushPull__export twa:ml-1`, onClick: () => onIPushPullSendSnapshot(), tooltip: "Send Snapshot to ipushpull", disabled: isLiveIPushPullReport || !isCompletedReport, accessLevel: props.accessLevel }),
|
|
112
|
-
true ? (React.createElement(ButtonPause, { className: `ab-${elementType}__IPushPull__pause twa:ml-1 twa:fill-red-500`, onClick: () => props.onIPushPullStopLiveData(), tooltip: "Stop sync with ipushpull", disabled: !isLiveIPushPullReport, accessLevel: props.accessLevel })) : (React.createElement(ButtonPlay, { className: `ab-${elementType}__IPushPull__play twa:ml-1`, onClick: () => onIPushPullStartLiveData(), tooltip: "Start sync with ipushpull", disabled: isLiveIPushPullReport || !isCompletedReport, accessLevel: props.accessLevel })),
|
|
113
|
-
isCompletedReport && (React.createElement(Flex, { className: join(props.accessLevel == 'ReadOnly' ? GeneralConstants.READ_ONLY_STYLE : '', `ab-${elementType}__IPushPull__controls`), alignItems: "stretch" }, props.api.entitlementApi.isModuleFullEntitlement('Schedule') && (React.createElement(ButtonSchedule, { className: `ab-${elementType}__IPushPull__schedule twa:ml-1`, onClick: () => onNewIPushPullSchedule(), tooltip: "Schedule", disabled: isLiveIPushPullReport || !isCompletedReport, accessLevel: props.accessLevel })))),
|
|
114
|
-
' ',
|
|
115
|
-
React.createElement(ButtonNewPage, { className: `ab-${elementType}__IPushPull__newpage twa:ml-1`, onClick: () => props.onShowAddIPushPullPage(), tooltip: "New Page", disabled: isLiveIPushPullReport, accessLevel: props.accessLevel }),
|
|
116
|
-
React.createElement(ButtonLogout, { className: `ab-${elementType}__IPushPull__logout twa:ml-1`, onClick: () => getIPPApi().logoutFromIPushPull(), tooltip: "Logout", disabled: isLiveIPushPullReport, accessLevel: props.accessLevel })))) : (React.createElement(ButtonLogin, { className: `ab-${elementType}__IPushPull__login twa:ml-1`, onClick: () => props.onShowIPushPullLogin(), tooltip: "Login to ipushpull", accessLevel: props.accessLevel },
|
|
87
|
+
return props.IsIPushPullRunning ? (React.createElement(Flex, { flexDirection: "row", className: `ab-${elementType}__IPushPull__wrap twa:gap-1`, flexWrap: props.viewType === 'ToolPanel' ? 'wrap' : 'nowrap' },
|
|
88
|
+
React.createElement(Flex, { className: "twa:min-w-[140px]" },
|
|
89
|
+
React.createElement(Select, { disabled: allReports.length == 0 || isLiveIPushPullReport, options: availableReports, className: `ab-${elementType}__IPushPull__select twa:w-full`, placeholder: "Select Report", onChange: (reportName) => onSelectedReportChanged(reportName), value: props.CurrentIPushPullReportName, isClearable: true })),
|
|
90
|
+
React.createElement(Flex, { className: "twa:min-w-[140px]" },
|
|
91
|
+
React.createElement(Select, { disabled: !hasReport || isLiveIPushPullReport, options: availableFolders, className: `ab-${elementType}__IPushPull__select twa:w-full`, placeholder: "Select Folder", onChange: (folder) => onFolderChanged(folder), value: props.CurrentIPushPullFolder, isClearable: true })),
|
|
92
|
+
React.createElement(Flex, { className: "twa:min-w-[140px]" },
|
|
93
|
+
React.createElement(Select, { disabled: !hasFolder || isLiveIPushPullReport, options: availablePages, className: `ab-${elementType}__IPushPull__select twa:w-full`, placeholder: "Select Page", onChange: (page) => onPageChanged(page), value: props.CurrentIPushPullPage, isClearable: true })),
|
|
94
|
+
React.createElement(Flex, { className: join(props.accessLevel == 'ReadOnly' ? GeneralConstants.READ_ONLY_STYLE : '', `ab-${elementType}__IPushPull__controls twa:w-full`) },
|
|
95
|
+
React.createElement(Flex, null,
|
|
96
|
+
React.createElement(ButtonExport, { className: `ab-${elementType}__IPushPull__export`, onClick: () => onIPushPullSendSnapshot(), tooltip: "Send Snapshot to ipushpull", disabled: isLiveIPushPullReport || !isCompletedReport, accessLevel: props.accessLevel }),
|
|
97
|
+
isLiveIPushPullReport ? (React.createElement(ButtonPause, { className: `ab-${elementType}__IPushPull__pause twa:fill-red-500`, onClick: () => props.onIPushPullStopLiveData(), tooltip: "Stop sync with ipushpull", disabled: !isLiveIPushPullReport, accessLevel: props.accessLevel })) : (React.createElement(ButtonPlay, { className: `ab-${elementType}__IPushPull__play`, onClick: () => onIPushPullStartLiveData(), tooltip: "Start sync with ipushpull", disabled: isLiveIPushPullReport || !isCompletedReport, accessLevel: props.accessLevel })),
|
|
98
|
+
props.api.entitlementApi.isModuleFullEntitlement('Schedule') && (React.createElement(ButtonSchedule, { className: `ab-${elementType}__IPushPull__schedule`, onClick: () => onNewIPushPullSchedule(), tooltip: "Schedule", disabled: isLiveIPushPullReport || !isCompletedReport, accessLevel: props.accessLevel })),
|
|
99
|
+
React.createElement(ButtonNewPage, { className: `ab-${elementType}__IPushPull__newpage`, onClick: () => props.onShowAddIPushPullPage(), tooltip: "New Page", disabled: !hasFolder || isLiveIPushPullReport, accessLevel: props.accessLevel }),
|
|
100
|
+
React.createElement(ButtonLogout, { className: `ab-${elementType}__IPushPull__logout`, onClick: () => getIPPApi().logoutFromIPushPull(), tooltip: "Logout", disabled: isLiveIPushPullReport, accessLevel: props.accessLevel }))))) : (React.createElement(ButtonLogin, { className: `ab-${elementType}__IPushPull__login twa:ml-1`, onClick: () => props.onShowIPushPullLogin(), tooltip: "Login to ipushpull", accessLevel: props.accessLevel },
|
|
117
101
|
' ',
|
|
118
102
|
"Login"));
|
|
119
103
|
};
|
package/src/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { AdaptablePlugin } from '@adaptabletools/adaptable';
|
|
1
|
+
import { AdaptablePlugin } from '@adaptabletools/adaptable/types';
|
|
2
2
|
import type { Middleware } from 'redux';
|
|
3
3
|
import * as Redux from 'redux';
|
|
4
4
|
import { IPushPullState } from '@adaptabletools/adaptable/src/AdaptableState/IPushPullState';
|
|
5
5
|
import { IPushPullApi } from '@adaptabletools/adaptable/src/Api/IPushPullApi';
|
|
6
|
-
import { IPushPullPluginOptions } from '
|
|
6
|
+
import { IPushPullPluginOptions } from './IPushPullPluginOptions';
|
|
7
7
|
import { IAdaptable } from '@adaptabletools/adaptable/src/AdaptableInterfaces/IAdaptable';
|
|
8
8
|
declare class IPushPullPlugin extends AdaptablePlugin {
|
|
9
9
|
options: IPushPullPluginOptions;
|
|
@@ -13,10 +13,11 @@ declare class IPushPullPlugin extends AdaptablePlugin {
|
|
|
13
13
|
constructor(options?: IPushPullPluginOptions);
|
|
14
14
|
afterInitApi(adaptable: IAdaptable): void;
|
|
15
15
|
rootReducer: (rootReducer: any) => {
|
|
16
|
-
|
|
16
|
+
Internal: (state: IPushPullState, action: Redux.Action) => IPushPullState;
|
|
17
17
|
};
|
|
18
18
|
reduxMiddleware: (adaptable: IAdaptable) => Middleware;
|
|
19
19
|
afterInitStore(adaptable: IAdaptable): void;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
export
|
|
21
|
+
export type { IPushPullPluginOptions, IPushPullConfig } from './IPushPullPluginOptions';
|
|
22
|
+
export declare const ipushpullPlugin: (options?: IPushPullPluginOptions) => IPushPullPlugin;
|
|
23
|
+
export default ipushpullPlugin;
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AdaptablePlugin } from '@adaptabletools/adaptable';
|
|
1
|
+
import { AdaptablePlugin } from '@adaptabletools/adaptable/types';
|
|
2
2
|
import env from './env';
|
|
3
3
|
import packageJson from '../package.json';
|
|
4
4
|
import adaptableCorePackageJson from '@adaptabletools/adaptable/package.json';
|
|
@@ -8,8 +8,8 @@ import * as PopupRedux from '@adaptabletools/adaptable/src/Redux/ActionsReducers
|
|
|
8
8
|
import { AdaptableViewFactory, AdaptableViewPanelFactory, } from '@adaptabletools/adaptable/src/View/AdaptableViewFactory';
|
|
9
9
|
import { PushPullModule } from './Module/PushPullModule';
|
|
10
10
|
import { IPushPullApiImpl } from './IPushPullApiImpl';
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
11
|
+
import { IPushPullService } from './Utilities/Services/IPushPullService';
|
|
12
|
+
import { IPushPullClient } from './ipushpull-client';
|
|
13
13
|
import { iPushPullInitialState, IPushPullReducer, IPushPullSetCurrentAvailablePages, IPushPullSetCurrentPage, } from './Redux/ActionReducers/IPushPullRedux';
|
|
14
14
|
import { IPushPullLoginPopup } from './View/IPushPullLoginPopup';
|
|
15
15
|
import { IPushPullAddPagePopup } from './View/IPushPullAddPagePopup';
|
|
@@ -22,25 +22,18 @@ const suffix = name.endsWith('-cjs') ? '-cjs' : '';
|
|
|
22
22
|
if (version !== coreVersion) {
|
|
23
23
|
console.warn(`Version mismatch: "@adaptabletools/adaptable-plugin-ipushpull${suffix}" (v${version}) and "@adaptabletools/adaptable${suffix}" (v${coreVersion}) have different versions. They should be the exact same version.`);
|
|
24
24
|
}
|
|
25
|
+
const DEFAULT_API_URL = 'https://test.ipushpull.com/api/1.0';
|
|
25
26
|
const getApiKey = () => {
|
|
26
|
-
|
|
27
|
-
return key;
|
|
27
|
+
return env.IPUSHPULL_API_KEY || '';
|
|
28
28
|
};
|
|
29
29
|
const getApiSecret = () => {
|
|
30
|
-
|
|
31
|
-
return secret;
|
|
30
|
+
return env.IPUSHPULL_API_SECRET || '';
|
|
32
31
|
};
|
|
33
32
|
const defaultOptions = {
|
|
34
33
|
ippConfig: {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
api_url:
|
|
38
|
-
ws_url: 'https://www.ipushpull.com',
|
|
39
|
-
web_url: 'https://www.ipushpull.com',
|
|
40
|
-
docs_url: 'https://docs.ipushpull.com',
|
|
41
|
-
storage_prefix: 'ipp_local',
|
|
42
|
-
transport: 'polling',
|
|
43
|
-
hsts: false, // strict cors policy
|
|
34
|
+
api_key: getApiKey(),
|
|
35
|
+
api_secret: getApiSecret(),
|
|
36
|
+
api_url: DEFAULT_API_URL,
|
|
44
37
|
},
|
|
45
38
|
autoLogin: false,
|
|
46
39
|
throttleTime: 2000,
|
|
@@ -52,40 +45,37 @@ class IPushPullPlugin extends AdaptablePlugin {
|
|
|
52
45
|
PushPullService;
|
|
53
46
|
constructor(options) {
|
|
54
47
|
super(options);
|
|
48
|
+
const defaults = defaultOptions.ippConfig;
|
|
49
|
+
const userConfig = options?.ippConfig;
|
|
55
50
|
const ippConfig = {
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
api_url: userConfig?.api_url ?? defaults.api_url ?? DEFAULT_API_URL,
|
|
52
|
+
api_key: userConfig?.api_key || defaults.api_key,
|
|
53
|
+
api_secret: userConfig?.api_secret || defaults.api_secret,
|
|
58
54
|
};
|
|
59
|
-
if (!ippConfig.api_key) {
|
|
60
|
-
ippConfig.api_key = defaultOptions.ippConfig.api_key;
|
|
61
|
-
}
|
|
62
|
-
if (!ippConfig.api_secret) {
|
|
63
|
-
ippConfig.api_secret = defaultOptions.ippConfig.api_secret;
|
|
64
|
-
}
|
|
65
55
|
this.options = {
|
|
66
56
|
...defaultOptions,
|
|
67
57
|
...options,
|
|
68
58
|
ippConfig,
|
|
69
59
|
};
|
|
70
|
-
/**
|
|
71
|
-
* Contains the objects required to export (snapshot or live) data to ipushpull from AdapTable.
|
|
72
|
-
*
|
|
73
|
-
* Includes ipushpull config and objects and, optionally, any ipushpull Reports (including schedules).
|
|
74
|
-
*/
|
|
75
|
-
// IPushPull?: IPushPullState;
|
|
76
|
-
ipushpull.config.set(this.options.ippConfig);
|
|
77
60
|
}
|
|
78
61
|
afterInitApi(adaptable) {
|
|
62
|
+
const config = this.options.ippConfig;
|
|
63
|
+
const client = new IPushPullClient({
|
|
64
|
+
api_url: config.api_url ?? DEFAULT_API_URL,
|
|
65
|
+
api_key: config.api_key,
|
|
66
|
+
api_secret: config.api_secret,
|
|
67
|
+
});
|
|
79
68
|
this.iPushPullApi = new IPushPullApiImpl(adaptable, this.options);
|
|
80
|
-
this.PushPullService = new
|
|
81
|
-
this.iPushPullApi.setIPushPullInstance(
|
|
69
|
+
this.PushPullService = new IPushPullService(adaptable, this.options.cellStyles);
|
|
70
|
+
this.iPushPullApi.setIPushPullInstance(client);
|
|
82
71
|
this.registerProperty('api', () => this.iPushPullApi);
|
|
83
72
|
this.registerProperty('service', () => this.PushPullService);
|
|
84
73
|
}
|
|
74
|
+
// FIXME AFL improve typing
|
|
85
75
|
rootReducer = (rootReducer) => {
|
|
86
76
|
return {
|
|
87
|
-
|
|
88
|
-
let augmentedState = rootReducer.
|
|
77
|
+
Internal: (state, action) => {
|
|
78
|
+
let augmentedState = rootReducer.Internal(state, action);
|
|
89
79
|
if (!state) {
|
|
90
80
|
// required for store initialization
|
|
91
81
|
// (idiomatic way of default parameter value in reducer is not feasible because the passed argument is already initialized by the System reducer)
|
|
@@ -142,7 +132,7 @@ class IPushPullPlugin extends AdaptablePlugin {
|
|
|
142
132
|
return next(action);
|
|
143
133
|
}
|
|
144
134
|
case IPUSHPULL_DOMAIN_PAGES_SET: {
|
|
145
|
-
|
|
135
|
+
const result = next(action);
|
|
146
136
|
const currentFolder = middlewareAPI.getState().Internal.IPushPullCurrentFolder;
|
|
147
137
|
const isFolderValid = StringExtensions.IsNotNullOrEmpty(currentFolder);
|
|
148
138
|
if (isFolderValid) {
|
|
@@ -151,7 +141,7 @@ class IPushPullPlugin extends AdaptablePlugin {
|
|
|
151
141
|
.getPagesForIPushPullDomain(currentFolder);
|
|
152
142
|
middlewareAPI.dispatch(IPushPullSetCurrentAvailablePages(availablePages));
|
|
153
143
|
}
|
|
154
|
-
return
|
|
144
|
+
return result;
|
|
155
145
|
}
|
|
156
146
|
default: {
|
|
157
147
|
return next(action);
|
|
@@ -167,4 +157,5 @@ class IPushPullPlugin extends AdaptablePlugin {
|
|
|
167
157
|
AdaptableViewPanelFactory.set(IPushPullModuleId, IPushPullViewPanelControl);
|
|
168
158
|
}
|
|
169
159
|
}
|
|
170
|
-
export
|
|
160
|
+
export const ipushpullPlugin = (options) => new IPushPullPlugin(options);
|
|
161
|
+
export default ipushpullPlugin;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IPushPullClientConfig, IPushPullTokens, IPushPullDomainAccessInfo, IPushPullPageContentPayload, IPushPullPageContentResponse, IPushPullCellStyle } from './types';
|
|
2
|
+
export declare class IPushPullClient {
|
|
3
|
+
private config;
|
|
4
|
+
private tokens;
|
|
5
|
+
private refreshPromise;
|
|
6
|
+
constructor(config: IPushPullClientConfig);
|
|
7
|
+
private getBasicAuthHeader;
|
|
8
|
+
private buildOAuthBody;
|
|
9
|
+
login(username: string, password: string): Promise<IPushPullTokens>;
|
|
10
|
+
refreshToken(): Promise<IPushPullTokens>;
|
|
11
|
+
private doRefreshToken;
|
|
12
|
+
logout(): Promise<void>;
|
|
13
|
+
getDomainsAndPages(): Promise<IPushPullDomainAccessInfo[]>;
|
|
14
|
+
getPageContent(folderId: number, pageId: number): Promise<any>;
|
|
15
|
+
updatePageContent(folderId: number, pageId: number, payload: IPushPullPageContentPayload): Promise<IPushPullPageContentResponse>;
|
|
16
|
+
createPage(folderId: number, pageName: string, description?: string): Promise<any>;
|
|
17
|
+
isAuthenticated(): boolean;
|
|
18
|
+
private fetchWithAuth;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Converts per-cell data (array of rows, each cell being { value, formatted_value, style })
|
|
22
|
+
* into the official ipushpull page content format with deduplicated styles.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildPageContentPayload(cellData: Array<Array<{
|
|
25
|
+
value: any;
|
|
26
|
+
formatted_value: any;
|
|
27
|
+
style: IPushPullCellStyle;
|
|
28
|
+
}>>): IPushPullPageContentPayload;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
export class IPushPullClient {
|
|
2
|
+
config;
|
|
3
|
+
tokens = null;
|
|
4
|
+
refreshPromise = null;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
}
|
|
8
|
+
getBasicAuthHeader() {
|
|
9
|
+
const credentials = `${this.config.api_key}:${this.config.api_secret}`;
|
|
10
|
+
return `Basic ${btoa(credentials)}`;
|
|
11
|
+
}
|
|
12
|
+
buildOAuthBody(params) {
|
|
13
|
+
return Object.entries(params)
|
|
14
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
15
|
+
.join('&');
|
|
16
|
+
}
|
|
17
|
+
async login(username, password) {
|
|
18
|
+
const url = `${this.config.api_url}/oauth/token/`;
|
|
19
|
+
const body = this.buildOAuthBody({
|
|
20
|
+
grant_type: 'password',
|
|
21
|
+
username,
|
|
22
|
+
password,
|
|
23
|
+
});
|
|
24
|
+
const response = await fetch(url, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
28
|
+
Authorization: this.getBasicAuthHeader(),
|
|
29
|
+
},
|
|
30
|
+
body,
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
const errorData = await response.json().catch(() => ({}));
|
|
34
|
+
const message = errorData.error_description || errorData.error || response.statusText;
|
|
35
|
+
const err = new Error(message);
|
|
36
|
+
err.data = errorData;
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
this.tokens = await response.json();
|
|
40
|
+
return this.tokens;
|
|
41
|
+
}
|
|
42
|
+
async refreshToken() {
|
|
43
|
+
if (!this.tokens?.refresh_token) {
|
|
44
|
+
throw new Error('No refresh token available. Please login first.');
|
|
45
|
+
}
|
|
46
|
+
// Deduplicate concurrent refresh attempts
|
|
47
|
+
if (this.refreshPromise) {
|
|
48
|
+
return this.refreshPromise;
|
|
49
|
+
}
|
|
50
|
+
this.refreshPromise = this.doRefreshToken();
|
|
51
|
+
try {
|
|
52
|
+
const result = await this.refreshPromise;
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
this.refreshPromise = null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async doRefreshToken() {
|
|
60
|
+
const url = `${this.config.api_url}/oauth/token/`;
|
|
61
|
+
const body = this.buildOAuthBody({
|
|
62
|
+
grant_type: 'refresh_token',
|
|
63
|
+
refresh_token: this.tokens.refresh_token,
|
|
64
|
+
});
|
|
65
|
+
const response = await fetch(url, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
69
|
+
Authorization: this.getBasicAuthHeader(),
|
|
70
|
+
},
|
|
71
|
+
body,
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
this.tokens = null;
|
|
75
|
+
const errorData = await response.json().catch(() => ({}));
|
|
76
|
+
throw new Error(errorData.error_description || errorData.error || 'Token refresh failed');
|
|
77
|
+
}
|
|
78
|
+
this.tokens = await response.json();
|
|
79
|
+
return this.tokens;
|
|
80
|
+
}
|
|
81
|
+
async logout() {
|
|
82
|
+
if (!this.tokens?.access_token) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const url = `${this.config.api_url}/oauth/logout/`;
|
|
86
|
+
await fetch(url, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: {
|
|
89
|
+
Authorization: `Bearer ${this.tokens.access_token}`,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
this.tokens = null;
|
|
93
|
+
}
|
|
94
|
+
async getDomainsAndPages() {
|
|
95
|
+
const url = `${this.config.api_url}/domain_page_access`;
|
|
96
|
+
const response = await this.fetchWithAuth(url);
|
|
97
|
+
const data = await response.json();
|
|
98
|
+
return (data.domains ?? data).map((domain) => ({
|
|
99
|
+
id: domain.id,
|
|
100
|
+
name: domain.name,
|
|
101
|
+
pages: (domain.current_user_domain_page_access?.pages ?? domain.pages ?? []).map((page) => ({
|
|
102
|
+
id: page.id,
|
|
103
|
+
name: page.name,
|
|
104
|
+
special_page_type: page.special_page_type ?? 0,
|
|
105
|
+
write_access: page.write_access ?? false,
|
|
106
|
+
})),
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
async getPageContent(folderId, pageId) {
|
|
110
|
+
const baseUrl = this.config.api_url.replace(/\/api\/1\.0\/?$/, '');
|
|
111
|
+
const url = `${baseUrl}/api/2.0/domains/id/${folderId}/page_content/id/${pageId}/`;
|
|
112
|
+
const response = await this.fetchWithAuth(url);
|
|
113
|
+
return response.json();
|
|
114
|
+
}
|
|
115
|
+
async updatePageContent(folderId, pageId, payload) {
|
|
116
|
+
const baseUrl = this.config.api_url.replace(/\/api\/1\.0\/?$/, '');
|
|
117
|
+
const url = `${baseUrl}/api/2.0/domains/id/${folderId}/page_content/id/${pageId}/`;
|
|
118
|
+
const response = await this.fetchWithAuth(url, {
|
|
119
|
+
method: 'PUT',
|
|
120
|
+
headers: { 'Content-Type': 'application/json' },
|
|
121
|
+
body: JSON.stringify(payload),
|
|
122
|
+
});
|
|
123
|
+
return response.json();
|
|
124
|
+
}
|
|
125
|
+
async createPage(folderId, pageName, description) {
|
|
126
|
+
const url = `${this.config.api_url}/domains/${folderId}/pages/`;
|
|
127
|
+
const response = await this.fetchWithAuth(url, {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: { 'Content-Type': 'application/json' },
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
name: pageName,
|
|
132
|
+
description: description ?? '',
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
return response.json();
|
|
136
|
+
}
|
|
137
|
+
isAuthenticated() {
|
|
138
|
+
return this.tokens != null;
|
|
139
|
+
}
|
|
140
|
+
async fetchWithAuth(url, options = {}) {
|
|
141
|
+
if (!this.tokens?.access_token) {
|
|
142
|
+
throw new Error('Not authenticated. Please login first.');
|
|
143
|
+
}
|
|
144
|
+
const headers = new Headers(options.headers);
|
|
145
|
+
headers.set('Authorization', `Bearer ${this.tokens.access_token}`);
|
|
146
|
+
let response = await fetch(url, { ...options, headers });
|
|
147
|
+
if (response.status === 401) {
|
|
148
|
+
await this.refreshToken();
|
|
149
|
+
headers.set('Authorization', `Bearer ${this.tokens.access_token}`);
|
|
150
|
+
response = await fetch(url, { ...options, headers });
|
|
151
|
+
}
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
const errorData = await response.json().catch(() => ({}));
|
|
154
|
+
const message = errorData.detail || errorData.error || response.statusText;
|
|
155
|
+
throw new Error(message);
|
|
156
|
+
}
|
|
157
|
+
return response;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Converts per-cell data (array of rows, each cell being { value, formatted_value, style })
|
|
162
|
+
* into the official ipushpull page content format with deduplicated styles.
|
|
163
|
+
*/
|
|
164
|
+
export function buildPageContentPayload(cellData) {
|
|
165
|
+
const values = [];
|
|
166
|
+
const formattedValues = [];
|
|
167
|
+
const uniqueStyles = [];
|
|
168
|
+
const cellStyles = [];
|
|
169
|
+
const styleIndex = new Map();
|
|
170
|
+
for (const row of cellData) {
|
|
171
|
+
const rowValues = [];
|
|
172
|
+
const rowFormattedValues = [];
|
|
173
|
+
const rowStyleIndices = [];
|
|
174
|
+
for (const cell of row) {
|
|
175
|
+
rowValues.push(cell.value);
|
|
176
|
+
rowFormattedValues.push(cell.formatted_value);
|
|
177
|
+
const styleKey = JSON.stringify(cell.style);
|
|
178
|
+
let idx = styleIndex.get(styleKey);
|
|
179
|
+
if (idx === undefined) {
|
|
180
|
+
idx = uniqueStyles.length;
|
|
181
|
+
uniqueStyles.push(cell.style);
|
|
182
|
+
styleIndex.set(styleKey, idx);
|
|
183
|
+
}
|
|
184
|
+
rowStyleIndices.push(idx);
|
|
185
|
+
}
|
|
186
|
+
values.push(rowValues);
|
|
187
|
+
formattedValues.push(rowFormattedValues);
|
|
188
|
+
cellStyles.push(rowStyleIndices);
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
content: {
|
|
192
|
+
values,
|
|
193
|
+
formatted_values: formattedValues,
|
|
194
|
+
unique_styles: uniqueStyles,
|
|
195
|
+
cell_styles: cellStyles,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { IPushPullClient, buildPageContentPayload } from './IPushPullClient';
|
|
2
|
+
export { getThemeStyles } from './themes';
|
|
3
|
+
export type { IPushPullTheme, IPushPullThemeStyles } from './themes';
|
|
4
|
+
export type { IPushPullClientConfig, IPushPullTokens, IPushPullPageAccessInfo, IPushPullDomainAccessInfo, IPushPullCellStyle, IPushPullPageContentPayload, IPushPullPageContentResponse, } from './types';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IPushPullCellStyle } from './types';
|
|
2
|
+
export type IPushPullTheme = 'lightTheme' | 'darkTheme';
|
|
3
|
+
export interface IPushPullThemeStyles {
|
|
4
|
+
headerStyle: IPushPullCellStyle;
|
|
5
|
+
rowStyle: IPushPullCellStyle;
|
|
6
|
+
altRowStyle: IPushPullCellStyle;
|
|
7
|
+
}
|
|
8
|
+
export declare function getThemeStyles(theme: IPushPullTheme): IPushPullThemeStyles;
|