Skip to content

Commit

Permalink
feat: first version of session grid
Browse files Browse the repository at this point in the history
  • Loading branch information
BigJk committed Sep 24, 2024
1 parent 62043f9 commit f148971
Show file tree
Hide file tree
Showing 25 changed files with 1,386 additions and 10 deletions.
2 changes: 2 additions & 0 deletions frontend/src/js/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import GeneratorCreate from 'js/ui/views/generator/create';
import GeneratorEdit from 'js/ui/views/generator/edit';
import GeneratorSingle from 'js/ui/views/generator/single';
import Home from 'js/ui/views/home';
import SessionGrid from 'js/ui/views/session-grid';
import Settings from 'js/ui/views/settings';
import TemplateAll from 'js/ui/views/template/all';
import TemplateCreate from 'js/ui/views/template/create';
Expand Down Expand Up @@ -69,6 +70,7 @@ store.actions.loadAll().then(() => {
'/generator/create/:id': GeneratorCreate,
'/generator/:id': GeneratorSingle,
'/generator/:id/edit': GeneratorEdit,
'/session-grid': SessionGrid,
'/data-source': DataSourceAll,
'/data-source/:id': DataSourceSingle,
'/workshop': WorkshopAll,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/js/core/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,4 @@ export const GET_DEFAULT_DIRECTORIES = 'getDefaultDirectories';
// Misc function
export const FETCH_IMAGE = 'fetchImage';
export const PREVIEW_CACHE = 'previewCache';
export const GET_LOCAL_URL = 'getLocalURL';
164 changes: 164 additions & 0 deletions frontend/src/js/core/session-grid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { buildId } from 'js/types/basic-info';
import Entry from 'js/types/entry';
import Generator, { sanitizeConfig } from 'js/types/generator';
import {
GridElement,
GridGeneratorElement,
GridLinearExecution,
GridTemplateElement,
isGridGeneratorElement,
isGridLinearExecution,
isGridTemplateElement,
} from 'js/types/session-grid';
import Template from 'js/types/template';
import * as API from 'js/core/api';
import { settings } from 'js/core/store';
import { render } from 'js/core/templating';

const TEMPLATE_ENTRIES_CACHE_TIMEOUT = 10000;

/**
* Gets the name of a grid element. If the element has a name, it will be returned immediately. Otherwise, the name will be fetched from the API.
* @param element The grid element to get the name of
* @returns A promise that resolves to the name of the grid element
*/
export function getGridElementName(element: GridElement): Promise<string> {
return new Promise((resolve, reject) => {
if (element.name) {
resolve(element.name);
return;
}

if (isGridTemplateElement(element)) {
API.exec<Template>(API.GET_TEMPLATE, element.templateId)
.then((res) => {
resolve(res.name);
})
.catch(reject);
} else if (isGridGeneratorElement(element)) {
API.exec<Generator>(API.GET_GENERATOR, element.generatorId)
.then((res) => {
resolve(res.name);
})
.catch(reject);
}
});
}

/**
* Cache for the entries of a grid template element
*/
const entriesCache: { [key: string]: { timestamp: number; entries: Entry[] } } = {};

/**
* Get the choices for a grid template element.
* @param element The grid template element to get the choices for
* @param search An optional search string to filter the choices by
* @returns A promise that resolves to the choices for the grid template element
*/
export async function getGridTemplateChoices(
element: GridTemplateElement,
search?: string,
): Promise<{ id: string; name: string; source?: string }[]> {
if (element.templateId.length === 0) {
return [];
}

if (entriesCache[element.templateId] && Date.now() - entriesCache[element.templateId].timestamp < TEMPLATE_ENTRIES_CACHE_TIMEOUT) {
return Promise.resolve(
entriesCache[element.templateId].entries
.map((entry) => ({
name: entry.name,
id: entry.id,
source: entry.source,
}))
.filter((e) => !search || e.name.toLowerCase().includes(search.toLowerCase())),
);
}

return API.exec<Entry[]>(API.GET_ENTRIES_WITH_SOURCES, element.templateId).then((res) => {
entriesCache[element.templateId] = {
timestamp: Date.now(),
entries: res,
};
return res
.map((entry) => ({
name: entry.name,
id: entry.id,
source: entry.source,
}))
.filter((e) => !search || e.name.toLowerCase().includes(search.toLowerCase()));
});
}

/**
* Get the choices for a grid generator element.
* @param element The grid generator element to get the choices for
* @returns A promise that resolves to the choices for the grid generator element
*/
export async function getGridGeneratorConfigChoices(element: GridGeneratorElement): Promise<string[]> {
if (element.generatorId.length === 0) {
return [];
}

return API.exec<string>(API.GET_KEY, `${element.generatorId}_saved_configs`).then((configs) => Object.keys(JSON.parse(configs)));
}

/**
* Execute a grid element
* @param element The grid element to execute
*/
export async function executeElement(element: GridElement | GridLinearExecution) {
if (isGridTemplateElement(element)) {
if (!element.entryId) {
const choices = await getGridTemplateChoices(element);
if (choices.length === 0) {
return;
}

const choice = Math.floor(Math.random() * choices.length);
element.entryId = choices[choice].id;
element.dataSourceId = choices[choice].source;
}

const template = await API.exec<Template>(API.GET_TEMPLATE, element.templateId);
const entry = await API.exec<Entry>(API.GET_ENTRY, element.dataSourceId ?? buildId('template', template), element.entryId);
const res = await render(template.printTemplate, {
it: entry.data,
config: {},
sources: template.dataSources,
images: template.images,
settings: settings.value,
});
await API.exec(API.PRINT, res);
}

if (isGridGeneratorElement(element)) {
const config = {};
if (element.configName && element.configName.length > 0) {
const savedConfigs = await API.exec<string>(API.GET_KEY, `${element.generatorId}_saved_configs`);
if (savedConfigs) {
const configs = JSON.parse(savedConfigs);
if (configs[element.configName]) {
Object.assign(config, configs[element.configName]);
}
}
}

const generator = await API.exec<Generator>(API.GET_GENERATOR, element.generatorId);
const finalConfig = sanitizeConfig(generator, config);
const res = await render(generator.printTemplate, {
config: finalConfig,
sources: generator.dataSources,
images: generator.images,
settings: settings.value,
aiEnabled: element.aiEnabled,
aiToken: finalConfig.seed,
});
await API.exec(API.PRINT, res);
}

if (isGridLinearExecution(element)) {
throw new Error('Not implemented');
}
}
57 changes: 57 additions & 0 deletions frontend/src/js/types/session-grid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
type TemplateElementBase = {
color?: string;
name?: string;
};

type GridTemplateElement = TemplateElementBase & {
templateId: string;
dataSourceId?: string;
entryId?: string;
};

type GridGeneratorElement = TemplateElementBase & {
generatorId: string;
configName?: string;
aiEnabled?: boolean;
};

type GridElement = GridTemplateElement | GridGeneratorElement;

type GridLinearExecution = {
repeat?: number;
elements: GridElement[];
};

type SessionGrid = {
name: string;
elements: (GridElement | GridLinearExecution)[];
};

/**
* Check if an element is a grid template element
* @param element The element to check
* @returns True if the element is a grid template element
*/
export function isGridTemplateElement(element: any): element is GridTemplateElement {
return (element as GridTemplateElement).templateId !== undefined;
}

/**
* Check if an element is a grid generator element
* @param element The element to check
* @returns True if the element is a grid generator element
*/
export function isGridGeneratorElement(element: any): element is GridGeneratorElement {
return (element as GridGeneratorElement).generatorId !== undefined;
}

/**
* Check if an element is a grid linear execution
* @param element The element to check
* @returns True if the element is a grid linear execution
*/
export function isGridLinearExecution(element: any): element is GridLinearExecution {
return (element as GridLinearExecution).elements !== undefined;
}

export { GridElement, GridTemplateElement, GridGeneratorElement, GridLinearExecution, SessionGrid };
3 changes: 2 additions & 1 deletion frontend/src/js/ui/components/box.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ type BoxProps = {
maxHeight?: number;
minHeight?: number;
height?: number;
style?: any;
};

/**
* Box component: renders a box with a max width or width.
*/
export default (): m.Component<BoxProps> => ({
view({ attrs, children }) {
let style: any = {};
let style: any = { ...attrs.style };

if (attrs.maxWidth !== undefined && attrs.maxWidth > 0) {
style['max-width'] = `${attrs.maxWidth}px`;
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/js/ui/components/layout/center-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const containerClass = css`
width: 100%;
`;

export default (): m.Component => ({
view: ({ children }) => m(Flex, { justify: 'center' }, m(`div.${containerClass}`, children)),
type CenterContainerProps = {
key?: string;
};

export default (): m.Component<CenterContainerProps> => ({
view: ({ attrs, children }) => m(Flex, { key: attrs.key, justify: 'center' }, m(`div.${containerClass}`, { key: attrs.key }, children)),
});
2 changes: 2 additions & 0 deletions frontend/src/js/ui/components/layout/grid.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import m from 'mithril';

type GridProps = {
key?: string;
className?: string;
minWidth?: string;
maxWidth?: string;
Expand All @@ -24,6 +25,7 @@ export default (): m.Component<GridProps> => ({
return m(
`div${attrs.className ?? ''}${attrs.gap ? `.gap-${attrs.gap}` : '.gap-3'}`,
{
key: attrs.key,
style,
},
children,
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/js/ui/components/modals/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@ type PromptProps = {
title: string;
label: string;
description: string;
value?: string;
buttonText?: string;
onSuccess: (answer: string) => void;
};

const promptModal = (props: PromptProps) => (): m.Component => {
let state = '';
let state = props.value ?? '';

return {
view: () =>
m(
Modal,
{ title: props.title, onClose: () => popPortal() },
{ title: props.title, className: '.pt0', onClose: () => popPortal() },
m('div', [
m(
HorizontalProperty,
Expand Down
Loading

0 comments on commit f148971

Please sign in to comment.