-
Notifications
You must be signed in to change notification settings - Fork 12
Populate ChooseFile modal via file-meta entries in index #3949
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
446d15b
b401f28
bb36bc8
07c14a0
0458b9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,259 @@ | ||||||
| import { fn } from '@ember/helper'; | ||||||
| import { on } from '@ember/modifier'; | ||||||
| import { action } from '@ember/object'; | ||||||
| import type Owner from '@ember/owner'; | ||||||
| import Component from '@glimmer/component'; | ||||||
| import { tracked } from '@glimmer/tracking'; | ||||||
|
|
||||||
| import { restartableTask, timeout } from 'ember-concurrency'; | ||||||
| import { TrackedSet } from 'tracked-built-ins'; | ||||||
|
|
||||||
| import { LoadingIndicator } from '@cardstack/boxel-ui/components'; | ||||||
| import { eq } from '@cardstack/boxel-ui/helpers'; | ||||||
| import { DropdownArrowDown } from '@cardstack/boxel-ui/icons'; | ||||||
|
|
||||||
| import type { LocalPath } from '@cardstack/runtime-common/paths'; | ||||||
|
|
||||||
| import scrollIntoViewModifier from '@cardstack/host/modifiers/scroll-into-view'; | ||||||
| import { | ||||||
| fileTreeFromIndex, | ||||||
| type FileTreeNode, | ||||||
| } from '@cardstack/host/resources/file-tree-from-index'; | ||||||
| import { normalizeDirPath } from '@cardstack/host/utils/normalized-dir-path'; | ||||||
|
|
||||||
| interface Signature { | ||||||
| Args: { | ||||||
| realmURL: string; | ||||||
| selectedFile?: LocalPath; | ||||||
| openDirs?: LocalPath[]; | ||||||
| onFileSelected?: (entryPath: LocalPath) => void; | ||||||
| onDirectorySelected?: (entryPath: LocalPath) => void; | ||||||
| scrollPositionKey?: LocalPath; | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| export default class IndexedFileTree extends Component<Signature> { | ||||||
| <template> | ||||||
| <nav> | ||||||
| <TreeLevel | ||||||
| @entries={{this.fileTree.entries}} | ||||||
| @fileTree={{this.fileTree}} | ||||||
| @selectedFile={{if @selectedFile @selectedFile this.selectedFile}} | ||||||
| @openDirs={{this.effectiveOpenDirs}} | ||||||
| @onFileSelected={{this.selectFile}} | ||||||
| @onDirectorySelected={{this.toggleDirectory}} | ||||||
| @scrollPositionKey={{@scrollPositionKey}} | ||||||
| @relativePath='' | ||||||
| /> | ||||||
| {{#if this.showMask}} | ||||||
| <div class='mask' data-test-file-tree-mask> | ||||||
| {{#if this.fileTree.isLoading}} | ||||||
| <LoadingIndicator /> | ||||||
| {{/if}} | ||||||
| </div> | ||||||
| {{/if}} | ||||||
| </nav> | ||||||
|
|
||||||
| <style scoped> | ||||||
| .mask { | ||||||
| position: absolute; | ||||||
| top: 0; | ||||||
| left: 0; | ||||||
| background-color: white; | ||||||
| height: 100%; | ||||||
| width: 100%; | ||||||
| display: flex; | ||||||
| align-items: center; | ||||||
| justify-content: center; | ||||||
| } | ||||||
| nav { | ||||||
| position: relative; | ||||||
| } | ||||||
| </style> | ||||||
| </template> | ||||||
|
|
||||||
| private fileTree = fileTreeFromIndex(this, () => this.args.realmURL); | ||||||
| private localOpenDirs = new TrackedSet<string>(); | ||||||
| @tracked private selectedFile?: LocalPath; | ||||||
| @tracked private maskDismissed = false; | ||||||
|
|
||||||
| constructor(owner: Owner, args: Signature['Args']) { | ||||||
| super(owner, args); | ||||||
| this.hideMask.perform(); | ||||||
| } | ||||||
|
|
||||||
| private get showMask(): boolean { | ||||||
| if (this.fileTree.isLoading) { | ||||||
| return true; | ||||||
| } | ||||||
| return !this.maskDismissed; | ||||||
| } | ||||||
|
|
||||||
| private hideMask = restartableTask(async () => { | ||||||
| // fine tuned to coincide with debounce in RestoreScrollPosition modifier | ||||||
| await timeout(300); | ||||||
| this.maskDismissed = true; | ||||||
| }); | ||||||
|
|
||||||
| private get effectiveOpenDirs(): Set<string> { | ||||||
| if (this.args.openDirs) { | ||||||
| return new Set(this.args.openDirs); | ||||||
| } | ||||||
| return this.localOpenDirs; | ||||||
| } | ||||||
|
|
||||||
| @action | ||||||
| private selectFile(entryPath: LocalPath) { | ||||||
| this.selectedFile = entryPath; | ||||||
| this.args.onFileSelected?.(entryPath); | ||||||
| } | ||||||
|
|
||||||
| @action | ||||||
| private toggleDirectory(entryPath: LocalPath) { | ||||||
| let dirPath = normalizeDirPath(entryPath); | ||||||
|
|
||||||
| if (this.localOpenDirs.has(dirPath)) { | ||||||
| this.localOpenDirs.delete(dirPath); | ||||||
| } else { | ||||||
| this.localOpenDirs.add(dirPath); | ||||||
| } | ||||||
|
|
||||||
| this.args.onDirectorySelected?.(dirPath); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| interface TreeLevelSignature { | ||||||
| Args: { | ||||||
| entries: FileTreeNode[]; | ||||||
| fileTree: ReturnType<typeof fileTreeFromIndex>; | ||||||
| selectedFile?: LocalPath; | ||||||
| openDirs: Set<string>; | ||||||
| onFileSelected: (entryPath: LocalPath) => void; | ||||||
| onDirectorySelected: (entryPath: LocalPath) => void; | ||||||
| scrollPositionKey?: LocalPath; | ||||||
| relativePath: string; | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| class TreeLevel extends Component<TreeLevelSignature> { | ||||||
| <template> | ||||||
| {{#each @entries as |entry|}} | ||||||
|
||||||
| {{#each @entries as |entry|}} | |
| {{#each @entries key="path" as |entry|}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The relativePath parameter is declared in the TreeLevelSignature but is never used in the TreeLevel component. It's passed down recursively but has no effect on the component's behavior. Consider removing it if it's not needed, or document why it's being tracked for future use.