UNPKG

stackpress

Version:

Incept is a content management framework.

428 lines (426 loc) 15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = searchView; const ts_morph_1 = require("ts-morph"); function searchView(directory, _registry, model) { const file = `${model.name}/admin/views/search.tsx`; const source = directory.createSourceFile(file, '', { overwrite: true }); const ids = model.ids.map(column => column.name); const path = ids.map(name => `\${row.${name}}`).join('/'); source.addImportDeclaration({ isTypeOnly: true, moduleSpecifier: 'react', namedImports: ['ChangeEvent', 'MouseEventHandler', 'SetStateAction'] }); source.addImportDeclaration({ isTypeOnly: true, moduleSpecifier: 'stackpress/sql', namedImports: ['SearchParams'] }); source.addImportDeclaration({ isTypeOnly: true, moduleSpecifier: 'stackpress/view/client', namedImports: ['ServerPageProps', 'SessionPermission'] }); source.addImportDeclaration({ isTypeOnly: true, moduleSpecifier: 'stackpress/admin/types', namedImports: ['AdminConfigProps'] }); source.addImportDeclaration({ isTypeOnly: true, moduleSpecifier: '../../types.js', namedImports: [`${model.title}Extended`] }); source.addImportDeclaration({ moduleSpecifier: 'react', namedImports: ['useState'] }); source.addImportDeclaration({ moduleSpecifier: 'r22n', namedImports: ['useLanguage'] }); source.addImportDeclaration({ moduleSpecifier: 'frui/element/Table', namedImports: ['Table', 'Thead', 'Trow', 'Tcol'] }); source.addImportDeclaration({ moduleSpecifier: 'frui/element/Alert', defaultImport: 'Alert' }); source.addImportDeclaration({ moduleSpecifier: 'frui/form/Button', defaultImport: 'Button' }); source.addImportDeclaration({ moduleSpecifier: 'frui/field/Input', defaultImport: 'Input' }); source.addImportDeclaration({ moduleSpecifier: 'stackpress/view/client', namedImports: [ 'paginate', 'filter', 'order', 'notify', 'flash', 'useServer', 'useStripe', 'Crumbs', 'Pagination', 'LayoutAdmin' ] }); source.addImportDeclaration({ moduleSpecifier: 'stackpress/view/import', namedImports: ['batchAndSend'] }); model.lists.forEach(column => { if (typeof column.list.component !== 'string') return; source.addImportDeclaration({ moduleSpecifier: `../../components/lists/${column.title}ListFormat.js`, defaultImport: `${column.title}ListFormat` }); }); model.filters.forEach(column => { if (typeof column.filter.component !== 'string') return; source.addImportDeclaration({ moduleSpecifier: `../../components/filters/${column.title}Filter.js`, namedImports: [`${column.title}FilterControl`] }); }); model.spans.forEach(column => { if (typeof column.span.component !== 'string') return; source.addImportDeclaration({ moduleSpecifier: `../../components/spans/${column.title}Span.js`, namedImports: [`${column.title}SpanControl`] }); }); source.addFunction({ isExported: true, name: `Admin${model.title}SearchCrumbs`, statements: (` //hooks const { _ } = useLanguage(); //variables const crumbs = [{ label: _('${model.plural}'), icon: '${model.icon}' }]; return (<Crumbs crumbs={crumbs} />); `) }); source.addFunction({ isExported: true, name: `Admin${model.title}SearchFilters`, parameters: [{ name: 'props', type: `{ query: SearchParams, close: MouseEventHandler<HTMLElement> }` }], statements: (` //props const { query, close } = props; //hooks const { _ } = useLanguage(); return ( <aside> <header> <i className="icon fas fa-chevron-right" onClick={close}></i> {_('Filters')} </header> <form> ${Array.from(model.columns.values()).map(column => { if (column.filter.component) { return (` <${column.title}FilterControl className="control" value={query.filter?.${column.name}} /> `); } else if (column.span.component) { return (` <${column.title}SpanControl className="control" value={query.span?.${column.name}} /> `); } return ''; }).join('\n')} <Button className="submit" type="submit"> <i className="icon fas fa-fw fa-filter"></i> {_('Filter')} </Button> </form> </aside> ); `) }); source.addFunction({ isExported: true, name: `Admin${model.title}SearchForm`, parameters: [{ name: 'props', type: (`{ base: string, token: string, open: (value: SetStateAction<boolean>) => void, can: (...permits: SessionPermission[]) => boolean }`) }], statements: (` const { base, token, open, can } = props; const upload = (e: ChangeEvent<HTMLInputElement>) => { e.preventDefault(); //get the input const input = e.currentTarget; //get the first file const file = input.files?.[0]; //skip if we can't find the file if (!file) return; //proceed to send batchAndSend('import', token, file, notify).then(() => { flash('success', 'File imported successfully'); window.location.reload(); }); return false; }; return ( <div className="search"> <Button className="filter" type="button" onClick={() => open((opened: boolean) => !opened)} > <i className="icon fas fa-fw fa-filter"></i> </Button> <div className="form"> ${model.searchables.length > 0 ? (` <form> <Input className="input" /> <Button className="submit" type="submit"> <i className="icon fas fa-fw fa-search"></i> </Button> </form> `) : ''} </div> {can({ method: 'GET', route: \`\${base}/${model.dash}/export\` }) ?( <Button info className="action" href="export"> <i className="fas fa-download"></i> </Button> ): null} {can({ method: 'GET', route: \`\${base}/${model.dash}/import\` }) ?( <Button warning type="button" className="action import"> <i className="cursor-pointer fas fa-upload"></i> <input className="input" type="file" onChange={upload} /> </Button> ): null} {can({ method: 'GET', route: \`\${base}/${model.dash}/create\` }) ? ( <Button success className="action" href="create"> <i className="fas fa-plus"></i> </Button> ): null} </div> ); `) }); source.addFunction({ isExported: true, name: `Admin${model.title}SearchResults`, parameters: [{ name: 'props', type: (`{ base: string, query: Partial<SearchParams>, results: ProfileExtended[], can: (...permits: SessionPermission[]) => boolean }`) }], statements: (` const { can, base, query, results } = props; const { sort = {} } = query; const { _ } = useLanguage(); const stripe = useStripe('results-row-1', 'results-row-2'); return ( <Table> ${model.lists.filter(column => column.list.method !== 'hide').map(column => column.sortable ? (` <Thead noWrap stickyTop className="results-label sortable"> <span onClick={() => order('sort[${column.name}]')}> {_('${column.label}')} </span> {!sort.${column.name} ? ( <i className="icon fas fa-sort"></i> ) : null} {sort.${column.name} === 'asc' ? ( <i className="icon fas fa-sort-up"></i> ) : null} {sort.${column.name} === 'desc' ? ( <i className="icon fas fa-sort-down"></i> ) : null} </Thead> `) : (` <Thead noWrap stickyTop className="results-label"> {_('${column.label}')} </Thead> `)).join('\n')} <Thead stickyTop stickyRight className="results-label" /> {results.map((row, index) => ( <Trow key={index}> ${model.lists.filter(column => column.list.method !== 'hide').map(column => { const value = column.required && column.list.method === 'none' ? `{row.${column.name}.toString()}` : column.required && column.list.method !== 'none' ? `<${column.title}ListFormat data={row} value={row.${column.name}} />` : !column.required && column.list.method === 'none' ? `{row.${column.name} ? row.${column.name}.toString() : ''}` : `{row.${column.name} ? (<${column.title}ListFormat data={row} value={row.${column.name}} />) : ''}`; const align = column.sortable ? 'right' : 'left'; return column.filter.method !== 'none' ? (` <Tcol noWrap className={\`results-value ${align} filterable \${stripe(index)}\`}> <span onClick={() => filter('filter[${column.name}]', row.${column.name})}> ${value} </span> </Tcol> `) : (` <Tcol noWrap className={\`results-value ${align} \${stripe(index)}\`}> ${value} </Tcol> `); }).join('\n')} <Tcol stickyRight className={\`results-value center \${stripe(index)}\`}> {can({ method: 'GET', route: \`\${base}/${model.dash}/detail/${path}\`}) ? ( <Button info className="detail" href={\`detail/\${row.id}\`}> <i className="fas fa-fw fa-caret-right"></i> </Button> ) : null} </Tcol> </Trow> ))} </Table> ); `) }); source.addFunction({ isExported: true, name: `Admin${model.title}SearchBody`, statements: (` //props const { config, session, request, response } = useServer<${[ 'AdminConfigProps', 'Partial<SearchParams>', `${model.title}Extended[]` ].join(', ')}>(); const base = config.path('admin.base', '/admin'); const can = session.can.bind(session); const query = request.data(); const skip = query.skip || 0; const take = query.take || 50; const results = response.results as ${model.title}Extended[]; const total = response.total || 0; //hooks const { _ } = useLanguage(); const [ opened, open ] = useState(false); //handlers const page = (skip: number) => paginate('skip', skip); //render return ( <main className="admin-page admin-search-page"> <div className="admin-crumbs"> <Admin${model.title}SearchCrumbs /> </div> <div className={\`admin-filters \${opened? 'open': '' }\`}> <Admin${model.title}SearchFilters query={query} close={() => open(false)} /> </div> <div className="admin-search-form"> <Admin${model.title}SearchForm base={base} token={session.data.token} open={open} can={can} /> </div> {!!results?.length && ( <h1 className="admin-search-title">{_( 'Showing %s - %s of %s', (skip + 1).toString(), (skip + results.length).toString(), total.toString() )}</h1> )} <div className="admin-search-results"> {!results?.length ? ( <Alert info> <i className="no-results-icon fas fa-fw fa-info-circle"></i> {_('No results found.')} </Alert> ): ( <Admin${model.title}SearchResults base={base} can={can} query={query} results={results} /> )} </div> <Pagination total={total} take={take} skip={skip} paginate={page} /> </main> ); `) }); source.addFunction({ isExported: true, name: `Admin${model.title}SearchHead`, parameters: [{ name: 'props', type: 'ServerPageProps<AdminConfigProps>' }], statements: (` const { data, styles = [] } = props; const { favicon = '/favicon.ico' } = data?.brand || {}; const { _ } = useLanguage(); const mimetype = favicon.endsWith('.png') ? 'image/png' : favicon.endsWith('.svg') ? 'image/svg+xml' : 'image/x-icon'; return ( <> <title>{_('Search ${model.plural}')}</title> {favicon && <link rel="icon" type={mimetype} href={favicon} />} <link rel="stylesheet" type="text/css" href="/styles/global.css" /> {styles.map((href, index) => ( <link key={index} rel="stylesheet" type="text/css" href={href} /> ))} </> ); `) }); source.addFunction({ isExported: true, name: `Admin${model.title}SearchPage`, parameters: [{ name: 'props', type: 'ServerPageProps<AdminConfigProps>' }], statements: (` return ( <LayoutAdmin {...props}> <Admin${model.title}SearchBody /> </LayoutAdmin> ); `) }); source.addVariableStatement({ isExported: true, declarationKind: ts_morph_1.VariableDeclarationKind.Const, declarations: [{ name: 'Head', initializer: `Admin${model.title}SearchHead` }] }); source.addStatements(`export default Admin${model.title}SearchPage;`); }