feat: remove current and apply selected theme

pull/17675/head
Masum ULU 2 years ago
parent e894ee6be4
commit 9000840a8f

@ -4,8 +4,10 @@ import { ChangeThemeGeneratorSchema } from './schema';
import { ThemeOptionsEnum } from './theme-options.enum';
export async function changeThemeGenerator(host: Tree, schema: ChangeThemeGeneratorSchema) {
const schematicPath = schema.localPath || '@abp/ng.schematics';
const runAngularLibrarySchematic = wrapAngularDevkitSchematic(
'@abp/ng.schematics',
schema.localPath ? `${host.root}${schematicPath}` : schematicPath,
'change-theme',
);
@ -14,8 +16,10 @@ export async function changeThemeGenerator(host: Tree, schema: ChangeThemeGenera
});
return () => {
const destTheme = Object.values(ThemeOptionsEnum).find((t, i) => i + 1 === schema.name);
console.log(`✅️ theme changed to ${destTheme}`);
const destTheme = Object.values(ThemeOptionsEnum).find(
(theme, index) => index + 1 === schema.name,
);
console.log(`✅️ Switched to Theme ${destTheme}`);
};
}

@ -1,4 +1,5 @@
export interface ChangeThemeGeneratorSchema {
name: number;
targetOption: string;
localPath?: string;
}

@ -31,6 +31,10 @@
"$source": "argv",
"index": 1
}
},
"localPath": {
"description": "If set value schematics will work on given path",
"type": "string"
}
},
"required": ["name", "targetProject"]

