d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
137 lines
4.0 KiB
TypeScript
137 lines
4.0 KiB
TypeScript
import { generateRandomString } from '@sim/utils/random'
|
|
|
|
/** 1 point = 12700 EMU (English Metric Units). */
|
|
export const PT_TO_EMU = 12700
|
|
|
|
export interface OpaqueColor {
|
|
rgbColor: { red: number; green: number; blue: number }
|
|
}
|
|
|
|
export interface TextRangeInput {
|
|
rangeType?: 'ALL' | 'FROM_START_INDEX' | 'FIXED_RANGE'
|
|
startIndex?: number
|
|
endIndex?: number
|
|
}
|
|
|
|
export interface CellLocationInput {
|
|
rowIndex?: number
|
|
columnIndex?: number
|
|
}
|
|
|
|
/**
|
|
* Convert a hex color string (`#RRGGBB`, `#RGB`, or bare hex) into the
|
|
* Google Slides API's OpaqueColor shape with rgbColor scaled 0-1.
|
|
* Returns null when input is empty/invalid.
|
|
*/
|
|
export function hexToOpaqueColor(input?: string | null): OpaqueColor | null {
|
|
if (!input) return null
|
|
let hex = input.trim().replace(/^#/, '')
|
|
if (hex.length === 3) {
|
|
hex = hex
|
|
.split('')
|
|
.map((c) => c + c)
|
|
.join('')
|
|
}
|
|
if (!/^[0-9a-fA-F]{6}$/.test(hex)) return null
|
|
const r = Number.parseInt(hex.slice(0, 2), 16)
|
|
const g = Number.parseInt(hex.slice(2, 4), 16)
|
|
const b = Number.parseInt(hex.slice(4, 6), 16)
|
|
return {
|
|
rgbColor: {
|
|
red: r / 255,
|
|
green: g / 255,
|
|
blue: b / 255,
|
|
},
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build a Slides API TextRange. Defaults to range type ALL.
|
|
* `FROM_START_INDEX` requires startIndex; `FIXED_RANGE` requires both indices.
|
|
*/
|
|
export function buildTextRange(input: TextRangeInput | undefined) {
|
|
const rangeType = input?.rangeType ?? 'ALL'
|
|
if (rangeType === 'FROM_START_INDEX') {
|
|
return { type: 'FROM_START_INDEX', startIndex: input?.startIndex ?? 0 }
|
|
}
|
|
if (rangeType === 'FIXED_RANGE') {
|
|
return {
|
|
type: 'FIXED_RANGE',
|
|
startIndex: input?.startIndex ?? 0,
|
|
endIndex: input?.endIndex ?? 0,
|
|
}
|
|
}
|
|
return { type: 'ALL' }
|
|
}
|
|
|
|
/**
|
|
* Build an optional cellLocation if both row and column indices are provided.
|
|
* Slides API treats absence as targeting the shape itself (not a table cell).
|
|
*/
|
|
export function buildCellLocation(input: CellLocationInput | undefined) {
|
|
if (input?.rowIndex === undefined || input?.columnIndex === undefined) return undefined
|
|
return { rowIndex: input.rowIndex, columnIndex: input.columnIndex }
|
|
}
|
|
|
|
/** Generate a stable-enough object ID prefixed by kind. */
|
|
export function generateObjectId(kind: string): string {
|
|
return `${kind}_${Date.now()}_${generateRandomString(7)}`
|
|
}
|
|
|
|
/** Convert a points value (or undefined) to an EMU Dimension object. */
|
|
export function ptToEmuDimension(pt: number | undefined, fallbackPt: number) {
|
|
return {
|
|
magnitude: (pt ?? fallbackPt) * PT_TO_EMU,
|
|
unit: 'EMU',
|
|
}
|
|
}
|
|
|
|
/** Build a standard PageElementProperties with size + transform from points. */
|
|
export function buildElementProperties(opts: {
|
|
pageObjectId: string
|
|
width?: number
|
|
height?: number
|
|
positionX?: number
|
|
positionY?: number
|
|
defaultWidth?: number
|
|
defaultHeight?: number
|
|
defaultX?: number
|
|
defaultY?: number
|
|
}) {
|
|
return {
|
|
pageObjectId: opts.pageObjectId,
|
|
size: {
|
|
width: ptToEmuDimension(opts.width, opts.defaultWidth ?? 200),
|
|
height: ptToEmuDimension(opts.height, opts.defaultHeight ?? 100),
|
|
},
|
|
transform: {
|
|
scaleX: 1,
|
|
scaleY: 1,
|
|
translateX: (opts.positionX ?? opts.defaultX ?? 100) * PT_TO_EMU,
|
|
translateY: (opts.positionY ?? opts.defaultY ?? 100) * PT_TO_EMU,
|
|
unit: 'EMU',
|
|
},
|
|
}
|
|
}
|
|
|
|
/** Standard presentation URL for embedding in metadata. */
|
|
export function presentationUrl(presentationId: string): string {
|
|
return `https://docs.google.com/presentation/d/${presentationId}/edit`
|
|
}
|
|
|
|
/** Standard fetch headers for Slides API JSON calls. */
|
|
export function authJsonHeaders(accessToken: string) {
|
|
if (!accessToken) throw new Error('Access token is required')
|
|
return {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
'Content-Type': 'application/json',
|
|
}
|
|
}
|
|
|
|
/** Resolve the batchUpdate URL for a given presentation. */
|
|
export function batchUpdateUrl(presentationId: string | undefined): string {
|
|
const id = presentationId?.trim()
|
|
if (!id) throw new Error('Presentation ID is required')
|
|
return `https://slides.googleapis.com/v1/presentations/${id}:batchUpdate`
|
|
}
|