Skip to content

Commit 50b8ec4

Browse files
committed
feat: theme and layout indexer
1 parent 7110b46 commit 50b8ec4

File tree

7 files changed

+429
-1
lines changed

7 files changed

+429
-1
lines changed

src/indexer/IndexManager.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import TemplateIndexer from './template/TemplateIndexer';
2020
import { TemplateIndexData } from './template/TemplateIndexData';
2121
import CronIndexer from './cron/CronIndexer';
2222
import { CronIndexData } from './cron/CronIndexData';
23+
import LayoutIndexer from './layout/LayoutIndexer';
24+
import { LayoutIndexData } from './layout/LayoutIndexData';
25+
import ThemeIndexer from './theme/ThemeIndexer';
26+
import { ThemeIndexData } from './theme/ThemeIndexData';
2327

2428
type IndexerInstance =
2529
| DiIndexer
@@ -28,7 +32,9 @@ type IndexerInstance =
2832
| EventsIndexer
2933
| AclIndexer
3034
| TemplateIndexer
31-
| CronIndexer;
35+
| CronIndexer
36+
| LayoutIndexer
37+
| ThemeIndexer;
3238

3339
type IndexerDataMap = {
3440
[DiIndexer.KEY]: DiIndexData;
@@ -38,6 +44,8 @@ type IndexerDataMap = {
3844
[AclIndexer.KEY]: AclIndexData;
3945
[TemplateIndexer.KEY]: TemplateIndexData;
4046
[CronIndexer.KEY]: CronIndexData;
47+
[ThemeIndexer.KEY]: ThemeIndexData;
48+
[LayoutIndexer.KEY]: LayoutIndexData;
4149
};
4250

4351
class IndexManager {
@@ -56,6 +64,8 @@ class IndexManager {
5664
new AclIndexer(),
5765
new TemplateIndexer(),
5866
new CronIndexer(),
67+
new ThemeIndexer(),
68+
new LayoutIndexer(),
5969
];
6070
this.indexStorage = new IndexStorage();
6171
}
@@ -203,6 +213,12 @@ class IndexManager {
203213
case CronIndexer.KEY:
204214
return new CronIndexData(data) as IndexerDataMap[T];
205215

216+
case ThemeIndexer.KEY:
217+
return new ThemeIndexData(data) as IndexerDataMap[T];
218+
219+
case LayoutIndexer.KEY:
220+
return new LayoutIndexData(data) as IndexerDataMap[T];
221+
206222
default:
207223
return undefined;
208224
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Memoize } from 'typescript-memoize';
2+
import { Layout } from './types';
3+
import { AbstractIndexData } from 'indexer/AbstractIndexData';
4+
import LayoutIndexer from './LayoutIndexer';
5+
6+
export class LayoutIndexData extends AbstractIndexData<Layout> {
7+
@Memoize({
8+
tags: [LayoutIndexer.KEY],
9+
})
10+
public getLayouts(): Layout[] {
11+
return Array.from(this.data.values()).flat();
12+
}
13+
}
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import { RelativePattern, Uri } from 'vscode';
2+
import { Indexer } from 'indexer/Indexer';
3+
import { IndexerKey } from 'types/indexer';
4+
import { Layout } from './types';
5+
import { XMLParser } from 'fast-xml-parser';
6+
import FileSystem from 'util/FileSystem';
7+
import IndexManager from 'indexer/IndexManager';
8+
import ThemeIndexer from 'indexer/theme/ThemeIndexer';
9+
import { Theme } from 'indexer/theme/types';
10+
11+
export default class LayoutIndexer extends Indexer<Layout> {
12+
public static readonly KEY = 'layout';
13+
14+
private xmlParser: XMLParser;
15+
16+
public constructor() {
17+
super();
18+
19+
this.xmlParser = new XMLParser({
20+
ignoreAttributes: false,
21+
attributeNamePrefix: '@_',
22+
isArray: () => {
23+
return true;
24+
},
25+
});
26+
}
27+
28+
public getVersion(): number {
29+
return 1;
30+
}
31+
32+
public getId(): IndexerKey {
33+
return LayoutIndexer.KEY;
34+
}
35+
36+
public getName(): string {
37+
return 'layout';
38+
}
39+
40+
public getPattern(uri: Uri): RelativePattern {
41+
return new RelativePattern(uri, '**/layout/*.xml');
42+
}
43+
44+
public async indexFile(uri: Uri): Promise<Layout | undefined> {
45+
const xml = await FileSystem.readFile(uri);
46+
const parsed = this.xmlParser.parse(xml);
47+
48+
const pageNode = Array.isArray(parsed?.page) ? parsed.page[0] : undefined;
49+
50+
if (!pageNode) {
51+
return undefined;
52+
}
53+
54+
const getAttr = (node: any, name: string): string | undefined => {
55+
const value = node?.[`@_${name}`];
56+
if (Array.isArray(value)) {
57+
return value[0];
58+
}
59+
return value;
60+
};
61+
62+
const getBoolAttr = (node: any, name: string): boolean | undefined => {
63+
const raw = getAttr(node, name);
64+
if (raw === undefined) {
65+
return undefined;
66+
}
67+
const lowered = String(raw).toLowerCase();
68+
if (lowered === 'true' || lowered === '1') {
69+
return true;
70+
}
71+
if (lowered === 'false' || lowered === '0') {
72+
return false;
73+
}
74+
return undefined;
75+
};
76+
77+
const getNumAttr = (node: any, name: string): number | undefined => {
78+
const raw = getAttr(node, name);
79+
if (raw === undefined) {
80+
return undefined;
81+
}
82+
const n = Number(raw);
83+
return Number.isFinite(n) ? n : undefined;
84+
};
85+
86+
const mapUiComponents = (nodes: any[] | undefined) => {
87+
if (!Array.isArray(nodes)) {
88+
return [];
89+
}
90+
return nodes.map(node => ({
91+
name: getAttr(node, 'name'),
92+
component: getAttr(node, 'component'),
93+
as: getAttr(node, 'as'),
94+
ttl: getNumAttr(node, 'ttl'),
95+
group: getAttr(node, 'group'),
96+
acl: getAttr(node, 'acl'),
97+
cacheable: getBoolAttr(node, 'cacheable'),
98+
}));
99+
};
100+
101+
const mapBlocks = (nodes: any[] | undefined): any[] => {
102+
if (!Array.isArray(nodes)) {
103+
return [];
104+
}
105+
return nodes.map(node => ({
106+
name: getAttr(node, 'name'),
107+
class: getAttr(node, 'class'),
108+
cacheable: getBoolAttr(node, 'cacheable'),
109+
as: getAttr(node, 'as'),
110+
ttl: getNumAttr(node, 'ttl'),
111+
group: getAttr(node, 'group'),
112+
acl: getAttr(node, 'acl'),
113+
block: mapBlocks(node.block),
114+
container: mapContainers(node.container),
115+
referenceBlock: mapReferenceBlocks(node.referenceBlock),
116+
uiComponent: mapUiComponents(node.uiComponent),
117+
}));
118+
};
119+
120+
const mapReferenceBlocks = (nodes: any[] | undefined): any[] => {
121+
if (!Array.isArray(nodes)) {
122+
return [];
123+
}
124+
return nodes.map(node => ({
125+
name: getAttr(node, 'name') as string,
126+
template: getAttr(node, 'template'),
127+
class: getAttr(node, 'class'),
128+
group: getAttr(node, 'group'),
129+
display: getBoolAttr(node, 'display'),
130+
remove: getBoolAttr(node, 'remove'),
131+
block: mapBlocks(node.block),
132+
referenceBlock: mapReferenceBlocks(node.referenceBlock),
133+
uiComponent: mapUiComponents(node.uiComponent),
134+
container: mapContainers(node.container),
135+
}));
136+
};
137+
138+
const mapContainers = (nodes: any[] | undefined): any[] => {
139+
if (!Array.isArray(nodes)) {
140+
return [];
141+
}
142+
return nodes.map(node => ({
143+
name: getAttr(node, 'name') as string,
144+
after: getAttr(node, 'after'),
145+
before: getAttr(node, 'before'),
146+
block: mapBlocks(node.block),
147+
referenceBlock: mapReferenceBlocks(node.referenceBlock),
148+
uiComponent: mapUiComponents(node.uiComponent),
149+
container: mapContainers(node.container),
150+
}));
151+
};
152+
153+
const mapMoves = (nodes: any[] | undefined) => {
154+
if (!Array.isArray(nodes)) {
155+
return [];
156+
}
157+
return nodes.map(node => ({
158+
element: getAttr(node, 'element') as string,
159+
destination: getAttr(node, 'destination') as string,
160+
as: getAttr(node, 'as'),
161+
after: getAttr(node, 'after'),
162+
before: getAttr(node, 'before'),
163+
}));
164+
};
165+
166+
const bodyNode = Array.isArray(pageNode.body) ? pageNode.body[0] : undefined;
167+
const body = bodyNode
168+
? {
169+
block: mapBlocks(bodyNode.block),
170+
referenceBlock: mapReferenceBlocks(bodyNode.referenceBlock),
171+
uiComponent: mapUiComponents(bodyNode.uiComponent),
172+
container: mapContainers(bodyNode.container),
173+
move: mapMoves(bodyNode.move),
174+
}
175+
: { block: [], referenceBlock: [], uiComponent: [], container: [], move: [] };
176+
177+
const page = {
178+
update: Array.isArray(pageNode.update)
179+
? pageNode.update.map((u: any) => ({
180+
handle: getAttr(u, 'handle') as string,
181+
}))
182+
: [],
183+
body: [body],
184+
};
185+
186+
const path = uri.fsPath;
187+
const p = path.replace(/\\/g, '/');
188+
189+
let area = 'base';
190+
if (p.includes('/view/frontend/layout/') || p.includes('/app/design/frontend/')) {
191+
area = 'frontend';
192+
} else if (p.includes('/view/adminhtml/layout/') || p.includes('/app/design/adminhtml/')) {
193+
area = 'adminhtml';
194+
} else if (p.includes('/view/base/layout/') || p.includes('/app/design/base/')) {
195+
area = 'base';
196+
}
197+
198+
const theme = this.getTheme(path);
199+
200+
const layout: Layout = {
201+
area,
202+
theme: theme?.title ?? '-',
203+
path,
204+
page,
205+
};
206+
207+
return layout;
208+
}
209+
210+
private getTheme(path: string): Theme | undefined {
211+
const themeIndexData = IndexManager.getIndexData(ThemeIndexer.KEY);
212+
213+
return themeIndexData?.getThemeByFilePath(path);
214+
}
215+
}

src/indexer/layout/types.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
interface Block {
2+
name?: string;
3+
class?: string;
4+
cacheable?: boolean;
5+
as?: string;
6+
ttl?: number;
7+
group?: string;
8+
acl?: string;
9+
10+
block: Block[];
11+
container: Container[];
12+
referenceBlock: BlockReference[];
13+
uiComponent: UiComponent[];
14+
}
15+
16+
interface BlockReference extends Block {
17+
name: string;
18+
template?: string;
19+
class?: string;
20+
group?: string;
21+
display?: boolean;
22+
remove?: boolean;
23+
24+
block: Block[];
25+
referenceBlock: BlockReference[];
26+
uiComponent: UiComponent[];
27+
container: Container[];
28+
}
29+
30+
interface Container {
31+
name: string;
32+
after?: string;
33+
before?: string;
34+
35+
block: Block[];
36+
referenceBlock: BlockReference[];
37+
uiComponent: UiComponent[];
38+
container: Container[];
39+
}
40+
41+
interface ContainerReference {
42+
name: string;
43+
remove?: boolean;
44+
display?: boolean;
45+
46+
block: Block[];
47+
referenceBlock: BlockReference[];
48+
uiComponent: UiComponent[];
49+
container: Container[];
50+
}
51+
52+
interface UiComponent {
53+
name?: string;
54+
component?: string;
55+
as?: string;
56+
ttl?: number;
57+
group?: string;
58+
acl?: string;
59+
cacheable?: boolean;
60+
}
61+
62+
interface Update {
63+
handle: string;
64+
}
65+
66+
interface Remove {
67+
name: string;
68+
}
69+
70+
interface Move {
71+
element: string;
72+
destination: string;
73+
as?: string;
74+
after?: string;
75+
before?: string;
76+
}
77+
78+
interface Body {
79+
block: Block[];
80+
referenceBlock: BlockReference[];
81+
uiComponent: UiComponent[];
82+
container: Container[];
83+
move: Move[];
84+
}
85+
86+
interface Page {
87+
update: Update[];
88+
body: Body[];
89+
}
90+
91+
export interface Layout {
92+
area: string;
93+
theme: string;
94+
path: string;
95+
page: Page;
96+
}

0 commit comments

Comments
 (0)