@ -1,22 +1,41 @@
import { Rule, SchematicsException } from '@angular-devkit/schematics';
import { isLibrary, updateWorkspace, WorkspaceDefinition } from '../../utils';
import { allStyles, styleMap } from './style-map';
import { ProjectDefinition } from '@angular-devkit/core/src/workspace';
import { JsonArray, JsonValue } from '@angular-devkit/core';
import { Rule, SchematicsException, Tree, UpdateRecorder, chain } from '@angular-devkit/schematics';
import { ProjectDefinition } from '@angular-devkit/core/src/workspace';
import * as ts from 'typescript';
import { ImportDefinition, allStyles, importMap, styleMap } from './style-map';
import { ChangeThemeOptions } from './model';
import {
addImportToModule,
Change,
InsertChange,
isLibrary,
updateWorkspace,
WorkspaceDefinition,
} from '../../utils';
import { ThemeOptionsEnum } from './theme-options.enum';
import {
//@ts-ignore
findNodes,
getDecoratorMetadata,
getMetadataField,
} from '../../utils/angular/ast-utils';
export default function (_options: ChangeThemeOptions): Rule {
return async () => {
return async (host: Tree) => {
const targetThemeName = _options.name;
const selectedProject = _options.targetProject;
if (!targetThemeName) {
throw new SchematicsException('The theme name does not selected');
}
return updateWorkspace(storedWorkspace => {
updateProjectStyle(selectedProject, storedWorkspace, targetThemeName);
});
return chain([
updateWorkspace(storedWorkspace => {
updateProjectStyle(selectedProject, storedWorkspace, targetThemeName);
}),
updateWorkspace(storedWorkspace => {
updateAppModule(host, selectedProject, storedWorkspace, targetThemeName);
}),
]);
};
}
@ -48,6 +67,132 @@ function updateProjectStyle(
targetOption.styles = [...newStyles, ...sanitizedStyles] as JsonArray;
}
function updateAppModule(
host: Tree,
projectName: string,
workspace: WorkspaceDefinition,
targetThemeName: ThemeOptionsEnum,
) {
const selectedTheme = importMap.get(targetThemeName);
if (!selectedTheme) {
throw new SchematicsException('The theme does not found');
}
const project = workspace.projects.get(projectName);
const appModulePath = `${project?.sourceRoot}/app/app.module.ts`;
const text = host.read(appModulePath);
if (!text) {
throw new SchematicsException('The app module does not found');
}
const sourceText = text.toString('utf-8');
const source = ts.createSourceFile(
appModulePath,
sourceText,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TS,
);
const recorder = host.beginUpdate(appModulePath);
const impMap = Array.from(importMap.values())
?.filter(f => f !== importMap.get(targetThemeName))
.reduce((acc, val) => [...acc, ...val], []);
removeImportPath(source, recorder, impMap);
removeImportFromNgModuleMetadata(source, recorder, impMap);
insertImports(selectedTheme, source, appModulePath, recorder);
host.commitUpdate(recorder);
return host;
}
function insertImports(
selectedTheme: ImportDefinition[],
source: ts.SourceFile,
appModulePath: string,
recorder: UpdateRecorder,
) {
const changes: Change[] = [];
selectedTheme.map(({ importName, path }) =>
changes.push(...addImportToModule(source, appModulePath, importName, path)),
);
if (changes?.length > 0) {
for (const change of changes) {
if (change instanceof InsertChange) {
recorder.insertLeft(change.pos, change.toAdd);
}
}
}
}
function removeImportPath(
source: ts.SourceFile,
recorder: UpdateRecorder,
arr: ImportDefinition[],
) {
const node = findNodes(source, ts.isImportDeclaration);
for (const importMp of arr) {
const importPath = node.find(f => f.getFullText().match(importMp.path));
if (!importPath) {
continue;
}
/**
* We can add comment and sign as `removed` for the see what is removed
*
* recorder.insertLeft(importPath.getStart(), '//');
*/
recorder.remove(importPath.getStart(), importPath.getWidth() + 1);
}
}
function removeImportFromNgModuleMetadata(
source: ts.SourceFile,
recorder: UpdateRecorder,
arr: ImportDefinition[],
) {
/**
* Brings the @NgModule({...}) content
*/
const node = getDecoratorMetadata(source, 'NgModule', '@angular/core')[0] || {};
if (!node) {
throw new SchematicsException('The app module does not found');
}
/**
* Select imports array in the @NgModule({...}) content
*/
const matchingProperties = getMetadataField(node as ts.ObjectLiteralExpression, 'imports');
const assignment = matchingProperties[0] as ts.PropertyAssignment;
const assignmentInit = assignment.initializer as ts.ArrayLiteralExpression;
const elements = assignmentInit.elements;
if (elements?.length < 0) {
return;
}
for (const importMp of arr) {
const willRemoveModule = elements.find(f => f.getText().includes(importMp.importName));
if (!willRemoveModule) {
continue;
}
/**
* We can add comment and sign as `removed` for the see what is removed
*
* recorder.insertLeft(foundModule.getStart(), '//');
*/
recorder.remove(willRemoveModule.getStart(), willRemoveModule.getWidth() + 1);
}
}
export function getProjectTargetOptions(
project: ProjectDefinition,
buildTarget: string,

@ -8,6 +8,11 @@ export type StyleDefinition =
}
| string;
export type ImportDefinition = {
path: string;
importName: string;
};
export const styleMap = new Map<ThemeOptionsEnum, StyleDefinition[]>();
styleMap.set(ThemeOptionsEnum.Basic, [
@ -241,3 +246,49 @@ styleMap.set(ThemeOptionsEnum.LeptonXLite, [
]);
// the code written by Github co-pilot. thank go-pilot. You are the best sidekick.
export const allStyles = Array.from(styleMap.values()).reduce((acc, val) => [...acc, ...val], []);
export const importMap = new Map<ThemeOptionsEnum, ImportDefinition[]>();
importMap.set(ThemeOptionsEnum.Basic, [
{
path: '@abp/ng.theme.basic',
importName: 'ThemeBasicModule.forRoot()',
},
]);
importMap.set(ThemeOptionsEnum.Lepton, [
{
path: '@volo/abp.ng.theme.lepton',
importName: 'ThemeLeptonModule.forRoot()',
},
]);
importMap.set(ThemeOptionsEnum.LeptonXLite, [
{
path: '@abp/ng.theme.lepton-x',
importName: 'ThemeLeptonXModule.forRoot()',
},
{
path: '@abp/ng.theme.lepton-x/layouts',
importName: 'SideMenuLayoutModule.forRoot()',
},
{
path: '@abp/ng.theme.lepton-x/account',
importName: 'AccountLayoutModule.forRoot()',
},
]);
importMap.set(ThemeOptionsEnum.LeptonX, [
{
path: '@volosoft/abp.ng.theme.lepton-x',
importName: 'ThemeLeptonXModule.forRoot()',
},
{
path: '@volosoft/abp.ng.theme.lepton-x/layouts',
importName: 'SideMenuLayoutModule.forRoot()',
},
{
path: '@volosoft/abp.ng.theme.lepton-x/account',
importName: 'AccountLayoutModule.forRoot()',
},
]);

Loading…
Cancel
Save