Skip to content

[WORK in PROGRESS] unify interface for octopus-converters #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions packages/octopus-ai/examples/web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import * as OctopusAI from '../../src/index-web.js'
window.addEventListener('DOMContentLoaded', async () => {
const { createConverter, AIFileReader, WebExporter } = OctopusAI
console.log('OctopusAI:', OctopusAI)
const readerOptions = {
path: './assets/inside-photo.ai',
}

try {
const response = await fetch('./assets/inside-photo.ai')
const file = new Uint8Array(await response.arrayBuffer())
const converter = createConverter()
const reader = new AIFileReader(readerOptions)
const reader = new AIFileReader({ file })
const sourceDesign = await reader.getSourceDesign()

if (sourceDesign === null) {
console.error('Creating SourceDesign Failed')
return
}

const exporter = new WebExporter()
const result = await converter.convertDesign({ sourceDesign, exporter })

Expand Down
20 changes: 15 additions & 5 deletions packages/octopus-ai/src/entities/source/source-design.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { uniqueIdFactory } from '@opendesign/octopus-common/dist/utils/common.js
import { SourceArtboard } from './source-artboard.js'

import type { Metadata } from '../../services/readers/ai-file-reader-common.js'
import type { SourceImage, SourceTree } from '../../typings/index.js'
import type { SourceTree } from '../../typings/index.js'
import type { AdditionalTextData } from '../../typings/raw/index.js'
import type { SourceImage } from '@opendesign/octopus-common/dist/typings/octopus-common/index.js'
import type { Nullish } from '@opendesign/octopus-common/dist/utility-types.js'

export class SourceDesign {
Expand All @@ -13,12 +14,12 @@ export class SourceDesign {
private _metaData: Metadata
private _additionalTexData: AdditionalTextData
private _uniqueId: () => string
private _ids?: string[]

constructor(sourceTree: SourceTree) {
constructor(sourceTree: SourceTree, ids?: string[]) {
this._uniqueId = uniqueIdFactory(0)
this._artboards = sourceTree.artboards.map(
(artboardSource) => new SourceArtboard({ artboard: artboardSource, sourceDesign: this })
)
this._ids = ids
this._artboards = this._initArtboards(sourceTree)
this._images = sourceTree.images
this._additionalTexData = sourceTree.additionalTextData
this._metaData = sourceTree.metadata
Expand All @@ -28,6 +29,15 @@ export class SourceDesign {
return this._metaData
}

private _initArtboards(sourceTree: SourceTree): SourceArtboard[] {
const ids = this._ids
const rawArtboards = ids
? sourceTree.artboards.filter((artboardSource) => ids.includes(String(artboardSource.Id)))
: sourceTree.artboards

return rawArtboards.map((artboardSource) => new SourceArtboard({ artboard: artboardSource, sourceDesign: this }))
}

get images(): SourceImage[] {
return this._images
}
Expand Down
6 changes: 3 additions & 3 deletions packages/octopus-ai/src/octopus-ai-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { readPackageMeta } from './utils/read-pkg-meta.js'

import type { SourceDesign } from './entities/source/source-design.js'
import type { ConvertDesignResult } from './services/conversion/design-converter/index.js'
import type { Exporter } from './services/conversion/exporters/index.js'
import type { AIExporter } from './services/conversion/exporters/index.js'
import type { NodeFactories, WebFactories } from './services/general/platforms/index.js'
import type { Logger } from './typings/index.js'
import type { PackageMeta } from './utils/read-pkg-meta.js'
Expand All @@ -29,7 +29,7 @@ export type ConvertDesignOptions = {
* file containing metadata (OctopusManifest) gets periodically updated
*/
partialUpdateInterval?: number
exporter?: Exporter
exporter?: AIExporter
}

/**
Expand Down Expand Up @@ -81,4 +81,4 @@ export class OctopusAIConverter {
}
}

export type { Exporter, SourceDesign }
export type { AIExporter, SourceDesign }
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ import { TextLayerGroupingservice } from '../text-layer-grouping-service/index.j
import type { SourceArtboard } from '../../../entities/source/source-artboard.js'
import type { SourceDesign } from '../../../entities/source/source-design.js'
import type { OctopusAIConverter } from '../../../octopus-ai-converter.js'
import type { SourceImage } from '../../../typings/index.js'
import type { Manifest } from '../../../typings/manifest/index.js'
import type { Octopus } from '../../../typings/octopus/index.js'
import type { AdditionalTextData } from '../../../typings/raw/index.js'
import type { Exporter } from '../exporters/index.js'
import type { AIExporter } from '../exporters/index.js'
import type {
SourceImage,
GenericComponentConversionResult,
GenericDesignConversionResult,
} from '@opendesign/octopus-common/dist/typings/octopus-common/index.js'
import type { SafeResult } from '@opendesign/octopus-common/dist/utils/queue.js'

type DesignConverterGeneralOptions = {
octopusAIconverter: OctopusAIConverter
exporter?: Exporter
exporter?: AIExporter
partialUpdateInterval?: number
}

Expand Down Expand Up @@ -56,7 +56,7 @@ export class DesignConverter {
private _sourceDesign: SourceDesign
private _octopusManifest: OctopusManifest
private _octopusAIConverter: OctopusAIConverter
private _exporter?: Exporter
private _exporter?: AIExporter
private _partialUpdateInterval: number

static PARTIAL_UPDATE_INTERVAL = 3000
Expand Down Expand Up @@ -117,37 +117,35 @@ export class DesignConverter {
return { id: targetArtboardId, value, error, time }
}

private async _exportManifest(exporter: Exporter | null): Promise<Manifest['OctopusManifest']> {
private async _exportManifest(exporter: AIExporter | null): Promise<Manifest['OctopusManifest']> {
const { time, result: manifest } = getBenchmarkService()(() => this.manifest.convert())
await exporter?.exportManifest?.({ manifest, time })
return manifest
}

private async _exportArtboard(exporter: Exporter | null, artboard: SourceArtboard): Promise<ArtboardExport> {
private async _exportArtboard(exporter: AIExporter | null, artboard: SourceArtboard): Promise<ArtboardExport> {
const { images: imagesDep } = artboard.dependencies
const artboardImages = this._sourceDesign.images.filter((image) => imagesDep.some((dep) => image.id.includes(dep)))
const images = await Promise.all(
artboardImages.map(async (image) => {
const imageId = image.id
const rawData = await image.getImageData()
const imagePath = (await rejectTo(exporter?.exportImage?.(image.id, rawData) ?? Promise.reject(''))) as string
const imagePath = (await rejectTo(exporter?.exportImage?.(image) ?? Promise.reject(''))) as string
this.manifest.setExportedImage(imageId, imagePath)

return image
})
)

const converted = this.convertArtboardById(artboard.id)
const artboardPath = (await rejectTo(
exporter?.exportArtboard?.(artboard, converted) ?? Promise.reject('')
)) as string
const artboardPath = (await rejectTo(exporter?.exportComponent?.(converted) ?? Promise.reject(''))) as string
await exporter?.exportSourceArtboard?.(artboard)

this.manifest.setExportedArtboard(artboard.id, artboardPath)

return { images, artboard: converted }
}

private _initArtboardQueue(exporter: Exporter | null) {
private _initArtboardQueue(exporter: AIExporter | null) {
return new Queue({
name: DesignConverter.ARTBOARDS_QUEUE_NAME,
parallels: DesignConverter.ARTBOARDS_QUEUE_PARALLELS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ describe('DesignConverter', () => {

const sourceDesign = {
images: [
{ id: 'image-1.jpg', path: 'path/image-1.jpg', getImageData: async () => 'base64;image-1' },
{ id: 'image-2.jpg', path: 'path/image-1.jpg', getImageData: async () => 'base64;image-2' },
{ id: 'image-3.jpg', path: 'path/image-3.jpg', getImageData: async () => 'base64;image-3' },
{ id: 'image-1.jpg', getImageData: async () => 'base64;image-1' },
{ id: 'image-2.jpg', getImageData: async () => 'base64;image-2' },
{ id: 'image-3.jpg', getImageData: async () => 'base64;image-3' },
],
}

Expand All @@ -113,7 +113,7 @@ describe('DesignConverter', () => {
.mockReturnValueOnce({ id: 1, value: { id: '1' }, error: null, time: 5 })

const exporter: any = {
exportImage: vi.fn().mockImplementation(async (imagePath) => 'root/' + imagePath),
exportImage: vi.fn().mockImplementation(async (image) => 'root/' + image.id),
exportArtboard: vi.fn().mockImplementation(async (artboard) => 'root/' + artboard.value.id),
}

Expand All @@ -132,8 +132,15 @@ describe('DesignConverter', () => {
images: [sourceDesign.images[0], sourceDesign.images[2]],
})

expect(exporter.exportImage).toHaveBeenCalledWith('image-1.jpg', 'base64;image-1')
expect(exporter.exportImage).toHaveBeenCalledWith('image-3.jpg', 'base64;image-3')
expect(exporter.exportImage).toHaveBeenCalledWith({
getImageData: expect.any(Function),
id: 'image-1.jpg',
})

expect(exporter.exportImage).toHaveBeenCalledWith({
getImageData: expect.any(Function),
id: 'image-3.jpg',
})

expect(manifestInstanceMock.setExportedImage).toHaveBeenCalledWith('image-1.jpg', 'root/image-1.jpg')
expect(manifestInstanceMock.setExportedImage).toHaveBeenCalledWith('image-3.jpg', 'root/image-3.jpg')
Expand Down
13 changes: 6 additions & 7 deletions packages/octopus-ai/src/services/conversion/exporters/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import type { SourceArtboard } from '../../../entities/source/source-artboard.js'
import type { SourceDesign } from '../../../entities/source/source-design.js'
import type { ComponentConversionResult, DesignConversionResult } from '../design-converter/index.js'
import type { Exporter } from '@opendesign/octopus-common/dist/typings/octopus-common/index.js'

export type AuxiliaryData = { metadata: string; additionalTextData: string | null }

export interface Exporter {
exportAuxiliaryData?(_design: SourceDesign): Promise<AuxiliaryData>
exportImage?(_path: string, _data: Uint8Array): Promise<unknown>
exportArtboard?(_source: SourceArtboard, _artboard: ComponentConversionResult): Promise<unknown>
exportManifest?(_manifest: DesignConversionResult): Promise<unknown>
getBasePath(): Promise<string>
finalizeExport(): void
export interface AIExporter extends Exporter {
exportComponent?(component: ComponentConversionResult, role?: string): Promise<string>
exportManifest?(manifest: DesignConversionResult): Promise<string>
exportAuxiliaryData?(design: SourceDesign): Promise<AuxiliaryData>
exportSourceArtboard?(artboard: SourceArtboard): Promise<string>
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { v4 as uuidv4 } from 'uuid'

import { createOctopusArtboardFileName } from '../../../utils/exporter.js'

import type { Exporter, AuxiliaryData } from './index.js'
import type { AIExporter, AuxiliaryData } from './index.js'
import type { SourceArtboard } from '../../../entities/source/source-artboard.js'
import type { SourceDesign } from '../../../entities/source/source-design.js'
import type { ComponentConversionResult, DesignConversionResult } from '../design-converter/index.js'
import type { SourceImage } from '@opendesign/octopus-common/dist/typings/octopus-common/index.js'
import type { DetachedPromiseControls } from '@opendesign/octopus-common/dist/utils/async.js'

type LocalExporterOptions = {
Expand All @@ -20,7 +21,7 @@ type LocalExporterOptions = {
/**
* Exporter created to be used in automated runs.
*/
export class LocalExporter implements Exporter {
export class LocalExporter implements AIExporter {
private _outputDir: Promise<string>
private _assetsSaves: Promise<unknown>[]
private _completed: DetachedPromiseControls<void>
Expand Down Expand Up @@ -103,12 +104,12 @@ export class LocalExporter implements Exporter {

/**
* Exports given Image into folder specified in `LocalExporter.IMAGES_DIR_NAME`
* @param {string} name Name of the exported Image
* @param {Buffer} data Data representation of given image
* @param {SourceImage} with signature {id:string, getImageData: () => Promise<Uint8Array>}
* @returns {Promise<string>} which designates path to the exported Image
*/
exportImage(name: string, data: Buffer): Promise<string> {
return this._save(path.join(LocalExporter.IMAGES_DIR_NAME, path.basename(name)), data)
async exportImage(image: SourceImage): Promise<string> {
const data = await image.getImageData()
return this._save(path.join(LocalExporter.IMAGES_DIR_NAME, path.basename(image.id)), Buffer.from(data))
}

finalizeExport(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import { v4 as uuidv4 } from 'uuid'

import { createOctopusArtboardFileName } from '../../../utils/exporter.js'

import type { Exporter, AuxiliaryData } from './index.js'
import type { AuxiliaryData, AIExporter } from './index.js'
import type { SourceArtboard } from '../../../entities/source/source-artboard.js'
import type { SourceDesign } from '../../../entities/source/source-design.js'
import type { ComponentConversionResult, DesignConversionResult } from '../design-converter/index.js'
import type { SourceImage } from '@opendesign/octopus-common/dist/typings/octopus-common/index.js'
import type { DetachedPromiseControls } from '@opendesign/octopus-common/dist/utils/async.js'

type TempExporterOptions = {
Expand All @@ -26,7 +27,8 @@ type TempExporterOptions = {
/**
* Exporter created to be used in manual runs.
*/
export class TempExporter extends EventEmitter implements Exporter {

export class TempExporter extends EventEmitter implements AIExporter {
private _outputDir: Promise<string>
private _tempDir: string
private _completed: DetachedPromiseControls<void>
Expand Down Expand Up @@ -132,8 +134,9 @@ export class TempExporter extends EventEmitter implements Exporter {
* @param {Buffer} data Data representation of given image
* @returns {Promise<string>} which designates path to the exported Image
*/
async exportImage(name: string, data: Buffer): Promise<string> {
return this._save(path.join(TempExporter.IMAGES_DIR_NAME, path.basename(name)), data)
async exportImage(image: SourceImage): Promise<string> {
const data = await image.getImageData()
return this._save(path.join(TempExporter.IMAGES_DIR_NAME, path.basename(image.id)), Buffer.from(data))
}

finalizeExport(): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,13 @@
import type { AuxiliaryData } from './index.js'
import type { SourceArtboard } from '../../../entities/source/source-artboard.js'
import type { SourceDesign } from '../../../octopus-ai-converter.js'
import type { ComponentConversionResult, DesignConversionResult } from '../design-converter/index.js'
import type { AIExporter } from './index.js'
import type { SourceImage } from '@opendesign/octopus-common/dist/typings/octopus-common/index.js'

/**
* Minimalistic exporter used for web build
*/
export class WebExporter {
exportImage(name: string, _data: Uint8Array): Promise<unknown> {
return Promise.resolve(name)
}

getBasePath(): Promise<string> {
return Promise.resolve('')
}

finalizeExport(): void {
Promise.resolve()
}
exportArtboard(_source: SourceArtboard, _artboard: ComponentConversionResult): Promise<unknown> {
console.log('Exporting artboard')
return Promise.resolve()
}

exportManifest(_manifest: DesignConversionResult): Promise<unknown> {
console.log('Exporting manifest')
return Promise.resolve()
}

exportAuxiliaryData(_design: SourceDesign): Promise<AuxiliaryData> {
console.log('Exporting auxiliary data')
return Promise.resolve({ metadata: '', additionalTextData: null })
export class WebExporter implements AIExporter {
exportImage(image: SourceImage): Promise<string> {
const { id } = image
console.log('calling export method `exportImage()`')
return Promise.resolve(id)
}
}
Loading