Skip to content

Commit

Permalink
feat: importing themes (SoftwareBrothers#1403)
Browse files Browse the repository at this point in the history
Handle AdminJS themes with @adminjs/themes

---------

Co-authored-by: Riley Bąkowska <[email protected]>
Co-authored-by: Arian Sobczak <[email protected]>
  • Loading branch information
RiledUpCrow and ariansobczak-rst authored Apr 4, 2023
1 parent 589bd60 commit e6f1970
Show file tree
Hide file tree
Showing 65 changed files with 800 additions and 582 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"types": "./index.d.ts",
"import": "./index.js",
"require": "./index.js"
},
"./bundler": {
"import": "./lib/backend/bundler/config.js"
}
},
"scripts": {
Expand Down Expand Up @@ -84,7 +87,7 @@
},
"homepage": "https://github.com/SoftwareBrothers/adminjs#readme",
"dependencies": {
"@adminjs/design-system": "^4.0.0-beta-v4.7",
"@adminjs/design-system": "^4.0.0-beta-v4.8",
"@babel/core": "^7.21.0",
"@babel/parser": "^7.21.0",
"@babel/plugin-syntax-import-assertions": "^7.20.0",
Expand Down
130 changes: 66 additions & 64 deletions src/adminjs-options.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,57 +184,41 @@ export interface AdminJSOptions {
*/
env?: Record<string, string>;

/* cspell: disable */

/**
* Translation file. Change it in order to:
* Translations
*
* Change it in order to:
* - localize admin panel
* - change any arbitrary text in the UI
*
* This is the example for changing name of a couple of resources along with some
* properties to Polish
* properties to Polish. You can also use this technic to change any text even in english.
* So to change button label of a "new action" from default "Create new" to "Create new Comment"
* only for Comment resource, place it in action section.
*
* ```javascript
* {
* ...
* locale: {
* language: 'pl',
* translations: {
* labels: {
* Comments: 'Komentarze',
* }
* resources: {
* Comments: {
* properties: {
* name: 'Nazwa Komentarza',
* content: 'Zawartość',
* locale: {
* translations: {
* pl: {
* labels: {
* Comments: "Komentarze",
* },
* resources: {
* Comments: {
* properties: {
* name: "Nazwa Komentarza",
* content: "Zawartość",
* },
* actions: {
* new: 'Create new Comment'
* }
* }
* }
* }
* }
* }
* ```
*
* As I mentioned you can use this technic to change any text even in english.
* So to change button label for a "new action" from default "Create new" to "Create new Comment"
* only for Comment resource you can do:
*
* ```javascript
* {
* ...
* locale: {
* translations: {
* resources: {
* Comments: {
* actions: {
* new: 'Create new Comment',
* }
* }
* }
* }
* }
* }
* }
* }
* }
* }
* }
*}
* ```
*
* Check out the [i18n tutorial]{@tutorial i18n} to see how
Expand All @@ -251,6 +235,25 @@ export interface AdminJSOptions {
* Additional settings.
*/
settings?: Partial<AdminJSSettings>;

/**
* List of available themes, for example exports of the `@adminjs/themes` npm package.
*/
availableThemes?: ThemeConfig[];

/**
* ID of the default theme. If not provided, the first theme from the `availableThemes`
* list will be used.
*/
defaultTheme?: string;
}

export type ThemeConfig = {
id: string,
name: string,
overrides: Partial<ThemeOverride>;
bundlePath?: string;
stylePath?: string;
}

export type AdminJSSettings = {
Expand Down Expand Up @@ -282,7 +285,7 @@ export type Assets = {
* Mapping of core scripts in case you want to version your assets
*/
coreScripts?: CoreScripts;
}
};

/**
* @alias AssetsFunction
Expand All @@ -292,7 +295,7 @@ export type Assets = {
* @description
* Function returning {@link Assets}
*/
export type AssetsFunction = (admin?: CurrentAdmin) => Assets | Promise<Assets>
export type AssetsFunction = (admin?: CurrentAdmin) => Assets | Promise<Assets>;

/**
* Version Props
Expand All @@ -309,12 +312,12 @@ export type VersionSettings = {
* You can pass here your current API version.
*/
app?: string;
}
};

export type VersionProps = {
admin?: string;
app?: string;
}
};

/**
* Branding Options
Expand Down Expand Up @@ -358,7 +361,7 @@ export type BrandingOptions = {
* URL to a favicon
*/
favicon?: string;
}
};

/**
* Branding Options Function
Expand All @@ -370,8 +373,8 @@ export type BrandingOptions = {
* @returns {BrandingOptions | Promise<BrandingOptions>}
*/
export type BrandingOptionsFunction = (
admin?: CurrentAdmin
) => BrandingOptions | Promise<BrandingOptions>
admin?: CurrentAdmin,
) => BrandingOptions | Promise<BrandingOptions>;

/**
* Object describing regular page in AdminJS
Expand All @@ -393,15 +396,15 @@ export type AdminPage = {
* Page icon
*/
icon?: string;
}
};

/**
* Object describing map of regular pages in AdminJS
*
* @alias AdminPages
* @memberof AdminJSOptions
*/
export type AdminPages = Record<string, AdminPage>
export type AdminPages = Record<string, AdminPage>;

/**
* Default way of passing Options with a Resource
Expand All @@ -412,7 +415,7 @@ export type ResourceWithOptions = {
resource: any;
options: ResourceOptions;
features?: Array<FeatureType>;
}
};

/**
* Function taking {@link ResourceOptions} and merging it with all other options
Expand All @@ -430,20 +433,16 @@ export type FeatureType = (
/**
* Options returned by the feature added before
*/
options: ResourceOptions
) => ResourceOptions
options: ResourceOptions,
) => ResourceOptions;

