Skip to content

Commit

Permalink
feat: new theme api -d
Browse files Browse the repository at this point in the history
  • Loading branch information
arpowers committed Nov 27, 2024
1 parent 4effa13 commit f03d031
Show file tree
Hide file tree
Showing 36 changed files with 2,893 additions and 336 deletions.
2 changes: 1 addition & 1 deletion @fiction/admin/test/email.main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function setup(args: { context?: 'node' | 'app' } = {}) {
const { context = 'node' } = args
const mainFilePath = new URL(import.meta.url).pathname

const testUtils = await createSiteTestUtils({ mainFilePath, context, themes: [adminTheme.setup] }) satisfies { fictionAi: FictionAi }
const testUtils = await createSiteTestUtils({ mainFilePath, context, themes: [adminTheme.theme] }) satisfies { fictionAi: FictionAi }

const siteRouter = testUtils.fictionRouter
const component = CardSite
Expand Down
86 changes: 42 additions & 44 deletions @fiction/admin/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { FictionEnv } from '@fiction/core/index.js'
import type { CardFactory } from '@fiction/site/cardFactory.js'
import type { FictionAdmin } from '../index.js'
import { getCardTemplates } from '@fiction/cards'
import { getCardTemplates, templates } from '@fiction/cards'
import { safeDirname, vue } from '@fiction/core/index.js'
import { CardFactory } from '@fiction/site/cardFactory.js'
import { Theme } from '@fiction/site/theme.js'
import favicon from '@fiction/ui/brand/favicon.svg'
import icon from '@fiction/ui/brand/icon.png'
Expand All @@ -15,7 +15,7 @@ export const fictionLogo = `<svg xmlns="http://www.w3.org/2000/svg" fill="none"

export async function getTemplates() {
const tpl = await getCardTemplates()
return [...tpl, dash.template] as const
return [...tpl, dash.template]
}

export async function getPages(args: { factory: CardFactory }) {
Expand Down Expand Up @@ -99,54 +99,52 @@ export async function getPages(args: { factory: CardFactory }) {
]
}

export async function setup(args: { fictionEnv: FictionEnv, fictionAdmin: FictionAdmin }) {
const { fictionEnv, fictionAdmin } = args
const templates = await getTemplates()
const factory = new CardFactory({ templates })
return new Theme({
fictionEnv,
root: safeDirname(import.meta.url),
themeId: 'admin',
title: 'Admin',
version: '1.0.0',
templates,
isPublic: false,
getConfig: async () => {
const pg = await getPages({ factory })
const adminPages = await fictionAdmin.getAdminPages({ factory })
const pages = [...pg, ...adminPages]
return {
pages,
sections: {},
userConfig: {
branding: {
shareImage: { url: shareImage, format: 'image' },
favicon: { url: favicon, format: 'image' },
icon: { url: icon, format: 'image' },
},
styling: {
fonts: {
body: { fontKey: 'Inter', stack: 'sans' },
sans: { fontKey: 'Inter', stack: 'sans' },
},
buttons: { design: 'solid', rounding: 'full', hover: 'fade' },
},
standard: {
spacing: { contentWidth: 'sm', verticalSpacing: `none` },
export const theme = new Theme({
root: safeDirname(import.meta.url),
themeId: 'admin',
title: 'Admin',
version: '1.0.0',
getTemplates: async () => await getTemplates(),
isPublic: false,
getConfig: async (args) => {
const { factory, site } = args
const pg = await getPages({ factory })
const service = site.fictionSites.fictionEnv.getService<{ fictionAdmin: FictionAdmin }>()
const adminPages = await service.fictionAdmin.getAdminPages({ factory })
const pages = [...pg, ...adminPages]
return {
pages,
sections: {},
userConfig: {
branding: {
shareImage: { url: shareImage, format: 'image' },
favicon: { url: favicon, format: 'image' },
icon: { url: icon, format: 'image' },
},
styling: {
fonts: {
body: { fontKey: 'Inter', stack: 'sans' },
sans: { fontKey: 'Inter', stack: 'sans' },
},
buttons: { design: 'solid', rounding: 'full', hover: 'fade' },
},
}
},
templateDefaults: { page: 'dash', transaction: 'transaction' },
userConfig: {
standard: {
spacing: { contentWidth: 'sm', verticalSpacing: `none` },
},
},
}
},
templateDefaults: { page: 'dash', transaction: 'transaction' },
getBaseConfig: () => {
return {
spacing: { contentWidthSize: 'sm', spacingSize: `none` },
branding: {
logo: { format: 'html' as const, html: fictionLogo },
},
},
}
},

})
}
})

// createCard({
// templates,
Expand Down
40 changes: 37 additions & 3 deletions @fiction/cards/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { FictionEnv } from '@fiction/core'
import type { FictionEnv, FictionPluginSettings, FictionRouter } from '@fiction/core'
import type { FictionSites } from '@fiction/site'
import type { CardTemplate } from '@fiction/site/card'
import type { CardFactory } from '@fiction/site/cardFactory'
import type { Site } from '@fiction/site/site.js'
import { envConfig, safeDirname, vue } from '@fiction/core'
import { envConfig, FictionPlugin, safeDirname, vue } from '@fiction/core'
import { cardTemplate } from '@fiction/site/card'
import { z } from 'zod'
import { generateCardStructure } from './utils/generateStructure'

// Register path for tailwindcss to scan for styles
envConfig.register({
Expand All @@ -13,7 +15,7 @@ envConfig.register({
})

// Template imports organized by category
const templates = {
export const templates = {
layout: {
wrap: () => import('./wrap'),
area: () => import('./area'),
Expand Down Expand Up @@ -186,3 +188,35 @@ export async function getDemoPages(args: {

return await Promise.all(demoPagePromises)
}

export type CardsPluginSettings = {
fictionEnv: FictionEnv
fictionSites: FictionSites
fictionRouterSites: FictionRouter
} & FictionPluginSettings

export class FictionCards extends FictionPlugin<CardsPluginSettings> {
constructor(settings: CardsPluginSettings) {
const s = { ...settings, root: safeDirname(import.meta.url) }

super('FictionCards', s)
}

override setup() {
this.addStructureFile()
}

addStructureFile() {
this.fictionEnv.generators.push(async () => {
const cardTemplates = await getCardTemplates()

const results = await generateCardStructure({
templates: cardTemplates,
fictionSites: this.settings.fictionSites,
fictionRouterSites: this.settings.fictionRouterSites,
})

return { fileName: 'cardStructure.json', content: results.json }
})
}
}
2 changes: 1 addition & 1 deletion @fiction/cards/test/tpl.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('verify template settings config', async () => {
const templates = await getCardTemplates()

it('has template options set correctly', async () => {
const factory = new CardFactory({ site, templates })
const factory = new CardFactory({ site, templates, caller: 'verifyTemplateSettings' })
const demoPages = await getDemoPages({ templates, site, factory })

const templatesOptionConfigPromises = templates.map(async (_) => {
Expand Down
2 changes: 1 addition & 1 deletion @fiction/cards/utils/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export async function createDemoPage(args: { site: Site, template: CardTemplate<

const templates = await getCardTemplates()

const factory = new CardFactory({ templates, site })
const factory = new CardFactory({ templates, site, caller: 'createDemoPage' })

// replace the template with the inline template to allow for templates not in main list
const crds = cards.map((c) => {
Expand Down
93 changes: 93 additions & 0 deletions @fiction/cards/utils/generateStructure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { FictionRouter } from '@fiction/core'
import type { FictionSites } from '@fiction/site'
import type { CardTemplate } from '@fiction/site/card'
import { shortId } from '@fiction/core'
import { Site } from '@fiction/site'
import { CardFactory } from '@fiction/site/cardFactory'

export class CardStructureGenerator {
templates: CardTemplate[]
site: Site
factory: CardFactory
mode?: 'simple' | 'complete'
constructor(args: {
templates: CardTemplate[]
site: Site
factory: CardFactory
mode?: 'simple' | 'complete'
}) {
this.templates = args.templates
this.site = args.site
this.factory = args.factory
this.mode = args.mode || 'simple'
}

private async processTemplate(template: CardTemplate) {
const { settings } = template

const { zodToSimpleSchema } = await import('@fiction/site/utils/schema')

const site = this.site
const factory = this.factory

let out: Record<string, any> = {
templateId: settings.templateId,
title: settings.title,
description: settings.description || 'NO_DESCRIPTION',
subTitle: settings.subTitle || 'NO_SUBTITLE',
category: settings.category,
icon: settings.icon,
colorTheme: settings.colorTheme,
isPublic: settings.isPublic,
}

if (this.mode === 'complete') {
const config = settings.getConfig
? await settings.getConfig({ site, factory })
: {}

// Process schema if available
const schemaStructure = config.schema
? await zodToSimpleSchema(config.schema)
: undefined

out = { ...out, schema: schemaStructure, demoPage: config.demoPage?.cards[0] }
}

return out
}

async generateStructure() {
const structure = {
version: '1.0',
generatedAt: new Date().toISOString(),
templates: await Promise.all(this.templates.map(t => this.processTemplate(t))),
}

return {
json: JSON.stringify(structure, null, 2),
data: structure,
}
}
}

export async function generateCardStructure(args: {
templates: CardTemplate[]
fictionSites: FictionSites
fictionRouterSites: FictionRouter
}) {
const { templates, fictionSites, fictionRouterSites } = args

const site = await Site.create({
fictionSites,
siteRouter: fictionRouterSites,
themeId: 'minimal',
isProd: false,
siteId: `generate-${shortId()}`,
})

const factory = new CardFactory({ site, templates: site.theme.value.templates, caller: 'generateCardStructure' })

const generator = new CardStructureGenerator({ templates, site, factory })
return await generator.generateStructure()
}
26 changes: 14 additions & 12 deletions @fiction/core/plugin-env/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import type { RunVars } from '../inject.js'
import type { RunVars, StandardServices } from '../inject.js'
import type { CleanupCallback } from '../types/index.js'
import type { HookType, UserNotification } from '../utils/index.js'
import type { CliCommand } from './commands.js'
import type { FictionEnvHookDictionary } from './hooks.js'
import type { CliOptions, CliVars, ResetUiScope, ResetUiTrigger, ServerModuleDef, ServiceConfig } from './types.js'
import type { CliOptions, CliVars, ResetUiScope, ResetUiTrigger, ServerModuleDef, ServiceConfig, ServiceList } from './types.js'
import path from 'node:path'
import dotenv from 'dotenv'
import { version as fictionVersion } from '../package.json'
import { FictionObject } from '../plugin.js'
import { type BrowserEventObject, onBrowserEvent } from '../utils/eventBrowser.js'
import { TypedEventTarget } from '../utils/eventTarget.js'
import { crossVar, isApp, isCi, isDev, isNode, isTest, onResetUi, resetUi, runHooks, runHooksSync, shortId, toSlug, toSnake, vue, waitFor } from '../utils/index.js'
import { crossVar, isApp, isCi, isDev, isNode, isTest, onResetUi, resetUi, runHooks, runHooksSync, safeDirname, shortId, toSlug, toSnake, vue, waitFor } from '../utils/index.js'
import { logMemoryUsage } from '../utils/nodeUtils.js'
import { standardAppCommands } from './commands.js'
import { compileApplication } from './entry.js'
import { generateStaticConfig } from './generate.js'
import { envConfig, EnvVar, vars } from './onImport.js'
import { commonServerOnlyModules } from './serverOnly.js'
import { type ConfigFileGenerator, generateStaticConfig } from './utils/generate.js'

export { envConfig, EnvVar, vars }
export * from './commands.js'
Expand All @@ -25,6 +25,7 @@ export * from './types.js'

export interface FictionControlSettings {
hooks?: HookType<FictionEnvHookDictionary>[]
generators?: ConfigFileGenerator[]
envFiles?: string[]
envFilesProd?: string[]
env?: Record<string, string>
Expand Down Expand Up @@ -80,10 +81,12 @@ export class FictionEnv<
generatedConfig?: S
commands = this.settings.commands || standardAppCommands
hooks = this.settings.hooks || []
generators = this.settings.generators || []
envFiles = this.settings.envFiles || []
envFilesProd = this.settings.envFilesProd || []
env = this.settings.env || {}
cwd = this.settings.cwd
monorepoRoot = safeDirname(import.meta.url, '../../..')
mainFilePath = this.settings.mainFilePath || path.join(this.cwd, 'index.ts')
meta = this.settings.meta || { }
id = this.settings.id || toSlug(this.meta.app?.name) || 'fiction'
Expand Down Expand Up @@ -112,6 +115,7 @@ export class FictionEnv<
serverOnlyImports: Record<string, true | Record<string, string>> = commonServerOnlyModules()

distFolder = this.settings.distFolder || path.join(this.cwd, 'dist')
generatedFolder = path.join(this.cwd, '/.fiction')

heldKeys = vue.ref<Record<string, boolean>>({})

Expand All @@ -120,6 +124,10 @@ export class FictionEnv<
// the services are then accessed via useService provide
service = vue.shallowRef<{ runVars?: Partial<RunVars>, [key: string]: unknown }>({})

getService<Y extends ServiceList>(): Y & StandardServices {
return this.service.value as Y & StandardServices
}

async runHooks<T extends keyof FictionEnvHookDictionary>(hook: T, ...args: FictionEnvHookDictionary[T]['args']) {
return runHooks<FictionEnvHookDictionary, T>({ list: this.hooks, hook, args })
}
Expand Down Expand Up @@ -417,12 +425,6 @@ export class FictionEnv<
})
}

override async afterSetup() {
if (!this.isProd.value && !this.isApp.value && !this.isTest.value && !this.isRestart()) {
await this.generate()
}
}

async generate() {
await generateStaticConfig(this)
}
Expand Down Expand Up @@ -525,9 +527,9 @@ export class FictionEnv<
return v || ''
}

addUiRoot(root: string) {
addUiRoot(root?: string) {
// prevent memory leak
if (this.isApp.value) {
if (this.isApp.value || !root) {
return
}

Expand Down
2 changes: 1 addition & 1 deletion @fiction/core/plugin-env/test/generate.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'node:path'
import { generateStaticConfig } from '@fiction/core/plugin-env/generate.js'
import { generateStaticConfig } from '@fiction/core/plugin-env/utils/generate.js'
import { createTestUtils } from '@fiction/core/test-utils/init.js'
import { safeDirname } from '@fiction/core/utils'
import fs from 'fs-extra'
Expand Down
Loading

0 comments on commit f03d031

Please sign in to comment.