Passed
Push — trunk ( 878ba9...990204 )
by Christian
11:09 queued 12s
created

tabs.init.ts ➔ getParentRoute   B

Complexity

Conditions 8

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 17
dl 0
loc 25
rs 7.3333
c 0
b 0
f 0
1
/**
2
 * @package admin
3
 */
4
5
// eslint-disable-next-line import/no-named-default
6
import type { Route, RouteConfig, RawLocation, default as Router } from 'vue-router';
7
import type { TabItemEntry } from 'src/app/state/tabs.store';
8
9
/**
10
 * @deprecated tag:v6.6.0 - Will be private
11
 */
12
export default function initializeTabs(): void {
13
    Shopware.ExtensionAPI.handle('uiTabsAddTabItem', (componentConfig) => {
14
        Shopware.State.commit('tabs/addTabItem', componentConfig);
15
16
        // if current route does not exist check if they exists after adding the route
17
        const router = Shopware.Application.view?.router;
18
19
        /* istanbul ignore next */
20
        if (
21
            router &&
22
            router.currentRoute.fullPath.includes(componentConfig.componentSectionId) &&
23
            router.currentRoute.matched.length <= 0
24
        ) {
25
            createRouteForTabItem(router.currentRoute, router, () => undefined);
26
        }
27
    });
28
29
    /* istanbul ignore next */
30
    void Shopware.Application.viewInitialized.then(() => {
31
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
32
        const router = Shopware.Application.view!.router!;
33
34
        if (router && router.currentRoute.matched.length <= 0) {
35
            createRouteForTabItem(router.currentRoute, router, () => undefined);
36
        }
37
38
        /* istanbul ignore next */
39
        router.beforeEach((to, from, next) => {
40
            if (to.matched.length > 0) {
41
                next();
42
                return;
43
            }
44
45
            const routeSuccess = createRouteForTabItem(to, router, next);
46
47
            // only resolve route if it was created
48
            if (routeSuccess) {
49
                next(router.resolve(to.fullPath).route as RawLocation);
50
            } else {
51
                next();
52
            }
53
        });
54
    });
55
}
56
57
/* istanbul ignore next */
58
function createRouteForTabItem(to: Route, router: Router, next: () => void): boolean {
59
    /**
60
     * Create new route for the url if it matches a tab extension
61
     */
62
    let matchingTabItemConfig: undefined | TabItemEntry;
63
64
    Object.values(Shopware.State.get('tabs').tabItems).find((tabItemConfigs) => {
65
        const _matchingTabItemConfig = tabItemConfigs.find((tabItemConfig) => {
66
            return to.fullPath.endsWith(tabItemConfig.componentSectionId);
67
        });
68
69
        if (_matchingTabItemConfig) {
70
            matchingTabItemConfig = _matchingTabItemConfig;
71
            return true;
72
        }
73
74
        return false;
75
    });
76
77
    if (!matchingTabItemConfig) {
78
        next();
79
        return false;
80
    }
81
82
    const dynamicPath = getDynamicPath(to.fullPath, router);
83
    const parentRoute = getParentRoute(dynamicPath, router as Router & { options?: { routes: RouteConfig[] } });
84
85
    if (parentRoute && parentRoute?.children === undefined) {
86
        parentRoute.children = [];
87
    }
88
89
    if (parentRoute && parentRoute.children) {
90
        const firstChild = parentRoute.children[0];
91
        const newRouteName = `${parentRoute.name ?? ''}.${matchingTabItemConfig.componentSectionId}`;
92
93
        const routeAlreadyExists = router.match({
94
            name: newRouteName,
95
        }).matched.some((route) => route.name === newRouteName);
96
97
        if (!routeAlreadyExists) {
98
            router.addRoute(parentRoute.name, {
99
                path: dynamicPath,
100
                // @ts-expect-error
101
                component: Shopware.Application.view.getComponent('sw-extension-component-section'),
102
                name: newRouteName,
103
                meta: {
104
                    // eslint-disable-next-line max-len
105
                    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
106
                    parentPath: firstChild?.meta?.parentPath ?? '',
107
                },
108
                isChildren: true,
109
                props: {
110
                    'position-identifier': matchingTabItemConfig.componentSectionId,
111
                },
112
            });
113
        }
114
    }
115
116
    return !!parentRoute?.children;
117
}
118
119
/* istanbul ignore next */
120
function getDynamicPath(childPath: string, router: Router): string {
121
    /**
122
     * Replace childPath static values with dynamic values
123
     *
124
     * Before: "sw/product/detail/f6167bd4a9c1438c88c3bcc4949809e9/my-awesome-app-example-product-view"
125
     * After: "/sw/product/detail/:id/my-awesome-app-example-product-view"
126
     */
127
    const resolvedParentRoute = router.resolve(childPath.split('/').slice(0, -1).join('/')).route;
128
129
    return Object.entries(resolvedParentRoute.params).reduce<string>((acc, [paramKey, paramValue]) => {
130
        return acc.replace(paramValue, `:${paramKey}?`);
131
    }, childPath);
132
}
133
134
/* istanbul ignore next */
135
// eslint-disable-next-line max-len
136
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-non-null-assertion */
137
function getParentRoute(
138
    dynamicPath: string,
139
    router: Router & { options?: { routes: RouteConfig[] } },
140
): RouteConfig|undefined {
141
    const { routes } = router.options;
142
143
    const match = router.match(dynamicPath);
144
145
    // Get the matching object in the routes definition
146
    const routeForMatch = findMatchingRoute(routes, (route) => {
147
        return match.name === route.name;
148
    });
149
150
    // Get the parent route path
151
    const routeForMatchResolved = router.match(routeForMatch?.path ?? '');
152
    const parent = routeForMatchResolved.matched[routeForMatchResolved.matched.length - 1]?.parent;
153
    const matchingParentRoute = parent ?? match;
154
155
    // Get the matching object for the parent route in the routes definition
156
    return findMatchingRoute(routes, (route) => {
157
        return matchingParentRoute?.path === route.path;
158
    });
159
}
160
161
/* istanbul ignore next */
162
function findMatchingRoute(
163
    routes: RouteConfig[],
164
    conditionCheck: (route: RouteConfig) => boolean,
165
): RouteConfig|undefined {
166
    const flattenRoutesDeep = (_routes: RouteConfig[]): RouteConfig[] => {
167
        return _routes.flatMap((route) => {
168
            const children = route.children ?? [];
169
            return [route, ...flattenRoutesDeep(children)];
170
        });
171
    };
172
173
    return flattenRoutesDeep(routes).find(conditionCheck);
174
}
175