dvStudioRoutes function

List<RouteBase> dvStudioRoutes({
  1. required String mount,
  2. String title = 'Studio',
  3. DVStudioTransport? transport,
  4. void open(
    1. String path
    )?,
  5. Uri location(
    1. GoRouterState state
    )?,
  6. List<String> signInReturns = const <String>[],
  7. DVStudioPageView? view,
  8. DVStudioSplash? splash,
})

Studio's routes at mount (/__studio), titled title: Studio, every one of its screens, its sign-in and its first-run setup.

transport reaches Studio's API; the browser's, at <mount>/api/, by default. open loads a path from the server as a page, which is how a person who has just signed in reaches Studio: the server decides on the grant again before it renders anything. location is the address a Studio page was opened at; the browser's by default. signInReturns are the other mounts Studio's grant guards, such as a documentation site with access: studio, which the sign-in sends a person back to. view is the application's dartvelPagePreview: each page as its route builds it, which is how Studio's canvas draws a page exactly as the site does. splash is the application's splash, drawn while Studio's code loads and its grant is asked, where the first frame was an empty page.

Implementation

List<RouteBase> dvStudioRoutes({
  required String mount,
  String title = 'Studio',
  DVStudioTransport? transport,
  void Function(String path)? open,
  Uri Function(GoRouterState state)? location,
  List<String> signInReturns = const <String>[],
  DVStudioPageView? view,
  DVStudioSplash? splash,
}) {
  // A transport and nothing more on this side of the deferred imports: an
  // object of Studio's made here would bring every method it has into
  // main.dart.js, and with them the code they reach. Each library makes its
  // own client from it.
  final DVStudioTransport send =
      transport ?? dvStudioBrowserTransport(base: '$mount/');
  final String login = '$mount/login';
  final String setup = '$mount/setup';

  // The Studio grant, asked on every visit rather than remembered: a grant
  // taken away is refused on the next navigation, not the next page load.
  // The server has already refused a stranger before this page existed; this
  // is the same guard for a navigation that never reached the server.
  Future<String?> guard(BuildContext context, GoRouterState state) async {
    if (await dvStudioGranted(send)) return null;
    return Uri(
      path: login,
      queryParameters: <String, String>{'from': state.uri.toString()},
    ).toString();
  }

  /// The screen named [id], with [object] open in it, as a page of the
  /// application: the same shell, the same look, and where the person is
  /// carried by the address, so Back, a reload and a pasted link all land in
  /// the same place.
  Page<void> screen(
    BuildContext context,
    GoRouterState state, {
    required String id,
    String? object,
  }) =>
      NoTransitionPage<void>(
        key: state.pageKey,
        // The application's page views and its look, taken here, under
        // the application's MaterialApp and above Studio's own frame.
        child: _dvStudioPage(
          child: DVStudioHost(
            view: view,
            look: DVStudioAppLook.capture(context),
            splash: splash,
            child: DVStudioDeferred(
              load: dartvel_studio.loadLibrary,
              splash: splash,
              builder: (BuildContext context) => dartvel_studio.dvStudioAppFor(
                transport: send,
                title: title,
                mount: mount,
                screen: dvStudioScreenAliases[id] ?? id,
                object: object,
                location: location?.call(state),
                open: open,
                // Navigated to, not pushed: the address bar follows the
                // screen as a history entry of its own, so Back walks back
                // through the screens and the objects a person opened, which
                // is what Back does in every tool Studio is measured against.
                onSelect: (String section, String? chosen) =>
                    _openStudio(router: GoRouter.of(context), mount: mount, section: section, object: chosen),
              ),
            ),
          ),
        ),
      );

  return <RouteBase>[
    GoRoute(
      path: mount,
      redirect: guard,
      pageBuilder: (BuildContext context, GoRouterState state) => screen(
        context,
        state,
        // The mount is Pages: `dvStudioTargetFor` names the same screen at
        // `<mount>` as at `<mount>/pages`, and so does this.
        id: _studioScreenIds.first,
      ),
    ),
    GoRoute(
      path: login,
      pageBuilder: (BuildContext context, GoRouterState state) =>
          NoTransitionPage<void>(
            key: state.pageKey,
            child: _dvStudioPage(
              child: DVStudioDeferred(
                load: dartvel_studio_sign_in.loadLibrary,
                splash: splash,
                builder: (BuildContext context) =>
                    dartvel_studio_sign_in.dvStudioSignInFor(
                  transport: send,
                  mount: mount,
                  from: state.uri.queryParameters['from'],
                  title: title,
                  open: open ?? dvOpenUrl,
                  returns: signInReturns,
                ),
              ),
            ),
          ),
    ),
    // The first-run setup: the server sends every page of the mount here
    // until the owner has finished it, and answers this route only then.
    GoRoute(
      path: setup,
      pageBuilder: (BuildContext context, GoRouterState state) =>
          NoTransitionPage<void>(
            key: state.pageKey,
            child: _dvStudioPage(
              child: DVStudioDeferred(
                load: dartvel_studio_sign_in.loadLibrary,
                splash: splash,
                builder: (BuildContext context) =>
                    dartvel_studio_sign_in.dvStudioSetupFor(
                  transport: send,
                  mount: mount,
                  title: title,
                  open: open ?? dvOpenUrl,
                ),
              ),
            ),
          ),
    ),
    // After the two above, which are literal paths and so win: `<mount>/login`
    // is a sign-in, not a screen that happens to be called "login", because
    // go_router matches the first route that fits.
    for (final String id in _studioScreenIds) ...<RouteBase>[
      GoRoute(
        path: '$mount/$id',
        redirect: guard,
        pageBuilder: (BuildContext context, GoRouterState state) =>
            screen(context, state, id: id),
      ),
      GoRoute(
        // The object half, which keeps its slashes: a page is at the route
        // it answers, so `<mount>/pages/shop/product` names that page. The
        // whole rest is one parameter rather than a `*splat`, because
        // go_router matches on the decoded path -- `Uri.path` of
        // `<mount>/pages/shop%2Fproduct` is `<mount>/pages/shop/product` --
        // so the encoded form the server prints and the literal form both
        // land here and name the same page.
        path: '$mount/$id/:object(.*)',
        redirect: guard,
        pageBuilder: (BuildContext context, GoRouterState state) => screen(
              context,
              state,
              id: id,
              object: state.pathParameters['object'],
            ),
      ),
    ],
  ];
}