diff options
Diffstat (limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx')
-rw-r--r-- | src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx | 261 |
1 files changed, 205 insertions, 56 deletions
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx index e36adc40a..191b387c4 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx @@ -59,7 +59,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { @observable _layoutPreviewScale: number = 1; @observable _savedLayouts: DataVizTemplateLayout[] = []; - @observable _GPTTemplates: Doc[] = []; + @observable _suggestedTemplates: Doc[] = []; @observable _GPTOpt: boolean = false; @observable _userPrompt: string = ''; @observable _callCount: number = 0; @@ -99,7 +99,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { @action setDataViz = (dataViz: DataVizBox) => { this._dataViz = dataViz }; @action setTemplateDocs = (docs: Doc[]) => {this._templateDocs = docs.map(doc => doc.annotationOn ? DocCast(doc.annotationOn):doc)}; - @action setGPTTemplates = (docs: Doc[]) => {this._GPTTemplates = docs}; + @action setGSuggestedTemplates = (docs: Doc[]) => {this._suggestedTemplates = docs}; @computed get docsToRender() { return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : []; @@ -198,7 +198,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { document.addEventListener('pointerdown', this.onPointerDown, true); document.addEventListener('pointerup', this.onPointerUp); this._disposers.templates = reaction(() => this._templateDocs.slice(), (docs) => docs.map(this.getIcon)); - this._disposers.gpt = reaction(() => this._GPTTemplates.slice(), (docs) => docs.map(this.getIcon)); + this._disposers.gpt = reaction(() => this._suggestedTemplates.slice(), (docs) => docs.map(this.getIcon)); //this._disposers.columns = reaction(() => this._dataViz?.layoutDoc._dataViz_axes, () => {this.generateTemplates('')}) this._disposers.lightbox = reaction(() => LightboxView.LightboxDoc(), doc => { doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu()}); //this._disposers.fields = reaction(() => this._dataViz?.axes, cols => this._selectedCols = cols?.map(col => { return {title: col, type: '', desc: ''}})) @@ -233,7 +233,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { @action openMenu = () => { - const allTemplates = this._templateDocs.concat(this._GPTTemplates); + const allTemplates = this._templateDocs.concat(this._suggestedTemplates); this._shouldDisplay = true; this.updateIcons(allTemplates); }; @@ -357,7 +357,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { const res = await gptAPICall(prompt, GPTCallType.TEMPLATE); if (res && this._callCount === origCount) { - this._GPTTemplates = []; + this._suggestedTemplates = []; this._GPTLoading = false; const templates: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[] = JSON.parse(res); this.createGeneratedTemplates(templates, 500, 500); @@ -389,14 +389,14 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { GPTTemplates.push(template); }); - setTimeout(() => {this.setGPTTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/}, 100); + setTimeout(() => {this.setGSuggestedTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/}, 100); this.forceUpdate(); }; editTemplate = (doc: Doc) => { //this.closeMenu(); - DocumentViewInternal.addDocTabFunc(doc, OpenWhere.lightboxAlways); + DocumentViewInternal.addDocTabFunc(doc, OpenWhere.addRight); DocumentView.DeselectAll(); Doc.UnBrushDoc(doc); }; @@ -419,20 +419,22 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; // mainCollection.addDocument(doc); - const temp = TemplateLayouts.FourField001; + // const temp = TemplateLayouts.FourField001; - const img: Col = {type: TemplateFieldType.VISUAL, title: 'Image', desc: 'description whpoo', size: TemplateFieldSize.LARGE}; - const capt1: Col = {type: TemplateFieldType.TEXT, title: 'Type', desc: 'description hey', size: TemplateFieldSize.TINY}; - const capt2: Col = {type: TemplateFieldType.TEXT, title: 'Locality', desc: '', size: TemplateFieldSize.TINY}; - const desc: Col = {type: TemplateFieldType.TEXT, title: 'Description', desc: '', size: TemplateFieldSize.LARGE}; + // const img: Col = {type: TemplateFieldType.TEXT, title: 'Type', desc: 'description whpoo', size: TemplateFieldSize.LARGE, defaultContent: ''}; + // const capt1: Col = {type: TemplateFieldType.TEXT, title: 'Image', desc: 'description hey', size: TemplateFieldSize.TINY}; + // const capt2: Col = {type: TemplateFieldType.TEXT, title: 'Locality', desc: '', size: TemplateFieldSize.TINY, defaultContent: ''}; + // const desc: Col = {type: TemplateFieldType.TEXT, title: 'Description', desc: '', size: TemplateFieldSize.LARGE, defaultContent: 'This is a description of a rock. It is kind of long. It is very long. It is gratuitous. This description should be shorter. Oh well. This is a description of a rock. It is kind of long. It is very long. It is gratuitous. This description should be shorter. Oh well. This is a description of a rock. It is kind of long. It is very long. It is gratuitous. This description should be shorter. Oh well.'}; // const assignments = {'0': img, '1': capt1, '2': capt2, '3': desc} // this.createEmptyTemplate(temp, assignments); + // console.log(this.findValidTemplates(this.fieldsInfos, TemplateLayouts.allTemplates)); - this.generatePresetTemplates(this.findValidTemplates(this.fieldsInfos, TemplateLayouts.allTemplates), this.fieldsInfos); // console.log(this._dataViz?.colsInfo.get("IMG")?.size, this._dataViz?.colsInfo.get("IMG")?.type) // console.log(this.fieldsInfos) + + }; @action addField = () => { @@ -492,7 +494,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { const colMatchesField = (col: Col, field : Field) => { return field.sizes?.includes(col.size) && field.types?.includes(col.type) }; const matches: number[][] = Array(template.fields.length).fill([]).map(() => []); - console.log(matches) + template.fields.forEach((field, i) => { cols.forEach((col, v) => { if (colMatchesField(col, field)) { @@ -507,11 +509,13 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { maxMatches = (fieldsCt: number, matches: number[][]) => { const used: boolean[] = Array(fieldsCt).fill(false); const mt: number[] = Array(fieldsCt).fill(-1); + console.log(fieldsCt, matches) const augmentingPath = (v: number): boolean => { if (used[v]) return false; used[v] = true; for (const to of matches[v]) { + console.log(mt[to]); if (mt[to] === -1 || augmentingPath(mt[to])) { mt[to] = v; return true; @@ -538,7 +542,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { findValidTemplates = (cols: Col[], templates: TemplateDocInfos[]) => { let validTemplates: any[] = []; templates.forEach(template => { - const numFields = template.fields.length; + const numFields = template.fields.length; + if (!(numFields === cols.length)) return; const matches = this.matchesForTemplate(template, cols); if (this.maxMatches(numFields, matches) === numFields) { validTemplates.push(template.title); @@ -546,6 +551,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { }) validTemplates = validTemplates.map(title => TemplateLayouts.fieldByTitle(title)); + + console.log(validTemplates); return validTemplates; }; @@ -571,7 +578,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { // return new Doc; // } - createEmptyTemplate = (template: TemplateDocInfos, assignments: {[field: string]: Col}) => { + createEmptyTemplate = (template: TemplateDocInfos, assignments: {[field: string]: Col}): Doc => { const fields: Doc[] = []; Object.entries(assignments).forEach(([f, col]) => { @@ -618,12 +625,13 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; mainCollection.addDocument(renderedTemplate); + return renderedTemplate; }; compileFieldDescriptions = (templates: TemplateDocInfos[]): string => { let descriptions: string = ''; templates.forEach(template => { - descriptions += `---------- Description of template ${template.title}'s fields: ` + descriptions += `---------- NEW TEMPLATE TO INCLUDE: Description of template ${template.title}'s fields: ` template.fields.forEach((field, index) => { descriptions += `{Field #${index}: ${field.description}} ` }); @@ -633,7 +641,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { }; compileColDescriptions = (cols: Col[]): string => { - let descriptions: string = ''; + let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:'; cols.forEach(col => descriptions += `{title: ${col.title}, size: ${col.size}, type: ${col.type}, descreiption: ${col.desc}} `); return descriptions; @@ -648,6 +656,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { const inputText = fieldDescriptions.concat(colDescriptions); + console.log(inputText); ++this._callCount; const origCount = this._callCount; @@ -660,6 +669,9 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { if (res && this._callCount === origCount) { this._GPTLoading = false; + + console.log(res); + const assignments: {[templateTitle: string]: {[field: string]: string}} = JSON.parse(res); const brokenDownAssignments: [TemplateDocInfos, {[field: number]: Col}][] = []; Object.entries(assignments).forEach(([tempTitle, assignment]) => { @@ -681,11 +693,16 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { return []; }; - generatePresetTemplates = async(templates: TemplateDocInfos[], cols: Col[]) => { + generatePresetTemplates = async() => { + const cols = this.fieldsInfos; + const templates = this.findValidTemplates(cols, TemplateLayouts.allTemplates); const assignments: [TemplateDocInfos, {[field: number]: Col}][] = await this.assignColsToFields(templates, cols); + const renderedTemplates: Doc[] = []; assignments.forEach(([template, assignments]) => { - this.createEmptyTemplate(template, assignments); + renderedTemplates.push(this.createEmptyTemplate(template, assignments)); }); + + setTimeout(() => {this.setGSuggestedTemplates(renderedTemplates)}); } get templatesPreviewContents(){ @@ -699,7 +716,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { <div className='docCreatorMenu-section' style={{height: this._GPTOpt ? 200 : 200}}> <div className='docCreatorMenu-section-topbar'> <div className='docCreatorMenu-section-title'>Suggested Templates</div> - <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, () => this._GPTOpt = !this._GPTOpt)}> + <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, () => this._menuContent = 'dashboard')}> <FontAwesomeIcon icon='gear'/> </button> </div> @@ -709,7 +726,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} /> </div> ) : ( - this._GPTTemplates?.map(doc => + this._suggestedTemplates?.map(doc => ({icon: ImageCast(doc.icon), doc})).filter(info => info.icon && info.doc).map(info => <div className='docCreatorMenu-preview-window' @@ -730,10 +747,9 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { </div> <div className='docCreatorMenu-GPT-options'> <div className='docCreatorMenu-GPT-options-container'> - <button className='docCreatorMenu-menu-button' onPointerDown={e => this.setUpButtonClick(e, () => this.generateTemplates(this._userPrompt))}> + <button className='docCreatorMenu-menu-button' onPointerDown={e => this.setUpButtonClick(e, () => this.generatePresetTemplates())}> <FontAwesomeIcon icon='arrows-rotate'/> </button> - <input className='docCreatorMenu-GPT-prompt-input' placeholder='Additional prompt:' onChange={e => this._userPrompt = e.target.value}/> </div> {this._GPTOpt ? GPTOptions : null } </div> @@ -1036,9 +1052,12 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { return ( <div className='docCreatorMenu-dashboard-view'> <div className='topbar'> - <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, this.addField)}> - <FontAwesomeIcon icon='plus'/> - </button> + <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, this.addField)}> + <FontAwesomeIcon icon='plus'/> + </button> + <button className='docCreatorMenu-menu-button section-reveal-options float-right' onPointerDown={e => this.setUpButtonClick(e, () => this._menuContent = 'templates')}> + <FontAwesomeIcon icon='arrow-left'/> + </button> </div> {this.fieldsInfos.map((field, index) => <div className='field-panel' id={`${index}`}> @@ -1261,12 +1280,12 @@ interface TemplateOpts extends FieldOpts { export class FieldFuncs { - private static calculateFontSize = (contWidth: number, contHeight: number, text: string, uppercase: boolean): number => { + public static calculateFontSize = (contWidth: number, contHeight: number, text: string, uppercase: boolean): number => { const words: string[] = text.split(/\s+/).filter(Boolean); let currFontSize = 1; let rowsCount = 1; - let currTextHeight = currFontSize * rowsCount * (uppercase ? 1.25 : 1); + let currTextHeight = currFontSize * rowsCount * 2; while (currTextHeight <= contHeight) { let wordIndex = 0; @@ -1276,7 +1295,8 @@ export class FieldFuncs { while (wordIndex < words.length) { const word = words[wordIndex]; - const wordWidth = word.length * currFontSize; + const wordWidth = word.length * currFontSize * .5; + //console.log(wordWidth) if (currentRowWidth + wordWidth <= contWidth) { currentRowWidth += wordWidth; @@ -1291,11 +1311,11 @@ export class FieldFuncs { } } - wordIndex++; + wordIndex++; } - currTextHeight = rowsCount * currFontSize * (uppercase ? 1.25 : 1); - //console.log(rowsCount, currTextHeight) + currTextHeight = rowsCount * currFontSize * 2; + //console.log(rowsCount, currFontSize, currTextHeight) currFontSize += 1; } @@ -1312,6 +1332,7 @@ export class FieldFuncs { const width = r - l; const height = b - t; const coord = {x: l, y: t}; + console.log(coords, parentWidth, parentHeight, height); return {width, height, coord}; } @@ -1378,6 +1399,7 @@ export class FieldFuncs { title: title, x: coord.x, y: coord.y, + _layout_fitWidth: false, backgroundColor: opts.backgroundColor ?? '', _layout_borderRounding: `${opts.cornerRounding}px` ?? '0px', borderColor: opts.borderColor, @@ -1386,6 +1408,8 @@ export class FieldFuncs { _rotation: opts.rotation, }); + //setTimeout(() => {doc._height = height; doc._width = width}, 10); + return doc; } @@ -1415,8 +1439,12 @@ export class TemplateLayouts { return TemplateLayouts.FourField002; case 'fourfield3': return TemplateLayouts.FourField003; + case 'fourfield4': + return TemplateLayouts.FourField004; case 'threefield1': return TemplateLayouts.ThreeField001; + case 'threefield2': + return TemplateLayouts.ThreeField002; default: break; } @@ -1427,11 +1455,11 @@ export class TemplateLayouts { public static FourField001: TemplateDocInfos = { title: 'fourfield1', width: 416, - height: 721, + height: 700, opts: { - backgroundColor: '#7B8D62', + backgroundColor: '#C0B887', cornerRounding: 20, - borderColor: '#642B00', + borderColor: '#6B461F', borderWidth: '12', }, fields: [{ @@ -1454,13 +1482,13 @@ export class TemplateLayouts { description: 'The main focus of the template; could be an image, long text, etc.', opts: { cornerRounding: 20, - borderColor: '#642B00', + borderColor: '#8F5B25', borderWidth: '6', backgroundColor: '#CECAB9', } }, { tl: [-.8, .2], - br: [.8, .35], + br: [.8, .3], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], description: 'A caption for field #2, very short to short text that contextualizes the content of field #2', @@ -1474,10 +1502,10 @@ export class TemplateLayouts { br: [.87, .96], types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], - description: 'A medium-sized field for medium/long text or a secondary image that complements the main focus.', + description: 'A medium-sized field for medium/long text.', opts: { cornerRounding: 15, - borderColor: '#642B00', + borderColor: '#8F5B25', borderWidth: '6', backgroundColor: '#CECAB9', } @@ -1497,28 +1525,28 @@ export class TemplateLayouts { br: [.83, -.2], types: [TemplateFieldType.VISUAL, TemplateFieldType.TEXT], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], - description: '', + description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus.', opts: { borderWidth: '8', borderColor: '#F8E71C', } }, { - tl: [-.45, -.18], - br: [.45, 0], + tl: [-.65, -.2], + br: [.65, -.02], types: [TemplateFieldType.TEXT], - sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], - description: '', + sizes: [TemplateFieldSize.TINY], + description: 'A tiny field for just a word or two of plain text.', opts: { backgroundColor: 'transparent', color: 'white', contentXCentering: 'h-center' } }, { - tl: [-.45, 0], - br: [.45, .18], + tl: [-.65, 0], + br: [.65, .18], types: [TemplateFieldType.TEXT], - sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], - description: '', + sizes: [TemplateFieldSize.TINY], + description: 'A tiny field for just a word or two of plain text.', opts: { backgroundColor: 'transparent', color: 'white', @@ -1529,11 +1557,12 @@ export class TemplateLayouts { br: [.83, .95], types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], - description: '', + description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus, or share focus with field 1.', opts: { borderWidth: '8', borderColor: '#F8E71C', - backgroundColor: '#242425' + color: 'white', + backgroundColor: '#242425', } }], decorations: [{ @@ -1612,7 +1641,8 @@ export class TemplateLayouts { description: '', opts: { backgroundColor: 'transparent', - color: 'white', + color: 'red', + fontTransform: 'uppercase', contentXCentering: 'h-left' } }, { @@ -1635,6 +1665,72 @@ export class TemplateLayouts { }] }; + public static FourField004: TemplateDocInfos = { + title: 'fourfield4', + width: 414, + height: 583, + opts: { + backgroundColor: '#6CCAF0', + borderColor: '#1088C3', + borderWidth: '10' + }, + fields: [{ + tl: [-.86, -.92], + br: [-.075, -.77], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY], + description: 'A tiny field for just a word or two of plain text.', + opts: { + backgroundColor: '#E2B4F5', + borderWidth: '9', + borderColor: '#9222F1', + contentXCentering: 'h-center' + } + }, { + tl: [.075, -.92], + br: [.86, -.77], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY], + description: 'A tiny field for just a word or two of plain text.', + opts: { + backgroundColor: '#F5B4DD', + borderWidth: '9', + borderColor: '#E260F3', + contentXCentering: 'h-center' + } + }, { + tl: [-.81, -.64], + br: [.81, .48], + types: [TemplateFieldType.VISUAL], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + description: 'A large to huge field for visual content that is the main content of the template.', + opts: { + borderWidth: '16', + borderColor: '#A2BD77', + } + }, { + tl: [-.86, .6], + br: [.86, .92], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], + description: 'A medium to large field for text that describes the visual content above', + opts: { + borderWidth: '9', + borderColor: '#F0D601', + backgroundColor: '#F3F57D', + } + }], + decorations: [{ + tl: [-.852, -.67], + br: [.852, .51], + opts: { + backgroundColor: 'transparent', + borderColor: '#007C0C', + borderWidth: '10', + } + }] + }; + public static ThreeField001: TemplateDocInfos = { title: 'threefield1', width: 575, @@ -1646,8 +1742,8 @@ export class TemplateLayouts { tl: [-.66, -.747], br: [.66, .247], types: [TemplateFieldType.VISUAL], - sizes: [TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], - description: '', + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + description: 'A medium to large field for visual content that is the central focus.', opts: { borderColor: 'yellow', borderWidth: '8', @@ -1670,7 +1766,7 @@ export class TemplateLayouts { br: [.7, .46], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], - description: '', + description: 'A very small text field for one to a few words. A good caption for the image.', opts: { backgroundColor: 'transparent', contentXCentering: 'h-center', @@ -1680,7 +1776,7 @@ export class TemplateLayouts { br: [.95, .95], types: [TemplateFieldType.TEXT], sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], - description: '', + description: 'A medium to large text field for a thorough description of the image. ', opts: { backgroundColor: 'transparent', color: 'white' @@ -1717,6 +1813,59 @@ export class TemplateLayouts { }] }; + public static ThreeField002: TemplateDocInfos = { + title: 'threefield2', + width: 477, + height: 662, + opts: { + backgroundColor: '#9E9C95' + }, + fields: [{ + tl: [-.95, .8], + br: [-.1, .95], + types: [TemplateFieldType.VISUAL], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + description: '', + opts: { + backgroundColor: 'transparent', + color: 'white', + contentXCentering: 'h-right', + } + }, { + tl: [.1, .8], + br: [.95, .95], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], + description: 'A very small text field for one to a few words. The content should represent a general categorization of the image.', + opts: { + backgroundColor: 'transparent', + color: 'red', + fontTransform: 'uppercase', + fontBold: true, + contentXCentering: 'h-left' + } + }, { + tl: [0, -.9], + br: [.85, -.66], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], + description: 'A very small text field for one to a few words. The content should contextualize field 2.', + opts: { + backgroundColor: 'transparent', + contentXCentering: 'h-right' + } + }], + decorations: [{ + tl: [-.025, .8], + br: [.025, .95], + opts: { + backgroundColor: '#E0E0DA', + } + }] + }; + + + // public static FourField002: TemplateDocInfos = { // width: 450, // height: 600, |