@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.
@@ -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