├── CNAME ├── v1 ├── examples │ ├── .gitignore │ ├── l3rd-name │ │ ├── README.md │ │ ├── l3rd.ograf.json │ │ ├── lib │ │ │ ├── ograf-logo-app.svg │ │ │ ├── TextPlugin.js │ │ │ └── utils │ │ │ │ └── strings.js │ │ └── graphic.mjs │ ├── README.md │ ├── renderer-test │ │ ├── README.md │ │ ├── manifest.ograf.json │ │ └── graphic.mjs │ ├── minimal │ │ ├── README.md │ │ ├── minimal.ograf.json │ │ └── graphic.mjs │ └── ograf-logo │ │ ├── README.md │ │ ├── logo.ograf.json │ │ ├── graphic.mjs │ │ └── lib │ │ └── ograf-logo-app.svg ├── typescript-definitions │ ├── .npmignore │ ├── .gitignore │ ├── src │ │ ├── definitions │ │ │ ├── vendor.ts │ │ │ ├── types.ts │ │ │ └── render.ts │ │ ├── main.ts │ │ ├── apis │ │ │ └── graphicsAPI.ts │ │ └── generated │ │ │ └── graphics-manifest.ts │ ├── package.json │ ├── scripts │ │ ├── generate-types.js │ │ └── local-testing.js │ ├── README.md │ ├── tsconfig.json │ └── package-lock.json └── specification │ ├── json-schemas │ ├── lib │ │ ├── constraints │ │ │ ├── boolean.json │ │ │ ├── string.json │ │ │ └── number.json │ │ └── action.json │ └── graphics │ │ └── schema.json │ └── docs │ └── images │ ├── step-model1.svg │ ├── step-model2.svg │ ├── step-model3.svg │ └── components.svg ├── .gitignore ├── CHANGELOG.md ├── README.md ├── .github └── workflows │ └── publish-npm.yml └── docs └── logo ├── og-logo-colour.svg ├── og-logo-mono-black.svg ├── og-logo-mono-white.svg ├── ograf-logo-app.svg ├── ograf-logo-colour.svg ├── ograf-logo-mono-black.svg └── ograf-logo-mono-white.svg /CNAME: -------------------------------------------------------------------------------- 1 | ograf.ebu.io -------------------------------------------------------------------------------- /v1/examples/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.zip 2 | -------------------------------------------------------------------------------- /v1/typescript-definitions/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | scripts 3 | tsconfig.json 4 | -------------------------------------------------------------------------------- /v1/typescript-definitions/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | tsconfig.tsbuildinfo 4 | -------------------------------------------------------------------------------- /v1/examples/l3rd-name/README.md: -------------------------------------------------------------------------------- 1 | # Reference: Lower 3rd - Name 2 | 3 | This is a simple lower 3rd that displays a name. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | .DS_Store 4 | 5 | __old_from_tmp_repo/ 6 | 7 | # scripts used in local testing 8 | local-scripts/ 9 | -------------------------------------------------------------------------------- /v1/examples/README.md: -------------------------------------------------------------------------------- 1 | # OGraf Examples 2 | 3 | [Click here to download this folder as a zip file](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Febu%2Fograf%2Ftree%2Fmain%2Fv1%2Fexamples) 4 | -------------------------------------------------------------------------------- /v1/examples/renderer-test/README.md: -------------------------------------------------------------------------------- 1 | # Renderer Test Graphic 2 | 3 | This Graphic presents a number of steps for the renderer to perform. 4 | 5 | It intended to be used to test the functionality of a renderer. 6 | -------------------------------------------------------------------------------- /v1/typescript-definitions/src/definitions/vendor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vendors MUST use the prefix "v_" in any additional properties to ensure forward compatibility. For example "v_SuperFlyFluxCapacitorStatus". 3 | */ 4 | export type VendorSpecific = `v_${VendorName}${string}`; 5 | type VendorName = string; 6 | -------------------------------------------------------------------------------- /v1/examples/minimal/README.md: -------------------------------------------------------------------------------- 1 | # Reference: Minimal Graphic 2 | 3 | This is a minimal implementation of a Graphic. 4 | It contains the minimum required properties and methods to be a valid Graphic. 5 | 6 | _Note: This is intended to be used when developing and validating renderer systems._ 7 | _If you're looking for a good starting point for developing Graphics, use the [L3rd Name example](../l3rd-name/) instead._ 8 | -------------------------------------------------------------------------------- /v1/examples/minimal/minimal.ograf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json", 3 | "name": "Minimal Test Graphic", 4 | "description": "This Graphic includes the bare minimum required to be a valid OGraf Graphic. It displays a 'Hello World!' message.", 5 | "id": "minimal-example", 6 | "main": "graphic.mjs", 7 | "supportsRealTime": true, 8 | "supportsNonRealTime": false 9 | } 10 | -------------------------------------------------------------------------------- /v1/examples/ograf-logo/README.md: -------------------------------------------------------------------------------- 1 | # Reference: Minimal Graphic 2 | 3 | This is a version of minimal implementation of a Graphic. 4 | It fades IN and OUT a image-file. 5 | It contains the minimum required properties and methods to be a valid Graphic. 6 | 7 | _Note: This is intended to be used when developing and validating renderer systems._ 8 | _If you're looking for a good starting point for developing Graphics, use the [L3rd Name example](../l3rd-name/) instead._ 9 | -------------------------------------------------------------------------------- /v1/typescript-definitions/src/main.ts: -------------------------------------------------------------------------------- 1 | export * as GraphicsAPI from "./apis/graphicsAPI"; 2 | 3 | export * from "./definitions/render"; 4 | export * from "./definitions/types"; 5 | export * from "./definitions/vendor"; 6 | import * as GeneratedGraphicsManifest from "./generated/graphics-manifest"; 7 | 8 | export { GeneratedGraphicsManifest } 9 | 10 | // Also export the GraphicsManifest types using simplified names 11 | export type GraphicsManifest = GeneratedGraphicsManifest.HttpsOgrafEbuIoV1SpecificationJsonSchemasGraphicsSchemaJson 12 | export type GraphicsManifestCustomAction = GeneratedGraphicsManifest.HttpsOgrafEbuIoV1SpecificationJsonSchemasLibActionJson 13 | 14 | -------------------------------------------------------------------------------- /v1/examples/ograf-logo/logo.ograf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json", 3 | "name": "Minimal Test Graphics", 4 | "description": "This Graphic shows an OGraf logo", 5 | "id": "minimal-logo", 6 | "main": "graphic.mjs", 7 | "supportsRealTime": true, 8 | "supportsNonRealTime": false, 9 | "author": { 10 | "name": "Markus Nygård, YLE" 11 | }, 12 | "renderRequirements": [ 13 | { 14 | "resolution": { 15 | "width": { "min": 600, "max": 4000, "ideal": 1920 } 16 | }, 17 | "frameRate": { "min": 1, "max": 240, "ideal": 60 } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /v1/typescript-definitions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ograf", 3 | "version": "1.0.0", 4 | "main": "dist/main.js", 5 | "types": "dist/main.d.ts", 6 | "license": "MIT", 7 | "description": "TypeScript definitions for Ograf graphics", 8 | "repository": { 9 | "url": "https://github.com/ebu/ograf" 10 | }, 11 | "homepage": "https://ograf.ebu.io", 12 | "keywords": [ 13 | "ograf", 14 | "types", 15 | "typescript", 16 | "ebu", 17 | "broadcast", 18 | "graphics" 19 | ], 20 | "scripts": { 21 | "build": "tsc --build", 22 | "watch": "tsc --watch", 23 | "generate-types": "node scripts/generate-types.js" 24 | }, 25 | "devDependencies": { 26 | "typescript": "^5.6.3", 27 | "json-schema-to-typescript": "^15.0.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /v1/specification/json-schemas/lib/constraints/boolean.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://ograf.ebu.io/v1/specification/json-schemas/lib/constraints/boolean.json", 4 | "type": "object", 5 | "description": "The boolean constraint is used to specify a constraint for a boolean property. (Inspired by https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constrainboolean)", 6 | "properties": { 7 | "exact": { 8 | "description": "A boolean specifying a specific, required, value the property must have to be considered acceptable.", 9 | "type": "boolean" 10 | }, 11 | "ideal": { 12 | "description": "A boolean specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match.", 13 | "type": "boolean" 14 | } 15 | }, 16 | "patternProperties": { 17 | "^v_.*": {} 18 | }, 19 | "additionalProperties": false 20 | } 21 | -------------------------------------------------------------------------------- /v1/examples/l3rd-name/l3rd.ograf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json", 3 | "name": "Lower 3rd - Name", 4 | "description": "Name lower third", 5 | "id": "l3rd-name", 6 | "main": "graphic.mjs", 7 | "version": "0", 8 | "author": { 9 | "name": "Johan Nyman, SuperFly.tv" 10 | }, 11 | "customActions": [ 12 | { 13 | "id": "highlight", 14 | "name": "Highlight", 15 | "description": "Highlight the name", 16 | "schema": null 17 | } 18 | ], 19 | "supportsRealTime": true, 20 | "supportsNonRealTime": true, 21 | "schema": { 22 | "type": "object", 23 | "properties": { 24 | "name": { 25 | "type": "string", 26 | "title": "Name", 27 | "default": "John Doe" 28 | }, 29 | "title": { 30 | "type": "string", 31 | "title": "Title", 32 | "default": "Ograf expert" 33 | } 34 | } 35 | }, 36 | "v_myLittleNote": "Vendor-specific properties can be added using the 'v_' prefix, like this!" 37 | } 38 | -------------------------------------------------------------------------------- /v1/examples/renderer-test/manifest.ograf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json", 3 | "name": "Renderer Test Graphic", 4 | "description": "This Graphic is used to validate a Renderer", 5 | "id": "renderer-test", 6 | "main": "graphic.mjs", 7 | "supportsRealTime": true, 8 | "supportsNonRealTime": false, 9 | "schema": { 10 | "type": "object", 11 | "properties": { 12 | "message": { 13 | "type": "string", 14 | "title": "Message", 15 | "default": "Hello" 16 | } 17 | } 18 | }, 19 | "customActions": [ 20 | { 21 | "id": "highlight", 22 | "name": "Highlight", 23 | "schema": null 24 | }, 25 | { 26 | "id": "setColor", 27 | "name": "Set Color", 28 | "schema": { 29 | "type": "object", 30 | "properties": { 31 | "color": { 32 | "type": "string", 33 | "title": "Color", 34 | "default": "red" 35 | } 36 | } 37 | } 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This log lists changes between versions. 4 | 5 | 6 | 7 | 8 | ## Version 1 9 | 10 | The log below details the changes during development of this version: 11 | 12 | * 2025-03-27: Draft 0 made public. 13 | * 2025-04-23: Fix in JSON-schemas: Changed `main` property in Graphics manifest to be **mandatory**. 14 | (Before, it was defined as mandatory in the specification document, but not in the JSON-schemas.) 15 | * 2025-05-16: Add `renderRequirements` property to Graphics manifest 16 | * 2025-05-16: Add `data` argument to the `load` method. 17 | Before, the `data`-payload was only sent using the `updateData()` method. Now it must be sent on `load()` as well. 18 | * 2025-05-16: Change return values of Graphic methods to optionally be `undefined`. 19 | (An `undefined` value should be treated as `{ statusCode: 200 }`) 20 | * 2025-06-09: Rename the "v1-draft-0" to "v1" in preparation for the first release. 21 | * 2025-06-13: Add requirement on manifest file name. 22 | The manifest file must now have the suffix `".ograf"`. 23 | Before, there where no requirements on the manifest file name. 24 | * 2025-06-13: Add optional `skipAnimation` argument to `updateAction` and `customAction`. 25 | Before, it was only included in `playAction` and `stopAction`. 26 | * 2025-07-02: Change requirement on manifest file name. 27 | The manifest file must now have the suffix `".ograf.json"`. 28 | Before, it was `".ograf"`. 29 | -------------------------------------------------------------------------------- /v1/specification/json-schemas/lib/constraints/string.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://ograf.ebu.io/v1/specification/json-schemas/lib/constraints/string.json", 4 | "type": "object", 5 | "description": "The string constraint is used to specify a constraint for a string property. (Inspired by https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraindomstring)", 6 | "properties": { 7 | "exact": { 8 | "description": "A string or an array of strings, one of which must be the value of the property. If the property can't be set to one of the listed values, matching will fail.", 9 | "type": "string" 10 | }, 11 | "ideal": { 12 | "description": "A string (or an array of strings), specifying ideal values for the property. If possible, one of the listed values will be used, but if it's not possible, the user agent will use the closest possible match.", 13 | "oneOf": [ 14 | { 15 | "type": "string" 16 | }, 17 | { 18 | "type": "array", 19 | "items": { 20 | "type": "string" 21 | } 22 | } 23 | ] 24 | } 25 | }, 26 | "patternProperties": { 27 | "^v_.*": {} 28 | }, 29 | "additionalProperties": false 30 | } 31 | -------------------------------------------------------------------------------- /v1/examples/minimal/graphic.mjs: -------------------------------------------------------------------------------- 1 | class Graphic extends HTMLElement { 2 | connectedCallback() { 3 | // Called when the element is added to the DOM 4 | // Note: Don't paint any pixels at this point, wait for load() to be called 5 | } 6 | 7 | async load(params) { 8 | if (params.renderType !== "realtime") 9 | throw new Error("Only realtime rendering is supported by this graphic"); 10 | 11 | const elText = document.createElement("p"); 12 | elText.style.backgroundColor = "#ffffff"; 13 | elText.style.color = "#000000"; 14 | elText.style.display = "inline-block"; 15 | elText.style.padding = "10px"; 16 | elText.style.border = "1px solid #000000"; 17 | elText.innerHTML = "Hello world!"; 18 | this.appendChild(elText); 19 | 20 | // When everything is loaded we can return: 21 | return { 22 | statusCode: 200, 23 | }; 24 | } 25 | async dispose(_params) { 26 | this.innerHTML = ""; 27 | } 28 | async updateAction(_params) { 29 | // No actions are implemented in this minimal example 30 | } 31 | async playAction(_params) { 32 | // No actions are implemented in this minimal example 33 | } 34 | async stopAction(_params) { 35 | // No actions are implemented in this minimal example 36 | } 37 | async customAction(params) { 38 | // No actions are implemented in this minimal example 39 | } 40 | } 41 | 42 | export default Graphic; 43 | 44 | // Note: The renderer will render the component 45 | -------------------------------------------------------------------------------- /v1/specification/json-schemas/lib/action.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://ograf.ebu.io/v1/specification/json-schemas/lib/action.json", 4 | "type": "object", 5 | "properties": { 6 | "id": { 7 | "type": "string", 8 | "description": "The identity of the action. The id must be unique within the graphic." 9 | }, 10 | "name": { 11 | "type": "string", 12 | "description": "The name of the action. This is displayed to the user." 13 | }, 14 | "description": { 15 | "type": "string", 16 | "description": "A longer description of the action. This is displayed to the user." 17 | }, 18 | "schema": { 19 | "description": "The schema of the action. This is used to validate the action parameters as well as auto-generate a GUI for the action. If the action does not require any parameters, set this to null.", 20 | "oneOf": [ 21 | { 22 | "type": "object", 23 | "$ref": "https://superflytv.github.io/GraphicsDataDefinition/gdd-meta-schema/v1/lib/object.json" 24 | }, 25 | { 26 | "type": "null" 27 | } 28 | ] 29 | } 30 | }, 31 | "required": [ 32 | "id", 33 | "name" 34 | ], 35 | "patternProperties": { 36 | "^v_.*": {} 37 | }, 38 | "additionalProperties": false 39 | } 40 | -------------------------------------------------------------------------------- /v1/specification/json-schemas/lib/constraints/number.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://ograf.ebu.io/v1/specification/json-schemas/lib/constraints/number.json", 4 | "type": "object", 5 | "description": "The number constraint is used to specify a constraint for a numerical property. (Inspired by https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraindouble)", 6 | "properties": { 7 | "max": { 8 | "description": "A number specifying the largest permissible value of the property it describes. If the value cannot remain equal to or less than this value, matching will fail.", 9 | "type": "number" 10 | }, 11 | "min": { 12 | "description": "A number specifying the smallest permissible value of the property it describes. If the value cannot remain equal to or greater than this value, matching will fail.", 13 | "type": "number" 14 | }, 15 | "exact": { 16 | "description": "A number specifying a specific, required, value the property must have to be considered acceptable.", 17 | "type": "number" 18 | }, 19 | "ideal": { 20 | "description": "A number specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match.", 21 | "type": "number" 22 | } 23 | }, 24 | "patternProperties": { 25 | "^v_.*": {} 26 | }, 27 | "additionalProperties": false 28 | } 29 | -------------------------------------------------------------------------------- /v1/typescript-definitions/src/definitions/types.ts: -------------------------------------------------------------------------------- 1 | import { VendorSpecific } from "./vendor"; 2 | 3 | /** 4 | * Default return data of any action 5 | */ 6 | export type ReturnPayload = { 7 | /** 8 | * HTTP response code. 200 if the method was executed successfully, 4xx if client error, 5xx if server error 9 | * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status 10 | */ 11 | statusCode: number; 12 | /** 13 | * (Optional) A human-readable message to help understand the statusCode. 14 | */ 15 | statusMessage?: string; 16 | } & VendorExtend; 17 | 18 | /** 19 | * This indicates that a payload is empty 20 | * (but a vendor may choose to add their own vendor-specific properties) 21 | */ 22 | export type EmptyPayload = VendorExtend; 23 | 24 | /** 25 | * This indicates that the Parameters object is empty 26 | * (but a vendor may choose to add their own vendor-specific properties) 27 | */ 28 | export type EmptyParams = VendorExtend; 29 | 30 | /** 31 | * All parameters and return values can be extended with vendor-specific properties 32 | */ 33 | export interface VendorExtend { 34 | [vendorSpecific: VendorSpecific]: unknown; 35 | } 36 | 37 | /** Payload when invoking an action of a GraphicInstance or a Renderer */ 38 | export type ActionInvokeParams = { 39 | /** Graphic id, as defined by the Graphic manifest*/ 40 | id: string; 41 | /** Params to send into the method */ 42 | payload: unknown; 43 | } & VendorExtend; 44 | 45 | export type PlayActionReturnPayload = ReturnPayload & { 46 | /** The resulting step from a PlayAction */ 47 | currentStep: number; 48 | }; 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OGraf 2 | 3 | 4 | 5 | **OGraf** is an Open specification for HTML based Graphics, used in live television and post production workflows. 6 | 7 | * [Link to Project Web Page](https://ograf.ebu.io) 8 | * [Link to Project Github repository ](https://github.com/ebu/ograf) 9 | 10 | ## Project status 11 | 12 | * **The OGraf Graphics specification** (version 1) is production ready and considered stable. Additional features and non-breaking changes will be added continously. 13 | * **The OGraf Control API specification** is under development. 14 | 15 | EBU members as well as the general industry is invited to join the [HTML Graphics Working Group](https://tech.ebu.ch/groups/html_graphics) to participate in discussions and development of the OGraf specification. 16 | 17 | Feedback can also be submitted using [GitHub Issues](https://github.com/ebu/ograf/issues). 18 | 19 | Graphics and Render system developers are encouraged to follow changes in the [Changelog](./CHANGELOG.md). 20 | 21 | ### Time plan 22 | 23 | - **Done:** 24 | - Version 1 of **Graphics definition** 25 | - Early 2026: 26 | - First draft of **Server API** to be presented. 27 | - Mid 2026: 28 | - Version 1 to be extended with **Server API** definitions. 29 | 30 | ## Introduction 31 | 32 | The OGraf specification defines a way to create HTML based graphics as well as an (upcoming) Control API. 33 | It allows for vendor interoperability between Graphics, Rendering systems and Control systems. 34 | 35 | ## Getting Started 36 | 37 | Useful resources: 38 | * [Examples of OGraf Graphics](https://github.com/ebu/ograf/tree/main/v1/examples). 39 | * [OGraf Specification](./v1/specification/docs/Specification.md). 40 | 41 | 42 | ### Tools 43 | 44 | * The **[OGraf Devtool](https://github.com/SuperFlyTV/ograf-devtool)** is a tool for developing OGraf graphics. 45 | * The **[Simple Rendering system](https://github.com/SuperFlyTV/ograf-server)** can be used to play OGraf Graphics in a browser (for use in any existing system capable of rendering HTML graphics). 46 | -------------------------------------------------------------------------------- /v1/typescript-definitions/scripts/generate-types.js: -------------------------------------------------------------------------------- 1 | const { compile, compileFromFile } = require("json-schema-to-typescript"); 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | 5 | async function main() { 6 | 7 | 8 | const outputPath = path.resolve("./src/generated"); 9 | const schemaPath = path.resolve("../specification/json-schemas/graphics"); 10 | const options = { 11 | bannerComment: `/* eslint-disable */ 12 | /** 13 | * This file was automatically generated by json-schema-to-typescript. 14 | * DO NOT MODIFY IT BY HAND! Instead, modify the source JSON Schema file, 15 | * and run 'npm run generate-types' to regenerate this file. 16 | */`, 17 | customName: (schema, name) => { 18 | // console.log("name", name, schema); 19 | return undefined; 20 | }, 21 | }; 22 | 23 | // Compile JSON schemas to TypeScript types: 24 | await saveFile( 25 | path.join(outputPath, "graphics-manifest.ts"), 26 | await compileFromFile( 27 | path.join(schemaPath, "schema.json"), 28 | options 29 | ) 30 | ); 31 | // await saveFile( 32 | // path.join(outputPath, "renderer-manifest.d.ts"), 33 | // await compileFromFile( 34 | // path.join(schemaPath, "renderer-manifest/schema.json"), 35 | // options 36 | // ) 37 | // ); 38 | 39 | console.log(`Generated types at: ${outputPath}`); 40 | } 41 | async function saveFile(savePath, contents) { 42 | // Ensure the folder exists: 43 | await fs.promises.mkdir(path.dirname(savePath), { recursive: true }); 44 | 45 | // The ^v_.* are generated as [k: string]: unknown, fix that: 46 | contents = contents.replaceAll(/(v_.+\n.*\n[ \t]+)(\[k: string\]: unknown)/g, '$1[k: `v_${string}`]: unknown') 47 | 48 | 49 | // Replace contents in case of using localhost during testing: 50 | contents = contents.replaceAll('HttpLocalhost8080', 'HttpsOgrafEbuIo') 51 | contents = contents.replaceAll('Http1270018081V1', 'HttpsOgrafEbuIoV1') 52 | 53 | await fs.promises.writeFile(savePath, contents); 54 | } 55 | 56 | main().catch((err) => { 57 | console.error(err); 58 | process.exit(1); 59 | }); 60 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | # Allows you to run this workflow manually from the Actions tab 5 | workflow_dispatch: 6 | 7 | permissions: 8 | id-token: write # Required for OIDC 9 | contents: read 10 | 11 | jobs: 12 | Publish: 13 | name: Publish to NPM 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 15 16 | defaults: 17 | run: 18 | working-directory: ./v1/typescript-definitions 19 | steps: 20 | - uses: actions/checkout@v5 21 | - uses: actions/setup-node@v5 22 | with: 23 | node-version: 24 24 | registry-url: "https://registry.npmjs.org" 25 | 26 | # Ensure npm 11.5.1 or later is installed: 27 | # - name: Update npm 28 | # run: npm install -g npm@latest 29 | 30 | - name: Prepare Environment 31 | # Install from package-lock.json: 32 | run: npm ci 33 | env: 34 | CI: true 35 | 36 | - name: Determine type of release 37 | id: release-tag 38 | run: | 39 | if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then 40 | echo "Publish latest" 41 | echo "tag=latest" >> $GITHUB_OUTPUT 42 | else 43 | echo "Publish nightly" 44 | echo "tag=nightly" >> $GITHUB_OUTPUT 45 | fi 46 | 47 | - name: Bump version to prerelease 48 | if: github.ref != 'refs/heads/main' 49 | run: | 50 | COMMIT_TIMESTAMP=$(git log -1 --pretty=format:%ct HEAD) 51 | COMMIT_DATE=$(date -d @$COMMIT_TIMESTAMP +%Y%m%d-%H%M%S) 52 | GIT_HASH=$(git rev-parse --short HEAD) 53 | PRERELEASE_TAG=nightly-$(echo "${{ github.ref_name }}" | sed -r 's/[^a-z0-9]+/-/gi') 54 | npm version prerelease --preid $PRERELEASE_TAG-$COMMIT_DATE-$GIT_HASH 55 | 56 | - name: Generate types 57 | run: npm run generate-types 58 | env: 59 | CI: true 60 | 61 | - name: Build 62 | run: npm run build 63 | env: 64 | CI: true 65 | 66 | - name: Publish to NPM 67 | run: npm publish --tag ${{ steps.release-tag.outputs.tag }} 68 | env: 69 | CI: true 70 | -------------------------------------------------------------------------------- /v1/examples/ograf-logo/graphic.mjs: -------------------------------------------------------------------------------- 1 | class Graphic extends HTMLElement { 2 | connectedCallback() { 3 | // Called when the element is added to the DOM 4 | // Note: Don't paint any pixels at this point, wait for load() to be called 5 | } 6 | 7 | async load(params) { 8 | if (params.renderType !== "realtime") 9 | throw new Error("Only realtime rendering is supported by this graphic"); 10 | 11 | const logo = document.createElement("img"); 12 | logo.src = await this._loadImage( 13 | import.meta.resolve("./lib/ograf-logo-app.svg") 14 | ); 15 | logo.style.opacity = 0; 16 | logo.style.position = "absolute"; 17 | logo.style.left = `calc(5%)`; 18 | logo.style.bottom = `calc(85%)`; 19 | logo.style.width = "50px"; 20 | logo.style.boxShadow = "0 0 10px rgba(0,0,0,0.5)"; 21 | logo.style.borderRadius = "5px"; 22 | this.appendChild(logo); 23 | 24 | 25 | this.elements = { 26 | logo, 27 | }; 28 | 29 | // When everything is loaded we can return: 30 | return { 31 | statusCode: 200, 32 | }; 33 | } 34 | async dispose(_params) { 35 | this.innerHTML = ""; 36 | } 37 | async updateAction(_params) { 38 | // No updateActions are implemented in this example 39 | } 40 | async playAction(_params) { 41 | // Fade in logo 42 | this.elements.logo.style.transition = "opacity 1s"; 43 | this.elements.logo.style.opacity = "1"; 44 | } 45 | async stopAction(_params) { 46 | // fade out logo 47 | this.elements.logo.style.transition = "opacity 1s"; 48 | this.elements.logo.style.opacity = "0"; 49 | } 50 | async customAction(params) { 51 | // No customAction are implemented in this example 52 | } 53 | async goToTime(_payload) { 54 | throw new Error("Non-realtime not supported!"); 55 | } 56 | async setActionsSchedule(_payload) { 57 | throw new Error("Non-realtime not supported!"); 58 | } 59 | _loadImage(url) { 60 | return new Promise((resolve, reject) => { 61 | const newImg = new Image(); 62 | newImg.onload = function () { 63 | resolve(this.src); 64 | }; 65 | newImg.onerror = reject; 66 | newImg.src = url; 67 | }); 68 | } 69 | } 70 | 71 | export default Graphic; 72 | 73 | // Note: The renderer will render the component 74 | -------------------------------------------------------------------------------- /v1/typescript-definitions/README.md: -------------------------------------------------------------------------------- 1 | # OGraf Typescript Definitions 2 | 3 | These are Typescript definitions for the OGraf API. 4 | 5 | https://github.com/ebu/ograf 6 | 7 | ## Getting Started 8 | 9 | ```typescript 10 | 11 | import { GraphicsAPI } from 'ograf'; 12 | 13 | 14 | // Setup my OGraf graphic WebComponent: 15 | class MyOGrafGraphic extends HTMLElement implements GraphicsAPI.Graphic { 16 | connectedCallback() { 17 | // Called when the element is added to the DOM 18 | // Note: Don't paint any pixels at this point, wait for load() to be called 19 | } 20 | 21 | async load(params) { 22 | if (params.renderType !== "realtime") 23 | throw new Error("Only realtime rendering is supported by this graphic"); 24 | 25 | const elText = document.createElement("p"); 26 | elText.innerHTML = "Hello world!"; 27 | this.appendChild(elText); 28 | 29 | // When everything is loaded we can return: 30 | return { 31 | statusCode: 200, 32 | }; 33 | } 34 | async dispose(_params) { 35 | this.innerHTML = ""; 36 | } 37 | async updateAction(_params) { 38 | // No actions are implemented in this minimal example 39 | } 40 | async playAction(_params) { 41 | // No actions are implemented in this minimal example 42 | } 43 | async stopAction(_params) { 44 | // No actions are implemented in this minimal example 45 | } 46 | async customAction(params) { 47 | // No actions are implemented in this minimal example 48 | } 49 | async goToTime(_payload) { 50 | throw new Error("Non-realtime not supported!"); 51 | } 52 | async setActionsSchedule(_payload) { 53 | throw new Error("Non-realtime not supported!"); 54 | } 55 | } 56 | 57 | ``` 58 | 59 | 60 | ## For Developers 61 | 62 | The instructions below are for developers who want to work on the Typescript definitions. 63 | 64 | ### Install & build 65 | 66 | ```bash 67 | 68 | npm install 69 | npm run generate-types # If any of the specification has changed 70 | npm run build 71 | # or 72 | npm run watch 73 | 74 | ``` 75 | 76 | ### Publish to NPM 77 | 78 | To publish a new version to NPM: 79 | 80 | 1. Bump the version `npm version major|minor|patch` 81 | 2. Commit and push to GitHub 82 | 3. Go to the Actions tab and trigger the "Publish to NPM" workflow 83 | 84 | To publish a nightly build to NPM: 85 | 86 | 1. Go to the Actions tab and trigger the "Publish nightly to NPM" workflow 87 | -------------------------------------------------------------------------------- /v1/typescript-definitions/src/definitions/render.ts: -------------------------------------------------------------------------------- 1 | import { VendorExtend } from "./types"; 2 | 3 | /** 4 | * A RenderCharacteristics is a set of characteristics / capabilities 5 | * of the Renderer, that affects how the Graphic will be rendered. 6 | */ 7 | export type RenderCharacteristics = { 8 | resolution?: { 9 | width: number; 10 | height: number; 11 | } & VendorExtend; 12 | /** Which frameRate the renderer will be rendering in. Examples: 50, 60, 29.97 */ 13 | frameRate?: number; 14 | 15 | /** Whether the renderer has access to the public internet (so the graphic can fetch resources) */ 16 | accessToPublicInternet?: boolean 17 | 18 | // Ideas for future: 19 | // webcamInputs 20 | // keyer?: boolean; // 21 | 22 | } & VendorExtend; 23 | 24 | // These are inspired by the MediaTrackConstraints Web API. 25 | // see https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints 26 | 27 | export type ConstrainBoolean = { 28 | /** A Boolean which must be the value of the property. If the property can't be set to this value, matching will fail. */ 29 | exact?: boolean; 30 | 31 | /** A Boolean specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match. */ 32 | ideal?: boolean; 33 | } & VendorExtend; 34 | 35 | export type ConstrainNumber = { 36 | /** A number specifying the largest permissible value of the property it describes. If the value cannot remain equal to or less than this value, matching will fail. */ 37 | max?: number; 38 | 39 | /** A number specifying the smallest permissible value of the property it describes. If the value cannot remain equal to or greater than this value, matching will fail. */ 40 | min?: number; 41 | 42 | /** A number specifying a specific, required, value the property must have to be considered acceptable. */ 43 | exact?: number; 44 | 45 | /** A number specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match. */ 46 | ideal?: number; 47 | } & VendorExtend; 48 | 49 | /** The ConstrainString constraint type is used to specify a constraint for a property whose value is a string. */ 50 | export type ConstrainString = { 51 | /** A string or an array of strings, one of which must be the value of the property. If the property can't be set to one of the listed values, matching will fail. */ 52 | exact: T | T[]; 53 | 54 | /** A string or an array of strings, specifying ideal values for the property. If possible, one of the listed values will be used, but if it's not possible, the user agent will use the closest possible match. */ 55 | ideal: T | T[]; 56 | } & VendorExtend; 57 | -------------------------------------------------------------------------------- /v1/typescript-definitions/scripts/local-testing.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | /* 5 | ************************************************************************************************* 6 | 7 | This scripts replaces URLs in the manifest with local paths, for testing purposes. 8 | Usage: 9 | * `node scripts/local-testing.js` to replace URLs with local paths 10 | * `node scripts/local-testing.js --restore` to restore the original URLs 11 | * 12 | 13 | This is useful when testing the json-manifests locally. 14 | One simple way to serve the files locally is: 15 | * `cd ograf` 16 | * `npm install -g http-server` 17 | * `http-server -p 8080` // serves the files on http://localhost:8080 18 | 19 | ************************************************************************************************* 20 | */ 21 | 22 | let restore = false; 23 | process.argv.forEach((arg) => { 24 | if (arg === "--restore") restore = true; 25 | }); 26 | 27 | let replacements = [ 28 | { 29 | from: "https://ograf.ebu.io/", 30 | to: "http://localhost:8080/", 31 | }, 32 | ]; 33 | 34 | if (restore) { 35 | replacements = replacements.map((r) => { 36 | return { 37 | from: r.to, 38 | to: r.from, 39 | }; 40 | }); 41 | } 42 | 43 | let updateCount = 0; 44 | 45 | async function replaceInAllFiles(folderPath) { 46 | const files = await fs.promises.readdir(folderPath); 47 | 48 | for (const file of files) { 49 | if (file === "node_modules") continue; 50 | if (file === "local-testing.js") continue; 51 | // Only process these file types: 52 | 53 | const filePath = path.join(folderPath, file); 54 | 55 | // is dir? 56 | if ((await fs.promises.stat(filePath)).isDirectory()) { 57 | await replaceInAllFiles(filePath); 58 | } else { 59 | // Only process these file types: 60 | if ( 61 | !file.endsWith(".ts") && 62 | !file.endsWith(".js") && 63 | !file.endsWith(".json") 64 | ) 65 | continue; 66 | 67 | const fileContents = await fs.promises.readFile(filePath, "utf-8"); 68 | 69 | let newContents = fileContents; 70 | 71 | for (const replacement of replacements) { 72 | newContents = newContents.replaceAll(replacement.from, replacement.to); 73 | } 74 | 75 | if (newContents !== fileContents) { 76 | await fs.promises.writeFile(filePath, newContents); 77 | console.log(`Updated ${filePath}`); 78 | updateCount++; 79 | } 80 | } 81 | } 82 | } 83 | 84 | const basePath = path.resolve(__dirname, "../.."); 85 | replaceInAllFiles(basePath) 86 | .then(() => { 87 | if (!restore) { 88 | console.log(`Updated ${updateCount} files.`); 89 | console.log("To restore, run `node scripts/local-testing.js --restore`"); 90 | } else { 91 | console.log(`${updateCount} Files restored`); 92 | } 93 | }) 94 | .catch(console.error); 95 | -------------------------------------------------------------------------------- /docs/logo/og-logo-colour.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/logo/og-logo-mono-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/logo/og-logo-mono-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/logo/ograf-logo-app.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /v1/examples/l3rd-name/lib/ograf-logo-app.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /v1/examples/ograf-logo/lib/ograf-logo-app.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /v1/typescript-definitions/src/apis/graphicsAPI.ts: -------------------------------------------------------------------------------- 1 | import { RenderCharacteristics } from "../definitions/render"; 2 | import { 3 | PlayActionReturnPayload, 4 | ActionInvokeParams, 5 | ReturnPayload, 6 | EmptyPayload, 7 | EmptyParams, 8 | VendorExtend, 9 | } from "../definitions/types"; 10 | 11 | /** 12 | * ================================================================================================ 13 | * 14 | * The GraphicsAPI is a javascript interface, ie javascript methods exposed by the OGraf Graphics WebComponent. 15 | * 16 | * ================================================================================================ 17 | */ 18 | 19 | /** 20 | * This interface defines the methods that the Renderer can call on the Graphic. 21 | * @example class MyOGrafGraphic extends HTMLElement implements GraphicsAPI.Graphic {} 22 | * 23 | */ 24 | export interface Graphic { 25 | /** 26 | * Called by the Renderer when the Graphic has been loaded into the DOM 27 | * @returns a Promise that resolves when the Graphic has finished loading it's resources. 28 | */ 29 | load: ( 30 | params: { 31 | /** The data send here is defined in the manifest "schema". Note: This data MUST HAVE the same type as the `data` argument in updateAction method. */ 32 | data: unknown; 33 | 34 | /** Whether the rendering is done in realtime or non-realtime */ 35 | renderType: "realtime" | "non-realtime"; 36 | 37 | /** A set of characteristics / capabilities of the Renderer, that affects how the Graphic will be rendered. */ 38 | renderCharacteristics: RenderCharacteristics; 39 | } & VendorExtend 40 | ) => Promise; 41 | 42 | /** 43 | * Called by the Renderer to force the Graphic to terminate/dispose/clear any loaded resources. 44 | * This is called after the Renderer has unloaded the Graphic from the DOM. 45 | */ 46 | dispose: (params: EmptyParams) => Promise; 47 | 48 | /** This is called whenever user send a new data payload. */ 49 | updateAction: ( 50 | params: { 51 | /** The data send here is defined in the manifest "schema". Note: This data MUST HAVE the same type as the `data` argument in the load method. */ 52 | data: unknown; 53 | /** If true, skips animation (defaults to false) */ 54 | skipAnimation?: boolean; 55 | } & VendorExtend 56 | ) => Promise; 57 | 58 | /** This is called when user calls the "play" action. */ 59 | playAction: ( 60 | params: { 61 | /** How far to advance. 1 = next step/segment. (defaults to 1) */ 62 | delta: number; 63 | /** Jump to a specific step/segment (defaults to undefined) */ 64 | goto: number; 65 | /** If true, skips animation (defaults to false) */ 66 | skipAnimation?: boolean; 67 | } & VendorExtend 68 | ) => Promise; 69 | 70 | /** This is called when user calls the "stop" action. */ 71 | stopAction: ( 72 | params: { 73 | /** If true, skips animation (defaults to false) */ 74 | skipAnimation?: boolean 75 | } & VendorExtend 76 | ) => Promise; 77 | 78 | /** 79 | * Called by the Renderer to invoke an Action on the Graphic 80 | * @returns The return value of the invoked method (vendor-specific) 81 | */ 82 | customAction: ( 83 | params: ActionInvokeParams 84 | ) => Promise; 85 | 86 | /** 87 | * If the Graphic supports non-realtime rendering, this is called to make the graphic jump to a certain point in time. 88 | * @returns A Promise that resolves when the Graphic has finished rendering the requested frame. 89 | */ 90 | goToTime: ( 91 | params: { timestamp: number } & VendorExtend 92 | ) => Promise; 93 | 94 | /** 95 | * If the Graphic supports non-realtime rendering, this is called to schedule actions to be invoked at a certain point in time. 96 | * When this is called, the Graphic is expected to store the scheduled actions and invoke them when the time comes. 97 | * (A call to this replaces any previous scheduled actions.) 98 | * @returns A Promise that resolves when the Graphic has stored the scheduled actions. 99 | */ 100 | setActionsSchedule: ( 101 | params: { 102 | /** 103 | * A list of the scheduled actions to call at certain points in time. 104 | */ 105 | schedule: { 106 | timestamp: number; 107 | action: 108 | | ({ 109 | type: "updateAction"; 110 | params: Parameters[0]; 111 | } & VendorExtend) 112 | | ({ 113 | type: "playAction"; 114 | params: Parameters[0]; 115 | } & VendorExtend) 116 | | ({ 117 | type: "stopAction"; 118 | params: Parameters[0]; 119 | } & VendorExtend) 120 | | ({ 121 | type: "customAction"; 122 | params: Parameters[0]; 123 | } & VendorExtend); 124 | }[]; 125 | } & VendorExtend 126 | ) => Promise; 127 | } 128 | -------------------------------------------------------------------------------- /v1/examples/l3rd-name/lib/TextPlugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * TextPlugin 3.12.5 3 | * https://gsap.com 4 | * 5 | * @license Copyright 2008-2024, GreenSock. All rights reserved. 6 | * Subject to the terms at https://gsap.com/standard-license or for 7 | * Club GSAP members, the agreement issued with that membership. 8 | * @author: Jack Doyle, jack@greensock.com 9 | */ 10 | 11 | /* eslint-disable */ 12 | import { emojiSafeSplit, getText, splitInnerHTML } from "./utils/strings.js"; 13 | 14 | var gsap, 15 | _tempDiv, 16 | _getGSAP = function _getGSAP() { 17 | return gsap || typeof window !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap; 18 | }; 19 | 20 | export var TextPlugin = { 21 | version: "3.12.5", 22 | name: "text", 23 | init: function init(target, value, tween) { 24 | typeof value !== "object" && (value = { 25 | value: value 26 | }); 27 | 28 | var i = target.nodeName.toUpperCase(), 29 | data = this, 30 | _value = value, 31 | newClass = _value.newClass, 32 | oldClass = _value.oldClass, 33 | preserveSpaces = _value.preserveSpaces, 34 | rtl = _value.rtl, 35 | delimiter = data.delimiter = value.delimiter || "", 36 | fillChar = data.fillChar = value.fillChar || (value.padSpace ? " " : ""), 37 | _short, 38 | text, 39 | original, 40 | j, 41 | condensedText, 42 | condensedOriginal, 43 | aggregate, 44 | s; 45 | 46 | data.svg = target.getBBox && (i === "TEXT" || i === "TSPAN"); 47 | 48 | if (!("innerHTML" in target) && !data.svg) { 49 | return false; 50 | } 51 | 52 | data.target = target; 53 | 54 | if (!("value" in value)) { 55 | data.text = data.original = [""]; 56 | return; 57 | } 58 | 59 | original = splitInnerHTML(target, delimiter, false, preserveSpaces); 60 | _tempDiv || (_tempDiv = document.createElement("div")); 61 | _tempDiv.innerHTML = value.value; 62 | text = splitInnerHTML(_tempDiv, delimiter, false, preserveSpaces); 63 | data.from = tween._from; 64 | 65 | if ((data.from || rtl) && !(rtl && data.from)) { 66 | // right-to-left or "from()" tweens should invert things (but if it's BOTH .from() and rtl, inverting twice equals not inverting at all :) 67 | i = original; 68 | original = text; 69 | text = i; 70 | } 71 | 72 | data.hasClass = !!(newClass || oldClass); 73 | data.newClass = rtl ? oldClass : newClass; 74 | data.oldClass = rtl ? newClass : oldClass; 75 | i = original.length - text.length; 76 | _short = i < 0 ? original : text; 77 | 78 | if (i < 0) { 79 | i = -i; 80 | } 81 | 82 | while (--i > -1) { 83 | _short.push(fillChar); 84 | } 85 | 86 | if (value.type === "diff") { 87 | j = 0; 88 | condensedText = []; 89 | condensedOriginal = []; 90 | aggregate = ""; 91 | 92 | for (i = 0; i < text.length; i++) { 93 | s = text[i]; 94 | 95 | if (s === original[i]) { 96 | aggregate += s; 97 | } else { 98 | condensedText[j] = aggregate + s; 99 | condensedOriginal[j++] = aggregate + original[i]; 100 | aggregate = ""; 101 | } 102 | } 103 | 104 | text = condensedText; 105 | original = condensedOriginal; 106 | 107 | if (aggregate) { 108 | text.push(aggregate); 109 | original.push(aggregate); 110 | } 111 | } 112 | 113 | value.speed && tween.duration(Math.min(0.05 / value.speed * _short.length, value.maxDuration || 9999)); 114 | data.rtl = rtl; 115 | data.original = original; 116 | data.text = text; 117 | 118 | data._props.push("text"); 119 | }, 120 | render: function render(ratio, data) { 121 | if (ratio > 1) { 122 | ratio = 1; 123 | } else if (ratio < 0) { 124 | ratio = 0; 125 | } 126 | 127 | if (data.from) { 128 | ratio = 1 - ratio; 129 | } 130 | 131 | var text = data.text, 132 | hasClass = data.hasClass, 133 | newClass = data.newClass, 134 | oldClass = data.oldClass, 135 | delimiter = data.delimiter, 136 | target = data.target, 137 | fillChar = data.fillChar, 138 | original = data.original, 139 | rtl = data.rtl, 140 | l = text.length, 141 | i = (rtl ? 1 - ratio : ratio) * l + 0.5 | 0, 142 | applyNew, 143 | applyOld, 144 | str; 145 | 146 | if (hasClass && ratio) { 147 | applyNew = newClass && i; 148 | applyOld = oldClass && i !== l; 149 | str = (applyNew ? "" : "") + text.slice(0, i).join(delimiter) + (applyNew ? "" : "") + (applyOld ? "" : "") + delimiter + original.slice(i).join(delimiter) + (applyOld ? "" : ""); 150 | } else { 151 | str = text.slice(0, i).join(delimiter) + delimiter + original.slice(i).join(delimiter); 152 | } 153 | 154 | if (data.svg) { 155 | //SVG text elements don't have an "innerHTML" in Microsoft browsers. 156 | target.textContent = str; 157 | } else { 158 | target.innerHTML = fillChar === " " && ~str.indexOf(" ") ? str.split(" ").join("  ") : str; 159 | } 160 | } 161 | }; 162 | TextPlugin.splitInnerHTML = splitInnerHTML; 163 | TextPlugin.emojiSafeSplit = emojiSafeSplit; 164 | TextPlugin.getText = getText; 165 | _getGSAP() && gsap.registerPlugin(TextPlugin); 166 | export { TextPlugin as default }; -------------------------------------------------------------------------------- /docs/logo/ograf-logo-colour.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/logo/ograf-logo-mono-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/logo/ograf-logo-mono-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /v1/specification/json-schemas/graphics/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft/2020-12/schema", 3 | "$id": "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json", 4 | "type": "object", 5 | "properties": { 6 | "$schema": { 7 | "type": "string", 8 | "const": "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json", 9 | "description": "Reference to the JSON-schema for this manifest" 10 | }, 11 | "id": { 12 | "type": "string", 13 | "description": "The id of the Graphic uniquely identifies it. It is recommended to use a reverse domain name notation. For example: com.my-company.my-lowerthird." 14 | }, 15 | "version": { 16 | "type": "string", 17 | "description": "The version of the Graphic. The version SHOULD be alphabetically sortable. Examples: ['0', '1', '2'], ['1.0', '1.1', '1.2'], ['2024-07-01_final', '2024-07-01_final_final2']" 18 | }, 19 | "main": { 20 | "type": "string", 21 | "description": "The main entry point, ie the path to the main javascript file of the Graphic." 22 | }, 23 | "name": { 24 | "type": "string", 25 | "description": "Name of the Graphic" 26 | }, 27 | "description": { 28 | "type": "string", 29 | "description": "(optional) A longer description of the Graphic" 30 | }, 31 | "author": { 32 | "type": "object", 33 | "description": "(optional) About the author", 34 | "properties": { 35 | "name": { 36 | "type": "string", 37 | "description": "Name of the author" 38 | }, 39 | "email": { 40 | "type": "string", 41 | "description": "(optional) Email of the author" 42 | }, 43 | "url": { 44 | "type": "string", 45 | "description": "(optional) URL of the author" 46 | } 47 | }, 48 | "required": [ 49 | "name" 50 | ], 51 | "patternProperties": { 52 | "^v_.*": {} 53 | }, 54 | "additionalProperties": false 55 | }, 56 | "customActions": { 57 | "type": "array", 58 | "description": "Custom Actions that can be invoked on the Graphic.", 59 | "items": { 60 | "$ref": "https://ograf.ebu.io/v1/specification/json-schemas/lib/action.json" 61 | } 62 | }, 63 | "supportsRealTime": { 64 | "type": "boolean", 65 | "description": "Indicates if the Graphic supports real-time rendering" 66 | }, 67 | "supportsNonRealTime": { 68 | "type": "boolean", 69 | "description": "Indicates if the Graphic supports non-real-time rendering. Note: If true, the Graphic must implement the 'goToTime()' and the 'setActionsSchedule()' methods." 70 | }, 71 | "stepCount": { 72 | "type": "number", 73 | "description": "The number of steps a Graphic consists of. If the Graphic is simply triggered by a play, then a stop, this is considered a stepCount of 1 (which is the default behavior if left undefined). A value of -1 indicates that a Graphic as a dynamic/unknown number of steps.", 74 | "default": 1, 75 | "minimum": -1 76 | }, 77 | "schema": { 78 | "description": "The schema is used by a Graphic to define the data parameters of the 'update' method.", 79 | "type": "object", 80 | "$ref": "https://superflytv.github.io/GraphicsDataDefinition/gdd-meta-schema/v1/lib/object.json" 81 | }, 82 | "renderRequirements": { 83 | "description": "A list of requirements that this Graphic has for the rendering environment. At least one of the requirements must be met for the graphic to be expected to work.", 84 | "type": "array", 85 | "items": { 86 | "type": "object", 87 | "properties": { 88 | "resolution": { 89 | "description": "If set, specifies requirements for the resolution of the Renderer.", 90 | "type": "object", 91 | "properties": { 92 | "width": { 93 | "$ref": "https://ograf.ebu.io/v1/specification/json-schemas/lib/constraints/number.json" 94 | }, 95 | "height": { 96 | "$ref": "https://ograf.ebu.io/v1/specification/json-schemas/lib/constraints/number.json" 97 | } 98 | } 99 | }, 100 | "frameRate": { 101 | "description": "If set, specifies requirements for frame rate of the Renderer. Example: 60 fps", 102 | "$ref": "https://ograf.ebu.io/v1/specification/json-schemas/lib/constraints/number.json" 103 | }, 104 | "accessToPublicInternet": { 105 | "description": "If set, specifies requirement on whether the renderer has access to the public internet or not.", 106 | "$ref": "https://ograf.ebu.io/v1/specification/json-schemas/lib/constraints/boolean.json" 107 | } 108 | 109 | }, 110 | "patternProperties": { 111 | "^v_.*": {} 112 | }, 113 | "additionalProperties": false 114 | } 115 | } 116 | }, 117 | "required": [ 118 | "$schema", 119 | "id", 120 | "name", 121 | "main", 122 | "supportsRealTime", 123 | "supportsNonRealTime" 124 | ], 125 | "patternProperties": { 126 | "^v_.*": {} 127 | }, 128 | "additionalProperties": false 129 | } 130 | -------------------------------------------------------------------------------- /v1/typescript-definitions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "lib": ["ES2018"], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist", /* Redirect output structure to the directory. */ 16 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "exclude": [ 67 | "./test", 68 | "./dist" 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /v1/specification/docs/images/step-model1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | startend -------------------------------------------------------------------------------- /v1/typescript-definitions/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ograf", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "ograf", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "json-schema-to-typescript": "^15.0.3", 13 | "typescript": "^5.6.3" 14 | } 15 | }, 16 | "node_modules/@apidevtools/json-schema-ref-parser": { 17 | "version": "11.7.2", 18 | "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", 19 | "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", 20 | "dev": true, 21 | "dependencies": { 22 | "@jsdevtools/ono": "^7.1.3", 23 | "@types/json-schema": "^7.0.15", 24 | "js-yaml": "^4.1.0" 25 | }, 26 | "engines": { 27 | "node": ">= 16" 28 | }, 29 | "funding": { 30 | "url": "https://github.com/sponsors/philsturgeon" 31 | } 32 | }, 33 | "node_modules/@jsdevtools/ono": { 34 | "version": "7.1.3", 35 | "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", 36 | "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", 37 | "dev": true 38 | }, 39 | "node_modules/@types/json-schema": { 40 | "version": "7.0.15", 41 | "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 42 | "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 43 | "dev": true 44 | }, 45 | "node_modules/@types/lodash": { 46 | "version": "4.17.13", 47 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", 48 | "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", 49 | "dev": true 50 | }, 51 | "node_modules/argparse": { 52 | "version": "2.0.1", 53 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 54 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 55 | "dev": true 56 | }, 57 | "node_modules/fdir": { 58 | "version": "6.4.2", 59 | "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz", 60 | "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==", 61 | "dev": true, 62 | "peerDependencies": { 63 | "picomatch": "^3 || ^4" 64 | }, 65 | "peerDependenciesMeta": { 66 | "picomatch": { 67 | "optional": true 68 | } 69 | } 70 | }, 71 | "node_modules/is-extglob": { 72 | "version": "2.1.1", 73 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 74 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 75 | "dev": true, 76 | "engines": { 77 | "node": ">=0.10.0" 78 | } 79 | }, 80 | "node_modules/is-glob": { 81 | "version": "4.0.3", 82 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 83 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 84 | "dev": true, 85 | "dependencies": { 86 | "is-extglob": "^2.1.1" 87 | }, 88 | "engines": { 89 | "node": ">=0.10.0" 90 | } 91 | }, 92 | "node_modules/js-yaml": { 93 | "version": "4.1.0", 94 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 95 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 96 | "dev": true, 97 | "dependencies": { 98 | "argparse": "^2.0.1" 99 | }, 100 | "bin": { 101 | "js-yaml": "bin/js-yaml.js" 102 | } 103 | }, 104 | "node_modules/json-schema-to-typescript": { 105 | "version": "15.0.3", 106 | "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.3.tgz", 107 | "integrity": "sha512-iOKdzTUWEVM4nlxpFudFsWyUiu/Jakkga4OZPEt7CGoSEsAsUgdOZqR6pcgx2STBek9Gm4hcarJpXSzIvZ/hKA==", 108 | "dev": true, 109 | "dependencies": { 110 | "@apidevtools/json-schema-ref-parser": "^11.5.5", 111 | "@types/json-schema": "^7.0.15", 112 | "@types/lodash": "^4.17.7", 113 | "is-glob": "^4.0.3", 114 | "js-yaml": "^4.1.0", 115 | "lodash": "^4.17.21", 116 | "minimist": "^1.2.8", 117 | "prettier": "^3.2.5", 118 | "tinyglobby": "^0.2.9" 119 | }, 120 | "bin": { 121 | "json2ts": "dist/src/cli.js" 122 | }, 123 | "engines": { 124 | "node": ">=16.0.0" 125 | } 126 | }, 127 | "node_modules/lodash": { 128 | "version": "4.17.21", 129 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 130 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 131 | "dev": true 132 | }, 133 | "node_modules/minimist": { 134 | "version": "1.2.8", 135 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 136 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 137 | "dev": true, 138 | "funding": { 139 | "url": "https://github.com/sponsors/ljharb" 140 | } 141 | }, 142 | "node_modules/picomatch": { 143 | "version": "4.0.2", 144 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", 145 | "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", 146 | "dev": true, 147 | "engines": { 148 | "node": ">=12" 149 | }, 150 | "funding": { 151 | "url": "https://github.com/sponsors/jonschlinkert" 152 | } 153 | }, 154 | "node_modules/prettier": { 155 | "version": "3.4.0", 156 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.0.tgz", 157 | "integrity": "sha512-/OXNZcLyWkfo13ofOW5M7SLh+k5pnIs07owXK2teFpnfaOEcycnSy7HQxldaVX1ZP/7Q8oO1eDuQJNwbomQq5Q==", 158 | "dev": true, 159 | "bin": { 160 | "prettier": "bin/prettier.cjs" 161 | }, 162 | "engines": { 163 | "node": ">=14" 164 | }, 165 | "funding": { 166 | "url": "https://github.com/prettier/prettier?sponsor=1" 167 | } 168 | }, 169 | "node_modules/tinyglobby": { 170 | "version": "0.2.10", 171 | "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", 172 | "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", 173 | "dev": true, 174 | "dependencies": { 175 | "fdir": "^6.4.2", 176 | "picomatch": "^4.0.2" 177 | }, 178 | "engines": { 179 | "node": ">=12.0.0" 180 | } 181 | }, 182 | "node_modules/typescript": { 183 | "version": "5.6.3", 184 | "dev": true, 185 | "license": "Apache-2.0", 186 | "bin": { 187 | "tsc": "bin/tsc", 188 | "tsserver": "bin/tsserver" 189 | }, 190 | "engines": { 191 | "node": ">=14.17" 192 | } 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /v1/examples/renderer-test/graphic.mjs: -------------------------------------------------------------------------------- 1 | class Graphic extends HTMLElement { 2 | connectedCallback() { 3 | // Called when the element is added to the DOM 4 | // Note: Don't paint any pixels at this point, wait for load() to be called 5 | } 6 | 7 | async load(params) { 8 | this.style.fontSize = "24px"; 9 | 10 | // Note: the OGraf specification says that the load() method should not paint any pixels. 11 | // This directive is ignored in this test graphic. 12 | 13 | const infoMessageContainer = document.createElement("div"); 14 | infoMessageContainer.style.color = "#000"; 15 | infoMessageContainer.style.position = "absolute"; 16 | infoMessageContainer.style.top = "25%"; 17 | infoMessageContainer.style.left = "0%"; 18 | infoMessageContainer.style.right = "0%"; 19 | infoMessageContainer.style.textAlign = "center"; 20 | infoMessageContainer.style.zIndex = 1; 21 | this.appendChild(infoMessageContainer); 22 | 23 | 24 | const infoMessage = document.createElement("div"); 25 | infoMessage.style.backgroundColor = "#ffffff"; 26 | infoMessage.style.display = "inline-block"; 27 | infoMessage.style.padding = "1em"; 28 | infoMessageContainer.appendChild(infoMessage); 29 | this.infoMessage = infoMessage 30 | 31 | this._infoMessage('Starting up...') 32 | 33 | if (params.renderCharacteristics && 34 | params.renderCharacteristics.resolution 35 | ) { 36 | // This fills the background with a red box, which should be covered by the blue fill below 37 | const fullContainer = document.createElement("div"); 38 | fullContainer.style.position = "absolute"; 39 | fullContainer.style.top = '0' 40 | fullContainer.style.left = '0' 41 | fullContainer.style.right = '0' 42 | fullContainer.style.bottom = '0' 43 | fullContainer.style.backgroundColor = "#f00"; 44 | this.appendChild(fullContainer); 45 | 46 | // This paints a blue box, which should be the size of the renderer 47 | const sizedContainer = document.createElement("div"); 48 | sizedContainer.style.position = "absolute"; 49 | sizedContainer.style.top = '0' 50 | sizedContainer.style.left = '0' 51 | sizedContainer.style.width = `${params.renderCharacteristics.resolution.width}px` 52 | sizedContainer.style.height = `${params.renderCharacteristics.resolution.height}px` 53 | sizedContainer.style.backgroundColor = "#87a0de"; 54 | this.appendChild(sizedContainer); 55 | 56 | } 57 | 58 | return this._handleAction('load', params); 59 | } 60 | async dispose(_params) { 61 | this.innerHTML = ""; 62 | } 63 | async updateAction(params) { 64 | return this._handleAction('updateAction', params); 65 | } 66 | async playAction(params) { 67 | return this._handleAction('playAction', params); 68 | } 69 | async stopAction(params) { 70 | return this._handleAction('stopAction', params); 71 | } 72 | async customAction(params) { 73 | return this._handleAction('customAction', params); 74 | } 75 | 76 | loggedActions = [] 77 | _handleAction(type, params) { 78 | const lastAction = { type, params } 79 | this.loggedActions.push(lastAction); 80 | 81 | const currentStep = this.steps[this.stepIndex] 82 | if (!currentStep) { 83 | return this._infoMessage('No more steps to verify, this test is completed'); 84 | } 85 | let error = currentStep.verify(lastAction) 86 | 87 | 88 | if (!error) { 89 | this.stepIndex++ 90 | 91 | const nextStep = this.steps[this.stepIndex] 92 | 93 | if (nextStep) { 94 | return this._infoMessage(`${currentStep.completed ? `${currentStep.completed}\n` : ''}Step ${this.stepIndex} completed.\nNext, ${nextStep.prompt}`); 95 | } else { 96 | return this._infoMessage('No more steps to verify, this test is completed'); 97 | } 98 | } else { 99 | 100 | this._errorMessage(`Expected ${currentStep.prompt}\n${error}`) 101 | 102 | } 103 | } 104 | 105 | 106 | _errorMessage(error) { 107 | this.infoMessage.innerHTML = this._formatText(error) 108 | this.infoMessage.style.backgroundColor = "#bb0000"; 109 | this.infoMessage.style.color = "#fff"; 110 | 111 | return { 112 | statusCode: 400, 113 | statusMessage: error 114 | } 115 | } 116 | _infoMessage(str) { 117 | this.infoMessage.innerHTML = this._formatText(str) 118 | this.infoMessage.style.backgroundColor = "#fff"; 119 | this.infoMessage.style.color = "#000"; 120 | 121 | return { 122 | statusCode: 200, 123 | statusMessage: str 124 | } 125 | } 126 | _formatText(str) { 127 | return str.replaceAll(/\n/g, "
"); 128 | } 129 | 130 | 131 | steps = [ 132 | { 133 | prompt: 'N/A', 134 | completed: 'load() method seems okay!', 135 | verify: (lastAction) => { 136 | if (lastAction.type !== 'load') return 'Expected the load() method to be called' 137 | if (lastAction.params.renderType !== 'realtime') return 'Only realtime rendering is supported by this graphic!' 138 | if (!lastAction.params.data) return 'No `data` argument provided to the load() method' 139 | if (!lastAction.params.data.message) return 'Expected a `message` argument in the `data` object provided to the load() method' 140 | } 141 | }, 142 | { 143 | prompt: 'call the playAction() method (with no skipAnimation set)', 144 | verify: (lastAction) => { 145 | if (lastAction.type !== 'playAction') return 'Expected the playAction() method to be called'; 146 | if (lastAction.params.skipAnimation) return 'Expected "skipAnimation" to be undefined or false, got true'; 147 | } 148 | }, 149 | { 150 | prompt: 'call the playAction() method, with skipAnimation set to true.', 151 | verify: (lastAction) => { 152 | if (lastAction.type !== 'playAction') return 'Expected the playAction() method to be called'; 153 | if (lastAction.params.skipAnimation !== true) return `Expected the playAction to be called with { skipAnimation: true }, got ${JSON.stringify(lastAction.params)}`; 154 | } 155 | }, 156 | { 157 | prompt: `call the playAction() method, with 'goto' set to 4.`, 158 | verify: (lastAction) => { 159 | if (lastAction.type !== 'playAction') return 'Expected the playAction() method to be called'; 160 | if (lastAction.params.goto !== 4) return `Expected the playAction to be called with 'goto' set to 4, got ${JSON.stringify(lastAction.params)}`; 161 | if (lastAction.params.delta !== undefined) return `Expected the playAction to be called with 'delta' being undefined, got ${JSON.stringify(lastAction.params)}`; 162 | } 163 | }, 164 | { 165 | prompt: `call the playAction() method, with 'delta' set to -1.`, 166 | verify: (lastAction) => { 167 | if (lastAction.type !== 'playAction') return 'Expected the playAction() method to be called'; 168 | if (lastAction.params.delta !== -1) return `Expected the playAction to be called with 'delta' set to -1, got ${JSON.stringify(lastAction.params)}`; 169 | if (lastAction.params.goto !== undefined) return `Expected the playAction to be called with 'goto' being undefined, got ${JSON.stringify(lastAction.params)}`; 170 | } 171 | }, 172 | { 173 | prompt: `call the updateAction() method, with data { message: 'Hello' }`, 174 | verify: (lastAction) => { 175 | if (lastAction.type !== 'updateAction') return 'Expected the updateAction() method to be called'; 176 | if (lastAction.params.data.message !== 'Hello') return `Expected the updateAction to be called with 'delta' set to -1, got ${JSON.stringify(lastAction.params)}`; 177 | } 178 | }, 179 | { 180 | prompt: `call the stopAction() method`, 181 | verify: (lastAction) => { 182 | if (lastAction.type !== 'stopAction') return 'Expected the stopAction() method to be called'; 183 | } 184 | }, 185 | { 186 | prompt: 'call the stopAction() method, with skipAnimation set to true.', 187 | verify: (lastAction) => { 188 | if (lastAction.type !== 'stopAction') return 'Expected the stopAction() method to be called'; 189 | if (lastAction.params.skipAnimation !== true) return `Expected the stopAction to be called with { skipAnimation: true }, got ${JSON.stringify(lastAction.params)}`; 190 | } 191 | }, 192 | { 193 | prompt: 'call the customAction() method for the "highlight" action', 194 | verify: (lastAction) => { 195 | if (lastAction.type !== 'customAction') return 'Expected the customAction() method to be called'; 196 | if (lastAction.params.id !== 'highlight') return `Expected the customAction() method to be called with id "highlight", got ${JSON.stringify(lastAction.params)}`; 197 | } 198 | }, 199 | { 200 | prompt: 'call the customAction() method for the "setColor" action, with payload { color: "red" }', 201 | verify: (lastAction) => { 202 | if (lastAction.type !== 'customAction') return 'Expected the customAction() method to be called'; 203 | if (lastAction.params.id !== 'setColor') return `Expected the customAction() method to be called with id "setColor", got ${JSON.stringify(lastAction.params)}`; 204 | if (typeof lastAction.params.payload !== 'object') return `Property "payload" is missing in argument, got ${JSON.stringify(lastAction.params)}`; 205 | if (lastAction.params.payload.color !== 'red') return `Expected property "payload.color" to have the value "red", got ${JSON.stringify(lastAction.params)}`; 206 | } 207 | }, 208 | ] 209 | 210 | stepIndex = 0 211 | } 212 | 213 | export default Graphic; 214 | -------------------------------------------------------------------------------- /v1/examples/l3rd-name/lib/utils/strings.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * strings: 3.12.5 3 | * https://gsap.com 4 | * 5 | * Copyright 2008-2024, GreenSock. All rights reserved. 6 | * Subject to the terms at https://gsap.com/standard-license or for 7 | * Club GSAP members, the agreement issued with that membership. 8 | * @author: Jack Doyle, jack@greensock.com 9 | */ 10 | 11 | /* eslint-disable */ 12 | var _trimExp = /(?:^\s+|\s+$)/g; 13 | export var emojiExp = /([\uD800-\uDBFF][\uDC00-\uDFFF](?:[\u200D\uFE0F][\uD800-\uDBFF][\uDC00-\uDFFF]){2,}|\uD83D\uDC69(?:\u200D(?:(?:\uD83D\uDC69\u200D)?\uD83D\uDC67|(?:\uD83D\uDC69\u200D)?\uD83D\uDC66)|\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC69\u200D(?:\uD83D\uDC69\u200D)?\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D(?:\uD83D\uDC69\u200D)?\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]\uFE0F|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC6F\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3C-\uDD3E\uDDD6-\uDDDF])\u200D[\u2640\u2642]\uFE0F|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF6\uD83C\uDDE6|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F\u200D[\u2640\u2642]|(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642])\uFE0F|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2695\u2696\u2708]|\uD83D\uDC69\u200D[\u2695\u2696\u2708]|\uD83D\uDC68(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708]))\uFE0F|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83D\uDC69\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69]))|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|\uD83D\uDC68(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC66\u200D\uD83D\uDC66|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83C[\uDFFB-\uDFFF])\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]))|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDD1-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\u200D(?:(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC67|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC66)|\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC69\uDC6E\uDC70-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD26\uDD30-\uDD39\uDD3D\uDD3E\uDDD1-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])?|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDEEB\uDEEC\uDEF4-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267B\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])\uFE0F)/; 14 | export function getText(e) { 15 | var type = e.nodeType, 16 | result = ""; 17 | 18 | if (type === 1 || type === 9 || type === 11) { 19 | if (typeof e.textContent === "string") { 20 | return e.textContent; 21 | } else { 22 | for (e = e.firstChild; e; e = e.nextSibling) { 23 | result += getText(e); 24 | } 25 | } 26 | } else if (type === 3 || type === 4) { 27 | return e.nodeValue; 28 | } 29 | 30 | return result; 31 | } 32 | export function splitInnerHTML(element, delimiter, trim, preserveSpaces) { 33 | var node = element.firstChild, 34 | result = [], 35 | s; 36 | 37 | while (node) { 38 | if (node.nodeType === 3) { 39 | s = (node.nodeValue + "").replace(/^\n+/g, ""); 40 | 41 | if (!preserveSpaces) { 42 | s = s.replace(/\s+/g, " "); 43 | } 44 | 45 | result.push.apply(result, emojiSafeSplit(s, delimiter, trim, preserveSpaces)); 46 | } else if ((node.nodeName + "").toLowerCase() === "br") { 47 | result[result.length - 1] += "
"; 48 | } else { 49 | result.push(node.outerHTML); 50 | } 51 | 52 | node = node.nextSibling; 53 | } 54 | 55 | s = result.length; 56 | 57 | while (s--) { 58 | result[s] === "&" && result.splice(s, 1, "&"); 59 | } 60 | 61 | return result; 62 | } 63 | /* 64 | //smaller kb version that only handles the simpler emoji's, which is often perfectly adequate. 65 | 66 | let _emoji = "[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2694-\u2697]|\uD83E[\uDD10-\uDD5D]|[\uD800-\uDBFF][\uDC00-\uDFFF]", 67 | _emojiExp = new RegExp(_emoji), 68 | _emojiAndCharsExp = new RegExp(_emoji + "|.", "g"), 69 | _emojiSafeSplit = (text, delimiter, trim) => { 70 | if (trim) { 71 | text = text.replace(_trimExp, ""); 72 | } 73 | return ((delimiter === "" || !delimiter) && _emojiExp.test(text)) ? text.match(_emojiAndCharsExp) : text.split(delimiter || ""); 74 | }; 75 | */ 76 | 77 | export function emojiSafeSplit(text, delimiter, trim, preserveSpaces) { 78 | text += ""; // make sure it's cast as a string. Someone may pass in a number. 79 | 80 | trim && (text = text.trim ? text.trim() : text.replace(_trimExp, "")); // IE9 and earlier compatibility 81 | 82 | if (delimiter && delimiter !== "") { 83 | return text.replace(/>/g, ">").replace(/= 0xD800 && character.charCodeAt(0) <= 0xDBFF || text.charCodeAt(i + 1) >= 0xFE00 && text.charCodeAt(i + 1) <= 0xFE0F) { 96 | //special emoji characters use 2 or 4 unicode characters that we must keep together. 97 | j = ((text.substr(i, 12).split(emojiExp) || [])[1] || "").length || 2; 98 | character = text.substr(i, j); 99 | result.emoji = 1; 100 | i += j - 1; 101 | } 102 | 103 | result.push(character === ">" ? ">" : character === "<" ? "<" : preserveSpaces && character === " " && (text.charAt(i - 1) === " " || text.charAt(i + 1) === " ") ? " " : character); 104 | } 105 | 106 | return result; 107 | } -------------------------------------------------------------------------------- /v1/specification/docs/images/step-model2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | step 1startend -------------------------------------------------------------------------------- /v1/examples/l3rd-name/graphic.mjs: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Note: 5 | * 6 | * @typedef { import('../../typescript-definitions/src/main').GraphicsAPI.Graphic } Graphic 7 | */ 8 | 9 | /** @implements { Graphic } */ 10 | class MyGraphic extends HTMLElement { 11 | constructor() { 12 | super(); 13 | this.nonRealTimeState = {}; 14 | this.displayState = {} 15 | } 16 | connectedCallback() { 17 | // Called when the element is added to the DOM 18 | // Note: Don't paint any pixels at this point, wait for load() to be called 19 | } 20 | 21 | async load(loadParams) { 22 | // 1. Load resources 23 | // 2. Setup the DOM 24 | // 3. Initialize the GSAP timeline 25 | 26 | 27 | // Load the GSAP scripts --------------------------------------------------- 28 | const importsPromises = { 29 | gsap: import(import.meta.resolve("./lib/gsap-core.js")), 30 | CSSPlugin: import(import.meta.resolve("./lib/CSSPlugin.js")), 31 | TextPlugin: import(import.meta.resolve("./lib/TextPlugin.js")), 32 | }; 33 | 34 | this.g = await importsPromises.gsap; 35 | this.g.gsap.registerPlugin((await importsPromises.CSSPlugin).CSSPlugin); 36 | this.g.gsap.registerPlugin((await importsPromises.TextPlugin).TextPlugin); 37 | 38 | // Setup our DOM ----------------------------------------------------------- 39 | const container = document.createElement("div"); 40 | this.appendChild(container); 41 | 42 | container.style.position = "absolute"; 43 | container.style.left = "calc(10% + 60px)"; 44 | container.style.bottom = "calc(10% + 30px)"; 45 | container.style.height = "32px"; 46 | 47 | container.style.padding = "6px 20px"; 48 | container.style.backgroundColor = "#f00"; 49 | container.style.color = "#fff"; 50 | container.style.fontFamily = "Roboto, sans-serif"; 51 | container.style.fontSize = "28px"; 52 | container.style.fontWeight = "bold"; 53 | container.style.zIndex = "2"; 54 | container.style.borderBottomRightRadius = "20px"; 55 | container.style.borderTopRightRadius = "20px"; 56 | container.style.borderTopLeftRadius = "20px"; 57 | 58 | const nameText = document.createElement("div"); 59 | nameText.innerText = ""; 60 | container.appendChild(nameText); 61 | 62 | const container2 = document.createElement("div"); 63 | this.appendChild(container2); 64 | 65 | container2.style.position = "absolute"; 66 | container2.style.left = "calc(10% + 60px)"; 67 | container2.style.bottom = "10%"; 68 | container2.style.height = "23px"; 69 | container2.style.padding = "5px 20px 2px 20px"; 70 | container2.style.backgroundColor = "#b00"; 71 | container2.style.color = "#fff"; 72 | container2.style.fontFamily = "Roboto, sans-serif"; 73 | container2.style.fontSize = "18px"; 74 | container2.style.fontWeight = "bold"; 75 | container2.style.zIndex = "1"; 76 | container2.style.borderBottomRightRadius = "10px"; 77 | container2.style.borderBottomLeftRadius = "10px"; 78 | 79 | const nameText2 = document.createElement("div"); 80 | nameText2.innerText = ""; 81 | container2.appendChild(nameText2); 82 | 83 | const logo = document.createElement("img"); 84 | logo.src = await this._loadImage(import.meta.resolve("./lib/ograf-logo-app.svg")) 85 | logo.style.position = "absolute"; 86 | logo.style.left = `calc(10%)` 87 | logo.style.bottom = `calc(10% + 10px)` 88 | logo.style.width = "50px"; 89 | logo.style.boxShadow = "0 0 10px rgba(0,0,0,0.5)"; 90 | logo.style.borderRadius = "5px"; 91 | this.appendChild(logo); 92 | 93 | 94 | 95 | this.elements = { 96 | container, 97 | nameText, 98 | container2, 99 | nameText2, 100 | logo 101 | }; 102 | 103 | // Initialize the GSAP timeline -------------------------------------------- 104 | this.timeline = this.g.gsap.timeline(); 105 | this._resetTimeline(); 106 | 107 | if (loadParams.renderType === "realtime") { 108 | this.timeline.play(); // not really needed, it's playing by default 109 | } else if (loadParams.renderType === "non-realtime") { 110 | this.timeline.pause(); 111 | } else throw new Error("Unsupported renderType: " + loadParams.renderType); 112 | 113 | // Load initial data: 114 | this.initialData = loadParams.data 115 | await this._doAction("updateAction", { 116 | data: this.initialData || {}, 117 | skipAnimation: true 118 | }); 119 | 120 | // When everything is loaded we can return: 121 | return; 122 | } 123 | async dispose(_params) { 124 | this.innerHTML = ""; 125 | this.g = null; 126 | } 127 | async updateAction(params) { 128 | // params.data 129 | // console.log("params", params); 130 | 131 | await this._doAction("updateAction", params); 132 | } 133 | async playAction(params) { 134 | // params.delta 135 | // params.goto 136 | // params.skipAnimation 137 | 138 | await this._doAction("playAction", params); 139 | } 140 | async stopAction(params) { 141 | // params.skipAnimation 142 | 143 | await this._doAction("stopAction", params); 144 | } 145 | async customAction(params) { 146 | // params.id 147 | // params.payload 148 | 149 | await this._doAction('customAction', params); 150 | } 151 | async _doAction(type, params) { 152 | const timeline = this.g.gsap.timeline(); 153 | 154 | // Retrieve the timeline for the action: 155 | // Add the tweens to the timeline, so that they'll animate: 156 | const actionTimeline = this._getActionAnimation(type, params); 157 | 158 | timeline.add(actionTimeline, 0); 159 | 160 | // Wait for timeline to finish animating: 161 | await timeline.then(); 162 | } 163 | async goToTime(payload) { 164 | this.nonRealTimeState.timestamp = payload.timestamp; 165 | 166 | await this._updateState(); 167 | } 168 | async setActionsSchedule(payload) { 169 | this.nonRealTimeState.schedule = payload.schedule; 170 | 171 | await this._updateState(); 172 | } 173 | async _updateState() { 174 | this._resetTimeline(); 175 | 176 | // Initial state: 177 | 178 | // Load initial data: 179 | await this._doAction("updateAction", { 180 | data: this.initialData || {}, 181 | skipAnimation: true 182 | }); 183 | 184 | for (const event of (this.nonRealTimeState.schedule || [])) { 185 | const eventTimeline = this._getActionAnimation( 186 | event.action.type, 187 | event.action.params 188 | ) 189 | 190 | this.timeline.add(eventTimeline, event.timestamp / 1000); 191 | } 192 | 193 | this.timeline.seek(this.nonRealTimeState.timestamp / 1000); 194 | } 195 | _getActionAnimation(type, params) { 196 | let tweens = [] 197 | 198 | if (type === "updateAction") { 199 | tweens = this._getUpdateAnimation(params); 200 | } else if (type === "playAction") { 201 | tweens = this._getPlayAnimation(params); 202 | } else if (type === "stopAction") { 203 | tweens = this._getStopAnimation(params); 204 | } else if (type === "customAction") { 205 | if (params.id === 'highlight') { 206 | tweens = this._getHighlightAnimation(params.payload); 207 | } else { 208 | throw new Error(`Unknown customAction id "${params.id}"`); 209 | } 210 | } else { 211 | throw new Error(`Unknown action type "${type}"`); 212 | } 213 | 214 | const actionTimeline = this.g.gsap.timeline(); 215 | 216 | for (const tween of tweens) { 217 | actionTimeline.add(tween, 0); 218 | } 219 | 220 | if (params.skipAnimation) { 221 | actionTimeline.timeScale(999999) 222 | } 223 | } 224 | _resetTimeline() { 225 | const gsap = this.g.gsap; 226 | 227 | this.displayState = {} 228 | 229 | // Clear the timeline: 230 | this.timeline.clear(); 231 | 232 | // Initial animation state: 233 | const tweens = [ 234 | gsap.set(this.elements.container, { 235 | x: -30, 236 | backgroundColor: "#f00", 237 | color: "#fff", 238 | opacity: 0, 239 | }), 240 | gsap.set(this.elements.nameText, { 241 | text: "", 242 | }), 243 | gsap.set(this.elements.container2, { 244 | y: -20, 245 | opacity: 0, 246 | }), 247 | gsap.set(this.elements.nameText2, { 248 | text: "", 249 | }), 250 | gsap.set(this.elements.logo, { 251 | scale: 0.5, 252 | opacity: 0 253 | }), 254 | ]; 255 | 256 | // Add Tweens to the beginning of the timeline: 257 | for (const tween of tweens) { 258 | this.timeline.add(tween, 0); 259 | } 260 | } 261 | 262 | // -------------------- Actions -------------------- 263 | _getUpdateAnimation(params) { 264 | const gsap = this.g.gsap; 265 | 266 | 267 | this.displayState.data = params.data || {} 268 | 269 | 270 | 271 | const showTitle = this.displayState.data.title 272 | const isPlaying = this.displayState.isPlaying 273 | 274 | return [ 275 | gsap.to(this.elements.nameText, { 276 | duration: 0.4, 277 | text: this.displayState.data.name, 278 | }), 279 | gsap.to(this.elements.nameText2, { 280 | duration: 0.4, 281 | text: this.displayState.data.title, 282 | }), 283 | ( 284 | isPlaying && 285 | gsap.to(this.elements.container2, { 286 | duration: 0.3, 287 | y: 0, 288 | opacity: showTitle ? 1 : 0, 289 | ease: "power2.out", 290 | }) 291 | ), 292 | ( 293 | isPlaying && 294 | gsap.to(this.elements.container, { 295 | duration: 0.3, 296 | borderBottomLeftRadius: showTitle ? "0" : "20px", 297 | ease: "power2.out", 298 | }) 299 | ) 300 | ]; 301 | } 302 | _getPlayAnimation(params) { 303 | const gsap = this.g.gsap; 304 | 305 | this.displayState.isPlaying = true 306 | 307 | const showTitle = Boolean(this.displayState.data.title) 308 | 309 | 310 | return [ 311 | gsap.to(this.elements.container, { 312 | duration: 0.8, 313 | x: 0, 314 | opacity: 1, 315 | borderBottomLeftRadius: showTitle ? "0" : "20px", 316 | ease: "power2.out", 317 | }), 318 | 319 | gsap.to(this.elements.container2, { 320 | delay: 0.3, 321 | duration: 0.8, 322 | y: 0, 323 | opacity: showTitle ? 1 : 0, 324 | ease: "power2.out", 325 | }), 326 | gsap.to(this.elements.nameText2, { 327 | delay: 0.7, 328 | duration: 0.8, 329 | }), 330 | gsap.to(this.elements.logo, { 331 | duration: 0.5, 332 | opacity: 1, 333 | }), 334 | gsap.to(this.elements.logo, { 335 | duration: 1.5, 336 | rotation: 360, 337 | scale: 1 338 | }), 339 | ]; 340 | } 341 | _getStopAnimation(params) { 342 | const gsap = this.g.gsap; 343 | 344 | this.displayState.isPlaying = false 345 | 346 | return [ 347 | gsap.to(this.elements.container, { 348 | delay: 0.3, 349 | duration: 0.8, 350 | x: -30, 351 | opacity: 0, 352 | ease: "power2.in", 353 | }), 354 | gsap.to(this.elements.container2, { 355 | duration: 0.5, 356 | y: -20, 357 | opacity: 0, 358 | ease: "power2.in", 359 | }), 360 | gsap.to(this.elements.logo, { 361 | duration: 0.8, 362 | rotation: 180, 363 | scale: 0.8, 364 | opacity: 0 365 | }), 366 | ]; 367 | } 368 | _getHighlightAnimation(params) { 369 | const gsap = this.g.gsap; 370 | 371 | return [ 372 | gsap.to(this.elements.container, { 373 | duration: 0.8, 374 | backgroundColor: "#ff0", 375 | color: "#000", 376 | 377 | ease: "power2.in", 378 | }), 379 | gsap 380 | .to(this.elements.container, { 381 | delay: 2, 382 | duration: 0.8, 383 | backgroundColor: "#f00", 384 | color: "#fff", 385 | 386 | ease: "power2.in", 387 | }), 388 | gsap.to(this.elements.logo, { 389 | duration: 1, 390 | scale: 1.5, 391 | ease: "bounce.out", 392 | }), 393 | gsap.to(this.elements.logo, { 394 | delay: 1, 395 | duration: 1, 396 | scale: 1, 397 | }), 398 | 399 | ]; 400 | } 401 | _loadImage(url) { 402 | return new Promise((resolve, reject) => { 403 | const newImg = new Image; 404 | newImg.onload = function() { 405 | resolve(this.src) 406 | } 407 | newImg.onerror = reject; 408 | newImg.src = url 409 | }) 410 | } 411 | } 412 | 413 | export default MyGraphic; 414 | 415 | // Note: The renderer will render the component 416 | -------------------------------------------------------------------------------- /v1/specification/docs/images/step-model3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | step 1step 2startend -------------------------------------------------------------------------------- /v1/specification/docs/images/components.svg: -------------------------------------------------------------------------------- 1 | ControllerServerRendererGraphic InstanceGraphicGraphicGraphicEditor -------------------------------------------------------------------------------- /v1/typescript-definitions/src/generated/graphics-manifest.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * This file was automatically generated by json-schema-to-typescript. 4 | * DO NOT MODIFY IT BY HAND! Instead, modify the source JSON Schema file, 5 | * and run 'npm run generate-types' to regenerate this file. 6 | */ 7 | 8 | export type HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibObjectJson = 9 | CoreAndValidationSpecificationsMetaSchema & 10 | HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibGddTypesJson & 11 | HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibBasicTypesJson & { 12 | type: "boolean" | "string" | "number" | "integer" | "array" | "object"; 13 | gddType?: string; 14 | gddOptions?: { 15 | [k: string]: unknown; 16 | }; 17 | [k: string]: unknown; 18 | }; 19 | export type CoreAndValidationSpecificationsMetaSchema = CoreVocabularyMetaSchema & 20 | ApplicatorVocabularyMetaSchema & 21 | UnevaluatedApplicatorVocabularyMetaSchema & 22 | ValidationVocabularyMetaSchema & 23 | MetaDataVocabularyMetaSchema & 24 | FormatVocabularyMetaSchemaForAnnotationResults & 25 | ContentVocabularyMetaSchema & { 26 | /** 27 | * @deprecated 28 | */ 29 | definitions?: { 30 | [k: string]: { 31 | [k: string]: unknown; 32 | }; 33 | }; 34 | /** 35 | * @deprecated 36 | */ 37 | dependencies?: { 38 | [k: string]: 39 | | { 40 | [k: string]: unknown; 41 | } 42 | | string[]; 43 | }; 44 | /** 45 | * @deprecated 46 | */ 47 | $recursiveAnchor?: string; 48 | /** 49 | * @deprecated 50 | */ 51 | $recursiveRef?: string; 52 | [k: string]: unknown; 53 | } & ( 54 | | { 55 | /** 56 | * @deprecated 57 | */ 58 | definitions?: { 59 | [k: string]: { 60 | [k: string]: unknown; 61 | }; 62 | }; 63 | /** 64 | * @deprecated 65 | */ 66 | dependencies?: { 67 | [k: string]: 68 | | { 69 | [k: string]: unknown; 70 | } 71 | | string[]; 72 | }; 73 | /** 74 | * @deprecated 75 | */ 76 | $recursiveAnchor?: string; 77 | /** 78 | * @deprecated 79 | */ 80 | $recursiveRef?: string; 81 | [k: string]: unknown; 82 | } 83 | | boolean 84 | ); 85 | export type CoreVocabularyMetaSchema = { 86 | $id?: string; 87 | $schema?: string; 88 | $ref?: string; 89 | $anchor?: string; 90 | $dynamicRef?: string; 91 | $dynamicAnchor?: string; 92 | $vocabulary?: { 93 | [k: string]: boolean; 94 | }; 95 | $comment?: string; 96 | $defs?: { 97 | [k: string]: { 98 | [k: string]: unknown; 99 | }; 100 | }; 101 | [k: string]: unknown; 102 | } & ( 103 | | { 104 | $id?: string; 105 | $schema?: string; 106 | $ref?: string; 107 | $anchor?: string; 108 | $dynamicRef?: string; 109 | $dynamicAnchor?: string; 110 | $vocabulary?: { 111 | [k: string]: boolean; 112 | }; 113 | $comment?: string; 114 | $defs?: { 115 | [k: string]: { 116 | [k: string]: unknown; 117 | }; 118 | }; 119 | [k: string]: unknown; 120 | } 121 | | boolean 122 | ); 123 | export type ApplicatorVocabularyMetaSchema = { 124 | /** 125 | * @minItems 1 126 | */ 127 | prefixItems?: [ 128 | { 129 | [k: string]: unknown; 130 | }, 131 | ...{ 132 | [k: string]: unknown; 133 | }[] 134 | ]; 135 | items?: { 136 | [k: string]: unknown; 137 | }; 138 | contains?: { 139 | [k: string]: unknown; 140 | }; 141 | additionalProperties?: { 142 | [k: string]: unknown; 143 | }; 144 | properties?: { 145 | [k: string]: { 146 | [k: string]: unknown; 147 | }; 148 | }; 149 | patternProperties?: { 150 | [k: string]: { 151 | [k: string]: unknown; 152 | }; 153 | }; 154 | dependentSchemas?: { 155 | [k: string]: { 156 | [k: string]: unknown; 157 | }; 158 | }; 159 | propertyNames?: { 160 | [k: string]: unknown; 161 | }; 162 | if?: { 163 | [k: string]: unknown; 164 | }; 165 | then?: { 166 | [k: string]: unknown; 167 | }; 168 | else?: { 169 | [k: string]: unknown; 170 | }; 171 | /** 172 | * @minItems 1 173 | */ 174 | allOf?: [ 175 | { 176 | [k: string]: unknown; 177 | }, 178 | ...{ 179 | [k: string]: unknown; 180 | }[] 181 | ]; 182 | /** 183 | * @minItems 1 184 | */ 185 | anyOf?: [ 186 | { 187 | [k: string]: unknown; 188 | }, 189 | ...{ 190 | [k: string]: unknown; 191 | }[] 192 | ]; 193 | /** 194 | * @minItems 1 195 | */ 196 | oneOf?: [ 197 | { 198 | [k: string]: unknown; 199 | }, 200 | ...{ 201 | [k: string]: unknown; 202 | }[] 203 | ]; 204 | not?: { 205 | [k: string]: unknown; 206 | }; 207 | [k: string]: unknown; 208 | } & ( 209 | | { 210 | /** 211 | * @minItems 1 212 | */ 213 | prefixItems?: [ 214 | { 215 | [k: string]: unknown; 216 | }, 217 | ...{ 218 | [k: string]: unknown; 219 | }[] 220 | ]; 221 | items?: { 222 | [k: string]: unknown; 223 | }; 224 | contains?: { 225 | [k: string]: unknown; 226 | }; 227 | additionalProperties?: { 228 | [k: string]: unknown; 229 | }; 230 | properties?: { 231 | [k: string]: { 232 | [k: string]: unknown; 233 | }; 234 | }; 235 | patternProperties?: { 236 | [k: string]: { 237 | [k: string]: unknown; 238 | }; 239 | }; 240 | dependentSchemas?: { 241 | [k: string]: { 242 | [k: string]: unknown; 243 | }; 244 | }; 245 | propertyNames?: { 246 | [k: string]: unknown; 247 | }; 248 | if?: { 249 | [k: string]: unknown; 250 | }; 251 | then?: { 252 | [k: string]: unknown; 253 | }; 254 | else?: { 255 | [k: string]: unknown; 256 | }; 257 | /** 258 | * @minItems 1 259 | */ 260 | allOf?: [ 261 | { 262 | [k: string]: unknown; 263 | }, 264 | ...{ 265 | [k: string]: unknown; 266 | }[] 267 | ]; 268 | /** 269 | * @minItems 1 270 | */ 271 | anyOf?: [ 272 | { 273 | [k: string]: unknown; 274 | }, 275 | ...{ 276 | [k: string]: unknown; 277 | }[] 278 | ]; 279 | /** 280 | * @minItems 1 281 | */ 282 | oneOf?: [ 283 | { 284 | [k: string]: unknown; 285 | }, 286 | ...{ 287 | [k: string]: unknown; 288 | }[] 289 | ]; 290 | not?: { 291 | [k: string]: unknown; 292 | }; 293 | [k: string]: unknown; 294 | } 295 | | boolean 296 | ); 297 | export type UnevaluatedApplicatorVocabularyMetaSchema = { 298 | unevaluatedItems?: { 299 | [k: string]: unknown; 300 | }; 301 | unevaluatedProperties?: { 302 | [k: string]: unknown; 303 | }; 304 | [k: string]: unknown; 305 | } & ( 306 | | { 307 | unevaluatedItems?: { 308 | [k: string]: unknown; 309 | }; 310 | unevaluatedProperties?: { 311 | [k: string]: unknown; 312 | }; 313 | [k: string]: unknown; 314 | } 315 | | boolean 316 | ); 317 | export type ValidationVocabularyMetaSchema = { 318 | type?: 319 | | ("array" | "boolean" | "integer" | "null" | "number" | "object" | "string") 320 | | [ 321 | "array" | "boolean" | "integer" | "null" | "number" | "object" | "string", 322 | ...("array" | "boolean" | "integer" | "null" | "number" | "object" | "string")[] 323 | ]; 324 | const?: unknown; 325 | enum?: unknown[]; 326 | multipleOf?: number; 327 | maximum?: number; 328 | exclusiveMaximum?: number; 329 | minimum?: number; 330 | exclusiveMinimum?: number; 331 | maxLength?: number; 332 | minLength?: number; 333 | pattern?: string; 334 | maxItems?: number; 335 | minItems?: number; 336 | uniqueItems?: boolean; 337 | maxContains?: number; 338 | minContains?: number; 339 | maxProperties?: number; 340 | minProperties?: number; 341 | required?: string[]; 342 | dependentRequired?: { 343 | [k: string]: string[]; 344 | }; 345 | [k: string]: unknown; 346 | } & ( 347 | | { 348 | type?: 349 | | ("array" | "boolean" | "integer" | "null" | "number" | "object" | "string") 350 | | [ 351 | "array" | "boolean" | "integer" | "null" | "number" | "object" | "string", 352 | ...("array" | "boolean" | "integer" | "null" | "number" | "object" | "string")[] 353 | ]; 354 | const?: unknown; 355 | enum?: unknown[]; 356 | multipleOf?: number; 357 | maximum?: number; 358 | exclusiveMaximum?: number; 359 | minimum?: number; 360 | exclusiveMinimum?: number; 361 | maxLength?: number; 362 | minLength?: number; 363 | pattern?: string; 364 | maxItems?: number; 365 | minItems?: number; 366 | uniqueItems?: boolean; 367 | maxContains?: number; 368 | minContains?: number; 369 | maxProperties?: number; 370 | minProperties?: number; 371 | required?: string[]; 372 | dependentRequired?: { 373 | [k: string]: string[]; 374 | }; 375 | [k: string]: unknown; 376 | } 377 | | boolean 378 | ); 379 | export type MetaDataVocabularyMetaSchema = { 380 | title?: string; 381 | description?: string; 382 | default?: unknown; 383 | deprecated?: boolean; 384 | readOnly?: boolean; 385 | writeOnly?: boolean; 386 | examples?: unknown[]; 387 | [k: string]: unknown; 388 | } & ( 389 | | { 390 | title?: string; 391 | description?: string; 392 | default?: unknown; 393 | deprecated?: boolean; 394 | readOnly?: boolean; 395 | writeOnly?: boolean; 396 | examples?: unknown[]; 397 | [k: string]: unknown; 398 | } 399 | | boolean 400 | ); 401 | export type FormatVocabularyMetaSchemaForAnnotationResults = { 402 | format?: string; 403 | [k: string]: unknown; 404 | } & ( 405 | | { 406 | format?: string; 407 | [k: string]: unknown; 408 | } 409 | | boolean 410 | ); 411 | export type ContentVocabularyMetaSchema = { 412 | contentEncoding?: string; 413 | contentMediaType?: string; 414 | contentSchema?: { 415 | [k: string]: unknown; 416 | }; 417 | [k: string]: unknown; 418 | } & ( 419 | | { 420 | contentEncoding?: string; 421 | contentMediaType?: string; 422 | contentSchema?: { 423 | [k: string]: unknown; 424 | }; 425 | [k: string]: unknown; 426 | } 427 | | boolean 428 | ); 429 | export type HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibGddTypesJson = { 430 | [k: string]: unknown; 431 | }; 432 | export type HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibBasicTypesJson = { 433 | [k: string]: unknown; 434 | }; 435 | /** 436 | * The schema is used by a Graphic to define the data parameters of the 'update' method. 437 | */ 438 | export type HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibObjectJson1 = 439 | CoreAndValidationSpecificationsMetaSchema & 440 | HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibGddTypesJson & 441 | HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibBasicTypesJson & { 442 | type: "boolean" | "string" | "number" | "integer" | "array" | "object"; 443 | gddType?: string; 444 | gddOptions?: { 445 | [k: string]: unknown; 446 | }; 447 | [k: string]: unknown; 448 | }; 449 | 450 | export interface HttpsOgrafEbuIoV1SpecificationJsonSchemasGraphicsSchemaJson { 451 | /** 452 | * Reference to the JSON-schema for this manifest 453 | */ 454 | $schema: "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json"; 455 | /** 456 | * The id of the Graphic uniquely identifies it. It is recommended to use a reverse domain name notation. For example: com.my-company.my-lowerthird. 457 | */ 458 | id: string; 459 | /** 460 | * The version of the Graphic. The version SHOULD be alphabetically sortable. Examples: ['0', '1', '2'], ['1.0', '1.1', '1.2'], ['2024-07-01_final', '2024-07-01_final_final2'] 461 | */ 462 | version?: string; 463 | /** 464 | * The main entry point, ie the path to the main javascript file of the Graphic. 465 | */ 466 | main: string; 467 | /** 468 | * Name of the Graphic 469 | */ 470 | name: string; 471 | /** 472 | * (optional) A longer description of the Graphic 473 | */ 474 | description?: string; 475 | /** 476 | * (optional) About the author 477 | */ 478 | author?: { 479 | /** 480 | * Name of the author 481 | */ 482 | name: string; 483 | /** 484 | * (optional) Email of the author 485 | */ 486 | email?: string; 487 | /** 488 | * (optional) URL of the author 489 | */ 490 | url?: string; 491 | /** 492 | * This interface was referenced by `undefined`'s JSON-Schema definition 493 | * via the `patternProperty` "^v_.*". 494 | */ 495 | [k: `v_${string}`]: unknown; 496 | }; 497 | /** 498 | * Custom Actions that can be invoked on the Graphic. 499 | */ 500 | customActions?: HttpsOgrafEbuIoV1SpecificationJsonSchemasLibActionJson[]; 501 | /** 502 | * Indicates if the Graphic supports real-time rendering 503 | */ 504 | supportsRealTime: boolean; 505 | /** 506 | * Indicates if the Graphic supports non-real-time rendering. Note: If true, the Graphic must implement the 'goToTime()' and the 'setActionsSchedule()' methods. 507 | */ 508 | supportsNonRealTime: boolean; 509 | /** 510 | * The number of steps a Graphic consists of. 511 | * If the Graphic is simply triggered by a play, then a stop, this is considered a stepCount of 1 512 | * (which is the default behavior if left undefined). 513 | * A value of -1 indicates that a Graphic as a dynamic/unknown number of steps. 514 | */ 515 | stepCount?: number; 516 | schema?: HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibObjectJson1; 517 | /** 518 | * A list of requirements that this Graphic has for the rendering environment. At least one of the requirements must be met for the graphic to be expected to work. 519 | */ 520 | renderRequirements?: { 521 | /** 522 | * If set, specifies requirements for the resolution of the Renderer. 523 | */ 524 | resolution?: { 525 | width?: HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsNumberJson; 526 | height?: HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsNumberJson; 527 | [k: string]: unknown; 528 | }; 529 | frameRate?: HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsNumberJson1; 530 | accessToPublicInternet?: HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsBooleanJson; 531 | /** 532 | * This interface was referenced by `undefined`'s JSON-Schema definition 533 | * via the `patternProperty` "^v_.*". 534 | */ 535 | [k: `v_${string}`]: unknown; 536 | }[]; 537 | /** 538 | * This interface was referenced by `HttpsOgrafEbuIoV1SpecificationJsonSchemasGraphicsSchemaJson`'s JSON-Schema definition 539 | * via the `patternProperty` "^v_.*". 540 | */ 541 | [k: `v_${string}`]: unknown; 542 | } 543 | export interface HttpsOgrafEbuIoV1SpecificationJsonSchemasLibActionJson { 544 | /** 545 | * The identity of the action. The id must be unique within the graphic. 546 | */ 547 | id: string; 548 | /** 549 | * The name of the action. This is displayed to the user. 550 | */ 551 | name: string; 552 | /** 553 | * A longer description of the action. This is displayed to the user. 554 | */ 555 | description?: string; 556 | /** 557 | * The schema of the action. This is used to validate the action parameters as well as auto-generate a GUI for the action. If the action does not require any parameters, set this to null. 558 | */ 559 | schema?: HttpsSuperflytvGithubIoGraphicsDataDefinitionGddMetaSchemaV1LibObjectJson | null; 560 | /** 561 | * This interface was referenced by `HttpsOgrafEbuIoV1SpecificationJsonSchemasLibActionJson`'s JSON-Schema definition 562 | * via the `patternProperty` "^v_.*". 563 | */ 564 | [k: `v_${string}`]: unknown; 565 | } 566 | /** 567 | * The number constraint is used to specify a constraint for a numerical property. (Inspired by https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraindouble) 568 | */ 569 | export interface HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsNumberJson { 570 | /** 571 | * A number specifying the largest permissible value of the property it describes. If the value cannot remain equal to or less than this value, matching will fail. 572 | */ 573 | max?: number; 574 | /** 575 | * A number specifying the smallest permissible value of the property it describes. If the value cannot remain equal to or greater than this value, matching will fail. 576 | */ 577 | min?: number; 578 | /** 579 | * A number specifying a specific, required, value the property must have to be considered acceptable. 580 | */ 581 | exact?: number; 582 | /** 583 | * A number specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match. 584 | */ 585 | ideal?: number; 586 | /** 587 | * This interface was referenced by `HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsNumberJson`'s JSON-Schema definition 588 | * via the `patternProperty` "^v_.*". 589 | * 590 | * This interface was referenced by `HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsNumberJson1`'s JSON-Schema definition 591 | * via the `patternProperty` "^v_.*". 592 | */ 593 | [k: `v_${string}`]: unknown; 594 | } 595 | /** 596 | * The number constraint is used to specify a constraint for a numerical property. (Inspired by https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#constraindouble) 597 | */ 598 | export interface HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsNumberJson1 { 599 | /** 600 | * A number specifying the largest permissible value of the property it describes. If the value cannot remain equal to or less than this value, matching will fail. 601 | */ 602 | max?: number; 603 | /** 604 | * A number specifying the smallest permissible value of the property it describes. If the value cannot remain equal to or greater than this value, matching will fail. 605 | */ 606 | min?: number; 607 | /** 608 | * A number specifying a specific, required, value the property must have to be considered acceptable. 609 | */ 610 | exact?: number; 611 | /** 612 | * A number specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match. 613 | */ 614 | ideal?: number; 615 | /** 616 | * This interface was referenced by `HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsNumberJson`'s JSON-Schema definition 617 | * via the `patternProperty` "^v_.*". 618 | * 619 | * This interface was referenced by `HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsNumberJson1`'s JSON-Schema definition 620 | * via the `patternProperty` "^v_.*". 621 | */ 622 | [k: `v_${string}`]: unknown; 623 | } 624 | /** 625 | * If set, specifies requirement on whether the renderer has access to the public internet or not. 626 | */ 627 | export interface HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsBooleanJson { 628 | /** 629 | * A boolean specifying a specific, required, value the property must have to be considered acceptable. 630 | */ 631 | exact?: boolean; 632 | /** 633 | * A boolean specifying an ideal value for the property. If possible, this value will be used, but if it's not possible, the user agent will use the closest possible match. 634 | */ 635 | ideal?: boolean; 636 | /** 637 | * This interface was referenced by `HttpsOgrafEbuIoV1SpecificationJsonSchemasLibConstraintsBooleanJson`'s JSON-Schema definition 638 | * via the `patternProperty` "^v_.*". 639 | */ 640 | [k: `v_${string}`]: unknown; 641 | } 642 | --------------------------------------------------------------------------------