1
+ import type Owner from ' @ember/owner' ;
1
2
import { tracked } from ' @glimmer/tracking' ;
2
3
import {
3
4
Component ,
@@ -14,7 +15,14 @@ import {
14
15
type SerializeOpts ,
15
16
type JSONAPISingleResourceDocument ,
16
17
} from ' ./card-api' ;
17
- import { ResolvedCodeRef } from ' @cardstack/runtime-common' ;
18
+ import { restartableTask } from ' ember-concurrency' ;
19
+ import { consume } from ' ember-provide-consume-context' ;
20
+ import {
21
+ type ResolvedCodeRef ,
22
+ type Loader ,
23
+ loadCard ,
24
+ CardURLContextName ,
25
+ } from ' @cardstack/runtime-common' ;
18
26
import { not } from ' @cardstack/boxel-ui/helpers' ;
19
27
import { BoxelInput } from ' @cardstack/boxel-ui/components' ;
20
28
import CodeIcon from ' @cardstack/boxel-icons/code' ;
@@ -39,35 +47,81 @@ class BaseView extends Component<typeof CodeRefField> {
39
47
}
40
48
41
49
class EditView extends Component <typeof CodeRefField > {
42
- @tracked private rawInput: string | undefined = maybeSerializeCodeRef (
50
+ @consume (CardURLContextName ) declare cardURL: string | undefined ;
51
+ @tracked validationState: ' initial' | ' valid' | ' invalid' = ' initial' ;
52
+ @tracked private maybeCodeRef: string | undefined = maybeSerializeCodeRef (
43
53
this .args .model ?? undefined ,
44
54
);
45
55
46
56
<template >
47
57
<BoxelInput
48
- @ value ={{this .rawInput }}
58
+ data-test-hasValidated ={{this .setIfValid.isIdle }}
59
+ @ value ={{this .maybeCodeRef }}
60
+ @ state ={{this .validationState }}
49
61
@ onInput ={{this .onInput }}
50
62
@ disabled ={{not @ canEdit}}
51
63
/>
52
64
</template >
53
65
54
- private onInput = (inputVal : string ) => {
55
- this .rawInput = inputVal ;
56
- if (this .rawInput .length === 0 ) {
57
- this .args .set (undefined );
58
- return ;
59
- }
60
-
61
- let parts = this .rawInput .split (' /' );
62
- if (parts .length < 2 ) {
63
- this .args .set (undefined );
64
- return ;
66
+ constructor (owner : Owner , args : any ) {
67
+ super (owner , args );
68
+ if (this .maybeCodeRef != null ) {
69
+ this .setIfValid .perform (this .maybeCodeRef , true );
65
70
}
71
+ }
66
72
67
- let name = parts . pop ();
68
- let module = parts . join ( ' / ' ) ;
69
- this .args . set ({ module , name } );
73
+ private onInput = ( inputVal : string ) => {
74
+ this . maybeCodeRef = inputVal ;
75
+ this .setIfValid . perform ( this . maybeCodeRef );
70
76
};
77
+
78
+ private setIfValid = restartableTask (
79
+ async (maybeCodeRef : string , checkOnly ? : true ) => {
80
+ this .validationState = ' initial' ;
81
+ if (maybeCodeRef .length === 0 ) {
82
+ if (! checkOnly ) {
83
+ this .args .set (undefined );
84
+ }
85
+ return ;
86
+ }
87
+
88
+ let parts = maybeCodeRef .split (' /' );
89
+ if (parts .length < 2 ) {
90
+ this .validationState = ' invalid' ;
91
+ return ;
92
+ }
93
+
94
+ let name = parts .pop ()! ;
95
+ let module = parts .join (' /' );
96
+ try {
97
+ if (moduleIsUrlLike (module )) {
98
+ await loadCard (
99
+ { module , name },
100
+ {
101
+ loader: myLoader (),
102
+ relativeTo: this .cardURL ? new URL (this .cardURL ) : undefined ,
103
+ },
104
+ );
105
+ this .validationState = ' valid' ;
106
+ if (! checkOnly ) {
107
+ this .args .set ({ module , name });
108
+ }
109
+ } else {
110
+ let code = (await import (module ))[name ];
111
+ if (code ) {
112
+ this .validationState = ' valid' ;
113
+ if (! checkOnly ) {
114
+ this .args .set ({ module , name });
115
+ }
116
+ } else {
117
+ this .validationState = ' invalid' ;
118
+ }
119
+ }
120
+ } catch (err ) {
121
+ this .validationState = ' invalid' ;
122
+ }
123
+ },
124
+ );
71
125
}
72
126
73
127
export default class CodeRefField extends FieldDef {
@@ -128,3 +182,14 @@ function maybeSerializeCodeRef(
128
182
}
129
183
return undefined ;
130
184
}
185
+
186
+ function myLoader(): Loader {
187
+ // we know this code is always loaded by an instance of our Loader, which sets
188
+ // import.meta.loader.
189
+
190
+ // When type-checking realm-server, tsc sees this file and thinks
191
+ // it will be transpiled to CommonJS and so it complains about this line. But
192
+ // this file is always loaded through our loader and always has access to import.meta.
193
+ // @ts-ignore
194
+ return (import .meta as any ).loader ;
195
+ }
0 commit comments