/**
* Function which is invoked when user enters given AdminPage
*
* @alias PageHandler
* @memberof AdminJSOptions
*/
export type PageHandler = (
request: any,
response: any,
context: PageContext,
) => Promise<any>
export type PageHandler = (request: any, response: any, context: PageContext) => Promise<any>;

/**
* Bundle options
Expand All @@ -462,17 +461,20 @@ export type BundlerOptions = {
* The file path to babel config file or json object of babel config.
*/
babelConfig?: BabelConfig | string;
}
};

export interface AdminJSOptionsWithDefault extends AdminJSOptions {
rootPath: string;
logoutPath: string;
loginPath: string;
databases?: Array<BaseDatabase>;
resources?: Array<BaseResource | {
resource: BaseResource;
options: ResourceOptions;
}>;
resources?: Array<
| BaseResource
| {
resource: BaseResource;
options: ResourceOptions;
}
>;
dashboard: {
handler?: PageHandler;
component?: string;
Expand Down
17 changes: 17 additions & 0 deletions src/adminjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import { ListActionResponse } from './backend/actions/list/list-action.js'
import { defaultLocale, Locale } from './locale/index.js'
import { TranslateFunctions } from './utils/translate-functions.factory.js'
import { relativeFilePathResolver } from './utils/file-resolver.js'
import { Router } from './backend/utils/index.js'
import { ComponentLoader } from './backend/utils/component-loader.js'
import { OverridableComponent } from './frontend/index.js'
import { bundlePath, stylePath } from './utils/theme-bundler.js'

const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'))
Expand Down Expand Up @@ -123,6 +125,8 @@ class AdminJS {

const resourcesFactory = new ResourcesFactory(this, global.RegisteredAdapters || [])
this.resources = resourcesFactory.buildResources({ databases, resources })

this.addThemeAssets()
}

/**
Expand Down Expand Up @@ -299,6 +303,19 @@ class AdminJS {
return name
}

addThemeAssets() {
this.options.availableThemes?.forEach((theme) => {
Router.assets.push({
path: `/frontend/assets/themes/${theme.id}/theme.bundle.js`,
src: theme.bundlePath ?? bundlePath(theme.id),
})
Router.assets.push({
path: `/frontend/assets/themes/${theme.id}/style.css`,
src: theme.stylePath ?? stylePath(theme.id),
})
})
}

private static __unsafe_componentIndex = 0

public static __unsafe_staticComponentLoader = new ComponentLoader()
Expand Down
42 changes: 22 additions & 20 deletions src/backend/utils/options-parser/options-parser.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,24 @@
import merge from 'lodash/merge.js'

import AdminJS from '../../../adminjs.js'
import { AdminJSOptions, Assets, BrandingOptions } from '../../../adminjs-options.interface.js'
import AdminJS from '../../../adminjs.js'
import { CurrentAdmin } from '../../../current-admin.interface.js'
import { ThemeInState } from '../../../index.js'
import { Locale, defaultLocale } from '../../../locale/index.js'
import ViewHelpers from '../view-helpers/view-helpers.js'
import { defaultLocale, Locale } from '../../../locale/index.js'

const defaultBranding: AdminJSOptions['branding'] = {
companyName: 'Company',
withMadeWithLove: true,
}
const defaultAssets = {
const defaultAssets: Assets = {
styles: [],
scripts: [],
}

export const getAssets = async (
admin: AdminJS,
currentAdmin?: CurrentAdmin,
): Promise<Assets> => {
export const getAssets = async (admin: AdminJS, currentAdmin?: CurrentAdmin): Promise<Assets> => {
const { assets } = admin.options || {}
const computed = typeof assets === 'function'
? await assets(currentAdmin)
: assets
const computed = typeof assets === 'function' ? await assets(currentAdmin) : assets

return merge({}, defaultAssets, computed)
}
Expand All @@ -36,9 +32,7 @@ export const getBranding = async (
const h = new ViewHelpers(admin)
const defaultLogo = h.assetPath('logo.svg')

const computed = typeof branding === 'function'
? await branding(currentAdmin)
: branding
const computed = typeof branding === 'function' ? await branding(currentAdmin) : branding
const merged = merge({}, defaultBranding, computed)

// checking for undefined because logo can also be `false` or `null`
Expand All @@ -47,18 +41,26 @@ export const getBranding = async (
return merged
}

export const getLocales = async (
admin: AdminJS,
currentAdmin?: CurrentAdmin,
): Promise<Locale> => {
export const getLocales = async (admin: AdminJS, currentAdmin?: CurrentAdmin): Promise<Locale> => {
const { locale } = admin.options || {}
const computed = typeof locale === 'function'
? await locale(currentAdmin)
: locale
const computed = typeof locale === 'function' ? await locale(currentAdmin) : locale

return merge({}, defaultLocale, computed)
}

export const getTheme = async (
admin: AdminJS,
currentAdmin?: CurrentAdmin,
): Promise<ThemeInState> => {
const { availableThemes, defaultTheme } = admin.options
let themeId = defaultTheme ?? availableThemes?.[0].id
if (currentAdmin?.theme?.length) {
themeId = currentAdmin?.theme
}
const theme = availableThemes?.find(({ id }) => id === themeId)
return theme ? { ...theme, availableThemes } : null
}

export const getFaviconFromBranding = (branding: BrandingOptions): string => {
if (branding.favicon) {
const { favicon } = branding
Expand Down
Loading

0 comments on commit e6f1970

Please sign in to comment.