@acorex-platform/extension-source-control-core-client 0.0.21
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/fesm2022/acorex-platform-extension-source-control-core-client-contracts.mjs +69 -0
- package/fesm2022/acorex-platform-extension-source-control-core-client-contracts.mjs.map +1 -0
- package/fesm2022/acorex-platform-extension-source-control-core-client-source-control-page.component-DOALkPKo.mjs +749 -0
- package/fesm2022/acorex-platform-extension-source-control-core-client-source-control-page.component-DOALkPKo.mjs.map +1 -0
- package/fesm2022/acorex-platform-extension-source-control-core-client.mjs +322 -0
- package/fesm2022/acorex-platform-extension-source-control-core-client.mjs.map +1 -0
- package/package.json +28 -0
- package/types/acorex-platform-extension-source-control-core-client-contracts.d.ts +136 -0
- package/types/acorex-platform-extension-source-control-core-client.d.ts +9 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
import * as i6$1 from '@angular/common';
|
|
2
|
+
import { AsyncPipe, CommonModule } from '@angular/common';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { inject, Injectable, input, signal, computed, effect, untracked, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
5
|
+
import * as i2 from '@acorex/components/badge';
|
|
6
|
+
import { AXBadgeModule } from '@acorex/components/badge';
|
|
7
|
+
import * as i3 from '@acorex/components/button';
|
|
8
|
+
import { AXButtonModule } from '@acorex/components/button';
|
|
9
|
+
import * as i4 from '@acorex/components/decorators';
|
|
10
|
+
import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
11
|
+
import { AXDialogService } from '@acorex/components/dialog';
|
|
12
|
+
import * as i5 from '@acorex/components/loading';
|
|
13
|
+
import { AXLoadingModule } from '@acorex/components/loading';
|
|
14
|
+
import * as i5$1 from '@acorex/components/search-box';
|
|
15
|
+
import { AXSearchBoxModule } from '@acorex/components/search-box';
|
|
16
|
+
import * as i7 from '@acorex/core/translation';
|
|
17
|
+
import { AXTranslationModule, AXTranslationService } from '@acorex/core/translation';
|
|
18
|
+
import { AXPPageLayoutBaseComponent, AXPPageLayoutBase } from '@acorex-platform/framework-client/layout/views';
|
|
19
|
+
import { AXE_SC_PROVIDER_GITHUB, AXE_SC_PROVIDER_GITLAB, AXE_SC_PROVIDER_IDS, RootConfig, AXESourceControlHookKeys } from '@acorex-platform/extension-source-control-core-client/contracts';
|
|
20
|
+
import { trim, defaultTo } from 'lodash-es';
|
|
21
|
+
import { AXMIntegrationConnectionManager } from '@acorex-platform/module-integration-management-client';
|
|
22
|
+
import { AXPEntityService } from '@acorex-platform/framework-client/layout/entity';
|
|
23
|
+
import { AXPQueryService, AXPCommandExecutor } from '@acorex-platform/framework-client/runtime';
|
|
24
|
+
import { runtimeQueryPayloadData, AXPRegionalSetting } from '@acorex-platform/framework-shared/core';
|
|
25
|
+
import { AXEGithubQueryKeys } from '@acorex-platform/extension-github-client/contracts';
|
|
26
|
+
import { AXEGitLabQueryKeys } from '@acorex-platform/extension-gitlab-client/contracts';
|
|
27
|
+
import * as i1 from '@angular/forms';
|
|
28
|
+
import { FormsModule } from '@angular/forms';
|
|
29
|
+
import * as i6 from '@acorex/components/select-box';
|
|
30
|
+
import { AXSelectBoxModule } from '@acorex/components/select-box';
|
|
31
|
+
import { AXPSettingsService } from '@acorex-platform/framework-client/common';
|
|
32
|
+
import { AXPStateMessageComponent } from '@acorex-platform/framework-client/layout/components';
|
|
33
|
+
import { AXFormatService } from '@acorex/core/format';
|
|
34
|
+
import { AXPHookService } from '@acorex-platform/framework-client/core';
|
|
35
|
+
|
|
36
|
+
//#region ----- Imports -----
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region ----- Service -----
|
|
39
|
+
class AXESourceControlRepoSearchClient {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.queryService = inject(AXPQueryService);
|
|
42
|
+
}
|
|
43
|
+
async search(input) {
|
|
44
|
+
const key = this.resolveQueryKey(input.providerId);
|
|
45
|
+
const result = await this.queryService.fetch(key, input);
|
|
46
|
+
return runtimeQueryPayloadData(result)?.items ?? [];
|
|
47
|
+
}
|
|
48
|
+
resolveQueryKey(providerId) {
|
|
49
|
+
if (providerId === AXE_SC_PROVIDER_GITHUB) {
|
|
50
|
+
return AXEGithubQueryKeys.RepoSearch;
|
|
51
|
+
}
|
|
52
|
+
if (providerId === AXE_SC_PROVIDER_GITLAB) {
|
|
53
|
+
return AXEGitLabQueryKeys.RepoSearch;
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`Unsupported source-control provider: ${providerId}`);
|
|
56
|
+
}
|
|
57
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlRepoSearchClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
58
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlRepoSearchClient, providedIn: 'root' }); }
|
|
59
|
+
}
|
|
60
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlRepoSearchClient, decorators: [{
|
|
61
|
+
type: Injectable,
|
|
62
|
+
args: [{ providedIn: 'root' }]
|
|
63
|
+
}] });
|
|
64
|
+
|
|
65
|
+
//#region ----- Imports -----
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region ----- Service -----
|
|
68
|
+
class AXESourceControlProjectLinkService {
|
|
69
|
+
constructor() {
|
|
70
|
+
//#region ----- Services & Dependencies -----
|
|
71
|
+
this.connectionManager = inject(AXMIntegrationConnectionManager);
|
|
72
|
+
this.entityService = inject(AXPEntityService);
|
|
73
|
+
this.repoSearch = inject(AXESourceControlRepoSearchClient);
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region ----- Connections -----
|
|
77
|
+
async listConnections() {
|
|
78
|
+
const connections = await this.connectionManager.listForCurrentUser('tenant');
|
|
79
|
+
return connections.filter((item) => AXE_SC_PROVIDER_IDS.includes(item.providerId) &&
|
|
80
|
+
!item.isRevoked &&
|
|
81
|
+
!item.isDisabled);
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region ----- Link persistence -----
|
|
85
|
+
async findLinkByProjectId(projectId) {
|
|
86
|
+
const id = trim(projectId);
|
|
87
|
+
if (!id) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const result = await this.entityService
|
|
91
|
+
.withEntity(RootConfig.module.name, RootConfig.entities.projectSourceControlLink.name)
|
|
92
|
+
.data()
|
|
93
|
+
.query({
|
|
94
|
+
skip: 0,
|
|
95
|
+
take: 1,
|
|
96
|
+
filter: {
|
|
97
|
+
field: 'projectId',
|
|
98
|
+
operator: { type: 'equal' },
|
|
99
|
+
value: id,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
return result.items?.[0] ?? null;
|
|
103
|
+
}
|
|
104
|
+
async saveLink(input) {
|
|
105
|
+
const projectId = trim(input.projectId);
|
|
106
|
+
if (!projectId) {
|
|
107
|
+
throw new Error('Project id is required.');
|
|
108
|
+
}
|
|
109
|
+
const accessor = this.entityService
|
|
110
|
+
.withEntity(RootConfig.module.name, RootConfig.entities.projectSourceControlLink.name)
|
|
111
|
+
.data();
|
|
112
|
+
const existing = await this.findLinkByProjectId(projectId);
|
|
113
|
+
const linkedAt = new Date().toISOString();
|
|
114
|
+
const payload = {
|
|
115
|
+
projectId,
|
|
116
|
+
providerId: input.connection.providerId,
|
|
117
|
+
connectionId: input.connection.id,
|
|
118
|
+
remoteId: input.repo.remoteId,
|
|
119
|
+
fullName: input.repo.fullName,
|
|
120
|
+
webUrl: input.repo.webUrl,
|
|
121
|
+
defaultBranch: input.repo.defaultBranch,
|
|
122
|
+
linkedAt,
|
|
123
|
+
};
|
|
124
|
+
if (existing) {
|
|
125
|
+
await accessor.update(existing.id, payload);
|
|
126
|
+
return { ...existing, ...payload };
|
|
127
|
+
}
|
|
128
|
+
const createdId = await accessor.create(payload);
|
|
129
|
+
return {
|
|
130
|
+
id: String(createdId),
|
|
131
|
+
...payload,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async removeLink(projectId) {
|
|
135
|
+
const existing = await this.findLinkByProjectId(projectId);
|
|
136
|
+
if (!existing) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
await this.entityService
|
|
140
|
+
.withEntity(RootConfig.module.name, RootConfig.entities.projectSourceControlLink.name)
|
|
141
|
+
.data()
|
|
142
|
+
.delete(existing.id);
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region ----- Repository search -----
|
|
146
|
+
async searchRepositories(connection, query) {
|
|
147
|
+
return this.repoSearch.search({
|
|
148
|
+
providerId: connection.providerId,
|
|
149
|
+
connectionId: connection.id,
|
|
150
|
+
query: trim(query),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlProjectLinkService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
154
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlProjectLinkService, providedIn: 'root' }); }
|
|
155
|
+
}
|
|
156
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlProjectLinkService, decorators: [{
|
|
157
|
+
type: Injectable,
|
|
158
|
+
args: [{ providedIn: 'root' }]
|
|
159
|
+
}] });
|
|
160
|
+
|
|
161
|
+
//#region ----- Imports -----
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region ----- Service -----
|
|
164
|
+
class AXESourceControlRepositoryBrowseClient {
|
|
165
|
+
constructor() {
|
|
166
|
+
this.queryService = inject(AXPQueryService);
|
|
167
|
+
}
|
|
168
|
+
async listBranches(link) {
|
|
169
|
+
const input = {
|
|
170
|
+
providerId: link.providerId,
|
|
171
|
+
connectionId: link.connectionId,
|
|
172
|
+
fullName: link.fullName,
|
|
173
|
+
};
|
|
174
|
+
const result = await this.queryService.fetch(this.resolveBranchesQueryKey(link.providerId), input);
|
|
175
|
+
return runtimeQueryPayloadData(result)?.items ?? [];
|
|
176
|
+
}
|
|
177
|
+
async listCommits(link, branch, take = 30) {
|
|
178
|
+
const input = {
|
|
179
|
+
providerId: link.providerId,
|
|
180
|
+
connectionId: link.connectionId,
|
|
181
|
+
fullName: link.fullName,
|
|
182
|
+
branch,
|
|
183
|
+
take,
|
|
184
|
+
};
|
|
185
|
+
const result = await this.queryService.fetch(this.resolveCommitsQueryKey(link.providerId), input);
|
|
186
|
+
return runtimeQueryPayloadData(result)?.items ?? [];
|
|
187
|
+
}
|
|
188
|
+
resolveBranchesQueryKey(providerId) {
|
|
189
|
+
if (providerId === AXE_SC_PROVIDER_GITHUB) {
|
|
190
|
+
return AXEGithubQueryKeys.ListBranches;
|
|
191
|
+
}
|
|
192
|
+
if (providerId === AXE_SC_PROVIDER_GITLAB) {
|
|
193
|
+
return AXEGitLabQueryKeys.ListBranches;
|
|
194
|
+
}
|
|
195
|
+
throw new Error(`Unsupported source-control provider: ${providerId}`);
|
|
196
|
+
}
|
|
197
|
+
resolveCommitsQueryKey(providerId) {
|
|
198
|
+
if (providerId === AXE_SC_PROVIDER_GITHUB) {
|
|
199
|
+
return AXEGithubQueryKeys.ListCommits;
|
|
200
|
+
}
|
|
201
|
+
if (providerId === AXE_SC_PROVIDER_GITLAB) {
|
|
202
|
+
return AXEGitLabQueryKeys.ListCommits;
|
|
203
|
+
}
|
|
204
|
+
throw new Error(`Unsupported source-control provider: ${providerId}`);
|
|
205
|
+
}
|
|
206
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlRepositoryBrowseClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
207
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlRepositoryBrowseClient, providedIn: 'root' }); }
|
|
208
|
+
}
|
|
209
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlRepositoryBrowseClient, decorators: [{
|
|
210
|
+
type: Injectable,
|
|
211
|
+
args: [{ providedIn: 'root' }]
|
|
212
|
+
}] });
|
|
213
|
+
|
|
214
|
+
//#region ----- Imports -----
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region ----- Service -----
|
|
217
|
+
class AXESourceControlRepositoryHooksService {
|
|
218
|
+
constructor() {
|
|
219
|
+
this.hooks = inject(AXPHookService);
|
|
220
|
+
}
|
|
221
|
+
async resolveCommitActions(input) {
|
|
222
|
+
const payload = {
|
|
223
|
+
projectId: input.projectId,
|
|
224
|
+
link: input.link,
|
|
225
|
+
branch: input.branch,
|
|
226
|
+
commit: input.commit,
|
|
227
|
+
actions: [],
|
|
228
|
+
};
|
|
229
|
+
const result = await this.hooks.runAsync(AXESourceControlHookKeys.CommitActions, payload);
|
|
230
|
+
return result?.actions ?? [];
|
|
231
|
+
}
|
|
232
|
+
async resolveBranchActions(input) {
|
|
233
|
+
const payload = {
|
|
234
|
+
projectId: input.projectId,
|
|
235
|
+
link: input.link,
|
|
236
|
+
branch: input.branch,
|
|
237
|
+
baseBranch: input.baseBranch,
|
|
238
|
+
actions: [],
|
|
239
|
+
};
|
|
240
|
+
const result = await this.hooks.runAsync(AXESourceControlHookKeys.BranchActions, payload);
|
|
241
|
+
return result?.actions ?? [];
|
|
242
|
+
}
|
|
243
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlRepositoryHooksService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
244
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlRepositoryHooksService, providedIn: 'root' }); }
|
|
245
|
+
}
|
|
246
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlRepositoryHooksService, decorators: [{
|
|
247
|
+
type: Injectable,
|
|
248
|
+
args: [{ providedIn: 'root' }]
|
|
249
|
+
}] });
|
|
250
|
+
|
|
251
|
+
//#region ----- Imports -----
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region ----- Constants -----
|
|
254
|
+
const I18N$1 = '@extensions-source-control:pages.repository.commits';
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region ----- Component -----
|
|
257
|
+
class AXERepositoryCommitsPanelComponent {
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region ----- Lifecycle -----
|
|
260
|
+
constructor() {
|
|
261
|
+
//#region ----- Inputs -----
|
|
262
|
+
this.projectId = input.required(/* @ts-ignore */
|
|
263
|
+
...(ngDevMode ? [{ debugName: "projectId" }] : /* istanbul ignore next */ []));
|
|
264
|
+
this.link = input.required(/* @ts-ignore */
|
|
265
|
+
...(ngDevMode ? [{ debugName: "link" }] : /* istanbul ignore next */ []));
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region ----- Services & Dependencies -----
|
|
268
|
+
this.i18n = I18N$1;
|
|
269
|
+
this.browseClient = inject(AXESourceControlRepositoryBrowseClient);
|
|
270
|
+
this.hooksService = inject(AXESourceControlRepositoryHooksService);
|
|
271
|
+
this.commandExecutor = inject(AXPCommandExecutor);
|
|
272
|
+
this.settingsService = inject(AXPSettingsService);
|
|
273
|
+
this.formatService = inject(AXFormatService);
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region ----- State Signals -----
|
|
276
|
+
this.loading = signal(false, /* @ts-ignore */
|
|
277
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
278
|
+
this.branches = signal([], /* @ts-ignore */
|
|
279
|
+
...(ngDevMode ? [{ debugName: "branches" }] : /* istanbul ignore next */ []));
|
|
280
|
+
this.selectedBranch = signal('', /* @ts-ignore */
|
|
281
|
+
...(ngDevMode ? [{ debugName: "selectedBranch" }] : /* istanbul ignore next */ []));
|
|
282
|
+
this.commits = signal([], /* @ts-ignore */
|
|
283
|
+
...(ngDevMode ? [{ debugName: "commits" }] : /* istanbul ignore next */ []));
|
|
284
|
+
this.branchActions = signal([], /* @ts-ignore */
|
|
285
|
+
...(ngDevMode ? [{ debugName: "branchActions" }] : /* istanbul ignore next */ []));
|
|
286
|
+
this.commitActionsBySha = signal({}, /* @ts-ignore */
|
|
287
|
+
...(ngDevMode ? [{ debugName: "commitActionsBySha" }] : /* istanbul ignore next */ []));
|
|
288
|
+
this.datetimeFormat = signal('', /* @ts-ignore */
|
|
289
|
+
...(ngDevMode ? [{ debugName: "datetimeFormat" }] : /* istanbul ignore next */ []));
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region ----- Computed Properties -----
|
|
292
|
+
this.baseBranch = computed(() => {
|
|
293
|
+
const link = this.link();
|
|
294
|
+
const defaultBranch = trim(link.defaultBranch ?? '');
|
|
295
|
+
if (defaultBranch) {
|
|
296
|
+
return defaultBranch;
|
|
297
|
+
}
|
|
298
|
+
const branches = this.branches();
|
|
299
|
+
const markedDefault = branches.find((branch) => branch.isDefault)?.name;
|
|
300
|
+
return markedDefault ?? branches[0]?.name ?? 'main';
|
|
301
|
+
}, /* @ts-ignore */
|
|
302
|
+
...(ngDevMode ? [{ debugName: "baseBranch" }] : /* istanbul ignore next */ []));
|
|
303
|
+
this.branchOptions = computed(() => this.branches().map((branch) => ({
|
|
304
|
+
name: branch.name,
|
|
305
|
+
title: branch.name,
|
|
306
|
+
})), /* @ts-ignore */
|
|
307
|
+
...(ngDevMode ? [{ debugName: "branchOptions" }] : /* istanbul ignore next */ []));
|
|
308
|
+
effect(() => {
|
|
309
|
+
const link = this.link();
|
|
310
|
+
const projectId = trim(this.projectId());
|
|
311
|
+
if (!link || !projectId) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
untracked(() => {
|
|
315
|
+
void this.initialize(link, projectId);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
effect(() => {
|
|
319
|
+
const link = this.link();
|
|
320
|
+
const projectId = trim(this.projectId());
|
|
321
|
+
const branch = trim(this.selectedBranch());
|
|
322
|
+
if (!link || !projectId || !branch) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
untracked(() => {
|
|
326
|
+
void this.loadBranchData(link, projectId, branch);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region ----- Event Handlers -----
|
|
332
|
+
onBranchChange(branch) {
|
|
333
|
+
this.selectedBranch.set(trim(branch ?? ''));
|
|
334
|
+
}
|
|
335
|
+
commitActions(sha) {
|
|
336
|
+
return this.commitActionsBySha()[sha] ?? [];
|
|
337
|
+
}
|
|
338
|
+
commitMessage(commit) {
|
|
339
|
+
const message = trim(commit.message);
|
|
340
|
+
if (!message) {
|
|
341
|
+
return '—';
|
|
342
|
+
}
|
|
343
|
+
const firstLine = message.split('\n')[0]?.trim();
|
|
344
|
+
return firstLine || message;
|
|
345
|
+
}
|
|
346
|
+
formatCommittedAt(value) {
|
|
347
|
+
const iso = trim(value);
|
|
348
|
+
if (!iso) {
|
|
349
|
+
return '—';
|
|
350
|
+
}
|
|
351
|
+
const format = this.datetimeFormat();
|
|
352
|
+
if (!format) {
|
|
353
|
+
return iso;
|
|
354
|
+
}
|
|
355
|
+
return this.formatService.format(new Date(iso), 'datetime', { format });
|
|
356
|
+
}
|
|
357
|
+
actionTextKey(action) {
|
|
358
|
+
const title = action.title;
|
|
359
|
+
return typeof title === 'string' ? title : '';
|
|
360
|
+
}
|
|
361
|
+
async onActionClick(action) {
|
|
362
|
+
const command = action.command;
|
|
363
|
+
if (!command?.name) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
await this.commandExecutor.execute(command.name, command.options ?? {});
|
|
367
|
+
}
|
|
368
|
+
onViewCommit(commit) {
|
|
369
|
+
const url = trim(commit.webUrl);
|
|
370
|
+
if (url) {
|
|
371
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region ----- Data Loading -----
|
|
376
|
+
async initialize(link, projectId) {
|
|
377
|
+
this.loading.set(true);
|
|
378
|
+
try {
|
|
379
|
+
await this.loadDatetimeFormat();
|
|
380
|
+
const branches = await this.browseClient.listBranches(link);
|
|
381
|
+
this.branches.set(branches);
|
|
382
|
+
const initialBranch = this.resolveInitialBranch(link, branches);
|
|
383
|
+
this.selectedBranch.set(initialBranch);
|
|
384
|
+
if (!initialBranch) {
|
|
385
|
+
this.commits.set([]);
|
|
386
|
+
this.branchActions.set([]);
|
|
387
|
+
this.commitActionsBySha.set({});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
this.handleError(error, 'Failed to load repository branches.');
|
|
392
|
+
this.branches.set([]);
|
|
393
|
+
this.selectedBranch.set('');
|
|
394
|
+
}
|
|
395
|
+
finally {
|
|
396
|
+
this.loading.set(false);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async loadBranchData(link, projectId, branch) {
|
|
400
|
+
this.loading.set(true);
|
|
401
|
+
try {
|
|
402
|
+
const [commits, branchActions] = await Promise.all([
|
|
403
|
+
this.browseClient.listCommits(link, branch),
|
|
404
|
+
this.hooksService.resolveBranchActions({
|
|
405
|
+
projectId,
|
|
406
|
+
link,
|
|
407
|
+
branch,
|
|
408
|
+
baseBranch: this.baseBranch(),
|
|
409
|
+
}),
|
|
410
|
+
]);
|
|
411
|
+
this.commits.set(commits);
|
|
412
|
+
this.branchActions.set(branchActions);
|
|
413
|
+
const commitActionsEntries = await Promise.all(commits.map(async (commit) => {
|
|
414
|
+
const actions = await this.hooksService.resolveCommitActions({
|
|
415
|
+
projectId,
|
|
416
|
+
link,
|
|
417
|
+
branch,
|
|
418
|
+
commit,
|
|
419
|
+
});
|
|
420
|
+
return [commit.sha, actions];
|
|
421
|
+
}));
|
|
422
|
+
this.commitActionsBySha.set(Object.fromEntries(commitActionsEntries));
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
425
|
+
this.handleError(error, 'Failed to load repository commits.');
|
|
426
|
+
this.commits.set([]);
|
|
427
|
+
this.branchActions.set([]);
|
|
428
|
+
this.commitActionsBySha.set({});
|
|
429
|
+
}
|
|
430
|
+
finally {
|
|
431
|
+
this.loading.set(false);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
resolveInitialBranch(link, branches) {
|
|
435
|
+
const preferred = trim(link.defaultBranch ?? '');
|
|
436
|
+
if (preferred && branches.some((branch) => branch.name === preferred)) {
|
|
437
|
+
return preferred;
|
|
438
|
+
}
|
|
439
|
+
const markedDefault = branches.find((branch) => branch.isDefault)?.name;
|
|
440
|
+
if (markedDefault) {
|
|
441
|
+
return markedDefault;
|
|
442
|
+
}
|
|
443
|
+
return defaultTo(branches[0]?.name, '');
|
|
444
|
+
}
|
|
445
|
+
async loadDatetimeFormat() {
|
|
446
|
+
const dateFormat = await this.settingsService.get(AXPRegionalSetting.ShortDate);
|
|
447
|
+
const timeFormat = await this.settingsService.get(AXPRegionalSetting.ShortTime);
|
|
448
|
+
this.datetimeFormat.set(`${dateFormat} ${timeFormat}`.trim());
|
|
449
|
+
}
|
|
450
|
+
handleError(error, fallback) {
|
|
451
|
+
const message = error instanceof Error ? error.message : fallback;
|
|
452
|
+
console.error(message, error);
|
|
453
|
+
}
|
|
454
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXERepositoryCommitsPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
455
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXERepositoryCommitsPanelComponent, isStandalone: true, selector: "axe-repository-commits-panel", inputs: { projectId: { classPropertyName: "projectId", publicName: "projectId", isSignal: true, isRequired: true, transformFunction: null }, link: { classPropertyName: "link", publicName: "link", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<section class=\"flex flex-col gap-4 rounded-lg border border-surface bg-lighter-surface p-4\">\n\n <div class=\"flex flex-wrap items-start justify-between gap-3\">\n\n <div>\n\n <div class=\"font-medium\">{{ (i18n + '.title' | translate | async) ?? '' }}</div>\n\n <div class=\"text-sm opacity-70\">{{ (i18n + '.description' | translate | async) ?? '' }}</div>\n\n </div>\n\n <!-- ----- Branch filter ----- -->\n\n @if (branchOptions().length) {\n\n <ax-select-box\n\n class=\"min-w-[12rem]\"\n\n [dataSource]=\"branchOptions()\"\n\n [ngModel]=\"selectedBranch()\"\n\n (ngModelChange)=\"onBranchChange($event)\"\n\n valueField=\"name\"\n\n textField=\"title\"\n\n [placeholder]=\"(i18n + '.branch.placeholder' | translate | async) ?? ''\"\n\n ></ax-select-box>\n\n }\n\n </div>\n\n\n\n <!-- ----- Branch-level hook actions ----- -->\n\n @if (branchActions().length) {\n\n <div class=\"flex flex-wrap gap-2\">\n\n @for (action of branchActions(); track action.name ?? action.title) {\n\n <ax-button\n\n look=\"outline\"\n\n color=\"primary\"\n\n size=\"sm\"\n\n [text]=\"(actionTextKey(action) | translate | async) ?? ''\"\n\n (onClick)=\"onActionClick(action)\"\n\n >\n\n @if (action.icon) {\n\n <ax-prefix>\n\n <ax-icon [icon]=\"action.icon\"></ax-icon>\n\n </ax-prefix>\n\n }\n\n </ax-button>\n\n }\n\n </div>\n\n }\n\n\n\n <!-- ----- Commits loading indicator ----- -->\n\n @if (loading()) {\n\n <ax-loading></ax-loading>\n\n }\n\n <!-- ----- Empty commits state ----- -->\n\n @else if (!commits().length) {\n\n <axp-state-message\n\n icon=\"fa-light fa-code-commit\"\n\n [title]=\"i18n + '.empty.title'\"\n\n [description]=\"i18n + '.empty.description'\"\n\n look=\"minimal\"\n\n ></axp-state-message>\n\n }\n\n <!-- ----- Commits list ----- -->\n\n @else {\n\n <div class=\"flex flex-col gap-2\">\n\n @for (commit of commits(); track commit.sha) {\n\n <article class=\"flex flex-wrap items-start justify-between gap-3 rounded-lg border border-surface bg-surface p-3\">\n\n <div class=\"min-w-0 flex-1\">\n\n <div class=\"flex flex-wrap items-center gap-2\">\n\n <ax-badge\n\n size=\"sm\"\n\n look=\"twotone\"\n\n color=\"primary\"\n\n [text]=\"commit.shortSha ?? commit.sha.slice(0, 7)\"\n\n ></ax-badge>\n\n <span class=\"font-medium\">{{ commitMessage(commit) }}</span>\n\n </div>\n\n <div class=\"mt-1 flex flex-wrap gap-x-4 gap-y-1 text-sm opacity-70\">\n\n <span>{{ commit.authorName }}</span>\n\n <span>{{ formatCommittedAt(commit.committedAt) }}</span>\n\n </div>\n\n </div>\n\n <div class=\"flex shrink-0 flex-wrap items-center gap-2\">\n\n <ax-button\n\n look=\"blank\"\n\n size=\"sm\"\n\n [text]=\"(i18n + '.actions.view' | translate | async) ?? ''\"\n\n (onClick)=\"onViewCommit(commit)\"\n\n >\n\n <ax-prefix>\n\n <ax-icon icon=\"fa-light fa-arrow-up-right-from-square\"></ax-icon>\n\n </ax-prefix>\n\n </ax-button>\n\n <!-- ----- Commit-level hook actions ----- -->\n\n @for (action of commitActions(commit.sha); track action.name ?? action.title) {\n\n <ax-button\n\n look=\"outline\"\n\n size=\"sm\"\n\n color=\"primary\"\n\n [text]=\"(actionTextKey(action) | translate | async) ?? ''\"\n\n (onClick)=\"onActionClick(action)\"\n\n >\n\n @if (action.icon) {\n\n <ax-prefix>\n\n <ax-icon [icon]=\"action.icon\"></ax-icon>\n\n </ax-prefix>\n\n }\n\n </ax-button>\n\n }\n\n </div>\n\n </article>\n\n }\n\n </div>\n\n }\n\n</section>\n\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i4.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i4.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i5.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXSelectBoxModule }, { kind: "component", type: i6.AXSelectBoxComponent, selector: "ax-select-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "minValue", "maxValue", "value", "state", "name", "id", "type", "look", "multiple", "valueField", "textField", "disabledField", "textTemplate", "selectedItems", "isItemTruncated", "showItemTooltip", "itemHeight", "maxVisibleItems", "dataSource", "minRecordsForSearch", "caption", "itemTemplate", "selectedTemplate", "emptyTemplate", "loadingTemplate", "dropdownWidth", "searchBoxAutoFocus"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onOpened", "onClosed", "onItemSelected", "onItemClick"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: i7.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
456
|
+
}
|
|
457
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXERepositoryCommitsPanelComponent, decorators: [{
|
|
458
|
+
type: Component,
|
|
459
|
+
args: [{ selector: 'axe-repository-commits-panel', imports: [
|
|
460
|
+
AsyncPipe,
|
|
461
|
+
FormsModule,
|
|
462
|
+
AXBadgeModule,
|
|
463
|
+
AXButtonModule,
|
|
464
|
+
AXDecoratorModule,
|
|
465
|
+
AXLoadingModule,
|
|
466
|
+
AXSelectBoxModule,
|
|
467
|
+
AXTranslationModule,
|
|
468
|
+
AXPStateMessageComponent,
|
|
469
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"flex flex-col gap-4 rounded-lg border border-surface bg-lighter-surface p-4\">\n\n <div class=\"flex flex-wrap items-start justify-between gap-3\">\n\n <div>\n\n <div class=\"font-medium\">{{ (i18n + '.title' | translate | async) ?? '' }}</div>\n\n <div class=\"text-sm opacity-70\">{{ (i18n + '.description' | translate | async) ?? '' }}</div>\n\n </div>\n\n <!-- ----- Branch filter ----- -->\n\n @if (branchOptions().length) {\n\n <ax-select-box\n\n class=\"min-w-[12rem]\"\n\n [dataSource]=\"branchOptions()\"\n\n [ngModel]=\"selectedBranch()\"\n\n (ngModelChange)=\"onBranchChange($event)\"\n\n valueField=\"name\"\n\n textField=\"title\"\n\n [placeholder]=\"(i18n + '.branch.placeholder' | translate | async) ?? ''\"\n\n ></ax-select-box>\n\n }\n\n </div>\n\n\n\n <!-- ----- Branch-level hook actions ----- -->\n\n @if (branchActions().length) {\n\n <div class=\"flex flex-wrap gap-2\">\n\n @for (action of branchActions(); track action.name ?? action.title) {\n\n <ax-button\n\n look=\"outline\"\n\n color=\"primary\"\n\n size=\"sm\"\n\n [text]=\"(actionTextKey(action) | translate | async) ?? ''\"\n\n (onClick)=\"onActionClick(action)\"\n\n >\n\n @if (action.icon) {\n\n <ax-prefix>\n\n <ax-icon [icon]=\"action.icon\"></ax-icon>\n\n </ax-prefix>\n\n }\n\n </ax-button>\n\n }\n\n </div>\n\n }\n\n\n\n <!-- ----- Commits loading indicator ----- -->\n\n @if (loading()) {\n\n <ax-loading></ax-loading>\n\n }\n\n <!-- ----- Empty commits state ----- -->\n\n @else if (!commits().length) {\n\n <axp-state-message\n\n icon=\"fa-light fa-code-commit\"\n\n [title]=\"i18n + '.empty.title'\"\n\n [description]=\"i18n + '.empty.description'\"\n\n look=\"minimal\"\n\n ></axp-state-message>\n\n }\n\n <!-- ----- Commits list ----- -->\n\n @else {\n\n <div class=\"flex flex-col gap-2\">\n\n @for (commit of commits(); track commit.sha) {\n\n <article class=\"flex flex-wrap items-start justify-between gap-3 rounded-lg border border-surface bg-surface p-3\">\n\n <div class=\"min-w-0 flex-1\">\n\n <div class=\"flex flex-wrap items-center gap-2\">\n\n <ax-badge\n\n size=\"sm\"\n\n look=\"twotone\"\n\n color=\"primary\"\n\n [text]=\"commit.shortSha ?? commit.sha.slice(0, 7)\"\n\n ></ax-badge>\n\n <span class=\"font-medium\">{{ commitMessage(commit) }}</span>\n\n </div>\n\n <div class=\"mt-1 flex flex-wrap gap-x-4 gap-y-1 text-sm opacity-70\">\n\n <span>{{ commit.authorName }}</span>\n\n <span>{{ formatCommittedAt(commit.committedAt) }}</span>\n\n </div>\n\n </div>\n\n <div class=\"flex shrink-0 flex-wrap items-center gap-2\">\n\n <ax-button\n\n look=\"blank\"\n\n size=\"sm\"\n\n [text]=\"(i18n + '.actions.view' | translate | async) ?? ''\"\n\n (onClick)=\"onViewCommit(commit)\"\n\n >\n\n <ax-prefix>\n\n <ax-icon icon=\"fa-light fa-arrow-up-right-from-square\"></ax-icon>\n\n </ax-prefix>\n\n </ax-button>\n\n <!-- ----- Commit-level hook actions ----- -->\n\n @for (action of commitActions(commit.sha); track action.name ?? action.title) {\n\n <ax-button\n\n look=\"outline\"\n\n size=\"sm\"\n\n color=\"primary\"\n\n [text]=\"(actionTextKey(action) | translate | async) ?? ''\"\n\n (onClick)=\"onActionClick(action)\"\n\n >\n\n @if (action.icon) {\n\n <ax-prefix>\n\n <ax-icon [icon]=\"action.icon\"></ax-icon>\n\n </ax-prefix>\n\n }\n\n </ax-button>\n\n }\n\n </div>\n\n </article>\n\n }\n\n </div>\n\n }\n\n</section>\n\n" }]
|
|
470
|
+
}], ctorParameters: () => [], propDecorators: { projectId: [{ type: i0.Input, args: [{ isSignal: true, alias: "projectId", required: true }] }], link: [{ type: i0.Input, args: [{ isSignal: true, alias: "link", required: true }] }] } });
|
|
471
|
+
|
|
472
|
+
//#region ----- Imports -----
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region ----- Constants -----
|
|
475
|
+
const I18N = '@extensions-source-control:pages.repository';
|
|
476
|
+
const PROVIDER_PRESENTATION = {
|
|
477
|
+
[AXE_SC_PROVIDER_GITHUB]: {
|
|
478
|
+
providerId: AXE_SC_PROVIDER_GITHUB,
|
|
479
|
+
icon: 'fa-brands fa-github',
|
|
480
|
+
titleKey: '@extensions-github:integration.display-name',
|
|
481
|
+
},
|
|
482
|
+
[AXE_SC_PROVIDER_GITLAB]: {
|
|
483
|
+
providerId: AXE_SC_PROVIDER_GITLAB,
|
|
484
|
+
icon: 'fa-brands fa-gitlab',
|
|
485
|
+
titleKey: '@extensions-gitlab:integration.display-name',
|
|
486
|
+
},
|
|
487
|
+
};
|
|
488
|
+
//#endregion
|
|
489
|
+
//#region ----- Component -----
|
|
490
|
+
class AXESourceControlPageComponent extends AXPPageLayoutBaseComponent {
|
|
491
|
+
constructor() {
|
|
492
|
+
super();
|
|
493
|
+
//#region ----- Inputs -----
|
|
494
|
+
this.rootContext = input({}, /* @ts-ignore */
|
|
495
|
+
...(ngDevMode ? [{ debugName: "rootContext" }] : /* istanbul ignore next */ []));
|
|
496
|
+
this.pageConfig = input(/* @ts-ignore */
|
|
497
|
+
...(ngDevMode ? [undefined, { debugName: "pageConfig" }] : /* istanbul ignore next */ []));
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region ----- Services & Dependencies -----
|
|
500
|
+
this.i18n = I18N;
|
|
501
|
+
this.linkService = inject(AXESourceControlProjectLinkService);
|
|
502
|
+
this.dialogService = inject(AXDialogService);
|
|
503
|
+
this.translationService = inject(AXTranslationService);
|
|
504
|
+
//#endregion
|
|
505
|
+
//#region ----- State Signals -----
|
|
506
|
+
this.loading = signal(true, /* @ts-ignore */
|
|
507
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
508
|
+
this.linking = signal(false, /* @ts-ignore */
|
|
509
|
+
...(ngDevMode ? [{ debugName: "linking" }] : /* istanbul ignore next */ []));
|
|
510
|
+
this.reposLoading = signal(false, /* @ts-ignore */
|
|
511
|
+
...(ngDevMode ? [{ debugName: "reposLoading" }] : /* istanbul ignore next */ []));
|
|
512
|
+
this.step = signal('provider', /* @ts-ignore */
|
|
513
|
+
...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
514
|
+
this.connections = signal([], /* @ts-ignore */
|
|
515
|
+
...(ngDevMode ? [{ debugName: "connections" }] : /* istanbul ignore next */ []));
|
|
516
|
+
this.selectedConnection = signal(null, /* @ts-ignore */
|
|
517
|
+
...(ngDevMode ? [{ debugName: "selectedConnection" }] : /* istanbul ignore next */ []));
|
|
518
|
+
this.repositories = signal([], /* @ts-ignore */
|
|
519
|
+
...(ngDevMode ? [{ debugName: "repositories" }] : /* istanbul ignore next */ []));
|
|
520
|
+
this.searchQuery = signal('', /* @ts-ignore */
|
|
521
|
+
...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
|
|
522
|
+
this.existingLink = signal(null, /* @ts-ignore */
|
|
523
|
+
...(ngDevMode ? [{ debugName: "existingLink" }] : /* istanbul ignore next */ []));
|
|
524
|
+
this.selectingNewLink = signal(false, /* @ts-ignore */
|
|
525
|
+
...(ngDevMode ? [{ debugName: "selectingNewLink" }] : /* istanbul ignore next */ []));
|
|
526
|
+
//#endregion
|
|
527
|
+
//#region ----- Computed Properties -----
|
|
528
|
+
this.projectId = computed(() => {
|
|
529
|
+
const id = this.rootContext()?.['id'];
|
|
530
|
+
return typeof id === 'string' ? trim(id) : '';
|
|
531
|
+
}, /* @ts-ignore */
|
|
532
|
+
...(ngDevMode ? [{ debugName: "projectId" }] : /* istanbul ignore next */ []));
|
|
533
|
+
this.hasConnections = computed(() => this.connections().length > 0, /* @ts-ignore */
|
|
534
|
+
...(ngDevMode ? [{ debugName: "hasConnections" }] : /* istanbul ignore next */ []));
|
|
535
|
+
this.showLinkedSummary = computed(() => Boolean(this.existingLink()) && !this.selectingNewLink(), /* @ts-ignore */
|
|
536
|
+
...(ngDevMode ? [{ debugName: "showLinkedSummary" }] : /* istanbul ignore next */ []));
|
|
537
|
+
this.linkedRepository = computed(() => (this.showLinkedSummary() ? this.existingLink() : null), /* @ts-ignore */
|
|
538
|
+
...(ngDevMode ? [{ debugName: "linkedRepository" }] : /* istanbul ignore next */ []));
|
|
539
|
+
effect(() => {
|
|
540
|
+
const projectId = this.projectId();
|
|
541
|
+
if (!projectId) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
untracked(() => {
|
|
545
|
+
void this.initialize(projectId);
|
|
546
|
+
});
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
ngOnDestroy() {
|
|
550
|
+
clearTimeout(this.searchDebounce);
|
|
551
|
+
}
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region ----- Event Handlers -----
|
|
554
|
+
providerTitle(connection) {
|
|
555
|
+
const presentation = PROVIDER_PRESENTATION[connection.providerId];
|
|
556
|
+
if (presentation?.titleKey) {
|
|
557
|
+
return presentation.titleKey;
|
|
558
|
+
}
|
|
559
|
+
return defaultTo(connection.displayName, connection.providerId);
|
|
560
|
+
}
|
|
561
|
+
providerIcon(connection) {
|
|
562
|
+
return PROVIDER_PRESENTATION[connection.providerId]?.icon ?? 'fa-light fa-code-branch';
|
|
563
|
+
}
|
|
564
|
+
async onSelectConnection(connection) {
|
|
565
|
+
this.selectedConnection.set(connection);
|
|
566
|
+
this.step.set('repositories');
|
|
567
|
+
this.searchQuery.set('');
|
|
568
|
+
await this.loadRepositories();
|
|
569
|
+
}
|
|
570
|
+
onBackToProviders() {
|
|
571
|
+
if (this.existingLink()) {
|
|
572
|
+
this.onCancelChange();
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
this.step.set('provider');
|
|
576
|
+
this.selectedConnection.set(null);
|
|
577
|
+
this.repositories.set([]);
|
|
578
|
+
this.searchQuery.set('');
|
|
579
|
+
}
|
|
580
|
+
onSearchChange(value) {
|
|
581
|
+
this.searchQuery.set(value ?? '');
|
|
582
|
+
clearTimeout(this.searchDebounce);
|
|
583
|
+
this.searchDebounce = setTimeout(() => {
|
|
584
|
+
void this.loadRepositories();
|
|
585
|
+
}, 350);
|
|
586
|
+
}
|
|
587
|
+
async onSelectRepository(repo) {
|
|
588
|
+
const connection = this.selectedConnection();
|
|
589
|
+
const projectId = this.projectId();
|
|
590
|
+
if (!connection || !projectId) {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const confirmed = await this.confirmLink(repo);
|
|
594
|
+
if (!confirmed) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
this.linking.set(true);
|
|
598
|
+
try {
|
|
599
|
+
const link = await this.linkService.saveLink({ projectId, connection, repo });
|
|
600
|
+
this.existingLink.set(link);
|
|
601
|
+
this.selectingNewLink.set(false);
|
|
602
|
+
this.step.set('provider');
|
|
603
|
+
this.selectedConnection.set(null);
|
|
604
|
+
this.repositories.set([]);
|
|
605
|
+
this.searchQuery.set('');
|
|
606
|
+
await this.showLinkedToast(repo.fullName);
|
|
607
|
+
}
|
|
608
|
+
catch (error) {
|
|
609
|
+
const message = error instanceof Error ? error.message : 'Failed to link repository.';
|
|
610
|
+
this.toastService.show({ color: 'danger', content: message });
|
|
611
|
+
}
|
|
612
|
+
finally {
|
|
613
|
+
this.linking.set(false);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
async onChangeRepository() {
|
|
617
|
+
this.selectingNewLink.set(true);
|
|
618
|
+
this.step.set('provider');
|
|
619
|
+
this.selectedConnection.set(null);
|
|
620
|
+
this.repositories.set([]);
|
|
621
|
+
this.searchQuery.set('');
|
|
622
|
+
}
|
|
623
|
+
onCancelChange() {
|
|
624
|
+
this.selectingNewLink.set(false);
|
|
625
|
+
this.step.set('provider');
|
|
626
|
+
this.selectedConnection.set(null);
|
|
627
|
+
this.repositories.set([]);
|
|
628
|
+
this.searchQuery.set('');
|
|
629
|
+
}
|
|
630
|
+
async onUnlinkRepository() {
|
|
631
|
+
const link = this.existingLink();
|
|
632
|
+
const projectId = this.projectId();
|
|
633
|
+
if (!link || !projectId) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const confirmed = await this.confirmUnlink(link.fullName);
|
|
637
|
+
if (!confirmed) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
this.linking.set(true);
|
|
641
|
+
try {
|
|
642
|
+
await this.linkService.removeLink(projectId);
|
|
643
|
+
this.existingLink.set(null);
|
|
644
|
+
this.step.set('provider');
|
|
645
|
+
const message = await this.translationService.translateAsync(`${I18N}.messages.unlinked`);
|
|
646
|
+
this.toastService.show({ color: 'success', content: message ?? 'Repository unlinked.' });
|
|
647
|
+
}
|
|
648
|
+
catch (error) {
|
|
649
|
+
const message = error instanceof Error ? error.message : 'Failed to unlink repository.';
|
|
650
|
+
this.toastService.show({ color: 'danger', content: message });
|
|
651
|
+
}
|
|
652
|
+
finally {
|
|
653
|
+
this.linking.set(false);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
//#endregion
|
|
657
|
+
//#region ----- Data Loading -----
|
|
658
|
+
async initialize(projectId) {
|
|
659
|
+
this.loading.set(true);
|
|
660
|
+
try {
|
|
661
|
+
const [connections, link] = await Promise.all([
|
|
662
|
+
this.linkService.listConnections(),
|
|
663
|
+
this.linkService.findLinkByProjectId(projectId),
|
|
664
|
+
]);
|
|
665
|
+
this.connections.set(connections);
|
|
666
|
+
this.existingLink.set(link);
|
|
667
|
+
if (!link) {
|
|
668
|
+
this.step.set('provider');
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
const message = error instanceof Error ? error.message : 'Failed to load source-control data.';
|
|
673
|
+
this.toastService.show({ color: 'danger', content: message });
|
|
674
|
+
}
|
|
675
|
+
finally {
|
|
676
|
+
this.loading.set(false);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async loadRepositories() {
|
|
680
|
+
const connection = this.selectedConnection();
|
|
681
|
+
if (!connection) {
|
|
682
|
+
this.repositories.set([]);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
this.reposLoading.set(true);
|
|
686
|
+
try {
|
|
687
|
+
const items = await this.linkService.searchRepositories(connection, this.searchQuery());
|
|
688
|
+
this.repositories.set(items);
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
const message = error instanceof Error ? error.message : 'Failed to search repositories.';
|
|
692
|
+
this.toastService.show({ color: 'danger', content: message });
|
|
693
|
+
this.repositories.set([]);
|
|
694
|
+
}
|
|
695
|
+
finally {
|
|
696
|
+
this.reposLoading.set(false);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region ----- Dialogs -----
|
|
701
|
+
async confirmLink(repo) {
|
|
702
|
+
const title = await this.translationService.translateAsync(`${I18N}.dialogs.link.title`);
|
|
703
|
+
const message = await this.translationService.translateAsync(`${I18N}.dialogs.link.message`, {
|
|
704
|
+
params: { fullName: repo.fullName },
|
|
705
|
+
});
|
|
706
|
+
return Boolean(await this.dialogService.confirm(title ?? 'Link Repository', message ?? 'Link this repository?'));
|
|
707
|
+
}
|
|
708
|
+
async confirmUnlink(fullName) {
|
|
709
|
+
const title = await this.translationService.translateAsync(`${I18N}.dialogs.unlink.title`);
|
|
710
|
+
const message = await this.translationService.translateAsync(`${I18N}.dialogs.unlink.message`, {
|
|
711
|
+
params: { fullName },
|
|
712
|
+
});
|
|
713
|
+
return Boolean(await this.dialogService.confirm(title ?? 'Unlink Repository', message ?? 'Unlink this repository?'));
|
|
714
|
+
}
|
|
715
|
+
async showLinkedToast(fullName) {
|
|
716
|
+
const message = await this.translationService.translateAsync(`${I18N}.messages.linked`, {
|
|
717
|
+
params: { fullName },
|
|
718
|
+
});
|
|
719
|
+
this.toastService.show({ color: 'success', content: message ?? 'Repository linked.' });
|
|
720
|
+
}
|
|
721
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
722
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXESourceControlPageComponent, isStandalone: true, selector: "axe-source-control-page", inputs: { rootContext: { classPropertyName: "rootContext", publicName: "rootContext", isSignal: true, isRequired: false, transformFunction: null }, pageConfig: { classPropertyName: "pageConfig", publicName: "pageConfig", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
723
|
+
{
|
|
724
|
+
provide: AXPPageLayoutBase,
|
|
725
|
+
useExisting: AXESourceControlPageComponent,
|
|
726
|
+
},
|
|
727
|
+
], usesInheritance: true, ngImport: i0, template: "<div class=\"flex w-full flex-col gap-4\">\n <!-- ----- Loading indicator while page data is fetched ----- -->\n @if (loading()) {\n <ax-loading></ax-loading>\n }\n <!-- ----- Linked repository summary ----- -->\n @else if (linkedRepository(); as link) {\n <section class=\"flex flex-col gap-4 rounded-lg border border-surface bg-lighter-surface p-4\">\n <div class=\"flex flex-wrap items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <div class=\"text-sm opacity-70\">\n {{ (i18n + '.linked.summary' | translate | async) ?? '' }}\n </div>\n <div class=\"mt-1 text-lg font-medium\">{{ link.fullName }}</div>\n </div>\n <ax-badge\n size=\"sm\"\n look=\"twotone\"\n color=\"success\"\n [text]=\"(i18n + '.linked.badge' | translate | async) ?? ''\"\n ></ax-badge>\n </div>\n\n <div class=\"grid gap-3 md:grid-cols-2\">\n <div>\n <div class=\"text-sm opacity-70\">{{ (i18n + '.fields.provider' | translate | async) ?? '' }}</div>\n <div class=\"font-medium\">{{ link.providerId }}</div>\n </div>\n <!-- ----- Default branch when configured ----- -->\n @if (link.defaultBranch) {\n <div>\n <div class=\"text-sm opacity-70\">{{ (i18n + '.fields.default-branch' | translate | async) ?? '' }}</div>\n <div class=\"font-medium\">{{ link.defaultBranch }}</div>\n </div>\n }\n <div class=\"md:col-span-2\">\n <div class=\"text-sm opacity-70\">{{ (i18n + '.fields.web-url' | translate | async) ?? '' }}</div>\n <a class=\"font-medium text-primary hover:underline\" [href]=\"link.webUrl\" target=\"_blank\" rel=\"noopener noreferrer\">\n {{ link.webUrl }}\n </a>\n </div>\n </div>\n\n <div class=\"flex flex-wrap gap-2\">\n <ax-button\n look=\"outline\"\n color=\"primary\"\n [disabled]=\"linking()\"\n [text]=\"(i18n + '.actions.change' | translate | async) ?? ''\"\n (onClick)=\"onChangeRepository()\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-arrow-right-arrow-left\"></ax-icon>\n </ax-prefix>\n </ax-button>\n <ax-button\n look=\"outline\"\n color=\"danger\"\n [disabled]=\"linking()\"\n [text]=\"(i18n + '.actions.unlink' | translate | async) ?? ''\"\n (onClick)=\"onUnlinkRepository()\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-link-slash\"></ax-icon>\n </ax-prefix>\n </ax-button>\n </div>\n </section>\n\n <axe-repository-commits-panel [projectId]=\"projectId()\" [link]=\"link\"></axe-repository-commits-panel>\n }\n <!-- ----- No provider connection configured ----- -->\n @else if (!hasConnections()) {\n <section class=\"rounded-lg border border-dashed border-surface bg-lighter-surface p-6 text-center\">\n <ax-icon class=\"mb-3 text-3xl opacity-60\">\n <i class=\"fa-light fa-plug-circle-xmark\"></i>\n </ax-icon>\n <div class=\"font-medium\">{{ (i18n + '.empty.no-connection.title' | translate | async) ?? '' }}</div>\n <div class=\"mt-2 text-sm opacity-70\">{{ (i18n + '.empty.no-connection.description' | translate | async) ?? '' }}</div>\n </section>\n }\n <!-- ----- Provider selection step ----- -->\n @else if (step() === 'provider') {\n <section class=\"flex flex-col gap-3\">\n <div class=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <div class=\"font-medium\">{{ (i18n + '.steps.provider.title' | translate | async) ?? '' }}</div>\n <div class=\"text-sm opacity-70\">{{ (i18n + '.steps.provider.description' | translate | async) ?? '' }}</div>\n </div>\n <!-- ----- Cancel change when a repository is already linked ----- -->\n @if (selectingNewLink()) {\n <ax-button\n look=\"blank\"\n size=\"sm\"\n [disabled]=\"linking()\"\n [text]=\"(i18n + '.actions.back' | translate | async) ?? ''\"\n (onClick)=\"onCancelChange()\"\n ></ax-button>\n }\n </div>\n\n <div class=\"grid gap-3 md:grid-cols-2\">\n @for (connection of connections(); track connection.id) {\n <button\n type=\"button\"\n class=\"flex items-start gap-3 rounded-lg border border-surface bg-lighter-surface p-4 text-start transition hover:border-primary hover:shadow-sm\"\n [disabled]=\"linking()\"\n (click)=\"onSelectConnection(connection)\"\n >\n <ax-icon class=\"mt-0.5 text-2xl\">\n <i [class]=\"providerIcon(connection)\"></i>\n </ax-icon>\n <div class=\"min-w-0 flex-1\">\n <div class=\"font-medium\">\n @if (providerTitle(connection).startsWith('@')) {\n {{ providerTitle(connection) | translate | async }}\n }\n <!-- ----- Fallback when provider title is a plain display name ----- -->\n @else {\n {{ providerTitle(connection) }}\n }\n </div>\n <div class=\"mt-1 truncate text-sm opacity-70\">\n {{ connection.displayName || connection.providerId }}\n </div>\n </div>\n <ax-icon class=\"opacity-50\">\n <i class=\"fa-light fa-chevron-right\"></i>\n </ax-icon>\n </button>\n }\n </div>\n </section>\n }\n <!-- ----- Repository search and selection step ----- -->\n @else {\n <section class=\"flex flex-col gap-4\">\n <div class=\"flex flex-wrap items-center justify-between gap-3\">\n <div class=\"flex min-w-0 items-center gap-3\">\n <ax-button\n look=\"blank\"\n size=\"sm\"\n [disabled]=\"linking()\"\n [text]=\"(i18n + '.actions.back' | translate | async) ?? ''\"\n (onClick)=\"onBackToProviders()\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-arrow-left\"></ax-icon>\n </ax-prefix>\n </ax-button>\n <div class=\"min-w-0\">\n <div class=\"font-medium\">{{ (i18n + '.steps.repositories.title' | translate | async) ?? '' }}</div>\n @if (selectedConnection(); as connection) {\n <div class=\"truncate text-sm opacity-70\">\n @if (providerTitle(connection).startsWith('@')) {\n {{ providerTitle(connection) | translate | async }}\n }\n <!-- ----- Fallback when provider title is a plain display name ----- -->\n @else {\n {{ providerTitle(connection) }}\n }\n \u00B7 {{ connection.displayName || connection.providerId }}\n </div>\n }\n </div>\n </div>\n </div>\n\n <ax-search-box\n [value]=\"searchQuery()\"\n [disabled]=\"linking()\"\n (valueChange)=\"onSearchChange($event)\"\n [placeholder]=\"(i18n + '.steps.repositories.search-placeholder' | translate | async) ?? ''\"\n ></ax-search-box>\n\n <!-- ----- Repository list loading indicator ----- -->\n @if (reposLoading()) {\n <ax-loading></ax-loading>\n }\n <!-- ----- Empty repository search results ----- -->\n @else if (!repositories().length) {\n <div class=\"rounded-lg border border-dashed border-surface p-6 text-center text-sm opacity-70\">\n {{ (i18n + '.steps.repositories.empty' | translate | async) ?? '' }}\n </div>\n }\n <!-- ----- Repository results ----- -->\n @else {\n <div class=\"flex flex-col gap-2\">\n @for (repo of repositories(); track repo.remoteId) {\n <button\n type=\"button\"\n class=\"flex items-start justify-between gap-3 rounded-lg border border-surface bg-lighter-surface p-4 text-start transition hover:border-primary hover:shadow-sm\"\n [disabled]=\"linking()\"\n (click)=\"onSelectRepository(repo)\"\n >\n <div class=\"min-w-0\">\n <div class=\"font-medium\">{{ repo.fullName }}</div>\n <!-- ----- Repository description when available ----- -->\n @if (repo.description) {\n <div class=\"mt-1 line-clamp-2 text-sm opacity-70\">{{ repo.description }}</div>\n }\n <!-- ----- Default branch metadata ----- -->\n @if (repo.defaultBranch) {\n <div class=\"mt-2 text-xs opacity-60\">\n {{ (i18n + '.fields.default-branch' | translate | async) ?? '' }}: {{ repo.defaultBranch }}\n </div>\n }\n </div>\n <ax-icon class=\"shrink-0 opacity-50\">\n <i class=\"fa-light fa-link\"></i>\n </ax-icon>\n </button>\n }\n </div>\n }\n </section>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i2.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i3.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i4.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i4.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i5.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i5$1.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXERepositoryCommitsPanelComponent, selector: "axe-repository-commits-panel", inputs: ["projectId", "link"] }, { kind: "pipe", type: i6$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i7.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
728
|
+
}
|
|
729
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXESourceControlPageComponent, decorators: [{
|
|
730
|
+
type: Component,
|
|
731
|
+
args: [{ selector: 'axe-source-control-page', imports: [
|
|
732
|
+
CommonModule,
|
|
733
|
+
AXBadgeModule,
|
|
734
|
+
AXButtonModule,
|
|
735
|
+
AXDecoratorModule,
|
|
736
|
+
AXLoadingModule,
|
|
737
|
+
AXSearchBoxModule,
|
|
738
|
+
AXTranslationModule,
|
|
739
|
+
AXERepositoryCommitsPanelComponent,
|
|
740
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
741
|
+
{
|
|
742
|
+
provide: AXPPageLayoutBase,
|
|
743
|
+
useExisting: AXESourceControlPageComponent,
|
|
744
|
+
},
|
|
745
|
+
], template: "<div class=\"flex w-full flex-col gap-4\">\n <!-- ----- Loading indicator while page data is fetched ----- -->\n @if (loading()) {\n <ax-loading></ax-loading>\n }\n <!-- ----- Linked repository summary ----- -->\n @else if (linkedRepository(); as link) {\n <section class=\"flex flex-col gap-4 rounded-lg border border-surface bg-lighter-surface p-4\">\n <div class=\"flex flex-wrap items-start justify-between gap-3\">\n <div class=\"min-w-0\">\n <div class=\"text-sm opacity-70\">\n {{ (i18n + '.linked.summary' | translate | async) ?? '' }}\n </div>\n <div class=\"mt-1 text-lg font-medium\">{{ link.fullName }}</div>\n </div>\n <ax-badge\n size=\"sm\"\n look=\"twotone\"\n color=\"success\"\n [text]=\"(i18n + '.linked.badge' | translate | async) ?? ''\"\n ></ax-badge>\n </div>\n\n <div class=\"grid gap-3 md:grid-cols-2\">\n <div>\n <div class=\"text-sm opacity-70\">{{ (i18n + '.fields.provider' | translate | async) ?? '' }}</div>\n <div class=\"font-medium\">{{ link.providerId }}</div>\n </div>\n <!-- ----- Default branch when configured ----- -->\n @if (link.defaultBranch) {\n <div>\n <div class=\"text-sm opacity-70\">{{ (i18n + '.fields.default-branch' | translate | async) ?? '' }}</div>\n <div class=\"font-medium\">{{ link.defaultBranch }}</div>\n </div>\n }\n <div class=\"md:col-span-2\">\n <div class=\"text-sm opacity-70\">{{ (i18n + '.fields.web-url' | translate | async) ?? '' }}</div>\n <a class=\"font-medium text-primary hover:underline\" [href]=\"link.webUrl\" target=\"_blank\" rel=\"noopener noreferrer\">\n {{ link.webUrl }}\n </a>\n </div>\n </div>\n\n <div class=\"flex flex-wrap gap-2\">\n <ax-button\n look=\"outline\"\n color=\"primary\"\n [disabled]=\"linking()\"\n [text]=\"(i18n + '.actions.change' | translate | async) ?? ''\"\n (onClick)=\"onChangeRepository()\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-arrow-right-arrow-left\"></ax-icon>\n </ax-prefix>\n </ax-button>\n <ax-button\n look=\"outline\"\n color=\"danger\"\n [disabled]=\"linking()\"\n [text]=\"(i18n + '.actions.unlink' | translate | async) ?? ''\"\n (onClick)=\"onUnlinkRepository()\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-link-slash\"></ax-icon>\n </ax-prefix>\n </ax-button>\n </div>\n </section>\n\n <axe-repository-commits-panel [projectId]=\"projectId()\" [link]=\"link\"></axe-repository-commits-panel>\n }\n <!-- ----- No provider connection configured ----- -->\n @else if (!hasConnections()) {\n <section class=\"rounded-lg border border-dashed border-surface bg-lighter-surface p-6 text-center\">\n <ax-icon class=\"mb-3 text-3xl opacity-60\">\n <i class=\"fa-light fa-plug-circle-xmark\"></i>\n </ax-icon>\n <div class=\"font-medium\">{{ (i18n + '.empty.no-connection.title' | translate | async) ?? '' }}</div>\n <div class=\"mt-2 text-sm opacity-70\">{{ (i18n + '.empty.no-connection.description' | translate | async) ?? '' }}</div>\n </section>\n }\n <!-- ----- Provider selection step ----- -->\n @else if (step() === 'provider') {\n <section class=\"flex flex-col gap-3\">\n <div class=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <div class=\"font-medium\">{{ (i18n + '.steps.provider.title' | translate | async) ?? '' }}</div>\n <div class=\"text-sm opacity-70\">{{ (i18n + '.steps.provider.description' | translate | async) ?? '' }}</div>\n </div>\n <!-- ----- Cancel change when a repository is already linked ----- -->\n @if (selectingNewLink()) {\n <ax-button\n look=\"blank\"\n size=\"sm\"\n [disabled]=\"linking()\"\n [text]=\"(i18n + '.actions.back' | translate | async) ?? ''\"\n (onClick)=\"onCancelChange()\"\n ></ax-button>\n }\n </div>\n\n <div class=\"grid gap-3 md:grid-cols-2\">\n @for (connection of connections(); track connection.id) {\n <button\n type=\"button\"\n class=\"flex items-start gap-3 rounded-lg border border-surface bg-lighter-surface p-4 text-start transition hover:border-primary hover:shadow-sm\"\n [disabled]=\"linking()\"\n (click)=\"onSelectConnection(connection)\"\n >\n <ax-icon class=\"mt-0.5 text-2xl\">\n <i [class]=\"providerIcon(connection)\"></i>\n </ax-icon>\n <div class=\"min-w-0 flex-1\">\n <div class=\"font-medium\">\n @if (providerTitle(connection).startsWith('@')) {\n {{ providerTitle(connection) | translate | async }}\n }\n <!-- ----- Fallback when provider title is a plain display name ----- -->\n @else {\n {{ providerTitle(connection) }}\n }\n </div>\n <div class=\"mt-1 truncate text-sm opacity-70\">\n {{ connection.displayName || connection.providerId }}\n </div>\n </div>\n <ax-icon class=\"opacity-50\">\n <i class=\"fa-light fa-chevron-right\"></i>\n </ax-icon>\n </button>\n }\n </div>\n </section>\n }\n <!-- ----- Repository search and selection step ----- -->\n @else {\n <section class=\"flex flex-col gap-4\">\n <div class=\"flex flex-wrap items-center justify-between gap-3\">\n <div class=\"flex min-w-0 items-center gap-3\">\n <ax-button\n look=\"blank\"\n size=\"sm\"\n [disabled]=\"linking()\"\n [text]=\"(i18n + '.actions.back' | translate | async) ?? ''\"\n (onClick)=\"onBackToProviders()\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light fa-arrow-left\"></ax-icon>\n </ax-prefix>\n </ax-button>\n <div class=\"min-w-0\">\n <div class=\"font-medium\">{{ (i18n + '.steps.repositories.title' | translate | async) ?? '' }}</div>\n @if (selectedConnection(); as connection) {\n <div class=\"truncate text-sm opacity-70\">\n @if (providerTitle(connection).startsWith('@')) {\n {{ providerTitle(connection) | translate | async }}\n }\n <!-- ----- Fallback when provider title is a plain display name ----- -->\n @else {\n {{ providerTitle(connection) }}\n }\n \u00B7 {{ connection.displayName || connection.providerId }}\n </div>\n }\n </div>\n </div>\n </div>\n\n <ax-search-box\n [value]=\"searchQuery()\"\n [disabled]=\"linking()\"\n (valueChange)=\"onSearchChange($event)\"\n [placeholder]=\"(i18n + '.steps.repositories.search-placeholder' | translate | async) ?? ''\"\n ></ax-search-box>\n\n <!-- ----- Repository list loading indicator ----- -->\n @if (reposLoading()) {\n <ax-loading></ax-loading>\n }\n <!-- ----- Empty repository search results ----- -->\n @else if (!repositories().length) {\n <div class=\"rounded-lg border border-dashed border-surface p-6 text-center text-sm opacity-70\">\n {{ (i18n + '.steps.repositories.empty' | translate | async) ?? '' }}\n </div>\n }\n <!-- ----- Repository results ----- -->\n @else {\n <div class=\"flex flex-col gap-2\">\n @for (repo of repositories(); track repo.remoteId) {\n <button\n type=\"button\"\n class=\"flex items-start justify-between gap-3 rounded-lg border border-surface bg-lighter-surface p-4 text-start transition hover:border-primary hover:shadow-sm\"\n [disabled]=\"linking()\"\n (click)=\"onSelectRepository(repo)\"\n >\n <div class=\"min-w-0\">\n <div class=\"font-medium\">{{ repo.fullName }}</div>\n <!-- ----- Repository description when available ----- -->\n @if (repo.description) {\n <div class=\"mt-1 line-clamp-2 text-sm opacity-70\">{{ repo.description }}</div>\n }\n <!-- ----- Default branch metadata ----- -->\n @if (repo.defaultBranch) {\n <div class=\"mt-2 text-xs opacity-60\">\n {{ (i18n + '.fields.default-branch' | translate | async) ?? '' }}: {{ repo.defaultBranch }}\n </div>\n }\n </div>\n <ax-icon class=\"shrink-0 opacity-50\">\n <i class=\"fa-light fa-link\"></i>\n </ax-icon>\n </button>\n }\n </div>\n }\n </section>\n }\n</div>\n" }]
|
|
746
|
+
}], ctorParameters: () => [], propDecorators: { rootContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootContext", required: false }] }], pageConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageConfig", required: false }] }] } });
|
|
747
|
+
|
|
748
|
+
export { AXESourceControlPageComponent };
|
|
749
|
+
//# sourceMappingURL=acorex-platform-extension-source-control-core-client-source-control-page.component-DOALkPKo.mjs.map
|