@anymux/connect 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { observer } from 'mobx-react-lite';
3
3
  import type { CredentialFormModel } from '../models/CredentialFormModel';
4
+ import { isS3Compatible } from '../models/CredentialFormModel';
4
5
 
5
6
  interface CredentialFormProps {
6
7
  model: CredentialFormModel;
@@ -19,7 +20,26 @@ export const CredentialForm: React.FC<CredentialFormProps> = observer(({ model,
19
20
  'w-full rounded-md border border-border px-3 py-2 text-base sm:text-sm bg-card text-foreground focus:border-ring focus:outline-none focus:ring-1 focus:ring-ring';
20
21
  const labelClass = 'block text-sm font-medium text-foreground mb-1';
21
22
 
22
- const title = model.serviceType === 's3' ? 'S3 Credentials' : model.serviceType === 'webdav' ? 'WebDAV Credentials' : model.serviceType === 'icloud' ? 'iCloud Credentials' : 'Gitea Credentials';
23
+ const titleMap: Record<string, string> = {
24
+ 's3': 'Amazon S3 Credentials',
25
+ 'backblaze-b2': 'Backblaze B2 Credentials',
26
+ 'cloudflare-r2': 'Cloudflare R2 Credentials',
27
+ 'wasabi': 'Wasabi Credentials',
28
+ 'webdav': 'WebDAV Credentials',
29
+ 'icloud': 'iCloud Credentials',
30
+ 'gitea': 'Gitea Credentials',
31
+ 'unsplash': 'Unsplash API Key',
32
+ 'pexels': 'Pexels API Key',
33
+ 'imgur': 'Imgur Client ID',
34
+ };
35
+ const title = titleMap[model.serviceType] || 'Credentials';
36
+
37
+ const endpointPlaceholder: Record<string, string> = {
38
+ 's3': 'https://s3.amazonaws.com',
39
+ 'backblaze-b2': 'https://s3.us-west-004.backblazeb2.com',
40
+ 'cloudflare-r2': 'https://<account-id>.r2.cloudflarestorage.com',
41
+ 'wasabi': 'https://s3.wasabisys.com',
42
+ };
23
43
 
24
44
  return (
25
45
  <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50" onClick={() => model.closeForm()}>
@@ -31,7 +51,7 @@ export const CredentialForm: React.FC<CredentialFormProps> = observer(({ model,
31
51
 
32
52
  <form onSubmit={handleSubmit} className="flex flex-col flex-1 min-h-0">
33
53
  <div className="flex-1 overflow-y-auto px-4 sm:px-6 space-y-3">
34
- {model.serviceType === 's3' ? (
54
+ {isS3Compatible(model.serviceType) ? (
35
55
  <>
36
56
  <div>
37
57
  <label className={labelClass}>Access Key ID</label>
@@ -74,13 +94,14 @@ export const CredentialForm: React.FC<CredentialFormProps> = observer(({ model,
74
94
  />
75
95
  </div>
76
96
  <div>
77
- <label className={labelClass}>Endpoint (optional)</label>
97
+ <label className={labelClass}>Endpoint{model.serviceType === 's3' ? ' (optional)' : ''}</label>
78
98
  <input
79
99
  type="text"
100
+ required={model.serviceType !== 's3'}
80
101
  className={inputClass}
81
102
  value={model.endpoint}
82
103
  onChange={(e) => model.setField('endpoint', e.target.value)}
83
- placeholder="https://s3.amazonaws.com"
104
+ placeholder={endpointPlaceholder[model.serviceType] || ''}
84
105
  />
85
106
  </div>
86
107
  </>
@@ -155,6 +176,41 @@ export const CredentialForm: React.FC<CredentialFormProps> = observer(({ model,
155
176
  {' '}under Sign-In and Security &rarr; App-Specific Passwords.
156
177
  </p>
157
178
  </>
179
+ ) : model.serviceType === 'unsplash' || model.serviceType === 'pexels' || model.serviceType === 'imgur' ? (
180
+ <>
181
+ <div>
182
+ <label className={labelClass}>{model.serviceType === 'imgur' ? 'Client ID' : 'API Key'}</label>
183
+ <input
184
+ type="password"
185
+ required
186
+ className={inputClass}
187
+ value={model.apiKey}
188
+ onChange={(e) => model.setField('apiKey', e.target.value)}
189
+ placeholder={model.serviceType === 'unsplash' ? 'Unsplash Access Key' : model.serviceType === 'pexels' ? 'Pexels API Key' : 'Imgur Client ID'}
190
+ />
191
+ </div>
192
+ <p className="text-xs text-muted-foreground">
193
+ {model.serviceType === 'unsplash' ? (
194
+ <>Get a free API key at{' '}
195
+ <a href="https://unsplash.com/developers" target="_blank" rel="noopener noreferrer" className="underline text-primary hover:text-primary/80">
196
+ unsplash.com/developers
197
+ </a>
198
+ </>
199
+ ) : model.serviceType === 'pexels' ? (
200
+ <>Get a free API key at{' '}
201
+ <a href="https://www.pexels.com/api/" target="_blank" rel="noopener noreferrer" className="underline text-primary hover:text-primary/80">
202
+ pexels.com/api
203
+ </a>
204
+ </>
205
+ ) : (
206
+ <>Register an app at{' '}
207
+ <a href="https://api.imgur.com/oauth2/addclient" target="_blank" rel="noopener noreferrer" className="underline text-primary hover:text-primary/80">
208
+ api.imgur.com
209
+ </a>
210
+ </>
211
+ )}
212
+ </p>
213
+ </>
158
214
  ) : (
159
215
  <>
160
216
  <div>
@@ -28,21 +28,21 @@ import type {
28
28
  import { GitRepoBrowserModel, type SidebarTab } from '../models/GitRepoBrowserModel';
29
29
  import { GitHostBrowserModel } from '../models/GitHostBrowserModel';
30
30
 
31
- // Lazy-load the heavy FileBrowser from fs-ui
31
+ // Lazy-load the heavy FileBrowser from ui-kit
32
32
  const FileBrowser = React.lazy(() =>
33
- import('@anymux/fs-ui').then((m) => ({ default: m.FileBrowser }))
33
+ import('@anymux/ui-kit/file-browser').then((m) => ({ default: m.FileBrowser }))
34
34
  );
35
35
 
36
36
  const CommitList = React.lazy(() =>
37
- import('@anymux/fs-ui').then((m) => ({ default: m.CommitList }))
37
+ import('@anymux/ui-kit/git').then((m) => ({ default: m.CommitList }))
38
38
  );
39
39
 
40
40
  const BranchList = React.lazy(() =>
41
- import('@anymux/fs-ui').then((m) => ({ default: m.BranchList }))
41
+ import('@anymux/ui-kit/git').then((m) => ({ default: m.BranchList }))
42
42
  );
43
43
 
44
44
  const DiffViewer = React.lazy(() =>
45
- import('@anymux/fs-ui').then((m) => ({ default: m.DiffViewer }))
45
+ import('@anymux/ui-kit/git').then((m) => ({ default: m.DiffViewer }))
46
46
  );
47
47
 
48
48
  // ---- Helpers ----
package/src/index.ts CHANGED
@@ -39,6 +39,13 @@ export { ActionNotificationModel } from './models/ActionNotificationModel';
39
39
  export type { ActionRecord, ActionType } from './models/ActionNotificationModel';
40
40
  export { CredentialFormModel } from './models/CredentialFormModel';
41
41
  export type { CredentialServiceType } from './models/CredentialFormModel';
42
+ export { isS3Compatible, S3_COMPATIBLE_TYPES } from './models/CredentialFormModel';
43
+ export { backblazeB2Service } from './registry/services/backblaze-b2';
44
+ export { cloudflareR2Service } from './registry/services/cloudflare-r2';
45
+ export { wasabiService } from './registry/services/wasabi';
46
+ export { unsplashService } from './registry/services/unsplash';
47
+ export { pexelsService } from './registry/services/pexels';
48
+ export { imgurService } from './registry/services/imgur';
42
49
 
43
50
  // Utils
44
51
  export { showActionToast, showErrorToast, showInfoToast } from './utils/action-toast';
@@ -6,6 +6,9 @@ import type { IUserProfile } from '../types/user-profile';
6
6
  import { TokenManager } from '../auth/token-manager';
7
7
  import { serviceRegistry } from '../registry/service-registry';
8
8
 
9
+ /** Auth providers that use stored credentials instead of OAuth */
10
+ const CREDENTIAL_AUTH_PROVIDERS = new Set(['s3', 'backblaze-b2', 'cloudflare-r2', 'wasabi', 'webdav', 'gitea', 'icloud', 'unsplash', 'pexels', 'imgur', 'browser-fs', 'indexeddb']);
11
+
9
12
  const STORAGE_KEY = 'anymux-connect-connections';
10
13
  const USER_PROFILES_STORAGE_KEY = 'anymux-connect-user-profiles';
11
14
 
@@ -68,7 +71,7 @@ export class ConnectionManagerModel {
68
71
  }
69
72
  // Mark unconfigured OAuth services as not_configured
70
73
  for (const service of serviceRegistry.getAll()) {
71
- if (service.authProvider === 's3' || service.authProvider === 'webdav' || service.authProvider === 'gitea' || service.authProvider === 'browser-fs' || service.authProvider === 'indexeddb') continue;
74
+ if (CREDENTIAL_AUTH_PROVIDERS.has(service.authProvider)) continue;
72
75
  if (!this.configuredProviders.has(service.authProvider) && !this.isConnected(service.id)) {
73
76
  this.connections.set(service.id, {
74
77
  serviceId: service.id,
@@ -359,7 +362,7 @@ export class ConnectionManagerModel {
359
362
  return null;
360
363
  }
361
364
  // Skip credential-based services — their tokens don't expire
362
- if (['s3', 'webdav', 'gitea', 'icloud', 'browser-fs', 'indexeddb'].includes(service.authProvider)) {
365
+ if (CREDENTIAL_AUTH_PROVIDERS.has(service.authProvider)) {
363
366
  return this.tokenManager.getToken(serviceId);
364
367
  }
365
368
  const oldToken = this.tokenManager.getToken(serviceId);
@@ -399,7 +402,7 @@ export class ConnectionManagerModel {
399
402
  const service = serviceRegistry.get(serviceId);
400
403
  if (!service) return false;
401
404
  // S3/WebDAV/Gitea don't use OAuth scopes — treat all as granted
402
- if (service.authProvider === 's3' || service.authProvider === 'webdav' || service.authProvider === 'gitea' || service.authProvider === 'browser-fs' || service.authProvider === 'indexeddb') return true;
405
+ if (CREDENTIAL_AUTH_PROVIDERS.has(service.authProvider)) return true;
403
406
  const requiredScopes = service.scopes[capabilityId];
404
407
  if (!requiredScopes || requiredScopes.length === 0) return false;
405
408
  // If scopes haven't been loaded yet, assume granted to avoid a flash warning
@@ -1,6 +1,13 @@
1
1
  import { makeAutoObservable } from 'mobx';
2
2
 
3
- export type CredentialServiceType = 's3' | 'webdav' | 'gitea' | 'icloud';
3
+ export type CredentialServiceType = 's3' | 'backblaze-b2' | 'cloudflare-r2' | 'wasabi' | 'webdav' | 'gitea' | 'icloud' | 'unsplash' | 'pexels' | 'imgur';
4
+
5
+ /** S3-compatible service types that share the same credential fields */
6
+ export const S3_COMPATIBLE_TYPES: readonly CredentialServiceType[] = ['s3', 'backblaze-b2', 'cloudflare-r2', 'wasabi'] as const;
7
+
8
+ export function isS3Compatible(type: CredentialServiceType): boolean {
9
+ return (S3_COMPATIBLE_TYPES as readonly string[]).includes(type);
10
+ }
4
11
 
5
12
  export class CredentialFormModel {
6
13
  open = false;
@@ -28,6 +35,9 @@ export class CredentialFormModel {
28
35
  email = '';
29
36
  appPassword = '';
30
37
 
38
+ // API key services (Unsplash, Pexels)
39
+ apiKey = '';
40
+
31
41
  constructor() {
32
42
  makeAutoObservable(this);
33
43
  }
@@ -51,17 +61,17 @@ export class CredentialFormModel {
51
61
 
52
62
  /** Serialize current form state to JSON credential string */
53
63
  serialize(): string {
64
+ if (isS3Compatible(this.serviceType)) {
65
+ const creds: Record<string, string> = {
66
+ accessKeyId: this.accessKeyId,
67
+ secretAccessKey: this.secretAccessKey,
68
+ region: this.region,
69
+ bucket: this.bucket,
70
+ };
71
+ if (this.endpoint) creds.endpoint = this.endpoint;
72
+ return JSON.stringify(creds);
73
+ }
54
74
  switch (this.serviceType) {
55
- case 's3': {
56
- const creds: Record<string, string> = {
57
- accessKeyId: this.accessKeyId,
58
- secretAccessKey: this.secretAccessKey,
59
- region: this.region,
60
- bucket: this.bucket,
61
- };
62
- if (this.endpoint) creds.endpoint = this.endpoint;
63
- return JSON.stringify(creds);
64
- }
65
75
  case 'webdav':
66
76
  return JSON.stringify({
67
77
  url: this.url,
@@ -82,6 +92,12 @@ export class CredentialFormModel {
82
92
  email: this.email,
83
93
  appPassword: this.appPassword,
84
94
  });
95
+ case 'unsplash':
96
+ return JSON.stringify({ accessKey: this.apiKey });
97
+ case 'pexels':
98
+ return JSON.stringify({ apiKey: this.apiKey });
99
+ case 'imgur':
100
+ return JSON.stringify({ clientId: this.apiKey });
85
101
  }
86
102
  }
87
103
 
@@ -99,6 +115,7 @@ export class CredentialFormModel {
99
115
  this.repo = '';
100
116
  this.email = '';
101
117
  this.appPassword = '';
118
+ this.apiKey = '';
102
119
  }
103
120
 
104
121
  private applyPrefill(values: Record<string, string>): void {
@@ -10,6 +10,12 @@ import { giteaService } from './services/gitea';
10
10
  import { browserFsService } from './services/browser-fs';
11
11
  import { indexeddbService } from './services/indexeddb';
12
12
  import { boxService } from './services/box';
13
+ import { backblazeB2Service } from './services/backblaze-b2';
14
+ import { cloudflareR2Service } from './services/cloudflare-r2';
15
+ import { wasabiService } from './services/wasabi';
16
+ import { unsplashService } from './services/unsplash';
17
+ import { pexelsService } from './services/pexels';
18
+ import { imgurService } from './services/imgur';
13
19
 
14
20
  const services = new Map<string, ServiceDefinition>();
15
21
 
@@ -28,6 +34,12 @@ register(giteaService);
28
34
  register(browserFsService);
29
35
  register(indexeddbService);
30
36
  register(boxService);
37
+ register(backblazeB2Service);
38
+ register(cloudflareR2Service);
39
+ register(wasabiService);
40
+ register(unsplashService);
41
+ register(pexelsService);
42
+ register(imgurService);
31
43
 
32
44
  export const serviceRegistry = {
33
45
  get(id: string): ServiceDefinition | undefined {
@@ -0,0 +1,21 @@
1
+ import type { ServiceDefinition } from '../../types/service';
2
+
3
+ export const backblazeB2Service: ServiceDefinition = {
4
+ id: 'backblaze-b2',
5
+ name: 'Backblaze B2',
6
+ icon: 'HardDrive',
7
+ color: '#E21E29',
8
+ authProvider: 'backblaze-b2',
9
+ capabilities: [
10
+ { id: 'file-system', supported: false },
11
+ { id: 'object-storage', supported: true },
12
+ { id: 'git-repo', supported: false },
13
+ { id: 'git-host', supported: false },
14
+ { id: 'media', supported: false },
15
+ { id: 'contacts', supported: false },
16
+ { id: 'calendar', supported: false },
17
+ ],
18
+ scopes: {
19
+ 'object-storage': ['s3:GetObject', 's3:PutObject', 's3:ListBucket'],
20
+ },
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { ServiceDefinition } from '../../types/service';
2
+
3
+ export const cloudflareR2Service: ServiceDefinition = {
4
+ id: 'cloudflare-r2',
5
+ name: 'Cloudflare R2',
6
+ icon: 'Cloud',
7
+ color: '#F38020',
8
+ authProvider: 'cloudflare-r2',
9
+ capabilities: [
10
+ { id: 'file-system', supported: false },
11
+ { id: 'object-storage', supported: true },
12
+ { id: 'git-repo', supported: false },
13
+ { id: 'git-host', supported: false },
14
+ { id: 'media', supported: false },
15
+ { id: 'contacts', supported: false },
16
+ { id: 'calendar', supported: false },
17
+ ],
18
+ scopes: {
19
+ 'object-storage': ['s3:GetObject', 's3:PutObject', 's3:ListBucket'],
20
+ },
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { ServiceDefinition } from '../../types/service';
2
+
3
+ export const imgurService: ServiceDefinition = {
4
+ id: 'imgur',
5
+ name: 'Imgur',
6
+ icon: 'ImagePlus',
7
+ color: '#1BB76E',
8
+ authProvider: 'imgur',
9
+ capabilities: [
10
+ { id: 'file-system', supported: false },
11
+ { id: 'object-storage', supported: false },
12
+ { id: 'git-repo', supported: false },
13
+ { id: 'git-host', supported: false },
14
+ { id: 'media', supported: true },
15
+ { id: 'contacts', supported: false },
16
+ { id: 'calendar', supported: false },
17
+ ],
18
+ scopes: {
19
+ 'media': ['read'],
20
+ },
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { ServiceDefinition } from '../../types/service';
2
+
3
+ export const pexelsService: ServiceDefinition = {
4
+ id: 'pexels',
5
+ name: 'Pexels',
6
+ icon: 'Camera',
7
+ color: '#05A081',
8
+ authProvider: 'pexels',
9
+ capabilities: [
10
+ { id: 'file-system', supported: false },
11
+ { id: 'object-storage', supported: false },
12
+ { id: 'git-repo', supported: false },
13
+ { id: 'git-host', supported: false },
14
+ { id: 'media', supported: true },
15
+ { id: 'contacts', supported: false },
16
+ { id: 'calendar', supported: false },
17
+ ],
18
+ scopes: {
19
+ 'media': ['read'],
20
+ },
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { ServiceDefinition } from '../../types/service';
2
+
3
+ export const unsplashService: ServiceDefinition = {
4
+ id: 'unsplash',
5
+ name: 'Unsplash',
6
+ icon: 'Image',
7
+ color: '#111111',
8
+ authProvider: 'unsplash',
9
+ capabilities: [
10
+ { id: 'file-system', supported: false },
11
+ { id: 'object-storage', supported: false },
12
+ { id: 'git-repo', supported: false },
13
+ { id: 'git-host', supported: false },
14
+ { id: 'media', supported: true },
15
+ { id: 'contacts', supported: false },
16
+ { id: 'calendar', supported: false },
17
+ ],
18
+ scopes: {
19
+ 'media': ['public'],
20
+ },
21
+ };
@@ -0,0 +1,21 @@
1
+ import type { ServiceDefinition } from '../../types/service';
2
+
3
+ export const wasabiService: ServiceDefinition = {
4
+ id: 'wasabi',
5
+ name: 'Wasabi',
6
+ icon: 'Database',
7
+ color: '#56B847',
8
+ authProvider: 'wasabi',
9
+ capabilities: [
10
+ { id: 'file-system', supported: false },
11
+ { id: 'object-storage', supported: true },
12
+ { id: 'git-repo', supported: false },
13
+ { id: 'git-host', supported: false },
14
+ { id: 'media', supported: false },
15
+ { id: 'contacts', supported: false },
16
+ { id: 'calendar', supported: false },
17
+ ],
18
+ scopes: {
19
+ 'object-storage': ['s3:GetObject', 's3:PutObject', 's3:ListBucket'],
20
+ },
21
+ };
@@ -1,8 +1,8 @@
1
1
  declare module '@anymux/object-ui' {
2
2
  import type { FC } from 'react';
3
- import type { IMediaProvider } from '@anymux/object-ui/types/media';
4
- import type { IContactProvider } from '@anymux/object-ui/types/contact';
5
- import type { ICalendarProvider } from '@anymux/object-ui/types/calendar';
3
+ import type { IMediaProvider } from '@anymux/ui-kit/media';
4
+ import type { IContactProvider } from '@anymux/ui-kit/contacts';
5
+ import type { ICalendarProvider } from '@anymux/ui-kit/calendar';
6
6
 
7
7
  export const MediaBrowserDemo: FC;
8
8
  export const ContactBrowserDemo: FC;
@@ -25,9 +25,9 @@ declare module '@anymux/object-ui' {
25
25
 
26
26
  declare module '@anymux/google-drive' {
27
27
  import type { IFileSystem } from '@anymux/file-system';
28
- import type { IMediaProvider } from '@anymux/object-ui/types/media';
29
- import type { IContactProvider } from '@anymux/object-ui/types/contact';
30
- import type { ICalendarProvider } from '@anymux/object-ui/types/calendar';
28
+ import type { IMediaProvider } from '@anymux/ui-kit/media';
29
+ import type { IContactProvider } from '@anymux/ui-kit/contacts';
30
+ import type { ICalendarProvider } from '@anymux/ui-kit/calendar';
31
31
 
32
32
  export class GoogleDriveFileSystem implements IFileSystem {
33
33
  constructor(config: { accessToken: string; rootFolderId?: string });
@@ -113,8 +113,8 @@ declare module '@anymux/webdav' {
113
113
  }
114
114
 
115
115
  declare module '@anymux/icloud' {
116
- import type { ICalendarProvider } from '@anymux/object-ui/types/calendar';
117
- import type { IContactProvider } from '@anymux/object-ui/types/contact';
116
+ import type { ICalendarProvider } from '@anymux/ui-kit/calendar';
117
+ import type { IContactProvider } from '@anymux/ui-kit/contacts';
118
118
 
119
119
  export interface ICloudCredentials {
120
120
  email: string;
@@ -132,7 +132,7 @@ declare module '@anymux/icloud' {
132
132
  }
133
133
 
134
134
  declare module '@anymux/flickr' {
135
- import type { IMediaProvider } from '@anymux/object-ui/types/media';
135
+ import type { IMediaProvider } from '@anymux/ui-kit/media';
136
136
 
137
137
  export interface FlickrClientConfig {
138
138
  apiKey: string;
@@ -1 +0,0 @@
1
- {"version":3,"file":"GitBrowser-BLgTNQyd.js","names":["gitRepo: IGitRepo","_gitHost: IGitHost | undefined","createFileSystem: (branch: string) => Promise<IFileSystem>","onError?: (err: { message: string }) => void","fs: IFileSystem","ref: string","commitList: GitCommit[]","commit: GitCommit","entries: GitDiffEntry[]","data: PullRequest[]","data: Issue[]","host: IGitHost","tab: SidebarTab","branch: GitBranch","gitHost: IGitHost","data: PullRequest[]","data: Issue[]","tab: HostTab","model: ActionNotificationModel","type: ActionType","description: string","options?: {\n undo?: () => Promise<void>;\n /** Duration in ms before auto-dismiss. Default 5000. */\n duration?: number;\n }","message: string","context?: string","date: Date","e: MouseEvent","items: RefItem[]","GitRepoBrowser: React.FC<GitRepoBrowserProps>","type: 'success' | 'error' | 'warning'","message: string","sidebarTabs: Array<{ id: SidebarTab; label: string; icon: React.ReactNode }>","status: string","GitHostBrowser: React.FC<GitHostBrowserProps>","tabDefs: Array<{ id: 'prs' | 'issues'; label: string; icon: React.ReactNode }>"],"sources":["../src/models/GitRepoBrowserModel.ts","../src/models/GitHostBrowserModel.ts","../src/utils/action-toast.ts","../src/components/GitBrowser.tsx"],"sourcesContent":["import { makeAutoObservable, flow } from 'mobx';\nimport type {\n IGitRepo,\n IGitHost,\n GitBranch,\n GitTag,\n GitCommit,\n GitDiffEntry,\n IFileSystem,\n PullRequest,\n Issue,\n} from '@anymux/file-system';\n\nexport type SidebarTab = 'files' | 'branches' | 'commits' | 'prs' | 'issues';\n\nconst HOST_PAGE_SIZE = 25;\nconst COMMITS_PAGE_SIZE = 25;\n\nexport class GitRepoBrowserModel {\n // --- Core repo state ---\n branches: GitBranch[] = [];\n tags: GitTag[] = [];\n currentRef = '';\n fileSystem: IFileSystem | null = null;\n loading = true;\n error: string | null = null;\n\n // --- Tab state ---\n activeTab: SidebarTab = 'files';\n\n // --- Commits state ---\n commits: GitCommit[] = [];\n commitsLoading = false;\n commitsPage = 0;\n hasMoreCommits = false;\n\n // --- Diff state ---\n selectedCommitSha: string | undefined = undefined;\n diffEntries: GitDiffEntry[] = [];\n diffLoading = false;\n\n // --- Host data (PRs / Issues) ---\n prs: PullRequest[] = [];\n issues: Issue[] = [];\n hostLoading = false;\n hostError: string | null = null;\n prPage = 0;\n issuePage = 0;\n hasMorePrs = false;\n hasMoreIssues = false;\n\n constructor(\n private gitRepo: IGitRepo,\n private _gitHost: IGitHost | undefined,\n private createFileSystem: (branch: string) => Promise<IFileSystem>,\n private onError?: (err: { message: string }) => void,\n ) {\n makeAutoObservable(this, {\n // Mark constructor deps as non-observable (they are stable references)\n });\n }\n\n /** Update the gitHost adapter (e.g. when loaded asynchronously after construction) */\n setGitHost(host: IGitHost): void {\n this._gitHost = host;\n }\n\n // --- Computed ---\n\n get headSha(): string | undefined {\n return this.branches.find((b) => b.name === this.currentRef)?.sha;\n }\n\n get hasGitHost(): boolean {\n return !!this._gitHost;\n }\n\n // --- Actions ---\n\n setActiveTab(tab: SidebarTab): void {\n this.activeTab = tab;\n\n // Trigger data loading for the newly active tab\n if (tab === 'commits' && this.currentRef) {\n this.loadCommits();\n } else if (tab === 'prs' && this._gitHost) {\n this.loadPRs();\n } else if (tab === 'issues' && this._gitHost) {\n this.loadIssues();\n }\n }\n\n // --- Async flows ---\n\n initialize = flow(function* (this: GitRepoBrowserModel) {\n this.loading = true;\n this.error = null;\n\n try {\n const [branchList, tagList]: [GitBranch[], GitTag[]] = yield Promise.all([\n this.gitRepo.listBranches(),\n this.gitRepo.listTags().catch(() => [] as GitTag[]),\n ]);\n\n this.branches = branchList;\n this.tags = tagList;\n\n // Pick default branch\n const defaultBranch = branchList.find((b) => b.isDefault);\n const initialRef = defaultBranch?.name ?? branchList[0]?.name ?? 'main';\n this.currentRef = initialRef;\n\n // Create file system for the default branch\n const fs: IFileSystem = yield this.createFileSystem(initialRef);\n this.fileSystem = fs;\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to load repository';\n this.error = msg;\n this.onError?.({ message: msg });\n } finally {\n this.loading = false;\n }\n });\n\n switchRef = flow(function* (this: GitRepoBrowserModel, ref: string) {\n if (ref === this.currentRef) return;\n\n this.currentRef = ref;\n this.loading = true;\n this.error = null;\n this.selectedCommitSha = undefined;\n this.diffEntries = [];\n this.commitsPage = 0;\n\n try {\n const fs: IFileSystem = yield this.createFileSystem(ref);\n this.fileSystem = fs;\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to switch ref';\n this.error = msg;\n this.onError?.({ message: msg });\n } finally {\n this.loading = false;\n }\n\n // Reload commits if the commits tab is active\n if (this.activeTab === 'commits') {\n this.loadCommits();\n }\n });\n\n loadCommits = flow(function* (this: GitRepoBrowserModel) {\n if (!this.currentRef) return;\n\n this.commitsLoading = true;\n try {\n const fetchCount = COMMITS_PAGE_SIZE * (this.commitsPage + 1);\n const commitList: GitCommit[] = yield this.gitRepo.listCommits({\n ref: this.currentRef,\n maxCount: fetchCount + 1, // Fetch one extra to detect if more exist\n });\n const start = this.commitsPage * COMMITS_PAGE_SIZE;\n this.commits = commitList.slice(start, start + COMMITS_PAGE_SIZE);\n this.hasMoreCommits = commitList.length > start + COMMITS_PAGE_SIZE;\n } catch (err) {\n console.error('[GitRepoBrowserModel] Failed to load commits:', err);\n this.commits = [];\n this.hasMoreCommits = false;\n } finally {\n this.commitsLoading = false;\n }\n });\n\n selectCommit = flow(function* (this: GitRepoBrowserModel, commit: GitCommit) {\n this.selectedCommitSha = commit.sha;\n\n if (commit.parents.length === 0) {\n this.diffEntries = [];\n return;\n }\n\n this.diffLoading = true;\n try {\n const entries: GitDiffEntry[] = yield this.gitRepo.diff(\n commit.parents[0]!,\n commit.sha,\n );\n this.diffEntries = entries;\n } catch {\n this.diffEntries = [];\n } finally {\n this.diffLoading = false;\n }\n });\n\n loadPRs = flow(function* (this: GitRepoBrowserModel) {\n if (!this._gitHost) return;\n\n this.hostLoading = true;\n this.hostError = null;\n try {\n const data: PullRequest[] = yield this._gitHost.listPullRequests({\n state: 'all',\n maxResults: HOST_PAGE_SIZE * (this.prPage + 1),\n });\n const start = this.prPage * HOST_PAGE_SIZE;\n this.prs = data.slice(start, start + HOST_PAGE_SIZE);\n this.hasMorePrs = data.length > start + HOST_PAGE_SIZE;\n } catch (err) {\n this.hostError = err instanceof Error ? err.message : 'Failed to load';\n } finally {\n this.hostLoading = false;\n }\n });\n\n loadIssues = flow(function* (this: GitRepoBrowserModel) {\n if (!this._gitHost) return;\n\n this.hostLoading = true;\n this.hostError = null;\n try {\n const data: Issue[] = yield this._gitHost.listIssues({\n state: 'all',\n maxResults: HOST_PAGE_SIZE * (this.issuePage + 1),\n });\n const start = this.issuePage * HOST_PAGE_SIZE;\n this.issues = data.slice(start, start + HOST_PAGE_SIZE);\n this.hasMoreIssues = data.length > start + HOST_PAGE_SIZE;\n } catch (err) {\n this.hostError = err instanceof Error ? err.message : 'Failed to load';\n } finally {\n this.hostLoading = false;\n }\n });\n\n // --- Pagination helpers ---\n\n nextCommitsPage(): void {\n this.commitsPage += 1;\n this.loadCommits();\n }\n\n prevCommitsPage(): void {\n this.commitsPage = Math.max(0, this.commitsPage - 1);\n this.loadCommits();\n }\n\n nextPrPage(): void {\n this.prPage += 1;\n this.loadPRs();\n }\n\n prevPrPage(): void {\n this.prPage = Math.max(0, this.prPage - 1);\n this.loadPRs();\n }\n\n nextIssuePage(): void {\n this.issuePage += 1;\n this.loadIssues();\n }\n\n prevIssuePage(): void {\n this.issuePage = Math.max(0, this.issuePage - 1);\n this.loadIssues();\n }\n\n retryPRs(): void {\n this.hostError = null;\n this.prPage = 0;\n this.loadPRs();\n }\n\n retryIssues(): void {\n this.hostError = null;\n this.issuePage = 0;\n this.loadIssues();\n }\n\n // --- Branch selection from BranchList tab ---\n\n selectBranch(branch: GitBranch): void {\n this.switchRef(branch.name);\n }\n}\n","import { makeAutoObservable, flow } from 'mobx';\nimport type { IGitHost, PullRequest, Issue } from '@anymux/file-system';\n\nexport type HostTab = 'prs' | 'issues';\n\nconst PAGE_SIZE = 25;\n\nexport class GitHostBrowserModel {\n gitHost: IGitHost;\n\n activeTab: HostTab = 'prs';\n prs: PullRequest[] = [];\n issues: Issue[] = [];\n hasMorePrs = false;\n hasMoreIssues = false;\n page = 0;\n loading = false;\n error: string | null = null;\n\n constructor(gitHost: IGitHost) {\n this.gitHost = gitHost;\n makeAutoObservable(this, { gitHost: false });\n }\n\n get currentItems() {\n return this.activeTab === 'prs' ? this.prs : this.issues;\n }\n\n get hasMore() {\n return this.activeTab === 'prs' ? this.hasMorePrs : this.hasMoreIssues;\n }\n\n get showPagination() {\n return this.page > 0 || this.hasMore;\n }\n\n setActiveTab(tab: HostTab) {\n this.activeTab = tab;\n this.page = 0;\n this.loadData();\n }\n\n nextPage() {\n if (!this.hasMore) return;\n this.page += 1;\n this.loadData();\n }\n\n prevPage() {\n if (this.page <= 0) return;\n this.page -= 1;\n this.loadData();\n }\n\n retry() {\n this.error = null;\n this.page = 0;\n this.loadData();\n }\n\n loadData = flow(function* loadData(this: GitHostBrowserModel) {\n this.loading = true;\n this.error = null;\n\n try {\n if (this.activeTab === 'prs') {\n const data: PullRequest[] = yield this.gitHost.listPullRequests({\n state: 'all',\n maxResults: PAGE_SIZE * (this.page + 1),\n });\n const start = this.page * PAGE_SIZE;\n this.prs = data.slice(start, start + PAGE_SIZE);\n this.hasMorePrs = data.length > start + PAGE_SIZE;\n } else {\n const data: Issue[] = yield this.gitHost.listIssues({\n state: 'all',\n maxResults: PAGE_SIZE * (this.page + 1),\n });\n const start = this.page * PAGE_SIZE;\n this.issues = data.slice(start, start + PAGE_SIZE);\n this.hasMoreIssues = data.length > start + PAGE_SIZE;\n }\n } catch (err) {\n this.error = err instanceof Error ? err.message : 'Failed to load data';\n } finally {\n this.loading = false;\n }\n });\n}\n","import { toast } from 'sonner';\nimport type { ActionNotificationModel, ActionType } from '../models/ActionNotificationModel';\n\n/**\n * Show a toast notification for a user action, with optional undo.\n * Records the action in the ActionNotificationModel for history.\n */\nexport function showActionToast(\n model: ActionNotificationModel,\n type: ActionType,\n description: string,\n options?: {\n undo?: () => Promise<void>;\n /** Duration in ms before auto-dismiss. Default 5000. */\n duration?: number;\n },\n): string {\n const actionId = model.record(type, description, options?.undo);\n\n if (options?.undo) {\n toast(description, {\n duration: options.duration ?? 5000,\n action: {\n label: 'Undo',\n onClick: async () => {\n try {\n await options.undo!();\n model.markUndone(actionId);\n toast.success('Action undone');\n } catch (err) {\n toast.error(`Undo failed: ${err instanceof Error ? err.message : 'Unknown error'}`);\n }\n },\n },\n });\n } else {\n toast.success(description, {\n duration: options?.duration ?? 3000,\n });\n }\n\n return actionId;\n}\n\n/** Show an error toast */\nexport function showErrorToast(message: string, context?: string) {\n toast.error(context ? `${context}: ${message}` : message);\n}\n\n/** Show an info toast */\nexport function showInfoToast(message: string) {\n toast.info(message);\n}\n","import React, { useState, useEffect, useRef, useCallback, Suspense } from 'react';\nimport { observer } from 'mobx-react-lite';\nimport {\n GitBranch as GitBranchIcon,\n Tag as TagIcon,\n GitPullRequest,\n AlertCircle,\n Loader2,\n ChevronDown,\n ChevronLeft,\n ChevronRight,\n Check,\n FolderTree,\n GitCommit as GitCommitIcon,\n Diff,\n} from 'lucide-react';\nimport { toast } from 'sonner';\nimport type { ActionNotificationModel } from '../models/ActionNotificationModel';\nimport { showActionToast, showErrorToast } from '../utils/action-toast';\nimport { BrowserError } from '@anymux/ui/components/browser-error';\nimport type {\n IGitRepo,\n GitBranch,\n GitTag,\n IGitHost,\n IFileSystem,\n} from '@anymux/file-system';\nimport { GitRepoBrowserModel, type SidebarTab } from '../models/GitRepoBrowserModel';\nimport { GitHostBrowserModel } from '../models/GitHostBrowserModel';\n\n// Lazy-load the heavy FileBrowser from fs-ui\nconst FileBrowser = React.lazy(() =>\n import('@anymux/fs-ui').then((m) => ({ default: m.FileBrowser }))\n);\n\nconst CommitList = React.lazy(() =>\n import('@anymux/fs-ui').then((m) => ({ default: m.CommitList }))\n);\n\nconst BranchList = React.lazy(() =>\n import('@anymux/fs-ui').then((m) => ({ default: m.BranchList }))\n);\n\nconst DiffViewer = React.lazy(() =>\n import('@anymux/fs-ui').then((m) => ({ default: m.DiffViewer }))\n);\n\n// ---- Helpers ----\n\nfunction formatRelativeTime(date: Date): string {\n const now = Date.now();\n const diffMs = now - new Date(date).getTime();\n const seconds = Math.floor(diffMs / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n const weeks = Math.floor(days / 7);\n const months = Math.floor(days / 30);\n\n if (seconds < 60) return 'just now';\n if (minutes < 60) return `${minutes}m ago`;\n if (hours < 24) return `${hours}h ago`;\n if (days < 7) return `${days}d ago`;\n if (weeks < 5) return `${weeks}w ago`;\n if (months < 12) return `${months}mo ago`;\n return new Date(date).toLocaleDateString();\n}\n\nfunction LoadingSpinner() {\n return (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-5 w-5 text-gray-400 animate-spin\" />\n </div>\n );\n}\n\nfunction ErrorMessage({ message }: { message: string }) {\n return (\n <div className=\"flex items-center gap-2 px-4 py-6 text-sm text-red-600 dark:text-red-400 justify-center\">\n <AlertCircle className=\"h-4 w-4 shrink-0\" />\n <span>{message}</span>\n </div>\n );\n}\n\n// ---- Branch/Tag Selector Dropdown ----\n\ntype RefType = 'branch' | 'tag';\ninterface RefItem {\n type: RefType;\n name: string;\n sha: string;\n isDefault?: boolean;\n}\n\ninterface RefSelectorProps {\n branches: GitBranch[];\n tags: GitTag[];\n currentRef: string;\n onSelectRef: (ref: string, type: RefType) => void;\n}\n\nfunction RefSelector({ branches, tags, currentRef, onSelectRef }: RefSelectorProps) {\n const [isOpen, setIsOpen] = useState(false);\n const [filterTab, setFilterTab] = useState<'branches' | 'tags'>('branches');\n const [search, setSearch] = useState('');\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n // Close on click outside\n useEffect(() => {\n function handleClickOutside(e: MouseEvent) {\n if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {\n setIsOpen(false);\n }\n }\n if (isOpen) {\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }\n }, [isOpen]);\n\n const items: RefItem[] = filterTab === 'branches'\n ? branches.map((b) => ({ type: 'branch' as const, name: b.name, sha: b.sha, isDefault: b.isDefault }))\n : tags.map((t) => ({ type: 'tag' as const, name: t.name, sha: t.sha }));\n\n const filtered = search\n ? items.filter((i) => i.name.toLowerCase().includes(search.toLowerCase()))\n : items;\n\n return (\n <div ref={dropdownRef} className=\"relative\">\n <button\n onClick={() => setIsOpen(!isOpen)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors\"\n >\n <GitBranchIcon className=\"h-3.5 w-3.5 text-gray-500\" />\n <span className=\"max-w-[160px] truncate\" title={currentRef}>{currentRef}</span>\n <ChevronDown className=\"h-3 w-3 text-gray-400\" />\n </button>\n\n {isOpen && (\n <div className=\"absolute left-0 top-full mt-1 z-50 w-72 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg overflow-hidden\">\n {/* Search */}\n <div className=\"p-2 border-b border-gray-200 dark:border-gray-700\">\n <input\n type=\"text\"\n placeholder=\"Filter branches/tags...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"w-full px-2.5 py-1.5 text-xs rounded-md border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-900 focus:outline-none focus:ring-1 focus:ring-blue-500\"\n autoFocus\n />\n </div>\n\n {/* Tabs */}\n <div className=\"flex border-b border-gray-200 dark:border-gray-700\">\n <button\n onClick={() => { setFilterTab('branches'); setSearch(''); }}\n className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors ${\n filterTab === 'branches'\n ? 'border-b-2 border-blue-500 text-blue-600 dark:text-blue-400'\n : 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'\n }`}\n >\n <GitBranchIcon className=\"h-3 w-3\" />\n Branches\n </button>\n <button\n onClick={() => { setFilterTab('tags'); setSearch(''); }}\n className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium transition-colors ${\n filterTab === 'tags'\n ? 'border-b-2 border-blue-500 text-blue-600 dark:text-blue-400'\n : 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'\n }`}\n >\n <TagIcon className=\"h-3 w-3\" />\n Tags\n </button>\n </div>\n\n {/* Items */}\n <div className=\"max-h-64 overflow-auto\">\n {filtered.length === 0 ? (\n <div className=\"px-3 py-4 text-center text-xs text-gray-400\">\n {search ? 'No matches found' : `No ${filterTab} found`}\n </div>\n ) : (\n filtered.map((item) => (\n <button\n key={`${item.type}-${item.name}`}\n onClick={() => {\n onSelectRef(item.name, item.type);\n setIsOpen(false);\n setSearch('');\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-left hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors\"\n >\n <span className=\"w-4 flex-shrink-0\">\n {item.name === currentRef && (\n <Check className=\"h-3.5 w-3.5 text-blue-500\" />\n )}\n </span>\n <span className=\"text-xs truncate flex-1\" title={item.name}>{item.name}</span>\n {item.isDefault && (\n <span className=\"px-1.5 py-0.5 text-[9px] font-medium bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 rounded flex-shrink-0\">\n default\n </span>\n )}\n </button>\n ))\n )}\n </div>\n </div>\n )}\n </div>\n );\n}\n\n// ---- Git Repository File Browser ----\n\nexport interface GitRepoBrowserProps {\n gitRepo: IGitRepo;\n owner: string;\n repo: string;\n /** Creates an IFileSystem scoped to a particular branch/ref */\n createFileSystem: (branch: string) => Promise<IFileSystem>;\n /** Optional git host adapter for PRs/Issues tabs */\n gitHost?: IGitHost;\n onError?: (err: { message: string }) => void;\n /** Optional action notifications model for recording actions */\n actionNotifications?: ActionNotificationModel;\n}\n\nexport const GitRepoBrowser: React.FC<GitRepoBrowserProps> = observer(({\n gitRepo,\n owner,\n repo,\n createFileSystem,\n gitHost,\n onError,\n actionNotifications,\n}) => {\n const [model] = useState(\n () => new GitRepoBrowserModel(gitRepo, gitHost, createFileSystem, onError)\n );\n\n // When gitHost arrives asynchronously after model construction, inject it\n useEffect(() => {\n if (gitHost && !model.hasGitHost) {\n model.setGitHost(gitHost);\n }\n }, [gitHost, model]);\n\n const handleNotify = useCallback((type: 'success' | 'error' | 'warning', message: string) => {\n if (type === 'success' && actionNotifications) {\n const actionType = message.startsWith('Renamed') ? 'rename' as const\n : message.startsWith('Deleted') ? 'delete' as const\n : message.startsWith('Created') ? 'create' as const\n : message.startsWith('Uploaded') || message.includes('upload') ? 'upload' as const\n : 'create' as const;\n showActionToast(actionNotifications, actionType, message);\n } else if (type === 'error') {\n showErrorToast(message);\n } else {\n toast[type](message);\n }\n }, [actionNotifications]);\n\n useEffect(() => {\n model.initialize();\n }, [model]);\n\n if (model.loading && !model.fileSystem) {\n return <LoadingSpinner />;\n }\n\n if (model.error && !model.fileSystem) {\n return (\n <BrowserError\n error={model.error}\n context={`${owner}/${repo}`}\n onRetry={() => window.location.reload()}\n />\n );\n }\n\n const sidebarTabs: Array<{ id: SidebarTab; label: string; icon: React.ReactNode }> = [\n { id: 'files', label: 'Files', icon: <FolderTree className=\"h-3.5 w-3.5\" /> },\n { id: 'branches', label: 'Branches', icon: <GitBranchIcon className=\"h-3.5 w-3.5\" /> },\n { id: 'commits', label: 'Commits', icon: <GitCommitIcon className=\"h-3.5 w-3.5\" /> },\n ...(model.hasGitHost ? [\n { id: 'prs' as const, label: 'PRs', icon: <GitPullRequest className=\"h-3.5 w-3.5\" /> },\n { id: 'issues' as const, label: 'Issues', icon: <AlertCircle className=\"h-3.5 w-3.5\" /> },\n ] : []),\n ];\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Header with branch selector */}\n <div className=\"flex items-center gap-3 px-3 py-2 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 flex-shrink-0\">\n <RefSelector\n branches={model.branches}\n tags={model.tags}\n currentRef={model.currentRef}\n onSelectRef={(ref) => model.switchRef(ref)}\n />\n <div className=\"flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-400 min-w-0\">\n <span className=\"font-medium text-gray-700 dark:text-gray-300 truncate\" title={owner}>\n {owner}\n </span>\n <span>/</span>\n <span className=\"font-medium text-gray-700 dark:text-gray-300 truncate\" title={repo}>\n {repo}\n </span>\n </div>\n </div>\n\n {/* Sidebar tabs */}\n <div className=\"flex border-b border-gray-200 dark:border-gray-700 flex-shrink-0\">\n {sidebarTabs.map((tab) => (\n <button\n key={tab.id}\n onClick={() => model.setActiveTab(tab.id)}\n className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border-b-2 transition-colors ${\n model.activeTab === tab.id\n ? 'border-blue-500 text-blue-600 dark:text-blue-400'\n : 'border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'\n }`}\n >\n {tab.icon}\n {tab.label}\n </button>\n ))}\n </div>\n\n {/* Tab content */}\n <div className=\"flex-1 min-h-0 relative flex flex-col\">\n {model.loading && (\n <div className=\"absolute inset-0 bg-white/60 dark:bg-gray-900/60 z-10 flex items-center justify-center\">\n <Loader2 className=\"h-5 w-5 text-gray-400 animate-spin\" />\n </div>\n )}\n {model.error && model.fileSystem && (\n <div className=\"px-4 py-2 bg-red-50 dark:bg-red-900/20 text-xs text-red-600 dark:text-red-400 border-b border-red-200 dark:border-red-800 flex-shrink-0\">\n {model.error}\n </div>\n )}\n\n {/* Files tab */}\n {model.activeTab === 'files' && model.fileSystem && (\n <div className=\"flex-1 min-h-0\">\n <Suspense fallback={<LoadingSpinner />}>\n <FileBrowser\n key={model.currentRef}\n fileSystem={model.fileSystem}\n className=\"h-full\"\n onError={onError}\n onNotify={handleNotify}\n />\n </Suspense>\n </div>\n )}\n\n {/* Branches tab */}\n {model.activeTab === 'branches' && (\n <div className=\"flex-1 min-h-0\">\n <Suspense fallback={<LoadingSpinner />}>\n <BranchList\n branches={model.branches}\n currentBranch={model.currentRef}\n onSelectBranch={(branch) => model.selectBranch(branch)}\n className=\"h-full\"\n />\n </Suspense>\n </div>\n )}\n\n {/* Commits tab */}\n {model.activeTab === 'commits' && (\n <div className=\"flex-1 min-h-0 flex flex-col\">\n {model.commitsLoading ? (\n <LoadingSpinner />\n ) : (\n <>\n <Suspense fallback={<LoadingSpinner />}>\n <CommitList\n commits={model.commits}\n headSha={model.headSha}\n selectedSha={model.selectedCommitSha}\n onSelectCommit={(commit) => model.selectCommit(commit)}\n className={model.selectedCommitSha ? 'max-h-[50%] overflow-auto flex-shrink-0' : 'flex-1 overflow-auto'}\n />\n </Suspense>\n\n {/* Commits pagination */}\n {(model.commitsPage > 0 || model.hasMoreCommits) && (\n <div className=\"flex items-center justify-between px-4 py-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 flex-shrink-0\">\n <button\n onClick={() => model.prevCommitsPage()}\n disabled={model.commitsPage === 0}\n className=\"inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:pointer-events-none\"\n >\n <ChevronLeft className=\"h-3.5 w-3.5\" /> Previous\n </button>\n <span className=\"text-xs text-gray-500\">Page {model.commitsPage + 1}</span>\n <button\n onClick={() => model.nextCommitsPage()}\n disabled={!model.hasMoreCommits}\n className=\"inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:pointer-events-none\"\n >\n Next <ChevronRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n )}\n\n {/* Diff panel below commits when a commit is selected */}\n {model.selectedCommitSha && (\n <div className=\"flex-1 min-h-0 border-t border-gray-200 dark:border-gray-700 overflow-auto\">\n <div className=\"flex items-center gap-2 px-3 py-1.5 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex-shrink-0\">\n <Diff className=\"h-3.5 w-3.5 text-gray-400\" />\n <span className=\"text-[10px] font-medium text-gray-600 dark:text-gray-300\">\n Changes in{' '}\n <code className=\"font-mono text-blue-600 dark:text-blue-400\">\n {model.selectedCommitSha.slice(0, 7)}\n </code>\n </span>\n </div>\n {model.diffLoading ? (\n <LoadingSpinner />\n ) : (\n <Suspense fallback={<LoadingSpinner />}>\n <DiffViewer entries={model.diffEntries} className=\"p-2\" />\n </Suspense>\n )}\n </div>\n )}\n </>\n )}\n </div>\n )}\n\n {/* PRs tab */}\n {model.activeTab === 'prs' && model.hasGitHost && (\n <div className=\"flex-1 min-h-0 flex flex-col\">\n {model.hostLoading && <LoadingSpinner />}\n {!model.hostLoading && model.hostError && (\n <BrowserError error={model.hostError} context=\"Pull Requests\" onRetry={() => model.retryPRs()} />\n )}\n {!model.hostLoading && !model.hostError && (\n model.prs.length === 0 ? (\n <div className=\"flex items-center justify-center py-8 text-xs text-gray-400\">No pull requests found</div>\n ) : (\n <>\n <div className=\"flex-1 overflow-auto divide-y divide-gray-100 dark:divide-gray-800\">\n {model.prs.map((pr) => (\n <div key={pr.number} className=\"flex items-start gap-3 px-4 py-2.5 hover:bg-gray-50 dark:hover:bg-gray-800/50\">\n <GitPullRequest className=\"h-4 w-4 text-gray-400 shrink-0 mt-0.5\" />\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm truncate\" title={pr.title}>{pr.title}</span>\n <span className={`px-1.5 py-0.5 text-[10px] font-medium rounded ${\n pr.state === 'open' ? 'text-green-600 bg-green-50 dark:text-green-400 dark:bg-green-900/30' :\n pr.state === 'merged' ? 'text-purple-600 bg-purple-50 dark:text-purple-400 dark:bg-purple-900/30' :\n 'text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-900/30'\n }`}>\n {pr.state}\n </span>\n </div>\n <span className=\"text-xs text-gray-500\">\n #{pr.number} · {pr.author} · {formatRelativeTime(pr.createdAt)}\n </span>\n </div>\n </div>\n ))}\n </div>\n {(model.prPage > 0 || model.hasMorePrs) && (\n <div className=\"flex items-center justify-between px-4 py-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 flex-shrink-0\">\n <button\n onClick={() => model.prevPrPage()}\n disabled={model.prPage === 0}\n className=\"inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:pointer-events-none\"\n >\n <ChevronLeft className=\"h-3.5 w-3.5\" /> Previous\n </button>\n <span className=\"text-xs text-gray-500\">Page {model.prPage + 1}</span>\n <button\n onClick={() => model.nextPrPage()}\n disabled={!model.hasMorePrs}\n className=\"inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:pointer-events-none\"\n >\n Next <ChevronRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n )}\n </>\n )\n )}\n </div>\n )}\n\n {/* Issues tab */}\n {model.activeTab === 'issues' && model.hasGitHost && (\n <div className=\"flex-1 min-h-0 flex flex-col\">\n {model.hostLoading && <LoadingSpinner />}\n {!model.hostLoading && model.hostError && (\n <BrowserError error={model.hostError} context=\"Issues\" onRetry={() => model.retryIssues()} />\n )}\n {!model.hostLoading && !model.hostError && (\n model.issues.length === 0 ? (\n <div className=\"flex items-center justify-center py-8 text-xs text-gray-400\">No issues found</div>\n ) : (\n <>\n <div className=\"flex-1 overflow-auto divide-y divide-gray-100 dark:divide-gray-800\">\n {model.issues.map((issue) => (\n <div key={issue.number} className=\"flex items-start gap-3 px-4 py-2.5 hover:bg-gray-50 dark:hover:bg-gray-800/50\">\n <AlertCircle className={`h-4 w-4 shrink-0 mt-0.5 ${issue.state === 'open' ? 'text-green-500' : 'text-red-400'}`} />\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm truncate\" title={issue.title}>{issue.title}</span>\n {issue.labels.map((l) => (\n <span key={l} className=\"px-1.5 py-0.5 text-[10px] font-medium rounded bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400\">\n {l}\n </span>\n ))}\n </div>\n <span className=\"text-xs text-gray-500\">\n #{issue.number} · {issue.author} · {formatRelativeTime(issue.createdAt)}\n </span>\n </div>\n </div>\n ))}\n </div>\n {(model.issuePage > 0 || model.hasMoreIssues) && (\n <div className=\"flex items-center justify-between px-4 py-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 flex-shrink-0\">\n <button\n onClick={() => model.prevIssuePage()}\n disabled={model.issuePage === 0}\n className=\"inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:pointer-events-none\"\n >\n <ChevronLeft className=\"h-3.5 w-3.5\" /> Previous\n </button>\n <span className=\"text-xs text-gray-500\">Page {model.issuePage + 1}</span>\n <button\n onClick={() => model.nextIssuePage()}\n disabled={!model.hasMoreIssues}\n className=\"inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:pointer-events-none\"\n >\n Next <ChevronRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n )}\n </>\n )\n )}\n </div>\n )}\n </div>\n </div>\n );\n});\n\n// ---- Git Host Browser (PRs / Issues) ----\n\ninterface GitHostBrowserProps {\n gitHost: IGitHost;\n}\n\nfunction EmptyState({ message }: { message: string }) {\n return (\n <div className=\"flex items-center justify-center py-8 text-xs text-gray-400\">\n {message}\n </div>\n );\n}\n\nfunction statusColor(status: string) {\n switch (status) {\n case 'open': return 'text-green-600 bg-green-50 dark:text-green-400 dark:bg-green-900/30';\n case 'merged': return 'text-purple-600 bg-purple-50 dark:text-purple-400 dark:bg-purple-900/30';\n case 'closed': return 'text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-900/30';\n default: return 'text-gray-600 bg-gray-50';\n }\n}\n\nexport const GitHostBrowser: React.FC<GitHostBrowserProps> = observer(({ gitHost }) => {\n const [model] = useState(() => new GitHostBrowserModel(gitHost));\n\n useEffect(() => {\n model.loadData();\n }, [model]);\n\n const tabDefs: Array<{ id: 'prs' | 'issues'; label: string; icon: React.ReactNode }> = [\n { id: 'prs', label: 'Pull Requests', icon: <GitPullRequest className=\"h-3.5 w-3.5\" /> },\n { id: 'issues', label: 'Issues', icon: <AlertCircle className=\"h-3.5 w-3.5\" /> },\n ];\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center gap-2 px-4 py-2 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800\">\n <GitPullRequest className=\"h-4 w-4 text-gray-500\" />\n <span className=\"text-xs font-medium text-gray-700 dark:text-gray-300\">\n Git Host Browser\n </span>\n </div>\n\n <div className=\"flex border-b border-gray-200 dark:border-gray-700\">\n {tabDefs.map((tab) => (\n <button\n key={tab.id}\n onClick={() => model.setActiveTab(tab.id)}\n className={`flex items-center gap-1.5 px-4 py-2 text-xs font-medium border-b-2 transition-colors ${\n model.activeTab === tab.id\n ? 'border-blue-500 text-blue-600 dark:text-blue-400'\n : 'border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'\n }`}\n >\n {tab.icon}\n {tab.label}\n </button>\n ))}\n </div>\n\n <div className=\"flex-1 overflow-auto\">\n {model.loading && <LoadingSpinner />}\n {!model.loading && model.error && (\n <BrowserError\n error={model.error}\n context=\"Git Host\"\n onRetry={() => model.retry()}\n />\n )}\n\n {!model.loading && !model.error && model.activeTab === 'prs' && (\n model.prs.length === 0 ? <EmptyState message=\"No pull requests found\" /> : (\n <div className=\"divide-y divide-gray-100 dark:divide-gray-800\">\n {model.prs.map((pr) => (\n <div key={pr.number} className=\"flex items-start gap-3 px-4 py-2.5 hover:bg-gray-50 dark:hover:bg-gray-800/50\">\n <GitPullRequest className=\"h-4 w-4 text-gray-400 shrink-0 mt-0.5\" />\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm truncate\" title={pr.title}>{pr.title}</span>\n <span className={`px-1.5 py-0.5 text-[10px] font-medium rounded ${statusColor(pr.state)}`}>\n {pr.state}\n </span>\n </div>\n <span className=\"text-xs text-gray-500\">\n #{pr.number} · {pr.author} · {formatRelativeTime(pr.createdAt)}\n </span>\n </div>\n </div>\n ))}\n </div>\n )\n )}\n\n {!model.loading && !model.error && model.activeTab === 'issues' && (\n model.issues.length === 0 ? <EmptyState message=\"No issues found\" /> : (\n <div className=\"divide-y divide-gray-100 dark:divide-gray-800\">\n {model.issues.map((issue) => (\n <div key={issue.number} className=\"flex items-start gap-3 px-4 py-2.5 hover:bg-gray-50 dark:hover:bg-gray-800/50\">\n <AlertCircle className=\"h-4 w-4 text-green-500 shrink-0 mt-0.5\" />\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm truncate\" title={issue.title}>{issue.title}</span>\n {issue.labels.map((l) => (\n <span key={l} className=\"px-1.5 py-0.5 text-[10px] font-medium rounded bg-blue-50 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400\">\n {l}\n </span>\n ))}\n </div>\n <span className=\"text-xs text-gray-500\">\n #{issue.number} · {issue.author} · {formatRelativeTime(issue.createdAt)}\n </span>\n </div>\n </div>\n ))}\n </div>\n )\n )}\n </div>\n\n {model.showPagination && !model.loading && !model.error && model.currentItems.length > 0 && (\n <div className=\"flex items-center justify-between px-4 py-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800\">\n <button\n onClick={() => model.prevPage()}\n disabled={model.page === 0}\n className=\"inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:pointer-events-none\"\n >\n <ChevronLeft className=\"h-3.5 w-3.5\" />\n Previous\n </button>\n <span className=\"text-xs text-gray-500\">Page {model.page + 1}</span>\n <button\n onClick={() => model.nextPage()}\n disabled={!model.hasMore}\n className=\"inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors disabled:opacity-50 disabled:pointer-events-none\"\n >\n Next\n <ChevronRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n )}\n </div>\n );\n});\n"],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import { GitHostBrowser, GitRepoBrowser } from "./GitBrowser-BLgTNQyd.js";
2
-
3
- export { GitHostBrowser, GitRepoBrowser };