@@ -19,8 +19,12 @@ public class ShapeResult : IDisplayResult
1919 private string _differentiator ;
2020 private string _prefix ;
2121 private string _cacheId ;
22- private Dictionary < string , string > _otherLocations ;
2322 private StringValues _groupIds ;
23+ private Dictionary < string , string > _otherLocations ;
24+ private string _firstDisplayType ;
25+ private string _firstLocation ;
26+ private string _secondDisplayType ;
27+ private string _secondLocation ;
2428
2529 private Action < CacheContext > _cache ;
2630 private Action < ShapeDisplayContext > _displaying ;
@@ -63,7 +67,159 @@ public Task ApplyAsync(BuildEditorContext context)
6367 return ApplyImplementationAsync ( context , "Edit" ) ;
6468 }
6569
66- private async Task ApplyImplementationAsync ( BuildShapeContext context , string displayType )
70+ /// <summary>
71+ /// Sets the prefix of the form elements rendered in the shape.
72+ /// </summary>
73+ /// <remarks>
74+ /// The goal is to isolate each shape when edited together.
75+ /// </remarks>
76+ public ShapeResult Prefix ( string prefix )
77+ {
78+ _prefix = prefix ;
79+ return this ;
80+ }
81+
82+ /// <summary>
83+ /// Sets the default location of the shape when no specific placement applies.
84+ /// </summary>
85+ public ShapeResult Location ( string location )
86+ {
87+ _defaultLocation = location ;
88+ return this ;
89+ }
90+
91+ /// <summary>
92+ /// Sets the location to use for a matching display type.
93+ /// </summary>
94+ public ShapeResult Location ( string displayType , string location )
95+ {
96+ if ( _otherLocations != null )
97+ {
98+ _otherLocations [ displayType ] = location ;
99+ }
100+ else if ( _firstDisplayType == null )
101+ {
102+ _firstDisplayType = displayType ;
103+ _firstLocation = location ;
104+ }
105+ else if ( _secondDisplayType == null )
106+ {
107+ _secondDisplayType = displayType ;
108+ _secondLocation = location ;
109+ }
110+ else
111+ {
112+ _otherLocations = new Dictionary < string , string > ( 4 )
113+ {
114+ [ _firstDisplayType ] = _firstLocation ,
115+ [ _secondDisplayType ] = _secondLocation ,
116+ [ displayType ] = location ,
117+ } ;
118+ _firstDisplayType = null ;
119+ _firstLocation = null ;
120+ _secondDisplayType = null ;
121+ _secondLocation = null ;
122+ }
123+
124+ return this ;
125+ }
126+
127+ /// <summary>
128+ /// Sets the delegate to be executed when the shape is being displayed.
129+ /// </summary>
130+ public ShapeResult Displaying ( Action < ShapeDisplayContext > displaying )
131+ {
132+ _displaying = displaying ;
133+ return this ;
134+ }
135+
136+ /// <summary>
137+ /// Sets the delegate to be executed when the shape is rendered (not cached).
138+ /// </summary>
139+ public ShapeResult Processing ( Func < IShape , Task > processing )
140+ {
141+ _processingAsync = processing ;
142+ return this ;
143+ }
144+
145+ /// <summary>
146+ /// Sets the delegate to be executed when the shape is rendered (not cached).
147+ /// </summary>
148+ public ShapeResult Processing < T > ( Func < T , Task > processing )
149+ {
150+ _processingAsync = shape => processing ? . Invoke ( ( T ) shape ) ;
151+ return this ;
152+ }
153+
154+ /// <summary>
155+ /// Sets the shape name regardless its 'Differentiator'.
156+ /// </summary>
157+ public ShapeResult Name ( string name )
158+ {
159+ _name = name ;
160+ return this ;
161+ }
162+
163+ /// <summary>
164+ /// Sets a discriminator that is used to find the location of the shape when two shapes of the same type are displayed.
165+ /// </summary>
166+ public ShapeResult Differentiator ( string differentiator )
167+ {
168+ _differentiator = differentiator ;
169+ return this ;
170+ }
171+
172+ /// <summary>
173+ /// Adds the specified group identifier to the current shape result and returns the updated result.
174+ /// </summary>
175+ /// <param name="groupId">The group identifier to add.</param>
176+ /// <returns>
177+ /// The current <see cref="ShapeResult"/> instance with the specified group identifier added to its list of group identifiers.
178+ /// </returns>
179+ public ShapeResult OnGroup ( string groupId )
180+ {
181+ _groupIds = StringValues . Concat ( _groupIds , groupId ) ;
182+
183+ return this ;
184+ }
185+
186+ /// <summary>
187+ /// Sets the group identifiers the shape will be rendered in.
188+ /// </summary>
189+ /// <param name="groupIds"></param>
190+ /// <returns></returns>
191+ public ShapeResult OnGroup ( params string [ ] groupIds )
192+ {
193+ ArgumentNullException . ThrowIfNull ( groupIds ) ;
194+
195+ _groupIds = StringValues . Concat ( _groupIds , groupIds ) ;
196+
197+ return this ;
198+ }
199+
200+ /// <summary>
201+ /// Sets the caching properties of the shape to render.
202+ /// </summary>
203+ public ShapeResult Cache ( string cacheId , Action < CacheContext > cache = null )
204+ {
205+ _cacheId = cacheId ;
206+ _cache = cache ;
207+ return this ;
208+ }
209+
210+ /// <summary>
211+ /// Sets a condition that must return true for the shape to render.
212+ /// The condition is only evaluated if the shape has been placed.
213+ /// </summary>
214+ public ShapeResult RenderWhen ( Func < Task < bool > > renderPredicateAsync )
215+ {
216+ _renderPredicateAsync = renderPredicateAsync ;
217+ return this ;
218+ }
219+
220+ public IShape Shape { get ; private set ; }
221+
222+ private Task ApplyImplementationAsync ( BuildShapeContext context , string displayType )
67223 {
68224 // If no location is set from the driver, use the one from the context.
69225 if ( string . IsNullOrEmpty ( _defaultLocation ) )
@@ -77,12 +233,22 @@ private async Task ApplyImplementationAsync(BuildShapeContext context, string di
77233 // Look for mapped display type locations.
78234 if ( _otherLocations != null )
79235 {
80- string displayTypePlacement ;
81- if ( _otherLocations . TryGetValue ( displayType , out displayTypePlacement ) )
236+ if ( _otherLocations . TryGetValue ( displayType , out var displayTypePlacement ) )
82237 {
83238 _defaultLocation = displayTypePlacement ;
84239 }
85240 }
241+ else if ( _firstDisplayType != null )
242+ {
243+ if ( string . Equals ( _firstDisplayType , displayType , StringComparison . Ordinal ) )
244+ {
245+ _defaultLocation = _firstLocation ;
246+ }
247+ else if ( _secondDisplayType != null && string . Equals ( _secondDisplayType , displayType , StringComparison . Ordinal ) )
248+ {
249+ _defaultLocation = _secondLocation ;
250+ }
251+ }
86252
87253 // If no placement is found, use the default location.
88254 placement ??= new PlacementInfo
@@ -99,7 +265,7 @@ private async Task ApplyImplementationAsync(BuildShapeContext context, string di
99265 // If the placement should be hidden, then stop rendering execution.
100266 if ( placement . IsHidden ( ) )
101267 {
102- return ;
268+ return Task . CompletedTask ;
103269 }
104270
105271 // Parse group placement.
@@ -111,7 +277,7 @@ private async Task ApplyImplementationAsync(BuildShapeContext context, string di
111277 OnGroup ( groupId ) ;
112278 }
113279
114- bool hasGroupConstraints = ! StringValues . IsNullOrEmpty ( _groupIds ) ;
280+ var hasGroupConstraints = ! StringValues . IsNullOrEmpty ( _groupIds ) ;
115281
116282 // If no specific group is requested, use "" as it represents "any group" when applied on a shape.
117283 // This allows to render shapes when no shape constraints are set and also on specific groups.
@@ -120,17 +286,41 @@ private async Task ApplyImplementationAsync(BuildShapeContext context, string di
120286 // If the shape's group doesn't match the currently rendered one, return.
121287 if ( hasGroupConstraints && ! _groupIds . Contains ( requestedGroup , StringComparer . OrdinalIgnoreCase ) )
122288 {
123- return ;
289+ return Task . CompletedTask ;
124290 }
125291
126292 // If we try to render the shape without a group, but we require one, don't render it
127293 if ( ! hasGroupConstraints && ! string . IsNullOrEmpty ( context . GroupId ) )
128294 {
129- return ;
295+ return Task . CompletedTask ;
130296 }
131297
132298 // If a condition has been applied to this result evaluate it only if the shape has been placed.
133- if ( _renderPredicateAsync != null && ! await _renderPredicateAsync ( ) )
299+ if ( _renderPredicateAsync != null )
300+ {
301+ var renderPredicateTask = _renderPredicateAsync ( ) ;
302+
303+ if ( renderPredicateTask . IsCompletedSuccessfully )
304+ {
305+ if ( ! renderPredicateTask . Result )
306+ {
307+ return Task . CompletedTask ;
308+ }
309+
310+ // Predicate evaluated synchronously and returned true, continue without task.
311+ return BuildAndAddShapeAsync ( context , displayType , placement , renderPredicateTask : null ) ;
312+ }
313+
314+ // Predicate needs async evaluation, pass task to be awaited later.
315+ return BuildAndAddShapeAsync ( context , displayType , placement , renderPredicateTask ) ;
316+ }
317+
318+ return BuildAndAddShapeAsync ( context , displayType , placement , renderPredicateTask : null ) ;
319+ }
320+
321+ private async Task BuildAndAddShapeAsync ( BuildShapeContext context , string displayType , PlacementInfo placement , Task < bool > renderPredicateTask )
322+ {
323+ if ( renderPredicateTask != null && ! await renderPredicateTask )
134324 {
135325 return ;
136326 }
@@ -226,126 +416,9 @@ private async Task ApplyImplementationAsync(BuildShapeContext context, string di
226416 }
227417 }
228418
229- position = ! string . IsNullOrEmpty ( position ) ? position : null ;
230-
231419 if ( parentShape is Shape shape )
232420 {
233421 await shape . AddAsync ( newShape , position ) ;
234422 }
235423 }
236-
237- /// <summary>
238- /// Sets the prefix of the form elements rendered in the shape.
239- /// </summary>
240- /// <remarks>
241- /// The goal is to isolate each shape when edited together.
242- /// </remarks>
243- public ShapeResult Prefix ( string prefix )
244- {
245- _prefix = prefix ;
246- return this ;
247- }
248-
249- /// <summary>
250- /// Sets the default location of the shape when no specific placement applies.
251- /// </summary>
252- public ShapeResult Location ( string location )
253- {
254- _defaultLocation = location ;
255- return this ;
256- }
257-
258- /// <summary>
259- /// Sets the location to use for a matching display type.
260- /// </summary>
261- public ShapeResult Location ( string displayType , string location )
262- {
263- _otherLocations ??= new Dictionary < string , string > ( 2 ) ;
264- _otherLocations [ displayType ] = location ;
265- return this ;
266- }
267-
268- /// <summary>
269- /// Sets the delegate to be executed when the shape is being displayed.
270- /// </summary>
271- public ShapeResult Displaying ( Action < ShapeDisplayContext > displaying )
272- {
273- _displaying = displaying ;
274-
275- return this ;
276- }
277-
278- /// <summary>
279- /// Sets the delegate to be executed when the shape is rendered (not cached).
280- /// </summary>
281- public ShapeResult Processing ( Func < IShape , Task > processing )
282- {
283- _processingAsync = processing ;
284-
285- return this ;
286- }
287-
288- /// <summary>
289- /// Sets the delegate to be executed when the shape is rendered (not cached).
290- /// </summary>
291- public ShapeResult Processing < T > ( Func < T , Task > processing )
292- {
293- _processingAsync = shape => processing ? . Invoke ( ( T ) shape ) ;
294-
295- return this ;
296- }
297-
298- /// <summary>
299- /// Sets the shape name regardless its 'Differentiator'.
300- /// </summary>
301- public ShapeResult Name ( string name )
302- {
303- _name = name ;
304- return this ;
305- }
306-
307- /// <summary>
308- /// Sets a discriminator that is used to find the location of the shape when two shapes of the same type are displayed.
309- /// </summary>
310- public ShapeResult Differentiator ( string differentiator )
311- {
312- _differentiator = differentiator ;
313- return this ;
314- }
315-
316- /// <summary>
317- /// Sets the group identifiers the shape will be rendered in.
318- /// </summary>
319- /// <param name="groupIds"></param>
320- /// <returns></returns>
321- public ShapeResult OnGroup ( params string [ ] groupIds )
322- {
323- ArgumentNullException . ThrowIfNull ( groupIds ) ;
324-
325- _groupIds = StringValues . Concat ( _groupIds , groupIds ) ;
326-
327- return this ;
328- }
329-
330- /// <summary>
331- /// Sets the caching properties of the shape to render.
332- /// </summary>
333- public ShapeResult Cache ( string cacheId , Action < CacheContext > cache = null )
334- {
335- _cacheId = cacheId ;
336- _cache = cache ;
337- return this ;
338- }
339-
340- /// <summary>
341- /// Sets a condition that must return true for the shape to render.
342- /// The condition is only evaluated if the shape has been placed.
343- /// </summary>
344- public ShapeResult RenderWhen ( Func < Task < bool > > renderPredicateAsync )
345- {
346- _renderPredicateAsync = renderPredicateAsync ;
347- return this ;
348- }
349-
350- public IShape Shape { get ; private set ; }
351424}
0 commit comments